电路功能:主要给单片机提供电源和UART串口调试和下载程序。TENTEN
这里整块开发板采用了双路供电。一路采用DC电源接口直接输入5V直流电压;另一路采用USB接口输入5V直流电压,同时USB接口担当着电路的串口调试以及下载程序的媒介。
CH341芯片的TXD、RXD分别接到单片机开发板的15单片机的P30、P31输入输出接口;9号管脚没有外接电源通过0.1uF电容对地起到旁路电容(滤波);SDA管脚连接到三连排针,在程序下载时需要用跳线帽连接(SDA不接地)。
S3作为整个单片机开发板的整体供电开关。
由于大赛使用的芯片的原因,这里的P37引脚被P44引脚代替,P36引脚被P42引脚代替。
单片机使用两个上拉电阻直连的模块,采用IIC通讯,其中AIN2是连接的光敏电阻;其中:P20和P21模拟IIC通讯.
单片机使用两个上拉电阻直连的模块,采用IIC通讯,其中AIN2是连接的光敏电阻;其中:P20和P21模拟IIC通讯.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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_B1输出与距离的2倍成正比的低电平信号,从而达到测距的目的。
想要操作流水灯、数码管段选、数码管位选、继电器、蜂鸣器、ULN2003驱动 都应该结合138和74HC02的使用。其中数码管位段选还可以操作8*8点阵.
在此板子上用于产生方波,NET_SIG为输出口;rb3电位器可以调整方波发生频率.
拿到板子之后肯定会有所疑问:为什么这个板子上的蜂鸣器总是会响呢,更有甚者会以为是板子的问题;
但是我们不得不养成遇到问题理性分析的习惯,既然这个蜂鸣器会响,就要分析电路关闭它:
有源蜂鸣器的正极接通了电源VCC,负极利用网络标号“N_BUZZ”连接到ULN2003(高耐压、大电流复合晶体管阵列)“OUT7”引脚,那么ULN2003具体是什么复合晶体管呢?具体内部图如下:
通过内部连接图可以看出来,ULN2003内部由7组非门组成,因此当输入端“IN”为高电平时,输出端“OUT”为低电平,反之同理。
在分析完ULN2003芯片的连接以及工作原理,可以得到:N_BUZZ为高电平,蜂鸣器处于关闭状态。因此"OUT7"管脚为高电平,根据ULN2003内部原理可得,“IN7”管脚为低电平,ULN2007管脚的“IN1~IN7”依次连接了M74HC573MR锁存器的“Q1-Q7”管脚,根据M74HC573MR锁存器锁存的工作原理,当VCC以及GND供电无异常,1管脚以及11号管脚分别接低电平以及高电平,其输出为当前芯片输入;但当11号管脚为低电平时,M74HC573MR锁存器工作,输出信号为上一个锁定存储的输出电平状态。
也就是说想要操作OUT7的话,要置位或者复位P06,而中间连接了一个锁存器,开启锁存器要置位Y5C:
电路设计时采用了74HC138译码器以及74HC02与非门,具体如下图所示。
当Y5C为1时,WR=0,Y5=0;而WR通过跳线帽连接了GND始终为0所以让Y5为0就可以了。
这里简单列出74HC138译码器的真值表来编写程序提供参考:
关闭蜂鸣器的方法还是比较多的,比如说:
1、对P2口进行总线操作,P2|=0xa0; P0&=0xbf;(复位操作蜂鸣器的引脚)
或者P2|=0xa0; P0|=0x40;(置位位操作蜂鸣器的引脚)
这样就可以开关蜂鸣器了
2、由于频繁使用138译码器(数码管,继电器,蜂鸣器,流水灯,点阵…),所以干脆把操作138译码器这3个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),然后P0相应操作就行了,它的好处就是增加代码的可读性。
设计者为了扩展IO口可谓是“不择手段”,而51单片机典型的扩展IO口就是连接8255A,或者就是这个方法;P2口用作控制,P0口做为数据传输。
1、对LED的操作就是对P0口的操作,用上边的switch_138(4),直接选通U6这个锁存器。P0口进行操作,至于操作的具体内容就要看题目内容了。
2、流水灯的操作有时就是可以看成一个固定的数组,显示的“动画”可以通过code 直接牺牲掉ROM来换来计算的速度;当然用各种循环判断移位也是可以的了;总之方法非常的多,没有必要一一举例。
在单片机综合训练平台上实现LED的基本控制,首先让奇数LED点亮,再让偶数LED点亮,接着所有LED闪烁3下,最好所有LED依次点亮,再依次熄灭,如此循环往复。
核心:74HC138(三八译码器)、74HC573(锁存器)、74HC02(或非门),具体回看原先的电路原理分析
参考源码:
#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~Y7全部输出高电平
*返回值:无。
=======================================================*/
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函数选择不同的通道优化程序代码,具体的函数如下:
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输出函数优化程序代码,具体的函数如下:
void OutputP0 (unsigned char channel,unsigned char dat)
{
InitHC138(channel);
P0 = dat;
}
要把内容正确的显示在数码管上,首先要明确数码管的类型与段码结构。在CT107D单片机综合实训平台上使用的数码管是F3461BH 。为什么要看这个型号呢?因为它能告诉你数码管的类型。倒数第2个字母是“B ”,说明这个数码管是共阳 类型的,如果该字母为“A ”则为共阴 类型。不同类型的数据管,其段码数组是截然不同的。
在明确数码管类型之后,就可以确定段码数组了,也就是显示内容所对应的值,例如,要在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);
}
}
在单片机综合训练平台上,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端口,而且功耗更低。
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
按键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单片机有两个定时/计数器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单片机,与串口相关的寄存器有:
TH1和TL1:设置波特率参数。
TMOD:设置定时器1的工作模式。
SBUF:串行通信数据的发送和接收缓冲器。
SCON:串行接口控制寄存器。
在这里主要是掌握SCON,跟串口有关的各种属性都在这个寄存器里进行配置:
如果你在比赛或者应用中使用的是STC15F2K602 单片机,你还需要对新增的辅助寄存器AUXR 进行设置,否则是无法进行串口数据收发的,对于传统的89C52单片机,则不需要这个步骤。
新增的辅助寄存器AUXR的位定义如下:
在串口通信的程序设计中,主要有串口初始化和数据收发两个部分。
在初始化函数中,基本步骤如下:
<1> 设置定时器1的工作模式,也就是对TMOD寄存器赋值。
<2> 计算波特率参数,并赋值给TH1和TL1寄存器。
<3> 打开定时器1。
如果使用的是STC 12系统单片机,则要设置AUXR寄存器。
<4> 设置SCON寄存器。
<5> 使能串口中断ES。
<6> 使能总中断EA。
数据的发送通常采用查询的方式,而数据的接收则采用中断。
实际上,各个应用程序中,这些代码都差不多,可以参考一下的框架: