|
这一次,咱们主要实验一下定时器和中断,具体用定时器的定时中断功能来控制LED定时闪烁。 这个实验里,主要用到了单片机里的三样东西:端口,定时器,中断控制器。 在这里,咱们首先弄清楚单片机的定时器的工作原理以及如何操控它。 这事简单,拿出你的手机,一步一步跟我来做,看看手机的定时器是怎么操控的: 第一步,点击手机上正在显示的时间,看到屏幕下部的计时器没有?点开它; 第二步,拨动数字拨盘到你想要定时的计数值; 第三步,按一下启动三角按钮,启动计数器。 操作完成,然后你就看到计时数据在稳步倒退,然后你就可以不管它了,等退到0的时候,手机自然会发出计时结束的提示音,然后你就听到了这个声音,然后你要手动关掉这个提示音,然后你就应该去做先前计划的事了。 如果你要再次启用这个定时,就得手动再按一下启动按钮。 上面所提到的三步,就是对手机定时器的操作。 单片机的定时器的操作和工作过程,底子就这一套东西,一样的,初始化的配置的第一步,就是调出定时器——调出定时的定时功能;第二步就是输入计数初始值。这两步就算是配置完成。第三步就是启动这个定时器让它开始计数,也就是让它开始工作了。 所以,让单片机的定时器工作起来的基本原理,就是这么简单,是不是很出乎你的意料之外?但它就是这样的。 让定时器工作起来不是目的,目的是用它来定时提醒你时间到了该做某件事了,否则它就只能是个摆设。 前面提到手机定时器计数结束会发出提示音,单片机也用了这个机制,不过,它不是搞什么提示音,在芯片设计上,它是在相关寄存器中做一个标记位,这个标记位复位时为0,一旦定时器的计数结束,这个位就会被置位为1,也就是说,这个位,就是计数结束标志位。 那我们怎么知道这个位已经变成1了呢?用查询方法过一会来看一下过一会来看一下?本来就是为了准确计时,结果你把这个过一会加进来,那哪还有即时效果?这不误事么? 那就一直守着这个位等着它变成1?那你还做不做其它的事了?除非你没其它的事,否则别用这一套,比如说如果我们只有一件事只是闪个灯,这个也不是不可以用,哦对了,这个叫等候机制。 象人一样手脚在做事而耳朵在听着?对,就是这个思路,单片机就是这样的,CPU在做事的同时也没断了跟定时器的联系,这个联系机构就是中断控制器。定时器计数结束时,会置位计数结束标志位,中断控制器会一直盯着这个位,一旦这个位变成1,它就会放下手中的事来处理计数结束后约定的事,这个机制,就叫中断处理机制。至于究竟约定的是什么事,则全看你程序员在相应的中断函数里编的是些什么,你编什么它就执行什么,你不编的话,那它就没什么可执行的。 注意啊,在单片机中,这个中断处理机制是可以有针对性地关掉的,而且,单片机复位之后所有的中断源都是处于关闭状态的,而定时器就属于可屏蔽中断源,所以,我们还得打开这个中断开关。 前面说到计数重复的事,我们在这里既然是用于定时闪灯,那肯定得是不断重复啊,不断重复才能闪灯嘛。怎么重复?肯定不能让你来手动啊,所以,单片机的计数设计中还有一个计数结束自动重复开关,也就是STC手册中说的自动重装载模式,我们得把这个定时器设置成这个模式。 还有一个问题,计数结束之后,计数器之中的数值已经归零了吧?那你从零开始再次启动么?这样的话那肯定不符合我们的要求,所以,在设计中,这个计数初始值得找一个地方存着,然后计数归零后,定时器将会将这个计数值自动重装入定时器的计数器中,不过对于STC8H来说,我们不用管它,只要我们设定了自动重装载模式,单片机自己会处理这个事。其实手机的计时器就是这样的,计时结束并关闭提示音之后,计时拨盘上的数值自动显示为上一次的值。 单片机定时器的初始化的项目,也就这么多,下面我们开始实验。 我们用的芯片是STC8H8K64U,双列直插式,老办法,自己搭建电路,用面包板,LED接P54脚。器件清单一个STC8H8K64U,一块面包板,一个LED,一个1K电阻,一个电源滤波电容,一个下载器(不用下载器直接用USB线也行),一点连接线。 首先确定我们要用到的外设,这里就两个:定时器T0和输入输出端口P54。 一、先对定时器进行初始化,前面已经讲到了,它的初始化的项目至少包括以下七项: 1. 调出定时器的定时功能; 2. 输入计数初始值; 3. 打开自动重装载计数初始值功能开关; 4. 配置定时器中断源的中断优先级; 5. 打开定时器计数结束中断使能开关; 6. 启动定时器; 7. 打开单片机全局中断使能总开关。 下面我们来看看STC8H的手册上是不是这样的。 手册第16章定时器、计数器开头就有如下表述: 于是我们知道了在寄存器TMOD中通过操作C/T这个开关来调出它的定时器功能,第一项有了着落。 这段内容的意思是:计数器内计数值变化的导因是电路脉冲,STC8H这个单片机的计数器工作时是收到一个脉冲则计数值就增加1,而这个脉冲的来源呢,在我们选择为定时功能时,来源为单片机的系统时钟,但不一定等于系统时钟,你可以选择对系统时钟进行12分频后再作为定时器的工作频率,也可以不作分频。这个就多了一件事:选择对系统时钟进行12分频或不分频。至于具体如何进行操作,它也说明了在AUXR寄存器中。 再往下还有表述:定时器/计数器T0有四种工作模式,一是模式0即16位自动重装载模式,二是模式1即16位不可重装模式,等等。于是,第3项有了着落。 这里说明一下,前面说了,STC8H这个单片机的计数器工作时是收到一个脉冲则计数值就增加1,也就是说它的的定时计数结束模式采用溢出模式,即对于十六位计数器来说,最大计数值为65535,再计一次数就满了,这就是所谓的溢出,所以,想计十次数,我们输入寄存器的初始值就应该是65536-10=65526,这个,它与现在的很多单片机如RISC-V的计数方式是不一样的。 上面的这个TCON寄存器就解决了两个问题,一个是计数结束标志位TF0;一个是定时器的启动TR0,也就是只要把B4位置就是打开了启动开关。这里也说明一下,TCON这个寄存器的地址是88H,既然末尾是8的倍数,那当然可以位寻址啦,编程时直接用TF0、TR0。 于是,第6项就很清晰了。另外计数结束标志也找到了,不过这里要提醒一下,上面的表述是CPU在响应中断后才会自动将其清零,也就是说CPU不进中断则这个标志位一直会是1,一旦CPU进入中断则立即变成了0,所以,这个位,在中断函数中,你是不需要操作的,这一点,多说一句,并不是所有的单片机都是这个机制,也并不是所有的中断源都是采用的这个机制,你一定要先仔细看手册再去编程。 定时器启动语句如下: TR0=1; 这个不用多解释吧? 第1项定时器功能模式与第3项16位自动重装模式,在这里就确定了,TMOD寄存器的[1:0]为00B,语句如下: TMOD&=~(3<<0); 提醒一下,第16.2.3节定时器0模式0的内容,要好好看一下,这部分好理解,不讲了。 这个就是输入计数初始值的寄存器,一个是高八位一个是低八位,于是第2项的相关寄存器就确定了。 这个是定时器的工作频率的取值方法,意思是你可以对系统时钟进行1~256之间任意值的分频,这个操作起来要用到扩展寄存器,它是十六位地址的寄存器,用起来稍微有点麻烦,在STC8上我们就不用这个了,想用的话可以用南京沁恒CH32的,那个用起来很简单。 这个前面说过了,这里就确定了12分频时使用的寄存器,12分频时的语句如下: AUXR&=~(1<<7); 这个就说到了定时初始值的计算方式,如果我们的CPU主频选12MHz,选12分频,则定时器的工作频率就是1MHz,那么计数50000次就是50ms,65536-50000=15536,也就是说如果你设定的定时周期为50ms,那么计数初始值就是15536,换算成16进制就是0x3CB0。 于是第2项里的计数初始值也就确定了,这一项的语句也就出来了: TL0=0xB0; TH0=0x3C; 手册中定时器的相关内容算是看完了,接下来就是中断的设置了,我们来看第14章中断系统。 中断这部分吧,手册讲得不清楚,我简单说一下,它其实是分三个级别的,一个级别是我比你紧急,发生之后我可以打断你的事,这个我称之为打断优级级;第二个级别是没资格打断你的事发生了几起,那就都在后面排着队吧,这个时候就有一个前面的事处理完成之后,这几起事的处理顺序问题,这个行业内称为什么名字我不说了,我自己给这个级别起了个名字叫排队优级级;第三个级别是大家的打断优先级与排队优先级都一样,那前面的事处理完成之后的处理顺序怎么办?按查询先后顺序来,这个级别,我称其为查询优先级,程序运行中的中断控制器会不断查询各各断源的中断标志位,这就有个查询顺序的问题,这个查询顺序是由单片机自己设定的,程序员改不了,具体的查询顺序看下表14.3,其中的第三列次序就是的,这个其实也是中断编号,我们在编写中断函数时要用到的。 注意啊,STC8H其实只设计有两个级别,是打断与查询这两个优先级,沁恒之类的,设计有这三个级别。 还有,前两个级别是可以由程序员来设定的,但STC8只有第一个级别和第三个级别,所以,我们要做的也是能做的是设定中断源的打断优先级。 哦对了,对于STC8来说,高编号的级别高于低编号的级别。这个注意啊,其它的单片机不一定是这样的,这个一定要看手册。 这张表中就表述了优先级设置位、可以设定的优先级、等的,我们可以看到T0的中断号为1,可以用PT0和PT0H来设定为打断优先级的四个等级中的任意一个,中断允许位ET0,但没说具体的寄存器,所以我们继续往下看。 这张表除了确定了ET0所在的寄存器即第5项事,还解决了第7项事,总中断使能开关,所以,我们要打开这个寄存器中的两个开关:EA和ET0。这个寄存器的地址是A8H,很好,可以位寻址,于是打开这两个开关的语句就如下了: ET0=1; EA=1; 这个就看到了优先级设定的寄存器了,它的复位值均为0,也就是复位后这些可设置优先级的中断源的打断优先级均为0也就是最低级,如果你想设定T0的优先级为3,那相应的语句如下: IP|=(1<<1); IPH|=(1<<1); 好了,我们对于T0的初始化的内容就全部弄清楚了,然后汇总成如下初始化函数: void TIM0_init(void) { TMOD&=~(1<<3);//调出定时器功能 TMOD&=~(3<<0);//16位自动重装模式 AUXR&=~(1<<7);//对时钟作12分频作为定时器的工作频率 TL0=0xB0; TH0=0x3C; IP|=(1<<1);IPH|=(1<<1);//优先级3 ET0=1;//允许定时器溢出中断 TR0=1;//启动定时器 } 至于总中断开关EA,这个不是T0一个中断源的事,所以,它一般放在主程序中。 二、接下来初始化IO端口。 我们可以用P54端口,将其设置为准双向口也是可以的,串一个1K电阻可以直接驱动LED。 P5M0&=~(1<<4);P5M1&=~(1<<4); 三、再就是定时器中断函数了: 进入该中断函数后,按前面所说,不用你处理中断标志位TF0,直接翻转P54就是了。前面定时周期我们定为50ms,进行翻转后就是100ms闪烁一次,这个频率,我们已经能够看到闪烁了。 void tm0() interrupt 1 { LED=~LED; } 中断函数名可以自己起,但要记得后面加上interrupt 1 ,以表示这个是中断号为1的中断源的中断函数,前面说过,T0这个中断源的中断号为1。 四、再就是主函数,调用初始化函数、打开总中断开关、等待,没什么好讲的。 五、全部程序如下,就这点东西,所以没做多C,主频率12MHz: #include "stc8h.h" //P5.4_LED #define LED P54 void LED_init(void) //端口P54初始化 { P5M0&=~(1<<4);P5M1&=~(1<<4); //P54准双向口 } void TIM0_init(void) //T0初始化 { TMOD&=~(1<<3); //调出T0的定时器功能 TMOD&=~(3<<0); //T0的定时器设定为16位自动重装模式 AUXR&=~(1<<7); //对系统时钟作12分频作为定时器T0的工作频率 TL0=0xB0; TH0=0x3C; IP|=(1<<1);IPH|=(1<<1); //优先级3 ET0=1; //允许定时器T0溢出中断 TR0=1; //启动定时器T0 } void main() //主函数 { LED_init(); TIM0_init(); EA =1; //开总中断 while(1); } void tm0() interrupt 1 { LED=~LED; } 用STC-ISP下载时,注意在界面上选择用户程序运行时的IRC频率为12MHz。 PS:其实各类型单片机的学习方法都是一样的,只要你理解了原理,那对你来说都没有什么障碍,只是千万要注意,换了芯片之后,一定要细看手册,因为实现一种功能可以有各种不同的方法,由此各类芯片在设计上可能会有些不同甚至会有相当大的区别。
|