STM32学习笔记:基于HAL库的USART串口通信配置

Wesley13
• 阅读 955

版本:STM32F429 Hal库v1.10

串口通信能够实现两块电路之间不同的通信,在开发中作为打印调试也是一门利器(printf重定向)。

补充一点小知识:

  1. weak修饰符修饰的函数,说明这个函数如果在其他地方还有定义的话,则编译时使用其他地方定义的同名函数

  2. UNUSED(void x); 这个函数是防止编译器出现 未使用警告。

下面给出串口通信的具体步骤。

串口的初始化

1.声明串口属性结构体、并初始化(一般是作为全局变量)

typedef struct
{
    USART_TypeDef    *Instance;          /*    所使用的串口,值可以是    USART2 、USART3 、UART4、UART5、    UART7、UART8、USART1、USART6  */      UART_InitTypeDef    Init;  /* 串口通信参数结构体(附下表) */

    ...  __IO HAL_UART_StateTypeDef    State;  /*     串口当前状态(仅用于条件判断)    HAL_UART_STATE_RESET    // 未初始化完毕    HAL_UART_STATE_READY    // 就绪    HAL_UART_STATE_BUSY     // 忙,处理中    HAL_UART_STATE_BUSY_TX  // 忙于发送    HAL_UART_STATE_BUSY_RX  // 忙于接收    HAL_UART_STATE_BUSY_TX_RX// 忙于全双工通信    HAL_UART_STATE_TIMEOUT  // 超时    HAL_UART_STATE_ERROR   // 错误  */

    __IO uint32_t    ErrorCode;        /* 错误时返回的编号 */

}UART_HandleTypeDef;

typedef struct
{
  uint32_t BaudRate;                  /* 波特率 */

  uint32_t WordLength;                /*   数据位长度,可以是:    UART_WORDLENGTH_8B    UART_WORDLENGTH_9B*/


  uint32_t StopBits;/*  停止位    UART_STOPBITS_1    UART_STOPBITS_2*/


  uint32_t Parity;                    /*  校验位  采用何种校验是事先规定好的。通常专门设置一个奇偶校验位,用它使这组代码中“1”的个数为奇数或偶数。  若用奇校验,则当接收端收到这组代码时,校验“1”的个数是否为奇数,从而确定传输代码的正确性。    UART_PARITY_NONE  // 无    UART_PARITY_EVEN  // 偶校验    UART_PARITY_ODD   // 奇校验*/  uint32_t Mode;

/*  收发模式  UART_MODE_RX  URAT_MODE_TX  UART_MODE_TX_RX*/    uint32_t HwFlowCtl;/*

  硬件流控  硬件流:RTS/CTS  (Request To Send/Clear To Send)即请求发送/清除发送协议,用于半双工时的收发切换    UART_HWCONTROL_NONE    UART_HWCONTROL_RTS    UART_HWCONTROL_CTS    UART_HWCONTROL_RTS_CTS*/

uint32_t OverSampling;/*   过采样   可配置的16倍过采样或8倍过采样,因此为速度容差与时钟容差的灵活配置提供了可能。  UART_OVERSAMPLING_16  UART_OVERSAMPLING_8*/

}UART_InitTypeDef;

2.调用 HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart); 初始化

3.调用 HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) ;

开启接收中断(如果需要的话)

3.重写 void HAL_UART_MspInit(UART_HandleTypeDef *huart); 对IO口进行初始化

这一步包括配置IO口为复用模式,开启中断等。

//UART底层初始化,时钟使能,引脚配置,中断配置
//此函数会被HAL_UART_Init()调用
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_Initure;
    
    if(huart->Instance==USART1)          //如果是串口1,进行串口1 MSP初始化
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();            //使能GPIOA时钟
        __HAL_RCC_USART1_CLK_ENABLE();           //使能USART1时钟
    
        GPIO_Initure.Pin=GPIO_PIN_9;              //PA9
        GPIO_Initure.Mode=GPIO_MODE_AF_PP;        //复用推挽输出
        GPIO_Initure.Pull=GPIO_PULLUP;            //上拉
        GPIO_Initure.Speed=GPIO_SPEED_FAST;        //高速
        GPIO_Initure.Alternate=GPIO_AF7_USART1;    //复用为USART1
        HAL_GPIO_Init(GPIOA,&GPIO_Initure);           //初始化PA9

        GPIO_Initure.Pin = GPIO_PIN_10;            //PA10
        HAL_GPIO_Init(GPIOA,&GPIO_Initure);           //初始化PA10
        
#if EN_USART1_RX_IT
        HAL_NVIC_EnableIRQ(USART1_IRQn);                //使能USART1中断通道
        HAL_NVIC_SetPriority(USART1_IRQn,3,3);          //抢占优先级3,子优先级3
#endif    
    }
}

串口读写&中断处理

// http://t.cn/RuljJ01

// 读取串口状态
HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart);

// 从串口中接收字符(阻塞,具有毫秒级的超时管理机制)// 如果超时没接收完成,则不再接收数据到指定缓冲区,返回超时标志(HAL_TIMEOUT)HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 开启串口接收中断// 把 接收缓冲区指针 指向 要存放接收数据的数组,设置 接收长度,接收计数器初值,然后使能串口接收中断。接收到数据时,会触发串口中断。// 再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,不再触发接收中断,调用串口接收完成回调函数 HAL_UART_RxCpltCallback() 。HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

// 进入DMA中断,接收串口数据(非阻塞)
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

/*根据网上资料显示,HAL_UART_Receive_IT() 这个函数只能对串口中断接收进行一次接收,而且接收的字节大小是固定的uint16_t Size,但是在实际使用中,不可能完全满足每次接收到的字节数都是一样的,而且是确定的。所以大家采用的方法都是令 uint16_t Size = 1;这样的话,每接收到一个字节就中断一次。

那么中断处理函数处理的规则应该是
1、关闭此接收中断
2、将接收到的数据转移至缓存器
3、再次打开中断

*/

// 串口发送数据(阻塞,具有毫秒级的超时管理机制)// 如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); // 进入发送中断// 把 发送缓冲区指针 指向 要发送的数据,设置 发送长度,发送计数器初值,然后使能串口发送中断,触发串口中断。// 再然后,串口中断函数处理,直到数据发送完成,而后关闭中断,不再发送数据,串口发送完成触发回调函数:HAL_UART_TxCpltCallback()。HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 

// 进入DMA发送中断 HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

中断处理

HAL_UART_TxHalfCpltCallback();   一半数据(half transfer)发送完成后,通过中断处理函数调用。
HAL_UART_TxCpltCallback();     发送完成后,通过中断处理函数调用。
HAL_UART_RxHalfCpltCallback();   一半数据(half transfer)接收完成后,通过中断处理函数调用。
HAL_UART_RxCpltCallback();     接收完成后,通过中断处理函数调用。
HAL_UART_ErrorCallback();      传输过程中出现错误时,通过中断处理函数调用。

STM32串口中断入口函数

USARTx_IRQHandler   // x 可以是1到6

HAL_UART_IRQHandler

可看到串口发送和就是有三种通信模式:

第一种是上面用到的轮询的模式。CPU不断查询IO设备,如设备有请求则加以处理。例如CPU不断查询串口是否传输完成,如传输超过则返回超时错误。轮询方式会占用CPU处理时间,效率较低。

第二种就是中断控制方式。当I/O操作完成时,输入输出设备控制器通过中断请求线向处理器发出中断信号,处理器收到中断信号之后,转到中断处理程序,对数据传送工作进行相应的处理。

第三种就是直接内存存取技术(DMA)方式。所谓直接传送,即在内存与IO设备间传送一个数据块的过程中,不需要CPU的任何中间干涉,只需要CPU在过程开始时向设备发出“传送块数据”的命令,然后通过中断来得知过程是否结束和下次操作是否准备就绪。

在 HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 中再次调用 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
这样的话,就可以实现连续中断接收USART数据。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
UART1RxBuff[UART1RxBuffCount++] = aRxBuffer;
}
HAL_UART_Receive_IT(huart, (uint8_t *)&aRxBuffer, 1) ;
}

串口收发例程

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
2年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这