好记性不如烂笔头,以前总以为自己记性比较好,但事实总是一次一次的打我脸,刚开始学习单片机的时候在串口通信这一块发了一段时间,才将这一块弄懂了个七七八八,这几天回头想一下那一方面的知识,感觉忘得差不多了,现在重新理一遍,里面的程序大部分都是以前抄袭大佬的,但具体是哪一位大佬的博客现在也找不到了,希望大佬见谅。
首先了解串口通信先要熟悉SCON,PCON,TMOD三个寄存器
串口工作方式寄存器SCON,
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
功能 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
RI:接收中断标志位,数据接收结束时,标志位会自动置1,需要通过程序将其置0
TI:发送中断标志位,数据发送结束时,标志位会自动置1,需要通过程序将其置0
RB8:存放发送数据的第9位
TB8:存放接收数据的第9位
REN:串行接收允许位,0允许串行接收,1禁止串行接收
SM2:多机控制位
SM1,SM0:串行工作方式
SM0 | SM1 | 方式 | 说明 | 波特率 |
0 | 0 | 0 | 移位寄存器 | fosc/12 |
0 | 1 | 1 | 10位异步收发器(8位数据) | 可变 |
1 | 0 | 2 | 11位异步收发器(9位数据) | fosc/64或fosc/32 |
1 | 1 | 3 | 11位异步收发器(9位数据) | 可变 |
PCON寄存器
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
功能 | SMOD | - | - | - | - | - | - | - |
SOMD:波特率是否加倍选择位,0波特率不加倍,1波特率加倍
定时器工作方式寄存器TMOD
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
功能 | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
高四位为定时计数器1的设置,低四位是定时计数器0设置,串口通信波特率设置占用定时计数器1,这里主要说串口通信,不过多说定时计数器,只需要设置定时计数器1的工作方式即可
振荡周期:也称时钟周期(频率的倒数),单片机提供时钟信号的振荡源周期,频率一般有11.0592MHz,12MHz等
状态周期:是时钟周期的2倍,
机器周期:是包含6个状态周期,机器周期=1/单片机时钟频率
单片机时钟频率:是外部时钟的12分频,如果是12MHz的晶振,机器周期=1/单片机时钟频率=1/(12MHz/12)=12/12M=1us
这里一个机器周期为1us,若定时时间为1ms,则需要1000个机器周期,计算出初值;如果机器周期为2us,则只需要500个机器周期。
定时器初值计算:初值=(65536-机器周期数量)
波特率计算:
当串口工作在工作方式0和2是,波特率固定,方式0时fosc/12;方式2时fosc/32或fosc/64(根据SMOD判断)。
当串口工作在方式1时,波特率=(2^SMOD/32)*(单片机时钟频率/(256-X)),X是初值
C/T:定时器和计数器选择位,0为定时器,1为计数器
M1 | M0 | 工作方式 |
0 | 0 | 工作方式0:为13位定时/计数器 |
0 | 1 | 工作方式1:为16位定时/计数器 |
1 | 0 | 工作方式2:8位初值自动重装定时/计数器 |
1 | 1 | 工作方式3:仅适用于T0,分成两个8位计数器,T1停止计数 |
下面是程序结构和代码:
mian.c
#include "main.h" uchar UartRxBuffer[ 64 ] = { 0 }; //uart串口接收数据 uchar TX_Cnt = 0; //发送计数 uchar RX_Cnt = 0; //接收计数 bit TX_Busy = 0; //发送忙标志 void main() { uart_init(); while(1) { if((TX_Cnt!=RX_Cnt)&&(!TX_Busy)) { //将数组中的数据放入到发送缓冲区 SBUF=UartRxBuffer[TX_Cnt]; if(++TX_Cnt>=64) { TX_Cnt=0; } TX_Busy==1; } } } /************************************************* 函数:串口程序 功能:将接收到的数据存入到UartRxBuffer *************************************************/ void UART_INT (void) interrupt 4 { if(RI) { RI = 0; UartRxBuffer[RX_Cnt] = SBUF;//将数据写入数组 //防止溢出,当计数=16时,将计数清零 if(++RX_Cnt >= 64){ RX_Cnt = 0; } } if(TI) { TI = 0; TX_Busy = 0; } }
mcu_uart.c
#include "mcu_uart.h" /************************************************* 函数:uart_init 功能:初始化串口 出口:void 入口:void *************************************************/ void uart_init() { SCON = 0x50;//设置串口工作方式1 TMOD = 0x20;//设置计数器工作方式2 PCON = 0x00;//即SMOD=1,波特率不加倍 TH1 = 0xFD;//计数器初值,波特率是9600,晶振为11.0592MHz TL1 = 0xFD; ES = 1;//打开接收中断 EA = 1;//打开总中断 TR1 = 1;//打开计数器 } /************************************************* 函数:uart_tx_byte 功能:串口发送一个字节 出口:void 入口:一个字节 *************************************************/ void uart_tx_byte(uchar str) { SBUF=str; while(!TI); TI==0; } /************************************************* 函数:uart_tx_string 功能:串口发送一个字符串 出口:void 入口:字符串数组 *************************************************/ void uart_tx_string(uchar *str) { while(*str!='\0') { uart_tx_byte(*str++); } } /************************************************* 函数:uart_rx_string 功能:串口接收一个字符串 出口:字符串的长度 入口:字符串数组 *************************************************/ uchar uart_rx_string( uchar* RxBuffer ) { uchar rxLength = 0; uint uartRxTimOut = 0x7FFF; while( uartRxTimOut-- ) { if( 0 != RI ) { RI = 0; *RxBuffer = SBUF; RxBuffer++; rxLength++; uartRxTimOut = 0x7FFF; } } return rxLength; }
mian.h
#ifndef __MAIN_H__ #define __MAIN_H__ #include "mcu_uart.h" #endif
mcu_uart.h
#ifndef __MCU_UART_H__ #define __MCU_UART_H__ #include "mcu_type.h" void uart_init(); #endif
mcu_type.h
#ifndef __MCU_TYPE_H__ #define __MCU_TYPE_H__ #include <reg52.h> /*无符号*/ typedef unsigned char uchar; typedef unsigned int uint; #endif