为学习嵌入式做准备,重新拿起51单片机学习。此贴为学习笔记,仅记录易忘点,实用理论基础,并不是0基础。
资料参考:清翔零基础教你学51单片机
51单片机学习笔记
1. C语言中的易忘点
1.1 数据类型
1.2 位运算符
~ 按位取反
<< 左移
>> 右移
& 按位与
^ 按位异或
| 按位或
异或:相同取0,不同取1.
1.3 常用控制语句
条件 if
循环while 、 for
开关switch
1.4 C51程序的基本结构
#include <reg52.h> //包含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 <reg52.h> //引用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 <reg52.h>//包含51头文件
unsigned int i;//0~65535
void main()//main函数自身会循环
{
while(1)//大循环
{
P1 = 0; //点亮P1口8个LED
i = 65535;
while(i--);//软件延时
P1 = 0xff;//1111 1111 熄灭P1口8个LED
i = 65535;
while(i--);//软件延时
}
}
按位取反 闪烁写法:
#include <reg52.h>
unsigned int i;//0~65535
void main()//main函数自身会循环
{
P1 = 0xff;//熄灭8位LED
while(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;
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 <intrins.h>
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 <reg52.h> //包含51头文件
#include <intrins.h> //包含移位标准库函数头文件
#define uint unsigned int
#define uchar unsigned char
uchar 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 <reg52.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
sbit 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 <reg52.h>//包含51头文件
#include <intrins.h>//包含移位标准库函数头文件
#define uint unsigned int
#define uchar unsigned char
sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
//共阴数码管段选表0-9
uchar 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 = 2
shi = i % 100 / 10; //236 % 100 / 10 = 3
ge = i % 10;//236 % 10 =6
//第一位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFE; //1111 1110
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[bai];//
DU = 0;//锁存段选数据
delay(5);
//第二位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFD; //1111 1101
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
P0 = tabel[shi];//
DU = 0;//锁存段选数据
delay(5);
//第三位数码管
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = 0XFB; //1111 1011
WE = 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 <reg52.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管位选
sbit key_s2 = P3^0;//独立按键S2
sbit key_s3 = P3^1;//独立按键S3
uchar num;//数码管显示的值
//共阴数码管段选表0-9
uchar 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 1110
WE = 0;//锁存位选数据
while(1)
{
if(key_s2 == 0)//判断S2是否被按下
{
delay(20);//按键消抖
if(key_s2 == 0)
{
if(num != 9)//如果值不等于9则+1,功能把值限定为小于9
num++;
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 <reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit 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 0000
if( (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 <reg52.h>//包含51头文件
#include <intrins.h>//包含移位标准库函数头文件
#define uint unsigned int
#define uchar unsigned char
sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
uchar num;//数码管显示的值
uchar KeyValue = 20;//按键值 显示-
//共阴数码管段选表
uchar code tabel[]= {
//0 1 2 3 4 5 6 7 8
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F,
//9 A B C D E F H 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);//软件消抖10ms
if(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);//软件消抖10ms
if(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 1110
WE = 0;//锁存位选数据
DU = 1;//打开段选锁存器
while(1)
{
KeyScan();//20个按键键盘扫描
P0 = tabel[KeyValue];//显示按键值
}
}