3. 基于式编码器的转子初始位置读取与校验
任务目标
本案例实现基于式编码器的转子初始位置读取与校验。核心目标为:通过SPI接口读取式编码器的多圈位置数据,结合电机极对数计算机械角度对应的电角度,并校验编码器多圈数据与电机极对数的匹配关系,确保初始位置读取正确。
环境与依赖
- 硬件:STM32F405微控制器(SPI1接口)、多圈式编码器(如多摩川TS5700N系列,单圈分辨率17bit,多圈分辨率16bit)、FOC电机(极对数可配置,本例取4)
- 软件:STM32CubeIDE或Keil MDK,HAL库
关键完整代码
<code>// encoder.h
#ifndef ENCODER_H
#define ENCODER_H
#include "main.h"
#define ENC_SPI hspi1
#define ENC_CS_LOW() HAL_GPIO_WritePin(ENC_CS_GPIO_Port, ENC_CS_Pin, GPIO_PIN_RESET)
#define ENC_CS_HIGH() HAL_GPIO_WritePin(ENC_CS_GPIO_Port, ENC_CS_Pin, GPIO_PIN_SET)
#define ENC_SINGLE_TURN_BITS 17
#define ENC_MULTI_TURN_BITS 16
#define ENC_SINGLE_TURN_MASK ((1U << ENC_SINGLE_TURN_BITS) - 1)
#define ENC_MULTI_TURN_MASK ((1U << ENC_MULTI_TURN_BITS) - 1)
typedef struct {
uint32_t single_turn_raw; // 单圈原始值(0 ~ 131071)
int32_t multi_turn_raw; // 多圈原始值(有符号,-32768 ~ 32767)
float mechanical_deg; // 机械角度(度)
float electrical_deg; // 电角度(度)
uint8_t crc_ok; // CRC校验通过标志
} EncoderData_t;
void ENC_Init(void);
uint8_t ENC_ReadRaw(uint32_t *single_turn, int32_t *multi_turn);
uint8_t ENC_ReadAndVerify(EncoderData_t *data, uint8_t pole_pairs);
#endif
<code>// encoder.c
#include "encoder.h"
extern SPI_HandleTypeDef ENC_SPI;
// 编码器SPI读取命令(多摩川协议:0x00读取单圈+多圈+CRC)
static uint8_t enc_tx_buf[4] = {0x00, 0x00, 0x00, 0x00};
static uint8_t enc_rx_buf[4];
void ENC_Init(void) {
// SPI已由CubeMX初始化,此处仅做CS引脚上拉
ENC_CS_HIGH();
}
// 发送命令并接收4字节:Byte0-1=单圈(17bit),Byte2高7bit=多圈符号位+高6bit,Byte2 bit0+Byte3=多圈低8bit,Byte3高4bit为CRC
static uint8_t ENC_Transact(uint8_t cmd) {
enc_tx_buf[0] = cmd;
ENC_CS_LOW();
HAL_Delay(1); // 编码器准备时间
HAL_SPI_TransmitReceive(&ENC_SPI, enc_tx_buf, enc_rx_buf, 4, 100);
ENC_CS_HIGH();
return 1;
}
// 从4字节原始数据中解析单圈、多圈和CRC
static void ENC_Parse(uint32_t *single, int32_t *multi, uint8_t *crc) {
uint32_t raw = ((uint32_t)enc_rx_buf[0] << 24) | ((uint32_t)enc_rx_buf[1] << 16) |
((uint32_t)enc_rx_buf[2] << 8) | enc_rx_buf[3];
// 单圈:bit31~bit15(17位)
*single = (raw >> 15) & ENC_SINGLE_TURN_MASK;
// 多圈:bit14~bit0(15位有符号,实际16位含符号位在bit14)
int32_t mt = (raw >> 0) & 0x7FFF;
if (mt & 0x4000) { // bit14为符号位(实际多圈为15位数据+1符号位)
mt = mt - 0x8000;
}
*multi = mt;
// CRC:bit3~bit0(低4位)
*crc = raw & 0x0F;
}
// 简单CRC校验(多摩川协议:多项式x^4+x+1,初始值0)
static uint8_t ENC_CheckCRC(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80) crc = (crc << 1) ^ 0x13; // x^4+x+1 -> 0x13
else crc <<= 1;
}
}
return (crc >> 4) & 0x0F; // 取高4位作为校验值
}
uint8_t ENC_ReadRaw(uint32_t *single_turn, int32_t *multi_turn) {
ENC_Transact(0x00);
uint8_t crc_rx;
ENC_Parse(single_turn, multi_turn, &crc_rx);
// 计算CRC(对前3字节计算)
uint8_t crc_calc = ENC_CheckCRC(enc_rx_buf, 3);
return (crc_calc == crc_rx) ? 1 : 0;
}
uint8_t ENC_ReadAndVerify(EncoderData_t *data, uint8_t pole_pairs) {
uint32_t single;
int32_t multi;
if (!ENC_ReadRaw(&single, &multi)) {
data->crc_ok = 0;
return 0;
}
data->crc_ok = 1;
data->single_turn_raw = single;
data->multi_turn_raw = multi;
// 机械角度:单圈值 / 2^17 * 360°
data->mechanical_deg = (float)single / (float)(1U << ENC_SINGLE_TURN_BITS) * 360.0f;
// 电角度 = 机械角度 * 极对数,归一化到 [0, 360)
float elec = data->mechanical_deg * pole_pairs;
elec = fmodf(elec, 360.0f);
if (elec < 0) elec += 360.0f;
data->electrical_deg = elec;
// 校验多圈数据与极对数匹配:
// 电机每旋转一圈(机械),电角度变化 pole_pairs*360°,多圈值应增加/减少 pole_pairs 的整数倍
// 即:multi_turn_raw 应能被 pole_pairs 整除(考虑符号)
if (multi % pole_pairs != 0) {
return 0; // 不匹配
}
// 进一步校验:多圈值对应的机械圈数与单圈角度应一致
// 即:multi_turn_raw / pole_pairs 应等于当前累计机械圈数(整数部分)
int32_t expected_mech_turns = multi / pole_pairs;
float mech_deg_from_multi = (float)expected_mech_turns * 360.0f + data->mechanical_deg;
// 该值应接近编码器实际累计角度(此处仅做合理性检查,实际系统可结合其他传感器)
// 本例仅验证整除关系作为匹配判据
return 1;
}
<code>// main.c 调用示例
#include "encoder.h"
void FOC_InitPositionRead(void) {
EncoderData_t enc_data;
uint8_t pole_pairs = 4; // 电机极对数
ENC_Init();
if (ENC_ReadAndVerify(&enc_data, pole_pairs)) {
// 读取成功且校验通过
// enc_data.electrical_deg 即为转子初始电角度,可直接用于FOC矢量控制
// 示例:将电角度转换为0~65535对应0~360°供PWM查找表使用
uint16_t elec_angle_lsb = (uint16_t)(enc_data.electrical_deg / 360.0f * 65536.0f);
// 此处elec_angle_lsb即为FOC需要的初始电角度
} else {
// 读取失败或校验不通过,需重新读取或报警
Error_Handler();
}
}
运行与验证
- 硬件连接:编码器SPI信号(SCK、MISO、MOSI、CS)连接至STM32F405的SPI1引脚,CS接任意GPIO(本例为PA4),编码器供电3.3V或5V(按型号要求)。
- 工程配置:在CubeMX中使能SPI1(模式:全双工主机,速率≤1MHz,CPOL=0,CPHA=1),配置CS引脚为推挽输出,初始电平高。
- 编译下载:将上述代码加入工程,编译无误后下载至开发板。
- 验证方法:
- 手动旋转电机轴至任意位置,观察程序读取的
mechanical_deg是否与实际角度一致(可用刻度盘比对,误差应在编码器精度范围内)。 - 连续旋转电机轴超过一圈,观察
multi_turn_raw是否随圈数增减,且始终为极对数的整数倍。 - 当
ENC_ReadAndVerify返回1时,electrical_deg应满足:机械角度每增加360/pole_pairs度,电角度完成一个完整周期(0~360°)。 - 人为将极对数改为错误值(如3),函数应返回0,表明校验生效。
- 手动旋转电机轴至任意位置,观察程序读取的
通过条件:编码器读数稳定无跳变,CRC校验通过率100%,多圈值整除极对数,且电角度随机械角度按极对数倍频变化。
常见问题
- SPI读取全为0xFF:检查CS时序,确保CS低电平持续时间≥编码器响应时间(通常1μs以上),并确认SPI相位/极性配置正确。
- CRC校验总失败:确认编码器协议版本,部分型号CRC计算方式不同;可先用逻辑分析仪抓取波形,比对数据手册时序。
- 多圈值不整除极对数:若编码器安装时未对齐机械零位,多圈值可能带偏移。可在初始化时记录零位偏移量,并在校验前补偿。