利用CubeMX和HAL库配置串口通讯

串口通讯

What?

串口,全称串行接口(serial sport)。主要英语串行是逐位传输数据
大家日常生活中的USB就是串行接口的一种,稍微装过机器的人可能也知道RS系列的连接器(RS-232等)
RS-232公头
对于大家熟悉的USB,全称为Universal Serial Bus(通用串行总线)。
USB接口是电脑主板上的一种四针接口,其中中间两个针传输数据,两边两个针给外设供电。
USB接口

How?

串口的传输方式也很简单,除了电源线之外,只需要两根线:TX和RX用于发送和接收。串口的通讯需要遵循通讯协议——UART。计算机不像人类一样,可以直接从一连串数据中区分出那些是有用的数据,那些是没用的数据,因此需要告诉计算机,哪些是数据,哪些可以忽略掉。这正是协议的作用。

Why?

HAL—CubeMX配置

接下来我们使用CubeMX结合HAL库,进行串口通讯的配置。

1CubeMX配置界过程串口引脚配置界面

通讯模式选择

Mode

Asynchronous:异步
Synchronous:同步
Single Wire(Half-Duplex):半双工
Multiprocessor Communication:多处理器通信
IrDA:红外线
LIN:LIN模式
SmartCard:智能卡

这里我们一般使用【异步】(Asynchronous)通讯模式

不选择硬件流

Disable

配置串口的基本信息:

比特率,字长,奇偶校验,停止位,数据传输方向,过采样
base
这个窗口是用于设置通讯协议的相关信息
这里波特率一定要和通讯设备的匹配(这里和上位机通讯,设置为115200)
字长设置为8bit,奇校验,停止位是1.
这部分【协议】就是置定一种规范,两边都遵守这个规范,才能进行交流
就比如两人交流不仅仅语言要一样,也就是协议一样(由起始位,字长,谈话内容,校验位,停止位组成)
而且语速也要保持一致,也就是比特率要一样(英语听力考试大家都懂),否则接收到的数据就是乱码。

设置时钟

比特率的设置,与【时钟】相关,这点很【重要】!
可以查看对应板子的数据手册(stm32f4xx)找到uart1挂在APB1上,这里我选择内部时钟源,把APB1时钟频率设置为42M。
时钟树的设置如下:
timeTree

需要【提醒】的是,如果选择外部时钟源一定要查看原理图的外部时钟源和CubeMX上的HSE是否相【一致】,我之前在配置串口时,一直是乱码,最后发现板子的外部时钟源是8M,而CubeMX外部时钟源默认是25M,导致乱码。

配置好外设和时钟树后,接下来生成代码即可。

对HAL库函数进行解读

使用到的库函数介绍:

学习HAL库的方法之一,就是读HAL库给出的文件
在Drivers/STM32F4xx_HAL_Driver中可以找到每个外设对应的HAL库手册
description

而手册中会解释HAL库的用法。
use
接下来会教我们如何使用HAL库:

The PPP HAL driver can be used as follows

大意就是教我们如何驱动此设备。

声明一个结构体变量

