一、串口通信协议原理
串口在嵌入式系统当中是一类重要的数据通信接口,其本质功能是作为 CPU 和串行设备间的编码转换器。当数据从 CPU 经过串行端口发送出去时,字节数据转换为串行的位;在接收数据时,串行的位被转换为字节数据。应用程序要使用串口进行通信,必须在使用之前向操作系统提出资源申请要求(打开串口),通信完成后必须释放资源(关闭串口)。典型地,串口用于 ASCII 码字符的传输。通信使用3根线完成:地线,发送数据线,接收数据线。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:波特率是一个衡量通信速度的参数,它表示每秒钟传送的 bit 的个数;数据位是衡量通信中实际数据位的参数,当计算机发送一个信息包,标准的值是 5,7 和 8 位。如何设置取决于你的需求;停止位用于表示单个包的最后一位,典型的值为 1,1.5和 2 位,停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会;奇偶校验位是串口通信中一种简单的检错方式,有四种检错方式——偶、奇、高和低,也可以没有校验位。
对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。
物理层:物理层上我们这里拿出标准的RS232串口和USB转串口进行分析、讲解。在物理层上我们用到最多的还是USB转串口。
RS232:
在原理图上我们可以看到右侧RS232串口上两个通讯设备的“DB9 接口”之间通过串口信号线建立起连接,串口信号线中使用“RS-232 标准”传输数据信号。由于 RS-232 电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片(MAX3232CSE)”转换成控制器能识别的“TTL 校准”的电平信号才能实现通讯。
我们知道常见的电子电路中常使用 TTL 的电平标准,理想状态下,使用 5V 表示二进制逻辑 1,使用 0V 表示逻辑 0;而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V 表示逻辑 1,+15V 表示逻辑 0。由于控制器一般使用 TTL 电平标准,所以常常会使用 MA3232 芯片对 TTL 及 RS-232 电平的信号进行互相转换。即:
USB转串口:
这个通讯方式是在单片机中最经常运用到的通讯方式。USB转串口主要用于设备跟电脑通信。
这里原理图上用到的电平转换芯片主要是CH340G。一般我们主要运用的电平转换芯片有:CH340、PL2303、CP2102、FT232。
协议层:我们在使用串口的时候主要还是用到“通用异步通讯”——可以参考通讯基本概念基础。这篇讲解了通讯的一些基本原理。所以在协议层上就不多讲解这个概念问题了。
协议层是指串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。如图所示:
二、usart功能框图
在功能框图上把其划分为四个部分——GPIO引脚、数据寄存器、控制器、波特率。我们在这里就主要讲解前三大功能,波特率一般可以在stm32上自主设置波特率的值(一般常用9600、15200)。
GPIO引脚:在功能框图上对应的1框图上我们看到主要有Tx、Rx、SW_RX、nRTS、nCTS、SCLK六个功能引脚,在stm32上这些功能引脚当中最常用的是Tx、Rx引脚。
TX:发送数据输出引脚。
RX:接收数据输入引脚。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚(很少用到)。
nRTS:请求以发送 (Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将nRTS引脚变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送 (Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测nCTS引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
注意:在stm32的UART 只是异步传输功能,所以没有SCLK、nCTS 和nRTS 功能引脚。
数据寄存器:数据寄存器控制usart的读取还是发送。在官方的参考手册上对于数据寄存器(USART_DR)是这样描述的——包含接收到数据字符或已发送的数据字符,具体取决于所执行的操作是“读取”操作还是“写入”操作。
因为数据寄存器包含两个寄存器,一个用于发送 (TDR),一个用于接收 (RDR),因此它具有 双重功能(读和写)。
对于写的时候我们数据寄存器选择TDR寄存器,在进行偏移0x04个位,在传输到发送数据传输引脚(Tx)。
在读取的时候原理同读取一样。
控制器:主要是由控制寄存器USART_CR1、USART_CR2、USART_CR3共同进行控制;在功能框图上我们可以发现其USART的控制器具有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。
发送器:当 USART_CR1 寄存器的发送使能位 TE 置 1 时,启动数据发送,发送移位寄存器的数据会在 TX引脚输出,如果是同步模式 SCLK 也输出时钟信号。一个字符帧发送需要三个部分:**起始位 + 数据帧 + 停止位。**起始位是一个位周期的低电平,位周期就是每一位占用的时间;数据帧就是我们要发送的 8 位或 9 位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电平。停止位时间长短是可以通过 USART 控制寄存器2(USART_CR2) 的 STOP[1:0] 位控制,可选 0.5个、1 个、1.5 个和 2 个停止位。默认使用 1 个停止位。2 个停止位适用于正常 USART 模式、单
线模式和调制解调器模式。0.5 个和 1.5 个停止位用于智能卡模式。
当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧 (一个数据帧长度的高电平),接下来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状态寄存器 (USART_SR) 的 TC 位为 1,表示数据传输完成,如果 USART_CR1 寄存器的 TCIE 位置1,将产生中断。
接收器:如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 1,同时如果USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。
三、stm32库的配置
主要的函数:
USART_Init(); //初始化串口
USART_ITConfig(); //中断配置函数
NVIC_Init(); //配置中断
USART_SendData(); //数据发送函数
USART_GetFlagStatus(); //接受标志位函数
USART_GetITStatus(); //发送标志位函数
USART_ClearITPendingBit();//清除中断预处理
USART_ReceiveData(); //数据接受函数
usart_init()
void USART2_Init(unsigned long ulBaud)
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitStruct.USART_BaudRate = ulBaud; //波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_InitStruct.USART_StopBits = USART_StopBits_1; //1位停止位
USART_InitStruct.USART_Parity = USART_Parity_No; //无校验位
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_InitStruct.USART_HardwareFlowControl= USART_HardwareFlowControl_None;//硬件流控制,一般选择none
USART_Init(USART2, &USART_InitStruct);
// 使能串口
USART_Cmd(USART2, ENABLE);
// 允许串口接受中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
// 允许串口中断
NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;//中断通道为串口2
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;//先站优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;//从占优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//使能
NVIC_Init(&NVIC_InitStruct);
}
发送一个字符
unsigned char USART_SendChar(USART_TypeDef* USARTx, unsigned char ucChar)
{
while(!USART_GetFlagStatus(USARTx, USART_FLAG_TXE));//不断检查标志位
USART_SendData(USARTx, ucChar);//发送字符数据
return ucChar;//返回发送的字符
}
发送字符串
void USART_SendString(USART_TypeDef* USARTx, unsigned char* pucStr)
{
while(*pucStr != '\0')
USART_SendChar(USARTx, *pucStr++);
}
接受中断
void USART2_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART2,USART_IT_RXNE) == SET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
temp = USART_ReceiveData(USART2);
if(temp == '\n')
{
RXCUNT = 0;
RXOVER = 1;
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
}else
{
RXBUF[RXCUNT] = temp;
RXCUNT++;
}
}
}
main函数
在main函数上要用到几个标志位
u8 RXBUF[20]; //储存数组
u8 RXCUNT=0; //接受计数
u8 RXOVER=0; //接受标准
int main(void)
{
USART2_Init(9600);
while(1)
{
if(RXOVER==1) //接受标志位
{
RXOVER=0; //标志清零
USART_ITConfig(USART2,USART_IT_RXNE, ENABLE); //清除中断标志位
if(RXBUF[0]=='C') //当发送“C”则打印1111
{
USART_SendString(USART2," 1111 ");
}
if(RXBUF[0]=='S')
{
USART_SendString(USART2," 2222 ");
}
memset(RXBUF,'\0',sizeof(RXBUF)); //清空数组
}
}
}
注意:在我们使用串口清空函数(memser)的时候头文件要加上#include"string.h"。不然会报错。
现象: