超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序

本文在keil5的环境下编写代码,在stc-isp软件的辅助下理解合编写51单片机的代码,这两个软件的安装很多资源都有,这里就不过多赘述
这里我们采用模块化的编程 2023/7/10完成文章
本文采用的资料代码有部分来自于

本文目录

  • 点亮一个LED
    • 单片机内部LED的结构
    • 代码
  • Delay函数
    • stc-isp
      • Delay模块
    • LED闪烁
    • LED流水灯
  • 独立按键
    • 单片机内部独立按键的结构
    • 独立按键控制LED亮灭
      • 按键消抖
    • 独立按键控制LED移位
  • 数码管
    • 单片机内部数码管的结构
      • 段选
      • 位选
    • 数码管模块
      • bug解决方案
      • 模块代码
        • 数码管的消影
    • 动态化显示数码管
  • 矩阵键盘
    • 单片机内部矩阵键盘的结构
      • 矩阵键盘如何将我们摁下去的转换为地址信号
    • 矩阵键盘模块
    • 数码管显示矩阵键盘按键
    • 矩阵键盘密码锁
  • 定时器方式一 (重难点)
    • 定时器的概念以及用处
    • 单片机内部定时器的使用
      • 启动定时器
      • 机器周期
      • 初值寄存器
      • 自动加一
      • 计满溢出
      • 改变定时时长
    • 定时器的编程
      • 初始化
        • TMOD(工作方式寄存器)
          • GATE位
          • C/T位
          • M1 M0 位
      • 置初值
      • 启动
      • 判断溢出
      • 重置初值
      • 清理溢出
    • 定时器流水灯
  • 定时器方式二
    • 方式一和方式二的不同之处
  • 定时器中断系统(重难点)
    • 单片机内部中断系统的工作准则
      • 中断源
    • 中断允许寄存器
    • 中断请求的形式 & 中断请求标志位
    • 中断响应
    • 中断服务程序
    • 定时器+中断系统完成流水灯
  • 中断系统的外部中断(重难点)
    • 外部中断的硬件准备
    • 使用外部中断的关键点
      • 中断允许
      • 中断请求
        • 下降沿触发和低电平触发
      • 中断服务程序
    • 中断系统的优先级
      • 修改中断优先级
  • 串口通信
    • 串口通信基础知识
      • 通信的概念
      • 通信的分类
      • 通信协议
        • 硬件层协议
        • 软件层协议
    • 串行通信的分类以及波特率
      • 按传输方向分类
        • 单工
        • 半双工
        • 全双工
      • 按同步时钟信号分类
        • 同步通信
        • 异步通信
      • 传输速度 —— 波特率
    • 单片机的UART串口
      • 梳理概念
        • TI和RI
        • 串口控制寄存器——SCON
          • SM0和SM1
          • REN
          • TB8 和 RB8
        • 串口数据缓冲寄存器——SBUF寄存器
        • 波特率发生器
      • 程序的编写

点亮一个LED

我们在写单片机的程序之前,先要了解单片机内部硬件的引脚,给这些引脚用代码进行初始化,我们就可以完成单片机的编程

单片机内部LED的结构

超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第1张图片
从这个图片可以看出,51单片机的LED是一排共阳极的二极管,并且他们每个都有自己的引脚。
例如D1灯所对应的引脚是P20;D2灯所对应的引脚是P21…以此类推

于是我们可以总结出来:想要点亮哪个LED灯,就把哪个LED灯的引脚赋值为低电平即可

代码

#include   //这个相当于引入标准的C51单片机库

void main()
{
	P2_0 = 0; P2_1 = 1; P2_2 = 1; P2_3 = 1;
	P2_4 = 1; P2_5 = 1; P2_6 = 1; P2_7 = 1;
}

这样的书写虽然可以完成我们的目的,但是过于麻烦了,我们可以看出这里的所有引脚都是P2开头的,我们可以直接对P2进行整体的赋值

void main()
{
	P2 = 0xFE // 1111 1110 
	//P20是最低位
}

Delay函数

单片机程序中,经常需要做一个延迟的事情,于是我们就需要Delay延迟函数

stc-isp

由上面可以知道,此时我们需要一个延迟函数,无非就是利用循环进行消磨时间,这种事情其实并不需要我们自己编写,stc-isp软件可以帮助我们完成这个函数的书写
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第2张图片
找到这个软件的软件延时定时器,将系统频率合定时长度调整成为我们需要的

Delay模块

每次都要打开这个软件我们太麻烦了,于是我们修改这个代码,给Delay函数增加一个参数,让他每次可以根据我们的传参产生不同的延迟,单位是毫秒

void Delay(int ms)		//@12.000MHz
{
	unsigned char i, j;
	while(ms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

这样我们就完成了Delay模块的编写,以后我们需要这个模块就会直接调用,不会再多解释

LED闪烁

结合上面的Delay模块

如果我们需要完成一个每过500ms就闪烁一次的LED灯,我们应该怎么编写代码

void main()
{
	while(1)
	{
		P2 = 0xFE;
		Delay(500);
		P2 = 0xFF; // 1111 1111
	}
}

LED流水灯

如果我们需要完成一个每过500ms变化一次的流水灯,我们应该怎么编写代码

结合闪烁LED灯的知识,我们只需要让每次亮的灯发生变化,我们就完成了LED流水灯

void main()
{
	while(1)
	{
		P2=0xFE;//1111 1110
		Delay(500);
		P2=0xFD;//1111 1101
		Delay(500);
		P2=0xFB;//1111 1011
		Delay(500);
		P2=0xF7;//1111 0111
		Delay(500);
		P2=0xEF;//1110 1111
		Delay(500);
		P2=0xDF;//1101 1111
		Delay(500);
		P2=0xBF;//1011 1111
		Delay(500);
		P2=0x7F;//0111 1111
		Delay(500);
	}
}

独立按键

单片机内部独立按键的结构

超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第3张图片
从这个图片我们可以看出,51单片机内部的每个独立按键都是共阴极的独立按键,所以我们如果要使用这个独立按键,给它对应的引脚赋值为低电平即可(让独立按键导通)
需要注意的是:第一个独立按键对应的是P31,第二个独立按键对应的是P30

独立按键控制LED亮灭

如果我们需要用第一个独立按键控制第一个LED的亮灭,我们应该怎么编写程序

void main()
{
	while(1)
	{
		if(P3_1 == 0)
		{
			P2 = 0xFE;
		}
		else
		{
			P2 = 0xFF;
		}
	}
}

按键消抖

在我们按下按键的时候,由于我们的手的抖动,按键的电平也会随之发生抖动,如下图:
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第4张图片
所以我们需要进行按键消抖,具体的方法就是在我们按下按键和松下按键的时候进行延迟

Delay(20);
while(P3_1 == 0);
Delay(20); 

这样的按键消抖可以帮助我们做到

当我们按住按键的时候LED亮,只要松开就熄灭

void main()
{
	while(1)
	{
		if(P3_1 == 0)
		{
			Delay(20);
			while(P3_1 == 0)
			{
				P2_0 = 0;
			}
			Delay(20);
			P2_0 = 1;
		}
	}
}

独立按键控制LED移位

当我们需要用独立按键摁一下第一个按键,LED就往下亮一个

int Now; //全局变量不初始化默认为初始为0
void main()
{
	P2 = 0xFE; // 一开始的时候默认LED1亮
	while(1)
	{	
		if(P3_1 == 0)
		{
			Delay(20);
			while(P3_1 == 0);
			Delay(20);
			Now++;
			if(Now == 8)
			{
				Now = 0;
			}
			P2 = 0xFE << Now; //移位操作
		}
	}
}

数码管

动态的数码管分为两部分
数码管的段选是控制单个具体数码管上的哪个LED管亮
数码管的位选是控制哪个数码管亮

单片机内部数码管的结构

超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第5张图片
从上面这个图可以看出51内部的数码管的段选是通过74HC245芯片完成的,74HC245实际上是一个总线驱动器,起的是一个缓冲的作用
并且51单片机内部是一个共阴极的数码管

段选

先来研究这个图里面可以看出来的段选,此时我们研究的是一个数码管单元
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第6张图片
假如我们要显示 7 这个数字,此时我们需要亮的是abc这三根LED灯管,那么就给abc赋值位1,那么abcdegf为1110000,dp由于不使用,也为0

位选

超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第7张图片
51单片机的位选和51单片机内部的74138译码器有关,根据数字电路课程的学习,我们了解到74138可以通过3位地址线控制8位的数据线
举个例子,加入ABC输入为010,转换为十进制为2,那么Y2处就会输出低电平

数码管模块

根据上面所说的,我们基于模块化编程的思想,可以把数码管的显示代码模块化
同时根据上面说的那样,其实我们不难发现我们位选选中的数码管每次只能显示同一个数字,那我们如果要让多个数码管显示不同的数字应该怎么办呢?

bug解决方案

根据人眼的视觉暂留,只要我们极快速度的扫描这8位数码管即可
具体的实现方案就是在调用数码管显示函数的时候,中间不进行延迟函数即可

模块代码

//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

//数码管显示子函数
void Nixie(unsigned char Location,Number)
{
	switch(Location)		//位码输出
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number];	//段码输出
	Delay(1); // 数码管的消影
	P0 = 0x00;// 数码管的消影
}

Nixie函数需要传入的两个参数,一个是Location,一个是Number
其中Location参数的意思是数码管的位选,并且从单片机的最左边的数码管为1号数码管,Number是数码管需要显示的数字,也就是所谓的位选

数码管的消影

我们知道数码管显示数字的过程为:
位选 段选 位选 段选 位选 段选
我们为了更好的描述这个过程,我们称上述的过程为A1 B1 A2 B2 A3 B3

由于单片机的速度过快,数码管的位选跟不上数码管的段选,那么在B1到A2的过程中就会出现一定的问题,B1的段选就会在A2的位选上面有所体现,所以我们在每一次调用Nixie函数的时候,要把该函数的段选清零,为了数码管的稳定,还要延迟1毫秒

动态化显示数码管

如果我们需要实现数码管的第一位显示1,第二位显示2,第三位显示3,那么应该怎么书写代码

void main()
{
	while(1)
	{
		Nixie(1,1);
		Nixie(2,2);
		Nixie(3,3);
	}
}

矩阵键盘

单片机内部矩阵键盘的结构

超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第8张图片
通过上面这张图片,我们可以发现矩阵键盘很巧妙的设计,就是它只用了8根地址线,就完成了对16个键的控制,具体是怎么实现的呢?我们下面分解

矩阵键盘如何将我们摁下去的转换为地址信号

这里我们举例子来回答这个问题,假如我们摁下了S14键,那么此时P1_4为低电平的同时,P1_2也为低电平,其他的都为高电平
由此可知,我们只需要用 if 结构判断,当满足条件的两个引脚全部为低电平的时候,我们就可以锁定我们摁下的是矩阵键盘的哪个键

矩阵键盘模块

unsigned char MatrixKey()
{
	unsigned char KeyNumber=0;
	
	P1=0xFF; // 复位
	P1_3=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
	
	P1=0xFF; // 复位
	P1_2=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
	
	P1=0xFF; // 复位
	P1_1=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
	
	P1=0xFF; // 复位
	P1_0=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
	
	return KeyNumber;
}

矩阵键盘模块利用了非常多的 if 语句来实现,C语言基础好的也可以用switch case语句进行改写,这里不再赘述
模块返回的KeyNumber为我们摁下的键值

数码管显示矩阵键盘按键

如果我们需要让数码管的第一位和第二位显示我们矩阵键盘所摁下的值,应该怎么书写代码

int number;
void main()
{
	while(1)
	{
		number = MatrixKey();
		if(number != 0)
		{
			if(number >= 0)
			{
				Nixie(1,number/10);
				Nixie(2,number%10);
			}
			else
			{
				Nixie(1,number);
			}
		}
	}
}

矩阵键盘密码锁

如果要我们实现一个矩阵键盘的密码锁,当密码输入正确且为1234的时候,另外四位数码管显示1111表示密码正确,如果错误显示0000

int keynumber;
int user;
int right = 1234; // 正确密码
int count = 0; // 计数器 
void main()
{
	while(1)
	{
		if(keynumber) // 当矩阵键盘有按键被按下
		{
			if(keynumber < 10) 
			{
				// 当矩阵键盘的按键被按下的值小于10 的时候才有效
				for(count=0;count<4;count++)
				{
					user = user * 10;
					user = user + Keynumber;
					Nixie(1,user/1000);
					Nixie(2,(user/100)%10);
					Nixie(3,(user/10)%10);
					Nixie(4,user%10);
				}
				if(user == 1234)
				{
					for(count=5;count<=8;count++)
					{
						Nixie(count,1);
					}
				}
				else
				{
					for(count=5;count<=8;count++)
					{
						Nixie(count,0);
					}
				}
			}
		}
	}
}

定时器方式一 (重难点)

定时器的概念以及用处

定时器是51单片机内部自带的具有定时功能的硬件
并且我们一般使用的51单片机具有两个定时器,我们以定时器T0为例,来讲解51单片机的定时器
定时器还可以用来计数,但是需要注意的是,定时器在不可以同时用来定时和计数,同一时间内,只可以使用其一种功能

单片机内部定时器的使用

51单片机内部的定时器为16位的定时器,也可以被称作双八位定时器
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第9张图片

启动定时器后,每个机器周期到来,初值寄存器自动加1,直到计满溢出

启动定时器

启动定时器的意思就是给定时器一个启动信号

机器周期

单片机的机器周期公式:机器周期 = 12 / 晶振频率
我们一般使用的STC89C52单片机是12MHZ的单片机,一个机器周期就是一微秒

初值寄存器

每个定时器都有自己专用的寄存器,比如T0定时器的寄存器就是TH0和TL0,TH0是高八位,TL0是第八位
这两个寄存器的初值都为00H

自动加一

满足一个机器周期,从地位寄存器开始加,依次加一

计满溢出

当两个寄存器加到了11111111 11111111 的时候,再加一个1就变成了1 00000000 00000000 此时被称为溢出
只要是溢出了,就说明一次定时结束
总共计算了65536个机器周期,经过了65.536微秒

改变定时时长

假设我们想让定时器定时50ms的时间,那么我们就要产生50000个机器周期
则我们要让初值为 65536 - 50000 = 15536 = 3CB0H
那么在程序的定时器初始化的时候,就必须写成:

TH0 = 0x3C; // (65536-50000)/256
TL0 = 0xB0; // (65536-50000)%256

定时器的编程

初始化

初始化的意思是在使用定时器之前,先写上一些有关的初始化信息
初始化和TMOD值有关,我们先来看看TMOD是什么玩意

TMOD(工作方式寄存器)

先来看看手册对这个的描述
在这里插入图片描述
高四位是对T1进行设置的,低四位是对T0设置的

GATE位

GATE位也被成为门控位
当GATE位为0的时候,只需要一条TR0 = 1 语句就可以启动定时器
当GATE位为1的时候,还需要和外部中断引脚INT0共同启动定时器

C/T位

C/T位为0的时候,表示使用定时器功能
C/T位为1的时候,表示使用计数器功能

M1 M0 位

这两个合在一起为工作方式的设置

M1 M0 方式
0 0 方式0
0 1 方式1
1 0 方式2
1 1 方式3

综上所述,TMOD的取值应该为0000 0001 = 0x01

TMOD = 0x01;

置初值

假设还是定时50ms

TH0 = 0x3C;
TL0 = 0xB0;

启动

启动和TCON寄存器有关,我们先来看看TCON寄存器
在这里插入图片描述
在启动方面,我们只需要控制TCON中的TR0寄存器即可
启动:TR0 = 1
停止:TR0 = 0

其他的寄存器在手册上也有解释,这里就截图放在这里了:
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第10张图片

判断溢出

判断溢出的时候,需要用到TCON中的TF寄存器,对于第一个定时器而言就是TF0
当溢出后,系统自动将TF0赋值为1
所以在这种的使用条件下,我们需要手动将TF0再次清零,保证下一次的定时器使用 手动清零:TF0 = 0

while(TF0 == 0) //当TF0溢出的时候,跳出循环,即定时的时间到

重置初值

这一个一般用于定时器的循环
还是用

TH0 = 0x3C;
TL0 = 0xB0;

清理溢出

TF0 = 0;

定时器流水灯

如果我们需要用定时器来完成一个简易的流水灯,应该如何编写代码

void main()
{
	unsigned char k,n,i;
	n = 0x01; //准备赋值给LED
	k = 0; 
	TMOD = 0x01; //TMOD寄存器
	TH0 = 0x3C;
	TL0 = 0xB0; // 定时器的初值
	TR0 = 1; // 启动定时器
	while(1)
	{
		n = 0x01;
		for(i=0;i<8;i++)
		{
			P0 = ~n;
			while(k < 10) // 定时500ms
			{
				while(TF0 == 0); // 等待定时器一次计满溢出
				TH0 = 0x3C;
				TL0 = 0xB0; // 重置初值
				TF0 = 0; // 溢出清零
				k++;
			}
		}
		k = 0;
		n = n << 1;
	}
}

定时器方式二

大体上和定时器的方式一是类似的,在上面讲解到定时器的TMOD寄存器的M1和M0参数是工作方式的设置,当M1=1,M0=0的时候,进行方式二工作

方式一和方式二的不同之处

不同之处 方式一 方式二
技术长度不同 65536 256
计数最大时间不同 65536us 256us
重置初值 溢出后需要重置初值 溢出后不需要重置初值
寄存器真正使用 TL0和TH0 TL0

方式二的寄存器自动加一,用的是低位的寄存器TL0,还需要把TH0的值赋值为TL0的值一样的值,这样在溢出后,单片机会把TH0的值重新赋值给TL0

定时器中断系统(重难点)

单片机内部中断系统的工作准则

中断是指在遇到突发事件的时候,先中止当前正在进行的工作,转而去处理突发时间,待处理完成后,再返回到原先被中止的工作处,继续进行随后的工作

中断源

中断源打断的是单片机内部现在执行的程序
查找手册我们可以发现,51单片机存在5个中断源,也就是允许五种情况来打断

中断源 中断允许标志位 中断请求的形式 中断请求标志位 中断号
外部中断INT0 EX0 P3.2下降沿或低电平 IE0 0
定时器T0 ET0 溢出 TF0 1
外部中断INT1 EX1 P3.3下降沿或低电平 IE1 2
定时器T1 ET1 溢出 TF1 3
串口 ES 接收或者发送数据结束 TX/RX 4

这里我们先研究的是定时器T0的中断情况

中断允许寄存器

在这里插入图片描述
中断允许寄存器中有八个位地址及符号,从右到左分别对应了上面的几个中断源的中断允许标志位,我们如果想在程序中使用其中的中断源,就必须在程序中把相关的位置标记为1,也就是赋值为高电平
EA为中断允许的总开关
这里讲解的是定时器T0的中断打开:

EA = 1; // 打开中断总开关
ET0 = 1;  // 关闭: ET0 = 0;

中断请求的形式 & 中断请求标志位

从上面的表可以得到对应中断源的中断请求形式和中断请求标志位
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第11张图片

中断响应

当满足了中断请求的条件之后,程序会在断点处被打断,将还未执行的程序压入中,然后跳转到中断程序
如果对栈这个概念不是很清楚的朋友们,可以跳转到下面这个文章C语言0基础入门栈和队列

中断服务程序

中断服务程序的基本格式:

void timer0() interrupt 1 //也可以是2、3、4、5
{
	...
}

中断服务程序后的 interrupt 的值就是上面的中断号
中断服务程序依靠中断号来对程序进行查找,进行中断操作

定时器+中断系统完成流水灯

如果需要我们利用定时器+中断系统来完成一个定时50ms,循环两次即100ms让点亮的灯循环左移一次

void timer0() interrupt 1 //中断服务程序
{
	TH0 = 0x3C;
	TL0 = 0xB0; // 清空定时器
	k++;
}

void main()
{
	unsigned char n,i;
	k = 0;
	TMOD = 0x01;
	TH0 = 0x3C;
	TL0 = 0xB0;
	TR0 = 1; //启动定时器
	EA = 1;
	ET0 = 1; // 启动中断系统
	while(1)
	{
		n = 0x01;
		for(i=0;i<8;i++)
		{
			P0 = ~n;
			while(k<2);
			k = 0;
			n = n << 1;
		}
	}
}

中断系统的外部中断(重难点)

单片机有很多的外设可以进行外部中断,在这里主要讲解的是利用单片机的独立按键实现的外部中断
从上面定时器中断系统的讲解中我们可以知道,外部中断INT0的请求引脚是P3_2;外部中断INT1的请求引脚是P3_3

外部中断的硬件准备

首先我们需要找到原理图上关于按键的部分
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第12张图片
从这个图我们不难发现,在四个独立按键中,只有K3和K4是P32和P33的引脚,也就是只有这两个独立按键是可以接入外部中断
所以我们需要用杜邦线把P32或者P33与单片机核心芯片上的外部中断进行连接
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第13张图片

使用外部中断的关键点

中断允许

中断源 中断允许标志位 中断请求的形式 中断请求标志位 中断号
外部中断INT0 EX0 P3.2下降沿或低电平 IE0 0
定时器T0 ET0 溢出 TF0 1
外部中断INT1 EX1 P3.3下降沿或低电平 IE1 2
定时器T1 ET1 溢出 TF1 3
串口 ES 接收或者发送数据结束 TX/RX 4

中断请求

从上表可以看出,两个外部中断请求的标志位分别为IE0和IE1
中断请求的条件是P32或者P33来低电平P3_2 = 0; 按键摁下 || 或者是下降沿
低电平触发:只要是低电平,就一直触发中断请求,在请求完后,需要手动清除中断请求标志位IE0 = 0;
下降沿触发:一个下降沿只触发一次,不需要手动清除标志位

下降沿触发和低电平触发

此时我们就需要利用到TCON寄存器
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第14张图片
从上面这个可以总结出来,当IT1和IT0为1的时候为下降沿触发
当IT1和IT0为0的时候为低电平触发

中断服务程序

外部中断的中断号分别为0和2

如果要实现利用独立按键结合外部中断法实现流水灯的启停控制,我们应该如何编写代码

sbit key = P3_2;
unsigned char k;
unsigned char led[] = {0xff,0xfe,0xfc,0xf8,0xf0;0xe0,0xc0,0x80};
void int0() interrupt 0 // 外部中断
{
	Delay(20);
	if(!key)
	{
		TR0 = ~TR0; // 启动关闭定时器
	}
}

void timer0() interrupt 1
{
	TH0 = 0x3C;
	TL0 = 0xB0;
	k++;
}

void main()
{
	unsigned int i;
	k = 0;
	TMOD = 0x01;
	TH0 = 0x3C;
	TL0 = 0xB0;
	TR0 = 1; // 初始化定时器
	EA = 1;
	ET0 = 1;
	EX0 = 1;
	IT0 = 1; // 初始化外部中断
	while(1)
	{
		for(i=0;i<8;i++)
		{
			P0 = led[i];
			while(k < 4);
			k = 0;
		}
	}
}

中断系统的优先级

当CPU同时发现有两个以及两个以上的中断提出申请,那么应该响应哪一个呢?
这个就是中断系统的优先级需要考虑的问题
单片机内部的自然优先级:INT0 > T0 > INT1 > T1 > 串口
这样的优先级会让定时器有时候存在10ms的偏差

修改中断优先级

此时我们涉及到了IP寄存器的使用
在这里插入图片描述
从右到左分别对应自然优先级的从高到低
要提高哪个位的中断优先级就要把哪个位置1
例如想要把 T0 的中断优先级提高,那么就得用PT0 = 1;

串口通信

串口通信基础知识

重点介绍通信协议

通信的概念

通信就是两个设备之间进行数据的传输

通信的分类

并行通信:在通信中数据一起传输
串行通信:在通信中数据排序传输,也就是一个个的传输

通信协议

硬件层协议

例如USB口、网口等等都是标准的通信硬件接口
例如单片机和计算机的连接,单片机使用的是UART接口,计算机使用的是USB接口
规范了通信设备间物理上的连线,传输的电平信号、传输的秩序等

软件层协议

modbus协议、TCP/IP协议等等

串行通信的分类以及波特率

按传输方向分类

串行通信按照传输方向分类可以分为单工、半双工、全双工

单工

甲方可以向乙方传输信号,但是乙方不可以向甲方传输信号
例如:遥控器、收音机

半双工

数据可以在双方之间传输,但是在同一时间内只能由一方发给另一方,不可以同一时间内双方互相传输数据
例如:对讲机

全双工

发送数据的同时也可以接收数据,同时数据可以在双方之间传输
例如:电话

按同步时钟信号分类

同步通信

时钟同步,并且格式为信息帧,通信双方的时钟引脚是相连的
信息帧:同步通信中,一次通信传输的数据被称为信息帧
同步通信的传输效率非常高

异步通信

固定的数据帧以及传输的速度双方必须相同
由于异步通信需要由一个“0”作为通信的起始位,“1”作为通信的结束位,一次一共传输10位数据,只有8位数据是有效数据位
所以异步通信的传输效率只有80%

传输速度 —— 波特率

波特率是码元的传输效率单位,定义是单位时间内传输了多少个码元,单位是bps
码元是在数据传输过程中等时出现的符号,在我们所用的单片机中,我们采用的是二进制码元,码元就是0和1
对于单片机的串口通信而言,波特率就是每秒钟传输了多少个0或1

例如:9600bps:每秒传输9600个二进制位数

单片机的UART串口

如果要实现我们给单片机传输1-8不同的数字,利用串口通信的原理,点亮对于的单片机上的LED灯,当输入其他数值的时候指示灯全灭,同时单片机向电脑发送数据15,表示数据超出了限额

梳理概念

51单片机的串口,是一个异步全双工UART串口
51的内部含有TXD和RXD两根串口引脚,分别表示发送和接收引脚
我们可以从51单片机的原理图中看到这两根引脚:
在这里插入图片描述
为了先大致的了解一下51单片机的UART串口,可以先看看其原理图(其实我也不是很看的懂,都是英文的)
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第15张图片
看不懂没有关系,下面我会给大家进行讲解

TI和RI

TI:发送中断请求标志位
RI:接收中断请求标志位
即当TI或者RI其中有一个为1的时候,就会进入到中断服务程序,并且串口的中断号为4
并且进入中断服务程序后,需要手动清零中断请求标志位,中断请求标志位不会自动清零

串口控制寄存器——SCON

上面所讲到的TI和RI就是存储在串口控制寄存器SCON中的
我们先来看看SCON的每一位都代表着啥,再来分别的解释每一位的作用:
在这里插入图片描述

SM0和SM1

这两个位被称为工作方式选择位

SM0 SM1 方式 说明 波特率
0 0 0 移位寄存器 fosc/12
0 1 1 10位UART(八位数据) 可变(常用9600)
1 0 2 11位UART(九位数据) fosc/64或fosc/32
1 1 3 11位UART(九位数据) 可变
REN

当REN位为1的时候,允许该设备接收通信数据
等于0的时候禁止该设备接收数据

TB8 和 RB8

TB8是发送校验位的
RB8是接收校验位的

串口数据缓冲寄存器——SBUF寄存器

当我们传数据给单片机的时候,数据会先暂存到SBUF
例如接收串口的数据代码为:

mydata = SBUF;

当单片机传数据给计算机的时候,数据也会先存到SBUF
例如让单片机发送15给计算机的代码为:

SBUF = 0x0E;
波特率发生器

51单片机的内部波特率发生器为内部定时器T1
例如我们要设置单片机的波特率为9600,那么也就是单片机每1/9600秒就发送或者接收一位数据,我们就可以根据这个1/9600秒来设置T1的初值,在每次时间到的时候进行溢出,并且只能用定时器的工作模式2

下面来设置T1的初值
根据下面这个公式
超详细万字解析——入门51单片机(一)/通信/中断/定时器/LED/流水灯/含程序_第16张图片
fosc为时钟的周期,可以由时钟的频率得到,也就是单片机的频率

程序的编写

串口的初始化过程:
①设置串口SCON
②设置TMOD
③设置波特率
④开中断
⑤启动波特率发生器

unsigned char jieshou;
void transport() interrupt 4
{
	if(TI)
	{
		TI = 0; //清空中断标志位
	}
	if(RI)
	{
		RI = 0; //清空中断标志位
	}
	jieshou = SBUF; // 接收串口数据
	switch(jieshou)
	{
		//...
	}
}

void main()
{
	SCON = 0x50; // 设置串口的工作方式
	TMOD = 0x20; // 设置T1 的工作方式,设置方式二的定时功能
	TH1 = 0xFD;
	TL1 = 0xFD; // 波特率的计算
	ES = 1; // 串口的中断打开
	EA = 1; //开启总开关
	TR1 = 1; // 启动定时器(波特率发生器)T1
	while(1);
}

你可能感兴趣的:(51单片机,嵌入式硬件,单片机)