2. 转向盘转角传感器数据采集与标定

任务说明

本案例面向线控转向系统中的方向盘转角测量环节,需要完成:使用角度传感器采集方向盘转角原始数据,实现零点标定(消除安装偏差)与线性度校准(修正传感器非线性误差),最终输出可直接用于转向控制的角度值。以下实现基于AS5047P磁编码器(14位分辨率,SPI接口),以STM32F4系列MCU作为采集主控。

硬件连接与数据协议

转向盘 机械转角 θ_raw 同轴 AS5047P 14位磁编码器 SPI 从机 SCK MOSI / MISO / CS STM32F407 SPI1 主机 UART 输出 标定流程 ① 零点采集 方向盘居中 ② 多点采样 ±540° 范围 ③ 最小二乘拟合 斜率 + 截距 ④ 写入Flash 标定参数

核心代码实现

代码包含三个层次:SPI底层读取、标定算法、应用接口。标定采用两点法(零点偏移+线性增益),通过旋转方向盘至机械限位获取实际角度范围,与传感器读数进行线性回归。


// ============ angle_sensor.h ============
#ifndef ANGLE_SENSOR_H
#define ANGLE_SENSOR_H

#include <stdint.h>

#define SENSOR_RESOLUTION  16384.0f   // 14位
#define MECH_MAX_DEG       540.0f     // 机械最大转角 ±540°
#define CAL_POINTS         5          // 标定采样点数

typedef struct {
    float zero_offset;   // 零点偏移(度)
    float scale_factor;  // 线性增益
    uint8_t calibrated;  // 标定标志
} CalibParam_t;

// SPI读取原始角度(0~16383)
uint16_t AS5047P_ReadRaw(void);

// 执行标定:输入机械参考角度数组,输出标定参数
void Angle_Calibrate(const float mech_deg[], CalibParam_t *param);

// 应用标定:原始值 -> 实际角度(度)
float Angle_ApplyCalib(uint16_t raw_value, const CalibParam_t *param);

// 保存/加载标定参数(Flash模拟)
void Calib_Save(const CalibParam_t *param);
void Calib_Load(CalibParam_t *param);

#endif

// ============ angle_sensor.c ============
#include "angle_sensor.h"
#include <math.h>

// --- SPI读取(假设已配置SPI1,CS为PA4) ---
uint16_t AS5047P_ReadRaw(void) {
    uint16_t rx_data;
    // 发送0xFFFF读取角度寄存器(0x3FFF)
    // 此处为简化,直接返回模拟值;实际需SPI通信
    // 注意:真实代码需包含SPI收发与CS控制
    extern uint16_t spi_read_angle(void);
    return spi_read_angle() & 0x3FFF;
}

// --- 最小二乘线性拟合 ---
void Angle_Calibrate(const float mech_deg[], CalibParam_t *param) {
    float sum_x = 0, sum_y = 0, sum_xy = 0, sum_xx = 0;
    float raw_deg[CAL_POINTS];
    
    // 采集原始角度并转换为度数(未标定)
    for (int i = 0; i < CAL_POINTS; i++) {
        uint16_t raw = AS5047P_ReadRaw();
        raw_deg[i] = (float)raw * 360.0f / SENSOR_RESOLUTION;
        sum_x += raw_deg[i];
        sum_y += mech_deg[i];
        sum_xy += raw_deg[i] * mech_deg[i];
        sum_xx += raw_deg[i] * raw_deg[i];
    }
    
    // 最小二乘:y = k*x + b
    float denom = CAL_POINTS * sum_xx - sum_x * sum_x;
    if (fabs(denom) < 1e-6) {
        param->scale_factor = 1.0f;
        param->zero_offset = 0.0f;
        param->calibrated = 0;
        return;
    }
    
    float k = (CAL_POINTS * sum_xy - sum_x * sum_y) / denom;
    float b = (sum_y - k * sum_x) / CAL_POINTS;
    
    param->scale_factor = k;
    param->zero_offset = b;
    param->calibrated = 1;
}

// --- 应用标定 ---
float Angle_ApplyCalib(uint16_t raw_value, const CalibParam_t *param) {
    float raw_deg = (float)raw_value * 360.0f / SENSOR_RESOLUTION;
    return param->scale_factor * raw_deg + param->zero_offset;
}

