目录
1. 概述
2.串口介绍
2.1 原理框图
2.2 RS-232C
2.3 RS-422
2.4 RS-485
2.5 UART
3. STM32 USART介绍
4. CubeMx生成Uart初始化代码
4.1 NewProject选择单片机型号
4.2 设置rcc时钟
4.3 设置Usart
4.4 初始化代码
4.5 注意
5 工程源码解析
5.1 程序架构
5.2 源码
fml_ring_buffer.c
fml_usart.c
app_usart_task.c
stm32f4xx_it.c
main.c
6. 效果
7. 源码分享
在原子阿波罗探索者开发板(芯片STM32F429XX)上实现RS232串口通信功能,工程框架为UCOSIII+串口空闲中断实现数据接收发送。
串行接口(Serial Interface)简称串口,也称串行通信接口或串行通讯接口。是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。串行通讯的特点是:数据位的传送,按位顺序进行,根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。串行接口按电气标准及协议来分,包括TTL、RS-232、RS-422RS-422RS-422、RS485等,这4种串口只在电气信号上有差别,在帧格式,传输逻辑和软件操作上基本都是一样的。在实际项目应用中会有些差别。经常有人把RS-232、RS-422、RS-485 误称为通讯协议,这是不对的,它们仅是关于UART通讯的一个机械和电气接口标准。
以STM32单片机串口通信RS-232C为例,电原理图如下:
CPU——STM32的ARM内核,通过内部数据总线、地址总线、控制总线和UART连接。
UART——STM32的外设通用异步收发传输器,作为ARM内核与STM32外部串行数据交互的桥梁。
RS-232电平转换——RS-232 线路驱动器/接收器,如MAX232。将STM32的UART输出电平变换为RS-232C电平。
RS-232采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15米,最高速率为20kb/s。
典型的RS-422是四线接口。实际上还有一根信号地线,共5根线。其DB9连接器引脚定义。由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Slave),从设备之间不能通信,所以RS-422支持点对多的双向通信。接收器输入阻抗为4k,故发端最大负载能力是10×4k+100Ω(终接电阻)。RS-422的最大传输距离为1219米,最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100米长的双绞线上所能获得的最大传输速率仅为1Mb/s。
RS-485是从RS-422基础上发展而来的,所以RS-485许多电气规定与RS-422相仿。如都采用平衡传输方式、都需要在传输线上接终端电阻等。RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,而采用四线连接时,与RS-422一样只能实现点对多的通信,即只能有一个主(Master)设备,其余为从设备,但它比RS-422有改进,无论四线还是二线连接方式总线上可多接到32个设备。RS-485与RS-422的不同还在于其共模输出电压是不同的,RS-485是-7V至+12V之间,而RS-422在-7V至+7V之间,RS-485接收器最小输入阻抗为12kΩ、RS-422是4kΩ;由于RS-485满足所有RS-422的规范,所以RS-485的驱动器可以在RS-422网络中应用。
RS-485与RS-422一样,其最大传输距离约为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在很短的距离下才能获得最高速率传输。一般100米长双绞线最大传输速率仅为1Mb/s。
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称为UART)是一种异步收发传输器,是硬件范畴,将数据通过串行通讯进行传输。它在发送端执行并行到串行数据转换,在接收端执行串行到并行数据转换。它是通用的,因为传输速度、数据速度等参数是可配置的。
UART和USART都是单片机上的通用串口,UART是通用异步收/发器,USART是通用同步/异步收/发器。USART在UART基础上增加了同步功能,当我们在异步通信的时候,USART与UART没有什么区别。
STM32可通过对 USART_CR1 寄存器中的 M 位进行编程来选择 8 位或 9 位的字长。帧详细信息如下图:
选择RCC时钟为外部晶振,如图所示。
设置HCLK为180M,选择Clock Configuration选项卡,按下图中标注位置配置时钟。
本文是在探索者阿波罗板上实现串口调试,USART3发送引脚为PB10 ,接收引脚为PB11。选择USART3为异步模式,如下图:
设置USART3,如下图:
USART初始化相关函数如下表所示。
序号 |
函数名称 |
函数功能说明 |
1 |
MX_GPIO_Init() |
USART相关引脚时钟使能 |
2 |
MX_USART3_UART_Init() |
USART3配置,波特率、帧位数、校验等 |
3 |
HAL_UART_MspInit() |
UART底层硬件IO初始化,中断优先级设置 |
4 |
HAL_UART_MspDeInit() |
UART底层硬件IO复位 |
具体代码如下:
MX_GPIO_Init(),初始化GPIO时钟。
void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
}
MX_USART3_UART_Init(),配置串口波特率、数据格式等。
void MX_USART3_UART_Init(void)
{
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
HAL_UART_MspInit(),串口时钟、GPIO、中断初始化。
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(uartHandle->Instance==USART3)
{
/* USER CODE BEGIN USART3_MspInit 0 */
/* USER CODE END USART3_MspInit 0 */
/* USART3 clock enable */
__HAL_RCC_USART3_CLK_ENABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USART3 interrupt Init */
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
/* USER CODE BEGIN USART3_MspInit 1 */
/* USER CODE END USART3_MspInit 1 */
}
}
HAL_UART_MspDeInit(),串口复位函数。
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART3)
{
/* USER CODE BEGIN USART3_MspDeInit 0 */
/* USER CODE END USART3_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART3_CLK_DISABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_11);
/* USART3 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART3_IRQn);
/* USER CODE BEGIN USART3_MspDeInit 1 */
/* USER CODE END USART3_MspDeInit 1 */
}
}
CubeMx生成的初始化代码中,,发送数据没有问题,接收数据需要字节编写串口接收回调函数,并打开串口接收中断。
本文介绍的串口收发程序采用UCOSIII+空闲中断代码实现串口收发数据,串口初始化程序根据CubeMx生成的代码改变,经测试稳定可靠。
软件进行分层设计,分为应用层、模块层和硬件层等,和Uart相关文件及所在层如下表:
层名称 | 文件名 | 备注 |
应用层(APP) | main.c stm32f4xx_it.c app_usart_task.c |
|
模块层(FML) | fml_usart.c fml_ring_buffer.c |
|
硬件层(HAL、HDL) | HAL库 |
该模块为循环缓冲区模块,主要实现数据的缓冲。源码见工程文件,这里不再赘述。
该模块主要完成串口的初始化、配置等。声明中断回调函数,完成数据接收及初步处理工作。
序号 | 函数名称 | 函数功能说明 |
---|---|---|
1 | MX_USART3_UART_Init() | USART3配置,波特率、帧位数、校验等 |
2 | HAL_UART_MspInit() | UART底层硬件IO初始化,中断优先级设置 |
3 | HAL_UART_MspDeInit() | UART底层硬件IO复位 |
具体代码如下:
void MX_USART3_UART_Init(void)
{
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK)
{
//_Error_Handler(__FILE__, __LINE__);
}
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); //使能空闲中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE); //使能接收中断
//创建缓冲区
FML_ring_buffer_create(&ring_buffer_usart3, buffer_usart3, RING_BUFFER_USART3_SIZE);
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(uartHandle->Instance==USART3)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_USART3_CLK_ENABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USART3 interrupt Init */
HAL_NVIC_SetPriority(USART3_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(USART3_IRQn);
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART3)
{
/* Peripheral clock disable */
__HAL_RCC_USART3_CLK_DISABLE();
/**USART3 GPIO Configuration
PB10 ------> USART3_TX
PB11 ------> USART3_RX
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_11);
/* USART3 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART3_IRQn);
}
}
该应用为串口数据处理业务模块,主要是将接收到数据按照协议解析,并完成相应业务处理。主要函数如下:
void APP_usart_task(void *p_arg)
{
#define USART_RX_LEN 200
OS_ERR err;
CPU_TS ts;
static uint8_t USART_RxBuffer[USART_RX_LEN]; //缓冲区
uint16_t len;
uint8_t RxByteBuffer;
uint8_t RxByteCount = 0; //一般指令按字节查询计数
while(1)
{
//请求信号量,等待串口总线接收数据
OSTaskSemPend(0,OS_OPT_PEND_BLOCKING,&ts,&err);
while(1)
{
len = FML_ring_buffer_get_buf_used(&ring_buffer_usart3); //获取接收到的数据长度
if(len ==0) break;
FML_ring_buffer_read(&RxByteBuffer,1,&ring_buffer_usart3); //读1个字节
/*********************@@--检测HEAD1,HEAD2,字节0 1 ******************/
if(RxByteCount<= 1) //接收HEAD
{
if(RxByteBuffer==APP_USART_HEAD1 && RxByteCount==0) //收到HEAD1
{
USART_RxBuffer[0] = RxByteBuffer;
RxByteCount++;
}
else if(RxByteBuffer==APP_USART_HEAD2 && RxByteCount==1) //收到HEAD2
{
USART_RxBuffer[1] = RxByteBuffer;
RxByteCount++;
}
else //未收到“连续HEAD1/HEAD2”指令,重新开始
{ RxByteCount=0; }
}
/**********************@@--接收数据长度,字节2 **********************/
else if(RxByteCount==2) //接收数据长度+剩余数据
{
USART_RxBuffer[2]=RxByteBuffer ; //接收数据长度
RxByteCount++;
if(USART_RxBuffer[2]>(USART_RX_LEN-3) | USART_RxBuffer[2]<3) //指令长度错误
{
RxByteCount=0;
}
else //接收剩余数据
{
len = FML_ring_buffer_get_buf_used(&ring_buffer_usart3); //获取缓冲区剩余数据长度
if(len >= USART_RxBuffer[2]-3)
{
FML_ring_buffer_read(&USART_RxBuffer[3],USART_RxBuffer[2]-3,&ring_buffer_usart3); //读该条指令剩余数据
APP_UsartCommonCmdDataProcess(USART_RxBuffer, USART_RxBuffer[2]); /**@@调用一般指令处理程序*/
RxByteCount=0;
}
else //数据长度错误
{
RxByteCount=0;
}
len = FML_ring_buffer_get_buf_used(&ring_buffer_usart3); //获取接收到的数据长度
if(len == 0) break; //处理完成
else continue; //继续处理
}
}
}//while(1)
}//while(1)
}//app_usart_task
//串口接收完成回调函数
void FML_USART3_RxCpltCallback(void)
{
OS_ERR err;
OSTaskSemPost(&AppUsartTaskTCB,OS_OPT_POST_FIFO,&err); //发送信号量
}
该模块主要声明中断入口,并通过回调函数调用模块层的函数,由模块层完成数据接收及初步处理。和串口相关代码如下:
//串口USART3中断
void USART3_IRQHandler(void)
{
OSIntEnter();
FML_USART3_IRQHandler();
OSIntExit();
}
完成功能模块层的初始化,应用层业务模块初始化,创建任务流程。源代码详见工程文件。主要函数如下:
//创建串口任务
OSTaskCreate((OS_TCB* )&AppUsartTaskTCB,
(CPU_CHAR* )"APP Usart task",
(OS_TASK_PTR )APP_usart_task,
(void* )0,
(OS_PRIO )USART_TASK_PRIO,
(CPU_STK* )&USART_TASK_STK[0],
(CPU_STK_SIZE)USART_STK_SIZE/10,
(CPU_STK_SIZE)USART_STK_SIZE,
(OS_MSG_QTY )5, //任务消息数5条
(OS_TICK )0,
(void* )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR* )&err);
经工程设置为收到串口数据自动应答模式,经实测,通过串口调试助手间隔1mS向单片机发送数据,无丢帧现象。
下载地址:STM32基于RTOS和空闲中断实现的串口通信程序