【STM32】-串口开发经验分享-基于RTOS+空闲中断

目录

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. 源码分享

1. 概述    

        在原子阿波罗探索者开发板(芯片STM32F429XX)上实现RS232串口通信功能,工程框架为UCOSIII+串口空闲中断实现数据接收发送。

2.串口介绍

        串行接口(Serial Interface)简称串口,也称串行通信接口或串行通讯接口是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。串行通讯的特点是:数据位的传送,按位顺序进行,根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。串行接口按电气标准及协议来分,包括TTL、RS-232、RS-422RS-422RS-422、RS485等,这4种串口只在电气信号上有差别,在帧格式,传输逻辑和软件操作上基本都是一样的。在实际项目应用中会有些差别。经常有人把RS-232、RS-422、RS-485 误称为通讯协议,这是不对的,它们仅是关于UART通讯的一个机械和电气接口标准。

2.1 原理框图

        以STM32单片机串口通信RS-232C为例,原理图如下:

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第1张图片

        CPU——STM32ARM内核,通过内部数据总线、地址总线、控制总线和UART连接。

        UART——STM32的外设通用异步收发传输器,作为ARM内核与STM32外部串行数据交互的桥梁。

        RS-232电平转换——RS-232 线路驱动器/接收器,如MAX232。将STM32UART输出电平变换为RS-232C电平。

2.2 RS-232C

        RS-232采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15米,最高速率为20kb/s

2.3 RS-422

        典型的RS-422是四线接口。实际上还有一根信号地线,共5根线。其DB9连接器引脚定义。由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Slave),从设备之间不能通信,所以RS-422支持点对多的双向通信。接收器输入阻抗为4k,故发端最大负载能力是10×4k+100Ω(终接电阻)。RS-422的最大传输距离为1219米,最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100米长的双绞线上所能获得的最大传输速率仅为1Mb/s。

2.4 RS-485

        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

2.5 UART

         通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称为UART)是一种异步收发传输器,是硬件范畴,将数据通过串行通讯进行传输。它在发送端执行并行到串行数据转换,在接收端执行串行到并行数据转换。它是通用的,因为传输速度、数据速度等参数是可配置的。

3. STM32 USART介绍

        UART和USART都是单片机上的通用串口,UART是通用异步收/发器,USART是通用同步/异步收/发器。USART在UART基础上增加了同步功能,当我们在异步通信的时候,USART与UART没有什么区别。

STM32可通过对 USART_CR1 寄存器中的 M 位进行编程来选择 8 位或 9 位的字长。帧详细信息如下图:

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第2张图片

4. CubeMx生成Uart初始化代码

4.1 NewProject选择单片机型号

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第3张图片

4.2 设置rcc时钟

        选择RCC时钟为外部晶振,如图所示。

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第4张图片

         设置HCLK为180M,选择Clock Configuration选项卡,按下图中标注位置配置时钟。

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第5张图片

 4.3 设置Usart

        本文是在探索者阿波罗板上实现串口调试,USART3发送引脚为PB10 ,接收引脚为PB11。选择USART3为异步模式,如下图:

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第6张图片

        设置USART3,如下图:

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第7张图片

4.4 初始化代码

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 */
  }
} 

4.5 注意

        CubeMx生成的初始化代码中,,发送数据没有问题,接收数据需要字节编写串口接收回调函数,并打开串口接收中断。

5 工程源码解析

        本文介绍的串口收发程序采用UCOSIII+空闲中断代码实现串口收发数据,串口初始化程序根据CubeMx生成的代码改变,经测试稳定可靠。

5.1 程序架构

        软件进行分层设计,分为应用层、模块层和硬件层等,和Uart相关文件及所在层如下表:

层名称 文件名 备注
应用层(APP)

main.c

stm32f4xx_it.c

app_usart_task.c

模块层(FML)

fml_usart.c

fml_ring_buffer.c

硬件层(HAL、HDL) HAL库

5.2 源码

fml_ring_buffer.c

        该模块为循环缓冲区模块,主要实现数据的缓冲。源码见工程文件,这里不再赘述。

fml_usart.c

        该模块主要完成串口的初始化、配置等。声明中断回调函数,完成数据接收及初步处理工作。

序号 函数名称 函数功能说明
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);
    }
}

app_usart_task.c

        该应用为串口数据处理业务模块,主要是将接收到数据按照协议解析,并完成相应业务处理。主要函数如下:

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);    //发送信号量
}

stm32f4xx_it.c

        该模块主要声明中断入口,并通过回调函数调用模块层的函数,由模块层完成数据接收及初步处理。和串口相关代码如下:

//串口USART3中断
void USART3_IRQHandler(void)
{
    OSIntEnter();  
    FML_USART3_IRQHandler();       
    OSIntExit(); 
}

main.c

        完成功能模块层的初始化,应用层业务模块初始化,创建任务流程。源代码详见工程文件。主要函数如下:

//创建串口任务
	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);

6. 效果

        经工程设置为收到串口数据自动应答模式,经实测,通过串口调试助手间隔1mS向单片机发送数据,无丢帧现象。

【STM32】-串口开发经验分享-基于RTOS+空闲中断_第8张图片

7. 源码分享

        下载地址:STM32基于RTOS和空闲中断实现的串口通信程序

你可能感兴趣的:(STM32进阶,stm32,单片机,嵌入式硬件)