#训练营# 基于GD32E230的简易数字示波器项目实战(一):按键中断与PWM波形控制详解
基于GD32E230的简易数字示波器项目实战一按键中断与PWM波形控制详解最近在捣鼓一个用GD32E230做的简易数字示波器发现很多刚接触嵌入式的小伙伴对中断和PWM波形控制这块儿有点发怵。正好借着这个项目咱们来好好聊聊怎么用外部中断和定时器中断来优雅地处理按键和旋转编码器从而控制PWM波的频率、占空比甚至ADC的采样时间。这篇文章就是带你手把手走一遍这个流程就算你是零基础跟着做也能搞明白。1. 项目概览与核心思路这个项目是在立创EDA训练营里做的硬件部分咱们就不多说了重点放在软件实现上。整个程序的核心目标是让GD32E230这块单片机能够输出一个频率和占空比都可调的PWM波模拟信号源并且能通过按键和旋转编码器来灵活控制它。传统的做法可能是在main函数的while循环里不停地去“扫描”按键有没有被按下。这种方法简单但有个大问题太占CPU时间了CPU大部分精力都花在反复检查几个引脚的电平上了干不了别的正事。所以咱们这个项目采用了更高效的方法中断。简单来说就是让硬件按键、编码器、定时器在需要CPU处理的时候主动“打断”CPU当前的工作CPU处理完这个紧急事件后再回去接着干原来的活。这样CPU就解放出来了效率高得多。整个控制逻辑的核心就围绕着两个中断服务函数展开定时器中断每隔10ms触发一次在里面处理按键消抖和状态读取。外部中断当按键或旋转编码器的状态发生变化时比如按下、旋转立刻触发在这里根据当前的“模式”去调整PWM的频率、占空比或ADC采样时间。下面咱们就一步步拆解看看是怎么实现的。2. 按键处理的“心法”中断消抖按键消抖是嵌入式开发里老生常谈但又必须面对的问题。机械按键在按下和松开的瞬间金属触点会有一个短暂的、不稳定的物理抖动过程反映到电信号上就是电平会快速跳变几次。如果程序直接读取可能会误判为按下了很多次。通常的软件消抖方法是在检测到按键按下后延时10-20ms等抖动过去了再读一次状态。但中断服务函数有一个重要的原则要快进快出绝对不能在里面进行长时间的延时操作否则会影响其他中断的响应甚至导致系统异常。那怎么办呢咱们这个项目借鉴了一个很巧妙的思路把消抖逻辑放到定时器中断里去做。2.1 定时器配置10ms的心跳首先我们需要一个精准的“心跳”来定期检查按键状态。这里使用了GD32E230的定时器15TIMER15把它配置成每隔10ms产生一次中断。相关的配置代码在TIM.c文件里主要就是设置定时器的时钟源、预分频值和自动重装载值最终让中断周期为10ms。初始化完成后定时器15就会像节拍器一样每10ms“滴答”一次并触发中断。2.2 核心函数Key_GetNum()真正的按键状态判断逻辑封装在Key_GetNum()这个函数里。它不在main循环里被调用而是被放在定时器15的中断服务函数TIMER15_IRQHandler中。这意味着它每10ms被执行一次。这个函数的精妙之处在于它的判断逻辑。它不直接说“按键现在被按下了”而是通过对比上一次的按键状态和当前的按键状态来判断按键动作是否“完成”。咱们来看看它是怎么工作的结合原理解释函数内部会读取所有按键引脚比如PB13对应KEY1的当前电平current_state。同时它保存着上一次执行时读取到的状态last_state。关键判断来了如果current_state为0按键按下假设低电平有效而last_state为1上次是松开那么就可以确定KEY1刚刚被按下了一次。此时函数返回KEY1的键值。如果按键一直被按着那么current_state会一直为0last_state也会被更新为0函数就不会返回新的键值避免了重复触发。只有当按键从“按下”变为“松开”时才会根据last_state判断出是哪个键松开了虽然本项目主要响应按下事件但这个逻辑是完备的。这样一来每隔10ms“采样”一次按键状态既能有效过滤掉毫秒级的机械抖动又不会在中断里进行耗时操作。Key_GetNum()函数本身只包含简单的读取、比较和赋值操作执行速度极快完全符合中断服务函数的要求。提示你可以把Key_GetNum()函数想象成一个每隔10ms给按键拍一张快照的相机通过连续两张照片的对比来判断按键是否发生了有效的动作变化。3. 控制逻辑的“大脑”外部中断服务函数按键的状态我们知道了那怎么用它来控制PWM波呢这就要靠另一个重要的中断——外部中断EXTI。旋转编码器和按键的引脚都被配置为外部中断触发模式。当它们的状态发生变化比如按键按下、编码器旋转产生脉冲时会立刻触发名为EXTI4_15_IRQHandler的中断服务函数。这个函数就是整个波形控制系统的“大脑”。3.1 函数的工作流程这个函数主要干两件事判断是谁触发的中断通过读取中断标志位确定是哪个引脚对应哪个按键或编码器产生了中断。执行对应的控制逻辑根据当前被按下的功能键KEY1, KEY2, KEY3以及旋转编码器的转动方向去修改不同的全局变量从而改变PWM波或ADC采样。3.2 旋转编码器的防误判处理旋转编码器在转动时A、B两相会输出相位差90度的方波。通过判断相位关系可以知道是顺时针还是逆时针旋转。但在实际中可能会因为接触不良或抖动产生误判。项目中使用了两个变量cnt1和cnt2来存储编码器两个通道的“旋转量”只有当两个通道的变化量都达到一定阈值KeyThresholdValue时才认为发生了一次有效的旋转。这相当于一个简单的软件滤波能有效减少误触发。3.3 四大控制模式详解外部中断函数实现了四种控制模式通过按下不同的功能键来切换当前按下的功能键旋转编码器顺时针旋转编码器逆时针控制的参数KEY1开启PWM波输出关闭PWM波输出PWM输出使能KEY2增加PWM占空比减少PWM占空比占空比 (Pulse)KEY3增加PWM输出频率减少PWM输出频率频率 (AutoloadValue)旋转编码器按下增加ADC采样时间减少ADC采样时间ADC采样周期1. 模式切换KEY1开关PWM输出按下KEY1后旋转编码器就变成了一个开关。顺时针转打开PWM输出使能定时器14逆时针转关闭PWM输出失能定时器14。这是通过一个标志位Timer14flag来实现的。2. 占空比调节KEY2按下KEY2进入占空比调节模式。此时旋转编码器控制的是一个叫Pulse的变量它决定了PWM高电平的时间。每次有效旋转Pulse会增减一个步长deltaDutyRatio比如0.042即4.2%。程序中还对占空比做了限制防止设置得过小或过大比如小于0.6%会重置为75%大于100%重置为25%。这个限制值可以通过修改宏定义RatioThresholdValue来调整。原作者踩的坑这里有个小bug由于浮点数精度问题占空比的变化可能只能精确到小数点后两位。如果你对精度要求高可能需要用整数运算来替代浮点数。3. 频率调节KEY3按下KEY3进入频率调节模式。此时旋转编码器控制的是定时器的AutoloadValue自动重装载值这个值直接决定了PWM波的频率。频率变化不是线性加减而是比例变化。顺时针转频率变为原来的(1 deltaFreRatio)倍逆时针转频率变为原来的(1 - deltaFreRatio)倍。例如deltaFreRatio设为0.8当前频率1kHz。顺时针转一次频率变为1.8kHz逆时针转一次频率变为200Hz。程序中设置了频率上下限约16Hz到40kHz。当调到极限值时屏幕会给出提示并短暂锁定调节功能防止误操作。4. 采样时间调节按下编码器直接按下旋转编码器它本身也是一个按键进入ADC采样时间调节模式。采样时间有固定的几个档位如239.5、71.5、55.5、41.5个ADC时钟周期旋转编码器用于在这些档位间切换。档位越多采样精度可能越高但转换时间也越长。注意ADC采样时间的可选值定义在GD32的标准外设库文件gd32e23x_adc.c中。你可以用IDE的搜索功能CtrlF查找ADC_SAMPLETIME_239POINT5等关键字看到所有可用的选项。4. PWM波形生成与刷新机制控制逻辑修改了全局变量最终要靠PWM生成部分来执行。项目里有两个关键的函数PWM_PulseAdjustment(): 根据最新的Pulse占空比值重新计算并配置定时器的通道比较值。PWM_FrequencyAdjustment(): 根据最新的AutoloadValue频率相关值重新配置定时器的周期。这些函数被Key_ControlPWM()函数调用。而Key_ControlPWM()函数则是在main函数的while循环中不断执行的。它主要做三件事控制PWM输出开关检查Timer14flag为0就关闭失能定时器14为1就开启使能定时器14。检查参数变化通过比较Pulse、AutoloadValue、SampleTime等变量和它们临时保存的上一个值Pulse_temp等来判断用户是否调节了参数。刷新屏幕显示只有参数确实发生了变化才去调用屏幕刷新函数更新频率、占空比等信息的显示。这样做避免了屏幕不必要的频繁刷新让显示更稳定。5. 实战总结与避坑指南走完整个代码流程咱们来总结几个关键点和容易踩的坑1. 中断服务函数要“短平快”这是中断编程的金科玉律。像消抖这种需要延时的操作一定要想办法移出去。本项目用“定时器中断状态机判断”的方式完美解决了在中断中不能延时的问题。2. 全局变量的使用要谨慎这个项目里Pulse、AutoloadValue、Timer14flag等控制参数都定义成了全局变量在中断函数和主循环函数之间传递数据。虽然对于小项目这样写方便但不利于代码的模块化和维护。更好的做法是使用结构体来封装相关变量或者通过函数参数来传递。3. 浮点数精度问题在占空比调节部分原作者提到了因为浮点数精度导致的bug。在嵌入式系统中特别是没有硬件FPU的芯片上大量使用浮点运算会影响性能。对于占空比、频率这些参数可以考虑用整数比如0-1000代表0%-100%来表示和计算最后再转换为实际需要的寄存器值。4. 编码格式要统一原作者在文档里特别提到了乱码问题这是因为中途切换了编码格式GB2312和UTF-8混用。这虽然不影响程序运行但非常影响代码的可读性和可移植性。务必在项目开始时就统一好编码格式推荐UTF-8并在整个团队和所有工具中保持一致。5. 硬件连接很重要文档最后提到一个硬件问题最初用了只有10x衰减档的示波器探头作为输入导致测量频率和电压都不准。换用可切换1x/10x的探头并在软件中正确设置衰减比后问题解决。这提醒我们调试时软件逻辑没问题也要回头检查硬件连接和器件选型是否正确。好了关于这个简易数字示波器的按键中断和PWM控制部分核心内容就是这些。理解了中断如何分工协作定时器中断处理消抖外部中断执行控制以及全局变量如何在中断和主循环间传递状态你就掌握了这种高效事件驱动编程的精髓。下一步你就可以去研究ADC采样和波形显示的部分了。