电机驱动开发学习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 编程要点
- 初始化 PF9、PF10 为模拟输入,配置 ADC3 + DMA2 Stream0 Channel2 循环采集。
- 双通道扫描:Rank 1 温度(PF10),Rank 2 电压(PF9)。
- DMA 缓冲区长度
ADC_NUM_MAX = 128,满后在中断回调中对两路数据 累加求平均。 - 先
HAL_ADC_Stop_DMA处理数据,再Start_DMA,避免处理期间缓冲区被覆盖。 - 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. 测量精度与常见问题
提高精度的建议:
- 标定 VREF: 用万用表实测开发板 VREF+ 对地电压,替换宏中的
VREF(官方例程实测约 3.258 V)。 - 确认偏置: 上电未启动电机时,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. 串口助手「三通道字段解析-波形」配置
- 串口参数:115200、8N1。
- 接收区勾选「十六进制显示」。
- 切换到 「三通道字段解析-波形」 ,只启用字段1、字段2:
| 配置项 | 字段1(电压) | 字段2(温度) |
|---|---|---|
| 勾选启用 | ✓ | ✓ |
| 过滤帧头 | ✓,填入 AA 55 |
同左 |
| 相对帧头起始位置 | 2 | 4 |
| 字段总长度字节数 | 2 | 2 |
| 字段大小端 | 小端 | 小端 |
-
波形 Y 轴换算:通道1 ÷ 100 → 电压 (V);通道2 ÷ 10 → 温度 (℃)。
-
常见配置错误 :字段长度填 1、起始位置填 2/4/6 会导致只读到 uint16 低字节,波形乱跳;起始位置应分别为 2 和 4(跳过帧头后的两个 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 逐步加速。