唐_cWBGTI · 1 天前

【灵动Mini-F5375-OB开发板测评】使用SPI通信读取AS5047磁编码器

一、背景描述

最开始使用AS5600作为电机的位置传感器,后面发现运行一段时间会挂,不知道是不是iic线太长了,懒得去排查,也懒得重新焊线。而AS5047是14位的,相比较之下多了两位精度,而且通信使用SPI,速率更快。

二、AS5047简介

AS5047P是一款高分辨率的旋转位置传感器,适用于高达28,000转/分钟的高速角度测量,覆盖整个360度范围。这款新型位置传感器配备了革命性的集成动态角度误差补偿(DAEC™),几乎无延迟,并且设计坚固,能够有效抑制任何均匀外部杂散磁场的影响。通过标准的4路SPI串行接口,主机微控制器可以从AS5047P读取14位绝对角度位置数据,并无需专用编程器即可设置非易失性参数。增量运动通过一组ABI信号指示,最大分辨率为每转4000步(十进制模式)或4096步(二进制模式)。ABI信号的分辨率可编程,可以降低到每转100步或每转25脉冲摘录于AS5047数据手册

三、通信接口

AS5047的SPI通信时序图

image.png
从图中我们可以知道SPI的通信模式为低空闲第二边沿采样。

读寄存器流程

image.png

写入数据帧组成

image.png
从图中可以看出,写入的一帧数据中,低14位为有效数据,第15位是空闲(恒为零),最高位是奇偶校验位PARD,必须从16位数据中计算得出。在SPI写入操作中,写命令帧之后紧接着是MOSI上的写数据帧。写数据帧包含了命令帧中地址所指寄存器的新内容。
当新内容通过MOSI传输时,旧内容则通过MISO发送。在下一个MOSI上的命令中,寄存器的实际内容将通过MISO传输。

编写驱动程序

基于以上内容,我们可以开始进行MM32的SPI底层驱动开发。

打开MM32例程包里的[SPI_Master_Polling]

修改例程代码

  1. 根据自己选择的SPI和对应SPI所在的引脚修改GPIO和外设时钟。
  2. 根据时序图,配置SPI_CPOLSPI_CPHA分别为SPI_CPOL_LowSPI_CPHA_2Edge
  3. 时钟分频可以根据时序图中的tsck进行调整,频率不可超过1/tsck
  4. MM32单片机中,SPI的位宽只有8位和32位,而SPI的寄存器的位宽为16位,因此选择8位的位宽。
  5. 最后记得使能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);
        }    
    }
}

电机均匀旋转的编码器曲线图:
image.png
运行实物:
65c69e30792f149fd9f8f5e40aa0b847.jpg

五、总结

以上便是使用SPI读写AS5047磁编码器的主要内容。完整项目代码等我完成电机的电流闭环之后再分享出来。

推荐阅读
关注数
1
文章数
2
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息