ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

嵌入式实战:TMS320F28069可调PWM与液晶显示交互系统

嵌入式实战:TMS320F28069可调PWM与液晶显示交互系统 简介TMS320F28069液晶显示可调频率PWM输出是一份基于TI C28x内核DSP的完整嵌入式工程面向电机控制、电源变换等实时控制方向的开发者解决在LCD上实时显示并调节PWM输出频率的问题。压缩包共126个文件约320KB包含38个头文件、25个C源文件以及链接命令、启动汇编、工程构建脚本等可对照工程结构理解外设初始化、PWM模块配置与液晶屏驱动的调用关系。目前已有320人学习下载。通过阅读源码可以掌握浮点运算单元的使用方法、PWM频率和占空比的调节逻辑以及读取按键或触摸输入并刷新屏幕的完整流程配套工程也可直接导入CCS进行编译调试适合需要快速上手DSP控制与显示结合项目的学习者和工程师。 最近在调 TMS320F28069 的时候我做了一个很实用的小工具液晶显示可调频率 PWM 输出。用几个按键就能在运行时实时改变 PWM 的频率和占空比当前设置值直接显示在液晶屏上直观还不容易调错。这个项目本身不复杂但把 F28069 的 ePWM、GPIO 复用、外部中断/按键扫描和 SPI 屏驱动全串了一遍非常适合刚上手 C2000 系列、又想做个带交互界面的嵌入式小伙伴参考。核心要解决的矛盾其实很典型PWM 频率不能只写在初始化代码里必须支持运行时动态调整同时显示刷新又绝不能干扰 PWM 输出的稳定性。我整理了一下完整思路、具体代码和调试时踩过的坑直接照着抄就行。1. 项目思路与方案选型1.1 为什么选 TMS320F28069 做可调 PWM我手里这块板子是 TMS320F28069TI C2000 系列里非常经典的浮点 DSC主频最高 90MHz。很多人会问做个可变频率 PWM 是不是用 STM32、树莓派甚至 NE555 就行确实可以STM32 的定时器输出 PWM 也很方便树莓派的 PWM 波在 Linux 下改 sysfs 接口也能动态调NE555 甚至能实现频率固定、占空比可调的 PWM 电路。但 C2000 系列的 ePWM 模块是为数字电源和电机控制设计的它自带死区、故障保护、互补输出、高分辨率模式这些才是做功率驱动时真正救命的特性。F28069 的 ePWM 模块不是简单地靠定时器中断翻转 IO 模拟 PWM它由时基子模块、比较子模块、动作限定器、死区子模块、斩波子模块和故障保护子模块组成意思是 CPU 只要把参数写进寄存器后续每一个 PWM 周期都由硬件自己完成完全不需要软件干预。做可调频率 PWM 时我们只需要在运行时修改时基周期寄存器 TBPRD硬件会自动按新周期输出。这对于“频率可调”这个需求来说是从架构上就支持得很顺的工具。1.2 可调频率怎么实现才靠谱实现可变频率最蠢的办法是每次按键后把整个 ePWM 模块重新初始化一遍停止输出、改周期、再启动。这样做的后果是输出会出现一段不确定的毛刺在驱动电机或加热器时可能瞬间产生大电流冲击。正确做法是利用 ePWM 的影子寄存器机制我先把新值写给影子寄存器等时基计数器计数到一个特定事件比如周期结束或归零时硬件自动把影子值加载到当前寄存器。整个过程连续无缝频率跳变时不会出现明显的断流。所以核心设计就变成在按键事件中计算目标频率对应的 TBPRD 和目标占空比对应的 CMPA把它们写入影子寄存器交给硬件去更新。液晶显示只负责把当前参数反映给人看显示刷新放到主循环里慢慢做不参与任何 PWM 控制闭环。1.3 整体模块划分整个工程可以拆成三块模块作用实现方式输入交互调节频率和占空比两个按键或旋转编码器扫描后处理PWM 引擎输出可调频率/占空比方波F28069 ePWM1A/1B影子寄存器更新显示反馈展示当前频率和占空比SPI 接口液晶屏主循环刷新按键我用的是一路 GPIO 轮询加软件消抖如果你手头有旋转编码器解码逻辑也很容易移植原理一样。显示部分考虑到引脚紧张我选了 SPI 接口的 1.8 寸 TFT 屏只需要 4 根信号线。如果你只有 LCD1602 也能做驱动逻辑只会更简单。2. ePWM 模块频率、占空比和“保护”一起搞定2.1 时基时钟配置和周期关系ePWM 的时基子模块是整个输出的心跳。外部晶振或内部 OSC 经过 PLL 倍频到 90MHz 系统时钟再经过外设时钟分频得到 EPWMCLK最后还能通过 TBCTL 里的 CLKDIV 和 HSPCLKDIV 进一步分频得到 TBCLK。时基计数器在每个 TBCLK 上升沿加一或者减一。F28069 的时基计数器支持三种模式向上计数、向下计数、向上-向下计数。我用的是 up-down 模式这种模式下 PWM 波形中心对称适合电机控制和各种需要减小谐波的场合。频率计算公式如下fPWM TBCLK / (2 * TBPRD)举个例子假设 TBCLK 为 10MHz想要 10kHz 的 PWM那么 TBPRD 10,000,000 / (2 × 10,000) 500。这个值放在 16 位寄存器里毫无压力。如果要做超低频比如 1Hz同样 TBCLK 下 TBPRD 需要 5,000,000已经超过 16 位上限所以你需要在低频档主动把 TBCLK 分频降下来让 TBPRD 回到可表示范围内。这就是频率分档的本质原因。2.2 占空比更新与 100% 占空比的边界周期定了之后占空比由比较值 CMPA 决定。up-down 模式下时基计数器向上递增到 CMPA 时动作限定器置高向下递减到 CMPA 时动作限定器置低这样就产生一个以 CMPA 为中心对称的方波。占空比与比较值的关系是Duty% CMPA / TBPRD × 100%所以如果要保持占空比不变修改 TBPRD 时必须同步等比缩放 CMPA。这个细节最容易翻车。比如原来 TBPRD1000CMPA250占空比 25%我把频率调高一倍TBPRD 改成 500如果 CMPA 还是 250占空比瞬间变成了 50%这对后级负载来说是一次明显的冲击。所以在我后面的代码中无论是调频率还是调占空比都用百分比换算保证两条路径都走同一个计算函数。这里还要提醒一个 100% 占空比的坑。很多人写 PWM 到极限直接把 CMPA 设成等于 TBPRD想得到全高输出。但不少定时器在比较值等于周期值时会出现异常翻转现象STM32 的定时器也有类似怪癖网上“stm32定时器输出pwm时100%占空比异常”就是这一类的典型问题。F28069 的 ePWM 要输出恒高正确做法是改用强制动作直接把 AQCTLA 的 ZRO 设置为 AQ_SET或者用故障强制通路而不是死磕 CMPA 边界。2.3 死区控制和故障保护不是可选项如果你是拿 PWM 去驱动半桥或全桥电路死区绝对不能省。F28069 的 ePWM1A 和 ePWM1B 天然就是互补输出对经过死区子模块可以分别设置上升沿延时 DBRED 和下降沿延时 DBFED。我曾经在 28335 上被 DBRED/DBFED 这两个词的语义坑过一次当时天真地以为 DBRED 是“dead band rising edge delay”直接在互补桥上给上下管各加了几百个时钟周期结果波形上升沿和下降沿全乱套了。现在在 F28069 上我会严格对照技术参考手册的时序图先拿示波器看实际死区时间再反向校准寄存器值。典型 MOSFET 驱动死区时间在几百纳秒到 1 微秒量级具体以你用的管子栅极电荷和驱动芯片参数为准。故障保护模块 TZ 更是功率电路的保命符。它的原理是一旦 TZ 引脚检测到外部故障信号硬件立即将 PWM 引脚强制到指定电平高/低/高阻这个过程不经过 CPU响应时间在纳秒级。我在初始化时就把 TZ1 配置成了故障源动作设为强制拉低。这样哪怕 CPU 死机或者程序跑飞功率管也不会因为失控而直通炸机。3. 液晶显示接入和刷新策略3.1 选 SPI 接口屏的理由显示模块我最终选了 SPI 接口的 ST7735 TFT 屏而不是并行总线的 LCD。原因很简单F28069 的引脚本来就紧张并行屏要占 8 根以上数据线加控制线画原理图和布线都很痛苦。SPI 屏四根线搞定而且市面上驱动库非常成熟稍微改一下底层的 SPI 读写函数就能跑起来。如果你手头只有 LCD1602那更简单4 位并行模式加两个控制脚原理完全一样。TFT 屏的分辨率是 160×128对显示“F: 10000 Hz”和“D: 50.0%”这种纯文本信息绰绰有余。我甚至把波形模式、故障状态也画了上去界面可以做得比较友好。3.2 显示布局和刷新时机我的界面布局大概是这样第一行显示 PWM 频率比如“F: 10.000 kHz”第二行显示占空比“D: 50.0%”第三行显示当前计数模式“UP-DOWN”和状态信息。刷新时机这块我想强调一个原则显示刷新和人机交互可以在主循环里慢慢做但 PWM 寄存器更新必须放到精确的时间基准里。我实测过如果在 ePWM 周期中断里调用 SPI 写屏函数一个中文字模的数据写进去要几百微秒期间中断被长时间占用PWM 更新就可能产生 jitter。所以我的架构是按键按下后只修改一个“参数待更新”标志主循环检测到标志后计算新参数并写入 ePWM 寄存器然后再刷新显示。这样 PWM 更新和显示刷新互不阻塞实际测试波形非常稳定。4. 实战参数设计与代码实现4.1 按键输入、消抖和频率分档我用了三个按键一个选频率档位粗调/细调、一个增加、一个减少。频率调节做成两段式长按连续递增短按单步微调。这里没有用 Timestamp 搞复杂状态机就是 10ms 定时中断里扫描 GPIO连续读到两次稳定电平才认为按键有效再配合一个计数器实现长按变速。效率不高但足够稳定。频率档位设计上我分成了三档方便兼顾低频和高频频率范围TBCLK 分频TBPRD 计算典型应用20Hz ~ 1kHzTBCLK 降为 500kHzTBCLK/(2×f)范围大舵机控制、加热器1kHz ~ 10kHzTBCLK 为 5MHz250 ~ 2500呼吸灯、RC 电路10kHz ~ 100kHzTBCLK 为 20MHz100 ~ 1000电机驱动、数字电源表格里 TBCLK 不同本质上是通过修改 TBCTL 的 HSPCLKDIV/CLKDIV 实现的。每次换挡时我先把 PWM 的 CMPA 重置到安全占空比再切换 TBCLK再恢复目标占空比避免切换瞬间出现极其窄的脉宽。这个细节操作起来很小但对后级设备非常友好。4.2 核心代码设置频率与占空比核心代码我直接贴在下面。这套代码用 CCS 编写基于官方库但不依赖太重的外设抽象层方便你移植到自己工程里。#define TBCLK_HZ_20M 20000000UL #define MAX_TBPRD 65535 void pwm_calc_and_set(uint32_t freq_hz, uint16_t duty_percent) { uint32_t tbprd; uint32_t cmp; if (freq_hz 0) freq_hz 1; if (duty_percent 100) duty_percent 100; tbprd TBCLK_HZ_20M / (2UL * freq_hz); if (tbprd MAX_TBPRD) tbprd MAX_TBPRD; cmp (uint32_t)tbprd * duty_percent / 100; EALLOW; // 先停时基同步确保整个寄存器组原子更新 SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC 0; EPwm1Regs.TBPRD (uint16_t)tbprd; EPwm1Regs.CMPA.bit.CMPA (uint16_t)cmp; // 重新同步时基让所有ePWM模块一起开始 SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC 1; EDIS; }这里有个容易忽略的点EALLOW/EDIS 是 C2000 系列写保护寄存器所必需的忘记写会导致寄存器写不进去现象就是“改了半天频率输出完全没变化”。另一个点是 TBCLKSYNC 在配置期间拉低配置完再拉高所有 ePWM 时基一起启动避免多个 ePWM 模块之间相位错乱。初始化 ePWM1A 引脚复用时也别忘了配置 GPIO0 为 EPWM1A 功能否则你改再多的 TBPRD引脚上也不会输出波形EALLOW; GpioCtrlRegs.GPAMUX1.bit.GPIO0 1; // GPIO0 复用为 EPWM1A GpioCtrlRegs.GPADIR.bit.GPIO0 1; // 输出方向 EDIS;4.3 PWM 同步更新避免毛刺动态更新 PWM 有一个很隐蔽的问题如果你在主循环的不同位置分别写了 TBPRD 和 CMPA硬件加载影子寄存器时可能只抓到一半新数据造成一个周期内出现不期望的脉宽。最稳妥的办法是在周期中断里统一更新或者像我这样在写两个寄存器前暂时冻结时基同步。你甚至可以进一步用 ePWM 的周期中断来触发 ADC 采样这样采样点每次都落在 PWM 周期固定相位上测到的电压电流才有可比性。如果未来你还希望扩展出高分辨率 PWMF28069 也支持 HRPWM它能在常规 PWM 基础上做微步进调节等效分辨率可以做到 150ps 左右。这对数字电源的精细调压很有用思路是打开 HRCNFG 寄存器并配置 MEP 参数但普通可调频率显示项目用不上我这里就不展开讲等哪天做数字电源再单独写一篇。5. 实测中的坑和排查思路5.1 频率显示正确输出却不是预想值这个坑是我最初遇到最多的。现象是液晶显示频率已经变成 20kHz但示波器看输出还是 10kHz。排查下来发现是配置 TBCLK 分频时TBCTL 的 HSPCLKDIV 写错了位置导致实际 TBCLK 和计算值差了一倍。我的建议是调试时不要凭感觉先把 PWM 引脚接上示波器调一次频率就记录一次实际输出值和理论值对比。这样很容易发现分频链某处多除了一个 2。另外检查引脚复用。C2000 系列的 GPIO 默认很多是通用输入输出必须手动配置 GPAMUX 才能切换成外设功能。如果你改了 ePWM 寄存器但引脚没反应大概率就是 GPIO 复用没设置。5.2 占空比突变和毛刺我调试时遇到过调频率瞬间占空比跳变的现象典型的例子是频率从 10kHz 调到 5kHz占空比从 50% 飙到 60%。原因就是我前面说的 CMPA 和 TBPRD 没有联动缩放。后来我把所有设置统一收敛到pwm_calc_and_set()这个函数里任何调频调占空比操作都走百分比计算问题彻底消失。这个也算是嵌入式编程里“单一入口”思想的一个很现实的收益。5.3 故障保护和死区相关教训故障保护方面我第一次用它时犯了一个错误只配置了 TZ 动作但没有让引脚输出高阻结果故障时 PWM 引脚被钳位在高电平反而让功率管持续导通。后来我改成将 TZA/TZB 配置为高阻状态故障时整个输出变成断开这样才真正达到保护目的。死区配置也一样不是光设置一个 DBRED/DBFED 数值就完事要用示波器测出真正的死区时间根据你要驱动的器件开关速度去微调。这个调整在 28335 和 28069 上的寄存器细节略有不同换平台时千万别照抄一定重新查手册。5.4 显示数据更新的小问题显示部分我没遇到太多难啃的坑但有一个值得说SPI 屏初始化时如果不匹配像素偏移量寄存器画面会出现整体偏移或颜色错乱。这种情况百分之百是屏厂初始化序列不同造成的解决办法就是换你手里那款屏对应的初始化数组网上抄来的代码大概率要在 MADCTL 和显存偏移上改几行。再分享一个小经验如果你发现按按键后显示刷新很慢先别急着怀疑液晶驱动看看主循环里有没有别的大延时函数。我当时有一个毫秒延时函数在按键消抖里被反复调用屏幕刷新被严重拖后。最后我把消抖逻辑挪到定时中断里主循环只负责设置参数和刷屏流畅度一下就上来了。这个项目整体调试下来我的体会是做一个能动的可调 PWM 不难难的是让它在动态调节过程中不产生意外脉冲、不影响后级负载。F28069 的 ePWM 外设把大部分可靠性机制都做在了硬件里你只要理解了影子寄存器、TBCLK 分频和 CMPA 联动就能写出稳定可靠的可调 PWM 输出。后面我打算在这个基础上加一个频率参数掉电保存功能每次上电自动恢复上次的设置省得每次开机重新按按键。思路是外挂一个 SPI 接口的 EEPROM在按键停止变化后延时写入避免频繁擦写影响寿命。你要是有兴趣也可以往这个方向继续扩展。本文还有配套的精品资源点击获取
返回列表