0x00 前言
6月份实验室考核,考核的内容我也不知道(估计是 51,c,加点电分,或者数据结构?不知道),虽然是一些入门的东西,不过为了万无一失(谁也不敢说会不会出一些犄角旮旯的东西),还是从头捋一遍。(菜鸡瑟瑟发抖),先51看一下吧。我就拿
普中-51-A3的举例了
本文全当博主的复习,主要是结合 小蜜蜂51和普中51
0x01 最小系统板:
1.电源电路 2.复位电路 3.时钟电路 4.下载电路
MCU
4个广角,P0 P1 P2 P3 对应就行。
复位
RST 网络标号 跟上述单片机的9脚相连,
1)当按键按下后,电源会经过按键,接入RST引脚进行复位(51单片机是进行高电频复位)
2)不按的化,通过R与电容组成的RC充放电电路,进行复位
晶振电路
可以看出来是采用12兆晶振,两边c12 c13电容,接在单片机的18 19 脚。
电源电路 && 下载电路
通过下面的usb接口,接入电源,经过滤波电容进行滤波(图中红框处),紧接这连接D5V的开关,送到VCC,之后各部分才有电,当没有按下开关时,这个DIN5VS(5伏)只是和 CH340 这个芯片来识别串口,并没有供电
P5短接板
CH340用的也是12兆晶振
下载时就利用RTS 和DTR高低电频的变化,进行下载复位(STM就需要人为了)
注意:CH340的位置一定要接地,不然会发烫。
0x02 LED
1.定义:LED,即发光二极管,是一种半导体固体发光器件。(写上吧,万一考呢)
2.LED的工作原理。 LED的符号为: LED的工作是有方向性的,只有当正级接到LED阳极,负极接到LED的阴极的时候才能工作,如果反接LED是不能正常工作的。
3.原理图
正入负出,所以阳极都接到了VCC(高电平)上,阴极串联一个电阻接到了P2口,所以点亮的话把对应的P2口拉低就行。
流水:
#include <REGX51.H>
#include <intrins.h>
typedef unsigned char u8;
typedef unsigned int u16;
void delay(u16 i)
{
while(i--);//ͨ¹ýËÀÑ»·Õ¼ÓÃcpuÔËËãʱ¼äÀ´ÑÓʱ
}
void LED_WATER()
{
int i;
for (i = 0 ; i < 7; i++)
{
P2 = ~(0x01 << i);
delay(25000);
}
for (i = 0 ; i < 7; i++)
{
P2 = ~(0x80 >> i);
delay(25000);
}
}
void main()
{
P2 = ~(0x01);
delay(25000);
while(1)
{
LED_WATER();
}
}
(下面的代码只写关键部分)
0x03 蜂鸣器
有电路板的是无源蜂鸣器,没有电路板的是有缘蜂鸣器(用黑胶封闭)
有源:在IO里输出一个电平就行 无源:电平 + 一定频率脉冲
电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。 接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。 接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。 多谐振荡器由晶体管或集成电路构成,当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。
声音
改变单片机引脚输出波形的频率,就可以调整控制蜂鸣器音调,产生各种不同音色、音调的声音。
大小
改变输出电平的高低电平占空比,则可以控制蜂鸣器的声音大小。、
原理图
可以看到蜂鸣器BEEP接到了ULN2003D上,对应的是P2^5
ULN2003
放大电路,放大驱动电流
(下面是套话)
ULN2003 是高耐压、大电流达林顿陈列,由七个硅NPN 达林顿管组成。 ULN2003是大电流驱动阵列,多用于单片机、智能仪表、PLC、数字量输出卡等控制电路中。可直接驱动蜂鸣器、继电器等负载 。
代码
sbit bee = P2 ^ 5; //因为是无源,所以要 脉冲+电平
void main()
{
bee = ~bee; //产生脉冲
delay(10);
}
0x04 数码管
(因为是复习,就不分静态和动态了)
显示器&&接口
LED显示器有两种:段显示(7段,米字型等)和点阵显示
LED数码管根据LED的不同接法可分为2类:共阴和共阳。
(A3里没有单个的,这是别人的图)
共阴就是阴极接地,所以要是亮就得输入高电平
共阳就是阳极接vcc,所以要是亮就得输入低电平
共阳极数码管编码表:
unsigned char table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
共阴极数码管编码表:
unsigned char table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
原理图:
引脚对应着 P0,看一眼P0口:
共阳,低电平有效
注意:P0口是露底开路的,如果不加上拉电阻(VCC)的话,对P0无法进行输入输出的控制。
使能
锁存器:74HC245 (一般是74HC573,这个普中A3的板子有点神奇)
简单地说锁存器就是一个缓存器,它能保存上一次的状态,自带使能端口,清零端口。当使能端有效时,则左右两端导通,否则断开。
74HC245:
7HC573 :通常配合或非门(74HC02)使用
138译码器
74HC138 简单地说138译码器将一个特殊的编码翻译成一个状态。
代码:(因为普中这个板子,138译码器,接的是P2^2,P2^3,P2^4,有些麻烦,为了直观代码如下)
sbit lsa=P2^2;
sbit lsb=P2^3;
sbit lsc=P2^4;
void wei_poc(u8 poc)
{
switch(poc)
{
case 0:
lsa=1;lsb=1;lsc=1;break;
case 1:
lsa=0;lsb=1;lsc=1;break;
case 2:
lsa=1;lsb=0;lsc=1;break;
case 3:
lsa=0;lsb=0;lsc=1;break;
case 4:
lsa=1;lsb=1;lsc=0;break;
case 5:
lsa=0;lsb=1;lsc=0;break;
case 6:
lsa=1;lsb=0;lsc=0;break;
case 7:
lsa=0;lsb=0;lsc=0;break;
}
}
在蓝桥杯里有方便的写法。
动态数码管
动态显示的特点是将所有数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。动态显示的亮度比静态显示要差一些,所以在选择限流电阻时应略小于静态显示电路中的。
说白了就是利用视觉残留,让多个静态数码管循环亮起。
代码
u8 code DUANXUAN[]={ //标准字库
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8.
void SMC_Bit(u8 dat, u8 poc)
{
P0 = ~DUANXUAN[dat];//段
wei_poc(poc);//位
}
void SMC_Display()
{
SMC_Bit(1,0);
delay(500);
SMC_Bit(0,0);//消影
SMC_Bit(2,1);
delay(500);
SMC_Bit(0,1);//消影
SMC_Bit(3,2);
delay(500);
SMC_Bit(0,2);//消影
SMC_Bit(4,3);
delay(500);
SMC_Bit(0,3);//消影
SMC_Bit(5,4);
delay(500);
SMC_Bit(0,4);//消影
SMC_Bit(6,5);
delay(500);
SMC_Bit(0,5);//消影
SMC_Bit(7,6);
delay(500);
SMC_Bit(0,6);//消影
SMC_Bit(8,7);
delay(500);
SMC_Bit(0,7);//消影
}
(代码要结合前文,注意一下自己的是共阴还是共阳)
0x05 独立按键
按键的两个引脚,一端通过电阻上拉到高电平,另一端接地。
没有按键按下时,输入引脚为高电平。
有按键按下时,输入引脚为低电平。
扫描:通过反复读取按键输入引脚的信号,然后识别高低电平来判断是否有按键触发。
去抖动
(别人的图)
首次检测到输入引脚有低电平后,稍做延时,再次读取该引脚,如果确实是低电平,则触发,否则就是干扰信号。
原理图:
代码:
-------------------------------------
按键按下,L1灯亮;
按键松开,L1灯灭;
-------------------------------------
sbit K1 P3^1;
sbit L1 P2^0;
void ScanKeys_Alone()
{
if (K1 == 0)
{
delay(100);
if(K1 == 0)
{
L1 = 0; //按键按下,L1灯亮
while(K1 == 0); //分界线
L1 = 1; //按键松开,L1灯灭
}
}
}
要注意,按下和松开的位置。
0x06 矩阵按键
小蜜蜂老师这篇写得很仔细:https://www.cnblogs.com/ALittleBee/p/8411771.html
下面是我摘抄的一些:
与独立按键不同的是,按键的两个引脚都分别连接的单片机的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输出高电平,逐个读取判断列信号。
如此循环往复,扫描的按键的状态。
我们知道有按键按下动作,那么又怎么知道是哪一个按键按下呢?这时,我们最好定义一个键值全局变量,给矩阵行列上的每一个的按键编一个唯一的号码。当扫描的某一行某一列的按键动作后,把对应的编号复制给键值变量,这样我们判断这个键值,就知道是那个按键有触发动作了。
代码:
原理图:
sbit R1 = P1^7;
sbit R2 = P1^6;
sbit R3 = P1^5;
sbit R4 = P1^4;
sbit C1 = P1^3;
sbit C2 = P1^2;
sbit C3 = P1^1;
sbit C4 = P1^0;
void ScanKeys()
{
keyNum = 16;//黑屏
R1 = 0;
R2 = R3 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if(C1 == 0)
{
while(C1 == 0);
keyNum = 0;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C2 == 0)
{
while(C2 == 0);
keyNum = 1;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C3 == 0)
{
while(C3 == 0);
keyNum = 2;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C4 == 0)
{
while(C4 == 0);
keyNum = 3;
ShowKeyNum(SMG_NoDot[keyNum]);
}
R2 = 0;
R1 = R3 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if(C1 == 0)
{
while(C1 == 0);
keyNum = 4;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C2 == 0)
{
while(C2 == 0);
keyNum = 5;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C3 == 0)
{
while(C3 == 0);
keyNum = 6;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C4 == 0)
{
while(C4 == 0);
keyNum = 7;
ShowKeyNum(SMG_NoDot[keyNum]);
}
R3 = 0;
R2 = R1 = R4 = 1;
C1 = C2 = C3 = C4 = 1;
if(C1 == 0)
{
while(C1 == 0);
keyNum = 8;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C2 == 0)
{
while(C2 == 0);
keyNum = 9;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C3 == 0)
{
while(C3 == 0);
keyNum = 10;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C4 == 0)
{
while(C4 == 0);
keyNum = 11;
ShowKeyNum(SMG_NoDot[keyNum]);
}
R4 = 0;
R2 = R3 = R1 = 1;
C1 = C2 = C3 = C4 = 1;
if(C1 == 0)
{
while(C1 == 0);
keyNum = 12;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C2 == 0)
{
while(C2 == 0);
keyNum = 13;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C3 == 0)
{
while(C3 == 0);
keyNum = 14;
ShowKeyNum(SMG_NoDot[keyNum]);
}
else if(C4 == 0)
{
while(C4 == 0);
keyNum = 15;
ShowKeyNum(SMG_NoDot[keyNum]);
}
}
按键扫描有很多种写法,这是一种好理解,引脚比较灵活,但是笨笨的写法。
下面这种,需要引脚比较规则时比较方便:
void KeyDown()
{
io_Key=0x0f; //行
if(io_Key !=0x0f) //判断是否按下
{
delay(1000); //去抖动
if(io_Key !=0x0f)
{
io_Key =0x0f; //行
switch(io_Key)
{
case 0x07:
KeyValue=0;break;
case 0x0b:
KeyValue=1;break;
case 0x0d:
KeyValue=2;break;
case 0x0e:
KeyValue=3;break;
}
io_Key =0xf0; //列
switch(io_Key)
{
case 0x70:
KeyValue=KeyValue;break;
case 0xb0:
KeyValue=KeyValue+4;break;
case 0xd0:
KeyValue=KeyValue+8;break;
case 0xe0:
KeyValue=KeyValue+12;break;
}
while((a<50) && io_Key!=0xf0)
{
delay(1000);
a++;
}
}
}
}
0x07 中断系统
参考文章https://bbs.21ic.com/icview-2477100-1-1.html(小蜜蜂老师赛高)
在执行cpu当前程序时,由于系统中出现了某种急需处理的情况,cpu暂停正在执行的程序,转而去执行另外一段特殊的程序来处理出现的紧急事务,处理结束后,CPU自动返回到原来暂停的程序中去继续执行。这种程序在执行过程中由于外界的原因而被中间打断的情况,成为中断。
图解:
引起CPU中断的根源,称为中断源。中断源向CPU提出的中断请求。CPU暂时中断原来的事务A,转去处理事件B。对事件B处理完毕后,再回到原来被中断的地方(即断点),称为中断返回。实现上述中断功能的部件称为中断系统。
中断服务函数:内核响应中断后执行的相应处理程序。
中短向量:中断服务函数的入口地址。每个中断源都对应一个固定的入口地址。当内核响应中断请求时,就会暂停当前的程序执行,然后跳转到该入口地址执行代码。
注意:有中断请求不一定能打断主程序。
随着计算机技术的应用,人们发现中断技术不仅解决了快速主机与慢速I/O设备的数据传送问题,而且还具有如下优点: 分时操作。CPU可以分时为多个I/O设备服务,提高了计算机的利用率;
实时响应。CPU能够及时处理应用系统的随机事件,系统的实时性大大增强;
可靠性高。CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性提高。
(以上看一眼了解一下,万一考呢,下面说正事)
中断系统
一般来说,51单片机有5个中断源(忽略定时/计数器2),分2个优先级,这个5个中断源按照自然优先级从高到低依次为:
外部中断0:INT0
定时/计数器0:TF0
外部中断1:INT1
定时/计数器1:TF1
串口中断:RI/TI
(图一)
每个中断源都对应着一个固定的入口地址,也就是中断向量,它们依次是:
0 0x0003: INT0
1 0x000B: TF0
2 0x0013: INT1
3 0x001B: TF1
4 0x0023: RI/TI
也就是说,不管主程序执行到什么地方,只要外部中断1产生请求,内核要响应该中断,就会到0x0013这个地址去执行代码。如果你是在使用汇编语言进行程序开发的时候,你需要记住每个中断源对应的地址;如果你使用的是C语言,你只需要记住中断源的顺序就可以了,也就是最左边的中断号。
寄存器
中断相关的寄存器有4个,每个寄存器都是可以位寻址的,这该编程带来了方便。 其中2个为控制寄存器:IE寄存器与IP寄存器:
另外2个为中断请求标志:TCON寄存器与SCON寄存器:
代码
/////////////////////////////==中断0==///////////////
void Init_INT0()//触发方式
{
IT0 = 1;//下降沿触发
EX0 = 1;//中断使能打开
EA = 1;//总中断打开
}
void ServiceINT0() interrupt 0
{
......//终端服务函数的编写
}
///////////////////////////==中断0_end==////////////
(Init_INT0()函数结合 图一 去看)
0x08 定时/计数器
参考文章:https://bbs.21ic.com/icview-2477676-1-1.html
作为定时器时,计数信号的来源选择周期性的内部时钟脉冲;用作计数器时,计数信号的来源选择非周期性的外部输入信号。
本质上都是计数器
计算:
51单片机有两个定时/计数器T0和T1,为16位加法计数器,由低8位TLx和高8位THx两个寄存器组成,最大计数值为65535个计数脉冲。
原理: 每接收到一个计数脉冲,计数器就会加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寄存器
(小蜜蜂的图)
TCON:见上段落(中断)所写。
代码:
void Init_T0()//需要配置TMOD THX TLX ET0 EA TR0
{
//TMOD: GATE = 0; c/t = 0; M1,M0 = 0x01
TMOD = 0x01;
//初值5ms * 100 = 0.5s
TH0 = (65535 - 50000) / 256;//高8位
TL0 = (65535 - 50000) % 256;//低8位
//打开使能
ET0 = 1;
//总中断
EA = 1;
TR0 = 1;
}
void ServiceT0() interrupt 1
{
//因为没有打开自动重装载,所以要重新配置
TH0 = (65535 - 50000) / 256;//高8位
TL0 = (65535 - 50000) % 256;//低8位
......//编写服务函数
}
(定时器通常与全局变量相结合来编写)
0x09 串口通信
串口通信的概念有很多,这里只讲述博主认为重要的(重要的意思就是感觉能考的(手动滑稽))
一些概念:
串行 && 并行:
串行通信:是将数据字节分成一位一位的形式在 一条传输线上逐个地传送。
串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。
并行通信:通常是将数据字节的各位用多条数据线同时进行传送 。
并行通信的特点:并行通信控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收存在困难。
通信种类:
在51中一般是串行,在此不谈并行,串行通信有SPI,IIC,UART,大多数,串行通信指的就是UART.
传输方向:
单工是指数据传输仅能沿一个方向,不能实现反向传输。
半双工是指数据传输可以沿两个方向,但需要分时进行。
全双工是指数据可以同时进行双向传输。
通信方式:
异步通信:通信中双方的比特率要保持一致。以字符为单位进行数据帧传输,一次一帧。
同步通信:需要同一个时钟,以数据块传输。
比特率:
串口每秒传输的位数。
在51中有4种模式:
模式1,3,由定时器1的溢出率决定,波特率可变
通常用模式2 因为是由定时器1的8位自动重装载产生,当TH1溢出时,自动装到TL1去。
波特率怎么算的就大致看一下把(了解为主)主要是图中后两行话
串口通信有2个缓冲寄存器SBUF,一个发送,一个接收,物理上完全独立,字节寻址(99h).
发送:自动发完之后,TI标志位置1。
SBUF = 0x00; //发送
接收:自动接收之后,RI标志位置1。
变量 = SBUF; //接收到一个变量里
SCON
(小蜜蜂的图)
记住SCON = 0x50,类似蓝桥杯的板子还需要设置AUXR,普中A3就不用了。
代码:
// sfr AUXR=0x8e; //蓝桥杯要用
//--------------------------------------
unsigned char tmpRecv;
void Init_Uart()
{
TMOD=0x20;
TH1=0xfd;
TL1=0xfd;
TR1=1;
SCON = 0x50;
ES=1;
EA=1;
// AUXR &= 0x40; //蓝桥杯要用
}
void SendByte(unsigned char dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
void Sevice_Uart() interrupt 4
{
if(RI == 1)
{
RI = 0;
tmpRecv = SBUF;//接收
SendByte(tmpRecv + 1);//再发出去 (接收的值 + 1)
}
}
当然这里也给出穿字符串的写法:
void Send_Byte(u8 dat)
{
SBUF = dat;//发送
while(TI == 0);
TI = 0;
}
void Send_Str(u8* str)
{
while(*str != '\0')
{
Send_Byte(*str++);
}
}
0x0A 总结
因为篇幅的原因,51单片机的外设芯片,pwm之类的放到下一章去写,本文是博主的复习,借用了小蜜蜂和普中的一些素材。
因为是复习,所以点了一些博主觉得会考的知识点,放了一些博主用惯了的代码。如有不正确的地方,请路过的大佬斧正,希望可以帮助到各位看官,(下一篇是外设芯片篇)。