4、8155的定时器/计数器:定时器/计数器结构、工作模式设置、计数初值计算、定时中断应用
好,咱们接着聊8155。前面把I/O口和RAM讲透了,现在轮到它的定时器/计数器模块。说实话,这个模块在8155里算是个「小亮点」——它不像8253那么复杂,但胜在够用、省引脚。我在做智能语音交互系统时,就用它来产生按键扫描的时基信号,效果还不错。
4.1 定时器/计数器的内部结构
8155内部有一个14位的减法计数器。嗯,你没看错,是14位,不是16位。这个细节我当年看数据手册时还愣了一下,心想怎么不是标准的16位?后来才明白,这是为了和8155的地址空间对齐,省点逻辑门。
这个定时器由三个部分组成:
- 14位减法计数器:从初值开始递减,减到2时输出一个信号
- 控制寄存器:决定工作模式和输出波形
- 状态标志:一个标志位,告诉你计数器是否计到0了
说白了,它就是个「倒计时器」。你给它一个初值,它就开始往下数,数到2的时候干点活,数到0的时候停下来。我习惯把它想象成一个沙漏——沙子漏完的那一刻,就是触发事件的时候。
4.2 工作模式设置
8155的定时器有4种工作模式,由控制寄存器的高两位(M2、M1)决定。这4种模式主要影响输出引脚TIMER OUT的波形。我整理了一张表,方便你对照:
| M2 | M1 | 模式名称 | 输出波形说明 |
|---|---|---|---|
| 0 | 0 | 单次方波 | 计数过程中输出低电平,计数到2时输出高电平,保持到计数结束 |
| 0 | 1 | 连续方波 | 自动重装初值,持续输出方波 |
| 1 | 0 | 单次脉冲 | 计数到2时输出一个正脉冲 |
| 1 | 1 | 连续脉冲 | 自动重装,持续输出脉冲串 |
嗯,这里要注意:计数到2时输出变化,而不是计数到0。为什么?因为计数器从2减到1时,硬件判断「快到头了」,提前给出信号。这个设计其实挺巧妙的——给你留出一点反应时间。
重点提醒:8155的定时器是14位的,所以计数初值的范围是2H~3FFFH(十进制2~16383)。初值不能设为0或1,否则计数器不工作。我曾经踩过这个坑,初值设了个1,结果定时器纹丝不动,查了半天手册才发现问题。
4.3 计数初值计算
计算初值其实很简单,就一个公式:
计数初值 = 时钟频率 / 目标频率
举个例子。假设你的8155输入时钟是2MHz,你想要一个1kHz的方波。那么:
计数初值 = 2,000,000 / 1,000 = 2000
把2000写入定时器的低8位和高6位寄存器即可。注意,高6位要放在高字节的低6位里,别搞错位了。
我个人习惯是先算好十进制,再转成十六进制,然后手动拆成高低字节。比如2000 = 0x07D0,低字节是0xD0,高字节是0x07。写入时:
MOV A, #0D0H ; 低8位
MOV DPTR, #4004H ; 定时器低8位地址
MOVX @DPTR, A
MOV A, #07H ; 高6位(注意只取低6位)
MOV DPTR, #4005H ; 定时器高6位地址
MOVX @DPTR, A
你想想看,如果目标频率是100Hz,初值就是20000。但20000超过了14位的最大值16383,怎么办?那就得用分频或者级联了。我在项目中遇到过这种情况,最后是外接了一个分频器解决的。
小技巧:如果你需要精确的定时,建议把初值算出来后再用示波器实测一下。因为8155的时钟输入可能有误差,而且指令执行也会占用几个时钟周期。我一般会先算个大概,然后微调初值,直到波形符合要求。
4.4 定时中断应用
8155的定时器本身没有独立的中断引脚。它怎么产生中断呢?答案是:用TIMER OUT引脚接外部中断。比如接到8051的INT0或INT1上。
具体做法是这样的:
- 设置8155定时器为单次脉冲模式(M2=1, M1=0)
- 计算好初值并写入
- 启动定时器(控制寄存器D7=1)
- 当计数器减到2时,TIMER OUT输出一个正脉冲
- 这个脉冲触发8051的外部中断
我在做语音交互系统时,就用这个方式实现了按键的定时扫描。每10ms触发一次中断,在中断服务程序里读取按键状态。这样CPU不用一直轮询,省电又高效。
代码片段大概是这样的:
; 初始化8155定时器,10ms定时
; 假设时钟频率2MHz,初值=20000(但超出14位范围,改用5000+4分频)
; 实际用初值5000,4分频后得到10ms
MOV DPTR, #4000H ; 8155控制寄存器
MOV A, #0C2H ; 启动定时器,单次脉冲模式
MOVX @DPTR, A
MOV DPTR, #4004H ; 定时器低8位
MOV A, #88H ; 5000的低8位
MOVX @DPTR, A
MOV DPTR, #4005H ; 定时器高6位
MOV A, #13H ; 5000的高6位(0x1388的高6位是0x13)
MOVX @DPTR, A
; 设置外部中断INT0
SETB IT0 ; 下降沿触发
SETB EX0 ; 允许INT0中断
SETB EA ; 开总中断
注意:8155的定时器在单次模式下,计数结束后不会自动重装。如果你需要连续定时,要么用连续模式,要么在中断服务程序里重新写入初值并启动。我建议用连续模式加外部中断,这样更省事。
还有一个坑:8155的定时器启动后,你不能马上读它的状态。因为写入初值和启动之间需要几个时钟周期的延迟。我一般会在启动后加几条NOP指令,或者等一个中断后再操作。
好了,关于8155的定时器/计数器,核心内容就这些。说白了,它就是个小巧的定时工具,用好了能帮你省一个外部定时器芯片。下一章咱们聊聊如何把这些模块组合起来,搭建一个完整的语音交互系统。