ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32H750 PWM寄存器驱动:从PSC/ARR/CCR到高级定时器实战

STM32H750 PWM寄存器驱动:从PSC/ARR/CCR到高级定时器实战 简介STM32H750基于ARM Cortex-M7内核是ST H7系列中兼顾性能与性价比的代表型号。本资源即针对该芯片的寄存器库驱动PWM输出工程面向嵌入式开发者解决定时器PWM配置、占空比调节及跨型号移植等实际问题。压缩包内共28个文件以14个H头文件和9个C源文件为主配合Keil工程文件、链接脚本、启动文件以及可直接烧录的hex固件整体仅307KB按外设模块划分目录便于对照学习。已有802人学习浏览。工程除PWM输出外还整合LED、按键、外部中断、看门狗等外设驱动寄存器级操作清晰展示了定时器选型、时钟配置、PWM模式设置、预装载与比较值写入等完整流程同时提供系统延时与串口初始化代码方便调试。无论用于电机控制、电源调光还是作为H7系列底层驱动参考都可快速移植复用。1. STM32H750 的 PWM 为什么值得用寄存器库驱动在 STM32H750 上输出 PWMHAL 库一条 HAL_TIM_PWM_Start 就完事但真到了电机噪声、多通道相位不一致、或者板子主频和默认配置不同的时候HAL 的抽象反而挡住判断路径。最近我拆了一套以寄存器库驱动为核心、支持 STM32H7 系列单片机的工程入口是 test.c交付物里还有 test.hex、Keil 工程文件、qspi_code.scf.scf 链接脚本以及 HARDWARE 目录下的 LED、TIMER、WDG、KEY、EXTI 模块。这套东西的价值不在 PWM 本身而在于它把系统时钟、延时、串口、看门狗和定时器框架串在了一起适合用 Keil MDK 做裸机开发、想弄清楚 PSC、ARR、CCR 之间关系的开发者。下面按定时器选型、寄存器配置、H7 移植边界和故障排查的顺序把这份工程的技术点全部展开。2. TIM1/TIM8 与通用定时器的 PWM 选型差异2.1 高级定时器和通用定时器的能力边界STM32H750 里能输出 PWM 的定时器分成两档TIM1/TIM8 属于高级控制定时器TIM2~TIM5 是通用定时器。它们的时基、计数模式、预分频结构是一样的差在输出外围上。高级定时器带互补输出、死区插入、刹车输入和重复计数一套配置下来可以直接驱动全桥或半桥电路通用定时器只有单端输出适合 LED 调光、舵机控制、蜂鸣器这类不需要考虑上下桥臂直通的场景。从这个资源的 HARDWARE 目录组织方式看TIMER 模块独立占一个目录说明初始化部分没有依赖 HAL 库的文件结构移植到其他 H7 型号时只需要改 RCC 总线和 GPIO 复用编号。我做选型时有个习惯先看负载需不需要互补 PWM需要就直接上 TIM1 或 TIM8不需要就优先用通用定时器因为通用定时器的寄存器字段更少调试成本低。电机控制里 PWM 频率对电机的影响很明显频率太低转矩波动大频率太高开关损耗上去了选哪个定时器往往不只看资源占用还要看后续要不要扩展死区。能力TIM1/TIM8 高级定时器TIM2~TIM5 通用定时器单端 PWM 输出支持支持互补输出支持不支持死区时间控制支持BDTR 寄存器不支持刹车输入支持不支持重复计数器支持不支持DMA 请求支持支持典型用途电机驱动、数字电源LED、舵机、蜂鸣器2.2 时钟分频链路与 PWM 频率计算H7 的定时器时钟不是直接等于 APB 总线时钟。当 APBx 预分频系数不为 1 时定时器时钟等于 APBx 时钟的 2 倍。H750 在 480MHz 主频下通常把 AHB 配到 240MHz、APB1 和 APB2 都配到 120MHz所以 TIM1/TIM8挂在 APB2和 TIM2~TIM5挂在 APB1的定时器时钟都是 240MHz。PWM 周期由两个寄存器决定PSC 对定时器时钟做预分频ARR 决定计数周期。公式是f_pwm f_tim / ((PSC 1) * (ARR 1))工程里如果要输出 1kHz 的 PWM典型配置是 PSC 239ARR 999计数器以 1MHz 步进1000 个计数周期对应 1kHz。占空比由 CCR 决定PWM 模式 1 且向上计数时CNT 从 0 计数到 CCR 期间输出有效电平。PWM 占空比计算公式就是 (ARR 1 - CCR) / (ARR 1)具体取哪一段要看输出的有效极性配置。配参数时建议先定 PSC 再定 ARR。PSC 决定占空比调节的步进精度PSC 越大计数器节拍越粗CCR 每变化 1 对应的占空比跳变也越大。下表是 240MHz 时钟下几组常见配置目标频率PSCARRCCR50%占空比占空比分辨率10kHz24-1999500约0.1%20kHz24-1499250约0.2%1kHz240-1999500约0.1%100Hz2400-1999500约0.1%2.3 RCC 使能是寄存器操作的第一步H7 的 RCC 总线结构里TIM1 挂在 APB2TIM2~TIM5 挂在 APB1。工程里的 TIMER 模块初始化先操作 RCC这块地方如果漏了后面写 TIMx 的所有寄存器都不会生效读回全是 0。// APB1 上 TIM3 时钟使能 RCC-APB1ENR | RCC_APB1ENR_TIM3EN; // APB2 上 TIM1 时钟使能 RCC-APB2ENR | RCC_APB2ENR_TIM1EN;常见错误是把 TIM3 写进 APB2ENR或者把 TIM1 写进 APB1ENR代码不报错但外设根本没工作。另一个容易踩的点是总线使能后需要内存屏障编译优化可能把这条使能语句和后面的寄存器操作重排建议在 RCC 使能后加一条__DSB()。调试时先确认 TIMx 寄存器能否读出非零值再往下查 GPIO 和比较器配置能省去大量盲目改代码的时间。3. PSC、ARR、CCR寄存器级 PWM 配置的完整路径3.1 GPIO 复用功能与引脚映射PWM 信号最终要从引脚出去GPIO 配置顺序决定信号能不能正常到达引脚。以 TIM1_CH1 对应的 PA8 为例完整配置分三步模式切到复用、推挽输出、AF 编号写进 AFR 寄存器。// PA8 复用功能模式 GPIOA-MODER ~(GPIO_MODER_MODE8_Msk); GPIOA-MODER | (2UL GPIO_MODER_MODE8_Pos); // 推挽输出、高速 GPIOA-OTYPER ~GPIO_OTYPER_OT8; GPIOA-OSPEEDR | (3UL GPIO_OSPEEDR_OSPEED8_Pos); // 浮空避免上下拉引入额外功耗和边沿劣化 GPIOA-PUPDR ~(GPIO_PUPDR_PUPD8_Msk); // PA8 的 TIM1_CH1 是 AF2 GPIOA-AFR[1] ~GPIO_AFRH_AFSEL8_Msk; GPIOA-AFR[1] | (2UL GPIO_AFRH_AFSEL8_Pos);AFR[1] 对应 PA8~PA15PA0~PA7 在 AFR[0]这个位置写代码时最容易混。PA8 的 AF2 是 TIM1_CH1但 PA8 同时也有 OTG_FS_SOF 和 MCO1 等复用选项。AF 编号配错时引脚可能是浮空或被其他外设占用定时器寄存器全部正确示波器上却始终没有波形。我把这个问题的排查顺序固定在 GPIO 之前先看 AF 复用表而不是先怀疑定时器配置。3.2 定时器配置顺序与关键位定时器的配置顺序决定上电瞬间输出的是稳定波形还是不确定毛刺。按下面这个顺序写能保证计数器开始计数前所有参数都生效// 时基PSC239, ARR999, 1kHz TIM1-PSC 239; TIM1-ARR 999; TIM1-CCR1 500; // 50% 占空比 // CCMR1PWM 模式1, 输出预装载 TIM1-CCMR1 ~TIM_CCMR1_OC1M_Msk; TIM1-CCMR1 | (6UL TIM_CCMR1_OC1M_Pos); TIM1-CCMR1 | TIM_CCMR1_OC1PE; // CCER通道1输出使能 TIM1-CCER | TIM_CCER_CC1E; // 高级定时器主输出使能掉这个位输出永远为无效电平 TIM1-BDTR | TIM_BDTR_MOE; // CR1自动重载预装载 启动计数器 TIM1-CR1 | TIM_CR1_ARPE; TIM1-CR1 | TIM_CR1_CEN;几个容易忽略的位OC1M 是 3 位字段写入前必须先清掩码。不清就直接或运算残留值和本次值叠加实际模式完全不可预期。OC1PE 置位后CCR 的修改在下一个更新事件才生效配合 ARPE 可以避免运行中改占空比时出现瞬间突变。BDTR 的 MOE 位只存在于高级定时器。从 F1 或 F4 迁到 H7 的开发者在 TIM1 上反复折腾没有输出大概率是少了这一行。ARPE 置位后 ARR 的修改在周期边界生效动态变频时推荐开启。3.3 动态设置占空比的参数边界占空比计算建议用 ARR 1 而不是 ARR。以 50% 为例正确写法是TIM1-CCR1 (TIM1-ARR 1) / 2;如果写 ARR / 2ARR 为奇数时结果偏小实际占空比和 50% 差了一个计数周期频率越高误差越明显。需要按百分比设置时推荐用一个带边界判断的函数void set_pwm_duty(TIM_TypeDef *tim, uint32_t ch, uint32_t percent) { uint32_t arr tim-ARR; uint32_t ccr_val; if (percent 100) { ccr_val arr 1; // 真正进入恒有效区间 } else { ccr_val (arr 1) * percent / 100; } // 按通道写 CCR1~CCR4寄存器地址连续 *(volatile uint32_t *)((uint32_t)tim-CCR1 (ch - 1) * 4) ccr_val; }percent 边界放在赋值之前处理顺带解决 100% 占空比时输出出现窄脉冲的问题。这个问题在 PWM 驱动 LED 全亮或电机全速时特别明显数值上 CCR 小于等于 ARR 时总有一个计数周期比较器翻转波形不是理想恒定电平。4. STM32H7 系列移植的时钟树、死区与多通道同步4.1 从 H750 到 H743 的启动差异与 QSPI 执行H750 的片内 Flash 只有 128KBH743 从 1MB 起步。这份工程带的是 startup_stm32h750xx.s 启动文件如果直接拿 H743 型号编译下载向量表偏移和堆栈初始化在启动阶段就错位现象是上电后立即进入 HardFault连 main 都进不去。因此在 STM32 Cube 之类的工具里更改单片机型号时一定要同步替换启动文件确认__initial_sp和 Reset_Handler 的位置符合目标型号的内存布局。工程里还出现了 SCRIPT/qspi_code.scf.scf 链接脚本说明代码可能有一部分放在片外 QSPI Flash 执行。H750 片内 Flash 小把 App 放到 QSPI 是常见方案搜“stm32h750 片外 app 卡死”时被问得最多的问题就是 PLL 还没稳定就跳到了外部地址。排查步骤我一般这样走先确认 RCC-CR 的 HSE 就绪标志PLL 锁定后再跳转再看链接脚本的 RO 基址和烧录算法是否一致最后检查 QSPI 是否工作在 Memory-Mapped 模式。调试时可以在 sys.c 中临时把时钟切到 HSI如果不卡死了问题基本锁定在 PLL 配置而不是 PWM 本身。4.2 死区时间配置与 BDTR 寄存器H 桥或三相逆变器里同一桥臂的上下管互补 PWM 之间必须插入死区时间。TIM1/TIM8 的 BDTR 寄存器提供 8 位 DTG 字段分段映射到不同死区时长死区时间配置是电机驱动里绕不开的细节。以 240MHz 定时器时钟为例tDTS 1/240MHz ≈ 4.17ns。目标死区 2μs可以用 DTG[7:5] 110 区间DT (32 DTG[4:0]) × 8 × tDTS反推 DTG[4:0] 2000ns / (8 × 4.17ns) - 32 ≈ 28写成寄存器就是 0xDC// 死区约 2us, 同时使能主输出 TIM1-BDTR (0xDC TIM_BDTR_DTG_Pos) | TIM_BDTR_MOE;BDTR 建议一次性写入完整值而不是运行中反复用按位或去改字段。上电时先写了 MOE 再补其他字段中间可能有一段无保护输出窗口对功率电路来说很危险。刹车输入对应 BKE 位H7 上还有第二刹车 BK2E具体字段名要以参考手册为准不建议照抄 F1/F4 的代码。4.3 多通道同步与 MMS 主模式选择多个 PWM 通道要严格同频同相时分别初始化多个定时器在时间上很难保证同步。常用做法是指定一个主定时器通过 MMS主模式选择字段把更新事件输出到 TRGO其他从定时器从 TRGI 接收外部触发。在寄存器库里的配置路径是 CR2 寄存器// TIM1 更新事件作为主模式输出到 TRGO TIM1-CR2 ~TIM_CR2_MMS_Msk; TIM1-CR2 | (2UL TIM_CR2_MMS_Pos); // 010: 更新事件从定时器配置为触发从模式后主定时器每个周期发出的更新事件会使从定时器同步清零重载。这条路径在寄存器库里不需要额外的事件路由比外部 GPIO 握手可靠得多。同样原理可以用在 PWM 触发 ADC 采样上MMS 选择更新事件ADC 配置外部触发每次 PWM 周期都在固定相位点采样电流省掉一路中断开销还能避开开关噪声。5. PWM 故障保护、100% 占空比边界与波形验证5.1 占空比打到 100% 时波形为什么异常示波器接在 TIM1_CH1 上把 CCR 改成和 ARR 相等你会看到波形不是恒高而是每个周期出现一个极窄的低电平脉冲。原因是 PWM 模式 1 在 CNT CCR 时输出有效CCR ARR 意味着计数器走到 ARR 的瞬间比较条件不成立输出强制切到无效电平直到下一次计数器清零才恢复。这个窄脉冲长度只有一个计数节拍但对感性负载或过流保护电路来说它仍是真实的电平翻转可能引起误触发。修复方式就是让 CCR 越过 ARRif (duty_percent 100) { TIM1-CCR1 TIM1-ARR 1; } else { TIM1-CCR1 (TIM1-ARR 1) * duty_percent / 100; }5.2 基于刹车输入的输出封锁电机驱动里不能只靠软件判断电压电流通常把过流比较器输出接到 TIM1_BKIN 引脚通过 BDTR 的 BKE 位使能刹车功能。刹车事件发生后 MOE 自动清零高级定时器输出被强制拉到安全电平这个过程不依赖 CPU 执行速度即使 main 循环卡在某个阻塞等待里硬件也会在一个总线周期内把输出拉掉// 使能刹车, 极性低电平有效 TIM1-BDTR | TIM_BDTR_BKE; // 刹车时输出无效电平 TIM1-BDTR ~(TIM_BDTR_OSSI | TIM_BDTR_OSSR); // 软件触发一次刹车, 用来验证保护路径 TIM1-BDTR | TIM_BDTR_BG; if (!(TIM1-BDTR TIM_BDTR_MOE)) { // 输出已被封锁, 可以在这里记录故障状态 }5.3 没有示波器怎么验证 PWM手头没有示波器时可以先把 PWM 引脚接到 LED改变 CCR 看亮度变化粗判占空比更新是否生效。更可靠的办法是把计算值和设定值打印到串口工程里的 SYSTEM/usart 已经做了 printf 重定向直接调用printf(freq%luHz duty%lu%%\r\n, f_tim / ((TIM1-PSC 1) * (TIM1-ARR 1)), (TIM1-CCR1 * 100UL) / (TIM1-ARR 1));串口打印值和设定值一致但波形仍不对再用逻辑分析仪抓 TIM1_CH1 引脚重点看窄脉冲和电平毛刺出现在周期内哪个位置顺藤摸瓜基本能在 GPIO 复用、BDTR 配置或者 CCR 边界条件里找到根因。本文还有配套的精品资源点击获取
返回列表