一文搞懂51单片机基础

一文搞懂51单片机基础

0.基础前缀知识

C51是Intel下采用PDIP(矩形封装)封装方法的一种单片机。其内部结构并非冯诺依曼式结构。

0.1 命名规则

该种BUS结构的芯片特点是什么?

晶振元器件的作用是什么?

0.2 STC89C52芯片内部结构图:

​ 该结构的一大特点是,所有的元器件都是挂载在中线上的,接外设和其他模块联动的引脚主要是P0,P1,P2,P3,P4这四组,晶振作用则主要在于节拍器,然后才有了时钟周期,有了周期之后就能程序按照固定的周期去执行,再通过该时钟周期也能完成计时器操作。

什么是晶振?晶振在电路中的作用是什么?_晶振可以在电路中作为振荡源使用吗-CSDN博客

0.3 脚位

单片机中脚位的作用是什么?

常见的管脚有那些?

​ DIP封装后,要连接CPU其下面的功能主要是通过脚位。只有根据开发手册的引脚图再去实行相关功能的应用。

其中常用:

  • Vcc: 电源 + 级
  • RST: 复位
  • XTAL: 时钟
  • Gnd: 电源﹣级,接地
0.4 最小执行系统

单片机最小执行系统需要哪几部分构成?

最小执行系统各自部分的作用是什么?

​ 最小系统主要由三部分构成

  • 电源: 不言而喻

  • 晶振: 时钟和计时器

  • 复位: 主要用于发生异常情况时,恢复到初始值操作。

  • 下载电路 : 单片机的下载烧录程序原理是通过PC(上位机),上位机将程序编译为XXX.hex的文件再通过串口写入到单片机中。

    串口的通信的核心芯片为CH340G,然后右边部分接受一放大电路,也就是14引脚,其上再接了Q3用了电容进行存储电荷,也就实现了Q3的冷启动原理。另外CPU管理串口通信的核心是通过引脚P30和P31这两个引脚,他们通过P5芯片进行了转接操作,降低后续不同协议间的干荣,并最终控制RTS引脚和DTR引脚达到串口通信的目的。

一. C语言补充

  • 位运算:

    • & : 按位与
    • | : 按位或
    • ^ : 按位异或
    • ~ : 按位取反
    • << : 左移位,空余位补0操作
  • 指针与地址:

    • “*”: 指针运算符

    • “&”:取地址运算符


二. 点灯

51中的GPIO的定义与使用方法?

​ 通用输出引脚,控制电平,点亮外设。 带有Px.x形式的引脚均属于GPIO引脚。

2.1 GPIO引脚
2.1.1 P0端口

锁存器与缓冲器进行数据传输: 三态门的作用在于实现对于不同电路的选通与隔离,也就是达到了控制数据流向作用(读是总线读取到Q端,写则是Q端写往总线)。 关于锁存器的话,则是依赖时钟电路脉冲(CLK),因为触发器是依赖脉冲的,对于D触发器来讲,当控制端CP端信号发出时(也就是时钟脉冲信号),数据由内部总线传向Q端,一直到下一次CP脉冲信号发出,才完成数据的更改,以此达到锁存数据目的。

2.1.2 P1端口

用途单一的端口,用作数据输入/输出端口使用:

P2 P3端口不做过多赘述

总的来说:

  • 要P0输出高电平,必须外接上拉电阻
  • P0 P1 P2 即可做输入也可输出
  • P3 即可普通I/O口 也能做串口 外部中断 计数器等功能
2.2 LED点灯

Led电子特性是什么?

流水灯引脚定义方法是什么?

​ LED通过其电流的不用太大,其旁电阻为限流电阻。共阳解法,阴级为高电平时熄灭。另外进行延时操作时,因为晶振原因 存在误差。具体误差需调试方知。

流水灯程序实现:

#include "reg52.h"

#define LED_PORT P2 //宏定义 等价于LED_PORT即为P2,宏定义时不需要分号


sbit LED1 = P2^0; //LED管脚定义
sbit LED2 = P2^1;

typedef unsigned int u16;  //自定义重命名将u16类型等价为Int类型

void delay_5us(u16 time)
{
	while(time --);
}

void main()
{
	
	int i = 0;
	while(1)
	{
		for(i = 0;i < 8; ++i)
		{
			LED_PORT = ~(0x01 << i);//将1由移i位赋值到LED_PORT 再取反 低电平点亮,高电平灭,
            //原始是1左移,取反后变为0右移
			delay_5us(50000);
		}
	}
	
	
}

//引用新的库函数方法
#include "reg52.h"
#include "intrins.h" //引用库函数

#define LED_PORT P2 //宏定义 等价于LED_PORT即为P2


sbit LED1 = P2^0; //LED管脚定义,笨方法
sbit LED2 = P2^1;

typedef unsigned int u16;  //自定义重命名将u16类型等价为Int类型

void delay_5us(u16 time)
{
	while(time --);
}

void main()
{
	
	int i = 0;
	while(1)
	{

		LED_PORT = ~0x01; 
		
		for(i = 0;i < 7; i++)
		{
			//左移
			LED_PORT = _crol_(LED_PORT, 1);
			delay_5us(50000);
		}
		
			for(i = 0;i < 7; i++)
		{
			//右移
			LED_PORT = _cror_(LED_PORT, 1);
			delay_5us(50000);
		}
	}
	
	
}

​ 8个流水灯既可以用传统笨方法P2^1 一直到 P2^8式定义再去进行赋值操作,也可以用宏定义,再一个16进制表示所有的灯的电平。


三. 数码管[^31]

数码管(LED数码管)的显示原理是什么?

数码管的结构是怎样的?

数码管的显示方法有哪些?

多个数码管的显示方法?

38译码器的使用方法是什么?

​ 数码管简而言之很像是一种高级的LED显示,主要是由于其是多个LED的集合管理(通常而言为了便于封装采用的10引脚,dp为右下角小点),其接法主要分为:

  • 共阳极: 低电平时显示
  • 共阴极: 高电平时显示(本电路板使用的是共阴极)

​ 通常而言,LED灯显示需要稳定的电流,采用共阳极结构比较普遍,因为控制电功率比控制电流更为容易(P(功率) = U(电压)*I(电流)),这种方法进一步减轻主芯片负担。

数码管的显示方法主要有两种:

  • 静态显示(了解): 最原始的方法,每个组成数码管的一个LED为一个IO口,若有4个数码就需4*8个IO口
  • 动态显示(常用): 存在一个位选口(38译码器),将所用的数码管共用IO口,位选选择具体哪个数码显示,并利用视觉效应让人觉得是一直在显示。

​ 38译码器的实现原理会将进行一次取反操作若P2^2, P2^3, P2^4的电平为 110则取反后为001也就是只有7号引脚(LED8)会被选中。另外38译码器的有效电平为低电平。A2 A1 A0 分别为高电位、次高点位、低电位, 其输入为101时对应的十进制为5,所有Y5输出有效(低电平),38译码器具体选择哪个数码管称为位选。

为什么在使用(共阴)数码管的段选时,低位对应的是a段?

​ 板子上的数码管驱动芯片采用的是74HC245,该芯片最大特性便是输出和输出统一。同一个图中并不用台区管线上的数字,只要在一根线上即表示同一根线驱动,这里需要看P00 => P07 该数字只是一个名字,并没有具体的含义,而需要说明的是名字中数字越大对应的二进制位越高也就是其实际驱动电路位(0000 0000)这样的16进制格式。

3.2 动态递增显示数码管
#include "reg52.h"

#define LED_PORT P0 //段选定义

typedef unsigned int u16;
typedef unsigned char u8;


//位选定义
sbit LSA = P2^2; 
sbit LSB = P2^3; //次高位
sbit LSC = P2^4;// 高位


//共阴极数码管的0 ~ F的展示
u8 gsmg_code[17] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void delay_5us(u16 time)
{
	while(time--);
}

void main()
{
	int i = 0;
	while(1)
	{
	
		for(i = 0;i < 8;i++)
		{
			switch(i)
			{
				//排法的tips: 从高位向低位排,不易出错
				case 0: LSC = 1;LSB = 1; LSA = 1; break; //从左到右第一个灯
				case 1: LSC = 1;LSB = 1; LSA = 0; break;
				case 2: LSC = 1;LSB = 0; LSA = 1; break;
				case 3: LSC = 1;LSB = 0; LSA = 0; break;
				case 4: LSC = 0;LSB = 1; LSA = 1; break;
				case 5: LSC = 0;LSB = 1; LSA = 0; break;
				case 6: LSC = 0;LSB = 0; LSA = 1; break;
				case 7: LSC = 0;LSB = 0; LSA = 0; break;
			}
			LED_PORT = gsmg_code[i];
			delay_5us(10000);
			//LED_PORT = 0x00; //消音 消去前一位产生的空白残影
		}
	}
	
}

​ 该51板子芯片的一大特征为排序的数字名越高,越属于高位引脚。然后就是关于引脚多个情况时,从高位排走。最后就是想要嵌入式循环执行,少不了外层的一直循环


四. 按键及检测

按键的本质原理是什么?

按键的消抖是是什么?

​ 按键本质是机械电子开关,按下时电路接通,弹开后电路断开。其中弹开并不会马上产生稳定的电脉冲,会存在抖动。抖动是由机械特性所决定的(一般5~10ms样子)。但实际上逻辑操作时我们只让CPU对按键仅做一次闭合操作,因此需要消抖。简而言之,按键默认的IO口会给个高电平,按下后产生抖动的低电平的脉冲

消抖方法:

  • 硬件消抖
  • 软件消抖(延时检测状态方法)

4.1 独立按键

电路原理相对简单

在这里插入图片描述

按下KEY亮下LED。需要注意的是

  • 按键按下或是释放需要进行检测(key值记录,1为释放 0为按下)
  • 释放和按下逻辑为两个独立的逻辑区域,检测的时候不用再丢在一个区域里面(即同一个if)
  • static静态变量的优点在于只用初始化一次 后续不必再进行初始化
/*
独立按键实验
*/

#include "reg52.h"

sbit KEY1 = P3^1;
sbit KEY2 = P3^0;
sbit KEY3 = P3^2;
sbit KEY4 = P3^3;

//LED灯的初始定义
sbit LED1 = P2^0;

typedef unsigned int u16;
typedef unsigned char u8;
	
//0.延时函数设置
void delay_5us(u16 time)
{
	while(time--);
}

//1.不停扫描键盘,判断是否有按下动作
u8 Key_scan(u8 mode)// mode 0 单次扫描  mode 1 多次扫描
{
	static u8 key = 1; //static关机子并不是一个可以更改的关键字,静态变量,用于保持变量的初始化操作
	//key为1时表示按键被释放可以按下
    
	if(mode)
		key = 1;
	
	//释放后才可进行重新按键操作
	if(key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0))
	{
		delay_5us(2000); //消抖
		key = 0;   //标记按键按下
		
		if(KEY1 == 0)
			return 1;
	}
	
	if(KEY1 == 1) 
	{
		key = 1;//标记按键释放
	}
		
		return 0;
}
		