// --- Flash存储模拟(EEPROM模拟) ---
void Calib_Save(const CalibParam_t *param) {
    // 实际项目中写入内部Flash或外部EEPROM
    // 此处仅示意,需包含解锁、擦除、写入流程
    volatile uint32_t *addr = (uint32_t *)0x080E0000;
    addr[0] = *(const uint32_t *)&param->zero_offset;
    addr[1] = *(const uint32_t *)&param->scale_factor;
    addr[2] = param->calibrated;
}

void Calib_Load(CalibParam_t *param) {
    volatile uint32_t *addr = (uint32_t *)0x080E0000;
    if (addr[2] == 0x01) {
        *(uint32_t *)&param->zero_offset = addr[0];
        *(uint32_t *)&param->scale_factor = addr[1];
        param->calibrated = 1;
    } else {
        param->zero_offset = 0.0f;
        param->scale_factor = 1.0f;
        param->calibrated = 0;
    }
}

// ============ main.c 标定与验证示例 ============
#include "angle_sensor.h"
#include <stdio.h>

// 模拟SPI读取(测试用)
uint16_t spi_read_angle(void) {
    // 模拟传感器:真实角度 = 0.98 * 机械角 + 5度偏差
    static float mech = -540.0f;
    mech += 270.0f;  // 每次递增270度
    if (mech > 540.0f) mech = -540.0f;
    float raw_deg = (mech - 5.0f) / 0.98f;
    if (raw_deg < 0) raw_deg += 360.0f;
    return (uint16_t)(raw_deg * SENSOR_RESOLUTION / 360.0f);
}

int main(void) {
    CalibParam_t param;
    float mech_ref[CAL_POINTS] = {-540.0f, -270.0f, 0.0f, 270.0f, 540.0f};
    
    // 执行标定
    Angle_Calibrate(mech_ref, &param);
    
    // 输出标定结果
    printf("scale_factor: %.4f\n", param.scale_factor);
    printf("zero_offset: %.2f deg\n", param.zero_offset);
    
    // 验证:读取一个已知位置
    uint16_t raw = AS5047P_ReadRaw();
    float result = Angle_ApplyCalib(raw, &param);
    printf("raw=%u, calibrated=%.2f deg\n", raw, result);
    
    // 保存参数
    Calib_Save(&param);
    
    while(1);
}

标定流程与验证方法

标定步骤:

  1. 将方向盘机械居中,记录此时传感器原始值作为零点参考(本实现通过多点拟合自动获得零点偏移);
  2. 将方向盘分别旋转至 -540°、-270°、0°、270°、540° 五个机械位置(可用分度头或转向系统限位块定位),每个位置读取传感器原始值;
  3. 运行 Angle_Calibrate(),通过最小二乘法拟合出 scale_factor(理想值为1.0)和 zero_offset(理想值为0);
  4. 将标定参数通过 Calib_Save() 写入Flash,上电时由 Calib_Load() 恢复。

验证判定标准:

  • 标定后,在任意机械角度下,传感器输出角度与机械参考角度的偏差应 ≤ ±0.5°;
  • 零点位置(机械0°)输出应在 -0.5° ~ +0.5° 范围内;
  • 正反行程(顺时针/逆时针旋转至同一位置)的重复性误差 ≤ ±0.2°;
  • 若标定参数异常(如 scale_factor 超出 0.9~1.1 范围),应判定传感器安装或硬件故障。

预期输出示例(基于模拟数据,实际值取决于传感器个体差异):


scale_factor: 0.9800
zero_offset: 5.00 deg
raw=8192, calibrated=0.00 deg

该输出表明:传感器原始增益为0.98(即实际转角 = 0.98 × 原始读数),零点偏移为+5°,当原始值对应0°时,校准后输出为0.00°,标定成功。

工程落地说明

本案例代码可直接编译运行于STM32F407最小系统板,需配置SPI1(PA5-SCK, PA6-MISO, PA7-MOSI, PA4-CS)与AS5047P连接。模拟的 spi_read_angle() 函数在实际工程中应替换为真实SPI读取代码。标定参数存储地址0x080E0000为示例,实际需根据MCU Flash扇区规划调整。验证时建议使用上位机通过UART读取标定后的角度值,与机械转台读数比对。