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();
    }
}

运行与验证

  1. 硬件连接:编码器SPI信号(SCK、MISO、MOSI、CS)连接至STM32F405的SPI1引脚,CS接任意GPIO(本例为PA4),编码器供电3.3V或5V(按型号要求)。
  2. 工程配置:在CubeMX中使能SPI1(模式:全双工主机,速率≤1MHz,CPOL=0,CPHA=1),配置CS引脚为推挽输出,初始电平高。
  3. 编译下载:将上述代码加入工程,编译无误后下载至开发板。
  4. 验证方法:
    • 手动旋转电机轴至任意位置,观察程序读取的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计算方式不同;可先用逻辑分析仪抓取波形,比对数据手册时序。
  • 多圈值不整除极对数:若编码器安装时未对齐机械零位,多圈值可能带偏移。可在初始化时记录零位偏移量,并在校验前补偿。