void main()
{
	
	  u8 key = 0;
		while(1)
		{
			key = Key_scan(0);
			if(key == 1)
			{
				LED1 = 0;
				delay_5us(10000);
				LED1 = 1;
			}
			//delay_5us(10000);
		}
}
4.2 矩阵按键

矩阵按键的原理是什么?

矩阵按键区别于独立按键的是什么?

​ 有点类似于数码管,都是尽可能少的IO口去控制多个按键 提高效率。其核心原理图如下所示,其核心实现也是先给一高电平,让所有进行扫描,扫描到低电平时再进行定位。也就是说行列各需四根引脚。其区别于独立按键最大的不同在于键位的具体定位。

1.给初始电平 2.扫描定位

4.2.2 矩阵按键的扫描定位

扫描定位有哪些方法?

各自方法的实现是怎样的?

​ 矩阵按键的一大核心就是确定好具体是哪个按键被按下,再根据按下的键位进一步的响应,矩阵按键特性是当行列都是高电平时按下后不会反应,只有一高一低按下时才会产生电平,通常检测方法有两种:

  • 行列扫描法

    ​ 更像是一种轮询扫描法,先给一列为低电平(0),其余列为高电平(此时就当确定了列),再依次判断每行,若该列的所有行都没低电平则继续开始下一列的循环,一直到行也确定了低电平,即完成了整个检测。核心在于控制变量法思想,只有低电平时按键才能被检测到。

  • 线翻转法

    ​ 先让所有的行为低电平,检查列线并记录列值,再让所有的列为低电平,检查行线值,进而达到最终目的。

本实验主要采用行列(列行)扫描法

关于每按动一次但位选不变的情况原因?

​ 看似代码每次按下时均会调入不同的switch进而进行加位操作,但是实际上因为main函数只调用了一次,所以真正的按键调用后switch中的Choice只调用了一次,所以就一直是初始的那个地址位。

/*
独立按键实验
*/
	
//0.延时函数设置
void delay_5us(u16 time)
{
	.....
}

void Choice_WEI(u16 num)
{
	...
}

//1.不停扫描键盘,判断是否有按下动作
u8 key_matrix_rank_scan(void)// mode 0 单次扫描  mode 1 多次扫描
{
	//C嵌入式标准比较严苛静态变量声明需在最前
	static u16 current_position = 1; //当前位置状态
	static u8 key_value = 0;//扫描按键是否按下 
	//0. 先给第一列低电平,其他列为高电平这样就不会检查到其他列去(控制变量法),此时为按键松开状态
	KEY_MATRIX_PORT = 0xf7;
	//1.检测第一列是否有按下
	
	
	
	//按动一下后变第二位
	if(KEY_MATRIX_PORT != 0xf7 && key_value == 0)
	{
		delay_5us(10000); //消抖
		switch(KEY_MATRIX_PORT)
		{
			case 0x77: 
				Choice_WEI(current_position++);
				SMG_LED_PORT = gsmg_code[1]; 
				key_value = 1;
				//Choice_WEI(num++);
				//WEI3 = 1;WEI2 = 1;WEI1 = 1;
				break; //数码关非LED灯不用担delay去进一步操作
			case 0xb7: 
				SMG_LED_PORT = gsmg_code[5]; 
				
				Choice_WEI(current_position++);
				key_value = 1;
				break;
			case 0xd7: SMG_LED_PORT = gsmg_code[9];break; 
			case 0xe7: SMG_LED_PORT = gsmg_code[13]; break;
		}
		
	}
	
	while(KEY_MATRIX_PORT != 0xf7);//等待按键松开
	key_value = 0;
}
		


void main()
{
			
				key_matrix_rank_scan();
				delay_5us(500);
}

按动按键位选增加的完整代码(行列扫描法)

/*
矩阵按键实验
*/

#include "reg52.h"
//矩阵键盘宏定义
#define KEY_MATRIX_PORT P1
//数码管段选宏定义
#define SMG_LED_PORT P0

sbit WEI1 = P2^2; //低位位选
sbit WEI2 = P2^3;
sbit WEI3 = P2^4; //高位位选


//单个LED灯的初始定义
sbit LED1 = P2^0;

typedef unsigned int u16;
typedef unsigned char u8;


u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
	
//0.延时函数设置
void delay_5us(u16 time)
{
	while(time--);
}

void Choice_WEI(u16 num)
{
	switch(num)
	{
		case 1 : WEI3 = 1, WEI2 = 1, WEI1 = 1; break;
		case 2 : WEI3 = 1, WEI2 = 1, WEI1 = 0; break;
		case 3 : WEI3 = 1, WEI2 = 0, WEI1 = 1; break;
		case 4 : WEI3 = 1, WEI2 = 0, WEI1 = 0; break;
		case 5 : WEI3 = 0, WEI2 = 1, WEI1 = 1; break;
		case 6 : WEI3 = 0, WEI2 = 1, WEI1 = 0; break;
		case 7 : WEI3 = 0, WEI2 = 0, WEI1 = 1; break;
		case 8 : WEI3 = 0, WEI2 = 0, WEI1 = 0; break;
	}
}

//1.不停扫描键盘,判断是否有按下动作
u8 key_matrix_rank_scan(void)// mode 0 单次扫描  mode 1 多次扫描
{
	//C嵌入式标准比较严苛静态变量声明需在最前
	static u16 current_position = 1; //当前位置状态
	static u8 key_value = 0;//扫描按键是否按下 
	//0. 先给第一列低电平,其他列为高电平这样就不会检查到其他列去(控制变量法),以此达到只检查第一列的状态,此时为按键松开状态
	KEY_MATRIX_PORT = 0xf7;
	//1.检测第一列是否有按下
	
	
	
	//2.检测第一列的不同行是否有按键按下
	if(KEY_MATRIX_PORT != 0xf7 && key_value == 0)
	{
		delay_5us(10000); //消抖
		switch(KEY_MATRIX_PORT)
		{
			case 0x77: 
				Choice_WEI(current_position++);
				SMG_LED_PORT = gsmg_code[1]; 
				key_value = 1;
				//Choice_WEI(num++);
				//WEI3 = 1;WEI2 = 1;WEI1 = 1;
				break; //数码关非LED灯不用担delay去进一步操作
			case 0xb7: 
				SMG_LED_PORT = gsmg_code[5]; 
				
				Choice_WEI(current_position++);
				key_value = 1;
				break;
			case 0xd7: SMG_LED_PORT = gsmg_code[9];break; 
			case 0xe7: SMG_LED_PORT = gsmg_code[13]; break;
		}
		
	}
	
	while(KEY_MATRIX_PORT != 0xf7);//等待按键松开
	key_value = 0;
}
		


void main()
{
	while(1)
    {	
     	key_matrix_rank_scan();
		delay_5us(500);
    }
	
			
		
}

五. IO扩展口操作

本章解决的核心问题是什么?

IO扩展口的数据传输原理的怎样的?

​ 单片机的IO口比较有限,若想通过有限的IO口来控制更多的设备,就涉及到本章的IO口扩展操作。扩展的一大核心手法为 ==> 串转并.

主要用到芯片为74HC595。

​ 该模块的15-7引脚为并行输出,9引脚为串行输出,10为低电位复位引脚。输入引脚为:

  • 12: RCLK存储寄存器时钟输入 为下降沿(0)时存入,上升沿(1)取出。

  • 11:SRCLk为移位寄存器时钟输入 上升沿(高电平)时,传入一位二进制数据

  • 14:SER串行数据输入 赋值数据传输

​ 在使用LED点阵时,通过时序进行电路的传输,再通过阳正阴负时则被点亮。LED点阵主要列是负极,行为正。

关于74HC595的数据传输一次性只能传输一位数据,整个数据传输流程是,由CPU(89C52)一位一位的传入到74HC595中,74HC595可进行短暂的存储,再由74HC595写入到LED点阵中点亮。

#include "reg52.h"

typedef unsigned int u16;
typedef unsigned char u8;

//sbit RCLK = P3^5; // 存储寄存器时钟输入
sbit SER = P3^4;  //串行数据输入
sbit RCLOCK = P3^5;
sbit SRCLK = P3^6; //移位寄存器时钟输入,声明的管脚需大写

#define  LEDDZ_PORT P0  //点子控制阴极

u8 ghc595_buf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

//延时函数
void delay_10us(u16 ten_us)
{
	while(ten_us--);
}
//ms级别延时函数
void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--);
	for(j=110;j>0;j--);
}

void Hc595_write_data(u8 dat)
{
	u16 i = 0;
	for(i = 0;i < 8;i++)
	{
		SER = dat >> 7;
		dat <<= 1; //等价于 data = data << 1
		SRCLK = 0;
		delay_10us(3);
		SRCLK = 1;
		delay_10us(3);
	}
	RCLOCK = 0;
	delay_10us(10);
	RCLOCK = 1; //上升沿将存数据输出
}

void main()
{
	u16 num = 0;
	while(1)
	{
		LEDDZ_PORT = 0x00;
		for(num = 0;num < 8;num++)
		{
			//清除缓存数据
			Hc595_write_data(0x00);
			delay_10us(100);
			Hc595_write_data(ghc595_buf[num]);
			delay_10us(10000);
		}
	
	}

}

​ 以此实现了4根脚(3个595驱动 1个低位)去驱动LED点阵。3个595总结就是一脚传数据,一个负责写入,一个负责写出。


六. 电机

C51中的电机技术主要分为哪两种?

各自电机实现的原理是什么?

电机技术主要分为两种:

  • 直流电机
  • 步进电机
6.1 直流电机

直流电机的特征是什么?

驱动芯片的作用是什么?

​ 直流电机更像是普通一般马达,主要是转速和转动方向,对转动精度要求较低。直流电机没有正负极之分,交换接线后即可实现正反转。实现原理是电磁感应。

​ 当驱动功率不同的组件时,直接用GPIO引脚驱动的话可能出现驱动不起来或是烧毁芯片的情况,因此便出现了驱动芯片或是驱动电路。其核心在于稳定驱动电流。这里的驱动芯片主要是ULN2003。其主要是由非门电路构成,其输出时不能直接接电路,要输出高电平需要在其输出口加上拉电阻[^61]。

#include "reg52.h"

typedef unsigned int u16;
typedef unsigned char u8;

sbit DC_Motor = P1^1;

void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}
void main()
{
	DC_Motor = 1; //开启电机
	delay_ms(500);
	DC_Motor = 0;
	while(1)
	{
			delay_ms(500);
			DC_Motor = 1; //开启电机
			delay_ms(500);
			DC_Motor = 0;//关闭电机
	}
}
6.2 步进电机

并没有步进电机零件,后续再深挖

​ 步进电机的本质是将电脉冲信号转换为角位移或线位移。步进电机控制由双环形脉冲电路、功率驱动等组成。步进电机的具体实现原理则是通过电平产生磁性的强弱进而去控制偏转的角度。


七.中断系统(***)

本章属于单片机中一大核心点,51单片机的三大核心主要为:

  • 中断
  • 定时器
  • 串口

中断的概念是什么?

实现中断需要哪些操作?

C51中的中断优先级的高低顺序?

​ 中断算是单片机的一大核心功能,中断系统是指CPU在执行任务时,收到中断请求,抽离出来并打上断点,去执行其他更为紧急的任务,当其他任务执行完成之后,再返回到原有的任务中来继续执行原有任务。中断可分为单级中断和多级中断(中断的中断),在面对存在多个中断请求时,根据中断的优先级别去完成先后中断响应。(高优先级可打断低优先级)

​ 中断的使用是借助相关寄存器进行的,中断请求也称为中断源,由中断源向寄存器发起请求,CPU在收到寄存器的请求后进行响应。STC89C5X系列共提供的系列中断源(51系列提供了5个中断源):

  • 外部中断源 : INT0,INT1,INT2,INT3
  • 定时器中断源: 定时器0/1中断
  • 串口中断

​ 中断系统的具体内部结构如下(INT0和INT1下降沿有效,T0,1时钟中断,RX TX则是串口中断):

7.2 中断相关寄存器及捕获

与中断相关的寄存器各种有哪些? 各自功能是什么?

​ 寄存器的作用主要是方便实现一些列的中断管理(中断允许,发生,响应等)

(1). 中断允许寄存器

​ 因为存在多个中断源,主要是用于运行那个中断源,这个主要由寄存器IE控制,所有需要中断的均需要此

  • EX0(IE.0),外部中断 0 允许位;

  • ET0(IE.1),定时/计数器 T0 中断允许位;

  • EX1(IE.2),外部中断 1 允许位;

  • ET1(IE.3),定时/计数器 T1 中断允许位;

  • ES(IE.4),串行口中断允许位;

  • EA (IE.7), CPU 中断允许(总允许)位。

(2). 中断请求标志(TCON)

​ 主要用于中断执行时的计时器及触发相关操作。

  • IT0(TCON.0),外部中断 0 触发方式控制位。 中断触发产生 其余控制方法类似

​ 当 IT0=0 时,为电平触发方式。

​ 当 IT0=1 时,为边沿触发方式(下降沿有效)。

  • IE0(TCON.1),外部中断 0 中断请求标志位。

  • IT1(TCON.2),外部中断 1 触发方式控制位。

  • IE1(TCON.3),外部中断 1 中断请求标志位。

    即低四位用于外部中断.

  • TF0(TCON.5),定时/计数器 T0 溢出中断请求标志位。

  • TF1(TCON.7),定时/计数器 T1 溢出中断请求标志位。

7.2.2 中断响应(捕获)

中断捕获是通过什么方式进行的?

​ C51中由于中断个数有限,因此主要是通过中断位进行中断号捕获的,常见的中断响应如下:

void int0() interrupt 0 using 1  //using 1 可写可不写
{
//编写用户所需的功能代码
}

一个完整的中断执行流程: 首先中断源发起中断请求,该中断源的中断允许为1且CPU开中断(EA=1),其次进入中断响应阶段,选择好具体的中断号,并设置好触发中断的条件,以及达到触发条件后具体的响应。

7.3 外部中断系统

实现外部中断的代码流程有哪些?

  1. 先是打开中断允许的以及中断触发开关设置

    //因为reg52.h中已经包含好了 中断EA EXO ITO一些列引脚定义
    void Exti0_inti(void)
    {
    	EA = 1;		//中断总控制开关,允许CPU中断产生
    	EX0 = 1;//允许外部0中断
    	IT0 = 1; // 1为下降沿(低电平)触发方式有效,0是上升沿有效, 因为这里要和按键联动触发中断,因此采用下降沿触发
    }
    
  2. 中断响应函数

    //中断响应函数 ,按动一下灯亮一下
    void Exti0() interrupt 0
    {
    //	 LED1 = 0;
    //	 delay_us(10000);
    //	 LED1 = 1;
    	
    	//消抖
    	delay_us(2000);
    	if(KEY3 == 0)
    	{
    		LED1 = 0;
      	delay_us(10000);
    	  LED1 = 1;
    	}
    }
    
  3. 主函数调用

7.4 定时器&&定时器中断(**)

单片机定时的原理(定时操作是怎么实现的)是怎样的?

单片机几个周期定义是什么?

程序中想要手动达到定时的需求该怎么操作?

​ 单片机定时器/计算器(即可定时又可计数)系统是一个相对独立系统,其工作过程是自动完成,不需要CPU的介入。而定时器想要定时,必定存在一种节拍,而单片机中的节拍就是晶振,通过节拍,衍生出了一系列周期概念:

  • 振荡周期 :提高信号的周期(晶振周期)
  • 状态周期: 2 × 振荡周期
  • 机器周期:6 × 状态周期 机器周期 = 1 / 单片机时钟频率
  • 指令周期:执行一条指令所需要的时间(这里的指令指的是汇编指令非C语言)
例如:外接晶振为 12MHz 时,51 单片机相关周期的具体值为:
振荡周期=1/12us;
状态周期=1/6us;
机器周期=1us;
指令周期=1~4us
7.4.2 定时器的核心原理

​ 核心表示,高低位寄存器各占4位,每脉冲一次,寄存器中的数值+1,一直到两个寄存器的数值最后溢出,发出中断请求,然后则是计数器的数值+1,计数器的最终值 - 计数器的初值就得最后计时时间。其中要操作定时/计数器,主要需要通过两大寄存器:

  • TMOD工作方式寄存器
  • TCON控制寄存器

(1).TMOD工作方式寄存器

​ 因为该51中集成了两个定时T0,T1,TMOD的低四位控制T0,高四位则对应T1。

  • GATE: 门控位,即是否受外部中断源影响

  • C/T: 模式选择,0位定时模式,1为计数模式,默认0

  • M1M0: 具体定时计数的工作方式

    ​ 用得最多的是方式1和2,串口通信通常用方式2。

(2).TCON控制寄存器

​ 该寄存器的第四位用于外部中断,主要看高四位

TF1/TF0: T1/T0计数器溢出中断请求标志位,溢出后置该位为1,CPU响应中断后清0

TR1/TR0 控制位,用于控制对应定时/计数器的启动与停止,1开始工作,0停止工作。

7.4.2 定时器的实现方法

定时器实现定时需要哪几步?

​ 总的来说想要达到想要的定时,分为:

1.计数器/定时器初始化(引脚开关初始化,初值初始化可采用stc软件初始化)

void time0_init(void)
{
	EA = 1;  //总中断开关打开
	TMOD = 0x01;	//使用计数模式1
	TH0 = 0xfc;  //高位初始值
	TL0 = 0x18;   //低位初始值
	ET0 = 1;  //运行计数器中断
	TR0 = 1;	//计数器0开始工作标志位
}

2.计时

#include "reg52.h"

typedef unsigned int u16;
typedef unsigned char u8;

sbit LED1 = P2^0;

void time0_init(void)
{
	EA = 1;  //总中断开关打开
	TMOD = 0x01;	//使用计数模式1
	TH0 = 0xfc;  //高位初始值
	TL0 = 0x18;   //低位初始值
	ET0 = 1;  //运行计数器中断
	TR0 = 1;	//计数器0开始工作标志位
}

void delay_10us(u16 ten_us)
{
	while(ten_us--);
}

void main()
{
	time0_init();
	while(1);
}

void time0() interrupt 1
{
    //中断采用static的原因在于希望每次进入中断时为上一次的累加值而不是重新开始,这样才实现了逻辑上的计数操作
	 static u16 i; 
	 TH0 = 0xfc;  //高位初始值
	 TL0 = 0x18;   //低位初始值
	 i++; //每隔1ms i+1
	 if(i == 1000)
	 {
			i = 0;
			LED1 = !LED1;
	 }
}

总的来讲,中断操作的都是一个东西,要对其进行中断操作时,现要打开中断相关开关,首先是EA开关,其次是各自分中断开关,然后是中断触发方式(外部中断触发主要看上(下)沿,定时器触发主要是通过溢出设置初值),然后则是中断响应函数的书写,响应函数中涉及到的部分变量注意添加static关键字。


八. 串口通信(**)

​ WIFI,蓝牙,GPS等均是使用串口通信进行的,显得串口通信尤为重要。其次是单片机不仅可以实现串口通信,还可以实现SPI,IIC等。串口通信是指计算机之间按位进行一种数据传输,串口是一种接口标准。

8.1 通信基本概念

通信基本相关概念有哪些?

** 通信传输基本的两大方式: 串行通信和并行通信: **

​ 串行通常是一位一位的传,而并行则是8位 16位 32位一起传输,效率更高成本也更高

通信基础概念:

  • 异步: 收发双方时钟不要求一致,发送一定长度数据后需要有校验位,还需要有数据起止位。一次发几位收几位。

  • 同步:收发双方时钟虚相同且不留间隙,一次发一位收一位。

  • 半双工: 数据可以沿两个方向但需分时进行

  • 全双工: 数据可同时双向传输

  • 通信速率:比特率(波特率) 位/每秒(bps)

标准RS-232C串口的接口标准如下,每一根引脚均有属于自己的功能,其中较为重要的是TXD和RXD引脚(收发双方主要通过TXD和RXD进行的):

8.2 串口通信协议及内部结构

串口的通信协议及内部结构是怎样的?

​ 串口通信需要保持一致的波特率,异步的方式,遵循 96-N-8-1的格式。

  • 96:波特率为9600 常见还有4800 115200
  • N:是否含有奇偶校验位
  • 8:一次异步传输的数据位数
  • 1:1位的停止位,该数据由通信双方约定而成并无固定值

其内部结构主要如下,其中RXD和TXD对应的是单片机内部引脚。

8.3 串口相关寄存器与工作方式

串口的两大相关寄存器是什么?

串口相关寄存器

  • 控制寄存器SCON

    • SM0和SM1为工作方式选择

    • SM2:主要用于方式2和方式3,主要是实现多机通信有关

    • REN: 允许串行口接受数据,置1, 若为0,则禁止接受

    • TB8:只在工作方式2或3中发送数据的第9位设置作为数据的奇偶校验位

    • RB8:在方式 2 或方式 3 中,是接收到数据的第 9 位,作为奇偶校验位或地址帧/数据帧的标志位。在方式 1 时,若 SM2=0,则 RB8 是接收到的停止位。

    • TI:中断标志位,针对方式0,**发送(针对输出)**第8位数据结束时置1,向CPU发送中断标志,且需手动清0。

    • RI:接收中断标志位,接受位针对输入的中断,**串口接收中断的开关通过ES打开,ES = 1时,开启接受中断。**需手动清0-

  • 电源寄存器PCON

    SMOD:波特率倍增位。在串口方式 1、方式 2、方式 3 时,波特率与 SMOD 有

    关,当 SMOD=1 时,波特率提高一倍。复位时,SMOD=0。

8.3.2串口工作方式

​ 这里的输入输出是以单片机本身作为对象参照而言。

  • 方式0:同步移位寄存器,发送一位收一位,且收发双方时钟一致。

    • 输出:

    • 输入:

  • 方式1:10位异步数据收发(1位起始位,1位终止位,8位数据位),输入通过TXD引脚,输出通过RXD引脚,输入时还需REN置1

    • 输入:

    • 输出:image-20240923104959932

  • 方式2与方式3(1帧共11位,起始位和终止1位,数据位9位)则与1相反,TXD为数据发送引脚,RXD为接受

    • 输出:

    • 输入:

    总结: 异步的方式均会使用SUBF作为一个临时缓存和其他寄存器作为起或止的标志位存储,同步移位寄存时则不会。

8.4 串口具体使用方法

串口进行传输的使用步骤是什么?

串口通信的要求是什么?怎样达到该要求?

方式 0 的波特率 = fosc/12

方式 2 的波特率 =(2 ^SMOD/64)· fosc

方式 1 的波特率 =(2 ^SMOD/32)·(T1 溢出率)

方式 3 的波特率 =(2^ SMOD/32)·(T1 溢出率)

使用串口进行通信时,需要通信双方波特率保持一致,这样才能保证发送帧数据和收到帧数据的速率相同。 波特率的大小是和定时器初始值直接挂钩的,因此可设置定时器的初值,进而设置波特率的大小。另外串口的中断也是用的和定时器/计数器同一的引脚口。

1.先是进行初始化

void ScpDataTransfer_inti(u8 baud)   //软件计算出初始值
{
	EA = 1;
	SCON = 0x50;
	TMOD = 0x20;  //计数方式用2即8位自动重装计数器
	ES = 1;    //接受中断开关打开
	TR1 = 1;
	TH1 = baud; //计数器初值设定用于单片机波特率的设置
	TL1 = baud;
	PCON = 0x80;//波特率加倍
}

2.中断接受响应

//串口中断
void uart_routine() interrupt 4
{
	u8 rec_data;
	if(RI == 1)  //收中断标志位
	{
		RI = 0;
		rec_data = SBUF;
		LED = ~rec_data;
		ScpDataTransfer_Send(rec_data); //将收到的数据发送
	}
}
#include "reg52.h"
#define LED P2

typedef unsigned int u16;
typedef unsigned char u8;

void ScpDataTransfer_inti(u8 baud)  
{
	EA = 1;
	SCON = 0x50;
	TMOD = 0x20;  //计数方式用2即8位自动重装计数器
	ES = 1;   
	TR1 = 1;
	TH1 = baud; //计数器初值设定
	TL1 = baud;
	
	PCON = 0x80;//波特率加倍
}

void delay_time(u16 time)
{
	while(time--);
}

void ScpDataTransfer_Send(u8 byte)
{
	SBUF = byte;
	while(TI == 0); // 等待发送完成
	TI = 0;
}

void main()
{
	ScpDataTransfer_inti(0xfa);//波特率设置为9600
	while(1){}
}



//串口中断
void uart_routine() interrupt 4
{
	u8 rec_data;
	if(RI == 1)  //收中断标志位
	{
		RI = 0;
		rec_data = SBUF;
		LED = ~rec_data;
		ScpDataTransfer_Send(rec_data); //将收到的数据发送
	}
}

总的来说涉及到使用中断的操作,第一步都是先把一系列相关开关打开,再遇到触发时进行相应设置,而串口通信很重要的有以下一些点:

  • 双方的波特率需一致,以确保收发每一帧的数据一致
  • 异步数据收发时都会用到SUBF作为临时存储

九. IIC-EEPROM

IIC协议通讯的基本概念是什么?

IIC通信具体协议是怎样的?

​ IIC是一种串行总线通信标准,主要用于连接微控制器与外围设备(多个外设和MCU间的通信)。 IIC主要由两根信号线构成

  • SDA:双向串行数据线
  • SCL:时钟线,用于数据的收发同步

​ 简而言之就是每个设备有每个设备独立的地址,但一次通信只能是一个主机和多个从机之间进行数据的交换,为防止数据冲突,会利用仲裁方式觉得具体哪个设备占用总线,即一次只能有一种传输路线控制总线。

  • 主机:发起数据传输并启动时钟信号的设备
  • 从机:被主机寻址,并接受数据传输的设备
  • 仲裁:多个主机同时尝试控制总线但只允许其中一个控制并使传输不被破坏的过程
  • 同步:两个或多个器件同步时钟信号的过程
9.2 IIC协议层

IIC协议层的主要作用是什么?

​ IIC协议层主要是进行一系列数据传输的规范化操作。

(1).起止信号

​ SCL高电平时,SDA由高 => 低代表起始信号,SDA由低 => 高代表终止信号,真理的DA均是由主机发出,主机发出起始信号后,总线处于占用状态,发出终止信号后,总线则处于空闲状态。

