51单片机串口通讯原理及程序源码-----day8
1.电平分类:
1、 TTL: Vcc: 5V;
2、 LVTTL : Vcc: 3.3V等
3、 CMOS: Vcc: 5V;
4、 LVCMOS : Vcc: 3.3V等
5、 RS232标准: 1: -3V ~ -15V 0 : 3V ~ 15V
所以计算机与单片机之间通讯时需要加电平转换芯片CH340T 、 MAX232。
MAX232 供电电压5V
MAX3232 供电电压3.3V
DB9实物介绍:
引脚:
1、 载波检测(DCD)
2、 接收数据(RXD)
3、 发送数据(TXD)
4、 数据终端准备好(DTR)
5、 信号地线(SG)
6、 数据准备好(DSR)
7、 请求发送(RTS)
8、 清除发送(CTS)
9、 振铃提示(RI)
2.通信分类:
(1)并行通信通常是将数据字节的各位用多条数据线同时进行传送 。
并行通信控制简单、 传输速度快; 由于传输线较多, 长距离传送时成本高且接收方的各位同时接收存在困难
(2)串行通信是将数据字节分成一位一位的形式在一条传输线上逐个地传送。串行通信的特点: 传输线少, 长距离传送时成本低, 且可以利用电话网等现成的设备, 但数据的传送控制比并行通信复杂。
异步通信的特点: 不要求收发双方时钟的严格一致, 实现容易, 设备开销较小, 但每个字符要附加2~3位用于起止位, 各帧之间还有间隔, 因此传输效率不高。
2.UART的四种模式:
模式0:
模式1: 以TXD为例, 平时没数据时TXD为高电平, 来了数据, 一位起始位0, 八位数据位( 一个字节) , 一位停止位1。
模式2和3: 原理和模式1一样, 只是添加了一位奇偶校验位( 防止通信出错) 。,奇校验在D7后补充一位0或者1根据前面1的个数来补充,如果是奇校验且D0-D7之间1的个数为偶数则补1,如果是偶校验且D0-D7之间个数为奇数则补1.
3.单位: 波特率 = 位/字符× 字符/秒 = 位/秒。 波特率: 就是发送一位数据的速率, 即发送一个数据的持续时间 = 1/baud;
常用串口波特率:
300、 600、 1200、 2400、 4800、 9600、19200 ……115200;
SBUF:串口数据缓冲寄存器, 发送数据时, 只要将数据送入SBUF, 则单片机自动发送数据,接收数据时, 自动将数据接收至SBUF。
SCON串口控制寄存器
SM1与SM2为模式0时,波特率为时钟频率/12,比特率固定。
模式2与模式3时9位数据多一位奇偶校验位。
模式1为串口常用模式。
SM2: 使能模式2和3中的多机通信功能。 通常不使用。
REN: 使能串口接收。 由软件置1, 则允许串口接收数据; 由软件清零, 则禁止串口接收数据。
TB8,RB8: 方式2和方式3中的校验位, 分为四种方式A.偶校验;B.奇校验;C.强制为0;D.强制为1 。
TI: 发送中断标志, 在发送停止位时由硬件置1。必须通过软件才能清零。
RI: 接收中断标志, 接收停止位的中间时刻由硬件置1, 必须通过软件清零。
定时器TMOD:
模式2: 自动装载8位计数器。 主要应用在串口波特率发生器。
SMOD:波特率选择位。当用软件置位SMOD,即SMOD=1,则使串行通信方式1、2、3的波特率加倍;SMOD=0,则各工作方式的波特率不加倍。复位时SMOD=0。
一般选择不加倍,所以SMOD为0,SYSclk是单片机时钟,也就是晶振的频率,11.0592MHz,运算时要转化为基本单位Hz,即11059200Hz
定时器工作模式是8位自动重装载,TH1和TL1赋的初值一样。
定时器1使用自动重装模式, 即模式2。8位自动重装载,所以使用的是2^8=256
TH1 = TL1 = 256 - 11059200/(12 * 32 *9600)
TH1=TL1=0xFD;
实现步骤:
1、 将定时器1置为自动重装模式。
2、 将串口设置为方式1。
3、 根据公式计算出定时器1的初值。
4、 打开定时器1, 打开串口允许接受。
发送字符:
uint8 Buf[]="how are you!\n";
void delay(uint16 n)
{
while (n--);
}
/*
* UART初始化
* 波特率:9600
*/
void UART_init(void)
{
SCON = 0x50; // 10位uart,允许串行接受
TMOD = 0x20; // 定时器1工作在方式2(自动重装)
TH1 = 0xFD;
TL1 = 0xFD;
TR1 = 1;
}
/*
* UART 发送一字节
*/
void UART_send_byte(uint8 dat)
{
SBUF = dat;
while (TI == 0);
TI = 0;
}
/*
* UART 发送字符串
*/
void UART_send_string(uint8 *buf)
{
while (*buf != '\0')
{
UART_send_byte(*buf++);
}
}
void main()
{
UART_init();
while (1)
{
UART_send_string(Buf);
delay(20000);
}
}
void main()
{
unsigned char i;
EA = 1; //使能总中断
ConfigTimer0(1); //配置T0定时1ms
ConfigUART(9600); //配置波特率为9600
while (1)
{
//将接收字节在数码管上以十六进制形式显示出来
disbuf[0] = ucDataOneTab[RxdByte >> 4];
disbuf[1] = ucDataOneTab[RxdByte & 0x0F];
for (i = 0; i < 8; i++ )
{
SendData(disbuf[i], ucDataTwoTab[i]);
Delay1ms(1);
}
}
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
SCON = 0x50; //配置串口为模式1
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x20; //配置T1为模式2
TH1 = 256 - (11059200/12/32)/baud; //计算T1重载值
TL1 = TH1; //初值等于重载值
ET1 = 0; //禁止T1中断
ES = 1; //使能串口中断
TR1 = 1; //启动T1
}
/* UART中断服务函数 */
void InterruptUART() interrupt 4
{
if (RI) //接收到字节
{
RI = 0; //手动清零接收中断标志位
RxdByte = SBUF; //接收到的数据保存到接收字节变量中
SBUF = (RxdByte>>0x04)+(RxdByte&0x0f); //接收到的数据的高位低位相加返回
//用以提示用户输入的信息是否已正确接收
}
if (TI) //字节发送完毕
{
TI = 0; //手动清零发送中断标志位
}
}
超级简单,不扩展了。。。。
ASCII表:
知识点一:
PCON只有第七位与串口通信波特率有关, 置1可以加倍串口通信波特率。 PCON |= 0x80; ====> 9600*2=19200
知识点二:
关于UART数据读取的知识点: CPU读取RXD数据时, 采样(读取数据)速度为波特率的16倍。 在第7、 8、 9计数状态时, 采样此时的RXD数值, 取值为三个采样值的多数(即至少2次)作为读取的数值。 波特率9600为例:
7、8、9中有2个1则为高电平,有两个0则为低电平。
对于起始位来说, 下跳沿开始读数, 但会通过7、 8、 9的采样来确定这个数是不是0, 如果不是, 则复位接收电路。
用单片机IO口模拟串口
如果需要两个串口, 但我们知道一般的51系列D单片机只提供一个串口, 那么另一个串口只能靠
普通IO口模拟。
置1或0分别代表高低电平, 也就是串口通信中所说的位, 如起始位用低电平, 则将其置0, 停止位为高电平, 则将其置1, 各种数据位则根据情况置1或置0。
设波特率为9600,则发送一位bit数据需要1/9600=104us
设计思路:数据发送时使用定时器判断IO的电平翻转。
1/9600 = (65536-x)12/11059200
x =65536 -(11059200/960012)
x = 65440 = 0xFFA0
实测在140us需要根据实际进行调节。
实际调整完后X=0xFFBB接近于104us
//发送部分
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
uchar Send_Value[1] = {0x59};
void Send_Data(bit i)
{
if(i != 0) TXD = 1;
else TXD = 0;
TR0 = 1; //开启定时
while(!TF0); //判断是否到达发送结束时间104us
TR0 = 0; //停止定时器0计数
TH0 = 0xFF; //重赋初值
TL0 = 0xBB;
TF0 = 0; //清定时器溢出中断
}
main()
{
uchar a = 0;
uchar i;
uint j;
TMOD |= 0x01;
TH0 = 0xFF; //定时初值
TL0 = 0xBB;
TXD = 1; //将发送端置高
while(1)
{
TF0 = 0;//清定时器溢出
Send_Data(0);//发送起始位
a = Send_Value[0];
for(i=0;i<8;i++) //依次发送8位数据
{
Send_Data((bit)(a&0x01));//强制类型转换先发送最低位
a = a>>1; //向右移动一位
}
Send_Data(1); //停止位
for(j=0;j<50000;j++); //延时发送
}
}
接收部分:
串口发送数据时起始位为低电平。
源代码部分:
接收一个ASCII ,将接收的数据再发送出去。
#include <reg52.h>
typedef unsigned char uint8;
typedef unsigned int uint16;
sbit RXD2 = P3^0;
sbit TXD2 = P3^1;
#define MCLK 11059200UL
#define BAUD_RATE 9600UL
#define WAIT_TIME()
do
{
while(!TF0);
TF0 = 0;
}while(0)
/*
* 定时器初始化
*/
void timer0_init(void)
{
TMOD &= 0xF0;
TMOD |= 0x02; //定时器0,方式2
TH0 = 256 - MCLK/(12*BAUD_RATE);
TL0 = TH0;
TF0 = 0;
TR0 = 0;
}
/*
* UART发送
*/
void UART_send_byte(uint8 dat)
{
uint8 len=8;
// 1. start bit
TL0 = TH0;
TR0 = 1;
TXD2 = 0;
WAIT_TIME();
// 2. DAT_LEN bit data
while (len--)
{
TXD2 = (bit)(dat & 0x01); //先发送低位
WAIT_TIME();
dat = dat>>1;
}
// 3. stop bit
TXD2 = 1;
WAIT_TIME();
TR0=0;
}
/*
* UART接收
*/
uint8 UART_rev_byte(void)
{
uint8 len=8, dat=0;
while (RXD2);
// 1. jump start bit
TR0 = 1;
TL0 = 256 - MCLK/(12*BAUD_RATE)/2; //调到起始位的中间7/8/9位进行采集 1/2的9600 19200
WAIT_TIME(); //起始位结束
WAIT_TIME(); //调到起始位的中间7/8/9位进行采集
// 2. receive DAT_LEN bit data
while (len--)
{
dat >>= 1;
if (RXD2)
dat |= 0x80;
WAIT_TIME();
}
// 3. wait stop bit
WAIT_TIME();
TR0 = 0;
return dat;
}
main()
{
uint8 a;
timer0_init();
while (1)
{
UART_send_byte('i');
UART_send_byte('n');
UART_send_byte('p');
UART_send_byte('u');
UART_send_byte('t');
UART_send_byte(':');
a = UART_rev_byte();
UART_send_byte(a);
UART_send_byte('\r');
UART_send_byte('\n');
}
}