资讯详情

资讯详情

PMC251定时器与外部中断编程:标志位处理、边沿触发及协同应用

简介面向应广PMC251单片机开发者的定时器与外部中断学习资源包内容涵盖定时器自由运行、捕获等模式配置以及外部中断边沿触发、标志位处理等关键知识点适合嵌入式入门者及需要快速上手该芯片的工程师。压缩包共18个文件容量约256KB包含C源码、PDK芯片配置文件、工程与编译辅助文件、中断边沿设置及中断请求示意图、readme说明等文件结构清晰便于对照工程查看配置逻辑。已有325人学习。通过阅读源码和图示可直观理解中断请求产生过程、软件清除中断标志位的必要性并能直接导入工程修改预装载值、中断优先级等参数为实时控制系统开发提供可复用的参考模板。1. 从一份PDK工程看PMC251的中断骨架在产线上经常遇到这种需求一个旋转编码器输出脉冲既要定时读取计数器又要在紧急信号到来时立刻停车。PMC251这类8位单片机在成本敏感场景下很常用但很多人直接操作寄存器时往往在“中断标志位没清干净导致重复进中断”上卡半天。这份PMC251定时和外中断的工程包包含PDK项目文件、C源码、PRJ工程和关键截图相当于把定时器和外部中断的完整骨架搭好了。本文把这个工程拆开讲透重点说清楚定时器的重装载机制、外部中断的边沿触发选择以及标志位该怎么清。适合正在用应广8位MCU做实时控制的工程师也适合想快速把中断框架跑起来的新手。2. PMC251定时器的工作模式与重装载配置2.1 定时器的时钟源与分频选择PMC251内部定时器的计数时钟可以来自指令周期或外部引脚实际工程里大多数情况是用内部时钟。内部时钟经过一个预分频器可以配置为1:1、1:2、1:4等比率。预分频的价值是当你想让定时器在低溢出频率下工作又不想让计数寄存器数值太大时用分频先降速。比如系统时钟4MHz要生成1ms中断如果分频1:1计数器需要涨4000次需要16位定时器或多次重装如果分频1:4只需要1000次8位定时器就够用。这个取舍在写配置代码之前要先算好。工程里PMC251CS14.C给出的配置是把预分频放在定时器控制寄存器里和中断使能位分开管理。我一般建议把分频比和装载值写在一起用宏定义方便以后换频率。例如#define F_CPU 4000000UL // 系统时钟 #define TIMER_TICK 1000 // 目标中断周期 1ms #define PRESCALER 4 // 1:4 分频 #define LOAD_VALUE (F_CPU / PRESCALER / TIMER_TICK - 1)这段代码的意思是先用系统时钟除以分频比得到实际计数频率再除以想要的中断周期得到计数次数减1是因为计数到0也包括一个时钟。LOAD_VALUE不能超过定时器位宽如果超了就拉大分频比或者改用更低的溢出频率。这里有个容易错的地方不要把LOAD_VALUE直接写到定时器装载寄存器里就叫完成任务还要看这个定时器是否支持硬件自动重载如果只支持单次计数就要在中断里手动把装载值写回来。2.2 自由运行模式与自动重装载的差异摘要里提到PMC251有自由运行、波特率发生器、捕获、比较模式但实际工程里最常用的是自由运行加自动重装载。自由运行模式下计数器从0递增到最大值溢出后重新从0开始如果启用了重装载寄存器则溢出后不是回到0而是回到LOAD_VALUE。这两种行为的差别在PWM和周期中断场景下非常明显使用硬件重装载中断周期和PWM占空比都不会受中断服务程序抖动影响而不使用硬件重装载你在中断里多了几条指令就会把计时周期拉长几微秒。在工程包中PMC251CS14.PDK已经帮你选好了工作模式但自己新写工程时要注意查看头文件里定时器模式的定义。下面给出一个典型配置代码段使用应广IDE的C语言写法// 定时器0初始化内部时钟1:4分频自由运行自动重载 T0M 0b00000100; // 选择内部时钟和分频比1:4 T0L (uint8_t)LOAD_VALUE; // 装载低字节 T0H (uint8_t)(LOAD_VALUE 8); // 装载高字节 T0IE 1; // 开定时器中断 EAL 1; // 总中断使能注意具体的寄存器名可能会因芯片型号和IDE版本不同而变化这里用的是解包工程中常见的宏写法实际编译时以头文件里的定义为准。这段代码逻辑很直白先配置定时器的工作模式和分频再写入16位装载值最后打开定时器中断和总中断。需要留意的是T0L和T0H是不是同时写入还是需要特定顺序要去看器件手册。我遇到过一些器件要求“先写高字节再写低字节”否则低字节写入会触发一个暂存过程顺序反了装载值会错一半。2.3 中断服务程序里的重装载陷阱在支持硬件自动重载的芯片上中断里其实不需要再写T0L/T0H但很多应用的“实时性”要求高会在中断里做一小段处理。这时候要小心一段代码执行时间不能超过中断周期否则下一个中断请求会排队或丢失。为了避免丢失很多工程师在中断入口先清除标志位再干活。PMC251的中断标志需要软件清除这点在工程截图中特别标注为“需要软件清除中断标志位.png”。void interrupt_timer0(void) { if (T0IF) { // 检查定时器0中断标志 T0IF 0; // 软件清除标志位必须立即清 // 这里放周期任务比如累加一个软件滴答定时器的tick计数 tick_count; // 如果开启了PWM或串口可以在此时更新相关寄存器 } }这段代码的逻辑是看到中断标志位T0IF置1说明定时器溢出条件满足先把它清零再执行业务代码。如果不清标志中断返回后会立即再次触发造成死循环式连续中断系统看起来像卡死在中断里实际上只是标志没清。还有一种情况是清除时序不对有些器件要求先读某个寄存器再清标志否则会漏事件。工程里的readme.txt建议把“清除标志”放在中断服务程序的第一行这是对的但要注意别在清除后才读取和溢出有关的计数数据否则可能已经滚动了。不同分频比下4MHz系统时钟对应的装载值可以按下面的表估算分频比计数时钟频率(4MHz)8位最大周期16位最大周期典型装载值(1ms)1:14MHz64us16.384ms39991:41MHz256us65.536ms9991:8500kHz512us131.072ms4991:16250kHz1.024ms262.144ms249表格说明在4MHz系统时钟下如果定时器只有8位用1:16分频才能实现1ms中断如果有16位计数器分频可以小一些计数精度更高。这个表在手算定时时长时非常有用建议贴在工程注释里。2.4 定时器作为串口波特率基准时要注意什么工程包里的“定时器”不只是做tick用它也可以作为波特率发生器。比如你用PMC251的UART波特率可能就是从定时器溢出时钟派生出来的。如果此时还同时用作普通中断就有个优先级和竞争问题。常见做法是专门用一个定时器做波特率时钟不开放它的中断只让硬件在溢出时翻转自身分频另外用一个定时器做周期中断。这样两者互不干扰。配置波特率定时器的步骤一般是先算好UART要求的位周期再反推定时器分频和装载值。由于波特率一般有2%的误差容忍装载值通常用四舍五入而不是直接截断。例如9600波特率位周期约104.17us假设计数时钟1MHz则装载值应该是104但104.17更接近104误差约0.16%没问题。如果你贪图简单用104也是对的。但如果装载值取100误差4%就会出现乱码。3. 外部中断的边沿触发与标志位处理3.1 边沿触发设置上升沿还是下降沿外部中断在PMC251上一般占用几个I/O口通过配置中断控制寄存器来选择上升沿触发、下降沿触发或者电平变化触发。工程里专门有一张“中断边沿设置.png”的截图说明作者当时在这个点上是踩过坑的。选择上升沿还是下降沿取决于你的信号源光电编码器输出通常是一个开漏输出加外部上拉静止时是高电平转动时产生低脉冲这时候用下降沿触发就能捕捉到每个低脉冲前沿如果是按钮接地平时接VCC按下为低电平同样用下降沿。反过来如果你要捕捉一个信号从低变高的时刻比如电源就绪信号那就用上升沿。实际配置时要注意边沿触发和电平触发在标志位清除逻辑上不同。边沿触发的中断标志会锁存即使你在进中断后立刻清标志只要再有边沿就会再次置位。电平触发则不同如果引脚电平保持有效标志清除后还会立即重新置位所以电平触发模式下需要额外判断电平是否真的恢复。PMC251外部中断支持边沿触发这是工程里推荐的方式。3.2 外部中断初始化代码模板结合工程包的结构写一段外部中断初始化代码以INT0为例// 外部中断初始化下降沿触发 INT0S 0; // 0表示下降沿触发1表示上升沿触发 INT0E 1; // 使能INT0外部中断 EAL 1; // 打开总中断这段代码只有三行但顺序很重要。有些器件要求先设好边沿选择再打开中断使能否则在使能瞬间如果引脚电平已经满足触发条件会立刻产生一个虚假中断。我一般会在配置前先读一下引脚电平或者把中断标志位清零再加一条NOP延时让状态稳定。对于PMC251我建议这个顺序先设边沿再清标志位最后使能中断。3.3 中断标志位为什么必须软件清除工程中有一个截图叫“需要软件清除中断标志位.png”这几乎是所有用应广单片机的人都会遇到的第一个坑。在PMC251的外设设计中中断请求寄存器INTF的对应位不会因为进入中断自动清零需要用户在中断服务程序里写0。如果不写CPU会反复响应同一个中断请求即使你的主程序什么都没做也会一直卡在中断里。下面这段中断服务程序演示了正确的清除时机void interrupt_int0(void) { if (INT0F) { // 检查INT0中断标志 INT0F 0; // 先清标志再处理业务 // 记录边沿时间戳、计算脉冲数等 pulse_count; } }在大多数情况下“先清标志再处理”能避免重复触发。但有一个例外如果你希望知道中断到来时刻的引脚电平那就得在读引脚电平之后清标志否则数据已经过去了。比如你想判断这次下降沿之后引脚是否马上恢复高电平就要先读寄存器再清标志。这个顺序问题在调试时容易忽略。3.4 用中断标志位快速定位问题调试外部中断不触发最直接的办法是看INTF寄存器有没有置位。工程里给出了“中断请求.png”截图说明作者是通过观察中断请求状态来区分“信号没进来”和“中断没使能”的。如果INTF对应位一直是0说明引脚上没有正确边沿去查硬件连接和输入模式如果INTF对应位是1且一直不消失说明进入了中断但没清标志或者中断服务程序没有正确响应。这个排查思路比单纯猜代码要快得多。触发方式的选择可以参考下表触发方式中断标志行为典型应用场景注意事项上升沿锁存需软件清检测信号启动时刻电平变化要足够快下降沿锁存需软件清按钮、编码器脉冲标志不清会反复进中断电平触发有效电平期间反复置位紧急停车信号必须同时读电平判断是否释放注意上表里“标志行为”是我在调试时留的疑点不同系列可能有差异。应广PMC251从工程截图看是需要手动清除的这点要以数据手册为准。3.5 外部中断的硬件去抖与软件去抖机械按钮或继电器触点会产生抖动一次按下可能产生几十毫秒的多个边沿。PMC251的外部中断是硬件边沿触发无法自动去抖所以必须靠软件处理。常见做法是在中断里记下当前的时间戳然后与上一次中断时间比较uint16_t last_int_time; void interrupt_int0(void) { if (INT0F) { INT0F 0; if ((tick_count - last_int_time) 10) { // 大于10ms理解为有效 pulse_count; last_int_time tick_count; } } }这里的tick_count来自定时器中断的累加值时间基准是上一章的1ms中断。通过差值大于10ms把10ms内的重复边沿全部忽略。这个办法在测量电机转速时特别有用能滤掉碳刷火花带来的毛刺。如果要求更高可以再加一个滑动窗口滤波但PMC251资源有限用软件时间戳就足够了。4. 定时器与外部中断协同构建一个可复用的帧接收逻辑4.1 为什么用定时器做串口超时判断在Modbus这类半双工通信协议中一帧数据是连续的帧与帧之间有一个静默区间。最稳妥的做法是串口每收到一个字节就重置一个定时器如果定时器溢出就认为这一帧已经结束。PMC251同时具备UART和定时器但很多低端8位MCU没有硬件帧超时检测所以工程里经常用定时器中断来实现。这个场景正好把定时器和外部中断结合外部中断负责检测UART的起始位边沿定时器负责超时判断。虽然UART本身有硬件接收中断但起始位边沿检测可以用外部中断也可以用UART的RX脚中断视型号而定。这里我们说一个更通用的做法用外部中断捕捉RX引脚的下落沿作为一帧开始信号然后启动定时器定时器的装载值对应3.5个字符的时间。每次接收到一个字节主循环里重新装载定时器。定时器溢出时置一个“帧接收完成”标志。这样既不用改动UART驱动又能在帧结束时及时处理。4.2 协同逻辑的状态设计为了保证实时性不建议在中断里做复杂的协议解析。更清晰的做法是设一个状态机状态切换放在主循环里中断只负责置位和计数。下面这个过程可以用状态转移来描述但不画图只用代码和步骤说明。外部中断捕捉到RX起始位置START_FLAG。主循环看到START_FLAG后启动定时器装载3.5字符时间开启超时中断。UART每收到一字节UART中断里调用一次reset_timer()重新装载。定时器溢出说明超过3.5字符时间没有新字节置FRAME_DONE_FLAG。主循环看到FRAME_DONE_FLAG取出缓冲区的数据复位状态。这样安排定时器中断很干净只是复位一个计数外部中断也很干净只置位。很大的优势是调试时可以直接观察三个标志位不用理清中断嵌套。4.3 可参考的代码骨架下面给出一个简化的代码骨架假设系统时钟4MHz9600波特率3.5字符时间约为4ms实际计算是一个字符11位9600波特率下单字符约1.146ms3.5字符约4.01ms所以定时器周期取4ms。#define FRAME_TIMEOUT_US 4000UL #define LOAD_FRAME (F_CPU / PRESCALER / 1000000UL * FRAME_TIMEOUT_US) void reset_frame_timer(void) { T0L (uint8_t)LOAD_FRAME; T0H (uint8_t)(LOAD_FRAME 8); T0IF 0; // 清标志重新开始计数 T0IE 1; // 确保中断使能 } void interrupt_timer0(void) { if (T0IF) { T0IF 0; frame_done 1; // 帧超时到达 T0IE 0; // 关闭超时中断等待下一帧 } }这里的关键是reset_frame_timer函数它重建装载值并清标志。注意在中断里重装载时一定要先写高字节再写低字节或者反过来否则可能出现旧值高字节新值低字节的混合。我在调试时遇到过类似问题后来加了几个NOP指令才稳定。4.4 中断优先级先响应急需处理的还是先响应时间精确的PMC251的优先级控制相对简单但设计不好也会出问题。比如外部中断用来测高速脉冲定时器用来做系统滴答。如果外部中断频繁进入定时器中断被延误滴答就不准如果定时器中断优先级过高外部中断响应变慢就会丢脉冲。我的实践是把定时器中断设为高优先级因为它提供时间基准外部中断设为低优先级并在外部中断服务里只做最短处理剩下的计算放主循环。如果芯片支持嵌套中断还要考虑是否需要禁止定时器中断以避免外部中断被打断。工程里的readme.txt虽然没有细说优先级但从中断服务程序都是清标志置位来看作者是有意保持中断极简的。中断源职责中断服务程序内容典型优先级定时器0系统tick/帧超时清标志、计数器、置超时标志高外部中断边沿捕捉清标志、记录时间戳、置开始标志低这样做的好处是即便外部中断响应延迟了几个微秒时间戳也能通过定时器的tick推算出来。5. 用逻辑分析仪验证中断时序边沿、标志位与毛刺处理5.1 先测标志位再测代码很多新手调中断上来就单步跟代码其实效率很低。我先用逻辑分析仪同时抓三个信号外部中断输入引脚、定时器溢出输出如果有和中断标志位对应的测试IO。工程里给出的“中断请求.png”应该就是类似目的。你可以临时在中断服务程序里翻转一个测试IO逻辑分析仪就能直接看到中断是否在预期的时刻触发以及中断服务程序耗时多少。比如你期望1ms中断一次实际测试发现1.6ms一次那一定是装载值算错了或者分频不对。5.2 毛刺的处理技巧外部中断引脚如果没做滤波一根长线或者电机电磁干扰就能让中断频繁触发。PMC251的输入结构不一定有可配置的施密特触发或数字滤波所以硬件上我一般会加一个100pF到1nF的电容到地配合电阻构成低通滤波。软件上再用时间戳去抖双保险。需要注意加电容后边沿会变缓如果信号是低频开关量没问题但如果是高速编码器电容太大会导致边沿时间超过阈值直接丢中断。一般建议用示波器看边沿让RC时间常数小于脉冲宽度的1/10。5.3 一个验证定时器装载值的小技巧为了防止“装载值算错但能跑”的情况我习惯在定时器中断里做一次计数输出。比如把全局变量tick_count每隔256次翻转一个测试IO逻辑分析仪测量高电平宽度如果是0.256秒假设1ms中断256次那说明装载值时基正确。这个方法不需要改动中断逻辑只是加一个条件判断非常实用。5.4 排错清单最后整理一个简易排错清单工程包里虽然没有写但根据C源码和截图的顺序可以反推出来中断不触发先读INTF寄存器确认中断请求是否到达再用示波器看引脚有无正确边沿再检查全局中断EAL是否开启。中断反复触发一定是标志没清或者电平触发模式下电平没有恢复先查清标志代码位置。定时误差大用逻辑分析仪测溢出周期然后检查分频、装载值、中断处理耗时。外部中断与定时器互相影响区分优先级缩短服务程序把重活丢给主循环。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →