学习某一个东西,我们首先要了解这个东西的定义是什么,用来干什么的,怎么用,用的过程中有什么注意事项,这些都OK了,那么我们就算是基本掌握他了。
0 前言
? USART–通用同步/异步串行接收/发送器,通用同步/异步串行接收/发送器USART是一个全双工通用同步/异步串行收发模块。
? UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。
? 简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。
? 串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
? 异步通信以一个字符为传输单位,通信中两个字符间的时间间隔多少是不固定的,然而在同一个字符中的两个相邻位间的时间间隔是固定的。
1.USART
1.1概述
在STM32的参考手册中,串口被描述成通用同步异步收发器(USART),它提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互联网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。还可以使用DMA方式,实现高速数据通信。
发送和接收共用的可编程波特率,最高达4.5Mbits/s
1.2 USART的框图
1.2.1 串口的硬件接线
RX: 接受数据串行输入。通过过采样技术来区别数据和噪音,从而恢复数据。
TX: 发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。具体智能卡模式请查看《STM32F10X数据手册》。
irDA_OUT:irDA模式下的数据输出。
irDA_IN:irD模式下的数据输入 。具体irDA模式请查看《STM32F10X数据手册》。
nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时, nRTS 将被设置为高电平停止接收。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。具体硬件流模式请查看《STM32F10X数据手册》。
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
想要与串口实现双向通信硬件上面至少需要3根线:**接受数据输入(RX)和发送数据输出(TX)以及GND。**如下图所示。
1.2.2串口的数据发送和接收
1、一个完整的串口字符包括:
1位起始位
8位数据位
1位可选的奇偶检验位
0.5-2位的停止位
从框架图可知,串口的数据发送依靠数据发送寄存器,数据接收依靠数据接收寄存器。当数据发送的时候,数据发送寄存器就会将数据移位到移位寄存器中进行发送;当数据接收的时候,先接收到接收移位寄存器中,在放入数据接收寄存器中。所以有**串口的发送和接受都应该是空闲开始的。**在发送和接收的过程中都可以设置相应的位来产生相应的中断。
数据发送时,USART_CR1寄存器的发送使能位TE会被置1,启动数据发送,发送移位寄存器的数据会在TX引脚输出,低位在前,高位在后。起始位是一个位周期的低电平,位周期就是每一位占用的时间。
数据接收时,USART_CR1寄存器的RE位会被置1,使能USART接收,接收器在RX线开始搜索起始位。在确定起始位后,就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器的数据移到接收数据寄存器中,并把USART_SR寄存器的RXNE位置。如果USART_CR2寄存器的RXNEIE置1可以产生中断。
一般我们串口中断的方式来传输数据,这样可以保证数据传输的实时性。下面是几个带标志的中断源:
中断事件 | 事件标志 | 使能控制位 |
---|
发送寄存器为空 | TXE | TXIEI | CTS标志 | CTS | CTSIE | 发送完成 | TC | TCIE | 准备好读取接收数据 | RXNE | RXNEIE | 检测到上溢错误 | ORE | OREIE | 检测到空闲线路 | IDLE | IDLE | 奇偶检验 | PE | PEIE | 断路标志 | LBD | LB | 多缓冲区通信中的噪声标志、 上溢错误和帧错误 | NF、ORE、FE | EIE |
2、串口通讯协议设计
(1)制定串口通信协议(推荐)
一般串口完整数据帧的定义:帧头(2字节,例如AA、BB) + 数据长度(2字节) +命令(1字节)+ 数据 + CRC16校验(2字节) + 帧尾(可选的)(2字节)
判断接收到帧头后,开启定时器,在定时器计数时间到还未接收到完整的数据帧,就把这包数据丢弃
完整接收到数据帧后(字节长度 ,判断校验,判断帧尾部),就可以执行对应的命令
(2)空闲中断
? 通过空闲中断标志来识别一帧的结束, 无需制定通信协议,但是可能会出现帧粘包、帧数据丢失、帧数据延迟等问题
(3)根据接收到的字符之间的间隔进行判断
? 其实我不是很理解这个地方为什么会被带起来这种方案,这是设计通信不太靠谱的方案。。。 各种风险无法保证帧的完整 ,虽然可以做,当做扩展一下,但是不要带偏了。
2.使用示例
2.1 USART配置
使用官方提供的固件库配置usart
.c文件内容
#include "usart.h"
#include "stdio.h"
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
/**
* @brief 串口1和串口2初始化
* @param NONE
* @retval none
*/
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA| RCC_APB2Periph_AFIO, ENABLE);
//
/*PA2 PA3 USART2*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART2 Tx (PA.02) as alternate function */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*USART1*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Tx (PA.09)*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief 串口1和串口2初始化
* @param NONE
* @retval none
*/
void USART_Configuration(void)
{
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
USART_DeInit(USART1);
/* Configure USART1 */
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART1 */
USART_Init(USART1, &USART_InitStructure);
USART_DeInit(USART2);
/* Configure USART2 */
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART2 */
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_TC, ENABLE);//开启串口发送中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE);
USART_ITConfig(USART2, USART_IT_TC, ENABLE);//开启串口发送中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART2, ENABLE);
}
/**
* @brief 串口1和串口2中断初始化
* @param NONE
* @retval none
*/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//开启串口发送中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
/
//Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);//开启串口发送中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
}
void board_init(void)
{
GPIO_Configuration()
USART_Configuration();
NVIC_Configuration();
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断是否真的发生了中断
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
printf("串口1接收测试\n");
//在这里可以做入队操作
//入队一定要保准数据完整的进入队列不能被中断打断 所以此处入队操作必须关闭中断
//然后在主函数或者其他地方去处理
//入队完成后不要忘记打开中断
}
else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_TC);
printf("串口1发送测试\n");
}
}
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //判断是否真的发生了中断
{
printf("串口2接收测试\n");
USART_ClearITPendingBit(USART2, USART_IT_RXNE);
}
else if(USART_GetITStatus(USART2, USART_IT_TC) != RESET)
{
printf("串口2发送测试\n");
USART_ClearITPendingBit(USART2, USART_IT_TC);
}
}
.h文件内容
#ifndef __USART_H
#define __USART_H
void board_init(void);
#endif
mian.c文件内容
void main( void )
{
while(1)
{
if()//判断队列
{
//出队操作
//解析协议
//执行串口命令
}
}
}
3.使用总结
1、在程序设计中,可能会设计到串口接收完,但是无法及时处理的问题,这个时候我们可以在中断中将接收的消息入队,任务空闲再来处理串口数据
2、使用空闲中断来识别一帧,如果数据因为线路上面的延迟而造成数据推迟到达,这样会造成一包数据不完整,但是这种操作可以使用在实时性不高的操作中,比如在串口工具接收发送显示时候
3、在设计串口通信协议的时候,要考虑可能存在粘包、丢包、丢字节、接收到帧头但数据却延时接收等的问题
4、对于大多数UART来说,内部发送缓冲器”空”会产生中断或置对应的标志位,但此时数据不一定真的发送完成,因为数据有可能还在输出移位寄存器中。如果仅靠发送缓冲的状态来判断一包数据是否发送完成,从而决定是否关闭内部UART的发送使能和接口的发送使能,则这个数据包的最后一个字节将不会发送到总线上,以致对方会少接收一一个字节。
5、波特率设置太高会有误码的情况,
|