日志

STM32 HAL库之串口详细篇

 来源    2021-01-14    1  

一、基础认识

(一) 并行通信

原理:数据的各个位同时传输

优点:速度快

缺点:占用引脚资源多,通常工作时有多条数据线进行数据传输

8bit数据传输典型连接图:

传输的数据是二进制:11101010,则通信使用8条线同时进行数据传输,发送端一次性发送8位数据,接收端一次性接收8位数据。

(二) 串行通信

原理:数据按位顺序传输

优点:占用引脚资源少

缺点:速度相对较慢,通常工作时只有一条数据线进行数据传输

8bit数据传输典型连接图:

传输的数据是二进制:11101010,则通信使用8条线同时进行数据传输,发送端一次性发送8位数据,接收端一次性接收8位数据。

8bit数据传输典型连接图:

传输的数据是二进制:11101010,则通信使用1条线进行数据传输,发送端一次性发送1位数据,接收端一次性接收1位数据。

串行通信的分类:

1.单工:数据只能在一个方向上传输,通信双方数据只能由一方传输到另一方

2.半双工:数据可以错时双向传输,通信双方数据可以支持两个方向传输,但是同一时间只能由一方传输到另外一方。

3.全双工:数据可以同时双向传输,通信双方数据可以同时进行双向传输,对于其中一个设备来说,设备需要支持发送数据时可以进行数据接收。

串行通信的通讯方式:

l  同步通信:带时钟同步信号的传输,如SPI、IIC、USART(同步)

l  异步通信:不带时钟同步信号的传输,如UART、USART(异步)

常见数据传输协议:

(三)   UART和USART

UART:通用异步收发器

USART:通用同步/异步收发器,其可选使用异步方式,那将和UART无区别,如果是同步,则需要多一根时钟线(USART_CK)

(四)  STM32的USART注意:

l  通常USART1接口的通信速率较快,其它USART接口较慢。如STM32F103C8T6的USART1接口通信速率是4.5Mbps,其它USART接口的通信速率是2.25Mbps。

l  片上所有的USART接口都可以使用DMA操作

l  USART的扩展及距离:

UART和COM是物理接口形式(物理接口)

TTL和RS-232是电平标准(电信号)

串口接收:

l  扫描模式

l  中断模式

l  DMA模式

二、串口基础配置

模式选择:

Asynchronous  异步通信

Synchronous  同步通信

Single Wire (Half-Duplex) 单线/半双工

Multiprocessor Communication 多处理器

支持局域互连网络LIN、智能卡(SmartCard)协议与lrDA(红外线数据协会) SIR ENDEC规范。

默认的TX GPIO:

l  模式为:推挽式复用功能

l  输出速率:高速

默认的RX GPIO:

l  模式为:浮空输入

参数设置

Baud Rate

任意设置,未做限制,输入框

 

Word Length

数据位可选8位或9位

Parity

校验位可选无校验(None)、偶校验(Even)、奇校验(Odd)

Stop Bits

停止位可选1位、2位

Data Direction

数据方向,可选收发(Receive and Transmit)、只接收(Receive Only)、只发送(Transmit Only)

三、阻塞发送函数

以阻塞模式发送大量数据

当没有启用UART奇偶校验( PCE sign0 ),并且单词长度配置为9位( m1 - m0 sign01 )时,*发送的数据作为一组U16处理。在9位/无奇偶校验传输的情况下,pData需要作为uint16_t指针处理

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

参数:

huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。

PData: 指向数据缓冲区的pData指针(U8或u16数据元素)。

Size: 要发送的数据元素( u8或U16 )的大小

Timeout:超时持续时间,单位ms,0就是0ms超时,数据最大发送时间,超过则返回异常

返回:

HAL 状态

typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

例如:

HAL_UART_Transmit(&huart1,"dongxiaodong\r\n",strlen("dongxiaodong\r\n"),0xFFFF);

四、串口扫描接收

(一)相关函数

阻塞接收函数

在阻塞模式下接收大量数据。

当没有启用UART奇偶校验( PCE sign0 ),并且单词长度配置为9位( m1 - m0 sign01 )时,*接收到的数据作为一组U16处理。

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

huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。

pData:指向数据缓冲区的指针( u8或U16数据元素)。

Size: 要接收的数据元素数量( u8或U16 )。

Timeout:超时持续时间,单位ms,0就是0ms超时,数据接收最大等待时间,超出则异常

返回:

HAL 状态

typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

例如:

uint8_t data=0;
while (1)
{
if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){

    }
}

(二)代码实现

HAL_UART_Transmit(&huart1,"dongxiaodong\r\n",strlen("dongxiaodong\r\n"),0xFFFF);
uint8_t data=0;
while (1)
{
    //串口接收数据
    if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){
            //将接收的数据发送
             HAL_UART_Transmit(&huart1,&data,1,0);
        }
}

其中timeout为0表示没有延时,所以串口接收函数是不阻塞的,while循环将一直轮询

加个延时函数

这样一来的话,接收数据就异常了,会接收数据不全,所以这样是不可靠的

那改成这样呢?

uint8_t data[100]={0};
if(HAL_UART_Receive(&huart1,data,100,1000)==HAL_OK){
            
}

接收100个数据,等待时间为1秒,这样的话接收区没满时,每次运行这条语句都要延时等待1S,这时相当于一个HAL_Dealy(1000),这会阻塞while循环。只有数据接收到刚刚等于100才能返回HAL_OK,所以不能用于接收变成数据,这是不理想的。

五、 串口中断接收

(一)cubemx设置

使能串口中断

优先级选择

Preemption Priorit:抢占优先级

Sub Priority :子优先级

数字越小优先级越高

自动生成的代码中已经使能了中断

(二)相关函数

接收中断开启,只开启一次中断

以非阻塞模式接收一定数量的数据,当UART奇偶校验未启用(PCE = 0),且字长配置为9位(M1-M0 = 01)时,

*接收到的数据作为一组u16处理。在这种情况下,Size必须指出数字

*的u16可通过pData。

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

参数:

huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。

pData:指向数据缓冲区的指针(u8或u16数据元素)。

Size:需要接收的数据元素(u8或u16)的数量。

返回:

HAL 状态

typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

中断接收回调函数

HAL_UART_RxHalfCpltCallback();一半数据接收完成时调用

HAL_UART_RxCpltCallback();数据完全接受完成后调用

函数原型

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

(三) 编程实现方法1

uint8_t my_uart1_redata=0;
//开启串口接收中断
void my_uart1_enable_inpterr(){
    //开启一次中断
    HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    
}
//串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1)//判断串口号
    {
        //发送
        HAL_UART_Transmit(&huart1,&my_uart1_redata,1,100);
        //开启一次中断
        HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    }
}

存在问题:

数据发送太快之后就可能导致单片机无法再接收数据,以至于永久性损坏,通常可以在主循环里判断标志位再次启动,可以避免永久性损坏问题。

(四) 编程实现方法2

修改stm32f1xx_it.c里面的串口中断

#include <usart.h>
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
   //正在接收
   if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
     {
             //NET_UART_RECV(READ_REG(huart1.Instance->RDR));
             my_uart1_callback(huart1.Instance->DR);
             __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
     }
     
   //溢出-如果发生溢出需要先读SR,再读DR寄存器 则可清除不断入中断的问题
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_ORE)== SET)
    {
        __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_ORE);          //读SR
        //READ_REG(huart1.Instance->RDR);                         //读DR
    }
       //手动关闭自带的串口中断处理
#if 0
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
#endif
  /* USER CODE END USART1_IRQn 1 */
}

标准函数

//开启串口接收中断
void my_uart1_enable_inpterr(){
    //开启一次中断
     __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);    //使能接收中断
    
}
//串口收到数据回调
void my_uart1_callback(uint8_t rdata){
    
        //发送
        HAL_UART_Transmit(&huart1,&rdata,1,1);

}

修改了HAL自带的串口中断函数,可以有效的避免接收中断失效问题,但是你测试的时候会发现串口助手发送的数据和串口助手接收到的数据不完整,这是正常的,因为中断接收是很快的,而发送是阻塞的,而实际也不会这样使用,所以一般都会用数组做缓冲区接收串口数据。

六、 配置串口为中断接收DMA发送

l  STM32可用DMA的外设:定时器、ADC、SPI、IIC、USART

l  使用DMA必须开启中断

l  串口DMA模式最大为u16个字节,则65535

(一)cubmx设置

通用配置

中断开启

DMA发送设置

Dirction : DMA传输方向

四种传输方向:

l  外设到内存 Peripheral To Memory

l  内存到外设 Memory To Peripheral

l  内存到内存 Memory To Memory

l  外设到外设 Peripheral To Peripheral

Priority: 传输速度

l  最高优先级 Very Hight

l  高优先级 Hight

l  中等优先级 Medium

l  低优先级;Low

Priority: 优先级

l  最高优先级 Very Hight

l  高优先级 Hight

l  中等优先级 Medium

l  低优先级;Low

mode:模式

