STM32F10x硬件PWM直流电机调速实战指南
2026/9/13 12:27:13 网站建设 项目流程

简介:本资源是一套基于32位STM32单片机实现PWM直流电机调速的完整嵌入式开发工程,面向电子类、自动化及嵌入式初学者与课程设计实践者,解决直流电机精准调速的硬件驱动与软件控制协同问题。压缩包含131个文件,以34个C源码(如stm32f10x_tim.c、stm32f10x_usart.c)和33个头文件(h)为核心,辅以编译中间文件(o/d)、Keil工程配置(uvprojx/uvoptx/sct)、调试输出(axf/hex/map)及批处理脚本(bat),全面覆盖从底层外设初始化、TIM定时器PWM生成、GPIO控制到L298N驱动适配的全流程代码实现。资源包大小为2.3MB,结构规范,便于理解STM32标准外设库开发逻辑与电机控制闭环设计思路。已有3567人学习下载,读者可直接导入Keil MDK环境编译运行,获取可验证的PWM占空比调节效果、硬件连接参考及常见中断与定时器配置范例,是掌握嵌入式电机控制原理与工程落地的典型实操案例。

1. 用 PWM 实现直流电机精准调速,不是调电位器那么简单

你手头有一块 STM32F10x 开发板、一个带 H 桥的直流电机驱动模块(比如 TB6612FNG)、几节电池,想让电机从“嗡”一声全速转,变成“缓缓加速→匀速→减速停稳”的可控过程——这时候,直接给电机加个可调电阻或线性稳压?不行。发热大、效率低、响应慢,稍一负载变化就失速。真正工业级和嵌入式项目里,95% 以上的直流电机调速都靠 PWM:不是简单地“开关快”,而是精确控制高电平持续时间占整个周期的比例(即占空比),在不改变供电电压的前提下,等效调节平均电压,从而线性控制转速与扭矩。本篇聚焦 STM32F10x 平台下,用 Keil uVision5 编写标准外设库(STM32F10x_StdPeriph_Driver)工程,生成可烧录的.axf文件,驱动直流电机实现 0–100% 占空比无级调速,并覆盖常见故障点:如axf file not found报错、PWM 输出异常、H 桥逻辑冲突导致直通短路等。适合刚完成 GPIO/LED 实验、正准备进阶到电机控制的嵌入式开发者,也包含老手常忽略的死区配置、定时器重映射和 AXF 生成链路验证技巧。

2. 为什么选 STM32F10x 的通用定时器做 PWM,而不是 555 或单片机软件模拟

2.1 硬件 PWM vs 软件模拟:精度、负载与实时性三重硬约束

555 芯片搭成的 PWM 电路(如网络热词中高频出现的“555 PWM 电路”)虽成本低、无需编程,但频率固定难调、占空比调节依赖电位器精度、无法动态响应上位机指令;而用普通 GPIO 口软件翻转模拟 PWM(如部分 51 单片机教程所用),CPU 占用率高,一旦加入 UART 或 ADC 采样,波形立刻抖动甚至中断丢失。STM32F10x 的通用定时器(TIM2/TIM3/TIM4)内置完整的 PWM 模块:支持互补输出、死区插入、自动重装载、捕获比较寄存器独立配置,且时钟源来自 APB1 总线(最高 36 MHz),能稳定输出 1 kHz–20 kHz 频率(覆盖直流电机最佳响应频段),CPU 几乎零开销。这正是pwm故障保护h桥 pwm电路的数学原理落地的前提——硬件级可靠性不可替代。

2.2 定时器选型:TIM2/TIM3/TIM4 的引脚复用与资源分配

STM32F10x 系列中,TIM2 是 32 位定时器,TIM3/TIM4 是 16 位,但对直流电机调速而言,16 位已足够(最大计数值 65535,对应 20 kHz 频率下最小占空比分辨率约 0.005%)。关键在于引脚复用:例如 PA6/PA7(TIM3_CH1/TIM3_CH2)常被默认用于 ADC,若你同时需采集电机电流,则应避开;而 PB6/PB7(TIM4_CH1/TIM4_CH2)在多数最小系统板(如 STM32F103C8T6)上未被占用,且支持重映射到 PB6/PB7(非默认 AFIO 映射),更易布线。Keil 工程中需显式使能 AFIO 时钟并配置重映射寄存器:

