今天咱来聊聊串口,其实串口这个词.含义实在是太广泛了,前面所提到的IIC、SPI其实都可以归结为串口通信.确切滴说是串行通信,那么今天的串口,到底和前面有啥区别呢?

IIC、SPI其实有一个非常有意思的共同点,就是一定会留出一个线路作为时钟SCL/SCLK.那是因为无论是发送方还是接收方.都会以这个时钟信号为参考,进行数据的传输.从数字逻辑的角度来讲,叫做是同步时序电路,所以,IIC和SPI本质上都属于同步串行通信.而今天所提到的的串口,实质上应该是指LVTTL/CMOS电平的UART接口.UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)其实应该属于串口的一种,是一种异步串行接口,为毛叫异步,和同步又有什么区别呢?

咱先看看硬件结构,常见的UART串口会有8个功能端口

序号管脚名称功能
1DCD载波检测。主要用于Modem通知计算机其处于在线状态,即Modem检测到拨号音, 处于在线状态。
2DTR数据终端就绪;当此引脚高电平时,通知Modem可以进行数据传输,计算机已经准备好。
3DSR此引脚高电平时,通知计算机Modem已经准备好,可以进行数据通讯了。
4RTS请求发送;此脚有计算机来控制,用以通知Modem马上传送数据至计算机;否则,Modem将收到的数据暂时放入缓冲区中。
5CTS清除发送;此脚由Modem控制,用以通知计算机将欲传的数据送至Modem。
6RIModem通知计算机有呼叫进来,是否接听呼叫由计算机决定
7RXD用于接收外部设备送来的数据
8TXD用于将数据发送给外部设备
9GND……

从上面的管脚说明可以看出来,1-6号管脚其实是和调制解调器(或者一些其它设备)的功能有关.这几个管脚更多的作用是进行控制.而真正完成首发数据的管脚,便是RXD和TXD两个.实际情况是一般的UART串口,RXD、TXD加上共地.就能够完成数据传输的功能了,不知道大家发现没有.所有的管脚端口中,木有时钟的影子啊!没错,和SPI、IIC等同步串行通信依赖于同一个时钟不同.UART串口的接收方和发送方.所需要的参考时钟都由自己本地产生,相互间的时钟并不同步.也就是使用不同的时钟,所以,UART也叫异步串行通信接口.硬件连接上有个值得一提的地方.

两个设备的TXD和RXD其实是交叉的,一台设备的TX,另外一台对应就是RX.一台是RX,另外一台对应就是TX,不少第一次接触的朋友,在这上面会出点小差错.咱这里讨论的主要是LVTTL/CMOS电平的UART串口,电平范围大约是0-3.3v或者是0-5v.当然,UART只是一种通信规范,如果在电平上做做文章,进行一次电平转换,就能衍生出.譬如RS232、RS485等不同电平的串行接口,再配合内部的一些控制电路,又能完成红外功能.

举个栗子吧

串口都有什么协议(51入门系列教程协议协议)(1)

这就是一个单片机和RS232调制解调器之间,RS232通信的连接.中间夹了一片电平转换器,注意一下哟.对电器特性、逻辑电平和各种信号线功能都作了规定。在TxD和RxD上:

逻辑1(MARK)=-3V~-15V

逻辑0(SPACE)= 3~ 15V

在RTS、CTS、DSR、DTR和DCD等控制线上:

信号有效(接通,ON状态,正电压)= 3V~ 15V

信号无效(断开,OFF状态,负电压)=-3V~-15V

所以,UART其实有着非常广泛用法,咱拿来发送个字符数据,其实是最基本的用法.

Ok,没有统一的参考时钟源(异步通信),设备之间怎么开始通信呢?其实异步通信每个数据之间的时间间隔是不固定的.但是,同一个数据中相邻bit间的时间间隔是固定的.

下面来看看UART的协议

串口都有什么协议(51入门系列教程协议协议)(2)

上图中bit发送的顺序是从右往左依次发送,传输的速率叫波特率,也就是每秒传输的bit位数.譬如一秒传输1000个字符.每个字符1个起始位、1个校验位、1个停止位和7个数据位.共10位,那么1000个字符就有1000x10=10000bits.所以,仪表传输10000个字符对应的比特率就是10000bps(10000 bits per second),数据线空闲时为高.数据线被拉低,则表示一个起始位,数据开始传输.接收设备在接收到起始信号后,进行发送和接收的时钟同步.也就是最早就得商量好,传输的波特率是多少.双方知道波特率后,才能不用同一个时钟,而正常通信.但是由于不同设备的时钟差异,总是会出现小小的传输错误.所以,UART串口有一个很重要的指标就是某个波特率下的误码率.下面简单总结一下各个位的作用

起始位先发出一个逻辑”0”信号,表示传输字符的开始。
数据位可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输,低位在前。
校验位数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)。
停止位它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
空闲位处于逻辑“1”状态,表示当前线路上没有资料传送。

