尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PMC251定时器与外部中断合流:OTP单片机中断设计实战

PMC251定时器与外部中断合流:OTP单片机中断设计实战 简介面向应广PMC251单片机的定时器与外部中断开发示例适合正在学习8位单片机中断编程或需要快速搭建PMC251定时功能的嵌入式开发者。资源包内含完整工程文件、C源码、头文件及工程配置文件并配有中断边沿设置、中断请求、软件清除中断标志位等关键配置的界面截图可对照代码理解中断从触发到响应的完整流程。压缩包共18个文件以源文件.c/.h、工程配置文件.prj/.pdk/.pre和说明文档.txt为主整体大小仅256KB结构精简便于直接查看。已有325人学习下载适合需要参考PMC251定时器自由运行、捕获模式、比较模式以及外部中断边沿/电平触发配置的开发者。通过对中断标志位处理、定时器预装载值设置和中断服务程序编写等细节的示例演示能帮助读者减少重复排查时间快速验证定时与外部中断结合使用的控制逻辑。1. 定时器和外部中断在 PMC251 上当不成两个独立中断PMC251 定时器和外部中断这个标题真正要解决的问题是手头只有一颗 8 脚或十几脚的 OTP 单片机定时器负责时间基准外部中断负责捕捉外部信号两者还要在一个中断入口里互相不干扰地并存。PMC251 这类应广芯片的定位和 51 不一样51 有独立的中断向量应广很多小资源型号只给一个中断入口所有中断标志在一个函数里排队处理。这个差异直接决定了写法和排错思路定时器不是拿来延时的外部中断也不是拿来写业务代码的两个事件的判断顺序错了稳定运行的项目也可能随机丢数据。下面的内容按定时器原理 → 外部中断配置 → 两个中断合流 → 可复用的测量技巧展开适合用应广做小家电、玩具、传感器采集和低成本控制板的工程师。2. PMC251 定时器的计数结构、初值计算与滴答定时器搭建2.1 Timer0 的计数回路预分频、溢出和自动回绕应广 PMC251 的定时器模块一般就是 Timer0一个 8 位向上计数计数器。意思是计数器按固定节拍加一从某个初值一路加到 0xFF再加一次就溢出硬件把 T0IF 标志位置起来。如果 T0IE 使能且总中断已打开CPU 就会进入中断服务函数。这跟 51 的定时器思路是同源的但寄存器模型更简化51 要同时管 TH0、TL0、TMOD、TCON 四类寄存器应广这边用 T0M 选预分频、T0C 写初值、T0IF 查标志、T0IE 使能中断四个寄存器各管一件事。计数时钟先经过预分频再进计数器预分频是粗调T0C 是细调。溢出后计数器会自动回到写进 T0C 的初值继续跑这一点很多人会在中断里再写一次 T0C属于典型的 51 惯性思维。对 PMC251 这类内核来说中断里重复写 T0C 不仅多余还可能在临界处吞掉一个 tick让定时周期不是变准而是变乱。2.2 定时周期计算公式与常用参数表溢出周期的计算公式是T_溢出 (256 - T0C初值) × 预分频系数 / Fsys其中 T0C 的取值范围是 0 到 255。特别注意T0C 写 0 不代表周期是 0而是代表 256。第一次做滴答定时器时这个位置最容易算错一错就是一个 tick 的时间差后面做脉冲间隔测量或软串口时全部偏掉。定时目标预分频系数T0C 初值十六进制实际溢出周期250us1:160x83(256-131)×16/8MHz 250us500us1:160x06(256-6)×16/8MHz 500us1ms1:640x83(256-131)×64/8MHz 1ms2ms1:640x06(256-6)×64/8MHz 2ms表格假设系统时钟 8MHz。把初值 0x83 从预分频 1:16 换成 1:64周期从 250us 变成 1ms放大四倍这就是预分频的作用。选参数时先定目标周期再反推装填值T0C 256 - T_目标 × Fsys / 预分频除得尽就优先用这个组合除不尽就换一挡预分频别硬凑。OTP 芯片烧进去就改不了上板调试前先在仿真器或计算器上把组合验算一遍。2.3 1ms 滴答定时器最小代码下面代码使用应广官方头文件的寄存器名具体宏定义以你手上的型号头文件为准。// 系统时钟 8MHzTimer0 预分频 1:64 // 溢出周期 (256 - 131) * 64 / 8MHz 1ms volatile unsigned int tick_ms; void timer0_init(void) { T0M 0b011; // 预分频 1:64数值含义见型号头文件 T0C 0x83; // 初值 131计数 125 次后溢出 T0IF 0; // 清残留标志 T0IE 1; // 允许 Timer0 中断 INTEN | 0b00000010; // 中断源使能位按头文件核对 ENGINT 1; // 打开总中断 } void interrupt_handler(void) // 入口名沿用官方例程或项目模板 { if (T0IF) { T0IF 0; // 清标志清除方式以手册为准 tick_ms; // 1ms 滴答 } }T0M 写 0b011 是假设当前型号把它定义为 1:64如果你的头文件里提供了 T0M_DIV64 这类宏就直接用宏可读性更好。T0C 写 0x83 之后计数器从 131 向上走需要 125 次计数才溢出每次计数耗时 64/8MHz8us总周期正好 1ms。T0IF 清零是关键步骤漏掉一次会让中断连续触发现象就是主循环卡死清法是写 0 还是写 1 要查手册不要照搬其他型号。2.4 滴答定时器驱动软时钟有了 tick_ms 之后主循环里最常见的用法是定时执行周期任务。我一般不用模运算判断是否到 500ms因为应广这类低成本内核的指令集没有硬件除法取模会引入库函数调用白白吃掉主循环的时间。更稳的做法是倒计时变量volatile unsigned char timer_100ms; unsigned char remain_500ms; // 中断里每累计 100 次 tick_ms 就置位 timer_100ms void loop(void) { if (timer_100ms) { timer_100ms 0; if (remain_500ms) { remain_500ms--; if (remain_500ms 0) { remain_500ms 5; // 500ms 到执行周期任务 } } } }倒计时变量由主循环消耗中断只负责维护 tick_ms 和触发标志。主循环处理多慢都不影响中断计数周期任务的精度只受滴答本身影响。应广没有硬件除法器写时间片时养成减法代替取模的习惯代码跑起来会明显更跟手。3. PMC251 外部中断的边沿触发、消抖与睡眠唤醒3.1 先分清外部中断脚和唤醒脚应广很多低引脚型号的 IO 带有唤醒功能但唤醒脚不等于外部中断脚。唤醒脚解决的是 SLEEP 状态下电平变化把芯片叫醒的问题叫醒之后程序从睡醒点继续跑外部中断则是边沿触发后跳进中断服务函数。PMC251 的外部中断引脚数量少引脚编号必须以数据手册的 INT 章节为准别拿其他型号的老例子直接套。配置之前把下面这张表看一遍适配 PMC251 时不会漏项。寄存器作用常见符号配置外部中断输入时要注意的IO 方向PAC对应位写 1 设为输入数字输入使能PADIER不开输入缓冲外部信号进不了内部上拉使能PAH接按键时开上拉悬空时别开唤醒使能PAWU低功耗唤醒场景才打开边沿选择INTEDG决定下降沿还是上升沿触发外部中断标志EINTIF使能前先清一次防止上电误触发3.2 外部中断初始化的最小寄存器序列下面代码假设外部中断脚为 PA3、下降沿触发实际引脚号以 PMC251 数据手册为准。// 外部中断初始化PA3 输入下降沿触发 void ext_int_init(void) { PAC | (1 3); // 方向1 表示输入 PADIER | (1 3); // 打开数字输入缓冲漏这步中断永远不触发 PAH | (1 3); // 内部上拉适合接按键到地的接法 INTEDG 0; // 0 为下降沿触发极性看手册 EINTIF 0; // 清外部中断标志 EINTIE 1; // 打开外部中断使能 ENGINT 1; // 总中断 }PADIER 是应广很容易被忽略的一步。为了省电很多型号的 IO 默认数字输入缓冲是关闭的外部中断检测器根本看不到引脚变化。所以初始化顺序应是方向设输入、开数字输入使能、按需开上拉、选边沿、清标志、开使能。如果嫌麻烦漏掉清标志上电瞬间引脚电平抖动可能直接进一次假中断。3.3 外部中断的 ISR 里只做三件事外部中断服务函数里我一般只允许做三件事置一个 volatile 标志、计数器加一、记录当前时间戳。这三件事之间不要再夹延时、界面刷新或长运算。Modbus 帧接收数据程序里外部中断或 UART 中断负责收字节帧间隙和超时判断全部交给滴答定时器就是这个分工逻辑的典型例子。如果中断里做超过 20 行的事情定时器滴答必然被拖住定时器中断晚到几百微秒通信和数据采集的时序一起偏。应广没有硬件中断优先级所有中断都挤在一个入口ISR 越短留给其他事件的时间窗越大。3.4 机械按键消抖中断置标志主循环算时间窗机械按键按下时触点会抖动一次按击可能产生几十个边沿。如果在外部中断里做延时消抖等于用几十次中断把主循环堵死。我一般采用中断只置按键标志 主循环判断两次触发间隔的方式消抖volatile unsigned int key_flag; volatile unsigned int last_key_ms; // 主循环里 if (key_flag) { key_flag 0; if ((unsigned int)(tick_ms - last_key_ms) 20) { last_key_ms tick_ms; key_process(); // 确认是一次有效按键 } }last_key_ms 初始化为 0第一次按键时 tick_ms 减去 0 会得到一个很大的数大于 20 的判断会直接通过等价于第一次按键有效。这里用无符号减法处理 tick 回绕tick_ms 从 65535 回到 0 时差值仍然正确不需要额外判断大小关系。4. 定时器与外部中断合用一个中断函数的正确分工4.1 单入口 ISR 的标志判断顺序PMC251 这类应广型号没有多中断向量无论定时器还是外部中断最终都进同一个中断入口。ISR 内部按标志位判断谁触发了中断。很多人习惯写 if...else if...else但这里更建议用连续的 if不要用 elsevoid interrupt_handler(void) { if (EINTIF) { EINTIF 0; last_edge tick_ms; // 外部事件只记录时间戳 } if (T0IF) { T0IF 0; tick_ms; // 滴答照常走 } }两个事件可能在同一个时钟周期同时到达。用 else 把第二个事件压到下一次中断表面看只是慢一个周期但遇到 UART 起始位或高速计数时慢掉的这一个周期就是波特率偏差或计数丢失的根源。判断顺序也按实时性排事件源标志位丢失后果ISR 建议位置外部中断EINTIF信号直接丢计数不完整先判断Timer0T0IF晚一个中断周期还能补后判断外部脉冲容易丢且不会自动恢复排在前面滴答定时器即使晚一两个中断周期也还能补回来排在后面。4.2 实战码盘测速用定时器做时基、外部中断测间隔做小车测速或直流电机测速时应广这种方式最常用码盘或霍尔传感器每转输出 N 个脉冲外部中断捕获每个下降沿的时间戳相邻两次时间戳的差就是脉冲间隔再算出转速。没有硬件输入捕获模块就靠定时器滴答加外部中断软件实现。volatile unsigned int tick_ms; // 1ms 滴答 volatile unsigned int last_edge; // 上一次边沿时间 volatile unsigned int edge_interval; // 本次间隔单位 ms volatile unsigned int pulse_cnt; // 脉冲计数 void interrupt_handler(void) { if (EINTIF) { EINTIF 0; edge_interval tick_ms - last_edge; last_edge tick_ms; pulse_cnt; } if (T0IF) { T0IF 0; tick_ms; } }主循环里用rpm 60000 / (edge_interval * pulse_per_round)计算转速。比如每转 8 个脉冲脉冲间隔 12ms转速就是 60000/(12×8)625rpm。edge_interval 由两个无符号数相减得到即使 tick_ms 回绕结果仍然正确。这个方案测低速没问题电机转得越快 edge_interval 越短1ms 精度的分辨率会不够把时基调高的方法写在最后一章。4.3 中断只做事件采样状态机留在主循环多个项目里我会把按键外部中断和定时器滴答组合成一个按键状态机外部中断负责把有事件发生标记出来定时器滴答负责计算按下持续时间主循环里的状态机负责判断短按、长按。if (key_flag) { key_flag 0; switch (key_state) { case KEY_RELEASE: key_down_ms tick_ms; key_state KEY_HOLD; break; case KEY_HOLD: if ((unsigned int)(tick_ms - key_down_ms) 1000) { key_state KEY_LONG; long_press_handler(); } break; default: break; } }key_state 在主循环里管理中断里只置 key_flag 和刷新 tick_ms。状态机跑多慢都不影响中断采样按键长短按即使中断偶尔晚一个 tick 也不会崩溃。这种写法把事件采集和业务逻辑的职责拆开在应广小资源芯片上尤其管用因为 ISR 空间和可执行时间都有限。5. 扩展技巧脉冲频率与占空比测量以及 OTP 调试顺序在上面的代码基础上再往前一步外部中断每次进入时翻转边沿选择位就能用同一个引脚在上升沿和下降沿分别记录时间戳测出高电平时长和完整周期同时得到频率和占空比。这对电机驱动回读、PWM 信号解码非常实用等于用软件给 PMC251 补了一个输入捕获通道。volatile unsigned int t_rise, last_fall; volatile unsigned int high_ms, period_ms; void interrupt_handler(void) { if (EINTIF) { EINTIF 0; if (INTEDG 0) { // 当前上升沿 t_rise tick_ms; INTEDG 1; // 下一次抓下降沿 } else { high_ms tick_ms - t_rise; period_ms tick_ms - last_fall; last_fall tick_ms; INTEDG 0; // 下一次抓上升沿 } } if (T0IF) { T0IF 0; tick_ms; } }主循环里频率 1000 / period_ms占空比 high_ms / period_ms。两个时间差都用无符号减法回绕不影响结果。误差主要来自三处ISR 入口响应延迟、tick_ms 的 1ms 分辨率、以及标志清除到读取 tick_ms 之间的指令间隙。要进一步提高分辨率可以把时间戳扩展成16 位滴答 T0C 计数值的组合读取时先读 T0C 再读 tick_ms等效时基可以压到接近单条指令周期。应广 PMC251 一般是 OTP 封装烧错就废。所以量产或比赛前我建议按这个顺序验证先只用定时器跑滴答确认 1ms 周期在示波器上准确再单独触发外部中断用信号发生器扫边沿观察计数是否稳定最后把两个中断合到同一个 ISR加上边沿翻转逻辑验证频率和占空比都正常。每一小步都确认过了再烧录不然中断标志清法和边沿切换的时序问题在 OTP 芯片上根本没有重来的机会。本文还有配套的精品资源点击获取
返回列表