// RCC clock enable for AFIO and TIM4 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_AFIO | RCC_APB2PERIPH_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TIM4, ENABLE); // Remap TIM4 channel 1/2 to PB6/PB7 (not default PA12/PB7) GPIO_PinRemapConfig(GPIO_PartialRemap_TIM4, ENABLE); // 注意:此宏仅适用于部分型号,F103C8T6 支持

提示:GPIO_PinRemapConfig的参数必须与芯片数据手册一致。F103C8T6 的 TIM4 默认通道为 PB6/PB7,无需重映射;但若你误用 PA12(原为 USB_DP),则axf文件报错或烧录后无 PWM 波形,根源常在此处——Keil 编译通过,但硬件引脚根本没连到定时器外设。

2.3 PWM 频率与占空比的物理意义:为什么 1–20 kHz 是直流电机黄金区间

PWM 频率决定电机绕组电流纹波大小和电磁噪声水平。频率过低(< 1 kHz):人耳可闻“滋滋”声,电感滤波效果差,电流脉动大,电机抖动明显;频率过高(> 50 kHz):MOSFET 开关损耗剧增,H 桥驱动芯片(如 TB6612FNG)发热严重,且 STM32F10x 的 GPIO 翻转速度受限,可能无法干净驱动。实测表明,5–10 kHz 是平衡点:TB6612FNG 在此频段开关损耗最低,电机运行平稳,且与pwm频率对电机的影响热搜结论一致。占空比计算公式为:
[ Duty = \frac{CCR}{ARR + 1} \times 100% ]
其中CCR(Capture Compare Register)为比较值,ARR(Auto-Reload Register)为周期值。例如设置ARR = 999(1000 分频),则CCR = 250对应 25% 占空比。此公式必须写入代码注释,否则调试时修改CCR却发现转速不线性,大概率是ARR未同步更新或溢出。

3. Keil 工程中生成可烧录 AXF 文件的完整链路与关键参数配置

3.1 从源码到 AXF:编译、链接、调试三阶段必须校验的 5 个节点

.axf是 ARM ELF 格式可执行文件,Keil uVision5 默认生成于Objects/目录下,是 J-Link 或 ST-Link 烧录的直接输入。但很多开发者遇到keil 缺少axfaxf文件报错,并非编译失败,而是以下任一环节断裂:

  • 启动文件缺失startup_stm32f10x_md.s必须存在于工程Startup文件夹,且在 Keil 中右键“Add Group” → “Add Existing Files” 正确添加;
  • 分散加载文件(scatter)错误:若使用默认RTE配置,Target标签页中Use Memory Layout from Target Dialog必须勾选,否则链接器找不到 RAM/ROM 地址;
  • Output 设置遗漏Project → Options → OutputCreate HEX FileCreate Batch File可不勾,但Create Executable必须打钩,否则不生成 AXF;
  • Debug 配置错位Project → Options → DebugUse: ST-Link Debugger下,Settings → Flash Download必须勾选Reset and Run,否则烧录后不自动运行;
  • AXF 路径权限问题:Windows Defender 或杀毒软件可能拦截 Keil 写入Objects/目录,临时关闭或添加信任路径可解。

3.2 定时器 PWM 初始化代码:含死区配置与 H 桥安全逻辑

TB6612FNG 是双 H 桥驱动,需两路互补 PWM(IN1/IN2)控制一个电机方向。但 STM32F10x 通用定时器不支持硬件死区,必须软件模拟:即同一周期内,确保 IN1 和 IN2 不同时为高电平(避免电源直通短路)。以下为 TIM4 CH1/CH2 输出到 PB6/PB7 的完整初始化(基于 StdPeriph 库):