看一个上位机的串口调试工具

串口都有什么协议(51入门系列教程协议协议)(3)

嗯,应该知道这些是干嘛的了吧.特别注意流控位,和其他功能端口有关,现在基本上所有的单片机都集成了硬件UART.这里还是给一个51 IO口模拟UART串口的代码吧

#include

sbit BT_SND =P1^0; //TXD口

sbit LED =P1^2;

sbit BT_REC =P1^1; //RXD口

#define MODE_QUICK

#define F_TM F0

#define TIMER0_ENABLE TL0=TH0; TR0=1;

#define TIMER0_DISABLE TR0=0;

sbit ACC0= ACC^0;

sbit ACC1= ACC^1;

sbit ACC2= ACC^2;

sbit ACC3= ACC^3;

sbit ACC4= ACC^4;

sbit ACC5= ACC^5;

sbit ACC6= ACC^6;

sbit ACC7= ACC^7;

void IntTimer0() interrupt 1

{

F_TM=1;

}

//发送一个字符

void PSendChar(unsigned char inch)

{

#ifdef MODE_QUICK

ACC=inch;

F_TM=0;

BT_SND=0; //start bit

TIMER0_ENABLE; //启动

while(!F_TM);

BT_SND=ACC0; //先送出低位

F_TM=0;

while(!F_TM);

BT_SND=ACC1;

F_TM=0;

while(!F_TM);

BT_SND=ACC2;

F_TM=0;

while(!F_TM);

BT_SND=ACC3;

F_TM=0;

while(!F_TM);

BT_SND=ACC4;

F_TM=0;

while(!F_TM);

BT_SND=ACC5;

F_TM=0;

while(!F_TM);

BT_SND=ACC6;

F_TM=0;

while(!F_TM);

BT_SND=ACC7;

F_TM=0;

while(!F_TM);

BT_SND=1;

F_TM=0;

while(!F_TM);

TIMER0_DISABLE; //停止timer

#else

unsigned char ii;

ii=0;

F_TM=0;

BT_SND=0; //start bit

TIMER0_ENABLE; //启动

while(!F_TM);

while(ii<8)

{

if(inch&1)

{

BT_SND=1;

}

else

{

BT_SND=0;

}

F_TM=0;

while(!F_TM);

ii ;

inch>>=1;

}

BT_SND=1;

F_TM=0;

while(!F_TM);

#endif

TIMER0_DISABLE; //停止timer

}

//接收一个字符

unsigned char PGetChar()

{

#ifdef MODE_QUICK

TIMER0_ENABLE;

F_TM=0;

while(!F_TM); //等过起始位

ACC0=BT_REC;

TL0=TH0;

F_TM=0;

while(!F_TM);

ACC1=BT_REC;

F_TM=0;

while(!F_TM);

ACC2=BT_REC;

F_TM=0;

while(!F_TM);

ACC3=BT_REC;

F_TM=0;

while(!F_TM);

ACC4=BT_REC;

F_TM=0;

while(!F_TM);

ACC5=BT_REC;

F_TM=0;

while(!F_TM);

ACC6=BT_REC;

F_TM=0;

while(!F_TM);

ACC7=BT_REC;

F_TM=0;

while(!F_TM)

{

if(BT_REC)

{

break;

}

}

TIMER0_DISABLE; //停止timer

return ACC;

#else

unsigned char rch,ii;

TIMER0_ENABLE;

F_TM=0;

ii=0;

rch=0;

while(!F_TM); //等过起始位

while(ii<8)

{

rch>>=1;

if(BT_REC)

{

rch|=0x80;

}

ii ;

F_TM=0;

while(!F_TM);

}

F_TM=0;

while(!F_TM)

{

if(BT_REC)

{

break;

}

}

TIMER0_DISABLE; //停止timer

return rch;

#endif

}

//检查是不是有起始位

bit StartBitOn()

{

return (BT_REC==0);

}

void main()

