前言
51单片机串口通信原理及代码,实现单片机与PC的交互
一、前置知识了解
数据传输方式分类:
- 串行通信:使用一条数据线,将数据一位一位传输,每个数据占据一个固定的时间长度。特点:传输线少,长时间传输成本低,但传输控制比并行复杂。
- 并行通信:将数据字节的各位用多条数据线同时进行传输。特点:控制简单,速度快,但长距离传输时间成本高且接受方接受困难,扛干扰能力差。
数据同步方式分类
- 异步通信: 发送与接受设备使用各自的时钟控制数据的发送和接受过程。双方时钟不一定一致,但通信时钟应尽可能保持一致。特点: 不要求双方时钟严格一致,实现简单,设备开销小,但传输效率低。
- 同步通信: 要建立发送和接受双方的时钟直接控制,使双方达到完全同步。分为外同步和自同步。特点: 双方时钟应严格一致,控制复杂,开销大,但传输效率高。
数据传输方向分类
- 单工通信: 数据传输只能沿一个方向
- 半双工通信: 数据可双向传输,但须分时进行
- 全双工通信: 数据可以同时双向传输。
通信速率
- 比特率: 每秒钟传输二进制代码的位数,单位:位/秒。如:每秒传输240字符,每个字符含10位,则其比特率为:2400bps
- 波特率: 当定义若干位为一个码元,以2400bps为例,定义每四位为一码元,则其波特率为
2400
/
4
=
600
2400/4=600
2400/4=600
二、51单片机寄存器
1. 串口控制寄存器SCON
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|
字节地址:98H | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI | SCON |
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为工作方式选择位
SM0 | SM1 | 方式 | 说明 | 波特率 |
---|
0 | 0 | 0 | 移位寄存器 |
f
o
s
c
/
12
f_{osc}/12
fosc?/12 | 0 | 1 | 1 | 10位异步收发器(8位数据) | 可变 | 1 | 0 | 2 | 11位一步收发器(9位数据) |
f
o
s
c
/
64
f_{osc}/64
fosc?/64或
f
o
s
c
/
32
f_{osc}/32
fosc?/32 | 1 | 1 | 3 | 11位异步收发器(9位数据) | 可变 |
f
o
s
c
f_{osc}
fosc?为外部晶振频率
2. 电源控制寄存器PCON
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|
字节地址:97H | SMOD | | | | | | | PCON | |
SMOD: 波特率倍增位。 在串口方式1、2、3(见工作方式选择表)时,波特率和SMOD有关。
S
M
O
D
=
1
SMOD=1
SMOD=1,波特率提高一倍,复位时置0。
如何理解波特率,及其倍增位,以全双工异步通信为例,如下图所示。 图中结构可以看出为全双工通信,如果为异步通信(即接收端与发送端双方的时钟由各自控制 ),为了保证设备正常工作,需要尽可能让双方时钟保持一致;如果为同步通信,则发送端除了控制自己的时钟,同时还要控制接收端的时钟,使双方时钟保持一致。 举例说明,当发送端与接收端同步时(即双方波特率比值为1:1),发送端发送一个a,接收端接收一个a。
当发送端与接收端时钟不同步时(例如双方波特率比值为2:1)。发送端发送一个a,接收端接收到两个a。 这就是双方时钟不一致(即波特率不同)产生的现象。
无论同步通信还是异步通信都要使双方时钟保持一致,区别只在于同步时钟是由谁控制。至于为什么要进行波特率倍增,大概可能估计是由于硬件限制,考虑到最大波特率也无法进行正常通信的情况。
三、工作方式选择
接收到的数据和需要发送的数据存储在两段相互独立的内存中,但两端内存对外都命名为SBUF,因此对发送数据和接收数据的处理只需对SBUF处理即可。 无论是工作在哪种工作模式,在输入数据到单片机时,均须使SCON寄存器中的REN置1。 - 除方式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,产生中断终止输出。
输入时序图
四、串口通信代码
波特率计算使用提供的波特率计算软件即可。电路图不太能看懂就省略了。 功能:
- 由于显示设备限制,将PC发送的数据返回给PC以代替单片机上的数据显示。
- 通过按键矩阵,可输入a~p字符,按下独立按键K3即可将单片机的数据发送给PC。
这是简单的设备之间的通信,后续单片机如果有合适的显示设备,或许会完善使用按键矩阵和独立按键实现拼音9键,实现单片机和PC的英文交互。
时延函数 delay.h
#ifndef _DELAY_H__
#define _DELAY_H__
void delay_10us(unsigned int _10us){
while(_10us--){}
}
void delay_1ms(unsigned int _1ms){
_1ms*=110;
while(_1ms--){
}
}
#endif
中断初始化函数 interrupt_utils.h
#ifndef _INTERRUPT_H_
#define _INTERRUPT_H_
#include "reg52.h"
void Int0_Init(const unsigned char *mode){
EA=1;
EX0=1;
IT0=*mode;
}
void Int1_Init(const unsigned char *mode){
EA=1;
EX1=1;
IT1=*mode;
}
void Timer0_Init(const unsigned char *mode ,const unsigned char *HighVal , const unsigned char *LowVal ){
EA=1;
ET0=1;
TR0=1;
TMOD |= *mode;
TH0 = *HighVal;
TL0 = *LowVal;
}
void Timer1_Init(const unsigned char *mode ,const unsigned char *HighVal , const unsigned char *LowVal ){
EA=1;
ET1=1;
TR1=1;
TMOD |= *mode;
TH1 = *HighVal;
TL1 = *LowVal;
}
#endif
串口初始化及相关功能函数 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;
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;
}
void Uart_Send_byte(unsigned char dat){
SBUF = dat;
while(!TI);
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;
}
void UART_Routine( ) interrupt 4{
RI=0;
if(Num>9){
Num=0;
}
data_buffer[Num++] = SBUF;
Uart_Send_byte(data_buffer[Num-1]);
}
#endif
矩阵按键扫描函数 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
主函数
#include "Uart_Init.h"
#include "Scan_Button.h"
void main(){
Int0_Init(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{
Uart_Send_String();
}
|