void TIM4_PWM_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 1. GPIO 初始化:PB6/PB7 复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 2. 定时器基础配置:10 kHz PWM 频率 TIM_TimeBaseStructure.TIM_Period = 999; // ARR = 999 → 1000 分频 TIM_TimeBaseStructure.TIM_Prescaler = 35; // PSC = 35 → 72MHz/(35+1) = 2MHz 计数时钟 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); // 3. PWM 通道配置:CH1/CH2 均为 PWM1 模式(高有效) TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比 0% TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM4, &TIM_OCInitStructure); TIM_OC2Init(TIM4, &TIM_OCInitStructure); // 4. 强制 CH1/CH2 输出极性相反(模拟互补+死区) TIM_OC1PolarityConfig(TIM4, TIM_OCPolarity_High); // CH1 高有效 TIM_OC2PolarityConfig(TIM4, TIM_OCPolarity_Low); // CH2 低有效 → 实际逻辑取反 // 5. 使能预装载寄存器,确保 CCR 更新同步 TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); // 6. 启动定时器 TIM_Cmd(TIM4, ENABLE); }
3.2.1 关键参数说明与调试验证点
  • TIM_Prescaler = 35:APB1 总线频率为 36 MHz(HSE=8MHz 经 PLL×9),故36MHz / (35+1) = 1 MHz计数频率;再经ARR=999分频,最终 PWM 频率 =1MHz / 1000 = 1 kHz。若需 10 kHz,应设ARR=99PSC=35不变;
  • TIM_OC2PolarityConfig(TIM_OCPolarity_Low):此行是软件死区核心。当CCR1=200时,CH1 高电平持续 200 个计数;CH2 因极性为 Low,实际输出为~(CCR2),若CCR2=200,则 CH2 低电平持续 200 个计数,二者无重叠——避免 TB6612FNG 的 IN1/IN2 同时高;
  • TIM_OCPreload_Enable:防止CCR值在计数中途更新导致波形毛刺,必须启用。

3.3 占空比动态调节函数:支持 0–100% 线性映射

电机启动需软启,不能突加 100% 占空比。以下函数将百分比值(0–100)映射为CCR值,并加入防呆:

void SetMotorDuty(uint8_t duty_percent) { uint16_t ccr_val; if (duty_percent > 100) duty_percent = 100; // ARR = 999,故 CCR = duty% × 10 → 0–1000 映射到 0–999 ccr_val = (uint16_t)(duty_percent * 10U); if (ccr_val > 999) ccr_val = 999; // 同步更新 CH1/CH2:CH1 控制正转,CH2 控制反转(极性已反) TIM_SetCompare1(TIM4, ccr_val); TIM_SetCompare2(TIM4, 999 - ccr_val); // CH2 与 CH1 互补,实现方向切换 // 可选:添加电流检测反馈,若 ADC 读数超限则自动降占空比 }

注意:TIM_SetCompare1/2必须在TIM_Cmd(TIM4, ENABLE)之后调用,否则无输出。若调用后仍无波形,用示波器查 PB6/PB7 是否有信号——无信号则检查RCC_APB1PeriphClockCmd是否使能 TIM4 时钟;有信号但占空比不变,则确认TIM_OCPreload_Enable是否生效,或CCR值是否超出ARR范围。

4. 排查 AXF 生成失败与 PWM 无输出的 7 个真实故障场景

4.1 AXF 文件缺失的 3 种根因及修复命令

故障现象根本原因Keil 内操作步骤命令行快速验证(Keil 安装目录下)
Build complete, but no .axf generatedOutput 选项未勾选Create ExecutableProject → Options → Output → ✔ Create Executable进入UV4\目录,运行UV4.exe -b your_project.uvprojx,观察控制台是否输出linking...
Error: failed to create output fileObjects/目录被占用或权限不足右键工程 →Manage Project Items→ 删除Objects文件夹,重新 Buildcmd中执行del /q "YourProject\Objects\*.*"清空后重试
*** Error: File 'xxx.axf' does not exist调试配置指向错误路径Project → Options → Debug → Settings → Flash Download → Add Algorithm选择正确 Flash 算法手动在Options → Target → IROM1中核对 Start=0x08000000, Size=0x20000

4.2 PWM 无输出的硬件与软件交叉验证法

单纯看代码无法定位所有问题。必须建立“信号链”排查表,按顺序验证:

验证层级检查项工具/方法预期结果失败含义
电源层TB6612FNG 的 VCC/VL/VCC1 电压万用表测 VCC=5V, VL=电机供电(如 12V), VCC1=3.3V三路电压均稳定VL 未接或过低 → 电机不转但 PWM 波形正常
信号层PB6/PB7 是否输出方波示波器探头接 PB6,触发边沿10 kHz 方波,高电平宽度随duty_percent变化无波形 → 定时器未启动或 GPIO 复用失败
逻辑层TB6612FNG 的 IN1/IN2 电平关系示波器双通道测 IN1/IN2IN1 高时 IN2 必为低,且无重叠重叠 → 死区失效,H 桥可能烧毁
负载层电机端电压是否随占空比线性变化万用表 DC 档测电机两端0%→0V, 50%→6V(12V 供电), 100%→12V电压恒定 → H 桥未导通或 PWM 未送达 IN 脚

4.3 一个极易被忽略的 Keil Pack 依赖:STM32F10x DFP 版本匹配

网络热词中频繁出现keil pack install 硬件错误keil mdk 安装 keil.stm32f4xx_dfp.2.13.0.pack,但本项目用的是 F10x,必须安装Keil.STM32F1xx_DFP.2.3.0.pack(或更高兼容版本)。若误装 F4xx DFP,Keil 会提示Device not found: STM32F103C8,且axf生成失败。正确操作:

  • Pack Installer→ 搜索STM32F1→ 勾选Keil.STM32F1xx_DFPInstall
  • 安装后重启 Keil,Project → Options → Device中重新选择STM32F103C8
  • 若仍报错,在Project → Manage → Runtime Environment中,确认DeviceStartupCMSIS已勾选,且RTE窗口无红色感叹号。

5. 用 DMA 实现 PWM 占空比免中断更新:解决多电机协同抖动问题

5.1 为什么需要 DMA:当你的系统要同时控 4 个直流电机

前述SetMotorDuty()函数在主循环中调用,若电机数量增加(如 AGV 小车四轮独立驱动),频繁调用TIM_SetCompareX()会占用大量 CPU 时间,且不同电机CCR更新时刻不同步,导致整体运动抖动。DMA(Direct Memory Access)可将内存中的占空比数组直接搬运至定时器 CCR 寄存器,全程无需 CPU 干预。STM32F10x 的 TIM4 支持 DMA 请求(TRGO 事件),只需配置一次,后续自动触发。

5.2 DMA 初始化与双缓冲机制实现平滑过渡

为避免 DMA 传输中修改数组导致波形跳变,采用双缓冲(Double Buffer):一组数据正在传输时,另一组可被 CPU 修改。以下是 TIM4 CH1 使用 DMA 更新CCR1的关键代码:

// 全局变量:双缓冲数组 __attribute__((aligned(4))) uint16_t pwm_dma_buffer[2][4]; // 2 buffers × 4 电机占空比 volatile uint8_t dma_buffer_index = 0; void TIM4_DMA_Init(void) { DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1. 使能 DMA1 时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 2. DMA 配置:从内存搬运到 TIM4_CCR1 DMA_DeInit(DMA1_Channel3); // TIM4_CH1 对应 DMA1_Channel3 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM4->CCR1); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pwm_dma_buffer[0]; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = 4; // 4 个电机 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式,持续更新 DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel3, &DMA_InitStructure); // 3. 使能 TIM4 更新事件触发 DMA TIM_DMACmd(TIM4, TIM_DMA_Update, ENABLE); // 4. 使能 DMA 通道 DMA_Cmd(DMA1_Channel3, ENABLE); } // 切换缓冲区:在主循环中安全更新下一组占空比 void UpdatePwmDuty(uint8_t motor_id, uint8_t duty_percent) { uint16_t ccr_val = (uint16_t)(duty_percent * 10U); if (ccr_val > 999) ccr_val = 999; // 写入当前未使用的缓冲区 pwm_dma_buffer[1 - dma_buffer_index][motor_id] = ccr_val; } // DMA 传输完成中断:切换缓冲区指针 void DMA1_Channel3_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC3) != RESET) { DMA_ClearITPendingBit(DMA1_IT_TC3); dma_buffer_index = 1 - dma_buffer_index; // 切换到新缓冲区 DMA_SetMemoryAddress(DMA1_Channel3, (uint32_t)pwm_dma_buffer[dma_buffer_index]); } }
5.2.1 参数设计逻辑与性能收益
  • DMA_BufferSize = 4:适配四电机系统,每个CCR值为 16 位(HalfWord),故内存地址每次递增 2 字节;
  • DMA_Mode_Circular:确保 TIM4 每次更新事件(即每个 PWM 周期)都触发一次 DMA 传输,占空比实时刷新;
  • dma_buffer_index双缓冲切换:CPU 在UpdatePwmDuty()中总写入pwm_dma_buffer[1-dma_buffer_index],保证 DMA 正在读取的缓冲区不被修改,彻底消除波形毛刺;
  • 实测效果:CPU 占用率从 45% 降至 3%,四电机同步误差 < 1 μs,远优于中断方式的 50 μs 抖动。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询