1、. .主要学习51单片机的外部引脚和内部结构等,叙述一下。本书任务驱动教学,引入案例有浅变深,循序渐进,给读者留下思考和发挥空间。3.1 利用单片机的I/O口驱动LED3.1.1利用单片机的P0.0 端口驱动1只LED闪烁编程的目的是利用C语言控制单片机I/O端口按要求输出矩形波脉冲信号,信号的周期由延时函数决定。一、电路原理STC-89C51单片机的P0口采用为OD门输出,不存在拉出电流,因此利用P0驱动负载时有两种接法:一种是加上拉电阻R2,见图3-1-1,既用1K电阻接电源正极,此时P0口输出高电平时LED亮;另一种电路为P0.7低电平驱动有效,在P0.7输出低电平时,STC-89C51
2、端口灌入电流达20mA,可直接驱动小负载。图3-3-1中的R3为限流电阻,限制LED2的工作电流。图3-1-1 驱动LED电路原理单片机的最小系统包括晶体振荡电路,加电复位电路,同时要求单片机的31引脚接高电平。时钟频率主要由晶体CY决定,C1、C2为独石电容,用于微小调整单片机时钟的振荡频率;R1和C3组成加电复位电路,C3为电解电容器;整个电路由+5V电压供电。电路使用的元件参数见表3.1.1。表3.1.13-1-1电路元件表元件名称序号标称规格(封装,功率电压等参数)作用单片机ICSTC89C51DIP40核心芯片电容器C130PF独石振荡电容器C230PF独石振荡电容器C310F点解电
3、容器复位晶振CY12MHzS型振荡电阻R11K1/4W,金属膜电容器C3放电电阻电阻R21K1/4W,金属膜端口电位上拉电阻电阻R31K1/4W,金属膜限流电阻发光二极管LED15红色高亮显示发光二极管LED25红色高亮显示二、程序设计1.程序设计平台程序设计采用keil C 软件,为了养成一个良好的文件管理习惯,建议:编程前,在计算机的某个硬盘分区下建立一个目录,目录的名字为你编写程序的主题,然后把keil C产生的所有文件都放在该目录下。程序设计步骤如下:(1)运行keil C,创建一个项目。利用keil C菜单栏中project-new project创建,项目的名字为你编程的主题,如本
4、案例可以用“LED_1.uv2”或“一个LED闪烁实验.uv2”,存放目录为你的主题目录下;(2)创建建一个文件,然后以“.c”后缀名,文件存放在你的主题目录下;(3) 把c文件添加在你的项目中,在project workspace窗口利用add file to group实现。如果软件界面不显示这个窗口,运行keil C菜单栏中的view-project window。(4)设置编译器,是编译过程中能生成“.hex”文件。默认文件名与主题名字一样。运行project-option fortarget 1-output窗口下点击create HEX file。(5)在编程区域写你的程序,结束后
5、存储文件并编译,要做检查,尽量避免警告出现,直到程序编译无误为止。(6)程序烧写并试运行。这个过程称为软件和硬件联合调试,如果实验板无误,程序编译通过,但在硬件上运行不成功是常用的事情,需要对程序进行多次调试。在没有实验板的情况下,可以利用proteus软件仿真调试,也可以检验你的程序设计结果。2.程序设计(1)利用P0.0口驱动LED闪烁,高电平有效/*/#include /包含头文件sbit LED = P00; / LED接P0.0。在kell C51软件中,定义P0.0为P00,void delay(unsigned char x) /延时函数unsigned char i,j;for
6、(i = 0;i x;i+)for(j = 0;j 200;j+);void main(void) /主函数While(1) /程序死循环LED = 0; /P0.0输出低电平,LED灭void delay(100); /调用延时函数,延时一段时间,约0.3秒,不精确LED = 1; /P0.0输出高电平,LED亮void delay(100);/*/(2)利用P0.7端口驱动LED闪烁,低电平有效/*/#include /包含头文件,程序为小5号字sbit LED = P07; void delay(unsigned char x) /延时函数unsigned char i,j;for(i
7、= 0;ix;i+)for(j = 0;j 1000;j+);void main(void) /主函数while(1)LED = LED; /LED初始状态为灭,执行一次,LED改变一次状态void delay(100); /延时一段时间/*/三、程序说明1应为使用的单片机芯片为STC89C51,因此程序开始处加入#include,文件reg51.h中包含了51芯片所有特殊功能寄存器的名称定义和相对应的地址值;2. 单片机程序单步顺序执行,先执行主函数,在主函数可以调用分函数,分函数可以调用分函数,但分函数不能调用主函数,程序执行一条命令再执行下一条,单片机等待的过程是执行了一个循环命令或一个
8、浪费时间的程序,实际执行过程永远不会结束。delay()为函数延,单片机在执行此函数相关指令时占用的时间过长,在执行过程中,如果没有中断,单片机只能忙这一件事情。利用delay()不能得到精确的延时。延时函数还可以利用下面的函数实现。/*/void delay(unsigned int x)while(x)x-;/*/3. 利用位定义命令让LED等价于P0.0或P0.7,等于先sbit P0_0 = P00,然后#defined LED P0_0,也在程序前面不做此类定义,在程序里面直接写成P00 = 1或P00 = 0,先定义是为了增加程序的可读性。程序执行P00 = 1后,P0.0对以的单
9、片机内部位寄存器就设置为高电平,同时P0.0端口也输出高电平,单片机的所有I/O口都可位未定义,也可以字节定义。4. 单片机C语言程序设计需要的C语言关键字不多,并且在keil C 中用到的关键字是独有的,因此对于没有学习过C语言的人学习单片机C语言程序设计困难并不是很大,主要掌握单片机C语言书写格式,怎样用C语言控制单片机的硬件资源,另外在编程时,还要有清晰的逻辑思维头脑和认真实践,由浅逐步深入学习,当你坚持到最后时,单片机C语言程序设计实际上很简单。5. 每个人在编写程序时都有自己的风格。一般情况下,函数的字符左行距为0,其下每条语句前留一个“tab”键空。算数逻辑符号的左右留一个空格,关
10、键语句要有中文或英文说明,每一个函数有时也可以用“/*.*/”上下隔开这样有助于提高程序的层次感和可读性。3.1.2利用P0口驱动8个LED,产生跑马灯或流水灯效果一、参考电路实验电路见图3-3-2所示,在单片机的最小系统的基础上,P0口依次接入8个LED,P0口的上拉电阻可以使用8个1K电阻。图3-1-2流水灯电路在利用Proteus软件仿真时,可以用电阻排代替。单片机最小系统在单片机元件放置后已经默认,即C1、C2、C3、CY、R1可以省略。其它元件选取见表3.1.2所示。表3.1.1 3-1-2电路Proteus软件元件表元件名称component总类Category分类Sub- Cat
11、egory结果Results模型Model type单片机Micoroprocessor IC8051 Family80C51电阻排ResistorResistor PackRESPACK-8ANALOG发光二极管OptoelectronicsLEDsled-redDIGITAL二、参考程序/*/#includevoid delay(void)unsigned char i,j;for(i = 0;i 255;i+)for(j = 0;j 255;j+);void main(void)unsigned char j = 0x01;while(1)j = j 1;if(j = 0x00) j =
12、 0x01;P0 = j;delay();/*/三、程序说明1.语句j = j 1,执行的目的是控制P0整个字节的数据在循环时左移变化,程序循环第一次, P0 = 0x02,P0.1连接的LED亮,其它灭;循环第七次,P0.7连接的LED亮,其它灭;循环第八次,j = 0x00,if语句条件满足,j = 0x01,P0.0连接的LED亮,其它灭,然后依次变化下去。如果P0口驱动8个LED低电平有效,如下修该:/*/void main(void)unsigned char j = 0xfe;while(1)j =( j 1) | 0x01 ;if(j = 0xff) j = 0xfe;P0 =
13、j;delay();2. delay()函数无形参,延时时间不能设置。3.1.3利用P0口驱动一个数码管,显示0 9,并循环一、参考电路实验电路在单片机的最小系统基础上,P0口接一只共阳数码管,见图3-1-3。带小数点的数码管是由8个LED组成,七个LED组成数字,另一只LED用来显示小数点。如果数码管内部的8只LED的正极接在一起,负极分别引出,引脚依次命名为a、b、c、d、e、f、g和dot,称为带小数点的7段共阳极性数码管。图3-1-3单片机驱动共阳数码管电路单片机的P0.0-P0.7口分别接数码管的a-dot引脚,如果让数码管显示1,数码管b、c段亮,程序控制P0输出0xbe十六进制编
14、码即可,因此共阳数码管显示0-9十进制数字,需要利用10个显示码组成的数组。对于共阴数码管,也有相应的编码要求。小数点在不用时一般不让显示,高位端口P0.7输出高电平即可。由于P0每个端口的灌电流达20mA,数码管每段LED正常显示5mA即可,因此需要R29用来限制数码管每一段电流,以防止驱动电流过大而烧毁器件。利用Proteus软件仿真时,数码管采用Optoelectronics元件库中7-Segment Displays下的7-SEG-COM-ANODE。二、参考程序/*/#includecode unsigned char seven_seg10 = 0xc0,0xf9,0xa4,0xb
15、0,0x99,0x92,0x82,0xf8,0x80,0x90;void delay (void) /* 时间延迟函数 */ unsigned char i,j;for (i = 0;i 255;i+) for (j = 0;j = 255;j+);void main (void)unsigned char i; /* 变量 i 用来储存 09 */ /*无穷循环 */while (1) for (i = 0; i10; i+)P0 = seven_segi; /*输出09到共阳七段显示器*/delay(); /*调用时间延迟函数delay*/*/三、程序说明1. 当程序中使用常量数据时,可以
16、把数据存储在单片机的程序存储器中,对此类数据声明时,前面需要加上关键字code或const,如本实验中的共阳数码管数字显示需要的编码,表3-1-3是共阳数码管编码,表3-1-4是共阴数码管编码。单片机驱动液晶显示器显示的汉字也是一种常量数据。表3-1-3共阳数码管显示编码显示数字dotgfedcba16进制0110000000xc01111110010xf92101001000xa43101100000xb04100110010x995100100100x926100000100x827111110000xf88100000000x809100100000x90表3-1-4共阴数码管显示编码显
17、示数字dotgfedcba16进制0001111110x3f1000001100x062010110110x5b3010011110x4f4011001100x665011011010x6d6011111010x7d7000001110x078011111110x7f9011011110x6f2.本程序数码管显示使用了一个for循环,让变量i依次由0递增到9,并将数字显示码送到P0,需要注意,seven_seg10 有10各数据,seven_seg0 为第一个数据,seven_seg9 为第10个数据。数码管显示的数据变化时间由延时函数决定。for循环体嵌套在while循环体中,数码管回循环显
18、示09,永不结束,除非电路断电。利用数码管也可以显示日期和时间,在以后的程序设计案例中就可以学到。3.2单片机定时器/计数器应用3.2.1利用Timer0中断产生1秒延时,让数1个码管显示秒计数本案例主要目的是熟练掌握单片机内部Timer0或Timer1的编程控制方法,会利用Timer0或Timer1中断精确定时。同时掌握数码管动态显示原理,学会6位数字显示的程序设计。本案例使用的电路为图3-1-3。一、程序设计/*/#includecode unsigned char seven_seg10 = 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x9
19、0;unsigned char cp,i; /声明全局变量void timer0_isr(void) interrupt 1 / timer0中断服务函数TR0 = 0; /停止计数TL0 = 0x11; / TL0重新预置TH0 = 0xee; / TH0重新预置TR0 = 0; /开始计数cp+; / timer0中断1次,变量cp加1if(cp = 200) /中断200次,时间刚好为1秒cp = 0;i +;if(i = 10) i = 0;P0 = seven_segi / P0输出显示数据void timer0_initialize(void) / timer0中断初始化函数EA
20、= 0; /设置中断允许寄存器IE中的 EA位,关闭中断总开关TMOD = 0x01; /设置计时器模式控制寄存器,Time0工作在定时方式1TR0 = 0; /设置计时器控制寄存器TCON寄存器的TR0位为0,Timer0/停止计数TL0 = 0x11;TH0 = 0xee; / Timer0的16位计数器初始值为0xee11,12MHz晶体振/频率,单片机的机器周期为1微妙,Timer0每1微秒加1/计数,加满溢出变产生中断,从计数到中断刚好为5毫秒PT0 = 1; /设置中断优先次序寄存器IP中的PT0位,Timer0中断优先ET0 = 1; /设置中断允许寄存器IE中ET0的位,开启中
21、断小开关EA = 1; /打开中断总开关TR0 = 1; /开始计数void main (void)timer0_initialize() / timer0初始化,为中断做好准备while (1); /等待中断/*/二、程序说明1.中断服务函数名中,interruput为关键字,1为timer0中断号。在reg51.h头文件中已经定义,表3.2.1为单片机常用中断的中断号。在使用中断服务函数时,直接在名后加interruput和中断号即可。表3.2.1 reg51.h中单片机常用中断号定义中断源中断触发原因中断号INT0外部INT0引脚有低电平或下降沿信号输入0Timer0Timer0计数溢出
22、1INT1外部INT0引脚有低电平或下降沿信号输入2Timer1Timer1计数溢出3串行中断串口缓存SBUF写入数据或读出数据42.timer0可以用作计数,也可以用来定时,由由TMOD寄存器中为决定,作为计数器使用时,接受单片机外部引脚P3.4输入的脉冲加计数;作为定时器使用时,接受单片机内部的机器周期脉冲。如果单片机的振荡频率为,振荡周期为,则机器周期为。本案例中MHz,则微秒,timer0工作在模式1为16为加计数器,当计数器初始值为0xee11时,则从开始计数到产生中断需要的时间为0xffff 0xee11个微秒,刚好为5000微秒,也即5毫秒。本案例timer0的初装也可以利用下面
23、语句完成:/*/#defined TEMOR0_COUNT 0xee11TL0 = TEMOR0_COUNT & 0x00ff; /取TEMOR0_COUNT的低字节并装入TL0TH0 = TEMOR0_COUNT 8; / TEMOR0_COUNT左移8位,并将低字节装入TEMOR0_COUNT/*/利用定时器/计数器定时时,需要现设置工作模式,并计算它的初装值,计算初装值不好计算,常利用计算机中的计算器工具辅助。timer0工作在模式1可以最大65535微秒中断1次,如工作模式2,最大256微秒中断1次。3.当程序中只涉与一个中断时,可以不对中断的优先级进行设置,因此在本案例中语句PT0
24、= 1 可以省略。程序中有多个中断但没有进行优先级设定的情况下,单片机中断优先级默认按终端号递增而依次降低。4.数码管显示语句放在了timer0中断服务函数里面,由于5毫秒中断1次,因此数码管显示的数据会每5毫秒更新1次。1秒内更新200次,更新过程是把原来的数据覆盖,但显示数据1秒内变化1次。3.2.2利用4个数码管,实现秒信号加计数一、电路原理图3-2-1所示的电路中,4位七段数码管采用4只单个数码管组成,可以显示00009999,数据输入端并联后接P0口,位选即每个数码管的阳极分别接P2口,中间加非门驱动。因为P2口反相驱动,因此,如果千位数码管显示,P2.7输出低电平,P2的其它端口输
25、出高电平,并且此时P0输出千位数据。在利用Proteuse软件仿真时,数码管SEVEN_SEG使用7SEG-MPX4-CA,非门NOT选用74 HC series类型中Gates & Inverters分类下的74HC04。电阻R29是限流电阻。图3-2-1 4位七段数码管显示二、程序设计/*/#include#defined unsigned char uchar#defined unsigned int uintcode uchar seven_seg10 = 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90;code uchar seg_
26、scan4 = 0xef,0xdf,0xbf,0x7f; /各个数码管对应的位选数据uchar counter4 = 0,0,0,0; /个位、十位、百位和千位数uint cp,i;void timer0_isr(void) interrupt 1 / timer0中断服务函数uchar j;TR0 = 0; /停止计数TL0 = 0x11; / TL0重新预置TH0 = 0xee; / TH0重新预置TR0 = 0; /开始计数cp+; / timer0中断1次,变量cp加1if(cp = 200) /中断200次,时间刚好为1秒cp = 0;counter0+; /个位数,1秒加1if(c
27、ounter0 = 10) counter0= 0; counter1+; if(counter1 = 10) counter1= 0; counter2+;if(counter2 = 10) counter2= 0; counter3+;if(counter3 = 10) counter3= 0;P0 = seven_segcounterj; / P0输出数据编码P2 = seg_scanj; /P2输出位选信号j+;if(j = 4) j = 0;void timer0_initialize(void) / timer0中断初始化函数EA = 0; TMOD = 0x01;TR0 = 0;
28、 TL0 = 0x11;TH0 = 0xee;PT0 = 1; ET0 = 1; EA = 1; TR0 = 1; void main (void)timer0_initialize() while (1); /*/三、程序说明1imer0第1次中断,j = 0,显示个位,显示5毫秒后,timer0第2次中断,j = 1,显示十位,以后随着中断依次显示百位和千位,最后重新显示个位。每位显示停留的时间为5毫秒。中断4次才能让4为依次扫描显示完,占用的时间为20毫秒,1秒钟内,4位数码管扫描显示50遍,根据人眼视觉暂留特点,你看到的结果是4位数据同时显示,这种显示方法为动态扫描显示。2变量j =
29、0时,counter0是个位十进制数,执行P0 = seven_segcounter0,P0口输出个位显示数据编码;数码管的位选信号只利用了P2的高4位,执行P2 = seg_scan0,P2 = 0xef,经过非门反相,加在数码管上的位选信号只有个位是高电平,此时只显示个位。3数组counterj也可以用1个变量代替,在显示时需做下面处理/*/uint x;P0 = seven_segx/1000; / P0输出千位数据编码P2 = seg_scan3; /千位数码管共阳极设置为高电平P0 = seven_segx%1000/100; / P0输出百位数据编码P2 = seg_scan2;
30、/千位数码管共阳极设置为高电平P0 = seven_segx%1000%100/10; / P0输出十位数据编码P2 = seg_scan1; /千位数码管共阳极设置为高电平P0 = seven_segx%1000%100%10; / P0输出千位数据编码P2 = seg_scan0; /千位数码管共阳极设置为高电平/*/4思考题(1)要让数码管在电路加电开始就显示1230,怎样更改有关数据?(2)为什么不用P2口直接驱动数码管?(3)本案例如果200微秒中断1次,也可以实现1定时,显示结果会怎样?3.2.3设计一个6位数码管时间显示程序本案例使用6位数码管显示时间,能显示时/分/秒,显示格式
31、是00.00.00到23.59.59。其中小时和分钟之间的小数点常亮,分钟和秒之间的小数点进行秒闪烁。一、 参考电路二、 参考程序/*/#include#defined unsigned char ucharcode uchar seven_seg10 = 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90;code uchar seg_scan6 = 0xfb,0xf7,0xef,0xdf,0bf,0x7f; /各个数码管对应的位选数据uchar counter3 = 0,0,0; /时位、分位和秒位uchar tick,cp,i,j; /ti
32、ck:秒信号,cp:中断次数累计void dispaly(void) uchar d;d = d * tick; /d 的值1秒内改变1次,要么0x00,要么0xffd = d | 0x7f; /d 的值1秒内改变1次,要么0x7f,要么0xffswich(i)case 0 : P0 = seven_segcounter0%10; break; /显示秒个位case 1 : P0 = seven_segcounter0/10; break; /显示秒十位case 2 : P0 = d & seven_segcounter0%10; break; /数据高8位1秒改变1次,实现小/数点秒闪烁ca
33、se 3 : P0 = seven_segcounter0/10; break; /显示分十位case 4 : P0 = 0x7f & seven_segcounter0%10; break; /显示数据高8位为低电平,小数点常亮case 5 : P0 = seven_segcounter0/10; break; /显示时十位 break; P2 = seg_scani;i+;if(i = 6) i = 0;void timer0_isr(void) interrupt 1 / timer0中断服务函数TR0 = 0; /停止计数TL0 = 0x11; / TL0重新预置TH0 = 0xee;
34、 / TH0重新预置TR0 = 0; /开始计数cp+; / timer0中断1次,变量cp加1if(cp = 100) /中断100次,时间刚好为0.5秒cp = 0;tick = tick; /秒脉冲信号j+;if(j = 2) /刚好1秒j = 0; counter0+; /个位数,1秒加1if(counter0 = 60) counter0= 0; counter1+; if(counter1 = 60) counter1= 0; counter2+;if(counter2 = 24) counter2= 0;display(); void timer0_initialize(void) / timer0中断初始化函数EA = 0; TMOD = 0x01;TR0 = 0; TL0 = 0x11;TH0 = 0xee;PT0 = 1; ET0 = 1; EA = 1; TR0 = 1; void main (void)timer0_initialize() / timer0初始化,为中断做好