一、背景描述
最开始使用AS5600作为电机的位置传感器,后面发现运行一段时间会挂,不知道是不是iic线太长了,懒得去排查,也懒得重新焊线。而AS5047是14位的,相比较之下多了两位精度,而且通信使用SPI,速率更快。
二、AS5047简介
AS5047P是一款高分辨率的旋转位置传感器,适用于高达28,000转/分钟的高速角度测量,覆盖整个360度范围。这款新型位置传感器配备了革命性的集成动态角度误差补偿(DAEC™),几乎无延迟,并且设计坚固,能够有效抑制任何均匀外部杂散磁场的影响。通过标准的4路SPI串行接口,主机微控制器可以从AS5047P读取14位绝对角度位置数据,并无需专用编程器即可设置非易失性参数。增量运动通过一组ABI信号指示,最大分辨率为每转4000步(十进制模式)或4096步(二进制模式)。ABI信号的分辨率可编程,可以降低到每转100步或每转25脉冲摘录于AS5047数据手册。
三、通信接口
AS5047的SPI通信时序图
从图中我们可以知道SPI的通信模式为低空闲第二边沿采样。
读寄存器流程
写入数据帧组成
从图中可以看出,写入的一帧数据中,低14位为有效数据,第15位是空闲(恒为零),最高位是奇偶校验位PARD,必须从16位数据中计算得出。在SPI写入操作中,写命令帧之后紧接着是MOSI上的写数据帧。写数据帧包含了命令帧中地址所指寄存器的新内容。
当新内容通过MOSI传输时,旧内容则通过MISO发送。在下一个MOSI上的命令中,寄存器的实际内容将通过MISO传输。
编写驱动程序
基于以上内容,我们可以开始进行MM32的SPI底层驱动开发。
打开MM32例程包里的[SPI_Master_Polling]
修改例程代码
- 根据自己选择的SPI和对应SPI所在的引脚修改GPIO和外设时钟。
- 根据时序图,配置
SPI_CPOL
和SPI_CPHA
分别为SPI_CPOL_Low
和SPI_CPHA_2Edge
。 - 时钟分频可以根据时序图中的
tsck
进行调整,频率不可超过1/tsck
。 - MM32单片机中,SPI的位宽只有8位和32位,而SPI的寄存器的位宽为16位,因此选择8位的位宽。
最后记得使能SPI。
SPI初始化的代码如下:void SPI_Configure(void) { SPI_InitTypeDef SPI_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); SPI_StructInit(&SPI_InitStruct); SPI_InitStruct.SPI_Mode = SPI_Mode_Master; SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; SPI_InitStruct.SPI_DataWidth = 8; SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStruct); SPI_BiDirectionalLineConfig(SPI1, SPI_Enable_RX); SPI_BiDirectionalLineConfig(SPI1, SPI_Enable_TX); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_5); /* PA5 SPI_SCK */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_5); /* PA6 SPI_MISO */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_5); /* PA7 SPI_MOSI */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_5); /* PA4 SPI_NSS */ GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_6; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOB, &GPIO_InitStruct); SPI_Cmd(SPI1, ENABLE); }
编写SPI读写AS5047寄存器程序
根据读寄存器流程可知,需要写入两次16位的数据,在第二次写入的时候,可以读取第一次写入的寄存器数据。
此外,还需要根据写入的地址计算校验位。方法如下:
uint16_t get_parity(uint16_t data)
{
uint16_t i, bits;
data = data & 0x7FFF;
bits = 0;
for(i = 0; i < 16; i++)
{
bits += (data & 0x0001);
data = data >> 1;
}
bits = bits & 0x0001;
return bits;
}
因为我们配置了8位的SPI通信,因此需要手动实现一个16位的SPI接口函数:
uint16_t spi_read16bit_reg(uint16_t addr)
{
uint8_t reg_data[2];
SPI_SendData(SPI1, addr >> 8);
while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_TXEPT));
while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_RXAVL));
reg_data[0] = SPI_ReceiveData(SPI1);
SPI_SendData(SPI1, addr & 0xff);
while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_TXEPT));
while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_RXAVL));
reg_data[1] = SPI_ReceiveData(SPI1);
return (reg_data[0] << 8) | reg_data[1];
}
再根据寄存器读取的流程,编写AS5047的寄存器读取函数如下:
uint16_t as5047_get_addr(uint16_t addr)
{
uint16_t enc = 0;
addr = addr | (0x4000);//计算校验位
if(get_parity(addr))
{
addr = addr | (0x8000);//计算校验位
}
SPI1_NSS_L();
spi_read16bit_reg(addr);
SPI1_NSS_H();
SPI1_NSS_L();
enc = spi_read16bit_reg(addr);
SPI1_NSS_H();
return enc & 0x3FFF;
}
uint16_t as5047_get_raw(void)
{
return as5047_get_addr(AS5047D_CMD_ANGLECOM);
}
四、测试例程
最后提供主函数测试代码:
int main(void)
{
PLATFORM_Init();
SPI_Configure();
while (1)
{
if(PLATFORM_Get_Ticks() - tick_10 >= 20)
{
tick_10 = PLATFORM_Get_Ticks();
printf("motor:%d\n", raw_angle);
}
if(PLATFORM_Get_Ticks() - ticks >= 500)
{
ticks = PLATFORM_Get_Ticks();
PLATFORM_LED_Toggle(LED1);
}
}
}
电机均匀旋转的编码器曲线图:
运行实物:
五、总结
以上便是使用SPI读写AS5047磁编码器的主要内容。完整项目代码等我完成电机的电流闭环之后再分享出来。