(#) Declare a UART_HandleTypeDef handle structure
 (eg. UART_HandleTypeDef huart1).

通过调用HAL_UART_MspInit() API来初始化UART的低级资源

(#) Initialize the UART low level resources by implementing the
 HAL_UART_MspInit() API:
    (##) Enable the USARTx interface clock.	使能时钟
    可以直接搜索:PPP_CLK即可快速在HAL库中定位

    (##) UART pins configuration:
    配置引脚
        (+++) Enable the clock for the UART GPIOs.
        (+++) Configure these UART pins (TX as alternate function pull-up,
         RX as alternate function Input).
    (##) NVIC configuration if you need to use interrupt process
     (HAL_UART_Transmit_IT()and HAL_UART_Receive_IT() APIs):
    如果使用中断的话需要配置发送中断和接收中断
        (+++) Configure the USARTx interrupt priority.
        (+++) Enable the NVIC USART IRQ handle.
    (##) DMA Configuration if you need to use DMA process
     (HAL_UART_Transmit_DMA()and HAL_UART_Receive_DMA() APIs):
     使用DMA的话需要配置DMA的发送和接收API
        (+++) Declare a DMA handle structure for the Tx/Rx stream.
        (+++) Enable the DMAx interface clock.
        (+++) Configure the declared DMA handle structure with the required
              Tx/Rx parameters.
        (+++) Configure the DMA Tx/Rx stream.
        (+++) Associate the initialized DMA handle to the UART DMA Tx/Rx handle.
        (+++) Configure the priority and enable the NVIC for the transfer complete
              interrupt on the DMA Tx/Rx stream.
        (+++) Configure the USARTx interrupt priority and enable the NVIC USART IRQ handle
              (used for last byte sending completion detection in DMA non circular mode)

配置波特率/字长/停止位/硬件流和传输模式

(#) Program the Baud Rate, Word Length, Stop Bit, Parity, Hardware
        flow control and Mode(Receiver/Transmitter) in the huart Init structure.

至此,上面的初始化配置已经被CubeMX配置完毕,接下来就是在生成的代码中进行用户层面的编写。

根据模式进行对应的初始化

如果是异步模式,调用HAL_UART_Init()进行初始化

(#) For the UART asynchronous mode, initialize the UART registers by calling
    the HAL_UART_Init() API.

如果是半双工模式,调用HAL_HalfDuplex_Init()进行初始化

(#) For the UART Half duplex mode, initialize the UART registers by calling
    the HAL_HalfDuplex_Init() API.

如果是LIN模式,调用HAL_LIN_Init() 进行初始化

(#) For the LIN mode, initialize the UART registers by calling 
the HAL_LIN_Init() API.

如果是多设备模式,调用HAL_MultiProcessor_Init()进行初始化

(#) For the Multi-Processor mode, initialize the UART registers by calling
    the HAL_MultiProcessor_Init() API.

具体某种中断的开启通过宏定义,在传输和接收的过程中进行动态管理。

 (@) The specific UART interrupts (Transmission complete interrupt,
        RXNE interrupt and Error Interrupts) will be managed using the macros
        __HAL_UART_ENABLE_IT() and __HAL_UART_DISABLE_IT() inside the transmit
        and receive process.

这些初始化API最后也将通过MSP初始化函数来配置低级资源,MSP初始化函数自己可以进行编写。

 [..]
   (@) These APIs (HAL_UART_Init() and HAL_HalfDuplex_Init()) configure also the
        low level Hardware GPIO, CLOCK, CORTEX...etc) by calling the customized
        HAL_UART_MspInit() API.

回调函数激活

Callback registration
回调注册函数,简单来讲,就是在发生某个特定事件后会进入这个函数,从而完成一些自定义的操作。

将宏定义USE_HAL_UART_REGISTER_CALLBACKS 设为1,将允许动态地配置回调函数

[..]
The compilation define USE_HAL_UART_REGISTER_CALLBACKS when set to 1
allows the user to configure dynamically the driver callbacks.

当宏定义USE_HAL_UART_REGISTER_CALLBACKS 设为0时,或者没有进行此宏定义的时候,回调函数的各种特性将没有被激活,此时,回调函数仍然处于弱定义的状态

 [..]
When The compilation define USE_HAL_UART_REGISTER_CALLBACKS is set to 0 or 
not defined, the callback registration feature is not available and weak (surcharged) callbacks are used.

当宏定义USE_HAL_UART_REGISTER_CALLBACKS 设为1时,便可进行下列函数的调用:

 [..]Use Function @ref HAL_UART_RegisterCallback() to register a user callback.

HAL_UART_RegisterCallback()

使用HAL_UART_RegisterCallback() 函数去使能一个callback函数
其中Register可以被替换为下列单词,以实现不同的功能。
比如:HAL_UART_TxCpltCallback() 就是使能发送完成回调函数

除此之外,还有下列一些函数可供调用:

Function @ref HAL_UART_RegisterCallback() allows to register following callbacks:

(+) TxHalfCpltCallback        : Tx Half Complete Callback.
(+) TxCpltCallback            : Tx Complete Callback.
(+) RxHalfCpltCallback        : Rx Half Complete Callback.
(+) RxCpltCallback            : Rx Complete Callback.
(+) ErrorCallback             : Error Callback.
(+) AbortCpltCallback         : Abort Complete Callback.
(+) AbortTransmitCpltCallback : Abort Transmit Complete Callback.
(+) AbortReceiveCpltCallback  : Abort Receive Complete Callback.
(+) MspInitCallback           : UART MspInit.
(+) MspDeInitCallback         : UART MspDeInit.
This function takes as parameters the HAL peripheral handle, the Callback IDand a pointer to the user callback function.

同时,也可以使用HAL_UART_UnRegisterCallback()去失能某个回调函数。

串口收发数据的方式

串口可以设置以下三种种发送/接收数据的方式:

 [..]
    Three operation modes are available within this driver :

 *** Polling mode IO operation ***
 =================================
 [..]轮询方式
   (+) Send an amount of data in blocking mode using HAL_UART_Transmit()
   通过 HAL_UART_Transmit()发送数据
   (+) Receive an amount of data in blocking mode using HAL_UART_Receive()
	通过 HAL_UART_Receive()接收数据
 *** Interrupt mode IO operation ***
 ===================================
 [..]中断方式
   (+) Send an amount of data in non blocking mode using HAL_UART_Transmit_IT()
   (+) At transmission end of transfer HAL_UART_TxCpltCallback is executed and user can
        add his own code by customization of function pointer HAL_UART_TxCpltCallback
        通过中断方式发送数据,并且可以进行发送完成的回调
   (+) Receive an amount of data in non blocking mode using HAL_UART_Receive_IT()
   (+) At reception end of transfer HAL_UART_RxCpltCallback is executed and user can
        add his own code by customization of function pointer HAL_UART_RxCpltCallback
        通过中断方式接收数据,并且可以进行接收完成的回调
   (+) In case of transfer Error, HAL_UART_ErrorCallback() function is executed and user can
        add his own code by customization of function pointer HAL_UART_ErrorCallback
		当传输错误时,执行自定义的HAL_UART_ErrorCallback()进行错误处理/错误提示

*** DMA mode IO operation ***
 ==============================
 [..]DMA方式(不常用)使用方法与前两种大同小异,不再进行说明。
   (+) Send an amount of data in non blocking mode (DMA) using HAL_UART_Transmit_DMA()
   (+) At transmission end of half transfer HAL_UART_TxHalfCpltCallback is executed and user can 
   add his own code by customization of function pointer HAL_UART_TxHalfCpltCallback
   (+) At transmission end of transfer HAL_UART_TxCpltCallback is executed and user can
        add his own code by customization of function pointer HAL_UART_TxCpltCallback
        
        
   (+) Receive an amount of data in non blocking mode (DMA) using HAL_UART_Receive_DMA()
   (+) At reception end of half transfer HAL_UART_RxHalfCpltCallback is executed and user can
        add his own code by customization of function pointer HAL_UART_RxHalfCpltCallback
   (+) At reception end of transfer HAL_UART_RxCpltCallback is executed and user can
        add his own code by customization of function pointer HAL_UART_RxCpltCallback


   (+) In case of transfer Error, HAL_UART_ErrorCallback() function is executed and user can
        add his own code by customization of function pointer HAL_UART_ErrorCallback
        
   (+) Pause the DMA Transfer using HAL_UART_DMAPause()
   (+) Resume the DMA Transfer using HAL_UART_DMAResume()
   (+) Stop the DMA Transfer using HAL_UART_DMAStop()

(#) 有两种传输方式:

    (+) 阻塞模式:以轮询模式执行接收,直到接收到预期数量的数据,
        或直到 IDLE 事件发生。 接收仅在函数执行期间处理。
        当函数退出时,不会发生数据接收。传输完成后由函数返回HAL 状态和实际接收数据元素的数量。

    (+) 非阻塞模式:使用中断或 DMA 执行接收。
        这些 API 返回 HAL 状态。
        数据处理结束将通过
        使用中断模式时的专用 UART IRQ 或使用 DMA 模式时的 DMA IRQ。
        HAL_UARTEx_RxEventCallback() 用户回调将在接收过程中执行
        当检测到接收错误时,将执行 HAL_UART_ErrorCallback() 用户回调。
        
 (#) There are two mode of transfer:
       (+) Blocking mode: The reception is performed in polling mode, until either expected number of data is received,
           or till IDLE event occurs. Reception is handled only during function execution.
           When function exits, no data reception could occur. HAL status and number of actually received data elements,
           are returned by function after finishing transfer.
       (+) Non-Blocking mode: The reception is performed using Interrupts or DMA.
           These API's return the HAL status.
           The end of the data processing will be indicated through the
           dedicated UART IRQ when using Interrupt mode or the DMA IRQ when using DMA mode.
           The HAL_UARTEx_RxEventCallback() user callback will be executed during Receive process
           The HAL_UART_ErrorCallback()user callback will be executed when a reception error is detected.

串口常用宏定义

*** UART HAL driver macros list ***
=============================================
[…]下面是HAL库的串口驱动宏
Below the list of most used macros in UART HAL driver.

  (+) __HAL_UART_ENABLE: Enable the UART peripheral
  (+) __HAL_UART_DISABLE: Disable the UART peripheral
  (+) __HAL_UART_GET_FLAG : Check whether the specified UART flag is set or not
  (+) __HAL_UART_CLEAR_FLAG : Clear the specified UART pending flag
  (+) __HAL_UART_ENABLE_IT: Enable the specified UART interrupt
  (+) __HAL_UART_DISABLE_IT: Disable the specified UART interrupt
  (+) __HAL_UART_GET_IT_SOURCE: Check whether the specified UART interrupt has occurred or not

 [..]
   (@) You can refer to the UART HAL driver header file for more useful macros
   在头文件中还有更多有用的宏定义可以使用。

利用HAL函数进行程序编写

1.定义UART结构体

UART_HandleTypeDef huart1;

接下来我们通过这个修改结构体句柄来操纵串口

  1. 串口发送/接收函数编写
    **HAL_UART_Transmit();**串口发送数据,使用超时管理机制
    **HAL_UART_Receive();**串口接收数据,使用超时管理机制
    **HAL_UART_Transmit_IT();**串口中断模式发送
    **HAL_UART_Receive_IT();**串口中断模式接收
    **HAL_UART_Transmit_DMA();**串口DMA模式发送
    **HAL_UART_Transmit_DMA();**串口DMA模式接收

串口数据发送

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

向设备发送一段数据,超时则退出。

参数:
para1 >> UART_HandleTypeDef *huart 串口句柄
para2 >> uint8_t *pData 需要发送的数据地址
para3 >> 发送的字节数
para4 >> 发送所需的最大时间(如果超出则退出发送)

例如:

 HAL_UART_Transmit(&huart1, (uint8_t *)stm32, 5, 0xffff);   
 //串口发送三个字节数据,最大传输时间0xffff

串口中断接收

HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能:以中断的形式,接收串口发送的指定长度的数据。
此函数的执行流程:
设置好接收到的数据存放的位置,接收数据的长度,然后使能串口接收中断
当串口接收到数据时,会触发串口中断。
再然后串口中断函数进行处理??
直到接收到指定长度的数据,然后关闭中断,进入中断接收调用函数,【不再】触发接收中断

注意这里只触发一次中断便不再触发

HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1);   
//中断接收一个字符,存储到value中
//这里value也可以改成数组地址
  1. 串口中断回调函数
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);  

功能:HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,
串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改,

HAL_UART_RxCpltCallback(&huart1){     
      //用户设定的代码 
      }  
  1. 串口中断处理函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart);  

功能:对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用

如果接收数据,则会进行接收中断处理函数

 /* UART in mode Receiver ---------------------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    UART_Receive_IT(huart);
  }

如果发送数据,会进行发送中断处理函数

  /* UART in mode Transmitter ------------------------------------------------*/
  if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }

如果监测到错误,会进行相应错误处理

 /* If some errors occur */
  if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    /* UART parity error interrupt occurred ----------------------------------*/
    if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }

    /* UART noise error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }

    /* UART frame error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }

    /* UART Over-Run interrupt occurred --------------------------------------*/
    if (((isrflags & USART_SR_ORE) != RESET) && (((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_EIE) != RESET)))
    {
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

  1. 串口查询函数
    HAL_UART_GetState(); 判断UART的接收是否结束,或者发送数据是否忙碌
while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)   //检测UART发送结束

UART接收和中断

因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数

具体流程图如下:

HAL库调用逻辑

1、初始化串口

2、在main中第一次调用接收中断函数

3、进入接收中断,接收完数据 进入中断回调函数

4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,

5 回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断

HAL_UART_RxCpltCallback函数就是用户要重写直接放在uart.c文件中即可。

代码实现:
在main.c中添加以下定义:

#include <string.h>
 
#define RXBUFFERSIZE  256     //最大接收字节数
char RxBuffer[RXBUFFERSIZE];   //接收数据
uint8_t aRxBuffer;			//接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0;		//接收缓冲计数

在main()主函数中,调用一次接收中断函数

/* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
/* USER CODE END 2 */

在usart.c中编写中断回调函数

/* USER CODE BEGIN 2 */
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
 
	if(Uart1_Rx_Cnt >= 255)  //溢出判断
	{
		Uart1_Rx_Cnt = 0;
		memset(RxBuffer,0x00,sizeof(RxBuffer));
		HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); 	
        
	}
	else
	{
		RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
	
		if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
		{
			HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			Uart1_Rx_Cnt = 0;
			memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
		}
	}
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
}
/* USER CODE END 2 */

这里留下两个疑问:
1.中断回调函数时如何被触发的?即如何进入中断服务函数,最终触发回调函数?
2.为什么需要在中断回调函数中再次开启一次接收中断?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值