AS5045是一款高精度磁旋转编码器,采用非接触式设计,通过检测磁场变化来测量旋转角度。我在多个工业项目中用过这款传感器,它的稳定性和抗干扰能力确实令人印象深刻。与光电编码器相比,磁编码器更适合存在油污、灰尘的恶劣环境。
这款编码器输出14位分辨率(16384个位置/转),实测角度误差小于0.5度。它通过RS485接口输出数据,通信距离可达1200米(需适当降低波特率)。硬件连接非常简单,只需要4根线:
实际布线时有个容易踩的坑:如果通信距离超过50米,务必在总线两端各加一个120Ω终端电阻。我有次调试时数据总是不稳定,后来发现就是因为忘了接终端电阻。编码器本身没有内置终端电阻,需要外接,这点要特别注意。
STM32的UART是TTL电平,直接连接RS485设备会烧毁芯片!必须使用电平转换模块。我常用MAX485芯片搭建转换电路,成本不到5元。关键引脚连接如下:
| MAX485引脚 | STM32连接 | 作用说明 |
|---|---|---|
| RO | USART_RX | 接收数据输出 |
| DI | USART_TX | 发送数据输入 |
| RE/DE | GPIO(如PC7) | 收发控制,低电平接收 |
| A/B | AS5045的A/B线 | RS485差分信号线 |
硬件设计时有个实用技巧:将RE和DE引脚短路后接同一个GPIO,这样只需一个IO口就能控制收发状态。原理图设计建议加TVS二极管保护RS485线路,工业现场经常会有浪涌电压。
AS5045支持1200-57600波特率,我的经验是:
电缆建议用带屏蔽的双绞线,比如Belden 3105A。曾有个项目用普通网线,电机一启动通信就丢包,换成屏蔽线后问题立刻解决。接线时注意A/B线不能接反,否则通信完全不通。
Modbus-RTU的一帧数据由以下几部分组成:
以读取角度值为例,主机发送:
code复制01 03 00 01 00 02 95 CB
从机回复:
code复制01 03 04 00 00 00 00 FA 33
Modbus要求使用CRC-16/MODBUS算法。STM32的CRC硬件单元默认是CRC-32,需要特殊配置:
c复制// CRC16-MODBUS初始化
void CRC16_Init(void)
{
__HAL_RCC_CRC_CLK_ENABLE();
CRC->POL = 0x8005; // 多项式
CRC->CR |= CRC_CR_RESET;
CRC->CR &= ~CRC_CR_POLYSIZE; // 16位多项式
}
计算CRC的实用函数:
c复制uint16_t Calc_CRC16(uint8_t *buf, uint16_t len)
{
CRC->CR |= CRC_CR_RESET;
for(uint16_t i=0; i<len; i++)
{
CRC->DR = buf[i];
}
return (uint16_t)(CRC->DR);
}
我在实际项目中发现,如果CRC计算错误,AS5045会直接丢弃请求而不回复。调试时可以先屏蔽CRC校验,等通信正常后再启用。
USART和GPIO初始化:
c复制void RS485_Init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能时钟
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_USART2_CLK_ENABLE();
// 配置USART2_TX(PD5)和USART2_RX(PD6)
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
// 配置收发控制引脚PD7
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_RESET); // 默认接收模式
// USART参数配置
huart2.Instance = USART2;
huart2.Init.BaudRate = baudrate;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart2);
// 使能接收中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
HAL_NVIC_SetPriority(USART2_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}
发送函数要注意控制收发状态:
c复制void RS485_Send(uint8_t *buf, uint16_t len)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_SET); // 切到发送模式
HAL_UART_Transmit(&huart2, buf, len, 100);
while(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC)==RESET); // 等待发送完成
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, GPIO_PIN_RESET); // 切回接收模式
}
接收中断处理:
c复制uint8_t RxBuf[32];
uint16_t RxCnt = 0;
void USART2_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
{
uint8_t byte = (uint8_t)(huart2.Instance->DR & 0xFF);
if(RxCnt < sizeof(RxBuf))
{
RxBuf[RxCnt++] = byte;
}
}
}
建议用定时器触发数据请求:
c复制// 定时器3初始化(500ms间隔)
void TIM3_Init(void)
{
htim3.Instance = TIM3;
htim3.Init.Prescaler = 7200-1; // 72MHz/7200=10kHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 5000-1; // 10kHz下5000=500ms
HAL_TIM_Base_Init(&htim3);
HAL_TIM_Base_Start_IT(&htim3);
}
// 定时器中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3)
{
uint8_t cmd[] = {0x01, 0x03, 0x00, 0x01, 0x00, 0x02, 0x95, 0xCB};
RS485_Send(cmd, sizeof(cmd));
}
}
角度值解析方法:
c复制float Get_Angle(void)
{
if(RxCnt >= 9 && RxBuf[0]==0x01 && RxBuf[1]==0x03)
{
uint16_t crc = Calc_CRC16(RxBuf, RxCnt-2);
if(crc == (RxBuf[RxCnt-1]<<8 | RxBuf[RxCnt-2]))
{
uint16_t angle_raw = (RxBuf[3]<<8) | RxBuf[4];
return (float)angle_raw * 360.0f / 16384.0f;
}
}
return -1.0f; // 错误值
}
通信完全无响应:
数据偶尔错误:
STM32收不到回复:
c复制uint32_t lastRxTime = 0;
// 在接收中断中更新时间戳
void USART2_IRQHandler(void)
{
lastRxTime = HAL_GetTick();
// ...原有代码...
}
// 主循环中检查超时
if(HAL_GetTick() - lastRxTime > 100)
{
RxCnt = 0; // 超时重置接收
}
AS5045除了基本的角度测量,还支持这些实用功能:
寄存器地址0x02存储圈数,结合角度值可实现多圈测量:
c复制int32_t Get_MultiTurnAngle(void)
{
uint8_t cmd[] = {0x01, 0x03, 0x00, 0x01, 0x00, 0x02, 0x95, 0xCB};
RS485_Send(cmd, sizeof(cmd));
HAL_Delay(10);
if(RxCnt >= 9)
{
uint16_t angle = (RxBuf[3]<<8) | RxBuf[4];
int16_t turns = (RxBuf[5]<<8) | RxBuf[6];
return (turns * 16384) + angle;
}
return 0;
}
通过写寄存器实现零点校准:
c复制void Set_ZeroPosition(void)
{
uint8_t cmd[] = {0x01, 0x06, 0x00, 0x02, 0x00, 0x01, 0xXX, 0xXX};
uint16_t crc = Calc_CRC16(cmd, 6);
cmd[6] = crc & 0xFF;
cmd[7] = crc >> 8;
RS485_Send(cmd, sizeof(cmd));
}
执行后当前机械位置会被设为0度。我在机器人关节校准中经常用到这个功能,比机械调零方便得多。注意校准前要确保磁铁安装位置正确。