会议网站建设,网站设计书模板,上海网站设计联系方式,网站建设与营销服务一#xff0c;单片机及开发板介绍
1#xff0c;基本介绍
单片机#xff0c;英文Micro Controller Unit#xff0c;简称MCU内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(…一单片机及开发板介绍
1基本介绍
单片机英文Micro Controller Unit简称MCU内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机LED等)的控制单片机跟计算机相比单片机算是一个袖珍版计算机一个芯片就能构成完整的计算机系统。但在性能上与计算机相差甚远但单片机成本低、体积小、结构简单在生活和工业控制领域大有所用同时学习使用单片机是了解计算机原理与结构的最佳选择单片机上电时所有I/O口默认都为高电平
2命名规则
以下为STC89C52系列的命名规则不同系列单片机命名规则可能不同如STC32G12K128351-LQPF64与其命名规则不同具体的查STC官网手册
3内部结构
单片机基本采用8051微处理器为内核不同系列单片机不同主要是下图除8051微处理器外其他外设的不同具体系列单片机的不同可以在STC官网查手册 4最小应用系统
要想让单片机运行起来需要给外部的一些电路单片机和能让它运行起来的基本电路叫做最小应用系统如下图所示具体系列单片机的不同可以在STC官网查手册
右上为电源电路左下为晶振电路有一些单片机有内置晶振可以不要此电路为CPU提供时钟驱动程序一步一步往下走左中为复位电路可以让程序从头开始运行高电平时复位上电一瞬间电容充电相当于短路电路只连接上半部分将RST接为高电平当电容充满时相当于断路电流流向下半电路R1此时RST为低电平达到一个上电复位的效果 。
二点亮LED
1点亮一个LED
下图所示LED右端接VCC左端如果为负极则导通正极则不导通单片机引脚输出高电平不导通低电平导通。 CPU控制引脚高低电平的原理
MCU单片机内有许多个寄存器寄存器就是存储器。寄存器以8个为一组也就对应了引脚8个为一组,下图所示为P2系列引脚每个存储器对应一个引脚每个存储器都连接了一根线再通过驱动器来增大电流然后连接到引脚。CPU通过软件程序直接访问寄存器给他里面写值如果值为1则寄存器输出高电平如果为0则寄存器输出低电平 要想让引脚P20输出低电平就要通过代码实现
#include REGX52.H //其中定义了各个寄存器的地址这样P2就有了定义//每个芯片的库是不一样的右键点击Insert就可以引入该芯片的库
void main(){P20xFE; //要使P20所连接的灯亮其他灯都不亮就要使寄存器配置为1111 1110 //单片机软件编程不支持二进制所以就要将二进制转换为十六进制
}
//上述是一次操作了八位寄存器也可以直接操作一位寄存器写为P2_00;
//注P2_0这种1位寄存器只在REGX52.H中有定义在REG52.H中没有定义 2LED闪烁
创建延时函数
第一步在STC-ISP中找到软件延时计算器并选择 第二步修改系统频率为晶振频率。选择定时长度即延时时间。选择8051指令集可以看又边框你选择的指令集适用于什么系列。 第三步
点击生成C代码复制代码然后回到Keil软件将代码粘贴这样在使用的时候直接调用即可。
代码演示
#include REGX52.H
#include INTRINS.H #此头文件中定义了很多函数其中包括_nop_()
void Delay500ms(void) //12.000MHz
{unsigned char data i, j, k;_nop_();i 4;j 205;k 187;do{do{while (--k);} while (--j);} while (--i);
}void main(){while(1){P20xFE;Delay500ms();P20xFF;Delay500ms();}
}
扩展对延时函数进行修改可以指定延时多少毫秒
void Delay1ms(int x) //12.000MHz
{unsigned char data i, j;while(x){i 2;j 239;do{while (--j);} while (--i);xx-1;}
}
//在延时1ms的函数上进行修改
3LED流水灯
#include REGX52.H
#include INTRINS.H #此头文件中定义了很多函数其中包括_nop_()
void Delay500ms(void) //12.000MHz
{unsigned char data i, j, k;_nop_();i 4;j 205;k 187;do{do{while (--k);} while (--j);} while (--i);
}void main(){while(1){P20xFE; //1111 1110亮第一个Delay500ms();P20xFD; //1111 1101亮第二个Delay500ms();P20xFB; //1111 1011亮第三个Delay500ms();P20xF7; //1111 0111亮第四个Delay500ms();P20xEF; //1110 1111Delay500ms();P20xDF; //1101 1111Delay500ms();P20xBF; //1011 1111Delay500ms();P20x7F; //0111 1111Delay500ms();}
}
三独立按键控制LED
轻触按键相当于电子开关按下时接通松开时断开
1按下开关亮松开就灭
#include REGX52.H
void main(){while(1){if(P3_10){//开关连接的I/O口为P3_1单片机接通时所有I/O口都为高电平。由于开关另一端接 //地所以开关按下时P3_1输出低电平。P2_00;//点亮第一个LED灯}else{P2_01;}}
} 2按一下开关亮松开后仍亮再按一次灭
对于机械开关当机械触电断开闭合时由于机械触点的弹性作用一个开关在闭合时不会马上稳定地接通在断开时也不会一下子断开所以在开关闭合及断开的瞬间会伴随一连串的抖动。为了解决这个问题可以在按下时延时几十毫秒松开时也延时几十毫秒。 #include REGX52.H
#include INTRINS.H
void Delay1ms(int x) //12.000MHz
{unsigned char data i, j;while(x){i 2;j 239;do{while (--j);} while (--i);xx-1;}
}
void mian(){while(1){if(P3_10){ Delay1ms(20);while(P3_10);Delay1ms(20);P2_0~P2_0;//位运算取反可以让P2_0亮或者灭}}}
3LED按照1~9的二进制形式依次闪烁
#include REGX52.H
#include INTRINS.H
void Delay1ms(int x) //12.000MHz
{unsigned char data i, j;while(x){i 2;j 239;do{while (--j);} while (--i);xx-1;}
}
void mian(){unsigned int LEDNum0;//设置一个变量表示数字1~9,因为寄存器8个为一组而char为8个字符所 //以用charwhile(1){if(P3_10){ Delay1ms(20);while(P3_10);Delay1ms(20);LEDNum; //LEDNum不断增加即依次表示1~9P2~LEDNum;//当单片机通电后所有引脚默认为高电平1根据点亮LED工程中所说的 //二极管负极接引脚所以当引脚输出低电平时二极管才能点亮}}
}
//P2因为所有引脚默认为高电平所以为1111 1111P2超出范围变为0000 0000
//再P2变为1111 1111
4LED依次点亮按一下开关移位一次
#include REGX52.H
#include INTRINS.H
void Delay1ms(int x) //12.000MHz
{unsigned char data i, j;while(x){i 2;j 239;do{while (--j);} while (--i);xx-1;}
}
void mian(){unsigned int LEDNum0;//设置一个变量表示数字1~9while(1){if(P3_10){ Delay1ms(20);while(P3_10);Delay1ms(20);LEDNum;if(LEDNum8) LEDNum0;P2~(0x00LEDNum);//每次左移一位}}
}
四数码管
1数码管介绍
1一位LED数码管
一位数码管共有8个LED。其内部连接如下图为了减少数码管的引脚数在数码管内将8个LED的正极或负极引脚连接起来接成一个公共端COM端根据公共端是正极还是负极可分为共阳极和共阴极。数码管共十个引脚左下角的为1号引脚逆时针递增中间的两个引脚即3号和8号引脚为两个公共端。 2多位LED数码管
如图为4位数码管他有两排12个引脚。内部电路如图所示也分为共阳极和共阴极两种方式。控制方式比如说要控制第二个数码管显示数字1共阳极我们就可以使9号引脚为高电平再令11号引脚为低电平假如11号引脚控制的是最右段LED这样就可以使第二个数码管显示一这叫做静态显示。但是这种方法只能让单个数码管显示或者多个数码管显示同一个数字为了让不同数码管显示不同数字我们就要采用动态显示的方法利用人眼的暂留特性先显示第一位再快速显示第二位这样就看起来好像两位在同时点亮。 2数码管驱动器件
下图所示为单片机学习板数码管部分的原理图 138译码器
P22,P23,P24 接单片机I/O口引脚Y0~Y7分别接8个数码管的8个公共端此译码器的作用是减少单片机I/O口的占用将8个引脚转为P22,P23,P24这3个引脚控制所以叫138译码器。原理二进制转化。P24,P23,P22(C为最高位也就是P24为最高位)可以表示二进制111转化为十进制就是7而P24,P23,P22为000时转为十进制就为0所以P24,P23,P22可以表示0~7这8个数也就对应了Y0~Y7。当CBA分别为001时就对应了Y1为0其他都为1当CBA为011时就代表Y3为0其他都为1。 左下角的G1,G2A,G2B叫做使能端相当于一种电源开关。G1接高电平G2A,G2B接上低电平这个芯片就能工作。除此之外这个芯片还需要电源和接地。
双向数据缓冲器
VCC和GND为电源。OE是这个芯片的使能如图所示它连接了低电平所以这个芯片可以工作低电平有效。DIRdirection引脚也就是方向的意思主要用于控制将左边数据缓冲到右边还是右边数据缓冲到左边如果DIR接高电平就是从左到右如果接低电平就是从右往左如图所示它接LE引脚LE为跳线帽它插在哪个地方就把两个引脚给短路实物图中将LE插到VCC所以此缓冲器从左往右缓冲。A0连接B0,A1连接B1A2连接B2依次类推它就是起一个数据缓冲的作用。 需要缓冲的原因因为单片机高电平驱动能力有限输出最大电流不能太大 低电平驱动能力强一些所以LED通常采用低电平点亮。所以如果没有缓冲器直接与单片机连接它的电流会很小灯会暗。加上缓冲器后可以提高驱动能力单片机的高电平会作为信号只需要微弱的信号即可被缓冲器接收到进入缓冲器然后缓冲器用自己的电源为数码管提供高电平
3静态数码管显示
#include REGX52.H
unsigned char NixieTable[]{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x39,0x5E,0x79,0x71,0x00};//分别对应0~9A,B,C,D,E,F,空
void Nixie(unsigned char Location, Number){switch(Location)//Location代表哪个数码管显示{case 1:P2_41;P2_31;P2_21;break;case 2:P2_41;P2_31;P2_20;break;case 3:P2_41;P2_30;P2_21;break;case 4:P2_41;P2_30;P2_20;break;case 5:P2_40;P2_31;P2_21;break;case 6:P2_40;P2_31;P2_20;break;case 7:P2_40;P2_30;P2_21;break;case 8:P2_40;P2_30;P2_20;break;}P0NixieTable[Number];//要显示的数字
}
void main(){while(1){Nixie(3,2);//第3个晶体管显示2}
}
4动态数码管显示
在操作数码管时如果仅是下方代码会出现下图所示的数码管重影现象这是因为数码管显示时会有一个位选和段选的过程一般地操作数码管时先执行段选再执行位选。位选是选择待操作的数码管如开发板上的是8位数码管位选就是选择8位数码管中的某一个。段选是选择数码管里面的LED灯即通过选择点亮响应的LED灯以达到显示需要的数据的目的。但是由于单片机速度很快位选-段选-位选-段选————的过程就会变为段选-位选-段选-位选的过程再选中下一位时上一位还没有完全消失直接串到下一位导致重影
#include REGX52.H
void Delay1ms(int x) //12.000MHz
{unsigned char data i, j;while(x){i 2;j 239;do{while (--j);} while (--i);xx-1;}
}
unsigned char NixieTable[]{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x39,0x5E,0x79,0x71,0x00};//分别对应0~9A,B,C,D,E,F,空
void Nixie(unsigned char Location, Number){switch(Location)//Location代表哪个数码管显示{case 1:P2_41;P2_31;P2_21;break;case 2:P2_41;P2_31;P2_20;break;case 3:P2_41;P2_30;P2_21;break;case 4:P2_41;P2_30;P2_20;break;case 5:P2_40;P2_31;P2_21;break;case 6:P2_40;P2_31;P2_20;break;case 7:P2_40;P2_30;P2_21;break;case 8:P2_40;P2_30;P2_20;break;}P0NixieTable[Number];//要显示的数字
}
void main(){while(1){Nixie(3,2);//第3个晶体管显示2Nixie(2,4);Nixie(1,5);}
} 为了避免这个问题我们就需要在段选之后把它清零 以下为正确代码
#include REGX52.H
void Delay1ms(int x) //12.000MHz
{unsigned char data i, j;while(x){i 2;j 239;do{while (--j);} while (--i);xx-1;}
}
unsigned char NixieTable[]{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x39,0x5E,0x79,0x71,0x00};//分别对应0~9A,B,C,D,E,F,空
void Nixie(unsigned char Location, Number){switch(Location)//Location代表哪个数码管显示{case 1:P2_41;P2_31;P2_21;break;case 2:P2_41;P2_31;P2_20;break;case 3:P2_41;P2_30;P2_21;break;case 4:P2_41;P2_30;P2_20;break;case 5:P2_40;P2_31;P2_21;break;case 6:P2_40;P2_31;P2_20;break;case 7:P2_40;P2_30;P2_21;break;case 8:P2_40;P2_30;P2_20;break;}P0NixieTable[Number];//要显示的数字Delay(1);//使其稳定显示否则数码管会变暗P00x00;//清零
}
void main(){while(1){Nixie(3,2);//第3个晶体管显示2Nixie(2,4);Nixie(1,5);}
}
补充
上述方法属于单片机直接扫描的方法就是不断给单片机输入要显示的数据。这种方法对硬件设备要求简单但会耗费大量CPU时间。
专用驱动芯片扫描的方法其内部自带显存扫描电路单片机只需要按照通讯协议告诉它显示什么即可 TM1640专用驱动芯片扫描74HC595三根线即可驱动
五LCD1602调试工具
使用LCD1602液晶屏作为调试窗口提供类似printf函数的功能可实时观察单片机内部数据的变换情况便于调试和演示。也可使用串口进行调试
这里提供的LCD1602代码属于模块化的代码只需要知道这个函数的作用和使用方法即可。这些代码是需要自己编写的有具体的.c文件.h文件
使用之前必须先初始化 #include REGX52.H
#include LCD1602.H
void main(){LCD_Init();LCD_ShowChar(1,1,A);//在第一行第一列显示ALCD_ShowString(1,3,Hello);//在第一行第三列开始显示HelloLCD_ShowNum(1,9,123,3);//在第一行第九咧显示123显示位数为3LCD_ShowSignedNum(1,13,-66);
}
六矩阵键盘
在键盘中按键数量较多时为了减少I/O口的占用通常将按键排列成矩阵形式。采用逐行或逐列的扫描就可以读出任何按键的状态。数码管的扫描也是矩阵的形式它是输出扫描矩阵键盘属于输入扫描。下图为矩阵键盘的原理图 矩阵按键读取过程如上图如果给P10低电平0读取P17,P16,P15,P14的引脚状态如果P17输入为低电平则表示按键S4按下如果P16输入为低电平则表示S8按下。
单片机的I/O口为一种弱上拉模式又叫准双向口这使得一个I/O口既可以输入又可以输出。为什么上述按键读取过程中P17接触了低电平P10后读取为低电平而不是他本身输出的高电平这是因为单片机弱上拉模式的内部结构简单图为下图当要输出高电平时就把开关接到VCC要输出低电平就把开关接到地读取时在图中节点引出的电路然后经过施密特触发器等后续电路来读取。这样的话如果I/O口接地那么读取的就是接地的低电平0而不会受输出高电平的影响。 1利用LCD1602显示按下的矩阵键盘的键码值
#include REGX52.H
#include Delay.h
#include LCD1602.h
int MatrixKey(){int KeyNumber0;P10xFF;//将P1的I/O口全部置为0P1_30;if(P1_70){Delay(20);while(P1_70);Delay(20);KeyNumber1;}//按键检测if(P1_60){Delay(20);while(P1_60);Delay(20);KeyNumber5;}if(P1_50){Delay(20);while(P1_50);Delay(20);KeyNumber9;}if(P1_40){Delay(20);while(P1_40);Delay(20);KeyNumber13;}P10xFF;//将P1的I/O口全部置为0P1_20;if(P1_70){Delay(20);while(P1_70);Delay(20);KeyNumber2;}//按键检测if(P1_60){Delay(20);while(P1_60);Delay(20);KeyNumber6;}if(P1_50){Delay(20);while(P1_50);Delay(20);KeyNumber10;}if(P1_40){Delay(20);while(P1_40);Delay(20);KeyNumber14;}P10xFF;//将P1的I/O口全部置为0P1_10;if(P1_70){Delay(20);while(P1_70);Delay(20);KeyNumber3;}//按键检测if(P1_60){Delay(20);while(P1_60);Delay(20);KeyNumber7;}if(P1_50){Delay(20);while(P1_50);Delay(20);KeyNumber11;}if(P1_40){Delay(20);while(P1_40);Delay(20);KeyNumber15;}P10xFF;//将P1的I/O口全部置为0P1_00;if(P1_70){Delay(20);while(P1_70);Delay(20);KeyNumber4;}//按键检测if(P1_60){Delay(20);while(P1_60);Delay(20);KeyNumber8;}if(P1_50){Delay(20);while(P1_50);Delay(20);KeyNumber12;}if(P1_40){Delay(20);while(P1_40);Delay(20);KeyNumber16;}return KeyNumber;}
void main(){LCD_Init();while(1){int aMatrixKey();if(a){LCD_ShowNum(1,1,a,2);//在第一行第九咧显示123显示位数为3}}
}
2,矩阵键盘密码锁
S1~S9表示数字1~9S10表示数字0S11为确定S12为取消
七定时器计数器
1定时器介绍
定时器属于单片机内部资源其电路的连接和运转均在单片机内部完成。
定时器作用1,用于计时系统可实现软件计时或是程序每隔一段时间完成一项操作2替代长时间的延时函数提高CPU运行效率和处理速度延时函数会使CPU进入等待直到延时时间结束CPU才开始重新运行这就说明CPU在延时的这段时间内无法完成其他事情。而利用定时器来延时的话CPU就可以做其他事情。
STC89C52中有3个定时器T0,T1,T2T0和T1与传统的51单片机兼容T2是此型号单片机增加的资源不同型号的单片机定时器的个数和操作方式有所不同但一般T0,T1的操作方式是51单片机所共有的
2定时器原理
1运行框图
定时器在单片机内部相当于一个闹钟根据时钟的输出信号每来一次脉冲计数单元就增加一当计数单元数值增加到“设定的闹钟提醒时间”时计数单元就会向中断系统发出中断申请产生闹铃提醒使程序跳转到中断服务函数中执行 2定时器内部模式及模式1原理
工作模式
STC89C52的T0和T1均有四种工作模式
模式013位定时器/计数器模式116位定时器/计数器最常用模式28位自动重装模式模式3两个8位计数器
模式1原理
如下图所示其内部电路按照运行框图共分为三部分。 计数器
可以看到计数器中的TL1(timer low)和TH1(timer high),他就是一个16位的计数器由两个字节组成高字节为TH低字节为TL。1表示为定时器T12个字节最大能计数65535。左边的时钟为计数器提供脉冲每来一个脉冲计数器就加一。当加到最大的65535时再来一个脉冲计数器就会归0当计数器达到最大值时给TF1一个标志位然后传给中断系统产生中断。计数器下方为它的控制位可以控制计数器启动或者暂停
时钟
脉冲的时间由SYSclk来确定它是系统时钟即晶振周期STC89C52晶振频率为12MHz。时钟有两个来源一个是SYSclk一个是T0 PinT0 Pin是单片机上的一个引脚也就是说单片机的时钟可以由单片机自己提供也可以由引脚连接的外设提供当由外设提供时定时器就变成了一个计数器。SYSclk提供晶振频率后会经过分频如上图电路12表示12分频输出的就为1MHz表示一次为1微秒记一次数当记到最大值就会产生中断6表示6分频每个2微秒记一次数。电路之后的C/T表示开关如果给高电平1那么开关就会连接到T0 Pin如果给低电平0,开关就会连接到SYSclk。
中断系统 中断资源适合单片机型号有关的不同型号的单片机拥有的中断资源不同例如中断源个数不同中断优先级个数不同等。
STC89C52中有8个中断源(外部中断0定时器0中断外部中断1定时器1中断串口中断外部中断2外部中断3) 4个中断优先级
3定时器寄存器
1寄存器作用介绍
让中断按照我们想要的方式运行就要依靠定时相关寄存器。单片机中寄存器就是一种特殊的RAM一方面它可以存储和读取数据另一方面每个寄存器背后都连接了一根导线控制着电路的连接方式。寄存器相当于一个复杂机器的操作按钮单片机通过寄存器配置内部线路的连接 寄存器就是用来控制下图电路中的开关如C/T寄存器可以控制C/T开关拨到那个位置
3定时器/计数器控制寄存器TCON(timer control)
以下为官方手册上的内容 可以结合电路上的寄存器进行理解 3定时器/计数器工作模式寄存器 (timer mode)
GATE寄存器
GATE:计数器的启动暂停可以由控制寄存器的TR1直接控制也可以由TR0和外设INT1来联合控制INT1为单片机引脚。GATE就可以选择是TR1单独控制还是联合控制 控制原理可以看下图电路GATE经过非门然后经过或门最后通过与门。如果GATE置0那么经过非门信号就变为1由于是或门有1就为1所以不管INT1是0还是1或门输出的都为1这时就不受INT1控制如果GATE为1经过非门为0这时INT1为1则输出1为0则输出0也就是受INT1控制 其他寄存器 4TH,TL寄存器
前面说过来计数的寄存器
4中断寄存器 1 中断允许寄存器IE和XICON
EA为总中断允许控制位EA0所有的中断都会关闭如下简化图EA相当于总开关
其他的如ET2,ES,ET1,EX1,ET0,EX0就是控制单条路的开关 2中断优先级控制寄存器IP/XICON和IPH 5程序实现定时器计时 配置定时器模式TMOD
配置定时器模式TMOD启动定时器0并且定时器0处于16位定时器模式根据前面的表格可知M10M01时定时器0处于16位定时器模式即TMOD0000 0001换算为16进制为0x01。注意这里的TMOD上写着不可位寻址表示的是只能整体赋值而TCON上写着可位寻址则表示可以给单独一个寄存器为1或者0. 配置TCON 然后根据下表配置TCON。着重说一下配置计数器的寄存器TL0和TH0我们知道这个寄存器能记录0~65535的次数当达到65535时就请求中断在STC89C52中晶振频率12MHz12分频后也就是每隔1微秒计数加1总共可以定时65535微秒也就65毫秒左右。如果想让它定时1s我们可以先让他记满1毫秒产生中断然后再记1毫秒这样记1000次就可以达到计数1s的目的。为了让他一次能记1ms需要将它初始化为64535 配置中断 按照电路图配置即可
中断函数
中断函数即达到规定时间后需要CPU做什么这里我们用的定时器0就需要使用C这个函数.中断函数中进行简短的任务执行的任务时间不能太长 最终代码
#include REGX52.H
void Timer0_Init(){TMOD0x01;//配置模式0000 0001
//如果使用两个定时器时配置第一个而不影响第二个配置第二个而不影响第一个
//就可以使用与或的方法如配置定时器0而不影响定时器1 TMODTMOD0xF0 TMODTMOD|0x01
//第一步与使得高四位不变低四位清零第二步或可以使高四位不变
//低四位按要求改变这里是将低四位置为0001TF00//中断溢出标志位要先清0TR01//开启定时器0TH064535/256;TL064535%256;//或者用程序员计算器计算65532的十六进制ET01;//中断配置EA1;PT00;}
void main(){Timer0_Init();while(1){}}
int T0_Count;//计数变量当其为1000时说明记了1000个1ms即1s
void Timer0_Rountine(void) interrupt 1
{TH064535/256;TL064535%256;//每次记1ms后都赋初值使其从64535开始记T0_Count;if(T0_Count1000){T0_Count0;//加上需要执行的操作}
}
也可以使用STC-ISP来自动配置定时器 如下图
再定时器计算器中选择好时钟频率定时长度不能太长定时器模式根据所需要的定时器模式选择定时器时钟根据定时器原理图可知为12T 复制过来后要做修改比如STC89C52没有AUXR配置12T模式而是系统模式已经配置好了新版本有AUXR配置模式所以第一行删除。除此之外此代码没有中断的配置需要自己加上。
6应用1按键控制LED流水灯定时器 介绍两个函数这两个函数包含在函数库#Include INTRINS.H中
unsigned char _cror_unsigned char, usigned char); 和 unsigned char _crol_unsigned char, usigned char);分别指循环右移和循环左移。第一个形参表示要移位的数值第二个参数表示移多少位 比如
unsigned char a0x01;
a_cror_(a,1);//这时a等于0x02
a_cror_(a,2);//这时a等于0x04
//这样看它和左移没什么区别。
//但它们的区别是当a移到最高位0x80时他就会回到最开始的0x01以此来循环移位
//_crol_同理
#include REGX52.H
#include Timer0.h
#include Key.h
#include INTRINS.Hunsigned char KeyNum,LEDMode;void main()
{P20xFE;//先将LED等的最后一位点亮Timer0Init();//定时器初始化while(1){KeyNumKey(); //获取独立按键键码if(KeyNum) //如果按键按下{if(KeyNum1) //如果K1按键按下{LEDMode; //模式切换if(LEDMode2)LEDMode0;}}}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count;TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值T0Count; //T0Count计次对中断频率进行分频if(T0Count500)//分频500次500ms{T0Count0;if(LEDMode0) //模式判断P2_crol_(P2,1); //LED输出if(LEDMode1)P2_cror_(P2,1);}
}
//自写版本
#include REGX52.H
#include LCD1602.h
#include Key.h
#include Timer0.h
unsigned char KeyNum0,LEDMode0;
int time_count0;
int a0x01;
void main(){Timer0_Init();while(1){KeyNumKey();if(KeyNum1){LEDMode;if(LEDMode3){LEDMode1;}}}
}
void Timer0_Routine() interrupt 1
{ TL00x17;TH00xFC;time_count;if(time_count500){time_count0;if(LEDMode1){ if(a128) a1;P2~a;aa1;}if(LEDMode2){if(a1) a128;P2~a;aa1;}}}7应用2定时器时钟
#include REGX52.H
#include Delay.h
#include LCD1602.h
#include Timer0.hunsigned char Sec55,Min59,Hour23;void main()
{LCD_Init();Timer0Init();LCD_ShowString(1,1,Clock:); //上电显示静态字符串LCD_ShowString(2,1, : :);while(1){LCD_ShowNum(2,1,Hour,2); //显示时分秒LCD_ShowNum(2,4,Min,2);LCD_ShowNum(2,7,Sec,2);}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count;TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值T0Count;if(T0Count1000) //定时器分频1s{T0Count0;Sec; //1秒到Sec自增if(Sec60){Sec0; //60秒到Sec清0Min自增Min;if(Min60){Min0; //60分钟到Min清0Hour自增Hour;if(Hour24){Hour0; //24小时到Hour清0}}}}
}//自写版本
#include REGX52.H
#include LCD1602.h
#include Key.h
#include Timer0.h
#include Delay.h
unsigned char KeyNum0,LEDMode0;
int time_count0;
int s,h,m;
void main(){Timer0_Init();LCD_Init();LCD_ShowString(1,1,CLOCK:);LCD_ShowString(2,1, : : );while(1){LCD_ShowNum(2,1,h,2);LCD_ShowNum(2,4,m,2);LCD_ShowNum(2,7,s,2);}
}
void Timer0_Routine() interrupt 1
{ TL00x17;TH00xFC;time_count;if(time_count1000){time_count0;s;if(s60){m;s0;}if(m60){h;m0;}}
}八串口通信
1串口理论知识介绍
1串口基本介绍
串口是一种应用十分广泛的通讯接口可实现两个设备的互相通信。单片机的串口可以实现单片机与单片机单片机与电脑单片机与各式各样的模块互相通信。
51单片机内部自带UART可实现单片机的串口通信 2串口数据发送STC-ISP
用单片机给电脑发送数据时可以用STC-ISP中的串口助手的接收缓冲区查看。也可以用电脑像单片机发送数据以实现某些功能在发送缓冲区输入要发送的数据 3串口连接方式
简单的双向串口通信有两根通信线(发送端TXD和接收端RXD)TXD和RXD要交叉连接如下图。当只需要单向传输数据时可以直接用一根通信线。此外复杂的串口会有很多通讯接口如D89母头
注意当电平标准不一致时需要使用电平转换芯片电平标准如下 电平标准
电平标准是数据1和数据0的表达方式是传输线缆中人为规定的电压和数据的对应关系串口常用的电平标准有三种
TTL电平5V表示10V表示0
RS232电平-15~-3V表示13~15V表示0
RS485电平两线压差2~6V表示1-2~-6V表示0差分信号
4常见串口比较 点对点通信指两个设备之间进行通信。 CAN主要用于汽车领域使用差分信号传输距离远稳定性好 异步同步进一步解释通信双方发送数据时比如A发10(高低电平)B收到的是10(高低电平)。A再发1100B如果接受的速率不同就可能会接收成10(A发送1个A用时1ms而B为2ms接收一次这样B接受的就变为1)所以要保证接受速率相同比如A每隔1ms发送一次那么B就要每隔1ms接受一次这就是异步双方规定一个相同的速率发送和接收。同步是通过一根时钟线只检测这一根线当A给一个上升沿高电平B就采样一次这样来达到目的同步用于对时间要求严格它可以做到同时发送与接收。可以看一下上面不同的串口有同步的引脚都需要SCL或SCLK这样的时钟线
总线进一步理解可挂载多个设备的都用到总线可以用下图理解 5STC89C52内部串口 6串口参数和时序图
波特率串口通信的速率(发送和接收数据位的间隔时间)
校验位用于数据检验比如STC89C52的8位UART和9位UART8位UART表示一个字节9位UART就是一个字节加上最后一位校验位可以检验前面数据的正确性。常用的是奇偶校验以奇校验为例双方约定了使用奇校验A发送0000 0011这个是1个数为偶数所以再加一位1发送数据为0000 0011 1B接收时如果1的个数变为偶数则表示数据错误如果A发送为0000 0001则再加一位0发送数据1为0000 0001 0B接收时如果1的个数变为偶数则表示数据错误。但是如果两个数据同时反转1还为奇数这样就检测不出来所以这种方法准确率并不高。 停止位用于数据帧间隔一个数据发送完后停止一小段时间。数据是一个一个发送的比如一个字节有8个位数据就是1位1位的发送先发低位再发高位接收也是一位一位的收
7串口模式图
下图所示。数据只有到达单片机的总线单片机才能接收处理这些数据。红框内的电路是用来控制波特率也就是说通信双方的速率主要是由定时器来约定的TH1和TL1是定时器寄存器通过溢出率经2分频16分频等来控制收发器的采样时间使用时配置T1定时器的TH1,TL1来控制收发速率
SBUF串口数据缓存寄存器物理上是两个独立的寄存器但占用相同的地址。写操作时写入的是发送寄存器读操作时读出的是接收寄存器。
发送时先将数据写入SBUF中再通过发送控制器的控制将数据发送出去接收时从RXD接收数据回来经过移位寄存器一位位的移到SBUF中然后需要数据时直接读缓存就好了。写程序时当SBUF在等号左边意味着给SBUF赋值也就是要发送的数据写入SBUF如果SBUF在等号右边意味着取SBUF的值也就是要读取SBUF存的数据。当收到一位数据时接收控制器就会产生一个叫RI的接收中断提示可以取数据了发送数据时数据发送完发送控制器会产生一个叫TI的中断提示发送数据完毕 加上去中断逻辑的电路图如下 单片机通过如下电路通过USB与电脑连接
2STC89C52的串行口寄存器 3单片机向电脑发送数据
1配置寄存器串口初始化
配置SCON
SCON是串行控制寄存器用于选择串行通信的工作方式和某些控制功能。PCON是波特率选择特殊功能寄存器
这里我们使用串口通讯的模式二下面是软件实现方式。在PCON中对SMOD和SMOD0的描述下图可以得知SCON中的SM0/FE有两个作用当SMOD0变化时这两个作用切换当SMOD00时处于SM0模式此时它和SM1搭配就可以配出四种模式。令SM00,SM11就可以使串口处于模式一。 接着配置SCON的其他寄存器。如下图SM2用来控制方式2和3的我们使用的是方式1所以此位置为0REN位串行接收控制位的开关当需要单片机接收数据时我们置为1TB8和RB8均为方式2和3的置为0TI和RI分别为发送和接收的中断请求标志位我们在模式图中看过注意当数据发送或接收完毕后它是由系统置为1需要我们用软件重新置为0。 所以总结来说SCON应用二进制表示为0100 0000.由于SCON可以位寻址所以可以直接用SM11等单独表示
配置PCON
波特率加倍可以减少误差可以参照下方配置定时器内容所以SMOD置为1.PCON配置为1000 0000.即PCON |0x80 配置SBUF
SBUF用于往里面写数据或者从里面取数据初始化阶段不需要配置
配置定时器
根据下图可以知道控制波特率需要先配置定时器图中是定时器T1。串口定时器需要使用8位自动重装模式原来的16位不自动重装模式并不精准而波特率变化是很快的所以我们需要更精准的8位自动重装模式 配置为模式8位自动重装则TMOD0010 0000.则写为TMOD 0x0F; (前四位清零)TMOD | 0x20;配置前四位为0010 利用STC-ISP配置定时器和波特率注意勾选波特率倍速勾选波特率倍速可以降低误差。这里的定时器只做溢出波特率发生器(只要有溢出即可)参照模式图而不进入中断所以这里会关闭中断
2发送数据函数编写
#include REGX.H
#include Dalay.h
void Uart1_Init(void) //4800bps12.000MHz
{PCON | 0x80; //使能波特率倍速位SMODSCON 0x40; //8位数据,可变波特率TMOD 0x0F; //设置定时器模式TMOD | 0x20; //设置定时器模式TL1 0xF3; //设置定时初始值TH1 0xF3; //设置定时重载值ET1 0; //禁止定时器中断TR1 1; //定时器1开始计时
}
void UART_SendByte(unsigned char Byte)
{SBUFByte;while(TI0);TI0;//软件复位标志位
}
void main(){Uart1_Init();UART_SendByte(0x66);while(1){//如果把UART_SendByte(0x66);放到while里面让单片机一直发数据//电脑接收的数据就会出现问题这是因为误差的原因(整数晶振频率存在误差)//小数晶振频率如11.0592MHz误差就可以达到0。对于整数晶振频率如本例所用的//12MHz就会存在误差所以可以在每次UART_SendByte(0x66);完后加一个delay(10)//还有一点就是可以降低波特率波特率越低数据越稳定}}
接收数据时一定要把波特率调为发送数据设备的波特率这里单片机发送数据为4800接收时波特率也要调味4800. 校验位一般设置为无校验停止位设为1位 文本模式HEX模式HEX模式是十六进制模式比如接收缓冲区为HEX模式时单片机发送0x23则接收缓冲区接收为23.如果接收缓冲区为文本模式则根据ASCⅡ表将0x23接收为C。当然单片机发送的数据也可以是字符‘A’等。 4电脑向单片机发送数据
电脑通过串口控制LED
单片机接收数据时需要使用中断函数因为不知道电脑何时会发数据所以利用中断系统每当发过来数据就接收它。
1配置寄存器
在单片机向电脑发送数据时说过SCON寄存器其中有一个REN当要接收数据时就要将这个REN置为1。和前面一样的模式SM00,SM11,其他位为0所以SCON0x50
配置中断EA1打开中断总开关ES1打开串口中断开关 配置完成后当中断来时跳转到中断函数 下面的UART_Rountine就是所需要的中断函数 2代码实现
以下代码可以实现电脑发送数据控制LED而且还可以将单片机接收的数据再发给电脑
#include REGX.H
#include Dalay.h
void Uart1_Init(void) //4800bps12.000MHz
{PCON | 0x80; //使能波特率倍速位SMODSCON 0x50; //8位数据,可变波特率TMOD 0x0F; //设置定时器模式TMOD | 0x20; //设置定时器模式TL1 0x64; //设置定时初始值TH1 0x64; //设置定时重载值ET1 0; //禁止定时器中断TR1 1; //定时器1开始计时EA1;ES1;
}
void UART_SendByte(unsigned char Byte)
{SBUFbyte;while(TI0);TI0;//软件复位标志位
}
void UART_Routine() interrupt 4
{ if(RI1)//因为接收发送都会触发中断这里保证是单片机接收数据中断{P2SBUF;UART_SendByte(SBUF);//注意要写在中断函数不能再写在主函数RI0; }}
void main(){Uart1_Init();UART_SendByte(0x66);while(1){}
}
九LED点阵屏
1点阵屏介绍
LED点阵屏由若干个独立的LED组成LED以矩阵的形式排列以灯的亮灭来显示图像。
LED点阵屏按颜色分为单色双色全彩(每个LED中由红绿蓝三个LED)。大规模LED点阵通常由很多个小点阵拼接而成。
2显示原理
LED点阵屏结构类似于数码管有共阴和共阳两种接法。LED点阵屏需要进行逐行或逐列扫描才能使所有LED同时显示 3单片机控制LED点阵屏原理
LED点阵屏有16个引脚接口如果全部连接在单片机上就会浪费单片机引脚资源所以使用74HC595来扩展引脚(如下图) 74HC595工作原理
74HC595是串行输入并行输出的移位寄存器可用三根线输入串行数据8根线输出并行数据多片级联后还可输出16为24位32位等常用于IO口扩展。
它的左端P35,P36,P34接单片机引脚也就是通过3个单片机引脚控制LED点阵屏8个引脚D0~D7。 OE是输出使能其上加了横线表示低电平有效也就是OE引脚接低电平这个芯片才有效。SRCLR叫做串行清零端会把数据进行清空其上加了横线表示低电平时才会清空原理图中SRCLR接VCC表示不清空。QH‘用于多片级联。SER表示串行数据(数据分为串行数据和并行数据串行就是数据根据时钟一个一个的发送并行就是同时给多个数据比如同时给D0~D7八个引脚数据)
SRCLK为串行时钟当时钟每来一个上升沿SER串行数据就输入一个数据到移位寄存器(如下图左为移位寄存器右为缓存器)。当RCLK来一个上升沿时就会把移位寄存器内的八个数据同时送到缓存器。比如要给QA~QH输出0000 0101由于单片机上电后默认IO口为高电平所以开始要给SERCLK初始化为低电平将RCLK也初始化为低电平SER串行口输入数据需要先填到QH的移位寄存器也就是先给SER写1当给SER写1时把SERCLK设置为高电平这时1就到了移位寄存器的第一个格再对ERCLK清零和SER清零下一个数据SER输入为0,再给SERCLK输入高电平0就到了刚才1的位置1往下移动一格。不断输入数据当输入8个数据后设置RCLK为高电平数据就会从移位寄存器搬到缓存器再进行输出。如果想要多片联结输出多位就再按相同的步骤给SER数据然后放到移位寄存器这时移位寄存器的数据溢出溢出的数据就会通过QH到下一个移位寄存器 理论来说如果将点阵屏的16个引脚全都接在单片机的IO口上是可行的但是单片机的IO口是弱上拉模式在驱动高频电路时它的高电平输出电流很低这会导致点阵屏很暗。如果在单片机IO口后接一个三极管开关再驱动点阵屏就可以实现如下电路图,如果给I/O口低电平三极管就会导通电压VCC就会直接去驱动引脚而不是I/O电压驱动引脚这里I/O相当于一个控制信号。 4应用1显示静态图像
先介绍几个概念
•可位寻址/不可位寻址在单片机系统中操作任意寄存器或者某一位的数据时必须给出其物理地址又因为一个寄存器里有8位所以位的数量是寄存器数量的8倍单片机无法对所有位进行编码故每8个寄存器中只有一个是可以位寻址的。对不可位寻址的寄存器若要只操作其中一位而不影响其它位时可用“”、“|”、“^”的方法进行位操作
•sfrspecial function register特殊功能寄存器声明。例sfr P0 0x80;声明P0口寄存器物理地址为0x80
•sbitspecial bit特殊位声明。例sbit P0_1 0x81; 或 sbit P0_1 P0^1;声明P0寄存器的第1位
sfr sbit用在头文件的声明中比如STC89C52的头文件REGX52.H 有了以上概念我们在操作74HC595时为了方便就可以把引脚P3_5重新定义为以RCLK为名的引脚如下方
#include REGX52.H
sbit RCKP3^5;//P3_5是P3系列引脚的第五位重新定义时直接用P3^5//就可以表示为P3_5的引脚地址0xB5,由于RCLK已经命名过了//所以命名为RCK
sbit SRCLKP3^6;
sbit SERP3^4;
#include REGX52.H
#include Delay.h
sbit RCKP3^5;//P3_5是P3系列引脚的第五位重新定义时直接用P3^5//就可以表示为P3_5的引脚地址0xB5,由于RCLK已经命名过了//所以命名为RCK
sbit SRCLKP3^6;
sbit SERP3^4;
int i0;
void _74HC595_WriteByte(unsigned char Byte){//此函数用于74HC595传输数据for(i0;i8;i){//移位8次SERByte(0x80i);//因为第一个数据传输的是最高位数据(先传最后一个寄存器数据)//所以用逻辑来提取最高位数据//SER只能一位一位传输这样一次传输8位可以的原因是如果要传输的数据//为0则SER送到寄存器里的为0如果要传输的数据不为0则SER//送到寄存器里的为1SRCLK1;//将最高位下移一位SRCLK0;//清零}SRCLK1;//将八位数据送到缓存寄存器RCK0;//清零
}
void MatrixLED_ShowColumn(unsigned char Column,Data)
{_74HC595_WriteByte(Data);P0~(0x80Column);Delay(1);P00xFF;//清零显示防止段选位选出现重影}
void main()
{ SRCLK0;//初始化SCKSRCLK0;//初始化while(1){MatrixLED_ShowColumn(0,0x3C);MatrixLED_ShowColumn(1,0x42);MatrixLED_ShowColumn(2,0xA9);MatrixLED_ShowColumn(3,0x85);MatrixLED_ShowColumn(4,0x85);MatrixLED_ShowColumn(5,0xA9);MatrixLED_ShowColumn(6,0x42);MatrixLED_ShowColumn(7,0x3C);}}
5应用2显示动画
#include REGX52.H
#include Delay.h
#include MatrixLED.h
//用资料中的软件快速提取数据
//动画数据动画数据如果很多的话RAM内存可能会不够而Flash的内存比RAM大得多
//所以在此变量前加一个code表示把它存在Flash中
//注意放在Flash中以后此数组数据不可以再更改
unsigned char code Animation[]{0x3C,0x42,0xA9,0x85,0x85,0xA9,0x42,0x3C,0x3C,0x42,0xA1,0x85,0x85,0xA1,0x42,0x3C,0x3C,0x42,0xA5,0x89,0x89,0xA5,0x42,0x3C,
};void main()
{unsigned char i,Offset0,Count0;SRCLK0;//初始化SCKSRCLK0;//初始化while(1){for(i0;i8;i) //循环8次显示8列数据{MatrixLED_ShowColumn(i,Animation[iOffset]);}Count; //计次延时if(Count15){Count0;Offset8; //偏移8切换下一帧画面if(Offset16){Offset0;}}}
}
十DS1302实时时钟
普通单片机的定时器计时精度不高而且会占用CPU的运行时间除此之外单片机时钟在断电后不会继续运行时钟会清零。
而DS1302时钟芯片带有备用电池断电时会自动切换到备用电池让他在单片机不上电时继续计时而且在单片机有电时会对备用电池充电。DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时且具有闰年补偿等多种功能
RTC实时时钟是一种集成电路通常称为时钟芯片DS1302DS3231就是其中一种。 学一个芯片最重要的就是会看手册
1引脚定义和应用电路
DIP表示直插封装SO表示贴片封装
共有8个引脚第一部分表示电源VCC1.VCC2,GNDVCC2是主电源VCC1是备用电池。X1,X2接晶振晶振频率为32.768Hz它通过内部电路处理会输出一个1Hz的标准频率为实时时钟系统提供一个稳定的脉冲。然后单片机通过CE,IO,SCLK三个引脚将时钟芯片的信息读出来或者把时间写进去。其数据输入输出方式和74HC595类似 引脚名 作用 引脚名 作用 VCC2 主电源 CE 芯片使能 VCC1 备用电池 IO 数据输入/输出 GND 电源地 SCLK 串行时钟 X1、X2 32.768KHz晶振 2内部电路结构
内部时间均存在寄存器RAM中。CE引脚作为芯片使能相当于一个中介开关输入移位寄存器的数据到命令控制逻辑后首先要经过一个CE开关才能到实时时钟中CE为高电平开关闭合低电平断开IO和SCLK就控制输入移位寄存器IO相当于74HC595的SER一样用于输入数据 3寄存器
与时钟有关的寄存器
共有81h~91h几个地址每个地址有一个寄存器一个寄存器有8位其中如下图第一个地址中的寄存器表示秒。第二个地址中的寄存器表示分以此类推。WP是write protect,是写保护当它置为1时 写入的操作无效置为0就可以写入最后一个寄存器是用来存储涓流充电的 命令字
命令字用来控制是读还是写秒是读秒还是读时等即对上表选择的控制。
一个寄存器总共八个字节最高位固定为1不用管。如果要操作RAM第6位就要给1如果给0就是操作CK即时钟。第5位到第1位就是我们要操作的地址。第0位是读写模式如果给0就是写给1就是读。这8位加起来就可以具体到时钟的地址。比如说要写入秒那就给A4 A3 A2 A1 A0都为0RD为0那么这个寄存器就是1000 0000换为16进制为0x80这就正好对应了秒的地址80h如果要读秒那就把RD置为1,则为1000 0001换为16进制为0x81正好对应了81h 4时序图
根据时序图时钟芯片工作时先有单片机发一个命令字再决定是读出还是写入读出写入到哪里。完成后SCLK,CE均置零。
从CE线可以看出要读写数据时CE要置为1写完之后清零。SCLK就是给一个固定的时钟IO就给数据当SCLK为高电平IO上的数据将会被写入在时钟的下降沿DS1302就会把他的数据输出也就是说在时钟上升沿我用单片机给始终写入数据在时钟下降沿时钟芯片向单片机写入数据。(当RW给1时即为读数据这时IO口由时钟芯片掌握SCLK每输出一个下降沿数据读出一个。当RW给0时即为写数据这时IO口由单片机掌握SCLK每输出一个上升沿数据写入一个。) 5代码实现过程
首先编写初始化函数将CE和SCLK均置零
再编写写入函数(函数无返回值参数为命令字和要输入数据共两个字节)参照下方写入的时序图首先使CE1。根据类似的74HC595的串行数据输入方式这里的IO就是SER给IO一个数据然后置SCLK为1来存入寄存器。这里需要输入两个字节的数据第一个字节的数据是命令字取出命令字(主函数调用写入函数时给定)的第一个数据置SCLK为1再置为0如此循环8次完成命令字的输入接下来数据的输入也类似循环8次完成数据的写入。
再编写读取函数读取DS1302的数据(函数返回值为DS1302的数据参数为命令字)命令字的输入过程同写入函数有一个不同点看下方读取时的时序图可以看到读取的一个过程SCLK有15个脉冲写入时SCLK有16个脉冲。这是因为读取时命令字的写入是来一个上升沿写入一次而读取数据时IO口控制权交给DS1302并且来一个下降沿读取一次这就使其中的一个脉冲的上升沿和下降沿均有数据输入和读取为了解决这个问题在写入命令字时在for循环内先令SCLK1在令其为0这样在命令字写入结束时就正好在上升沿的位置而不会经过下降沿。在读取数据的下降沿中先令SCLK1再令其为0即可。 //DS1302.c
#include REGX52.H
sbit DS1302_SCLKP3^6;
sbit DS1302_IOP3^4;
sbit DS1302_CEP3^5;
void DS1302_Init()
{DS1302_CE0;DS1302_SCLK0;
}
void DS1302_WriteByte(unsigned char Command,Data)
{ DS1302_CE1;int i0;//第一个for循环为命令字写入for(i0;i7;i){DS1302_IOCommand(0x01i);//取出命令字的第0位DS1302_SCLK1;DS1302_SCLK0;//立马置为0要考虑时间问题//可以去数据手册看一下SCK这个传输过程所需时间为多少//如果单片机处理速度快于SCK则需要加延时函数}//第二个循环为数据写入for(i0;i7;i){DS1302_IOData(0x01i);//取出命令字的第0位DS1302_SCLK1;DS1302_SCLK0;} DS1302_CE0;//CE清零}
unsigned char DS1302_ReadByte(unsigned char Command)
{DS1302_CE1;int i0;unsigned char Data0x00;for(i0;i7;i){DS1302_IOCommand(0x01i);//取出命令字的第0位DS1302_SCLK0;DS1302_SCLK1;//使时序相同而调换的顺序 }for(i0;i8;i){DS1302_SCLK1;DS1302_SCLK0;if(DS1302_IO){Data|(0x01i);}}DS1302_CE0;return Data;}
常见问题 1如果在主函数执行一个简单例子如下代码显示是可以正常显示的但如果把Num和LCD_ShowNum放到while中LCD屏就会出现数字显示错误并且不清的问题 。改进方法是对 unsigned char DS1302_ReadByte(unsigned char Command)函数做如下处理 unsigned char DS1302_ReadByte(unsigned char Command) { DS1302_CE1; int i0; unsigned char Data0x00; for(i0;i7;i){ DS1302_IOCommand(0x01i);//取出命令字的第0位 DS1302_SCLK0; DS1302_SCLK1;//使时序相同而调换的顺序 } for(i0;i8;i){ DS1302_SCLK1; DS1302_SCLK0; if(DS1302_IO){Data|(0x01i);} } DS1302_CE0; DS1302_IO0 return Data; } #include REGX52.H
#include Delay.h
#include DS1302.h
#include LCD1602.h
unsigned char Num0x00;
void main()
{ DS1302_Init();LCD_Init();LCD_ShowString(1,1,RTC);DS1302_WriteByte(0x80,0x30);NumDS1302_ReadByte(0x81);LCD_ShowNum(1,1,Num,3);while(1){//NumDS1302_ReadByte(0x81);//LCD_ShowNum(1,1,Num,3);}}
2如果读出时间为一个大于59并且不动的数则芯片可能处于 写保护状态在DS1302_WriteByte之前加上DS1302_Write(0x8E,0x00)即可解除芯片写保护
3解决完第1个问题后LCD显示正常但在计数时9会突然变到16。这是因为时钟芯片内的寄存器不是按照正常二进制进行存储的而是以BCD码进行存储的(BCD码是用4位二进制码来表示1位十进制数如0001 0011表示131000 0101表示851010表示10不合法在十六进制中的体现为0x13表示130x85表示850x1A不合法)。在BCD编码中0000 1001表示9时下一位0001 0000表示10而0001 0000在二进制中表示16所以会产生突变。可以看到BCD码在16进制中的显示是可以代表十进制数的因为BCD码的0001 0011就可以转换为0x1313就是要表示的数。所以第一种解决办法是以16进制显示BCD编码的数。第二种方法可以将BCD转换为十进制它的公式如下所以就可以写成LCD_ShowNum(1,1,Num/16*10Num%16,3); 上述的BCD编码形式在我们之前看的芯片寄存器上也有体现以秒为例前4位为Seconds后三位为10Seconds也就是说后三位表示十位数字CH表示时钟静止如果置1则秒停止计数。 6应用1时钟显示
年月日数据很多都在主函数里调用很麻烦。接下来对DS1302.c进行改进
//DS1302.c
#include REGX52.H
sbit DS1302_SCLKP3^6;
sbit DS1302_IOP3^4;
sbit DS1302_CEP3^5;
#define DS1302_SECOND 0X80;
#define DS1302_MINUTE 0X82;
#define DS1302_HOUR 0X84;
#define DS1302_DATE 0X86;
#define DS1302_MONTH 0X88;
#define DS1302_DAY 0X8A;
#define DS1302_YEAR 0X8C;
#define DS1302_WP 0X8E;
unsigned char DS1302_Time[]{24,6,21,16,24,55,6}//定义一个数组存放时间//24年6月21日16时24分55秒星期六
void DS1302_Init()
{DS1302_CE0;DS1302_SCLK0;
}
void DS1302_WriteByte(unsigned char Command,Data)
{ DS1302_CE1;int i0;//第一个for循环为命令字写入for(i0;i7;i){DS1302_IOCommand(0x01i);//取出命令字的第0位DS1302_SCLK1;DS1302_SCLK0;//立马置为0要考虑时间问题//可以去数据手册看一下SCK这个传输过程所需时间为多少//如果单片机处理速度快于SCK则需要加延时函数}//第二个循环为数据写入for(i0;i7;i){DS1302_IOData(0x01i);//取出命令字的第0位DS1302_SCLK1;DS1302_SCLK0;} DS1302_CE0;//CE清零}
unsigned char DS1302_ReadByte(unsigned char Command)
{DS1302_CE1;int i0;unsigned char Data0x00;for(i0;i7;i){DS1302_IOCommand(0x01i);//取出命令字的第0位DS1302_SCLK0;DS1302_SCLK1;//使时序相同而调换的顺序 }for(i0;i8;i){DS1302_SCLK1;DS1302_SCLK0;if(DS1302_IO){Data|(0x01i);}}DS1302_CE0;return Data;
}
void DS1302_SetTime()//调用此函数将上述定义的时间数组设置到时钟芯片中去
{ DS1302_WriteByte(0x8E,0x00);//关闭写保护DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16DS1302_Time[0]%10);//将其转换为BCD码放入DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16DS1302_Time[0]%10);DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16DS1302_Time[0]%10);DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16DS1302_Time[0]%10);DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16DS1302_Time[0]%10);DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16DS1302_Time[0]%10);DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16DS1302_Time[0]%10);DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16DS1302_Time[0]%10);}
void DS1302_ReadTime()//调取此函数将时钟芯片内的数据读取回来存到时间数组中
{ unsigned char Temp;TempDS1302_Time[0]DS1302_ReadByte(0x8D);DS1302_Time[0]Temp/16*10Temp16;TempDS1302_Time[1]DS1302_ReadByte(0x89);DS1302_Time[1]Temp/16*10Temp16;TempDS1302_Time[2]DS1302_ReadByte(0x87);DS1302_Time[2]Temp/16*10Temp16;TempDS1302_Time[3]DS1302_ReadByte(0x85);DS1302_Time[3]Temp/16*10Temp16;TempDS1302_Time[4]DS1302_ReadByte(0x83);DS1302_Time[4]Temp/16*10Temp16;TempDS1302_Time[5]DS1302_ReadByte(0x81);DS1302_Time[5]Temp/16*10Temp16;TempDS1302_Time[6]DS1302_ReadByte(0x8B);DS1302_Time[6]Temp/16*10Temp16;
} DS1302.h文件
#ifndef __DS1302_H__
#define __DS1302_H__
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime();
void DS1302_Settime();
#endif
main.c文件
#include REGX52.H
#include LCD1602.h
#include DS1302.h
extern unsigned char DS1302_Time[];
void main()
{LCD_Init();DS1302_Init();LCD_ShowString(1,1, - - );//静态字符初始化显示LCD_ShowString(2,1, : : );DS1302_SetTime();//设置时间while(1){DS1302_ReadTime();//读取时间LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒}
}
7应用2可调时钟
十一蜂鸣器
1蜂鸣器介绍
• 蜂鸣器是一种将电信号转换为声音信号的器件常用来产生设备的按键音、报警音等提示信号 • 蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器 • 有源蜂鸣器内部自带振荡源将正负极接上直流电压即可持续发声频率固定 • 无源蜂鸣器内部不带振荡源需要控制器提供振荡脉冲才可发声调整提供振荡脉冲的频率可发出不同频率的声音。如果给一个电压不变的直流不发声。注意无源蜂鸣器不能一直通电 2驱动电路
1三极管驱动
三极管驱动电路中的三极管作为开关 左边电路图中R1左端接单片机IO口如果IO口给高电压则导通蜂鸣器响如果给低电平三极管电路截至。所以单片机在控制蜂鸣器中起信号控制作用。R1是限流电阻保证三极管能导通就以
2集成电路驱动 由于单片机不能直接驱动元器件输出高电平不稳定所以就采用一个芯片来驱动有点类似于之前说过的双向数据缓冲器74HC245
ULN2003驱动芯片 达林顿管是一种三极管复合的晶体管可以增强放大能力 如下ULN2003由7对非门组成每个NPN达林管组成每个非门当左端接单片机后单片机给1则右边输出为0以此来驱动元器件。注意如果左端给0右端输出的1不具有驱动能力实际上相当于右端断路处于高阻态状态。 根据上述的电路原理图给P15一个高电平就可以控制蜂鸣器了
十二AT24C02(I2C总线)
1存储器内部简化模型
存储器内部实际上是电路的网状结构如下图横向线称为地址总线(用来选中所需的线)竖向线称为数据总线。存储原理比如在地址总线选中了第一行可以给他加一个高电平1其他地址总线都不接。然后把左上角第一个节点两条线连上然后从左到右连接第二个节点第三个节点其他节点不连接(各线的相交处是不连接的在要存储时才会连接)。前三个节点连上以后数据总线的前三根线就会变为1。所以在第一根线也就是第一个地址下就存了1110 0000以此来存储数据。实际上两条线交错处的节点不是简单的相连而是通过二极管相连防止每个节点之间的互相干扰。在最初的Mask ROM中如果这个节点什么都不接如左图即相当于无数据当需要将交错线接到一起时就连接一个二极管是固定的电路。而PROM中对头连接两个二极管只需要击穿其中一个二极管就可以通电所以PROM可以通过编程给二极管两端击穿电压达到编程存储的效果由于二极管击穿后就永久损坏了所以PROM只能编程一次。 由于地址总线一次只能选中一行所以常在地址总线之前加一个138译码器
2AT24C02介绍
1简介
• AT24C02 是一种可以实现掉电不丢失的存储器可用于保存单片机运行时想要永久保存的数据信息 • 存储介质 E2PROM • 通讯接口 I2C 总线 • 容量 256 字节 2引脚及应用电路
AT24C02共有8个引脚WP是写保护高电平时处于写保护状态。 根据电路图使用时给AT24C02接上电源A0~A2接到VCC或者GNDSCL和SDA接外界电路下面介绍I2C时会详细介绍
3I2C总线介绍
1简介
• I2C 总线 Inter IC BUS 是由 Philips 公司开发的一种通用数据总线 • 两根通信线 SCL Serial Clock 、 SDA Serial Data • 同步、半双工带数据应答半双工即用一根线发送接收带数据应答检测对方是否接收到数据 • 通用的 I2C 总线可以使各种设备的通信标准统一对于厂家来说使用成熟的方案可以缩短芯片设计周期、提高稳定性对于应用者来说使用通用的通信协议可以避免学习各种各样的自定义协议降低了学习和应用的难度 2I2C电路规范
• 所有 I2C 设备的 SCL 连在一起 SDA 连在一起。如下图所示SCL和SDA是两条总线可以挂接很多设备。与串口TX,RX交叉连接不同 • 设备的 SCL 和 SDA 均要配置成开漏输出模式。开漏输出模式见下方解释。 • SCL 和 SDA 各添加一个上拉电阻阻值一般为 4.7KΩ 左右如下图电阻R • 开漏输出和上拉电阻的共同作用实现了“线与”的功能此设计主要是为了解决多机通信互相干扰的问题。解决原理开漏输出输出高电平时相当于引脚断开这样CPU和一个设备通信时其他设备都断开就不会相互干扰如果所有设备都开漏CPU的电平就会变为0所以在外面接两个上拉电阻当所有设备都断开上拉电阻将其拉到高电平(CPU通信时只控制低电平每次低电平完后上拉电阻都会自动将CPU引脚置为1)。 每个设备都有一个I2C地址CPU根据时序发地址与地址匹配的设备与CPU进行通信 弱上拉模式 前面介绍过电路图如下 开漏输出模式开漏输出模式没有上拉电阻当开关闭合时I/O输出为低电平0开关断开时信号1引脚处于浮空状态此时相当于电路断路一点小的干扰就会使引脚电压变化电压不稳定 3I2C时序结构
起始条件(即要开始通信之前给各设备一个要开始的信号)SCL高电平期间SDA从高电平切换到低电平。把SCL最后拉低是为了和下一个时序衔接起来保证下一个时序不用从高再到低。 终止条件 发送一个字节发送一个字节SCL低电平期间主机将数据位依次放到SDA线上高位在前然后拉高SCL从机将在SCL高电平期间读取数据位所以SCL高电平期间SDA不允许有数据变化依次循环上述过程8次即可发送一个字节。如下图SDA画叉的地方是数据变化的时候数据传输时每次传输一位画叉的地方只是为了便于理解画了两条线实际上只有一条当要传输的第一位数据为1时则线从低电平升高到高电平或者从高电平继续保持高电平
所有线都是由主机控制这也是由主机发送的从机(AT24C02)会读取SCL,SDA数据会自动读取。 接受一个字节SCL低电平期间从机将数据位依次放到SDA线上高位在前然后拉高SCL主机将在SCL高电平期间读取数据位所以SCL高电平期间SDA不允许有数据变化依次循环上述过程8次即可接收一个字节主机在接收之前需要释放SDA释放后主机将控制权交给从机此时从机控制主线主机接收数据 发送应答在接收完一个字节之后主机在下一个时钟发送一位数据数据0表示应答数据1表示非应答
接收应答在发送完一个字节之后主机在下一个时钟接收一位数据判断从机是否应答数据0表示应答数据1表示非应答主机在接收之前需要释放SDA
可以作为数据发送的第九位 4I2C数据帧
发送一帧数据第一个是发送开始标志S在之后第一节数据一定要发送从机地址读写位(共八位前七位为地址位其中前四位为固定端AT24C02固定为1010其他芯片不一样后三位可配置对应AT24C03的A0,A1,A2引脚)然后没发一个字节都跟一个RA接收应答之后再发送数据字节发送完最后一节后应答完跟一个终止。 接收一帧数据 先发送再接收数据帧 5AT24C02数据帧
但是AT24C02实际应用起来并不会像上述写入一帧数据一样byte1byte2byte3一直发 而是用如下数据帧写入它的数据帧和上述的I2C数据帧类似但有所不同。
字节写 继承了发送一帧数据。字节写只写入一个字节页写和I2C写入一帧数据一样可以写入多节数据这里不做介绍。 随机读 继承了复合格式 此外还有多种数据帧形式详细可以见手册。
未完