{

unsigned char gch;

TMOD=0x22; /*定时器1为工作模式2(8位自动重装),0为模式2(8位自动重装) */

PCON=00;

TR0=0; //在发送或接收才开始使用

TF0=0;

TH0=(256-96); //9600bps 就是 1000000/9600=104.167微秒

TL0=TH0;

ET0=1;

EA=1;

PSendChar(0x55);

PSendChar(0xaa);

PSendChar(0x00);

PSendChar(0xff);

while(1)

{

if(StartBitOn())

{

gch=PGetChar();

PSendChar(gch);

if(gch==\'p\')

{LED = 0;}

else if(gch==\'q\')

{LED = 1;}

else

{LED = LED;}

}

}

}

嗯,有点儿长.不过仔细看代码的运行过程,其实基本上就是遵循上面提到的时序.用定时器作为波特率发生器.也就是数据发送接收的本地时钟.然后每次接收一个字符.

如果接收到的是字母p,则点亮LED

如果接收到的是字母q,则点亮LED

上gif

串口都有什么协议(51入门系列教程协议协议)(4)

其实UART的时序很简单,难点是如何将接收的数据进行简单的处理.转化为控制相关的数据.说白了,就是字符串或者数据流的处理.这个是使用串口必须要做的一件事情.下面的代码进行了一些简单的字符串处理.可直接调用printf函数(这个是个比较牛的函数,不过消耗很多资源).使用STC89C52RC的硬件UART配置.做了一个简单的人机交互

看注释吧

#include

#include

#include

typedef unsigned char BYTE;

typedef unsigned int WORD;

#define uint unsigned int

#define uchar unsigned char

#define FOSC 11059200L //System frequency

#define BAUD 9600 //UART baudrate

/*Define UART parity mode*/

#define NONE_PARITY 0 //None parity

#define ODD_PARITY 1 //Odd parity

#define EVEN_PARITY 2 //Even parity

#define MARK_PARITY 3 //Mark parity

#define SPACE_PARITY 4 //Space parity

#define Vref 5.0

#define PARITYBIT EVEN_PARITY //Testing even parity

char rx_buff[16],rx_buff_count=0; //接收缓存,16字节

uchar GUI_flag=0;

sbit bit9 = P2^2; //P2.2 show UART data bit9

bit busy;

void SendData(BYTE dat);

void SendString(char *s);

void delayms(uint xms)

{

uint i,j;

for(i=0; i");

}

void sys_init(void) //串口初始化,9600bps

{

#if (PARITYBIT == NONE_PARITY)

SCON = 0x50; //8-bit variable UART

#elif (PARITYBIT == ODD_PARITY) || (PARITYBIT == EVEN_PARITY) || (PARITYBIT == MARK_PARITY)

SCON = 0xda; //9-bit variable UART, parity bit initial to 1

#elif (PARITYBIT == SPACE_PARITY)

SCON = 0xd2; //9-bit variable UART, parity bit initial to 0

#endif

TMOD = 0x20; //Set Timer1 as 8-bit auto reload mode

TH1 = TL1 = -(FOSC/12/32/BAUD); //Set auto-reload vaule

TR1 = 1; //Timer1 start run

ES = 1; //Enable UART interrupt

EA = 1; //Open master interrupt switch

printf("rnSTC89C52RC Uart GUI Test start!rn");

printf("Welcome to GUI TESTrn");

printf(">");

}

void main()

{

sys_init();

while (1)

{

if(GUI_flag)

{

input_ASK();

GUI_flag = 0;

}

}

}

/*----------------------------

UART interrupt service routine

---------------------------- */

void Uart_Isr() interrupt 4 using 1 //串口中断服务函数

{

if (RI)

{

RI = 0; //Clear receive interrupt flag

if(SBUF==0x0d)

{GUI_flag = 1;}

else{

rx_buff[rx_buff_count]= SBUF;

rx_buff_count ;

SendData(SBUF);

GUI_flag = 0;

}

bit9 = RB8; //P2.2 show parity bit

}

if (TI)

{

TI = 0; //Clear transmit interrupt flag

busy = 0; //Clear transmit busy flag

}

}

/*----------------------------

Send a byte data to UART

Input: dat (data to be sent)

Output:None

---------------------------- */

void SendData(BYTE dat) //发送一个字节

{

while (busy); //Wait for the completion of the previous data is sent

ACC = dat; //Calculate the even parity bit P (PSW.0)

if (P) //Set the parity bit according to P

{

#if (PARITYBIT == ODD_PARITY)

TB8 = 0; //Set parity bit to 0

#elif (PARITYBIT == EVEN_PARITY)

TB8 = 1; //Set parity bit to 1

#endif

}

else

{

#if (PARITYBIT == ODD_PARITY)

TB8 = 1; //Set parity bit to 1

#elif (PARITYBIT == EVEN_PARITY)

TB8 = 0; //Set parity bit to 0

#endif

}

busy = 1;

SBUF = ACC; //Send data to UART buffer

}

/*----------------------------

Send a string to UART

Input: s (address of string)

Output:None

---------------------------- */

void SendString(char *s) //发送一个字符串

{

while (*s) //Check the end of the string

{

SendData(*s ); //Send current char and increment string ptr

}

}

char putchar (char c) //修正标准库putchar函数,将SBUF作为输出源,使用printf

{

ES=0;

SBUF = c;

while(TI==0);

TI=0;

ES=1;

return 0;

}

视频中演示了,UART串口接收到字符串后。51进行对应的比对输出,达到交互的效果、像不像小霸王学习机?(一不小心暴露年龄啊)!!

了解更多51系列教程,请关注“云汉电子社区”官方微信公众号ickeybbs,或者登录云汉电子社区官方网站(bbs.ickey.cn)

,