51单片机入门总结
单片机 MCU51单片机的故事起始于20世纪80年代,它的起源与美国英特尔公司息息相关。最初,英特尔在1980年推出了一个重要的单片机产品——Intel 8051。这款单片机是基于其早期产品如8048和8031等型号发展的,尤其是8031单片机因其简单可靠且性能良好而备受好评。8051单片机相较于前代产品有了显著的改进,它集成了更强大的功能,比如4KB的程序存储器(ROM)、128字节的数据存储器
1-1 单片机简介
单片机 MCU
51单片机的故事起始于20世纪80年代,它的起源与美国英特尔公司息息相关。最初,英特尔在1980年推出了一个重要的单片机产品——Intel 8051。这款单片机是基于其早期产品如8048和8031等型号发展的,尤其是8031单片机因其简单可靠且性能良好而备受好评。8051单片机相较于前代产品有了显著的改进,它集成了更强大的功能,比如4KB的程序存储器(ROM)、128字节的数据存储器(RAM)、定时器/计数器、中断系统以及串行接口等,这些特性使其非常适合于各种控制应用。
由于8051架构的成功,它逐渐成为了事实上的行业标准。后续,虽然英特尔不再继续生产基于8051的单片机,但“51单片机”这一名称成为了所有兼容Intel 8051指令集的单片机的统称。这包括了不同厂家生产的多种变体,如Atmel、Philips(现在的NXP)、Silicon Labs等,它们在保持基本指令集兼容的基础上,增加了诸如更大容量的存储器、更快的时钟速度、更低的功耗以及集成更多外设等功能。
随着技术的进步,特别是Flash ROM技术的发展,使得单片机能够更容易地进行程序编写和重复擦写,进一步推动了51单片机的应用普及。在教育领域,由于其结构相对简单和学习资源丰富,51单片机成为了许多高校教学单片机原理和嵌入式系统开发的首选平台。
尽管现在市场上出现了许多更先进的单片机,如ARM Cortex-M系列的STM32等,但51单片机凭借其长期积累的生态系统、低成本和稳定性,在一些特定领域如工业控制、消费电子产品、教学实验等仍然占据着一席之地。随着物联网、人工智能等新技术的发展,51单片机也在不断适应新的需求,持续发挥其作用。
单片机的应用场景
单片机,作为一种集成度高、体积小、功耗低的微控制器,广泛应用于几乎所有的电子设备和控制系统中。以下是单片机的一些典型应用场景:
- 1. 家用电器:单片机在现代家庭中无处不在,从基本的电饭煲、洗衣机、空调、冰箱到高端的智能家居设备如智能灯泡、智能门锁、智能插座等,都依赖单片机进行控制和智能化管理。
- 2. 工业控制:在工业自动化领域,单片机用于构建数控机床、自动化生产线、电机控制、温度控制、数据采集系统以及各种报警和监控系统,提高生产效率和安全性。
- 3. 仪器仪表:无论是实验室的精密测量设备还是医疗领域的各种分析仪、监护仪、超声诊断设备,单片机都是实现设备智能化、精确测量和数据显示的核心。
- 4. 汽车与航空航天:从汽车的引擎控制、刹车系统、信息娱乐系统到飞机上的各种传感器和控制模块,单片机负责复杂的控制任务和数据处理。
- 5. 通信设备:现代通信基础设施,包括路由器、交换机以及移动电话基站等,广泛使用单片机进行信号处理、协议控制和数据传输。
- 6. 消费电子产品:手机、平板电脑、智能穿戴设备(如智能手表、健康监测手环)、高清视频处理设备等,单片机控制着用户界面、电源管理、传感器数据处理等功能。
- 7. 医疗设备:除了上述提到的监护仪等,还包括病床呼叫系统、呼吸机、输液泵等,单片机确保设备准确响应患者需求和安全操作。
- 8. 办公设备:打印机、复印机、扫描仪、投影仪等办公自动化设备内部,单片机负责控制各种操作流程和用户交互界面。
- 9. 广告与显示系统:户外LED广告牌、室内显示屏等,单片机控制着图像和视频的播放,实现动态展示效果。
- 10. 教育与科研:在教学实验、机器人竞赛、科研项目中,单片机作为学习和创新的基础平台,帮助学生和研究人员快速原型设计和验证想法。
单片机的应用场景极其广泛,几乎涉及所有需要电子控制的领域,随着技术进步,其应用范围还在不断扩大,尤其是在物联网、人工智能等新兴领域中展现出更大的潜力。
单片机能够做什么
51 单片机结构简单,成本低,生产多,不过时也不会被淘汰
如何学习单片机
- 1:动手实践
- 2:需要的工具:电烙铁,万用表,单片机书籍,c语言书籍,数字电子技术,模拟电子技术,所用到的器件的芯片手册
- 3:一边学习单片机一边学习相关电子电路的知识“零基础开始学,同步的学习”
- 4:软件的安装默认略过,stc烧录软件默认略过
单片机的封装
单片机的封装是指将单片机芯片包裹在外部保护材料中,并通过引脚或接触点与外界电路相连的方式。不同的封装形式适用于不同的应用场景,主要受到空间限制、散热需求、成本、生产效率等因素的影响。下面是几种常见的单片机封装类型及其特点:
1. DIP (Dual In-line Package):双列直插式封装,是最传统的封装形式之一,引脚分布在芯片两侧,适合手工焊接和面包板实验,但体积较大,不适合高密度的电路板设计。
2. SOP (Small Outline Package):小外形封装,是一种表面贴装技术(SMT)封装,引脚从封装两侧伸出,形状类似海鸥展翅,适用于空间受限的场合。
3. PLCC (Plastic Leaded Chip Carrier):带引线的塑料芯片载体,也是一种SMT封装形式,引脚从封装的四个侧面引出,常用于需要一定强度和可更换性的应用中。
4. QFP (Quad Flat Package):塑料方型扁平式封装,所有引脚均匀分布在芯片四周,适合需要大量引脚的复杂单片机,也是SMT封装,适用于高密度电路板。
5. PGA (Pin Grid Array package):插针网格阵列封装,引脚以矩阵形式排列在底部,通常用于需要高性能散热或高引脚数的单片机,多见于插槽式安装。
......
6. BGA (Ball Grid Array Package):球栅阵列封装,底部有众多焊球作为接触点,大大提高了引脚密度,适用于高端单片机或需要极小体积的设计,但焊接和维修较为复杂。
每种封装形式都有其优势和局限性,设计者需根据具体需求选择合适的封装类型。例如,对于原型开发和教学,DIP封装因便于手工焊接而受欢迎;而在商业产品设计中,SOP、QFP、BGA等小型化封装则更常见,以满足产品的小型化和高性能需求。
1-2 TTL电平特性
基础知识必备-数字电路中只有0和1,TTL电平特性
模拟电路中电压是连续的:0,0.3,1.2,4.5等
数字电路中只有两种电平状态,一种是高电平用 1 表示,一种是低电平用 0 表示
(定义单片机)的电源和IO口为TTL电平
高电平【1】 表示 + 5V 低电平【0】 表示 0V
如果使用3.3V的单片机高电平【1】 表示 + 3.3V 低电平【0】 表示 0V
RS232:计算机的串口,才有负逻辑高 -12V ,低电平 +12V
注:计算机与单片机之间通讯时需要加电平转换芯片MAX232(开发板的串口旁)
Voh 表示输出高电平的最低值,Vih表示输入高电平的可以检测到的最低值,Vil输入低电平的最高值,Vol输出低电平的最高值。
1-3 数的进制与位权
10进制数的位权
2进制数的位权
16进制数的位权
进制之间的转换(单片机基础必备知识)
注 :单片机中只有两种电平0和1,所以内部存储,运算全部都是二进制的方式
单片机中存储的最小结构(寄存器)是字节(Byte),1个字节 = 8 位 (bit)
使用8421法将二进制后数转换为10进制数,使用8421的方法进行转换(具体可以参考这张图)
二进制,10进制和16进制的转换内容
电脑中的计算器
1-4 进制间的转换
二进制,八进制,十六进制
二进制转换为16进制,从最低位,每4位一个区间,把各区间的4位二进制数依照表转换为16进制
注:从低位起将二进制数每4为划分为一个区间,再将它转换为16进制数。
进制间的转换练习
进制转换之间的练习
二进制位运算--------------------与,或,非
与: 实现的是必须都有,否则都没有,【也就是全真为真,有假为假】
C语言中的运算符:&
【0 & 0 = 0 , 0 & 1 = 0 , 1 & 0 = 0, 1 & 1 = 1】
当两个字节运算时,按位对齐,依次运算,如 c = a & b;
二进制运算-----------------------------------或运算
或:实现 “只要其中有1就有”
c语言中的运算符:| 【键盘上面的\shift + \】
0 | 1 = 1, 1 | 1 = 1, 1 | 0 = 1, 0 | 0 = 0.
当两个字节运算时,按位对齐,依次运算,如:c = a | b
或在数字电路中的符号
二进制运算 --------------------------------------- 非运算
非的运算符号是【~】-----> 就是C语言中的取反,相同为0,不同为1
C语言中的运算符 ~
没有 0 ~ 0 = 0 0 ~ 1 = 1 这种写法
只有 A = ~B
如: b =~a
二进制的非运算实现的是取反的操作
二进制的位运算----------------------- 按位异或运算【^】
按位异或:实现“必须不同,否则就没有”,相同为0不同为1
C语言中的运算符:^
**0 ^ 0 = 0; 1 ^0 = 1, 1 ^ 0 = 1 ,1 ^1 = 0;**
当两个字节异或运算时,按位对齐,依次运算
如: C = a ^ b
二进制位运算 ---- 同或(C语言中不存在)
同或:实现“必须相同,否则就没有”
C语言中是没有同或运算的
0 同或 0 = 1, 0 同或 1 = 0, 1 同或 0 = 0, 1 同或 1 = 1.
当两个字节同互运算时,按位对齐,依次运算
如:c = a通过b
注:可以通过对异或取反的操作得到同或运算
1-5 单片机的内部工作原理
单片机的内部结构图
C语言编写代码实现单片机点亮led灯
#include <REGX52.H>
// 位定义
sbit LED1 = P1^0;
void main(void){
while(1){
// 赋值为0点亮第一个LED灯
LED1 = 0;
}
}
单片机烧录软件stc-isp
C语言的预处理指令
#include 预处理指令,在c语言中.h文件统称为头文件
【编译---------链接 -----------运行】
从源代码(.c或.h)到执行文件(hex)的转换过程叫“编译”,做这件工作的程序叫“编译器”,“编译”细分为“预处理”+“编译”+“链接”。
预处理指令:#include #define #undef
\#include 被称为源代码包含指令,简称:包含指令
\#define #undef 宏定义指令【后面再讲】
**有两种表示方法:#include <Header.h> #include “Header.h”**
处理过程:就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,与复制粘贴的效果相同;
区别:使用尖括号< >,编译器会到系统路径下查找头文件;
使用双引号“”,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
Sbit的作用:定义一个可以进行位寻址的特殊功能寄存器
在keil中对应的位寻址定义
注:只有具备位寻址的特殊功能寄存器才能被sbit定义
sbit P2_0 = 0xA0;
sbit P2_1 = 0xA1;
sbit P2_2 = 0xA2;
sbit P2_3 = 0xA3;
sbit P2_4 = 0xA4;
sbit P2_5 = 0xA5;
sbit P2_6 = 0xA6;
sbit P2_7 = 0xA7;
头文件的作用
单片机内部对于特殊功能寄存器的定义与操作特殊功能寄存器中的位
C语言中main函数的含义
注:main函数是函数的入口,程序从main函数开始执行,每个C语言程序有且只有一个main函数
-----------------------------**main 函数的含义**-------------------------------------------
void main(){
}
函数的格式
【注:函数和方法的命名不要是 C语言的关键字】
注:main函数在c语言中是程序的入口,任何一个C语言程序有且只有一个main函数,如果出现两个main函数程序会报错。
1-6 单片机IO口的功能与定义
STC89C52管脚定义原理图 16页
Vcc --------表示的是电源
所有的单片机引脚都是按组分的左边的1-8引脚表示的是P1组
右边的39 - 32 是P0口
还有P3口与P2口等
GND 表示的是接地
T2EX/P1.1 表示的是这个引脚有第二个功能(可以理解为引脚的服用)
18 - 19 引脚是时钟的输入端
29 - 30 - 31 没有内部flash需要外部的flash外部添加引脚
引脚功能定义图:23页
ALE ----------------------> 地址锁存信号针对外部的引脚使用的
EA -----------------------> 内外存储器的选择引脚,接低电平使用外部的程序存储空间,接高电平使用内部程序存储空间
点亮多个LED灯
<使用并行操作点亮多个led灯,使用16进制的方式>
使用10进制的方式操作led没有使用16进制方便,容易换算
【单片机的数据类型和所占字节大小】
数据类型
单片机内部存放临时数据的空间是有限的,而且非常宝贵,用完就没有了单片机需要提前知道某一个数的大小范围,以方便它提前准备好能足够存放数据的空间此空间刚好合适,足够放得下是最好的选择,大了浪费,小了不够。
C语言中的数据类型
同一个变量只能定义一种数据类型:实现led灯的闪烁效果,这个时候亮的时间比较短,灭的时间比较长
#include <REGX52.H>
// 定义一个无符号的整形变量a
unsigned int a,b;
void main(void){
while(1){
a = 25550; // 变量的参数
P1 = 0xfe; // 1111 1110
while(a--); // 延时
b = 65535; // 变量重新赋值
P1 = 0xff; // 1111 1111
while(a--); // 延时
}
}
while (表达式)
{语句(内部也可为空)}
若为空,写法为:while (表达式);
特点:先判断表达式,后执行语句。
原则:若表达式不是0,即为真,那么执行语句。否则跳出while 语句。
注:while的工作原理,非0就是真,0就是假
【C语言中的++ 和 -- 的用法】?
a=2;
while (a--);
执行流程:
1。确认a是几? -> while检测a是真还是假
2。如果是真,等着,如果是假,这条语句结束,执行下一条
3。a把它自己减一个数->再回到上面1重新开始
软件的调试模式精确的找到调试的时间
1-7 51单片机软件调试
点击放大镜图标进入debug模式
#include <REGX52.H>
// 定义一个无符号的整形变量a
unsigned int a,b;
void main(void){
while(1){
a = 30800; // 变量的参数
P1 = 0xfe; // 1111 1110
while(a--); // 延时
b = 65535; // 变量重新赋值
P1 = 0xff; // 1111 1111
while(a--); // 延时
}
}
对以上这段简单的代码进行调试
特殊功能寄存器的查询状态,包括程序运行时间等参数
反汇编语言的参数,可以不予理会
打断点
1-8 C语言中的关键字
变量的定义:不能和C语言中的关键字重合
【宏定义#define c语言中的预处理指令】
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
uchar a;
uint b;
void main(void){
while(1){
a = 255; // 变量的参数
P1 = 0xfe; // 1111 1110
while(a--); // 延时
b = 65535; // 变量重新赋值
P1 = 0xff; // 1111 1111
while(a--); // 延时
}
}
**DEF:**
预处理指令:#define
\#define 宏定义指令
表示方法: #define AAA XXX
处理过程:把代码中所有用到XXX的地方用AAA代替,相当于一个改名称
的功能;
AAA是#define 后面的第一个字母组合,XXX是AAA空格后面的全部内容,直到换行前。
作用:有些地方经常需要编写较长的,且会重复出现的代码,用此功能
节省时间;
如: #define uint unsigned int
**【循环语句中for的用法】**
**C语言中的注释方式**
C语言中的单行注释
//
C语言中的多行注释
/*
*/
C语言中另外的一种注释
#ifndef 0
......
..................
#endif
在有些语言中,注释有时用于把一段代码“注释掉”,也就是使这段代码在程序中不起作用,但并不将其真正从源文件中删除。在C语言中,这可不是个好主意,如果你试图在一段代码的首尾分别加上/*和*/符号来“注释掉”这段代码,你不一-定能如愿。如果这段代码内部原先就有注释存在,这样做就会出问题。要从逻辑上删除一段C代码,更好的办法是使用#if指令。只要像下面这样使用:[引用自《c和 指针》]一书。
for语句案例
for循环可以嵌套使用,有两重或者是三重的for循环,建议使用for循环的时候最好包含一对大括号
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
uchar a;
uint b;
#if 0
a = 255; // 变量的参数
P1 = 0xfe; // 1111 1110
while(a--); // 延时
b = 65535; // 变量重新赋值
P1 = 0xff; // 1111 1111
while(a--); // 延时
#endif
void main(void){
while(1){
for(b = 30000; b > 0; b --){
P1 = 0xfe;
}
for(b = 30000; b > 0; b --){
P1 = 0xff;
}
}
}
for循环的用法
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
sbit LED1 = P1^0;
uchar a;
uint b;
#if 0
a = 255; // 变量的参数
P1 = 0xfe; // 1111 1110
while(a--); // 延时
b = 65535; // 变量重新赋值
P1 = 0xff; // 1111 1111
while(a--); // 延时
#endif
void main(void){
while(1){
#if 0
// for循环中有语句的写法
for(b = 30000; b > 0; b --){
P1 = 0xfe;
}
for(b = 30000; b > 0; b --){
P1 = 0xff;
}
// for循环中没有语句的写法
for(b = 30000; b > 0; b --);
P1 = 0xfe;
for(b = 30000; b > 0; b --);
P1 = 0xff;
// for循环的嵌套调用
for(a = 3; a > 0; a--){
for(b = 100000;b>0;b--){
LED1 = ~LED1;
}
}
#endif
}
}
1-9 单片机的机器周期和指令周期
定义:
(1)振荡周期: 也称时钟周期, 是指为单片机提供时钟脉冲信号的振荡源的周期,学习板上11.0592MHZ。
(2)状态周期: 每个状态周期为时钟周期的 2 倍, 是振荡周期经二分频后得到的。
(3)机器周期: 一个机器周期包含 6 个状态周期S1~S6, 也就是 12 个时钟周期。 在一个机器周期内, CPU可以完成一个独立的 操作。
(4)指令周期: 它是指CPU完成一条指令操作所需的全部时间, 每条指令执行时间都是有一个或几个机器周期组成。MCS - 51 系统中, 有单周期指令、双周期指令和四周期指令。
51芯片机器指令表
1-10 LED灯每1秒钟亮灭一次
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
sbit LED1 = P1^0;
uchar a;
uint b;
// 实现led模拟完1秒钟闪烁一次
void main(void){
while(1){
for(a = 5; a > 0; a--)
for(b = 11500; b > 0; b--);
LED1 = ~LED1;
}
}
for循环的语句是可以完成多级语句的嵌套的
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
sbit LED1 = P1^0;
uchar a;
uint b;
uint c;
// 实现led模拟完1秒钟闪烁一次
void main(void){
while(1){
for(c = 3; c > 0; c--)
for(a = 5; a > 0; a--)
for(b = 11500; b > 0; b--);
LED1 = ~LED1;
}
}
C语言函数的写法
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
sbit LED1 = P1^0;
// 函数声明
void Delay();
void main(void){
while(1){
LED1 = ~LED1;
Delay();
}
}
// 定义延时函数
void Delay(void){
uint i ,j;
for(i = 5; i > 0; i--){
for(j = 11500; j > 0; j--){
}
}
}
注:全局变量的生命周期和程序的生命周期一致,程序运行时创建,程序销毁时销毁,局部变量的生命周期在函数创建的时候创建出函数运行结束后会被销毁。
【带参数的子函数】
#include <REGX52.H>
/*
c 语言中的宏定义
*/
#define uint unsigned int
#define uchar unsigned char
uint i ,j;
sbit LED1 = P1^0;
// 函数声明
void Delay(uint c);
void main(void){
while(1){
LED1 = ~LED1;
Delay(10);
}
}
// 定义延时函数
void Delay(uint c){
for(i = c; i > 0; i--){
for(j = 11500; j > 0; j--){
}
}
}
1-11 LED流水灯实现
C 语言使用模块化编程实现流水灯
main 函数
LED.C文件
#include <REGX52.H>
#include "Delay.h"
#define uint unsigned int
#define uchar unsigned char
uchar table[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;
sbit LED5 = P1^4;
sbit LED6 = P1^5;
sbit LED7 = P1^6;
sbit LED8 = P1^7;
// 初始化LED灯
void LED_Init(void){
uchar i = 0;
uchar length = sizeof(table)/sizeof(table[0]);
while(1){
for(i = 0; i < length; i++){
P1 = table[i];
Delay(10);
}
}
}
LED.h文件
#ifndef __LED_H_
#define __LED_H_
void LED_Init(void);
#endif
延时函数文件Delay.c
#include <REGX52.H>
#include <stdio.h>
#include <stdint.h>
// 定义延时函数
void Delay(unsigned int c){
unsigned int i ,j;
for(i = c; i > 0; i--){
for(j = 11500; j > 0; j--){
}
}
}
延时函数头文件delay.h
#ifndef __DELAY_H_
#define __DELAY_H_
void Delay(unsigned int c);
#endif
注:
1-12 单片机位运算
逻辑左移
x << n 把x中的每一位向左平移N为**,右边空位补0,左边移出的数丢弃**,写法a = a << 2, b = a << 3, c = a << b;
注:逻辑左移的本质是“作用于二进制的数”二进制位向左边移动,然后右边补0
逻辑右移“作用于无符号数”
x>>n 把x中的每一位向右平移n位,当x为有符号数时,**左边空位补符号上的值,称为算术移位**;当x为无符号数时,左边空位补0,称为逻辑移位;右边移出的数丢弃。
写法:a=a >> 2;b=a >> 3;c=a >> b;
注:本质上是往右边移动,然后左边补0
逻辑右移
算数右移“作用于有符号数”
注:算数右移的本质是往右边移动,然后左边补符号位,1 表示位负数,0表示位正数
代码案例
【逻辑右移-----------> 右移一位左边补0】
【算术右移】
【位运算实现流水灯】
#include <REGX52.H>
#define uchar unsigned char
#define uint unsigned int
// 函数声明
void delay(uint c);
void left_Move(void);
void left_moveNum(void);
void main(void){
// left_Move();
left_moveNum();
while(1){
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
void left_Move(void){
uint i ,j;
while(1){
j = 0x01; // 0000 0001
for(i = 0; i < 8; i++){
P1 = ~j; // 1111 1110
j = j << 1; // 0000 0010
delay(1000);
}
}
}
// 实现流水灯左边移动三次,右边移动三次
void left_moveNum(void){
uint i ,j,k;
while(1){
for(k = 3; k > 0; k --){
j = 0x01; // 0000 0001
for(i = 0; i < 8; i++){
P1 = ~j; // 1111 1110
j = j << 1; // 0000 0010
delay(1000);
}
}
for(k = 3; k > 0; k --){
j = 0x80; // 1000 0000
for(i = 0; i < 8; i++){
P1 = ~j; // 1111 1110
j = j >> 1; // 0000 0010
delay(1000);
}
}
}
}
1-13 51单片机库函数使用
【带返回值函数编写】
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
uint Sum(uchar i,uchar j);
void main(void){
Sum(10,20);
while(1){
}
}
// 延时函数,没有返回值的函数
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
uint Sum(uchar i,uchar j){
uint k = 0;
k = i + j;
// return k 表示返回的值是uint 类型的数据
return k;
}
通过LED灯的亮灭情况来判断程序得到的结果
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
uint Sum(uchar i,uchar j);
void main(void){
// 00000001 + 00001110 = 0000 1111
P1 = Sum(1,14);
while(1);
}
// 延时函数,没有返回值的函数
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
uint Sum(uchar i,uchar j){
uint k = 0;
k = i + j;
// return k 表示返回的值是uint 类型的数据
return k;
}
【51单片机库函数的使用】
CROL 循环左移函数 , CROR 循环右移,
自己多研究C语言中的库函数
C51单片机的库函数
#include <REGX52.H>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
void leftMove(void);
void main(void){
leftMove();
while(1){
};
}
// 延时函数,没有返回值的函数
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
void leftMove(void){
P1 = 0xfe; // 1111 1110
while(1){
delay(500);
// 循环右移 _cror_(P1,1);
P1 = _crol_(P1,1);
}
}
51单片机的IO口结构
上电默认状态:
P0:开漏输出,需加上拉电阻才可正常使用
P1-3:标准输入输出,弱上拉模式
手册中虽有多种模式,但为了兼容传统8051单片机
结构,均不考虑。
1-14 锁存器工作原理
74HC573数字逻辑器件,高电平,低电平,高阻态
OE -----------------------> output enable 输出使能,低电平有效
序号:2-9 也就是 1D - 8D是这个锁存器的输入端
10 是GND接地
11 : LE 对应的数锁存端
序号:12 -19 每一路输入对应的输出
20 号引脚对应的就是电源 VCC
OE 在使能的情况下 -------------> LE 给一个高电平电源处于直通状态,LE处于低电平的时候锁住上层的信号,1编程0叫做锁存,由高电平跳变到低电平叫做下降沿。
不同的封装模式
单片机的真值表:学会真值表
【当OE时低电平也就是使能的状态下LE给一个高电平,就是直通的,如果LE是电平的时候,会锁住上次的信号,锁存住之后,无论输入信号如何变换,输出信号都不会改变】
OE -----------> 表示低电平有效,当OE处于高电平的时候芯片几乎是没有效果的,输出端为Z表示处于未知的状态
在OE为低电平的时候 LE是锁存端------> 为H电平,输入信号D是H电平,那么输出信号也是高电平,输入的信号是低电平,输出的信号也是低电平,LE锁存端为L ---------> 低电平的时候 输入数据-----------> 输出的数据锁存Q0表示上一个状态的数据
锁存器的使用
可以使用锁存器将发光的二极管锁住段选和位选:这里用锁存器的案例锁住led灯的亮灭
#include <REGX52.H>
#include <intrins.h>
#include "delay.h"
#include "74H573.h"
void main(void){
P1 = 0xf0; // 点亮4个发光二极管 1111 0000
delay(2000);
// 单片机上电默认是高电平,使用锁存器进行锁存给出低电平
P2_6 = 0; // 由于不同的单片机产品不同led没有锁存器模块没有出现预想的效果
P1 = 0xff; // 关闭发光二级管
while(1){
};
}
1-15 51 单片机外设
无源蜂鸣器,特点给一个电压就会发声,有一定频率的脉冲才会工作
蜂鸣器的工作原理
原理图:PNP型的三极管,给一个低电平然后蜂鸣器导通
蜂鸣器:频率是和音调对应的,和声音大小无关
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
// 位定义
sbit beep = P2^3;
// 函数声明
void delay(uint c);
void main(void){
uint i;
for(i = 2000; i > 0; i--){
beep = ~beep;
delay(15);
}
for(i = 1000; i > 0; i--){
beep = ~beep;
delay(20);
}
for(i = 1000; i > 0; i--){
beep = ~beep;
delay(25);
}
while(1);
// beep = 0;
// delay(1000);
// beep = 1;
// delay(1000);
// beep = 0;
// delay(1000);
// beep = 0;
// delay(1000);
// beep = 1;
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 11; b > 0; b --){
}
}
}
1-16 51单片机数码管显示
51 单片机最小系统
1: 电源,2:复位电路,3:晶振,4:如果要使用P0口,必须加上4.7k-10k的上拉电阻,如果要下载程序,必须要和电脑连接的串口下载通道。
数码管的显示原理:
【数码管原理图】
接地的是共阴级数码管,接电源是共阳极数码管
共阴极数码管显示表
数码管的位选,段选,想要数码管亮的时候就先要选择数码管的位,然后在去操作数码管的短,操作锁存器。
【eg1:静态方式点亮数码管】
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
// 函数声明
void delay(uint c);
void main(void){
// 打开第一个数码管的位选
wei_chose = 1;
P0 = 0x00; // 选中8位数码管全部显示为0
wei_chose = 0;
// 第一个数码管的段选,显示0
duan_chose = 1;
P0 = 0x3f; // 0011 1111
duan_chose = 0;
while(1){
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
【eg2: 数码管的静态显示】
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
// 函数声明
void delay(uint c);
void main(void){
// 打开第一个数码管的位选
wei_chose = 1;
P0 = 0x00; // 选中8位数码管全部显示为0
wei_chose = 0;
// 第一个数码管的段选,显示0
duan_chose = 1;
P0 = 0x3f; // 0011 1111
duan_chose = 0;
delay(1000);
// 打开第一个数码管的位选
duan_chose = 1;
P0 = 0x06; // 0011 1111
duan_chose = 0;
delay(1000);
duan_chose = 1;
P0 = 0x5b; // 0011 1111
duan_chose = 0;
delay(1000);
duan_chose = 1;
P0 = 0x4f; // 0011 1111
duan_chose = 0;
delay(1000);
duan_chose = 1;
P0 = 0x66; // 0011 1111
duan_chose = 0;
delay(1000);
duan_chose = 1;
P0 = 0x6d; // 0011 1111
duan_chose = 0;
delay(1000);
while(1){
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
【eg3:动态方式显示数码管】
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
// 函数声明
void delay(uint c);
void main(void){
while(1){
wei_chose = 1;
P0 = 0xfe;
wei_chose = 0;
duan_chose = 1;
P0 = 0x3f;
duan_chose = 0;
delay(1000);
wei_chose = 1;
P0 = 0xfd;
wei_chose = 0;
duan_chose = 1;
P0 = 0x06;
duan_chose = 0;
delay(1000);
wei_chose = 1;
P0 = 0xfb;
wei_chose = 0;
duan_chose = 1;
P0 = 0x5b;
duan_chose = 0;
delay(1000);
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
【eg4:动态方式显示数码管---> 数组的使用】
数组的标准写法:unsigned char table[] = {a,b,c,d...};
unsigned char : 数组内元素的数据类型
table : 表示数组的名称,可以自己定义
数组下标:数组的下标是从0开始的
unsigned char table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
注意:当写成unsigned char code table[]={};时,告诉编译器,将此数组存在程序存储区,而不是放在SRAM中。还有二维数组,多维数组知识。
【数码管的动态扫描】
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
#include <intrins.h>
/*
数组的定义要放在函数声明之前,
在编写程序的过程中遇到可重复性的程序优先考虑for循环
*/
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
// 函数声明
void delay(uint c);
#if 0
按位左移:1111 1110 按位左移
1--> 1111 1100
2--> 1111 1000
......
按位右移: 1111 1110 按位右移
1--> 0111 1111
2--> 0011 1111
算数右移: 1111 0000
1--> 1111 1000
2--> 1111 1100
如果 1111 1110 取反 0000 0001 << 1 == 0000 0010 ~(0000 0010) = 1111 1101
#endif
void main(void){
uchar length = sizeof(table)/sizeof(table[0]);
uchar i;
uchar temp;
temp = 0xfe;
// temp = 0x80;
for(i = 0; i < length; i++){
wei_chose = 1;
P0 = temp;
// temp = ~(temp);
// temp = ~(temp << 1);
// 循环左移
temp =_crol_(temp,1);
wei_chose = 0;
duan_chose = 1;
P0 = table[i];
duan_chose = 0;
delay(1000);
}
while(1){
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
【数码管算数运算和取余运算符】
算数运算符:+ - * / %
特别说明:
/ 除法,求商,对于整型和字符型变量,只得出整数的商,小数抛弃。
% 求余数,简称:求余,对于整型和字符型变量,只得出余数部分,商抛弃
优先级:* / % 同级,优先于+ -, + - 同级
如: 3/2=1, 3%2=1,
15/7=2, 18%7=4,
将123中的三个数分别分离出来
123/100=1, 123/10%10=2, 123%10=3,
【单目运算符的优先级最高】
【数码管显示子函数的编写】
C语言中求个位,10位,百位,千位数的方法
个 位----------------------------> 数 / 1 %10
100位----------------------------> 数 / 10 0%10
1000位----------------------------> 数 / 1 000%10
10000位----------------------------> 数 / 1 0000%10
#include<stdio.h>
int main(){
int n = 123456;
int unitPlace = n / 1 % 10;
int tenPlace = n / 10 % 10;
int hundredPlace = n / 100 % 10;
int thousandPlace = n / 1000 % 10;
printf("个位:%d\n十位:%d\n百位:%d\n千位:%d\n", unitPlace, tenPlace, hundredPlace, thousandPlace);
getchar();
return 0;
}
【数码管显示123】
#include <REGX52.H>
#include <intrins.h>
#include "Delay.h"
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
// 函数声明
void delay(uint c);
void Nixie(uchar wan,uchar qian,uchar bai);
void main(void){
uchar temp;
temp = 123;
while(1){
Nixie(temp/100%10,temp/10%10,temp/1%10);
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
void Nixie(uchar wan,uchar qian,uchar bai){
wei_chose = 1;
P0 = 0xfe;
wei_chose = 0;
duan_chose = 1;
P0 = table[wan];
duan_chose = 0;
delay(2);
wei_chose = 1;
P0 = 0xfd;
wei_chose = 0;
duan_chose = 1;
P0 = table[qian];
duan_chose = 0;
delay(2);
wei_chose = 1;
P0 = 0xfb;
wei_chose = 0;
duan_chose = 1;
P0 = table[bai];
duan_chose = 0;
delay(2);
}
【数码管显示特定的数】
#include <REGX52.H>
#include <intrins.h>
#include "Delay.h"
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
// 函数声明
void delay(uint c);
void Nixie(uint shu);
void main(void){
uchar temp;
temp = 123;
while(1){
Nixie(789);
}
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
void Nixie(uint shu){
wei_chose = 1;
P0 = 0xfe;
wei_chose = 0;P0 = 0;
duan_chose = 1;
P0 = table[shu /100];
duan_chose = 0;
delay(2);
wei_chose = 1;
P0 = 0xfd;
wei_chose = 0; P0 = 0;
duan_chose = 1;
P0 = table[shu /10%10];
duan_chose = 0;
delay(2);
wei_chose = 1;
P0 = 0xfb;
wei_chose = 0;P0 = 0;
duan_chose = 1;
P0 = table[shu /1%10];
duan_chose = 0;
delay(2);
}
1-17 51单片机中断概念
中断和定时器-中断的概念
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
中断优先级∶当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套︰当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
注:更高优先级的中断可以打断低优先级的中断,这种中断的处理方式是抢占式的。
1-17.1 相同优先级查询次序
当同一个优先级的中断产生多个时,有中断优先权排队的问题,由中断系统硬件确定自然优先级决定哪一个中断优先响应。
中断的响应条件
- 1:有中断源。
- 2:此中断源的中断允许位为1。
- 3:CPU开启系统总中断EA。
以上三条同时满足时,CPU才响应中断
1-17.2 中断触发方式
中断允许寄存器
1-17.3 中断函数实操
模版
void 函数名()interrupt 中断号{
内部中断服务函数
}
注:低电平的中断触发方式:如果中断不退出,程序会一直处于中断方式,打断主程序的执行。以下为外部中断引起蜂鸣器发声
#include <REGX52.H>
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
#define beep P2_3
#include <intrins.h>
/*
数组的定义要放在函数声明之前,
在编写程序的过程中遇到可重复性的程序优先考虑for循环
*/
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
// 函数声明
void delay(uint c);
uchar length = sizeof(table)/sizeof(table[0]);
uchar i;
uchar temp;
void main(void){
// 打开系统总中断
EA = 1;
// 外部中断1
EX0 = 1;
// 中断触发方式,下降沿触发
IT0 = 1;
temp = 0xfe;
while(1){
for(i = 0; i < length; i++){
wei_chose = 1;
P0 = temp;
// 循环左移
temp =_crol_(temp,1);
wei_chose = 0;
duan_chose = 1;
P0 = table[i];
duan_chose = 0;
delay(1000);
}
}
}
/*
外部中断0,进入中断函数触发蜂鸣器发声
*/
void Int0() interrupt 0{
beep = 0;
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
C 语言知识点补充
关系运算符
通过关系运算符将两个操作数连接起来的表达式称为关系表达式,用来表达一个判断条件的真与假,也就是要表达这个式子成立还是不成立,如果成立就是真,不成立就是假。
特别注意:关系表达式是一个运算,它们输出的结果只有一个,真/假,1/0, 成立/不成立
当我们在处理程序的时候,脑子里要加上一个问号,如:a<b,要问:a小于b成立吗?成立为真,不成立为假。再如1!=2,要问:1不等于2吗?对的,1确实不等于2,所以它是真的。
条件判断语句
1-18 定时器中断
定时器和计数器0和1的控制寄存器
定时控制寄存器解读
定时器初始值计算:重要
51 单片机时钟周期回顾
- 1: 一个震荡周期(时钟周期)就是一个高低电平的起伏变化
- 2:一个状态周期等于2个时钟周期
- 3:6个状态周期等于1个机器周期
- 4:一个机器周期等于12个时钟周期,一个机器周期内CPU可以完成一个独立操作
- 5:一个或多个机器周期组成1个指令周期
周期之间的关系如下图所示:
(1)振荡周期: 也称时钟周期, 是指为单片机提供时钟脉冲信号的振荡源的周期,学习板上11.0592MHZ。
(2)状态周期: 每个状态周期为时钟周期的 2 倍, 是振荡周期经二分频后得到的。
(3)机器周期: 一个机器周期包含 6 个状态周期S1~S6, 也就是 12 个时钟周期。 在一个机器周期内, CPU可以完成一个独立的操作。
(4)指令周期: 它是指CPU完成一条指令操作所需的全部时间, 每条指令执行时间都是有一个或几个机器周期组成。MCS - 51 系统中, 有单周期指令、双周期指令和四周期指令。
定时器初始值计算
eg 1: 例如:让定时器50ms产生一次中断,以11.0598M的晶振时钟为例子
一个机器周期等于12个时钟周期,因此11.0592M的晶振计算每一个时钟周期
1 / 11059200 = 0.09us
然后一个机器周期就是12个时钟周期那么CPU完成一次加减需要的时间是,(就是每增加一个数消耗的时间为1.08us)
12 * 0.09us = 1.08us
50ms 需要加减的数是 50000/1.08US = 19240
有以下的两种写法,这是第一种写法
TH0 = (65535- 50000)/ 256
TL0 = (65535- 50000)% 256
第二种写法
TH0 = (19240)/ 256
TL0 = (19240)% 256
如下图所示
注:
每个机器周期是12个时钟周期,
如果是11.0592M晶振,每个时钟周期为:1/11059200=0.09us,
每个机器周期为:12*0.09=1.08us,即每加一个数消耗的时间为:1.08us
50ms需要加多少个数呢?50000/1.08=46296
50ms的初值计算方法:
初值=65536-46296=19240
TH0=19240/256 TL0=19240%256
12M晶振的计算方法
注:计数器初始值的计算方法
从0加满需要65535个机器周期,再加1个后溢出,产生中断,共65536个机器周期
每个机器周期是12个时钟周期,
如果是12M晶振,每个时钟周期为:1/12000000=0.083us,
每个机器周期为:12*0.083=1us,即每加一个数消耗的时间为:1us
如果TH0和TL0初值都为0,那加满一次需要耗时65536*1us=65.536ms
为了方便计时,我们通常取整数,如:一次中断耗时50ms,20次中断即1秒
50ms的初值计算方法:
初值=65536-50000=15536,
TH0=15536/256 TL0=15536%256
TH0=(65536-50000)/256 TL0=(65536-50000)%256
定时器实操
定时器使用流程:
-
1:TMOD 确定TO和T1的工作方式
-
2:计算初始值,写入TL和TH寄存器
-
3:开启总中断,开启定时器中断,启动定时器
-
4:编写中断函数,在中断函数中赋初始值
-
5:处理其它事务的程序
注:中断服务函数当中尽量不要编写太多的语句,可以在主程序当中做的事情不要在中断中去处理
#include <REGX52.H>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
#define beep P2_3
/*
数组的定义要放在函数声明之前,
在编写程序的过程中遇到可重复性的程序优先考虑for循环
*/
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
// 函数声明
void delay(uint c);
/*
全局变量
*/
uchar length = sizeof(table)/sizeof(table[0]);
uchar i;
uchar temp;
uint num;
#if 0
******************* 定时器初始值的计算方式**********************
********************定时器周期之间转化关系对应*******************
注: 1个时钟周期(震荡周期 )为一个引脚电平的起伏变化
2个时钟周期等于一个状态周期
6个状态周期等于1个机器周期
1个机器周期等于12个时钟周期
一个指令周期由一个或多个机器周期组成
故而:计算初始值的方式为
************11.0592M的时钟频率计算初始值*************************
每个时钟周期= 1 / 11059200 = 0.09us
每个机器周期为 = 12 * 0.09 = 1.08us,每消耗一个数为1.08us
每50毫秒中断一次需要消耗的时间 ,毫秒转换为us 50ms = 50000
50000us /1.08us = 46296;
然后计数的时间是:
TH0 = (65536-46296)/256
TL0 = (65536-46296)%256
#endif
void main(void){
// 配置定时器0工作模式1
TMOD = 0x01;
// 计算定时器的初始值,配置TL0 和TH0 11.0592M
TH0 = (65535 - 46296)/256;
TL0 = (65535 - 46296)%256;
// 打开系统总中断
EA = 1;
// 打开定时器0的中断
ET0 = 1;
//启动定时器 0
TR0 = 1;
while(1){
if(num == 20){
// 对变量自增的时候要做清除
num = 0;
// 取反 1 = 0, 0 = 1
P1 = ~P1;
}
}
}
/*
编写中断函数,在中断函数中继续编写初始值
*/
void Int0() interrupt 1{
// 赋初始值
TH0 = (65535 - 46296)/256;
TL0 = (65535 - 46296)%256;
num++;
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
定时器1和定时器2同时使用
#include <REGX52.H>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
// 宏定义配置段选和位选
#define duan_chose P2_6
#define wei_chose P2_7
#define beep P2_3
/*
数组的定义要放在函数声明之前,
在编写程序的过程中遇到可重复性的程序优先考虑for循环
*/
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
// 函数声明
void delay(uint c);
/*
全局变量
*/
uchar length = sizeof(table)/sizeof(table[0]);
uchar i;
uchar temp;
uint num,num1;
#if 0
******************* 定时器初始值的计算方式**********************
********************定时器周期之间转化关系对应*******************
注: 1个时钟周期(震荡周期 )为一个引脚电平的起伏变化
2个时钟周期等于一个状态周期
6个状态周期等于1个机器周期
1个机器周期等于12个时钟周期
一个指令周期由一个或多个机器周期组成
故而:计算初始值的方式为
************11.0592M的时钟频率计算初始值*************************
每个时钟周期= 1 / 11059200 = 0.09us
每个机器周期为 = 12 * 0.09 = 1.08us,每消耗一个数为1.08us
每50毫秒中断一次需要消耗的时间 ,毫秒转换为us 50ms = 50000
50000us /1.08us = 46296;
然后计数的时间是:
TH0 = (65536-46296)/256
TL0 = (65536-46296)%256
#endif
void main(void){
// 配置定时器0和定时器1的工作模式1
TMOD = 0x11;
// 计算定时器的初始值,配置TL0 和TH0 11.0592M
TH0 = (65535 - 46296)/256;
TL0 = (65535 - 46296)%256;
// 计算定时器1的初始值
TH1 = (65535 - 50000)/256;
TL1 = (65535 - 50000)%256;
// 打开系统总中断
EA = 1;
// 打开定时器0和定时器1中断
ET0 = 1;
ET1 = 1;
//启动定时器 0 启动定时器1
TR0 = 1;
TR1 = 1;
while(1){
if(num == 20){
// 对变量自增的时候要做清除
num = 0;
// 取反 1 = 0, 0 = 1
P1 = ~P1;
}
if(num1 == 10){
num1 = 0;
beep = ~beep;
}
}
}
/*
编写中断函数,在中断函数中继续编写初始值
*/
void Timer0() interrupt 1{
// 赋初始值
TH0 = (65535 - 46296)/256;
TL0 = (65535 - 46296)%256;
num++;
}
void Timer1() interrupt 3{
// 赋初始值
TH1 = (65535 - 46296)/256;
TL1 = (65535 - 46296)%256;
num1++;
}
void delay(uint c){
uint a,b;
for(a = c; a > 0; a--){
for(b = 115; b > 0; b --){
}
}
}
1- 19 51单片机按键检测
线与--------------同0与位0,同1与为1
独立按键检测低电平
【按键原理图】
AD采样法检测按键
模拟开关CD4051或4D4067检测
专用芯片检测按键
【逻辑运算符】
-------------------------也称布尔运算-------------------------,结果只有两个值,1或0,真或假,
逻辑与:&& 只有两个操作数都为真,结果才为真,否则为假
【用于判断两个条件必须同时成立时】
逻辑或:|| 只要有一个操作数为真,结果就为真,两个都为假,结果为假
【用于判断或者,或者条件时】
逻辑非:! 取反,非0就是真,非真就是假
优先级别:!>&&>||
混合运算时:!>算术运算>关系运算>&&>||>赋值运算
主要用于if和while语句中
【查询法实现独立按键检测】
按键被按下时存在抖动现象,需要给一个延时函数对按键进行消除抖动,同时可以添加一个while循环做按键松手检测
独立按键检测
#include <REGX52.H>
#include <stdint.h>
#include "delay.h"
/*宏定义按键检测*/
#define KEY1 P3_0
#define KEY2 P3_1
#define KEY3 P3_2
#define KEY4 P3_3
/*led重命名*/
#define LED1 P1_0
#define LED2 P1_1
#define LED3 P1_2
#define LED4 P1_3
/*初始化按键*/
void Init_Key(void){
/*检测按键是否被按下*/
while(1){
/*第一个按键被按下*/
if(KEY1 == 0){
delay(20);
/*按键被按下,延时消除抖动*/
while(KEY1 == 0);
LED1 = ~LED1;
delay(20);
}
/*第二个按键被按下*/
if(KEY2 == 0){
delay(20);
/*按键被按下,延时消除抖动*/
while(KEY2 == 0);
LED2 = ~LED2;
delay(20);
}
/*第三个按键被按下*/
if(KEY3 == 0){
delay(20);
/*按键被按下,延时消除抖动*/
while(KEY3 == 0);
LED3 = ~LED3;
delay(20);
}
/*第三个按键被按下*/
if(KEY4 == 0){
delay(20);
/*按键被按下,延时消除抖动*/
while(KEY4 == 0);
LED4 = ~LED4;
delay(20);
}
}
}
【独立按键组合使用:两个按键同时按下翻转LED灯】
#include <REGX52.H>
#include <stdint.h>
#include "delay.h"
/*宏定义按键检测*/
#define KEY1 P3_0
#define KEY2 P3_1
#define KEY3 P3_2
#define KEY4 P3_3
/*led重命名*/
#define LED1 P1_0
#define LED2 P1_1
#define LED3 P1_2
#define LED4 P1_3
/*初始化按键*/
void Init_Key(void){
/*检测按键是否被按下*/
while(1){
/*第一个按键被按下*/
if(KEY1 == 0){
delay(20);
/*按键被按下,延时消除抖动*/
while(KEY1 == 0);
/*
while(!KEY1);
*/
LED1 = ~LED1;
delay(20);
}
/*第二个按键被按下*/
if(KEY2 == 0){
delay(20);
/*按键被按下,延时消除抖动*/
while(KEY2 == 0);
/*
while(!KEY2);
*/
LED2 = ~LED2;
delay(20);
}
/*第三个按键被按下*/
if(!KEY3 && !KEY4){
delay(20);
if(!KEY3 && !KEY4){
while(!(KEY3 && KEY4))
P1 = ~P1;
delay(10);
}
}
}
}
中断函数控制矩阵键盘中的一位
#include <REGX52.H>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define dula P2_6
#define wela P2_7
#define P32 P3_2
#define P34 P3_4
uint num,num1,dis,key;
uchar code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void DelayMs(uint c);
void Display(uint shu);
void Init();
void main ()
{
P34=0;
Init();
while(1)
{
if(key==1)
{
while(!P32)
Display(num);
key=0;
num++;
}
Display(num);
}
}
void Init()
{
EA=1;//打开总中断
EX0=1;//打开外部中断0
IT0=1;//设置中断类型为下降沿
}
void Int0() interrupt 0
{
key=1;
}
void DelayMs(uint c)
{
uint a,b;
for(a=c;a>0;a--)
for(b=115;b>0;b--);
}
void Display(uint shu)
{
wela=1;
P0=0xfe;//11111110 打开第一个数码管的位选11000000
wela=0;P0=0;
dula=1;
P0=table[shu/100];//显示0
dula=0;
DelayMs(2);
wela=1;
P0=0xfd;//11111101 打开第2个数码管的位选11000000
wela=0;P0=0;
dula=1;
P0=table[shu/10%10];//显示0
dula=0;
DelayMs(2);
wela=1;
P0=0xfb;//11111011 打开第3个数码管的位选11000000
wela=0;P0=0;
dula=1;
P0=table[shu%10];//显示0
dula=0;
DelayMs(2);
}
【C语言的Switch Case语句】
矩阵键盘扫描
注:在进行矩阵键盘扫描的时候一次只能给一条行线低电平,或者给列线低电平
1-20 串口通讯
1-21 IIC通讯
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)