HAL库 STM32 串口通信

这篇具有很好参考价值的文章主要介绍了HAL库 STM32 串口通信。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、实验条件

将STM32的PA9复用为串口1的TX,PA10复用为串口1的RX。STM32芯片的输出TX和接收RX与CH340的接收RX和发送TX相连(收发交叉且PCB上默认没有相连,所以需要用P3跳线帽进行手动连接),CH340的另一端通过USB口引出与USB线相接。CH340作用:RS232电平标准转USB电平标准)。再使用USB转串口线实现PC与板子的通信。

PC端需要安装CH340虚拟串口驱动,目的是为了有CH340的通信协议。在使用串口调试助手进行通信时注意一下几点。

1.发送英文字符需要用一个字符即8位,发送汉字需要两个字符即16位,如上图,发送汉字“宋”实际是发送“CB(1100 1011)CE(1100 1110)”而发送英文字符S实际是发送“53(0101 0011)”,本质上没有太大区别;

2.勾选了下方“发送新行”后,XCOM就会再你输入的需要发送的数据后自动加上一个回车(0X0D+0X0A),如果不勾选则我们在手动输入完“宋S”后还需敲一个回车键只有这样点击发送后,调试助手上方窗体才能将其显示,这是因为我们在程序的串口中断中自定义了一个数据接收协议,即只有当接受的数据以回车结尾(0X0D+0X0A),串口才认可数据接受完毕。

二、例程软件实现

1、支持printf函数 这个函数大部分无需改动 但是针对不同的串口时:要记得改USART1为对应串口

//加入以下代码,支持printf函数,而不需要选择use MicroLIB      
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
    int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
    x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
    while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
    return ch;
}
#endif 

2、初始化

#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误          
u8 aRxBuffer[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄
//初始化IO 串口1 
//bound:波特率
void uart_init(u32 bound)
{    
    //UART 初始化设置
    UART1_Handler.Instance=USART1;                        //USART1  让Instance指向的寄存器基地址 等于串口1的外设基地址
    UART1_Handler.Init.BaudRate=bound;                    //波特率
    UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B;   //字长为8位数据格式
    UART1_Handler.Init.StopBits=UART_STOPBITS_1;        //一个停止位
    UART1_Handler.Init.Parity=UART_PARITY_NONE;            //无奇偶校验位
    UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;   //无硬件流控
    UART1_Handler.Init.Mode=UART_MODE_TX_RX;            //收发模式
    HAL_UART_Init(&UART1_Handler);                        //HAL_UART_Init()会使能UART1
    
    HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
  
}

void uart_init(u32 bound)函数需要注意的点

1、HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)

(1)HAL_StatusTypeDef为一个枚举类型,也就是当返回值比较多,就全枚举到HAL_StatusTypeDef里

(2)huart是UART_HandleTypeDef类型的结构体指针变量,也称为串口句柄,指向结构体里第一个成员基地址。一开始就定义了UART_HandleTypeDef UART1_Handler;

UART1_Handler为UART_HandleTypeDef类型的结构体变量,可用于引用结构体里成员,取&为第一个成员基地址

即:huart = &UART1_Handler

所以调用HAL_UART_Init函数时,括号里应该填&UART1_Handler

(3)第二个形参的位置应该填一个指针,数组名可以也看作指向数组里第零个元素的指针,但是这里把aRxBuffer强制转换为指针类型,个人认为只是一种格式规范,已上板验证过,就算没有强制转换,也能实现串口通信。(为什么是u8?因为uint8_t *pData入口参数规定的)

(4)第三个形参,表示每次接收SIZE个字节后标示接收结束,然后会进入接收回调函数。

整个接收的过程卡了我一天......最终总结为以下:

串口调试助手每发送一个帧,接收移位寄存器把这一帧里有效位给接收数据寄存器(此观点为个人理解),然后接收寄存器(RDR)接收到了数据后,产生RXNE中断,进入中断处理函数,中断处理函数对里判断到是接收中断调用 UART_Receive_IT(huart)函数

 if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))//判断是否为读数据寄存器非空中断
    {
      UART_Receive_IT(huart); //调用接收函数
      return;
    }
  }

在接收函数里把接收到的值放pRxBuffPtr,同时RxXferCount--,直到RxXferCount减到零,调用接收回调函数。(当RxXferCount=0时表明这次已经传完,进行中断失能的处理)

    if (--huart->RxXferCount == 0U)
    {
      /* Disable the UART Data Register not empty Interrupt */  
      __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);    

      /* Disable the UART Parity Error Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_PE);

      /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
      __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

      /* Rx process is completed, restore huart->RxState to Ready */
      huart->RxState = HAL_UART_STATE_READY;

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
      /*Call registered Rx complete callback*/
      huart->RxCpltCallback(huart);
#else
      /*Call legacy weak Rx complete callback*/
      HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

      return HAL_OK;
    }

问题:RxXferCount、pRxBuffPtr、RxXferSize(这三个都是UART_HandleTypeDef结构体里的成员)在哪里被赋值?(pTxBuffPtr,TxXferSize 和 TxXferCount 三个变量分别用来设置串口发送的数据缓存指针,发送的数据量和还剩余的要发送的数据量)

在HAL_UART_Receive_IT()里,把形参给pRxBuffPtr、RxXferSize。(RxXferCount初始值为RxXferSize)。

我们可以看到例程里RXBUFFERSIZE=1,aRxBuffer[]容量也为1,所以RxXferCount=1,每收到1个字节就去一次接收回调函数,那难道说这个例程只能适用于接收1个字节的时候吗?

HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

答案是否定的,这里只是一个一个的接收,在回调函数里还要及时把接收的这个字节放在数组USART_RX_BUF[USART_REC_LEN]里,USART_REC_LEN为200。

但是这样的话,我没法知道接收完成没有,所以在串口回调函数里设计了小小的通信协议。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance==USART1)//如果是串口1
    {
        if((USART_RX_STA&0x8000)==0)//USART_RX_STA就是个自己定义的变量 16位
        {
            if(USART_RX_STA&0x4000)//接收到了0x0d
            {
                if(aRxBuffer[0]!=0x0a)USART_RX_STA=0;//接收错误,重新开始  字节
                else USART_RX_STA|=0x8000;    //接收完成了 
            }
            
            else //还没收到0X0D
            {    
                if(aRxBuffer[0]==0x0d)USART_RX_STA|=0x4000;
                else
                {
                    USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;
                    USART_RX_STA++;
                    if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收      
                }         
            } //接收到的内容就放在USART_RX_BUF里 主函数里再决定怎么处理
        }
    }
}

当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2个字节组成: 0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待 0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到 0X0D,那么在接收数据超过 USART_REC_LEN 的时候,则会丢弃前面的数据,重新接收。

注意:USART_RX_STA[13:0]位用来装已接收的字节个数,可以有16384个,但是USART_RX_BUF[USART_REC_LEN]里,USART_REC_LEN为200,所以最多接收200个字节。


三、常用的串口中断处理函数

我们直接把中断控制逻辑写在中断服务函数内部,不适用中断回调函数

此时不用初始化HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

直接在要开启中断的地方使用__HAL_UART_ENABLE_IT()单独开启中断即可。

调用了HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)对接收到的字符进行处理。

//串口1中断服务程序
void USART1_IRQHandler(void)                    
{ 
    u8 Res;
    HAL_StatusTypeDef err;
#if SYSTEM_SUPPORT_OS         //使用OS
    OSIntEnter();    
#endif
    if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET))  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
    {    //UART_FLAG_RXNE产生了 说明接收函数处理了数据
       HAL_UART_Receive(&USART1_Handler,&Res,1,1000);
   
        if((USART_RX_STA&0x8000)==0)//接收未完成
        {
            if(USART_RX_STA&0x4000)//接收到了0x0d
            {
                if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
                else USART_RX_STA|=0x8000;    //接收完成了 
            }
            else //还没收到0X0D
            {    
                if(Res==0x0d)USART_RX_STA|=0x4000;
                else
                {
                    USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
                    USART_RX_STA++;
                    if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收      
                }         
            }
        }            
    }
    HAL_UART_IRQHandler(&UART1_Handler); //为什么还需要这句 不明确?   
