各模块电路分析
电源 CH341模块
电路功能:主要为单片机提供电源UART串口调试和下载程序。TENTEN
整个开发板采用双向供电。一路使用DC直接输入5个电源接口V直流电压;一路使用;USB接口输入5V同时,直流电压USB接口是电路串口调试和下载程序的媒介。
CH341芯片的TXD、RXD单片机开发板分别收到15台单片机P30、P31输入输出接口;9号管脚无外部电源.1uF旁路电容(滤波)电容对地起作用;SDA管脚连接到三连排针,下载程序时需要用跳线帽连接(SDA不接地)。
S3作为整个单片机开发板的整体供电开关。
矩阵键盘模块
由于比赛中使用的芯片,这里有P37引脚被P44引脚代替,P36引脚被P42引脚代替。
AD转换模块PCF8591
单片机采用两个上拉电阻直接连接的模块IIC通讯,其中AIN二是连接光敏电阻;其中:P20和P21模拟IIC通讯.
AT24C02
单片机采用两个上拉电阻直接连接的模块IIC通讯,其中AIN二是连接光敏电阻;其中:P20和P21模拟IIC通讯.
DS1302时钟模块
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-zOAD1IZI-1658738325439)(assets/20220422_095912_image.png)]
采用SPI协议通讯,P17,P23,P13模拟SPI
超声波测距模块
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-MLb7fad2-1658738325440)(assets/20220422_100150_image.png)]
使用跳线帽时用跳线帽P10和N_A1连接;P11和N_B1连接;N_AI提供超声波所需的发射脉冲CX20106A从N_B输出与距离成正比的低电平信号,从而达到测距的目的。
单片机IO口拓展
想操作流水灯,数码管段选择,数码管位选择,继电器,蜂鸣器,ULN2003驱动 应结合138和744HC使用02。数字管位段的选择也可以操作8*8点阵.
LM324信号调节电路
NE555N方波产生电路
该板用于产生方波,NET_SIG为输出口;rb3电位器可以调节方波频率.
CT107D流水灯,蜂鸣器
关闭蜂鸣器
拿到板子后肯定会有一些疑问:为什么这个板子上的蜂鸣器总是响,甚至认为是板子的问题; 然而,我们必须养成理性分析问题的习惯。既然蜂鸣器会响,我们就应该分析电路并关闭它:
有源蜂鸣器的正极连接电源VCC,负极利用网络标号“N_BUZZ”连接到ULN2003(高耐压、大电流复合晶体管阵列)OUT引脚,那么ULN2003复合晶体管是什么?具体内部图如下:
通过内部连接图可以看出来,ULN2003内部由7组非门组成,因此当输入端IN输出端为高电平时OUT对于低电平,反之亦然。
在分析完ULN可获得2003芯片的连接和工作原理:N_BUZZ为高电平,蜂鸣器处于关闭状态。因此"OUT7"根据管脚的高电平ULN可以获得2003年的内部原理,IN7管脚为低电平,ULN2007管脚的“IN1~IN七依次连接M74HC573MR锁存器的“Q1-Q管脚,根据M74HC573MR锁定锁定的工作原理,当VCC以及GND供电无异常,1管脚和11管脚分别连接低电平和高电平,输出为当前芯片输入;但当11管脚为低电平时,M74HC573MR锁存器工作,输出信号为上一个锁定存储的输出电平状态。
也就是说,想操作OUT7.位置或复位P06中间连接了锁存器,打开锁存器的位置Y5C:
电路设计采用74HC138译码器以及74HC02和非门,如下图所示。
当Y5C为1时,WR=0,Y5=0;而WR连接跳线帽GND总是为0让Y5到0就够了。
这里简单列出74HC编写程序提供参考:
关闭蜂鸣器的方法有很多,比如: 1、对P两口进行总线操作,P2|=0xa0; P0&=0xbf;(蜂鸣器引脚复位操作) 或者P2|=0xa0; P0|=0x40;(蜂鸣器引脚的位置操作) 这样就可以开关蜂鸣器了。 2.138译码器(数码管、继电器、蜂鸣器、流水灯、点阵…),所以干脆操作138译码器这三个IO口写成函数。具体代码如下:
void switch_138(unsigned char dat){ switch(dat){ case 0:P27=0;P26=0;P25=0;break; case 1:P27=0;P26=0;P25=1;break; case 2:P27=0;P26=1;P25=0;break; case 3:P27=0;P26=1;P25=1;break; case 4:P27=1;P26=0;P25=0;break; case 5:P27=1;P26=0;P25=1;break; case 6:P27=1;P26=1;P25=0;break; case 7:P27=1;P26=1;P25=1;break; //default:break; } }
该函数在使用时直接使用switch_138(端口号4-7)P它的优点是增加代码的可读性。
流水灯
为了扩展设计师IO口可谓不择手段,51单片机的典型扩展IO口就是连接8255A,或者这种方法;P控制两口,P0口作为数据传输。 1、对LED操作正确P0口操作,用上面的操作switch_直接选择138(4)U这个锁定器。P0口操作,操作的具体内容要看题目内容。 2.流水灯的操作有时可以看作是一个固定的数组,显示的动画可以通过code 直接牺牲掉ROM换来计算速度;当然也可以用各种循环来判断移位;总之方法很多,没必要一一举例。
基础训练01:LED控制基础
在单片机综合训练平台上实现LED基本控制,首先让奇数LED点亮,然后让偶数LED点亮,然后一切LED闪烁3次,最好一切LED依次点亮,然后依次熄灭,循环往复。
核心:74HC138(三八译码器)HC573(锁定器)HC02(或非门)具体回顾原电路原理分析
参考源码:
#include "reg52.h" sbit HC138_A = P2^5; sbit HC138_B = P2^6; sbit HC138_C = P2^7; void Delay(unsigned int time) { while(time--); while(time--); } /*======================================================= *功能:通过HC138译码器控制HC573锁存器 *参数:n--HC138译码器低电平引脚 4:Y4输出低电平 5:Y5输出低电平 6:Y6输出低电平 7:Y7输出低电平 8:Y4~Y全部输出高电平 *返回值:无。 =======================================================*/ void Init74HC138(unsigned char n) { switch(n) { case 4: HC138_A = 0; HC138_B = 0; HC138_C = 1; break; case 5: HC138_A = 1; HC138_B = 0; HC138_C = 1; break;
case 6:
HC138_A = 0;
HC138_B = 1;
HC138_C = 1;
break;
case 7:
HC138_A = 1;
HC138_B = 1;
HC138_C = 1;
break;
case 8:
HC138_A = 0;
HC138_B = 0;
HC138_C = 0;
break;
}
}
void LEDRunning()
{
char i = 0;
P0 = 0xaa;
Delay(60000);
Delay(60000);
P0 = 0x55;
Delay(60000);
Delay(60000);
for(i = 0; i < 3; i++)
{
P0 = 0x00;
Delay(60000);
P0 = 0xff;
Delay(60000);
}
for(i = 0; i < 8; i++)
{
P0 <<= 1;
Delay(60000);
}
for(i = 0; i < 8; i++)
{
P0 <<= 1;
P0 |= 1;
Delay(60000);
}
}
void main()
{
Init74HC138(4);
while(1)
{
LEDRunning();
}
}
程序优化
HC138通道选择函数
编写HC138函数选择不同的通道优化程序代码,具体的函数如下:
void InitHC138(unsigned char n)
{
switch(n)
{
case 4:
P2 = (P2 & 0X1f) | 0X80;
case 5:
P2 = (P2 & 0X1f) | 0Xa0;
case 6:
P2 = (P2 & 0X1f) | 0Xc0;
case 7:
P2 = (P2 & 0X1f) | 0Xe0;
}
}
P0口输出函数
编写P0输出函数优化程序代码,具体的函数如下:
void OutputP0 (unsigned char channel,unsigned char dat)
{
InitHC138(channel);
P0 = dat;
}
共阳数码管的静态显示
数码管的基础知识
要把内容正确的显示在数码管上,首先要明确数码管的类型与段码结构。在CT107D单片机综合实训平台上使用的数码管是 。为什么要看这个型号呢?因为它能告诉你数码管的类型。倒数第2个字母是“ ”,说明这个数码管是 类型的,如果该字母为“ ”则为 类型。不同类型的数据管,其段码数组是截然不同的。
在明确数码管类型之后,就可以确定段码数组了,也就是显示内容所对应的值,例如,要在F3461BH上显示数值“7”,那么就要输出的数值为“0xf8”。怎么样才能得到这个段码数组呢?你可以从网上或其他参考资料上获取,也可以自己对于电路图或者测试段码得到。
在明确数码管类型之后,就可以确定段码数组了,也就是显示内容所对应的值,例如,要在F3461BH上显示数值“7”,那么就要输出的数值为“0xf8”。怎么样才能得到这个段码数组呢?你可以从网上或其他参考资料上获取,也可以自己对于电路图或者测试段码得到。
对于比赛的开发板采用的就是共阳数码管,具体数码管断码表参考如下:
数码管的电路连接
F3461BH是一个4位8段的数码管,其中a、b、c、d、e、f、g、dp引脚分别对应8个段码,该8个引脚通过74HC573锁存器与单片机的P0端口相连。另外有com1~com4四个公共控制脚,该应用为高电平则使能对应位的数码管。两个F3461BH一共有8个com控制引脚,也是通过74HC573锁存器与单片机的P0端口相连的。因此,在操控数码管显示的过程中也离不开74HC138译码器和74HC573锁存器,具体可以参考前面的绍译码器和锁存器的电路原理分析。
共阳数码管的段码表
unsigned char code numtab[18]=
{0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,
0xBF,0x7F};//共阳极数码管段码表
静态数码管的位置以及函内容控制函数
void ShowSMG_Bit(unsigned char dat, unsigned pos)//数码管位选控制以及内容控制
{
InitHC138(6); //数码管的位置
P0 = 0x01 << pos;
InitHC138(7); //数码管的内容
P0 = dat;
}
数码管显示函数
void SMG_Static()//数码管显示函数
{
unsigned char i,j;
for(i = 0; i < 8; i++)
{
for(j = 0; j < 10; j++)//0-9显示
{
ShowSMG_Bit(numtab[j],i);
delay_ms(500);
}
}
for(j = 0; j < 16; j++)//0-9以及A-F显示
{
InitHC138(6); //数码管的位置
P0 = 0xff;
InitHC138(7); //数码管的内容
P0 = numtab[j];
delay_ms(500);
}
}
基础训练02:数码管的静态显示
在单片机综合训练平台上,8个数码管分别单独依次显示0`9值,然后所有的数码管一起同时显示0-F的值,以此往复。
核心:1、数码管的段码和显示数值之间的关系;2、共阳数码管的基本控制方法(com端口与显示码之间的关系)
参考源码:
#include "reg52.h"
unsigned char code SMG_duanma[18]=
{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,
0xbf,0x7f};
void Delay(unsigned int t)
{
while(t--);
while(t--);
}
void InitHC138(unsigned char n)
{
switch(n)
{
case 4:
P2 = (P2 & 0x1f) | 0x80;
break;
case 5:
P2 = (P2 & 0x1f) | 0xa0;
break;
case 6:
P2 = (P2 & 0x1f) | 0xc0;
break;
case 7:
P2 = (P2 & 0x1f) | 0xe0;
break;
}
}
void ShowSMG_Bit(unsigned char dat, unsigned pos)
{
InitHC138(6); //数码管的位置
P0 = 0x01 << pos;
InitHC138(7); //数码管的内容
P0 = dat;
}
void SMG_Static()
{
unsigned char i,j;
for(i = 0; i < 8; i++)
{
for(j = 0; j < 10; j++)
{
ShowSMG_Bit(SMG_duanma[j],i);
Delay(60000);
}
}
for(j = 0; j < 16; j++)
{
InitHC138(6); //数码管的位置
P0 = 0xff;
InitHC138(7); //数码管的内容
P0 = SMG_duanma[j];
Delay(60000);
Delay(60000);
}
}
void main()
{
while(1)
{
SMG_Static();
}
}
共阳数码管的动态显示
数码管动态显示原理
动态显示实质上就是 。在轮流显示过程中,每位数码管点亮时间为1~2ms,由于人的 及发光二极管的 ,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。
HC573得通道选择函数
void SelectHC573(unsigned char channel)
{
switch(channel)
{
case 4:
P2 = (P2 & 0x1f) | 0x80;
break;
case 5:
P2 = (P2 & 0x1f) | 0xa0;
break;
case 6:
P2 = (P2 & 0x1f) | 0xc0;
break;
case 7:
P2 = (P2 & 0x1f) | 0xe0;
break;
}
}
数码管动态显示控制函数
void DisplaySMG_Bit(unsigned char value, unsigned char pos)
{
P0 = 0xff;
SelectHC573(6);
P0 = 0x01 << pos;
SelectHC573(7);
P0 = value;
}
数码管显示函数
void Display_Dynamic()
{
DisplaySMG_Bit(SMG_duanma[2],0);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[0],1);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[1],2);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[8],3);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[16],4);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[16],5);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[yu/10],6);
DelaySMG(500);
DisplaySMG_Bit(SMG_duanma[yu%10],7);
DelaySMG(500);
}
独立按键的基本操作
一般情况下,独立按键有两个引脚,其中一个通过上拉电阻接到单片机的I/O端口,另外一端接地。也就是说,平时按键没有动作的时候,输出的是高电平,如果有按下动作发生,则输出的是低电平。那么,我们在程序设计的时候,只要扫描跟按键引脚相连的I/O端口,如果发现有低电平产生,则判定该按键处于按下状态。有些时候,电路或者外围有电磁干扰,也会使单片机的I/O端口产生低电平,这种干扰信号会让单片机误认为是按键动作。所以,在扫描按键的时候应该做去抖动处理,把干扰信号过滤掉,从而获得准确的按键状态信号。
延时函数
按键抖动一般是5~10ms,一般按键软件消抖延迟10ms可以
msvoid delay_ms(unsigned char ms)
{
unsigned int i,j;
for(i=0;i<ms;i++)
{
for(j=0;j<333;j++);
}
}
独立按键扫描函数
按键S7按下L1点亮,松开熄灭;按键S6按下L2点亮,松开熄灭;
按键S5按下L3点亮,松开熄灭;按键S4按下L4点亮,松开熄灭.
void ScanKeys()
{
if(S7 == 0)
{
delay_ms(10);
if(S7 == 0)
{
L1 = 0;
while(S7 == 0);
L1 = 1;
}
}
if(S6 == 0)
{
delay_ms(10);
if(S6 == 0)
{
L2 = 0;
while(S6 == 0);
L2 = 1;
}
}
if(S5 == 0)
{
delay_ms(10);
if(S5 == 0)
{
L3 = 0;
while(S5 == 0);
L3 = 1;
}
}
if(S4 == 0)
{
delay_ms(10);
if(S4 == 0)
{
L4 = 0;
while(S4 == 0);
L4 = 1;
}
}
}
矩阵键盘的基本操作
与独立按键不同的是,按键的两个引脚都分别连接的单片机的I/O端口,一个作为行信号,另外一个作为列信号。我们以4X4的矩阵键盘为例,试着探讨其工作方式和扫描思路。
在上面的矩阵键盘中,要识别出黄色按键的按下状态,应该怎么做呢? 对与矩阵键盘,我们只能逐行扫描,然后读取列的状态信号。如果R3行输出低电平,那么黄色按键如果有按下动作的话,那读取C2列信号也应该为低电平,而该行上其他没有按下动作的按键的列信号则为高电平。因此,我们可以得到矩阵键盘的基本扫描步骤: <1> R1输出低电平,R2、R3、R4输出高电平,逐个读取判断列信号,如果都为高电平则R1行上没有按键按下。 <2> R2输出低电平,R1、R3、R4输出高电平,逐个读取判断列信号。 <3> R3输出低电平,R1、R2、R4输出高电平,发现C2列信号为低电平,那么可以判断得R3行的C2列的按键有按下动作。 <4> R4输出低电平,R1、R3、R4输出高电平,逐个读取判断列信号。 如此循环往复,扫描的按键的状态。 我们知道有按键按下动作,那么又怎么知道是哪一个按键按下呢?这时,我们最好定义一个键值全局变量,给矩阵行列上的每一个的按键编一个唯一的号码。当扫描的某一行某一列的按键动作后,把对应的编号复制给键值变量,这样我们判断这个键值,就知道是那个按键有触发动作了。
矩阵键盘的扫描函数
根据矩阵键盘的扫描原理,矩阵键盘从左到右从上到下,利用数码管依次显示0-F
按键显示函数
void ShowKeyNum(unsigned char value)
{
InitHC138(6);
P0 = 0x01;
InitHC138(7);
P0 = value;
}
按键扫描函数
void ScanKeys()
{
R1 = 0;
R2 = R3 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if(C1 == 0)
{
delay_ms(10);
if(C1 == 0)
{
while(C1 == 0);
key_num = 0;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C2 == 0)
{
delay_ms(10);
if(C2 == 0)
{
while(C2 == 0);
key_num = 1;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C3 == 0)
{
delay_ms(10);
if(C3 == 0)
{
while(C3 == 0);
key_num = 2;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C4 == 0)
{
delay_ms(10);
if(C4 == 0)
{
while(C4 == 0);
key_num = 3;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
R2 = 0;
R1 = R3 = R4 = 1;
C1 = C2 = C3 = C4 =1;
if(C1 == 0)
{
delay_ms(10);
if(C1 == 0)
{
while(C1 == 0);
key_num = 4;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C2 == 0)
{
delay_ms(10);
if(C2 == 0)
{
while(C2 == 0);
key_num = 5;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C3 == 0)
{
delay_ms(10);
if(C3 == 0)
{
while(C3 == 0);
key_num = 6;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C4 == 0)
{
delay_ms(10);
if(C4 == 0)
{
while(C4 == 0);
key_num = 7;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
R3 = 0;
R2 = R1 = R4 = 1;
C1 = C2 = C3 = C4 =1;
if(C1 == 0)
{
delay_ms(10);
if(C1 == 0)
{
while(C1 == 0);
key_num = 8;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C2 == 0)
{
delay_ms(10);
if(C2 == 0)
{
while(C2 == 0);
key_num = 9;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C3 == 0)
{
delay_ms(10);
if(C3 == 0)
{
while(C3 == 0);
key_num = 10;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C4 == 0)
{
delay_ms(10);
if(C4 == 0)
{
while(C4 == 0);
key_num = 11;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
R4 = 0;
R2 = R3 = R1 = 1;
C1 = C2 = C3 = C4 =1;
if(C1 == 0)
{
delay_ms(10);
if(C1 == 0)
{
while(C1 == 0);
key_num = 12;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C2 == 0)
{
delay_ms(10);
if(C2 == 0)
{
while(C2 == 0);
key_num = 13;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C3 == 0)
{
delay_ms(10);
if(C3 == 0)
{
while(C3 == 0);
key_num = 14;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
else if(C4 == 0)
{
delay_ms(10);
if(C4 == 0)
{
while(C4 == 0);
key_num = 15;
DisplayKeyNum(SMG_duanma[key_num]);
}
}
}
单片机中断系统
什么是中断?
你正在追电视剧《神雕侠侣》,正看得入迷的时候,电话响了,你暂停电视剧,去接电话,在接电话的过程中,门铃又响了,你暂时放下电话,去把门打开。如果追电视剧是在执行主程序,那么电话就是中断源,电话铃响了就是中断请求,暂停电视就是现场保护,接电话就是中断响应,门铃响了是更高一级的中断请求,去把门打开,那就是中断嵌套。开完门回来接着聊电话,那是中断返回,接完电话把电视剧暂停打开就是现场恢复。 内核与外设之间的主要交互方式有两种:轮询和中断。轮询的方式貌似公平,但实际工作效率很低,且不能及时响应紧急事件;中断系统使得内核具备了应对突发事件的能力。 中断有个特点,就是你不知道中断什么时候发生。因此,每个中断都需要有一个中断入口地址,也成为中断向量。这样,不管中断在什么时候发生,它都有一个确定的程序执行起始点。中断响应之后,执行的那段程序,我们称作中断服务函数,也就是这个函数专门是为该中断服务的。
关于中断系统
一般来说,51单片机有5个中断源(忽略定时/计数器2),分2个优先级,这个5个中断源按照自然优先级从高到低依次为:
外部中断0:INT0 定时/计数器0:TF0 外部中断1:INT1 定时/计数器1:TF1 串口中断:RI/TI
下面一图将充分说明51单片机的中断系统结构:
每个中断源都对应着一个固定的入口地址,也就是中断向量,它们依次是:
0 0x0003: INT0 1 0x000B: TF0 2 0x0013: INT1 3 0x001B: TF1 4 0x0023: RI/TI
也就是说,不管主程序执行到什么地方,只要外部中断1产生请求,内核要响应该中断,就会到0x0013这个地址去执行代码。如果你是在使用汇编语言进行程序开发的时候,你需要记住每个中断源对应的地址;如果你使用的是C语言,你只需要记住中断源的顺序就可以了,也就是最左边的 。
中断相关的寄+存器
中断相关的寄存器有4个,每个寄存器都是可以位寻址的,这该编程带来了方便。 其中2个为控制寄存器:IE寄存器与IP寄存器:
中断服务函数程序的编写
一般情况下,中断的处理函数有两个,其一为中断初始化函数,其二为中断服务函数。 初始化函数就是一个普通的函数,而中断服务函数却有特殊的格式要求: <1> 中断函数没有返回值,也不能带参数。 <2> 函数名后面要跟一个关键字interrupt,说明这是一个中断服务函数。 <3> 在关键字interrupt后面要跟上中断号,说明这个中断服务函数是为那个中断服务的。 中断服务函数的格式为:
void 函数名() interrupt 中断号
{ ----函数体---- }
我们要利用定时器0来进行间隔定时,中断程序架构我们C语言可以这样写:
/*=============初始化定时器0===========*/
void Init_Timer0()
{
}
/*=============定时器0中断服务函数===========*/
void Interrupt() interrupt 1
{
}
/*=============主函数===========*/
void main()
{
Iint_Timer0();
while(1);
}
定时器的基本原理与应用
什么是定时/计数器?
在没有钟表的时候,定时的方式通过有一注香的时间,或者一桶水的时间。前者烧香不断减少是减法,后者滴水不断增加是加法。 定时/计数器,是一种能够对内部时钟信号或外部输入信号进行计数,当计数值达到设定要求时,向CPU提出中断处理请求,从而实现定时或者计数功能的外设。定时/计数器的最基本工作原理是 。作为 时,计数信号的来源选择周期性的 ;用作 时,计数信号的来源选择非周期性的 。 。
51单片机的定时/计数器
51单片机有两个定时/计数器T0和T1,为16位加法计数器,由低8位TLx和高8位THx两个寄存器组成,最大计数值为65535个计数脉冲。 该加1计数器的计数脉冲来源有2个: <1> 系统时钟振荡器输出的12分频。 <2> T0或T1引脚输入的外部脉冲信号。 每接收到一个计数脉冲,计数器就会加1,当计数值累计至全为1时(8位255,13位8191,16位65535),再输入一个计数脉冲,计数器便会溢出回零,并且计数器的溢出是TCON寄存器的TF0或TF1位置1,同时向内核提出中断请求。如果定时/计数器工作于定时模式,则表示间隔定时时间到,如果工作与计数模式,则表示计数值已满。 假设单片机的外部晶振为12MHz,那么,经过12分频后输入计数器的计数脉冲为1MHz,即每个脉冲的周期为1us。因此定时器T0的16位工作模式最大的定时时间为65535us,65.5ms。如果要定时10ms的话,计数器就不能够从0开始计数了,必须给它一个计数初值。怎么计算这个初值呢? 要定时10ms,则相当于计数10000个脉冲后计数器的值就到达65535了,那么开始计数的这个地方就是计数初值。 65535 - 10000 = 55535 = 0xd8ef 把这个计算得到的初值写入TH0和TL0寄存器即可: TH0 = 0xd8;或者 TH0 = (65535 - 10000) / 256; TL0 = 0xef; 或者 TL0 = (65535 - 10000) % 256;
定时/计数器相关的寄存器
与定时/计数器相关的寄存器除了计数初值寄存器THx和TLx之外,就是TMOD寄存器和TCON寄存器,务必掌握。 <1> TMOD模式控制寄存器,不能进行位寻址,只能字节操作。
<2> TCON中断标志寄存器
定时/计数器的编程思路
在定时/计数器的程序设计中,通常有两个函数:初始化函数和中断服务函数。 在初始化函数中,一般需要进行以下几个配置: <1> 配置工作模式,即对TMOD寄存器编程。 <2> 计算技术初值,即对THx和TLx寄存器进行赋值。 <3> 使能定时/计数器中断,即ET0或ET1置1。 <4> 打开总中断,即EA =1。 <5> 启动定时器,即TR0或TR1置1。 在中断服务函数中,一般需要进行以下的编程: <1> 如果不是自动重装模式,需要对THx和TLx重新赋值。 <2> 进行间隔定时到达的逻辑处理(越少越好)。
/**********初始化定时器0***********/
void InitTimer0()
{
TMOD = 0x01;
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256;
ET0 = 1;
EA = 1;
TR0 = 1;
}
/**********定时器0中断服务函数***********/
void ServiceTimer0() interrupt 1
{
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256;
count++;
if(count % 10 == 0)
{
L1 = ~L1;
}
if(count == 100)
{
L8 = ~L8;
count = 0;
}
}
串行接口的基本原理与应用
串行接口是一个非常重要的外设,它是单片机与外部终端的数据传输渠道。不管是简单的51单片机,还是复杂的ARM处理器,串口通信都是必不可少的,有些芯片甚至有几个串行接口。就蓝桥杯的“单片机设计与开发”赛项来说,串行通信考查的几率也是相当大的。
串口重要概念概述
<1> 串行通信是指数据一位接一位地顺序发送或接收。 <2> 串行通信有SPI、IIC、UART等多种,最常见最通用的是指UART,无特殊说明,本文指的就是UART。 <3> 串行通信的制式有:单工、半双工、全双工三种。 <4> 计算机的串行通信接口是RS-232的标准接口,而单片机的UART接口则是TTL电平,两者的电气规范不一致,所以要完成两者之间的数据通信,就需要借助接口芯片在两者之间进行电平转换,常用的有MAX232芯片。 <5> 波特率:每秒钟传输的位数,9600波特率就是指每秒钟传输9600位。 注意:在51单片机中需要使用定时器1来产生波特率,因此,如果使用串口通信,则定时器1就不能做其他用途,在初始化串行接口模块的时候,除了要配置SCON寄存器之外,还有根据波特率参数设置定时器1的技术初值。
51单片机的串口相关寄存器
对于传统的51单片机,与串口相关的寄存器有: TH1和TL1:设置波特率参数。 TMOD:设置定时器1的工作模式。 SBUF:串行通信数据的发送和接收缓冲器。 SCON:串行接口控制寄存器。 在这里主要是掌握SCON,跟串口有关的各种属性都在这个寄存器里进行配置:
如果你在比赛或者应用中使用的是 单片机,你还需要对新增的 进行设置,否则是无法进行串口数据收发的,对于传统的89C52单片机,则不需要这个步骤。
新增的辅助寄存器AUXR的位定义如下:
串口通信的编程思路
在串口通信的程序设计中,主要有串口初始化和数据收发两个部分。 在初始化函数中,基本步骤如下: <1> 设置定时器1的工作模式,也就是对TMOD寄存器赋值。 <2> 计算波特率参数,并赋值给TH1和TL1寄存器。 <3> 打开定时器1。 如果使用的是STC 12系统单片机,则要设置AUXR寄存器。 <4> 设置SCON寄存器。 <5> 使能串口中断ES。 <6> 使能总中断EA。 数据的发送通常采用查询的方式,而数据的接收则采用中断。 实际上,各个应用程序中,这些代码都差不多,可以参考一下的框架: