首页 > 其他分享 >STM32 - 串口通信(HAL库)

STM32 - 串口通信(HAL库)

时间:2024-06-03 19:59:57浏览次数:16  
标签:HAL LOG huart UART STM32 buffer 串口

为什么要用HAL库?

使用方便,可以完全使用GUI配置、支持更多的芯片型号和开发板、良好的封装与抽象、Easy上手和开发

有什么缺点?

封装层次较高,造成稍微的性能损失


STM32cubemx部分

以使用stm32系列的NUCLEO-F03RB为例


1.配置时钟

选择STM32F103RCTx系列芯片,配置时钟的同时会自动配置IO口引脚

· HAL库串口发送/接收函数

/* 串口发送数据,使用超时管理机制 */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); 
/* 串口接收数据,使用超时管理机制 */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); 
/* 串口中断模式发送 */
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 
/* 串口中断模式接收 */ 
HAL_StatusTypeDef HAL_UART_Receive_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); 
/* 串口DMA模式接收 */
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 
/* 串口中断处理函数 */
HAL_StatusTypeDef HAL_UART_IRQHandler(UART_HandleTypeDef *huart); 
/* 串口发送中断回调函数 */
HAL_StatusTypeDef HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); 
/* 串口发送一半中断回调函数(用的较少) */
HAL_StatusTypeDef HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); 
/* 串口接收中断回调函数 */
HAL_StatusTypeDef HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); 
/* 串口接收一半回调函数(用的较少) */
HAL_StatusTypeDef HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); 
/* 串口接收错误函数 */
HAL_StatusTypeDef HAL_UART_ErrorCallback(); 

· 参数解释:

符号 释义
UART_HandleTypeDef * huart 串口号
uint8_t * pData 存放数据的数组
uint16_t Size 接收的数据长度

例如:

HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);

中断进行完之后,并不会直接退出,而是会进入中断回调函数中,我们在其中写入代码即可

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    /* 用户自定义的代码 */
}

发送

一般HAL_UART_Transmit()就够用了。

· 重定向printf函数

#ifdef __GNUC__ 
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch) 
#else 
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) 
#endif PUTCHAR_PROTOTYPE 
{ 
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF); return ch; 
}

· (扩展)优雅的Log系统

[LOG]:usart1_buffer:log hello warning hello error hello 
[LOG]:Some LOG occur 
[LOG]:Good morning! 
[WARNING]:Some warning! occur 
[LOG]:Good morning! 
[ERROR]:../Core/Src/main.c:200:Some ERROR!! occur 
[LOG]:Good morning!

· 怎么做到在前面自动标记log的种类,并且根据打印error所在位置呢?

#define __DEBUG__ 
#ifdef __DEBUG__ 
#define LOG(fmt, ...) printf("[LOG]:" fmt "\r\n", ##__VA_ARGS__) 
#define LOG_WARNING(fmt, ...) printf("[WARNING]:" fmt "\r\n", ##__VA_ARGS__) 
#define LOG_ERROR(fmt, ...) printf("[ERROR]:%s:%d:" fmt "\r\n", __FILE__, __LINE__, ##__VA_ARGS__) 
#elif 
#define LOG(fmt, ...) 
#define LOG_WARNING(fmt, ...) 
#define LOG_ERROR(fmt, ...) 
#endif

关于可变参数宏和C语言的一些预定义的宏,详细用法参考微软文档:可变参数宏 | Microsoft Docs

这样相当于我们对printf又做了一层封装,实现了对不同log进行分类,并且由于是编译期展开的,所以 是零成本的抽象,不会对运行时性能造成影响。

使用起来也很简单,和printf的用法一样。

LOG("Some LOG occur %s", your_log); 
LOG_ERROR("Some ERROR!! occur %s", your_error); 
LOG_WARNING("Some WARNING! occur %s", your_warning);

接收

接收要比较麻烦,因为单片机不知道你什么时候来数据,而在你没来数据的时候也不能干等着,因此就 要涉及到中断,HAL库中涉及到的就是这两个函数。

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);/* 串口中断模式接收 */
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);/* 串口DMA模式接收 */

HAL_UART_Receive_IT这个在HAL库中的实现有点坑,只能接收到定长的数据然后触发中断,没办法处 理不定长的数据,而且容易丢数据,不推荐使用。

这里的解决方案是利用闲时中断加上DMA来实现不定长数据的接收和处理。


闲时中断加上DMA

关于闲时中断和DMA 的详细原理,推荐阅读博客:STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA - 古月居 (guyuehome.com)

具体操作分四步走。

1.在cubeMX里面打开串口中断和配置dma

2.在串口初始化的时候加上这两句:

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);/* 启用闲时中断标识符 */
HAL_UART_Receive_DMA(&huart1, usart1_buffer, BUF_MAX_SIZE);/* 开启串口接收dma */

此处的usart1_buffer,BUF_MAX_SIZE为:

#define BUF_MAX_SIZE 128 /* buffer大小 */
uint8_t usart1_buffer[BUF_MAX_SIZE]; /* dma接收buffer */

3.在串口的中断回调函数中加上这几句

void USART1_IRQHandler(void) 
{ 
    /* USER CODE BEGIN USART1_IRQn 0 */ 
    /* 最好加在HAL_UART_IRQHandler前,防止回调的代码影响idle flag */
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)/* 判断是否idle */
    { 
        USER_UART_IDLECallback(&huart1);/* 调用自己的Idle回调函数 */
    } 
    /* USER CODE END USART1_IRQn 0 */ 
    HAL_UART_IRQHandler(&huart1); 
    /* USER CODE BEGIN USART1_IRQn 1 */ 
    
    /* USER CODE END USART1_IRQn 1 */ 
}

4.最后就是写你自己的中断回调函数啦

void USER_UART_IDLECallback(UART_HandleTypeDef *huart)/* 自定义函数 */
{ 
    HAL_UART_DMAStop(huart);/* 停下DMA */
    int len = 0;/* 接收数据长度 */
    uint8_t *buffer;/* buffer指针 */
    if (huart->Instance == huart1.Instance)
    { 
        len = BUF_MAX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); 
        buffer = usart1_buffer; 
    } 
    /* 现在你有了接收到数据的长度len和数据的buffer指针了 
       你可以做你任何想做的... */
    memset(buffer, 0, len);/* buffer清空 */ 
    HAL_UART_Receive_DMA(huart, buffer, BUF_MAX_SIZE);/* 重新开启dma */
    __HAL_UART_CLEAR_IDLEFLAG(huart);/* 清空idle flag */
}

(扩展)指令解析

现在我们能接收不定长的数据了,数据有了,但是要怎么解析比较方便呢,这里整理了一套还算好用的指令解析的方法,给大家分享一下。

受main函数的传入参数的启发,我们可以将一次传输的字符串进行分割,变成argc、argv 的形式。 在IDLE回调函数中进行扩展。

void USER_UART_IDLECallback(UART_HandleTypeDef *huart) 
{ 
    HAL_UART_DMAStop(huart); 
    
    char **argv = (char **)malloc(sizeof(char *) * 10); /* 参数值-字符串-二维数组 */ 
    int argc = 0; /* 参数个数 */
    int tem_p = 0; 
    int len = 0;
    uint8_t *buffer; 
    if (huart->Instance == huart1.Instance) 
    { 
        len = BUF_MAX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        LOG("usart1_buffer:%s", usart1_buffer); 
        buffer = usart1_buffer; 
    } 
    /* 现在你有了接收到数据的长度len和数据的buffer指针了 
       将buffer里面的数据加工,处理为argc,argv的形式 */
    buffer[len++] = ' '; 
    buffer[len++] = 'a'; 
    for (int i = 0; i < len; i++) 
    { 
        if (buffer[i] == ' ' || i == len - 1) 
        { 
            argv[argc] = (char *)malloc(sizeof(char) * (i - tem_p + 1));
            /* 注意,此 处malloc了,一定要记得free掉内存 */
            memcpy(argv[argc], buffer + tem_p, i - tem_p); 
            argv[argc][i - tem_p] = '\0'; 
            tem_p = i + 1; 
            argc++; 
            if (argc >= 10) break; 
        } 
    } /* 将argc,argv进行解析 */
    arg_prase(argc, argv); 
    memset(buffer, 0, len); 
    
    for (int i = 0; i < argc; i++) 
    { 
        free(argv[i]);//释放内存 
    } free(argv);/* 释放内存 */
    HAL_UART_Receive_DMA(huart, buffer, BUF_MAX_SIZE);
    __HAL_UART_CLEAR_IDLEFLAG(huart); 
}

arg_prase的定义如下:

uint8_t is_str_equal(const char *str_p1, char *str_p2) 
{ 
    int i = 0; 
    while (str_p1[i]) 
    { 
        i++; 
        if (str_p1[i] != str_p2[i]) 
            return 0; 
    } 
    return 1; 
} 

void arg_prase(int argc, char **argv) 
{ 
    for (int i = 0; i < argc; i++) 
    { 
        if (is_str_equal("hello", argv[i])) 
        { 
            LOG("Good morning!"); 
        } 
        else if (is_str_equal("log", argv[i])) 
        { 
            LOG("Some LOG occur"); 
        } 
        else if (is_str_equal("error", argv[i])) 
        { 
            LOG_ERROR("Some ERROR!! occur"); 
        } 
        else if (is_str_equal("warning", argv[i])) 
        { 
            LOG_WARNING("Some warning! occur"); 
        } 
    }
}

总结

以上就是STM32串口通信的所有内容,理论基础学完,更重要的是实践操作,多打代码、多实践使用,百炼成钢、熟能生巧!



ENDING
感兴趣可以分享给身边的小伙伴哦!

标签:HAL,LOG,huart,UART,STM32,buffer,串口
From: https://www.cnblogs.com/yanzongs/p/18229526

相关文章

  • STM32——ADC篇(ADC的使用)
    一、ADC的介绍 1.1什么是ADC        ADC(Analogto-DigitalConverter)模拟数字转换器,是将模拟信号转换成数字信号的一种外设。比如某一个电阻两端的是一个模拟信号,单片机无法直接采集,此时需要ADC先将短租两端的电压这个模拟信号转化成数字信号,单片机才能够进行处理。......
  • STM32系列--串口收发+基本定时器
      if(myusart.reflag>0){Com_Handle();myusart.recount=0;myusart.reflag=0;}main #define_maxbuf100typedefstruct{u8myadd;u8......
  • STM32与陶晶驰串口屏交互
    1、串口屏界面设计1.新建工程保存位置自定义,作为一个合格的嵌入式工程师要有路径下没有中文的情况并命名。选择自己串口屏对应的芯片,一般屏幕背面会有,也可以查看资料。 选择显示方向,自行选择。按照自己的爱好右边可对当前页面重命名。再进行一些基础代码修改。一般......
  • ChallengeMobile
    解题思路获取到输入的字符串保存到s,调用Jformat方法对s进行验证,返回true则代表输入字符串正确反之错误。Jformat方法分析:首先看到使用了LoadData加载了”ming“给了a方法,a方法的返回值赋值给了arr_b。接着判断SDK_INT是否小于29:意思就是判断Android版本是否小于10,如果不是......
  • (11.2)iic串口读写EEPROM实验:iic串口协议
    一、iic协议介绍iic(i2c,inter-integratedCircuit),即集成电路总线,是一种两线式串行总线。多用于主机(fpga)和从机(外围设备)在数据量不大且传输距离短的场合下使用(支持一主多从,根据器件地址进行从机的区分)iic由数据线SDA和时钟线SCL构成通信线路,既可以发送数据,也可以接受数据......
  • stm32L431使用SPI+DMA
    stm32L431使用SPI+DMA1.配置SPI和DMA1.1使用hal库配置(stmcubmax)配置可以按选择配置MOSI单线只发送数据则配置TransmitOnlyMasterMOSI,MISO双线配置Full-DuplexMaster1.2配置DMA​ 如1.1图所示我配置的是只发送数据模式!DMA配置模式Normal,地址增长Memory,数据......
  • JINGWHALE ART 年度流行色,用代码创造色彩斑斓的数字世界!
    JINGWHALEART年度流行色(智能色彩生产官方平台)是由JINGWHALE每年发布的年度流行色,于每年的1月1日发布次年的流行色。JINGWHALEART年度流行色依据中国古典文化思想(如中国传统正五色、金木水火土、十二生肖等文化思想)、以及年度流行指数,确定年度流行色。基于年度流行......
  • 基于STM32F103C8T6微控制器的物流信息检测系统
    摘要本论文设计并实现了一种基于STM32F103C8T6微控制器的物流信息检测系统。该系统旨在通过综合各种传感器模块实现对运输车环境及状态的实时监控,并通过4G网络将信息发送到手机端,以便用户进行远程管理和控制。首先,系统通过GPS模块获取运输车的位置信息,实现车辆定位和轨迹跟踪......
  • STM32 USB CDC调试记录
    STM32USBCDC调试一、前言最近在做STM32的IAP方案,官方提供的demo是基于USART实现,但是使用USART的话要和电脑通信要么借助USB转TTL工具;要么在板子上加一颗CH340类似的转换芯片。这就不是很方便,就想着直接可以通过USB线进行升级,所以USBCDC就进入我的视野,关于USBCDC是什么这里......
  • STM32使用定时器在普通gpio上模拟pwm-红牛开发板LED1的亮度调节
    stm32F103zet只有固定的几个针脚可以输出tim定时器信号,在不支持tim输出的口上就没法输出pwm,在红牛开发版上的表现就是控制lcd屏幕亮度的a1针脚,可以输出pwm,屏幕亮度可以无极调节,但是4个led灯就只能控制开关。使用arduino的analogWrite函数,只能调节开关。可以用定时器的中断来控制......