void iic_start(void)
{
	IIC_SCL = 1;
	IIC_SDA = 1;
	delay_10us(5);
	IIC_SDA = 0; //数据电平由高到低为起始电平
	delay_10us(5);
	IIC_SCL = 0; //钳住IIC总线准备数据发送
}

void iic_stop(void)
{
	IIC_SCL = 1;
	IIC_SDA = 0;
	delay_10us(5);
	IIC_SDA = 1;
	delay_10us(5);
	//IIC_SCL = 1;
}

(2).数据传输的有效性

​ SCL高电平时,不允许数据变化要求数据稳定,只有低电位时,才允许数据变化。即高电平时数据进行传输,且不允许数据改变,低电平时允许数据改变。

void iic_write_byte(u8 dat)
{
	int i = 0;
	IIC_SCL = 0;
	//int i = 0;
	//delay_10us(5);
	for(i=0;i < 8;i++) //循环将8位传出先高后低
	{
		if(dat & 0x80 == 1) //检查最高位数据是否为1
			IIC_SDA = 1;
		else
			IIC_SDA = 0;
		
		dat <<= 1;
		delay_10us(5);
		IIC_SCL = 1; //高电平允许数据进行传输
		delay_10us(10);
		IIC_SCL = 0;  //等待下一位数据传输
		delay_10us(3);
	}
}

(3).从机应答响应

  • 正常情况下从机响应:主机向从机进行数据传输,每传输一个字节(8位)数据,需要一个由从机向主机发送的校验位,也就是说在总线上传输的每一帧数据为9位(8位主机向从机数据[先传输最高位MSB],1位从机向主机的校验位)。其中校验位数据由特定的电平组成包括应答(ACK)和非应答(NACK)两种信号校验。

    • 从机 ACK:当从机收到一个字节数据后希望对方继续传输,由特定低电平脉冲构成
    • 从机NACK:当从机收到一个字节数据后希望对方停止传输,由特定高电平脉冲构成

  • 特殊情况下的从机响应(eg.从机正在进行实时处理任务时):此时从机会一直将SDA(数据线),置于高电平,而主机然后产生一个终止信号结束数据传送。

//从机产生ACK应答
void iic_ack(void)
{
	IIC_SCL = 0;
	IIC_SDA = 0;
	IIC_SCL = 1; //该时钟用于等待响应
	delay_10us(3);
	IIC_SDA = 0;
}

//从机NACK应答
void iic_nack(void)
{
	IIC_SCL = 0;
	IIC_SDA = 1;
	IIC_SCL = 1;
	delay_10us(3);
	IIC_SDA = 1;
}

信号中起始信号是必须的,终止信号和应答信号都可有可无。

(4).总线寻址

​ 寻址分为10位和7位,主要说下7位,其中D1-D7位主机位。D0表示数据传输的方向位,0表示主机向从机写数据,1表示主机从从机接受数据。10位和7位地址互相兼容。

9.3 IIC的数据传输

IIC传输过程中SDA的数据格式是怎样的?

SLAVE ADDRESS:从机地址,,虚线框表示数据由主机传向从机,空白框则是从机向主机,主要传输通讯模式分为三种:

S:表示启始信号

  • 主机写数据传输到从机如下

  • 主机向从机读数据:

  • 主机从机复合通讯(即主机从从机读数据和写数据相互交织)

    总的来讲,通讯流程为,主机发出起始位,然后从机等待主机的广播,广播后,从机再分别与主机广播的地址进行对比,若能对比上建立通信链路,主机或从机根据传输数据方向发出ACK(NACK),最后结束传输时,主机发出停止位P。

有点核心需要注意的是IIC上述的读和写在实际代码中的操作是针对数据总线而言的,也就是说主机要往从机写入数据,则需要:

1.主机先向总线写入从机地址到总线,进行广播

2.从机收到广播与主机建立通信,

2.从机再从总线读取数据(比如说要求从机将某个数据写入到SDA上)

3.主机再从总线上读取数据

​ 实际的情况是我们只能控制MCU(主机),也就是说如果我需要读取从机的数据话,主机

9.4 实验

​ IIC实验的一大关键是搞清主机与从机,这里的主机指的是MCU也就是CPU,首先先是CPU用IIC进行地址广播,所以就有了第一个地址0xA0也就是ATC24C02地址的原因,然后再是传入要写入到其他外设目的地址,最后再是写入到目的地址的目的数据。

/*******************************************************************************
* 函 数 名         : at24c02_write_one_byte
* 函数功能		   : 在AT24CXX指定地址写入一个数据
* 输    入         : addr:写入数据的目的地址 
					 dat:要写入的数据
* 输    出         : 无
*******************************************************************************/
void at24c02_write_one_byte(u8 addr,u8 dat)
{				   	  	    																 
  iic_start();  
	iic_write_byte(0XA0);	//1.首次向从设备发送写的	  
	iic_wait_ack();	   
  iic_write_byte(addr);	//发送写地址   
	iic_wait_ack(); 	 										  		   
	iic_write_byte(dat);	//发送字节    							   
	iic_wait_ack();  		    	   
   iic_stop();				//产生一个停止条件
}	

(PS.这里的IIC主要侧重于原理,后续实验部分将以STM32笔记部分)

总结

​ 总的来讲单片机的核心芯片MCU就像是人的大脑,而其他外设之类的更像是人的手脚之类,传输数据的总线更像是血管,而我们计算机能控制的主要是“大脑”,再通过该“大脑”去控制其他设备。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值