l  Normal:正常模式,当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次

l  Circular: 循环模式,传输完成后又重新开始继续传输,不断循环永不停止

Increment Address:地址增加

l  Peripheral:设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,勾选表示递增。

l  Memory:设置传输数据时候内存地址是否递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,这个Src Memory一样,只不过针对的是内存。,勾选表示递增。

data width:数据宽度

byte:字节,通用8位,与u8相同

word:字长,与硬件的位数相同,STM32是32位,所以对应是u32

Half Word:半个字长,所以对应是u16

(二)  编程实现

串口DMA发送

#include "string.h"
extern DMA_HandleTypeDef hdma_usart1_tx;

//发送数组数据
void my_uart1_send_data(uint8_t *tdata,uint16_t tnum){
        //等待发送状态OK
        while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
        //发送数据
        HAL_UART_Transmit_DMA(&huart1,tdata,tnum);
}

//发送字符串
void my_uart1_send_string(uint8_t *tdata){
        //等待发送状态OK
        while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
        //发送数据
        HAL_UART_Transmit_DMA(&huart1,tdata,strlen(tdata));
}

串口库函数中断接收

uint8_t my_uart1_redata=0;
//开启串口接收中断
void my_uart1_enable_inpterr(){
    //开启一次中断
    HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    
}
//串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1)//判断串口号
    {
        //发送
        my_uart1_send_data(&my_uart1_redata,1);
        //开启一次中断
        HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    }
}

主函数

//开启中断
my_uart1_enable_inpterr();
//发送数据
my_uart1_send_data("1dongxiaodong_DMA_1\r\n",strlen("1dongxiaodong_DMA_1\r\n"));
my_uart1_send_data("2dongxiaodong_DMA_2\r\n",strlen("2dongxiaodong_DMA_2\r\n"));
my_uart1_send_string("3dongxiaodong_DMA_3\r\n");

七、 串口DMA收和发

(一)CubeMX配置

通用配置

中断开启

DMA发送设置

DMA接收设置,要注意这里是循环

(二)编程实现

收发函数原型

#include "string.h"
extern DMA_HandleTypeDef hdma_usart1_tx;

//发送数组数据
void my_uart1_send_data(uint8_t *tdata,uint16_t tnum){
        //等待发送状态OK
        while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
        //发送数据
        HAL_UART_Transmit_DMA(&huart1,tdata,tnum);
}

//发送字符串
void my_uart1_send_string(uint8_t *tdata){
        //等待发送状态OK
        while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
        //发送数据
        HAL_UART_Transmit_DMA(&huart1,tdata,strlen(tdata));
}

uint8_t my_uart1_redata=0;
//开启串口接收中断
void my_uart1_enable_inpterr(){
    //开启一次中断
    //HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    HAL_UART_Receive_DMA(&huart1,&my_uart1_redata,1);//设置接收缓冲区
    
}
//串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    if(huart->Instance == USART1)//判断串口号
    {
        //发送
        my_uart1_send_data(&my_uart1_redata,1);
        //开启一次中断
        //HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);
    }
}

主函数使用

//初始化DMA接收
my_uart1_enable_inpterr();
//发送函数调用
my_uart1_send_data("1dongxiaodong_DMA_1\r\n",strlen("1dongxiaodong_DMA_1\r\n"));
my_uart1_send_data("2dongxiaodong_DMA_2\r\n",strlen("2dongxiaodong_DMA_2\r\n"));
my_uart1_send_string("3dongxiaodong_DMA_3\r\n");

八、printf实现

#include <stdio.h>
int fputc(int ch,FILE *f)
{
    uint32_t temp = ch;
 
    HAL_UART_Transmit(&huart1,(uint8_t *)&temp,1,0xFFFF);        //huart1是串口的句柄
    HAL_Delay(2);
 
    return ch;
}

参考:

正点原子、洋桃电子

相关文章
STM32 HAL库 UART 串口读写功能笔记
日志https://www.cnblogs.com/Mysterious/p/4804188.html STM32L0 HAL库 UART 串口读写功能 串口发送功能: uint8_t TxData[10 ...
1
(4)STM32使用HAL库实现串口通讯——理论讲解
日志一.查询模式 1. 二.中断模式 1.中断接收. 1.1先看中断接收的流程(以 USART2 为例) 在启动文件中找到中断向量 USART2_IRQHandler 找到USART2_IRQHandle ...
1
STM32 HAL库使用中断实现串口接收不定长数据
日志以前用DMA实现接收不定长数据,DMA的方法接收串口助手的数据,全部没问题,不过如果接收模块返回的数据,而这些数据如果包含回车换行的话就会停止接收,例如接收:AT\r\nOK\r\n,就只能接收到AT ...
2
STM32 HAL库的定时器中断回调函数跟串口中断回调函数
日志void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { //添加回调后的程序逻辑 if (htim->Instance == ...
2
STM32 HAL库利用DMA实现串口不定长度接收方法
日志参考:https://blog.csdn.net/u014470361/article/details/79206352 我这里使用的芯片是 F1 系列的,主要是利用 DMA 数据传输方式实现的,在配 ...
1
STM32 HAL库学习系列第1篇 ADC配置 及 DAC配置
日志ADC工作均为非阻塞状态 轮询模式 中断模式 DMA模式 库函数: HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);//轮询模式,需放 ...
3
STM32 HAL库学习系列第5篇 定时器TIM---编码器接口模式配置
日志cube基本配置,外设开启编码器,串口2 可能大家在设置的时候有这个错误 错误:error:  #20: identifier "TIM_ICPOLARITY_BOTHEDGE" ...
1
STM32 HAL库学习系列第4篇 定时器TIM----- 开始定时器与PWM输出配置
日志基本流程: 1.配置定时器 2.开启定时器 3.动态改变pwm输出,改变值  HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1); 函数总结: __HAL_TIM ...
2
STM32 HAL库学习系列第3篇 常使用的几种延时方式
日志1   自带的hal_delay 函数    毫秒级延迟 void HAL_Delay(__IO uint32_t Delay) { uint32_t tickstart = HAL_GetTick( ...
1
STM32 HAL库学习系列第2篇 GPIO配置
日志GPIO 库函数 基本就是使用以下几个函数 GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void H ...
2
STM32 HAL库学习系列第8篇---回调函数总结
日志普通函数与回调函数的区别:就是ST将中断封装,给使用者的API,就是标准库的中断函数 对普通函数的调用: 调用程序发出对普通函数的调用后,程序执行立即转向被调用函数执行,直到被调用函数执行完毕后,再返 ...
1
STM32 HAL库学习系列第7篇---定时器TIM 输入捕获功能
日志测量脉冲宽度或者测量频率 基本方法 1.设置TIM2 CH1为输入捕获功能:  2.设置上升沿捕获:  3.使能TIM2 CH1捕获功能:  4.捕获到上升沿后,存入capture_buf[0],改为 ...
1
STM32 HAL库 UART使用printf
日志STM32 HAL库 UART使用printf // 添加这个函数 int fputc(int ch,FILE *f) { uint8_t temp[1]={ch}; HAL_UART_Transmi ...
3
STM32L0 HAL库 UART 串口读写功能
日志串口发送功能: uint8_t TxData[10]= "01234abcde"; HAL_UART_Transmit(&huart2,TxData,10,0xffff); ...
1
STM32 HAL库 IIC 协议库函数
日志/* 第1个参数为I2C操作句柄 第2个参数为从机设备地址 第3个参数为从机寄存器地址 第4个参数为从机寄存器地址长度 第5个参数为发送的数据的起始地址 第6个参数为传输数据的大小 第7个参数为操作超 ...
1
STM32串口接收中断——基于HAL库
日志写在前面 最近需要使用一款STM32L4系列的芯片进行开发,需要学习使用HAL库.在进行串口中断使用的时候遇到了一些小麻烦,写下解决方案供大家参考. 1.UART相关的头文件引用错误   由于本人直接 ...
1
(7)STM32使用HAL库实现RS485通讯(全双工串口)
日志一.硬件 如下图所示,485芯片链接到单片机的USART2上,但是默认的USART2并不是在PD5和PD6上,这里是需要重映射的.另外PG4作为485收发的控制(在485协议中,RE.DE同时为高电平 ...
3
(1)STM32使用HAL库操作GPIO
日志一  初始化GPIO 使用HAL库的优点在于不用手动添加初始化的代码了,CubeMX会根据软件设置自动生成. 自动生成的HAL库GPIO初始化代码: static void MX_GPIO_Init( ...
2
STM32H7教程 第29章 STM32H7的USART串口基础知识和HAL库API
日志完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第29章       STM32H7的USART串口基础 ...
1
STM32 实现 4*4 矩阵键盘扫描(HAL库、标准库 都适用)
日志本文实现的代码是基于STM32HAL库的基础上的,不过标准库也可以用,只是调用的库函数不同,逻辑跟配置是一样的,按我这里的逻辑来配置即可. 1.键盘原理图: 原理举例:先把 F0-F7 内部拉高,这样 ...
1