起因
首先说明,这篇文章可能没什么营养,如果读者不希望浪费时间,可以直接跳到结果看看发生了什么,直接看结果也可以看明白的。
由于课程作业要求用汇编语言写,就尝试使用C51单片机,用汇编语言来写,然后测试看看代码有无问题,题目是这样的:
要求使用定时计数器1的定时功能实现一个PWM的输出,周期为100ms,占空比为20%,P1.0输出,P1.1输出它的反相波形。
代码思路不难,定时器1工作在定时功能,计数机器周期,也就是1MHz(后文会提到这个值怎么得来),也就是1us,此时计数值设置成1000,也就是重装载值为2^16 – 1000 = 64536,也就是FC1BH,得到初始计数值。
先把这个逻辑用汇编语言实现一下:
;思路就是拉高20个中断,在拉低80个中断,这样只需要两个跳转就能实现功能了
;还需要一个寄存器来记录一下P1口的状态
ORG 0000H
AJMP MAIN
ORG 001BH ;T1中断服务程序
MOV TH1,#0FCH
MOV TL1,#01BH
SUBB A,#01H
RETI
MAIN: MOV SP,#60H ;堆栈指针位置
MOV TMOD,#10
MOV TH1,#0FCH
MOV TL1,#01BH
MOV P1,#0FDH
MOV R0,#01H ;先把R0置1,表示高电平
MOV A,#14H ;先用20ms,表示高电平时间
SETB TR1 ;启动T1计数
SETB ET1 ;允许T1中断
SETB EA ;打开CPU中断
L1: JZ A0 ;如果A中的值变成0了,说明需要翻转IO口
AJMP L1 ;不是0,则循环检测A的值
A0: DJNZ R0,OUT2 ;检测R0的值,如果是1就跳转到OUT1,否则跳转到OUT2,注意这条指令完成后R0的值被减1
AJMP OUT1
OUT1: MOV R0,#00H ;修改R0的值,下次跳转进入OUT2
MOV A,#14H ;
MOV P1,#0FDH
AJMP L1 ;返回主程序
OUT2: MOV R0,#01H
MOV A,#50H ;这里赋值80,表示低电平持续时间80ms
MOV P1,#0FEH
AJMP L1 ;返回主程序
END
然后仿真,利用keil的逻辑分析仪查看周期,发现不是100ms,一开始我以为是中断函数中其他语句影响了时间,后来发现并不是,波形如下:
可以发现一个周期是10.19367ms,也就是说跟100ms差的很远,此时我们计算一下定时器的中断频率:100*1000/10.19367,忽略掉误差大概在10MHz,然后经过不断尝试,发现定时器频率其实是8MHz,我们给计数8000,也就是65535-8000 = 57535,也就是E0BFH:
现在达到目的了,但是问题还没有解决,因为从理论上来说,定时器频率使用的是机器周期,是振荡器频率的1/12,此时我给的振荡器频率是12MHz,也就是晶振频率,由此计算得到的定时器频率应该是1MHz,我以为是我的keil哪里设置有问题,于是打开proteus顺手搭了个基本电路测试一下:
到这里来测量一下高电平的宽度:
可以发现高电平时间确实是20ms,也就是说定时器频率确实是8MHz,这就奇怪了……
结果
找了好久也没有讲这个是为什么,当我正要放弃的时候,想着要不看看T0怎么样,结果正打算改TMOD的时候,发现赋值TMOD的时候少写了个H,真离谱,找半天,看来敲代码还是要认真……
理论上看一下少写H会发生什么(唉,被迫复习知识),原本想表达的是0x10,结果变成了0x0A,也就是定时器1工作在模式0,诶,这个时候是13位计数器,也就是TL高三位应该没有用,我给的初值是0xE0BF,一定要注意这里是第八位的高三位没用,也就是第八位满32进1,这时候相当于低五位初值为全1,高八位是E0,计数值就是1E * 32 + 1 = 961,怪不得此时相当于1us,已经很接近1000了,这就造成了8MHz的错觉……
我不知道世界上会不会有第二个人遇到这个问题并且恰好能得出8MHz这个数字,反正我就是按照比例在计算了一番,算出了这个数字,然后就被误导进去了。所以以后敲代码还是要认真一些,不然费时费力。