/******************************************************************** 文件名 LED0 500MS闪烁 * 描述: 点亮LED---用定时器的办法 * 2018-09-19 调试通过 * 功能 入门模板 * 作者: 大核桃 * 版本号:V1.00(2018.09.19) ********************************************************************/ #include "config.h" #include "intrins.h" /******************************************************************* * 文件名 变量重新定义区域 * 描述: * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.17) ********************************************************************/ typedef unsigned char uint8;//无符号字符型 typedef unsigned int uint16;//无符号整型 typedef unsigned long uint32;//无符号长整型 /******************************************************************* * 文件名:位重新定义区域 函数前置声明 * 描述: * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.17) ********************************************************************/ void Delay500ms(); //@11.0592MHz void MCU_Port_Init(void); void Bsp_Tim0_Init(void); sbit LED0 = P1^0; sbit LED1 = P1^1; sbit LED2 = P1^2; sbit LED3 = P1^3; sbit LED4 = P1^4; sbit LED5 = P3^2; sbit LED6 = P0^0; sbit LED7 = P0^1; bit flag500ms = 0;//500ms定时标志位 /******************************************************************* * 文件名 main函数入口 * 描述: * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.17) ********************************************************************/ void main(void) { MCU_Port_Init();//端口模式初始化函数 Bsp_Tim0_Init();//定时器0初始化函数 while(1) { if(flag500ms) { P2 = 0XFE;//1111_1110; } else { P2 = 0XFF;//1111_1111; } } } /******************************************************************* * 文件名:void MCU_Port_Init(void) * 描述: MCU端口上电初始化函数 * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.17) ********************************************************************/ void MCU_Port_Init(void) { //第0 和1位配置推完输出模式,大电流 P0M1 = 0XFC; // 1111_1100 P0M0 = 0X03; // 0000_0011 //第01234位配置推完输出模式,大电流,567配置高阻输入,用于ADC P1M1 = 0XE0; //1110_0000 P1M0 = 0X1F; //0001_1111 //P2配置位准双向口 P2M1 = 0X00; //0000_0000 P2M0 = 0X00; //0000_0000 P2 = 0XFF;//P2口初始化为1 //P5配置位准双向口 P5M1 = 0X00; //0000_0000 P5M0 = 0X00; //0000_0000 P5 = 0XFF;//P5口初始化为1 //P3 23467推完输出 P3M1 = 0X00; //0000_0000 P3M0 = 0XFC; //1101_1100 P3 = 0X23; //0010_0011 //上电IO默认是0 LED0 = 1;//输出1 LED1 = 0; LED2 = 0; LED3 = 0; LED4 = 0; LED5 = 0; LED6 = 0; LED7 = 0;// } /******************************************************************* * 文件名:void Bsp_Tim0_Init(void) * 描述: 定时器0初始化函数 * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.19) ********************************************************************/ void Bsp_Tim0_Init(void) //1000微秒@11.0592MHz { AUXR |= 0x80; //定时器时钟1T模式 TMOD &= 0xF0; //设置定时器模式 TMOD |= 0X01; TH0 = 0xD4; //设置定时初值 TL0 = 0xCD; //设置定时初值 TR0 = 1; //定时器0开始计时 ET0 = 1; //使能定时器0的中断 EA = 1; //打开总中断 } /******************************************************************* * 文件名:TIM0_IRQ_Handler * 描 述:中断服务函数 * 功 能 中断服务标号 INT0 ET0 INT1 ET1 UART1 ADC LVD TIME2 * 优先级: 0 1 2 3 4 5 6 12 * 版本号:V1.00(2018.09.19) ********************************************************************/ void TIM0_IRQ_Handler(void) interrupt 1 { static uint16 tmr500ms = 0; TH0 = 0xD4; //设置定时初值 TL0 = 0xCD; //设置定时初值 tmr500ms++; if(tmr500ms >= 500) { tmr500ms = 0; flag500ms = !flag500ms; //500MS闪烁 } }
复制代码因为在实际使用中,定时器和中断都是在一起配合使用,所以这儿我们就不分开,但是要说的是,定时器是硬件,是单片机内部存在的一个模块,而中断仅仅是一种处理问题的机制,上面这个d代码看着不多,但是消息量很大,我们一点一点解剖,理解了定时器的的原理,等你上手STM32的时候,定时器原理可以直接不看,直接拿来用就好了。
先从STC89C52RC的开始说起,我们知道,STC89C52RC是标准51内核,在标准51的体系下,12个时钟周期是一个机器周期,啥意思呢?比如你的外部晶振是11.0592MHZ,那么11059200的倒数,也就是周期了,这个倒数叫做时钟周期,也叫震荡周期,算一下时间,1/11059200 = 0.0904US,这就是STC89C52的时钟周期,那么51单片机就规定,12个这样的时钟周期为一个机器周期,所以在乘以12,那么一个机器周期的数值是1.085US,注意这是在11.0592MHZ下,如果是在12MHZ下,那么一个机器周期就是1US,这就是定时器的时间基准。我们再来看下,如果我们用STC89C52来做一个500MS的定时器该怎么做呢?配置如下即可实现:
/******************************************************************** 文件名:void Bsp_Tim0_Init(void) * 描述: 定时器0初始化函数 * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.19) ********************************************************************/ void Bsp_Tim0_Init(void) //1000微秒@11.0592MHz { TMOD &= 0xF0; //设置定时器模式 TMOD |= 0X01; TH0 = 0xFC; //设置定时初值 TL0 = 0x66; //设置定时初值 TR0 = 1; //定时器0开始计时 ET0 = 1; //使能定时器0的中断 EA = 1; //打开总中断 } /******************************************************************* * 文件名:TIM0_IRQ_Handler * 描 述:中断服务函数 * 功 能 中断服务标号 INT0 ET0 INT1 ET1 UART1 ADC LVD TIME2 * 优先级: 0 1 2 3 4 5 6 12 * 版本号:V1.00(2018.09.19) ********************************************************************/ void TIM0_IRQ_Handler(void) interrupt 1 { static uint16 tmr500ms = 0; TH0 = 0xFC; //设置定时初值 TL0 = 0x66; //设置定时初值 tmr500ms++; if(tmr500ms >= 500) { tmr500ms = 0; flag500ms = !flag500ms; //500MS闪烁 } }
复制代码还是来解释下,首先定时器的配置步骤是这样的:
1.先设置TMOD这个寄存器,选择定时器0的模式寄存器,配置定时器0为16位不可重装载模式
2.设置定时器的定时初值,高八位和低八位
3.打开定时器的运行标志位,因为TCON是一个可位寻址的寄存器,所以直接TR0 = 1;就好。
4.使能定时器0的中断ET0
5.打开总中断EA
OK,这样就配置好了寄存器,定时器也可以工作了,然而我们了解定时器是怎么运行的了吗?没有!!!很多人不知道为啥是这个数值,而且定时器的的初值还有好几种写法,如果有人用了不一样的而写法,你一定要知道是等价的写法。
关于初值的计算
我们知道定时器0是一个16位的定时器,最大计数65536(0-65535)分为高八位和低八位,TH0存储的是高八位的数据,TL0存储的是低八位的数据,0XFC是一个16进制数值,换算10进制是252,0X66是102,我们知道低八位最大计数到255,TH0就变成1,然后进位,清零,又开始从0计数,那么我们可以算算这个初值是多少?252*256 +102 = 64614,而64614的16进制表示形式就是0XFC66,这样我们就搞清楚定时器的计时原理了,如下所示,初值代码可以改写成这样:
TH0 = (65535 - 921) / 256; //设置定时初值 TL0 = (65535 - 921) % 256; //设置定时初值
复制代码也就是说,我们让单片机从64614开始计数,到65535溢出,总共计数921个,而我们又知道1个机器周期是1.085US,那么921个机器周期是多少呢?921*1.085 = 1000US,正好是1MS的定时,我们在程序中让其溢出500次,那么不就是500MS了吗?就是这样来的,原理一定要搞清楚。不管什么STM32,64,128都是这样的原理{:}。关于中断使用的时候,打开使能就好了,EA是总中断使能位,如果这个不打开,ET0单独打开是没用的,这才是一把手。
关于STC15W系列的定时器
好了,既然我们搞清楚了,STC89C52的定时器的原理了,我们来看下,STC15W的定时器配置,因为我们都是定时1MS,那么,为啥初值不一样呢?我们来算下STC15W的这个初值对应的10进制数值是多少?是54477,好陌生的数字,怎么来的呢?65536-54477 = 11059,也就是说在STC15W的内核下,我们只要计数11059个,就可以达到1MS的定时,我们知道,STC15W是单周期的时钟,也就是说我们不12分频,我们直接就是一个时钟周期就是一个机器周期,(1/11059200)*11059 = 1ms,明白了吧?所以,本节的程序代码,也可以这样写,是一样的作用的。
/******************************************************************** 文件名:void Bsp_Tim0_Init(void) * 描述: 定时器0初始化函数 * 功 能 * 作者:大核桃 * 版本号:V1.00(2018.09.19) ********************************************************************/ void Bsp_Tim0_Init(void) //1000微秒@11.0592MHz { AUXR |= 0x80; //定时器时钟1T模式 TMOD &= 0xF0; //设置定时器模式 TMOD |= 0X01; // TH0 = 0xD4; //设置定时初值 // TL0 = 0xCD; //设置定时初值 TH0 = (65535 - 11059) / 256;//设置定时初值 TL0 = (65535 - 11059) % 256;//设置定时初值 TR0 = 1; //定时器0开始计时 ET0 = 1; //使能定时器0的中断 EA = 1; //打开总中断 }
复制代码关于在单片机中大量存在的& |运算的详细说明
其实这个,不太想说,但无奈上网看到好多初学者根本不知道& |的作用,还是详细的说明下比较好,以定时器0的配置为例。
AUXR |= 0x80; AUXR是一个辅助寄存器,这不需要多说,|= 0x80有什么讲究呢?|(或),是让某一位置1的意思,让那一位置一呢?很明显,让是1的那一位置一,0X80不就是最高位是1吗?那就是让最高位置一好了,有人说,这有啥用呢?本来不就是1吗?人家还有后半句,而其他位保持不变,其他位?啥位?等于0的那些对吧?也就是低7位不变了。这样操作有啥好处呢?我们知道ET0 = 1;TR0 = 1;之所以可以这样操作,是因为他们可以被位寻址,可以进行单独的位操作,而AUXR是不可以进行位寻址的,因此一次操作必须操作8个位,你想想看,AUXR这个寄存器的功能如下图:
你能保证直接让AUXR = 0X80,不对其他位造成影响吗?现在大家的编程还比较简单,只有一个定时器,要是用到3个,4个定时器呢?这样不就互相干扰了吗?扯淡么?所以 & |的重要性也就凸现出来了。
再来看后面这2句,很明显TMOD是不可以位寻址的,按照我们刚才的分析,|是让某一位置一,那么&,自然就是让某一位清零了,来看下0XF0,二进制是1111_0000,也就是低4位置0,高4位不要管,因为&是乘法运算啊,只要都是1,那么就是1,很明显让低4位清零,下一句是0000_0001,让最低为置一啊,对吧,要注意,这里的2句是连续操作的,不是单独的的操作,什么意思?前者的运算结果,又给了后者,所以我们总体来看这2句代码,先让低四位清零,高四位不变,然后将这个结果进行或运算,让最低位置一,而高7位都不变,因为任何数|还是任何数啊,对吧,这就达到了一个互不干扰的目的,这样的代码在STM32上好多好多的,都是起到一个互不干扰的作用。这样的做法可以确保定时器0和定时器1是独立的,如果我们不这样做,你看看是工作在啥模式?除了定时器配置在16位不可重装模式在,定时器1被配置在了16位自动重装定时器,我们没有使用定时器1,万一出错怎么办?这就不好了。
关于自动重装载和不可自动重装模式
其实,没啥太大的区别,如果是自动重装,那么在中断服务函数中,TH0 TL0就不需要去再去重新赋值了,直接删掉就好,如果不是自动重装,则必须要加。还差点忘了一个事,你怎么确定你的定时是500MS呢?答案不能靠眼睛看把,看看示波器观察的结果,嗯,是对的。如下图,1S 1HZ的方波信号。
其实,没啥太大的区别,如果是自动重装,那么在中断服务函数中,TH0 TL0就不需要去再去重新赋值了,直接删掉就好,如果不是自动重装,则必须要加。还差点忘了一个事,你怎么确定你的定时是500MS呢?答案不能靠眼睛看把,看看示波器观察的结果,嗯,是对的。如下图,1S 1HZ的方波信号。