电机驱动开发学习5. 电源电压与温度读取

电机驱动开发学习5. 电源电压与温度读取

  • 一、电源电压采样
    • [1. 电源电压采样电路](#1. 电源电压采样电路)
    • [2. 分压原理](#2. 分压原理)
    • [3. 差分转单端与 1.24 V 偏置](#3. 差分转单端与 1.24 V 偏置)
    • [4. 硬件连接](#4. 硬件连接)
    • [5. STM32 ADC 采集实现](#5. STM32 ADC 采集实现)
      • [5.1 编程要点](#5.1 编程要点)
      • [5.2 GPIO、ADC 与 DMA 配置](#5.2 GPIO、ADC 与 DMA 配置)
      • [5.3 DMA 数据采集与滤波](#5.3 DMA 数据采集与滤波)
      • [5.4 电压值换算](#5.4 电压值换算)
      • [5.5 过压/欠压保护(扩展)](#5.5 过压/欠压保护(扩展))
    • [6. 测量精度与常见问题](#6. 测量精度与常见问题)
  • [二、NTC 温度采样](#二、NTC 温度采样)
    • [1. 温度采样电路](#1. 温度采样电路)
    • [2. NTC 热敏电阻原理](#2. NTC 热敏电阻原理)
    • [3. 由 ADC 电压求 NTC 电阻](#3. 由 ADC 电压求 NTC 电阻)
    • [4. 硬件连接](#4. 硬件连接)
    • [5. STM32 温度换算](#5. STM32 温度换算)
      • [5.1 电阻与温度换算](#5.1 电阻与温度换算)
      • [5.2 温度保护(扩展)](#5.2 温度保护(扩展))
  • 三、主函数与程序流程
  • 四、十六进制波形输出与串口助手
    • [1. 帧格式](#1. 帧格式)
    • [2. 串口助手「三通道字段解析-波形」配置](#2. 串口助手「三通道字段解析-波形」配置)
  • 五、电机控制与串口指令
    • [1. 串口指令](#1. 串口指令)
    • [2. 按键控制](#2. 按键控制)
  • [六、堵转保护与 D12 指示](#六、堵转保护与 D12 指示)
  • 七、测试

本实验在 lesson4 无刷电机控制基础上,增加 电源电压NTC 温度 的 ADC 采集,并通过串口以 十六进制波形帧 输出;同时保留按键与串口电机控制、堵转保护等功能。

模块 源文件 说明
ADC 采集 User/adc/bsp_adc.c ADC3 + DMA,双通道滤波
波形输出 adc_send_waveform_frame() 每 50 ms 发 6 字节帧
电机控制 User/bldcm_control/ 串口指令 + 按键
堵转保护 User/tim/bsp_motor_tim.c D12(LED2)指示

一、电源电压采样

在电机驱动系统中,实时监测母线(电源)电压是一项基础功能:可用于显示工作状态、判断供电是否稳定,并为后续的过压/欠压保护提供依据。野火直流无刷驱动板支持 12 V~70 V 宽电压输入,MCU 侧 ADC 只能采集 0~3.3 V 的模拟量,因此驱动板上设计了 电阻分压 + 隔离运放 + 差分转单端 的采样电路,将高压母线安全地送到 STM32。

1. 电源电压采样电路

电路从左到右可分为四段:

阶段 作用 关键器件
电阻分压 将母线高压按比例缩小 R53、R54 等分压电阻
隔离放大 实现强弱电隔离,保护 MCU AMC1200SDUBR 隔离运放
差分转单端 将差分信号转为 ADC 可采的单端电压 普通运放(负反馈)
ADC 采样 MCU 读取模拟电压 STM32 ADC(PF9)

与电流采样通道不同,电压检测部分的隔离运放不做额外放大 ,仅完成隔离与传递;因此 ADC 端电压与母线电压之间是 固定的 1/37 分压关系 ,再叠加 1.24 V 偏置

野火驱动板对控制信号使用光耦隔离,对 ADC 采样电路使用 AMC1200 隔离运放,实现了信号的完全电气隔离。官方说明见:直流无刷驱动板温度电压采集

2. 分压原理

在母线电压(记为 VBUS)上并联 R53、R54 等串联电阻,取 R54 两端电压作为隔离运放输入 Vi

根据分压定律:

复制代码
Vi / R54 = VBUS / (R53 + R54 + ...)

代入驱动板实际阻值,可化简为:

复制代码
Vi = VBUS / 37

即隔离运放输入端电压约为母线电压的 1/37

3. 差分转单端与 1.24 V 偏置

隔离运放输出为差分信号,后级运放将其转换为单端电压 VBUS_ADC 送入 STM32。该级为负反馈同相放大结构,反馈电阻 R51、R52、R55、R56 均为 10 kΩ,根据虚短、虚断可得:

复制代码
VBUS_ADC = Vi + 1.24 V

Vi = VBUS / 37 代入:

复制代码
VBUS_ADC = VBUS / 37 + 1.24

反解母线电压:

复制代码
VBUS = (VBUS_ADC - 1.24) × 37

注意: 硬件并不是以 0 V 作为零点,而是加了 1.24 V 偏置 。程序中必须先用 ADC 原始值换算出 VBUS_ADC,再减去 1.24 V 后乘以 37,才能得到真实母线电压。

4. 硬件连接

无刷驱动板与 F407 骄阳开发板(无刷电机驱动接口 2)的相关连接如下:

无刷驱动板 主控板 说明
VBUS PF9 电源电压 ADC 采样
TEMP PF10 温度采样
5V_IN 5V 驱动板辅助电源
GND GND 共地

ADC 配置(与官方例程一致,务必使用 ADC3):

项目 配置
ADC 外设 ADC3(PF9/PF10 挂在 ADC3 上,非 ADC1)
电压通道 PF9 → ADC_CHANNEL_7,扫描 Rank 2
温度通道 PF10 → ADC_CHANNEL_8,扫描 Rank 1
DMA DMA2 Stream0,Channel 2
分辨率 12 位(0~4095)

官方使用配套牛角排线连接,用万用表测量 PF9 对地电压,应约为 VBUS/37 + 1.24 V;若偏差较大,优先排查驱动板分压与供电。

5. STM32 ADC 采集实现

5.1 编程要点

  1. 初始化 PF9、PF10 为模拟输入,配置 ADC3 + DMA2 Stream0 Channel2 循环采集。
  2. 双通道扫描:Rank 1 温度(PF10),Rank 2 电压(PF9)。
  3. DMA 缓冲区长度 ADC_NUM_MAX = 128,满后在中断回调中对两路数据 累加求平均
  4. HAL_ADC_Stop_DMA 处理数据,再 Start_DMA,避免处理期间缓冲区被覆盖。
  5. DMA 完成中断入口为 DMA2_Stream0_IRQHandler(在 bsp_adc.h 中通过宏 ADC_DMA_IRQ_Handler 映射)。

5.2 GPIO、ADC 与 DMA 配置

bsp_adc.h 关键宏:

c 复制代码
#define TEMP_ADC              ADC3
#define ADC_NUM_MAX           128

#define TEMP_ADC_CHANNEL      ADC_CHANNEL_8   /* PF10 */
#define VBUS_ADC_CHANNEL      ADC_CHANNEL_7   /* PF9  */

#define TEMP_ADC_DMA_STREAM   DMA2_Stream0
#define TEMP_ADC_DMA_CHANNEL  DMA_CHANNEL_2

GPIO 配置为模拟模式、无上下拉:

c 复制代码
static void ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    TEMP_ADC_GPIO_CLK_ENABLE();
    VBUS_GPIO_CLK_ENABLE();

    GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStructure.Pull = GPIO_NOPULL;

    GPIO_InitStructure.Pin = TEMP_ADC_GPIO_PIN;   /* PF10 */
    HAL_GPIO_Init(TEMP_ADC_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = VBUS_GPIO_PIN;       /* PF9 */
    HAL_GPIO_Init(VBUS_GPIO_PORT, &GPIO_InitStructure);
}

ADC 关键参数:

参数 配置 说明
ScanConvMode ENABLE 多通道扫描
ContinuousConvMode ENABLE 连续转换
NbrOfConversion 2 温度 + 电压
Resolution 12B 12 位精度
DMAContinuousRequests ENABLE DMA 循环搬运

电压通道配置示例:

c 复制代码
ADC_Config.Channel      = VBUS_ADC_CHANNEL;   /* ADC_CHANNEL_7,PF9 */
ADC_Config.Rank         = 2;
ADC_Config.SamplingTime = ADC_SAMPLETIME_3CYCLES;
HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config);

HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t *)&adc_buff, ADC_NUM_MAX);

5.3 DMA 数据采集与滤波

adc_buff 中按 [温度, 电压, 温度, 电压, ...] 交替存放两路结果。DMA 搬运 128 个半字后触发完成回调:

c 复制代码
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    int32_t adc_mean;

    HAL_ADC_Stop_DMA(hadc);

    /* 温度:偶数下标 */
    adc_mean = 0;
    for (uint32_t count = 0; count < ADC_NUM_MAX; count += 2)
        adc_mean += (int32_t)adc_buff[count];
    adc_mean_t = (uint32_t)(adc_mean / (ADC_NUM_MAX / 2));

    /* 电压:奇数下标 */
    adc_mean = 0;
    for (uint32_t count = 1; count < ADC_NUM_MAX; count += 2)
        adc_mean += (int32_t)adc_buff[count];
    vbus_adc_mean = (int16_t)(adc_mean / (ADC_NUM_MAX / 2));

    HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t *)&adc_buff, ADC_NUM_MAX);
}

5.4 电压值换算

c 复制代码
#define VREF                3.3f    /* 理论 3.3 V,实测可标定为 3.258 V 等 */

#define GET_ADC_VDC_VAL(val)  ((float)(val) / 4096.0f * VREF)
#define GET_VBUS_VAL(val)     (((float)(val) - 1.24f) * 37.0f)

float get_vbus_val(void)
{
    float vdc = GET_ADC_VDC_VAL(vbus_adc_mean);
    return GET_VBUS_VAL(vdc);
}

换算链路:

复制代码
ADC 原始值 ──GET_ADC_VDC_VAL──► VBUS_ADC (V) ──GET_VBUS_VAL──► VBUS (V)

5.5 过压/欠压保护(扩展)

在保护例程中,可配置 ADC 模拟看门狗 监测电压通道。将期望的电压上下限换算为 ADC 阈值:

c 复制代码
#define VBUS_MAX     15    /* 过压阈值 (V) */
#define VBUS_MIN     10    /* 欠压阈值 (V) */

#define VBUS_HEX_MAX  ((VBUS_MAX / 37.0f + 1.24f) / VREF * 4096.0f)
#define VBUS_HEX_MIN  ((VBUS_MIN / 37.0f + 1.24f) / VREF * 4096.0f)

超出窗口时进入 HAL_ADC_LevelOutOfWindowCallback,可关断电机并提示用户。

6. 测量精度与常见问题

提高精度的建议:

  1. 标定 VREF: 用万用表实测开发板 VREF+ 对地电压,替换宏中的 VREF(官方例程实测约 3.258 V)。
  2. 确认偏置: 上电未启动电机时,PF9 电压应接近 1.24 V(仅偏置、无分压分量时);若母线有电,则约为 VBUS/37 + 1.24 V

二、NTC 温度采样

电机长时间运行后绕组与轴承会发热,过高温度会加速绝缘老化甚至烧毁电机。野火驱动板预留 NTC 温度采样接口,外接 NTC 热敏电阻并贴于电机表面,即可通过 STM32 ADC 实时读取温度。

1. 温度采样电路

如上图所示,驱动板温度采样电路由 NTC 分压网络 + 电压跟随器 组成:

复制代码
3.3 V ── NTC(Rt) ──┬── Temperature_ADC ──► PF10 (ADC)
                   │
                 4.7 kΩ
                   │
                  GND

后级运放接成 电压跟随器 (运放同相端与输出端电压相等),将分压点电压 Temperature_ADC 隔离缓冲后送入 MCU,输入阻抗高,对分压网络影响可忽略。

元件 参数 说明
NTC 热敏电阻 10 kΩ @ 25 ℃,B = 3950 需外接,贴于电机表面
下拉电阻 4.7 kΩ 驱动板板载,与 NTC 构成分压
采样引脚 PF10 对应 TEMP 信号

在驱动板 NTC 接口 插入配套 NTC 探头,将感温头用导热胶带固定在电机外壳或绕组附近,尽量贴近热源。

2. NTC 热敏电阻原理

NTC(Negative Temperature Coefficient)电阻值随温度升高而 减小。其阻值与温度的关系可用 B 参数方程描述:

复制代码
Rt = R25 × exp[B × (1/T - 1/T25)]
符号 含义
Rt 当前温度 T(K)下的电阻值
R25 25 ℃ 时的标称电阻,配套 NTC 为 10 kΩ
T25 25 ℃ 对应的开尔文温度,298.15 K
B 材料常数,配套 NTC 为 3950

由 B 参数方程反解温度(开尔文),再换算为摄氏度:

复制代码
T(℃) = B × T25 / [B + ln(Rt/R25) × T25] - 273.15

C 语言中 log() 为自然对数,对应公式中的 ln

3. 由 ADC 电压求 NTC 电阻

分压点电压 Temperature_ADC(记为 Vadc)与 NTC 电阻 Rt 的关系:

复制代码
Vadc / Rt = (VREF - Vadc) / 4700

整理得:

复制代码
Rt = (VREF - Vadc) × 4700 / Vadc

其中 VREF 为 ADC 参考电压(理论 3.3 V,建议实测标定)。Vadc 由 ADC 原始值经 GET_ADC_VDC_VAL 宏换算得到。

温度越高 → Rt 越小 → Vadc 越低,与 NTC 负温度系数特性一致。

4. 硬件连接

温度采样与电源电压共用同一组 ADC3 + DMA 采集,相关连接见第一章第 4 节。

  • ADC 通道: PF10 → ADC_CHANNEL_8,扫描 Rank 1(先于电压)
  • 缓冲区布局: 偶数下标为温度,奇数下标为电压

5. STM32 温度换算

温度与电压在同一套 ADC_Init() 中完成,DMA 回调见第一章 5.3 节。

5.1 电阻与温度换算

c 复制代码
float get_ntc_r_val(void)
{
    float vdc = GET_ADC_VDC_VAL(adc_mean_t);
    return (VREF - vdc) / (vdc / 4700.0f);
}

float get_ntc_t_val(void)
{
    float Rt  = get_ntc_r_val();
    float Ka  = 273.15f;
    float T25 = Ka + 25.0f;
    float B   = 3950.0f;

    if (Rt <= 0.0f)
        return 0.0f;

    return B * T25 / (B + log(Rt / 10000.0f) * T25) - Ka;
}

换算链路:

复制代码
ADC 原始值 ──GET_ADC_VDC_VAL──► Vadc (V) ──分压公式──► Rt (Ω) ──B 参数方程──► T (℃)

5.2 温度保护(扩展)

在保护例程中,可在主循环判断温度是否超出安全范围:

c 复制代码
#define TEMP_MAX  80    /* 过温阈值 (℃) */

float temp = get_ntc_t_val();
if (temp > TEMP_MAX)
{
    t_max_count++;
    if (t_max_count > 10)
    {
        set_bldcm_disable();
        printf("温度超过限制!\r\n");
    }
}
else
{
    t_max_count = 0;
}

连续多次超限才触发停机,可避免偶发干扰引起误动作。

三、主函数与程序流程

本工程在 lesson4 基础上增加 ADC 初始化与波形输出,主循环同时处理电机控制:

c 复制代码
int main(void)
{
    static uint8_t print_flag = 0;

    SystemClock_Config();
    HAL_Init();

    LED_GPIO_Config();
    Key_GPIO_Config();
    DEBUG_USART_Config();
    ADC_Init();

    bldcm_init();
    set_bldcm_disable();    /* 上电默认关断电机 */

    while (1)
    {
        deal_key_input();       /* 按键调速/换向 */
        deal_serial_data();     /* 串口指令 */
        motor_stall_poll();     /* 堵转保护处理 */

        /* 每 50 ms 发送一帧电压+温度波形 */
        if (HAL_GetTick() % 50 == 0 && print_flag == 0)
        {
            print_flag = 1;
            adc_send_waveform_frame();
        }
        else if (HAL_GetTick() % 50 != 0 && print_flag == 1)
        {
            print_flag = 0;
        }
    }
}

与官方 demo 的区别:官方 demo 使用 printf 文本打印电压/温度;本工程改为 二进制波形帧,便于串口助手绘制曲线,同时保留电机控制功能。

四、十六进制波形输出与串口助手

1. 帧格式

程序每 50 ms 调用 adc_send_waveform_frame(),发送 6 字节 二进制数据:

字节序号 内容 说明
0 0xAA 帧头 1
1 0x55 帧头 2
2~3 uint16 小端 通道1:电源电压 × 100(2400 → 24.00 V)
4~5 uint16 小端 通道2:温度 × 10(255 → 25.5 ℃)

示例:AA 55 60 09 FA 00 → VBUS = 24.00 V,T = 25.0 ℃。

发送实现:

c 复制代码
void adc_send_waveform_frame(void)
{
    uint8_t frame[6];
    uint16_t vbus_x100 = (uint16_t)(get_vbus_val() * 100.0f + 0.5f);
    uint16_t temp_x10  = (uint16_t)(get_ntc_t_val() * 10.0f + 0.5f);

    frame[0] = 0xAA;
    frame[1] = 0x55;
    frame[2] = (uint8_t)(vbus_x100 & 0xFF);
    frame[3] = (uint8_t)(vbus_x100 >> 8);
    frame[4] = (uint8_t)(temp_x10 & 0xFF);
    frame[5] = (uint8_t)(temp_x10 >> 8);

    Usart_SendBytes(frame, 6);
}

2. 串口助手「三通道字段解析-波形」配置

  1. 串口参数:115200、8N1。
  2. 接收区勾选「十六进制显示」
  3. 切换到 「三通道字段解析-波形」只启用字段1、字段2
配置项 字段1(电压) 字段2(温度)
勾选启用
过滤帧头 ✓,填入 AA 55 同左
相对帧头起始位置 2 4
字段总长度字节数 2 2
字段大小端 小端 小端
  1. 波形 Y 轴换算:通道1 ÷ 100 → 电压 (V);通道2 ÷ 10 → 温度 (℃)。

  2. 常见配置错误 :字段长度填 1、起始位置填 2/4/6 会导致只读到 uint16 低字节,波形乱跳;起始位置应分别为 24(跳过帧头后的两个 uint16)。

五、电机控制与串口指令

1. 串口指令

指令 示例 说明
v [speed] v 1000 设置 PWM 占空(0~5500),>0 时自动使能电机
d [dir] d 0 / d 1 0=正转,1=反转
? ? 打印帮助
  • 每条指令以 回车(\r 结束。
  • 在串口助手 「串口调试」 发送区输入,不要勾选「十六进制发送」
  • MCU 已 关闭字符回显 ,避免 ASCII 混入 AA 55 二进制流。
  • 指令执行结果仍以 printf 文本输出;查看回复时可暂时关闭「十六进制显示」。
  • 串口 RX 已过滤非 ASCII 噪声,仅 \r 触发命令解析。

2. 按键控制

按键 功能
K1 占空 +550(约 10% 步进)
K2 占空 -550
K3 正转
K4 反转

按键每次只加一档占空,从停转启动更平稳;串口一次给高占空(如 v 3000)在停转状态下更容易触发堵转保护。

六、堵转保护与 D12 指示

程序在 TIM8 PWM 更新中断中检测 Hall 换相:若连续 8000 个 PWM 周期 (约 533 ms)Hall 位置未变,判定堵转并自动关断电机,D12(LED2)点亮

为保护串口大占空启动,使能后 1.5 s 内MOTOR_STALL_STARTUP_MS)不计入堵转计时;运行中改速会重置计时器。

现象 含义 处理
D12 亮 堵转保护触发,电机已关断 v 0 清状态,再 v 550 逐步加,或用 K1
串口偶发失败、按键正常 串口一次给高占空易误触发堵转 v 550 起步,或多次按 K1
再次启动时 D12 先灭再亮 仍立即堵转 占空过高或机械卡阻,降低速度

七、测试

猫猫串口调试助手设置如下:

可以查看输出波形图:

  • 打开串口 115200,勾选十六进制显示,配置波形解析(第四章)。
  • 确认通道1 约 2400 (24 V 母线)、通道2 约 250(25 ℃ 室温)。
  • 在「串口调试」发 v 550 + 回车,观察电机转动;或按 K1 逐步加速。