【笔记】重学单片机(51)(上)_51单片机重学
为学习嵌入式做准备,重新拿起51单片机学习。此贴为学习笔记,仅记录易忘点,实用理论基础,并不是0基础。
资料参考:清翔零基础教你学51单片机
51单片机学习笔记
- 1. C语言中的易忘点
-
- 1.1 数据类型
- 1.2 位运算符
- 1.3 常用控制语句
- 1.4 C51程序的基本结构
- 2.电子电路基础
-
- 2.1 电平
- 2.2 I/O(引脚)
-
- 2.2.1 P3口的第二用途
- 2.3 电子元器件
-
- 2.3.1 电阻:
- 2.3.2 电容:
- 2.3.3 蜂鸣器
-
- 2.3.3.1 判断有源蜂鸣器和无源蜂鸣器
- 2.3.3.2 驱动蜂鸣器
- 2.3.4 常见符号
- 3.单片机 基础知识点
-
- 3.1 单片机最小系统
- 3.2 基本时序
- 4. 51单片机基础例程(I/O外设)
-
- 4.1 点亮LED灯
-
- 4.1.1编程知识点
-
- 4.1.1.1 位定义
- 4.1.2 编程
- 4.2 LED闪烁
-
- 4.2.1 编程知识点
-
- 4.2.1.1 变量作用域
- 4.2.1.2 软件延时
- 4.2.2 编程
- 4.3 流水灯
-
- 4.3.1 编程知识点
-
- 4.3.1.1宏定义
- 4.3.1.2 函数的定义
-
- 4.3.1.3 延时函数
- 4.3.1.4 循环移位函数
-
- 4.3.1.4.1 循环移位函数与左移和右移运算符的区别
- 4.3.2 编程
- 4.4 数码管静、动态显示
-
- 4.4.1 单个数码管--==静态==显示原理
- 4.4.2 74HC573锁存器工作原理
- 4.4.3 多个数码管--==动态==显示原理
- 4.4.4 编程知识点
-
- 4.4.4.1 数组的定义与引用
- 4.4.5 编程
-
- 4.4.5.1 共阴极数码管,静态显示(只亮一个数码管)
- 4.4.5.2 共阴极数码管,动态显示
- 4.5 非编码键盘
-
- 4.5.1 独立键盘
- 4.5.2 矩阵键盘
-
- 1.硬件接线
- 2.扫描
- 4.5.3 编程知识点
-
- 4.5.3.1程序去抖
- 4.5.3.2 开关语句
- 4.5.4 编程
-
- 4.5.4.1 独立键盘
- 4.5.4.2 矩阵键盘
1. C语言中的易忘点
1.1 数据类型
1.2 位运算符
~ 按位取反<> 右移& 按位与^ 按位异或| 按位或
异或:相同取0,不同取1.
1.3 常用控制语句
条件 if
循环while 、 for
开关switch
1.4 C51程序的基本结构
#include //包含51单片机头文件void main()//主函数{}
C语言设置的程序中只允许有一个main函数。程序总是从main函数开始运行的,main函数是void型(无返回值)。
2.电子电路基础
2.1 电平
数字电路中只有两种电平:高电平和低电平。
高电平:5V
低电平:0V
TTL电平规定高电平输出电压>2.4V,低电平输出电压<0.4V
计算机串口使用的是RS232电平
高电平:-12V
低电平:+12V
Tips:单片机与计算机串口通信时需要使用电平转换芯片,把RS232电平转为TTL电平后单片机才能识别。
2.2 I/O(引脚)
例程中
P0接LED,P2^3接蜂鸣器。
2.2.1 P3口的第二用途
2.3 电子元器件
2.3.1 电阻:
常用的贴片电阻封装:0402、0603、0805、1206、1210
贴片电阻读数参考:贴片电阻上的字符是如何表示电阻的?
103,那么10是有效数字,3表示10的3次方,所以103表示的阻值就是10x10^{3}1502,150是有效数字,2表示10的2次方,所以,1502表示的阻值就是150*10^{2}5R6=5.6Ω、R16=0.16Ω01C就是10k
2.3.2 电容:
电容的作用:储能,滤波,通交流隔直流,旁路,耦合,补偿,充放电等
1F(法拉) = 1000000μF(微法)1μF(微法)= 1000nF(纳法)1nF(纳法) = 1000pF(皮法)
2.3.3 蜂鸣器
2.3.3.1 判断有源蜂鸣器和无源蜂鸣器
可以用万用表电阻档Rxl档测试:用黑表笔接蜂鸣器 \"+\"引脚,红表笔在另一引脚上来回碰触,如果触发出咔、咔声的且电阻只有8Ω(或16Ω)的是无源蜂鸣器;如果能发出持续声音的,且电阻在几百欧以上的,是有源蜂鸣器。
2.3.3.2 驱动蜂鸣器
由于蜂鸣器的工作电流一般比较大,以致于单片机的I/O 口是无法直接驱动的,所以要利用放大电路来驱动,一般使用三极管来放大电流就可以了。
(除此之外还有一些其他驱动方式:PWM 输出口直接驱、I/O 口定时翻转电平驱动蜂鸣器方式)
2.3.4 常见符号
3.单片机 基础知识点
3.1 单片机最小系统
电源 (给整个系统提供能量)
单片机芯片 (运行程序/处理数据)
晶振电路 (给单片机工作提供节拍)
复位电路 (单片机上电时需要复位使程序从头开始运行)
3.2 基本时序
振荡周期: 也称时钟周期, 是指为单片机提供时钟脉冲信号的振荡源的周期。
机器周期: 一个机器周期包含 12 个时钟周期。 在一个机器周期内, CPU可以完成一个独立的操作。
例如计算外部晶振频率为11.0592Mhz的单片机一个机器周期所需的时间:
1/11.0592 * 12 ≈ 1.085μS
说明:“1/(11.0592*1000000)”乘以106把Mhz转为hz再乘以“12”是12个时钟周期等于一个机器周期最终值为秒,把这个单位为秒的值乘以106所得值约等于1.085微秒。
4. 51单片机基础例程(I/O外设)
例程中LED位共阳极!
要点亮开发板上LED灯只需要控制P1口输出低电平即可
(编程时给P1口赋值“0”)
4.1 点亮LED灯
普通发光二极管工作压降为:1.6v ~ 2.1 V。
工作电流为:1~20mA
注意:二极管需要上拉电阻。
否则电流=(5V-2.1V)/0Ω,电流无限大
有电阻后,电流=(5V-2.1V)/1kΩ,约等于3mA.
4.1.1编程知识点
4.1.1.1 位定义
关键字:sbit
功能:位定义
一般格式: sbit 标识符 = 地址值;
例如:sbit LED1 = P1^0;
注意:地址值中P1的“P”必须为大写的P
4.1.2 编程
#include //引用51头文件sbit LED1 = P1^0; //位定义void main()//主函数{LED1 = 0;//点亮P1.0上的LED}
4.2 LED闪烁
4.2.1 编程知识点
4.2.1.1 变量作用域
全局变量:在函数体外定义的变量通常为全局变量,作用范围:从定义开始的整个程序局部变量:在函数体内定义的变量通常为局部变量,作用范围:函数体内
4.2.1.2 软件延时
软件延时例如:
unsigned int i;i=65535;while(i);
4.2.2 编程
实现:P1口上所有LED闪烁
#include //包含51头文件unsigned int i;//0~65535void main()//main函数自身会循环{while(1)//大循环{P1 = 0;//点亮P1口8个LEDi = 65535;while(i--);//软件延时P1 = 0xff;//1111 1111 熄灭P1口8个LEDi = 65535;while(i--);//软件延时}}
按位取反 闪烁写法:
#include unsigned int i;//0~65535void main()//main函数自身会循环{P1 = 0xff;//熄灭8位LEDwhile(1) //大循环{P1 = ~P1;//使用按位取反运算符使LED闪烁i = 65535;while(i--);//软件延时}}
4.3 流水灯
4.3.1 编程知识点
4.3.1.1宏定义
理解成 用define/ typedef 取小名
#define uchar unsigned char注意宏定义后面不能加分号,它是预处理指令不是语句。
其中用“uchar”直接替换了unsigned char
此时我们可以用uchar去定义变量类型如:uchar i;等价于unsigned char i;
【C语言】typedef与define的区别
typedef,我们可以为基本类型(如int、float)或自定义的结构体、联合体等定义新的名称。define,定义常量、函数替换宏、条件编译等,它的作用范围更为广泛。所有满足条件的宏定义在预处理阶段都会被替换为指定的文本。
4.3.1.2 函数的定义
自定义函数一般格式为:
函数类型 函数名 (形式参数表){局部变量定义函数体语句}
4.3.1.3 延时函数
自定义函数:延时函数delay(毫秒级)
void delay(unsigned int z){unsigned int x,y;for(x = z; x > 0; x--)for(y = 114; y > 0 ; y--);}
给形参z赋值,如延时100毫秒:delay(100);
4.3.1.4 循环移位函数
标准库函数: intrins.h
内部函数:
字符型循环左移:_crol_字符型循环右移:_cror_
#include void test_crol (void) { unsigned char a; unsigned char b; a = 0xFE; //1111 1110 b = _crol_(a,1); // b now is 0xFD 二进制为1111 1101}
4.3.1.4.1 循环移位函数与左移和右移运算符的区别
循环左移是把最高位移到最低位上,
左移运算符是把最高位移除最低位补0
4.3.2 编程
实现:流水灯一直左循环
#include //包含51头文件#include //包含移位标准库函数头文件#define uint unsigned int#define uchar unsigned charuchar temp;//LED灯相关变量/*====================================函数: delay(uint z)参数:z 延时毫秒设定,取值范围0-65535返回值:无描述:12T/Fosc11.0592M毫秒级延时====================================*/void delay(uint z){uint x,y;for(x = z; x > 0; x--)for(y = 114; y > 0 ; y--); } void main()//main函数自身会循环{temp = 0xfe;P1 = temp; //1111 1110 初值LED1亮delay(100);//毫秒级延时 100毫秒while(1){temp = _crol_(temp, 1);//循环左移P1 = temp;//移位完成后赋值给P1 每个一个灯点亮delay(100);//毫秒级延时 100毫秒}}
4.4 数码管静、动态显示
4.4.1 单个数码管–静态显示原理
有共阳、共阴极,例程使用共阴!即b图左边。
共阴极数码码表:
0x3F, //\"0\"0x06, //\"1\"0x5B, //\"2\"0x4F, //\"3\"0x66, //\"4\"0x6D, //\"5\"0x7D, //\"6\"0x07, //\"7\"0x7F, //\"8\"0x6F, //\"9\"0x77, //\"A\"0x7C, //\"B\"0x39, //\"C\"0x5E, //\"D\"0x79, //\"E\"0x71, //\"F\"0x76, //\"H\"0x38, //\"L\"0x40, //\"-\"0x00, //熄灭
4.4.2 74HC573锁存器工作原理
锁存器作用:可以把数据输入端与输出端进行隔离或连接
左边D为输入,右边Q为输出
输出口Q要想输出高低电平OE脚必须接GND。
LE脚为高时,输出端Q随输入端D的数据而变化。
LE脚为低时,输出端Q数据保持不变,输入端D数据变化不会改变Q的数据。
4.4.3 多个数码管–动态显示原理
段选和位选
位选:选择哪一个数码管亮段选:该数码管怎么亮(数码管里的二极管怎么亮)
工作流:
打开位选锁存器->指定某个数码管->关闭位选锁存器,
打开段选锁存器->指定亮什么数字->关闭段选锁存器,
因为扫描周期短,所以肉眼可以看到多数码管同时亮 不同的数字。
4.4.4 编程知识点
4.4.4.1 数组的定义与引用
数组是一组有序数据的集合,数组中每一个数据都是同一数据类型。数组中的元素可以用数组名和下标来唯一确定。
数组的一般格式定义如下:
数据类型 数组名[常量表达式] = {元素表};
例如:
unsigned char tabel[3] ={0x3F, 0x06, 0x5B, };P0 = tabel[0]; //P0此时的值为0x3F
4.4.5 编程
4.4.5.1 共阴极数码管,静态显示(只亮一个数码管)
#include #include #define uint unsigned int#define uchar unsigned charsbit DU = P2^6;//数码管段选sbit WE = P2^7;//数码管位选////毫秒级延时函数定义//void delay(uint z)//{//uint x,y;//for(x = z; x > 0; x--)//for(y = 114; y > 0 ; y--); //} void main()//main函数自身会循环{WE = 1;//打开位选锁存器P0 = 0XFE; //1111 1110 选通第一位数码管WE = 0;//锁存位选数据DU = 1;//打开段选锁存器P0 = 0X06;//0000 0110 显示“1”DU = 0;//锁存段选数据while(1){}}
4.4.5.2 共阴极数码管,动态显示
#include //包含51头文件#include //包含移位标准库函数头文件#define uint unsigned int#define uchar unsigned charsbit DU = P2^6;//数码管段选sbit WE = P2^7;//数码管段选//共阴数码管段选表0-9uchar code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};/*====================================函数: delay(uint z)参数:z 延时毫秒设定,取值范围0-65535返回值:无描述:12T/Fosc11.0592M毫秒级延时====================================*/void delay(uint z){uint x,y;for(x = z; x > 0; x--)for(y = 114; y > 0 ; y--); } /*====================================函数:display(uchar i)参数:i 显示数值,取值范围0-255返回值:无描述:三位共阴数码管动态显示====================================*/void display(uchar i){uchar bai, shi, ge;bai = i / 100; //236 / 100 = 2shi = i % 100 / 10;//236 % 100 / 10 = 3ge = i % 10;//236 % 10 =6//第一位数码管 P0 = 0XFF;//清除断码WE = 1;//打开位选锁存器P0 = 0XFE; //1111 1110WE = 0;//锁存位选数据DU = 1;//打开段选锁存器P0 = tabel[bai];//DU = 0;//锁存段选数据delay(5);//第二位数码管P0 = 0XFF;//清除断码WE = 1;//打开位选锁存器P0 = 0XFD; //1111 1101WE = 0;//锁存位选数据DU = 1;//打开段选锁存器P0 = tabel[shi];//DU = 0;//锁存段选数据delay(5);//第三位数码管P0 = 0XFF;//清除断码WE = 1;//打开位选锁存器P0 = 0XFB; //1111 1011WE = 0;//锁存位选数据DU = 1;//打开段选锁存器P0 = tabel[ge];//DU = 0;//锁存段选数据delay(5);}void main()//main函数自身会循环{while(1){display(236); //数码管显示函数}}
4.5 非编码键盘
4.5.1 独立键盘
每个按键占用一个IO口,当按键数量较多时,IO口利用效率不高,但程序简单,适用于所需按键较少的场合。
按键一端与IO口连接,另外一端接地。I/O进行检测,
按键按下会被检测到低电平,未按下被检测为高电平。
4.5.2 矩阵键盘
电路连接复杂,但提高了IO口利用率,软件编程较复杂。适用于使用大量按键的场合。
上图为4行和4列,一共16个按键组成。
确定矩阵键盘上哪一个按键被按下可以采用列扫描和行扫描。列扫描时先把接在列上面的所有IO口拉高,接在行上的所有IO置低。当其中有一列内任何一个按键按下那么整条列线都会被拉低。
1.硬件接线
P3口的高4位(P3.4~P3.7)作为列线(输出)P3口的低4位(P3.0~P3.3)作为行线(输入)
uchar cord_l,cord_h;//声明列线和行线的值的储存变量P3 = 0xf0;//1111 0000
2.扫描
扫描分为两步:列扫描和行扫描,通过检测电平变化确定按键位置。
step1–列判断:行线(P3.0 ~ P3.7)为0,列线(P3.4 ~ P3.7)为1,即P3=0xf0。
当四列中,某一列被按下,列线的对应的值就会改变。
cord_l = P3 & 0xf0;// 只储存列线值 (f0的0作用:无论行线是多少,都不保存,写0)
例:2行2列的S11按下,读到L=0xd0(图中从下P37往上P30,1101 0000)
step2–行判断:在列判断后,保留读到的列线值,行线值写1。即用读到的列线值 或 0x0f。
例:0xd0|0x0f (1101 0000|00001111)得到1101 1111,2行2列的S11按下,导致P31的1变为0,即变为1101 1101 (0xdd)0xdd&0x0f=0x0d,此为行线值
P3 = cord_l | 0x0f; cord_h = P3 & 0x0f;// 只储存行线值(0f的0作用:无论列线是多少,都不保存,写0)
step3:
列线值+行线值
return (cord_l + cord_h);//返回键值码
4.5.3 编程知识点
4.5.3.1程序去抖
if(key_s3 == 0)//判断S3是否被按下{delay(20);//按键消抖if(key_s3 == 0){...while(!key_s3);//松手检测}}
4.5.3.2 开关语句
switch (表达式){case 常量表达式1: 语句1 break;case 常量表达式2: 语句2 break;}
4.5.4 编程
4.5.4.1 独立键盘
实现:按下开发板左下角S2按键数码管值+1,最大到9按下S3按下,值减一,最小减到0
#include #include #define uint unsigned int#define uchar unsigned charsbit DU = P2^6;//数码管段选sbit WE = P2^7;//数码管位选sbit key_s2 = P3^0;//独立按键S2sbit key_s3 = P3^1;//独立按键S3uchar num;//数码管显示的值//共阴数码管段选表0-9uchar code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};//毫秒级延时函数定义void delay(uint z){uint x,y;for(x = z; x > 0; x--)for(y = 114; y > 0 ; y--); } void main()//main函数自身会循环{num = 0; WE = 1;//打开位选锁存器P0 = 0XFE; //1111 1110WE = 0;//锁存位选数据while(1){if(key_s2 == 0)//判断S2是否被按下{delay(20);//按键消抖if(key_s2 == 0){if(num != 9)//如果值不等于9则+1,功能把值限定为小于9num++;while(!key_s2);//松手检测}}if(key_s3 == 0)//判断S3是否被按下{delay(20);//按键消抖if(key_s3 == 0){if(num > 0)//如果大于0则执行减一num--;while(!key_s3);//松手检测}}//松手之后刷新显示DU = 1;//打开段选锁存器P0 = tabel[num];//DU = 0;//锁存段选数据}}
4.5.4.2 矩阵键盘
实现:矩阵键盘
#include #define uchar unsigned char#define uint unsigned intsbit we = P2^7;sbit du = P2^6;uchar code leddata[]={ 0x3F, //\"0\" 0x06, //\"1\" 0x5B, //\"2\" 0x4F, //\"3\" 0x66, //\"4\" 0x6D, //\"5\" 0x7D, //\"6\" 0x07, //\"7\" 0x7F, //\"8\" 0x6F, //\"9\" 0x77, //\"A\" 0x7C, //\"B\" 0x39, //\"C\" 0x5E, //\"D\" 0x79, //\"E\" 0x71, //\"F\" 0x76, //\"H\" 0x38, //\"L\" 0x37, //\"n\" 0x3E, //\"u\" 0x73, //\"P\" 0x5C, //\"o\" 0x40, //\"-\" 0x00, //熄灭 0x00 //自定义 };void delay(uint z){uint x,y;for(x = z; x > 0; x--)for(y = 114; y > 0 ; y--);}uchar KeyScan()//带返回值的子函数{uchar cord_l,cord_h;//声明列线和行线的值的储存变量P3 = 0xf0;//1111 0000if( (P3 & 0xf0) != 0xf0)//判断是否有按键按下{delay(5);//软件消抖if( (P3 & 0xf0) != 0xf0)//判断是否有按键按下{ cord_l = P3 & 0xf0;// 储存列线值 P3 = cord_l | 0x0f; cord_h = P3 & 0x0f;// 储存行线值 while( (P3 & 0x0f) != 0x0f );//松手检测 return (cord_l + cord_h);//返回键值码}}}void KeyPro(){switch( KeyScan() ){ //第一行键值码case 0xee: P0 = leddata[0];break;case 0xde: P0 = leddata[1];break;case 0xbe: P0 = leddata[2];break;case 0x7e: P0 = leddata[3];break;//第二行键值码case 0xed: P0 = leddata[4];break;case 0xdd: P0 = leddata[5];break;case 0xbd: P0 = leddata[6];break;case 0x7d: P0 = leddata[7];break;//第三行键值码case 0xeb: P0 = leddata[8];break;case 0xdb: P0 = leddata[9];break;case 0xbb: P0 = leddata[10];break;case 0x7b: P0 = leddata[11];break;//第四行键值码case 0xe7: P0 = leddata[12];break;case 0xd7: P0 = leddata[13];break;case 0xb7: P0 = leddata[14];break;case 0x77: P0 = leddata[15];break;}}void main(){we = 1;//打开位选P0 = 0;//八位数码管全显示we = 0;//锁存位选du = 1;//打开段选端P0 = leddata[22];while(1){ KeyPro();//提取键值码并且送不同数值给数码管显示}}
实现:按下矩阵键盘和独立键盘任意键,数码管显示相应数值初始显示“-”横
#include //包含51头文件#include //包含移位标准库函数头文件#define uint unsigned int#define uchar unsigned charsbit DU = P2^6;//数码管段选sbit WE = P2^7;//数码管段选uchar num;//数码管显示的值uchar KeyValue = 20;//按键值 显示-//共阴数码管段选表uchar code tabel[]= {//01 2 3 4 5 6 7 80x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F,//9 A B C D E FH L 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x76, 0x38,//n u - 熄灭0x37, 0x3E, 0x40, 0x00 };/*====================================函数: delay(uint z)参数:z 延时毫秒设定,取值范围0-65535返回值:无描述:12T/Fosc11.0592M毫秒级延时====================================*/void delay(uint z){uint x,y;for(x = z; x > 0; x--)for(y = 114; y > 0 ; y--); } /*====================================函数:KeyScan()参数:无返回值:无描述:4*4矩阵键盘与独立键盘扫描按键按下KeyValue全局变量值发生相应变化====================================*/void KeyScan(){//4*4矩阵键盘扫描P3 = 0XF0;//列扫描if(P3 != 0XF0)//判断按键是否被按下{delay(10);//软件消抖10msif(P3 != 0XF0)//判断按键是否被按下{switch(P3) //判断那一列被按下{case 0xe0:KeyValue = 0;break;//第一列被按下case 0xd0:KeyValue = 1;break;//第二列被按下case 0xb0:KeyValue = 2;break;//第三列被按下case 0x70:KeyValue = 3;break;//第四列被按下 }P3 = 0X0F;//行扫描switch(P3) //判断那一行被按下{case 0x0e:KeyValue = KeyValue;break;//第一行被按下case 0x0d:KeyValue = KeyValue + 4;break;//第二行被按下case 0x0b:KeyValue = KeyValue + 8;break;//第三行被按下case 0x07:KeyValue = KeyValue + 12;break;//第四行被按下 }while(P3 != 0X0F);//松手检测}}P3 = 0XFF;//独立按键扫描if(P3 != 0XFF){delay(10);//软件消抖10msif(P3 != 0XFF){switch(P3) //判断那一行被按下{case 0xfe:KeyValue = 16;break;//S2被按下case 0xfd:KeyValue = 17;break;//S3被按下case 0xfb:KeyValue = 18;break;//S4被按下case 0xf7:KeyValue = 19;break;//S5被按下 }while(P3 != 0XFF);//松手检测}}}void main()//main函数自身会循环{WE = 1;//打开位选锁存器P0 = 0XFE; //1111 1110WE = 0;//锁存位选数据DU = 1;//打开段选锁存器while(1){KeyScan();//20个按键键盘扫描P0 = tabel[KeyValue];//显示按键值}}