#if SYSTEM_SUPPORT_OS         //使用OS
    OSIntExit();                                               
#endif
} 
#endif    

UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout)

四、主函数

while(1)
    {
            
       if(USART_RX_STA&0x8000)
        {                       
            len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
            printf("\r\n您发送的消息为:\r\n");
            HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000);    //发送接收到的数据
            while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET);        //等待发送结束
            printf("\r\n\r\n");//插入换行
            USART_RX_STA=0;
        }else
        {
            times++;
            if(times%5000==0)
            {
                printf("\r\nALIENTEK 精英STM32开发板 串口实验\r\n");
                printf("正点原子@ALIENTEK\r\n\r\n\r\n");
            }
            if(times%200==0)printf("请输入数据,以回车键结束\r\n");  
            if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
            delay_ms(10);   
        } 
    }
}
HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000); 

1、第二个形参 为数组名USART_RX_BUF强制转换为的指针,指向数组第0个元素,指针实现自加的语法在这个发送函数里

if (huart->Init.Parity == UART_PARITY_NONE)
        {
          pData += 2U;
        }
        else
        {
          pData += 1U;
        }

2、这个是超时,在设置的这个时间内没有发送完成,就返回超时(HAL_TIMEOUT)

如何取:

如果波特率为9600,发送一个位需要的时间为1/9600s=0.0001042s=0.1042ms,这里按数据位为8位,停止位为2位,

加起来就是10位,10个位发送所需的时间为:0.1042*10ms = 1.042ms,如果我要发送10个字节的数据,那发送这10个字节数据给接收方需要

的时间为:10*1.042ms = 10.42ms,这是算实际的发送10个字节的数据所需要的时间。我们在接收方接收数据时可以

把时间再加宽一些,让它有一点余量。让接收方能稳定的把数据从发送方接手过来,可以加个5ms,或更宽一点10ms,

加上发送10个字节所花的时间,就是15ms或20ms。

3、len 是取的USART_RX_STA的【13:0】位,总担心是个十六进制,不要担心,所有进制都会被化为二进制。

4、while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET);

这里的传输完成指的是USART_RX_BUF[len]传输完成,而不是发送一个字节寄存器产生那个传输完成。文章来源地址https://www.toymoban.com/news/detail-535436.html

到了这里,关于HAL库 STM32 串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 蓝桥杯嵌入式STM32 G431 hal库开发速成——ADC与DAC

    模数转换器(ADC):它将模拟信号转换为单片机能够处理的数字信号。在很多应用中,比如温度传感器、压力传感器等,信号最初都是模拟形式的。ADC 读取这些模拟信号,然后将它们转换为数字形式,以便单片机可以读取和处理。 数模转换器(DAC):它执行相反的操作,将

    2024年02月01日
    浏览(45)
  • 【嵌入式学习-STM32F103-USART串口通信】

    4-1 基本流程 4-2 整体代码 4-2-1 main.c 4-2-2 Serial.c 4-2-3 Serial.h 5-1 查询 5-2 中断 5-3 整体代码 5-3-1 main.c 5-3-2 Serial.c 5-3-3 Serial.h 6-1 使用状态机接收数据包的思路 6-2 串口收发HEX数据包 6-2-1 main.c 6-2-2 Serial.c 6-2-3 Serial.h 6-3串口收发文本数据包 6-3-1 main.c 6-3-2 Serial.c 6-3-3 Serial.h 全双工:打

    2024年02月15日
    浏览(48)
  • 蓝桥杯嵌入式STM32G431RBT6的学习(总大纲)(HAL库学习)板子介绍

    我写蓝桥杯嵌入式大概用到的外设,都是非常常用的。我在这里汇总一下。 蓝桥杯嵌入式基础模块——GPIO的使用(新板)STM32G431(HAL库开发)_薛定谔的猫咪死了的博客-CSDN博客 蓝桥杯嵌入式基础模块——串口的使用(新板)STM32G431(HAL库开发)_薛定谔的猫咪死了的博客-C

    2024年02月02日
    浏览(35)
  • 蓝桥杯嵌入式基础模块——LCD显示器的基本使用(新板)STM32G431(HAL库开发)

            在蓝桥杯嵌入式官方给我们提供好了,LCD显示的底层源码,我们只需要,记住里面的API函数,会用这些函数就行。         在官方给的资料中找到这个文件名字DK117_G4 Data Packet-开发板驱动文件里面就是所有的底层文件有两种类型,一种是基于HAL库的一种是标准库

    2024年02月09日
    浏览(47)
  • 【嵌入式知识08】STM32的USART串口通信,给上位机连续发送Hello Windows!

    本文主要介绍串口协议和RS-232、485标准,以及RS232、485电平与TTL电平的区别,了解\\\"USB/TTL转232\\\"模块的工作原理;并完成一个STM32的USART串口通讯程序。   串口通信(Serial Communication)的概念非常简单,串口按位(bit)发送和接收字节的通信方式。尽管比按字节(byte)的并行通信

    2024年02月13日
    浏览(39)
  • HAL库 STM32 串口通信

    将STM32的PA9复用为串口1的TX,PA10复用为串口1的RX。STM32芯片的输出TX和接收RX与CH340的接收RX和发送TX相连(收发交叉且PCB上默认没有相连,所以需要用P3跳线帽进行手动连接),CH340的另一端通过USB口引出与USB线相接。CH340作用:RS232电平标准转USB电平标准)。再使用USB转串口线

    2024年02月13日
    浏览(25)
  • STM32 HAL SWD下载与串口通信

    SWD是ST公司推出的开源的四线下载方式,分别为3V3、SWD、SWCLK、GND,相比JTAG等可以用较少的线来实现下载和仿真。 首先你需要购买一个DAPLINK,tb购买15块钱左右。只需要接到STM32F103C8T6最小系统板上面的同样的这四个排针即完成接线。 而对于类似正点原子的精英板等,则需要在

    2024年02月03日
    浏览(33)
  • STM32—HAL库中断/DMA控制和完成串口通信

    目录 一、解决的问题 二、串口通讯协议和RS-232的介绍以及USB/TTL转232模块的工作原理   1、 串口协议和RS-232标准:  (1)串口协议: (2)RS-232 标准:   2、RS232电平与TTL电平的区别   3、USB/TTL转232“模块(CH340芯片为例)  (1)基本原理:  (2)CH340模块介绍: ​三、搭

    2024年02月02日
    浏览(59)
  • STM32L4 HAL库通过串口通信改变PWM占空比

    使用串行通信的目的是为了让上位机能控制STM32来改变PWM的输出 这里用的是定时器TIM4的3通道,当然也可以改为其他的定时器,具体请参考手册 偷下懒,直接拿正点原子的例子程序修改了一下。 示例用的是UART1 引脚是PA9(TX),PA10(RX) 主要修改的地方在接收数据的部分,我用l

    2024年02月15日
    浏览(33)
  • 嵌入式学习笔记——STM32的USART通信概述

    上两篇文章中,已经实现了GPIO的通用输出以及通用输出模式,从本文开始,笔者将开始有关GPIO的复用功能的介绍,首先是最常用复用功能——串口,本文主要是介绍一些关于通信以及串口的基本概念。 通信协议:通信双方进行信息交换(接收或发送)要满足的规则,而这个规

    2023年04月08日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包