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的波形。我整理了一张表,方便你对照:

M2M1模式名称输出波形说明
00单次方波计数过程中输出低电平,计数到2时输出高电平,保持到计数结束
01连续方波自动重装初值,持续输出方波
10单次脉冲计数到2时输出一个正脉冲
11连续脉冲自动重装,持续输出脉冲串

嗯,这里要注意:计数到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上。

具体做法是这样的:

  1. 设置8155定时器为单次脉冲模式(M2=1, M1=0)
  2. 计算好初值并写入
  3. 启动定时器(控制寄存器D7=1)
  4. 当计数器减到2时,TIMER OUT输出一个正脉冲
  5. 这个脉冲触发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的定时器/计数器,核心内容就这些。说白了,它就是个小巧的定时工具,用好了能帮你省一个外部定时器芯片。下一章咱们聊聊如何把这些模块组合起来,搭建一个完整的语音交互系统。