• 51单片机:串口通信



    前言

    51单片机串口通信原理及代码,实现单片机与PC的交互

    一、前置知识了解

    数据传输方式分类:

    1. 串行通信:使用一条数据线,将数据一位一位传输,每个数据占据一个固定的时间长度。特点:传输线少,长时间传输成本低,但传输控制比并行复杂。
    2. 并行通信:将数据字节的各位用多条数据线同时进行传输。特点:控制简单,速度快,但长距离传输时间成本高且接受方接受困难,扛干扰能力差。

    数据同步方式分类

    1. 异步通信: 发送与接受设备使用各自的时钟控制数据的发送和接受过程。双方时钟不一定一致,但通信时钟应尽可能保持一致。特点: 不要求双方时钟严格一致,实现简单,设备开销小,但传输效率低。
    2. 同步通信: 要建立发送和接受双方的时钟直接控制,使双方达到完全同步。分为外同步和自同步。特点: 双方时钟应严格一致,控制复杂,开销大,但传输效率高。

    数据传输方向分类

    1. 单工通信: 数据传输只能沿一个方向
    2. 半双工通信: 数据可双向传输,但须分时进行
    3. 全双工通信: 数据可以同时双向传输。

    通信速率

    1. 比特率: 每秒钟传输二进制代码的位数,单位:位/秒。如:每秒传输240字符,每个字符含10位,则其比特率为:2400bps
    2. 波特率: 当定义若干位为一个码元,以2400bps为例,定义每四位为一码元,则其波特率为 2400 / 4 = 600 2400/4=600 2400/4=600

    二、51单片机寄存器

    1. 串口控制寄存器SCON

    76543210
    字节地址:98HSM0SM1SM2RENTB8RB8TIRISCON

    REN: 允许串行接收位。 R E N = 1 REN=1 REN=1启用串口接收数据,反之禁止。

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

    RI: 接收中断标志位。 在方式0中,接收到第8位数据结束时,在其他方式中,接收到停止位时,硬件置1,发出中断申请,须在中断程序中软件清0,取消此中断。

    SM2: 多机通信控制位,主要用于方式2和3(见下表)。 S M 2 = 1 SM2=1 SM2=1时,启用多机通信;接收的第九位数据赋值给RB8,当 R B 8 = 0 RB8=0 RB8=0,不激活RI(即 R I = 0 RI=0 RI=0),收到的数据丢弃;当 R B 8 = 1 RB8=1 RB8=1,激活RI(即 R I RI RI硬件置1),产生中断从SBUF中读取数据。当 S M 2 = 0 SM2=0 SM2=0,不管RB8为0或1,均可读取其中数据。

    TB8: 方式0和1中未用到。在方式2和3中,是发送数据的第9位,可以软件定义其作用,作为校验位或标志位。

    TI: 发送中断标志位。 在方式0中,发送第8位数据结束时,在其他方式中,发送停止位时,硬件置1,发出中断申请,须在中断程序中软件清0,取消此中断。

    SM0和SM1为工作方式选择位

    SM0SM1方式说明波特率
    000移位寄存器 f o s c / 12 f_{osc}/12 fosc/12
    01110位异步收发器(8位数据)可变
    10211位一步收发器(9位数据) f o s c / 64 f_{osc}/64 fosc/64 f o s c / 32 f_{osc}/32 fosc/32
    11311位异步收发器(9位数据)可变

    f o s c f_{osc} fosc为外部晶振频率

    2. 电源控制寄存器PCON

    76543210
    字节地址:97HSMODPCON

    SMOD: 波特率倍增位。 在串口方式1、2、3(见工作方式选择表)时,波特率和SMOD有关。 S M O D = 1 SMOD=1 SMOD=1,波特率提高一倍,复位时置0。

    如何理解波特率,及其倍增位,以全双工异步通信为例,如下图所示。
    在这里插入图片描述
    图中结构可以看出为全双工通信,如果为异步通信(即接收端与发送端双方的时钟由各自控制),为了保证设备正常工作,需要尽可能让双方时钟保持一致;如果为同步通信,则发送端除了控制自己的时钟,同时还要控制接收端的时钟,使双方时钟保持一致。
    在这里插入图片描述
    举例说明,当发送端与接收端同步时(即双方波特率比值为1:1),发送端发送一个a,接收端接收一个a。

    当发送端与接收端时钟不同步时(例如双方波特率比值为2:1)。发送端发送一个a,接收端接收到两个a。这就是双方时钟不一致(即波特率不同)产生的现象。

    无论同步通信还是异步通信都要使双方时钟保持一致,区别只在于同步时钟是由谁控制。至于为什么要进行波特率倍增,大概可能估计是由于硬件限制,考虑到最大波特率也无法进行正常通信的情况。

    三、工作方式选择

    1. 接收到的数据和需要发送的数据存储在两段相互独立的内存中,但两端内存对外都命名为SBUF,因此对发送数据和接收数据的处理只需对SBUF处理即可。
    2. 无论是工作在哪种工作模式,在输入数据到单片机时,均须使SCON寄存器中的REN置1。
    3. 除方式0外,其余RXD均为数据接收引脚,TXD均为数据发送引脚

    方式0

    串口为同步移位寄存器的输入输出方式。主要用于扩展并行输入或输出口。数据由RXD(P3.0)引脚输入或输出,同步位移脉冲由TXD(P3.1)引脚输出。例如外部设备须并行输出数据到单片机,但单片机没有多余的引脚,则可以使用该引脚通信功能,与74HC595扩展的区别在于需要使用TXD引脚同步双方时钟。发送和接收均为8位数据,从低至高位依次发送。

    输出时序图


    先将数据写入SBUF,从低至高依次输出数据,在第八位结束时,硬件置TI=1(详见SCON中TI的描述),产生中断结束发送数据,后续还需软件置0。

    输入时序图

    在这里插入图片描述
    输入前先将输入中断RI软件置0,输入完毕后RI硬件置1(详见SCON中RI描述),产生中断,结束输入。

    方式1

    一帧数据由1为起始位,8位数据位,1位停止位组成。

    输出时序图

    在这里插入图片描述
    接收到停止位时,TI硬件置1(详见SCON中TI描述),产生中断,结束输出。

    输入时序图


    接收到起始位开始输入,至接收到停止位时,前八数据为存入SBUF,停止位进入RB8,RI硬件置1,产生中断,结束输入。

    上述为SM2=1,多机通信中的场景。在SM2=0,单机通信中,接收到停止位即产生中断,结束输入。

    方式2、3

    数据格式
    在这里插入图片描述
    当输出时,数据第九位为TB8,为输出的标志位或校验位。
    当输入时,数据第九位位RB8,为输入的标志位或校验位。

    输出时序图

    在这里插入图片描述

    在这里插入图片描述
    如上图所示,当移位寄存器检测到上种情况时,TI硬件置1,产生中断终止输出。

    输入时序图

    在这里插入图片描述
    在这里插入图片描述

    四、串口通信代码

    波特率计算使用提供的波特率计算软件即可。电路图不太能看懂就省略了。
    功能:

    1. 由于显示设备限制,将PC发送的数据返回给PC以代替单片机上的数据显示。
    2. 通过按键矩阵,可输入a~p字符,按下独立按键K3即可将单片机的数据发送给PC。

    这是简单的设备之间的通信,后续单片机如果有合适的显示设备,或许会完善使用按键矩阵和独立按键实现拼音9键,实现单片机和PC的英文交互。

    时延函数 delay.h

    #ifndef _DELAY_H__
    #define _DELAY_H__
    /*10us=1,时延为10us*/
    void delay_10us(unsigned int _10us){
    	while(_10us--){}
    }
    /*1ms=1,时延为1ms*/
    void delay_1ms(unsigned int _1ms){
    	 _1ms*=110;
    	 while(_1ms--){
    	 }
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    中断初始化函数 interrupt_utils.h

    #ifndef _INTERRUPT_H_
    #define _INTERRUPT_H_
    #include "reg52.h"
    /*中断初始化*/
    /*外部中断0*/
    void Int0_Init(const unsigned char *mode){//外部中断0的触发方式,0低电平触发,1下降沿触发
    	EA=1;//总中断允许位
    	EX0=1;//外部中断0允许位
    	IT0=*mode;	
    }
    /*外部中断1*/
    void Int1_Init(const unsigned char *mode){//外部中断1的触发方式,0低电平触发,1下降沿触发
    	EA=1;//总中断允许位
    	EX1=1;//外部中断0允许位
    	IT1=*mode;	
    }
    void Timer0_Init(const unsigned char *mode ,const unsigned char *HighVal , const unsigned char *LowVal ){
    	EA=1;//打开中断总允许位
    	ET0=1;//打开T0中断允许位
    	TR0=1;//定时/计数器中断0开始工作
    	TMOD |= *mode;//设置T0工作模式
    	TH0 = *HighVal; //高八位寄存器初值
    	TL0 = *LowVal;  //第八位寄存器初值
    } 
    void Timer1_Init(const unsigned char *mode ,const unsigned char *HighVal , const unsigned char *LowVal ){
    	EA=1;//打开中断总允许位
    	ET1=1;//打开T0中断允许位
    	TR1=1;//定时/计数器中断0开始工作
    	TMOD |= *mode;//设置T0工作模式
    	TH1 = *HighVal; //高八位寄存器初值
    	TL1 = *LowVal;  //第八位寄存器初值
    } 
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    串口初始化及相关功能函数 uart_init.h

    #ifndef __UART_INIT_H__
    #define __UART_INIT_H__
    #include "interrupt_utils.h"
    unsigned char data_buffer[10];//存储接收的数据
    unsigned char Send_buffer[10];//要发送的数据
    unsigned char Num=0;
    unsigned char Send_Num=0;
    /*设置串口寄存器参数
    		1.设置串口寄存器
    		2.设置波特率参数
    */
    void Set_Uart( unsigned char *Uart_Reg_mode ,  unsigned char *Baud_mode){
    	SCON = *Uart_Reg_mode;
    	PCON |= *Baud_mode;
    }
    /*串口初始化函数*/
    void Uart_Init(unsigned char T1_Mode , unsigned char Uart_Reg_mode , 
    				 unsigned char Baud_mode ,  unsigned char Init_Val ){
    	  
    	  Set_Uart(&Uart_Reg_mode,  &Baud_mode);
    	  Timer1_Init(&T1_Mode ,&Init_Val , &Init_Val );
    	  ES=1;	//打开串口中断允许位
    	  ET1=0;//关闭定时器中断1的允许位	  
    }
    /*串口发送一个字符*/
    void Uart_Send_byte(unsigned char dat){
    	SBUF = dat;
    	while(!TI);//发出中断,TI硬件置1,跳出循环
    	TI=0;//重置,取消本次中断	
    	
    }
    /*串口发送一串字符*/
    void Uart_Send_String(){
    	unsigned char i;
    	for(i=0 ; i!=Send_Num;++i){
    		Uart_Send_byte(Send_buffer[i]);
    	}
    	Send_Num=0;
    }
    /*串口接受数据(不超过10位)*/
    void UART_Routine( ) interrupt 4{  
      	RI=0;		//将接收中断标志清0 
    	if(Num>9){	 //超出缓存大小,覆盖从头开始存储
    		Num=0;
    	}	
    	data_buffer[Num++] = SBUF;		
    	/*由于单片机暂无合适的显示设备,将接收到的数据返回给电脑*/
    	Uart_Send_byte(data_buffer[Num-1]);
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    矩阵按键扫描函数 scan_button.h

    #ifndef __SCAN_BUTTON_H__
    #define __SCAN_BUTTON_H__
    /*扫描矩阵按键*/
    #include "reg52.h"
    #include "delay.h"
    typedef unsigned int uint;
    typedef unsigned char uchar;
    
    sbit line0 = P1^7;
    sbit line1 = P1^6;
    sbit line2 = P1^5;
    sbit line3 = P1^4;
    sbit col0 = P1^3;
    sbit col1 = P1^2;
    sbit col2 = P1^1;
    sbit col3 = P1^0;
    
    char  Table16[4][4] = {{'a','b','c','d'},
    						{'e','f','g','h'},
    						{'i','j','k','l'},
    						{'m','n','o','p'}
    						} ;
    /*确定行*/
    uint get_line(void){
    	uint retVal=4;
    	col0=0;
    	col1=0;
    	col2=0;
    	col3=0;
    	if(!line0) retVal=0;
    	if(!line1) retVal=1;
    	if(!line2) retVal=2;
    	if(!line3) retVal=3;
    	col0=1;
    	col1=1;
    	col2=1;
    	col3=1;
    	return retVal;
    }
    /*确定列*/
    uint get_col(void){
    	uint retVal=4;
    	line0=0;
    	line1=0;
    	line2=0;
    	line3=0;
    	if(!col0) retVal=0;
    	if(!col1) retVal=1;
    	if(!col2) retVal=2;
    	if(!col3) retVal=3;
    	line0=1;
    	line1=1;
    	line2=1;
    	line3=1;
    	return retVal;
    }
    /*监测按键,并返回按键值*/
    char Scan_Button(){
    	uint row;
    	uint col;
    	delay_10us(6000);
    	row = get_line();
    	col = get_col();
    	delay_10us(6000);
    	if(row!=4&&col!=4)
    		return Table16[row][col];
    	return 0;
    }	
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    主函数

    #include "Uart_Init.h"
    #include "Scan_Button.h"
    /*
    	1.由于暂时无合适的显示设备,所以将单片机接收到的信息返回给电脑显示。
    	2.可以通过矩阵键盘输入信息,按独立按键k3进行发送
    */
    void main(){
       Int0_Init(0);//外部中断0初始化
       Uart_Init(0x20,0x50,0x80,0xFA);//串口中断初始化
       while(1){
    	  char cha;
    	  cha=Scan_Button();
    	  if(cha)
    	  	Send_buffer[Send_Num++] = cha;
       }
    }
    void Int0_Rountine() interrupt 0{ //按键矩阵输入完毕,按独立按键K3中断发送
    	Uart_Send_String();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    Curl 命令方式对elasticsearch备份和恢复—— 筑梦之路
    网络安全(黑客)自学
    本机配置SSH连接代码仓库
    什么是适应能力?如何提高适应能力?
    Lock和synchronized的区别
    Android13 编译ninja failed with: exit status 137
    【测控电路】三运放高共模抑制比放大电路
    3.3、差错检测
    一种基于行为空间的回声状态网络参数优化方法
    蓝牙学习二(连接和通讯简述)
  • 原文地址:https://blog.csdn.net/m0_46327721/article/details/125447547