串口中断收发定长数据

2023-05-16

一、实验设计效果

  1. 配置串口助手波特率为115200,传输数据长度为8 Bit,无奇偶校验位,1个停止位;
  2. 通过串口助手向MCU发送指定长度的字符串,MCU接收到指定长度的字符串后,回发给串口助手;

二、硬件工作原理和原理图

我们使用的正点原子STM32F103RB NANO开发板上将USART1(引脚为PA9,PA10)接出

USART1并没有在PCB上连接在一起,需要通过跳线帽来连接一下。这里我们把 P5 的 RXD 和 TXD 用跳线帽与 PA9 和 PA10 连接起来。

信号传输:串口 —— 调试器 —— USB ——电脑

三、实验记录

完成 CubeMX初始化配置 → 生成初始化HAL库工程 →在keli中编写串口程序

1. 完成 CubeMX初始化配置

1.1 利用CubeMX完成HAL库工程模板和初始化:

通过选择芯片型号创建CubeMX工程

在弹出的对话框中输入开发板上的芯片型号,STM32F103RB

在右侧筛选栏中选择Tx型,即开发板上芯片所用的LQFP64型封装,双击建立工程

1.2 RCC模块引脚的配置

在弹出的工程配置对话框中的第一个引脚配置选项卡下,先完成RCC时钟模块引脚配置:

选择启用外部的高速和低速时钟源,HSE和LSE,配置为晶振连接;

配置完成后,对应时钟引脚变绿,同时旁边出现其将要配置模式的文字说明;

1.3 串口的配置

  1. 在侧边栏的通信外设中选择USART1
  2. 在串口下拉框出选择异步(Asynchronous),成功后可以看到对应的串口引脚RX(接收引脚),TX(发送引脚)变绿。
  3. 选择下方的Parameter Settings选项卡确认串口参数。设置波特率为115200,传输数据长度为8 Bit,无奇偶校验位,1个停止位,数据方向(Data Direction)为接收和发送(Receive and Transmit)

  1. 在NVIC Settings选项卡下,开启串口中断,如需调整中断优先级可在右侧修改

1.4 完成时钟配置:

我们前面启用了RCC时钟模块的外部时钟引脚,这里我们需要将外部时钟源配置为实际使用的频率;

查看手册可知:LSE为32.768KHz,HSE为8MHz;

点击上方的时钟配置选项卡,进入时钟配置界面;

前面启用了外部晶振源,这里检查修改HSE\LSE的频率,和实际一致;

在主频选择框中输入72MHz,然后按回车OK;

系统将自动将时钟源、时钟流向、PLL等配置好,以产生相应的主频;

1.5 创建生成工程

2、UART模块HAL初始化代码及API带读

2.1 串口的初始化

1)打开main.c 文件,在硬件外设初始化位置,可以看到串口的初始化函数,MX_USART1_UART_Init();

2)MX_USART1_UART_Init()解析

3)HAL_UART_Init() 解析

函数前面部分位参数检验等次要内容,主要工作是完成串口的物理层和协议层配置,分别调用HAL_UART_MspInit(huart) UART_SetConfig(huart) 完成;

4)HAL_UART_MspInit() 解析

5)总结

2.2 串口中断 和 回调函数

1)同外部中断一样,使能串口中断后,相应串口中断的ISR也出现在stm32f1xx_it.c中

理论部分中提到,STM32串口有多个中断源,所以进入后,首先要判断当前进入中断的触发源是哪个

中断事件

事件标志

使能控制位

发送数据寄存器为空

TXE

TXEIE

CTS 标志

CTS

CTSIE

发送完成

TC

TCIE

准备好读取接收到的数据

RXNE

RXNEIE

检测到上溢错误

ORE

检测到空闲线路

IDLE

IDLEIE

奇偶校验错误

PE

PEIE

断路标志

LBD

LBDIE

多缓冲通信中的噪声标志、上溢错误和帧错误

NF/ORE/FE

EIE

2)HAL_UART_IRQHandler解析

在HAL_UART_IRQHandler中无非是三件事,判断是由什么中断响应的,有错误则处理,响应要调用接收或者发送处理函数

在本例中,我们发送使用轮询方式,而接收使用中断方式。UART每接收到一个字节都会触发串口ISR,并运行至2#处执行UART_Receive_IT(huart)。

3)UART_Receive_IT 解析

UART_Receive_IT 分为两部分:

一、判断UART是否忙碌,及有无完成定长数据的接收(由预设接收长度和实际接收长度确定);如还未完成,则根据是8位还是16位模式,自动将数据搬运至内存的对应位置,并更新数据接收相关状态参数;

/**
  * @brief  Receives an amount of data in non blocking mode
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
  uint8_t  *pdata8bits;
  uint16_t *pdata16bits;

  /* Check that a Rx process is ongoing */
  if (huart->RxState == HAL_UART_STATE_BUSY_RX)
  {
    if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
    {
      pdata8bits  = NULL;
      pdata16bits = (uint16_t *) huart->pRxBuffPtr;
      *pdata16bits = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
      huart->pRxBuffPtr += 2U;
    }
    else
    {
      pdata8bits = (uint8_t *) huart->pRxBuffPtr;
      pdata16bits  = NULL;

      if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
      {
        *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
      }
      else
      {
        *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
      }
      huart->pRxBuffPtr += 1U;
    }

如果接收完定长数据(--huart->RxXferCount == 0U),则除能所有串口接收中断。然后再根据空闲接收模式和一般接收模式进行处理;本例中我们使用的是一般接收模式,则会执行串口接收完成的回调函数HAL_UART_RxCpltCallback(huart);

 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;

      /* Check current reception Mode :
         If Reception till IDLE event has been selected : */
      if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
      {
        /* Set reception type to Standard */
        huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

        /* Disable IDLE interrupt */
        CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);

        /* Check if IDLE flag is set */
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
        {
          /* Clear IDLE flag in ISR */
          __HAL_UART_CLEAR_IDLEFLAG(huart);
        }

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered Rx Event callback*/
        huart->RxEventCallback(huart, huart->RxXferSize);
#else
        /*Call legacy weak Rx Event callback*/
        HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif
      }
      else
      {
       /* Standard reception API called */
#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 */
      }

4)HAL_UART_RxCpltCallback 串口接收完成回调函数

和前面的外部中断回调函数 HAL_GPIO_EXTI_Callback 一样,是一个弱定义的占位空函数,使用需要我们自己重写一个同名的函数,来完成串口接收完成后的响应内容;

5)接收完成中断流程图:

这里,我们再把串口接收中断的一般流程进行概括:当接收到一个字符之后,就会触发串口接收中断,在函数UART_Receive_IT中会把数据保存在串口句柄的成员变量pRxBuffPtr缓存中,同时RxXferCount 计数器减 1。如果我们设置RxXferSize=10,那么当接收到 10 个字符之后, RxXferCount 会由10 减到 0( RxXferCount 初始值等于 RxXferSize),这个时候再调用接收完成回调函数 HAL_UART_RxCpltCallback 进行处理。

2.3 串口HAL库相关API简介

stm32f1xx_hal_uart.c文件中:

1)HAL_StatusTypeDef HAL_UART_Transmit:串口发送函数

/**
  * @brief  Sends an amount of data in blocking mode.
  * @note   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
  *         the sent data is handled as a set of u16. In this case, Size must indicate the number
  *         of u16 provided through pData.
  * @param  huart Pointer to a UART_HandleTypeDef structure that contains
  *               the configuration information for the specified UART module.
  * @param  pData Pointer to data buffer (u8 or u16 data elements).
  * @param  Size  Amount of data elements (u8 or u16) to be sent
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
//STM32F1 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。
当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
通过该函数向串口寄存器 USART_DR 写入一个数据。
以阻止模式发送大量数据。
第一个参数:huart 指向包含指定 UART 模块的配置信息的UART_HandleTypeDef结构。
第二个参数:pData 指针指向数据缓冲区(传进的是用户定义的存储发送数据的变量)
第三个参数:Size 要发送的数据元素量(u8 或 u16)
第四个参数:Timeout 超时持续时间
返回 HAL 状态

2)HAL_UART_Receive: 串口接收函数

参数列表和发送函数类似

判断是否忙-->锁住-->标记接收忙-->获取tick计数,判断是否超时

-->赋值RxXferCount有多少数据要接收-->每次从DR内获取一个Byte存在pData指向的空间

uint32_t tickstart = 0U;
 
if (huart->RxState == HAL_UART_STATE_READY)  /* Check that a Rx process is not already ongoing */
  {
    __HAL_LOCK(huart);    /* Process Locked */
 
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;
 
    tickstart = HAL_GetTick();
 
    huart->RxXferSize = Size;
    huart->RxXferCount = Size;
 
 
    while (huart->RxXferCount > 0U)
    {
      huart->RxXferCount--;
      if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
            return HAL_TIMEOUT;
      *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
      }
}
 
    huart->RxState = HAL_UART_STATE_READY;
    __HAL_UNLOCK(huart);

3)HAL_UART_Receive_IT

只是配置了一下参数,并没有做任何处理;

配置存储在pData指向位置、空间大小RxXferSize 、接收计数RxXferCount ; 接收状态忙;确保使能接收中断

那么当有数据来的时候,依靠中断函数来处理,并更新句柄中的相应状态。

/* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    __HAL_LOCK(huart);    /* Process Locked */
    huart->pRxBuffPtr = pData;
    huart->RxXferSize = Size;
    huart->RxXferCount = Size;
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;
    __HAL_UNLOCK(huart);    /* Process Unlocked */
 
    /*Error Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_PE);
    __HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
 
    /* Enable the UART Data Register not empty Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
 
    return HAL_OK;
    }

3、程序编写

3.1 定义发送和接收缓冲区

这里可以对 LENGTH 常数宏进行修改,快速配置接收缓冲区字符长度;

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define LENGTH 14
uint8_t hello[] = "USART1 is ready...\r\n";
uint8_t recv_buf[LENGTH] = {0};
/* USER CODE END 0 */

3.2 修改main函数

在main函数中首先使用 HAL_UART_Transmit 轮询方式 发送hello数组内的提示信息,然后开启串口中断接收定长字符,这里,HAL_UART_Receive_IT(&huart1, (uint8_t*)recv_buf, 13); 准备以中断方式接收13个字符,存入字符型数组recv_buf中。

/* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    /* USER CODE BEGIN 2 */
    //发送提示信息
    HAL_UART_Transmit(&huart1, (uint8_t*)hello, sizeof(hello), 0xffff);
    //使能串口中断接收
    HAL_UART_Receive_IT(&huart1, (uint8_t*)recv_buf, LENGTH);
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

3.3 重新实现接收中断回调函数

在HAL中弱定义了一个中断回调函数HAL_UART_RxCpltCallback, 当调用HAL_UART_Receive_IT启动串口中断接收,在UART接收到足够的字节后就会自动调用该函数,我们需要在用户文件中重新定义该函数,放在哪都可以,这里我放在 main.c 中:

/* USER CODE BEGIN 4 */
/* 中断回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    /* 判断是哪个串口触发的中断 */
    if(huart ->Instance == USART1)
    {
        //将接收到的数据发送
        HAL_UART_Transmit_IT(huart, (uint8_t*)recv_buf, LENGTH);
        //重新使能串口接收中断
        HAL_UART_Receive_IT(huart, (uint8_t*)recv_buf, LENGTH);
    }
}
/* USER CODE END 4 */

4、烧录程序,并用串口助手观察实验现象

4.1 keli中的一些配置

后面直接编译烧录就行了

4.2 串口助手的使用:

  1. 通过USB连接上电脑,可在设备管理器中查询到单片机连接被分配的端口号,这里是COM4;

  1. 打开串口调试助手,我这里用的是XCOM V2.2;
    根据前面查询到的端口号,选择串口;
    根据程序里对串口控制器的配置选择串口协议设置;
    XCOM 串口调试助手的其他部分功能可见下图;

4.3 串口定长收发实验的现象

  1. 烧录完按下复位键运行程序,串口助手接收屏上显示初始化后的发送的欢迎信息:

  1. 此时串口中断接收定长的14字节已经使能,通过发送框往MCU发送字符将被存入缓冲数组中;
    满14个字符将触发接收完成中断,而在接收完成中断中,将会把接收到的缓冲数组中的字符重新发到串口助手上;

  1. 如勾选了发送新行,则每次发送会自动增加\r\n两个输出格式字符,效果如下

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

串口中断收发定长数据 的相关文章

  • 大端存储和小端存储

    一 大端存储 高字节数据存储在低地址 二 小端存储 低字节数据存储在低地址 注意 xff1a 无论是大端存储还是小端存储 xff0c 读取到的数据为0x0102030405 三 大端存储或小端存储都是由系统设定的 xff0c 其二者区别在于
  • 多播的概念

    一 多播概述 多播 xff1a 数据的收发仅仅在同一组中进行 xff08 相当于我往一个群里发 xff0c 只有加入这个群的人才能收到 xff09 多播的特点 xff1a 多播地址标示一组接口 多播可以用于广域网使用 在IPv4 xff0c
  • 利用C语言编写一个网络分析器

    一 链路层数据格式 mac报文 xff1a 14个字节 二 IP数据报文格式 三 TCP数据报文格式 四 UDP数据报文格式 五 demo xff08 网络分析器 xff09 recvfrom接收链路层帧数据 xff0c 不经过网络层 传输
  • ARP攻击代码(ARP欺骗)

    一 ARP攻击 ARP欺骗攻击原理 xff1a ARP欺骗攻击建立在局域网主机间相互信任的基础上的 比如 xff1a 假设A B C在同一个局域网中 xff0c 网关为192 168 43 1 xff0c IP地址和MAC地址分别假设如下
  • 使用wireshark抓包并分析TCP三次握手

    一 实验过程 1 TCP包抓取及分析过程 确认使用的协议 xff0c 使用HTTP服务 选择https www baidu com 作为目标地址 启动Wireshark软件 xff0c 点击开始抓包后 在浏览器地址输入https www b
  • 使用wireshark抓包并分析TCP四次挥手

    一 四次挥手 第一次挥手 xff1a xff08 FIN 43 ACK xff09 客户端发送FIN 43 ACK包给服务端 xff0c 用来关闭客户端到服务器的数据传送 此时客户端进入FIN WAIT 1状态 第二次挥手 xff1a AC
  • TR-069协议介绍

    一 概念 关于TR 069 协议命名 TR 069 全称 Technical Report 069 是由DSL Forum xff08 一个非盈利性的全球行业联盟 致力于发展宽带网络范 xff09 其成员包括通讯 设备 计算机 网络和服务提
  • FFMPEG关键结构体——AVCodecContext

    一 AVCodecContext结构体 这是一个描述编码器上下文的数据结构 xff0c 包含了众多编码器需要的参数信息 该结构体在libavcodec avcode h中定义 二 常见变量
  • FFMPEG关键结构体——AVIOContext

    一 AVIOContext结构体 这个结构体 xff0c 是FFmpeg中有关io操作的顶层结构体 xff0c 是avio的核心 FFmpeg支持打开本地文件路径和流媒体协议的URL 该结构体在libavformat avio h中定义 二
  • 指针数组和数组指针

    一 什么是指针数组 一个数组 xff0c 若其元素均为指针类型数据 xff0c 称为指针数组 xff0c 也就是说 xff0c 指针数组中的每一个元素都存放一个地址 xff0c 相当于一个指针变量 span class token keyw
  • Symfony学习笔记之翻译组件-----translation总结

    过际化 xff08 internationalization xff0c 常被简写为i18n xff09 xff0c 是指将字符串和其他一些具有区域特征的片段 xff0c 从你的程序中提取 xff08 abstract xff09 出来 x
  • 为什么要配环境变量?path用来干什么?

    提出问题 xff1a 为什么要配环境变量 xff1f 配环境变量解决了什么 xff1f xff08 阿菜进来看哈嘛 xff09 注 xff1a 环境变量具体概念自行百度百科 概念理解 xff1a 环境变量相关概念 path路径 xff1a
  • Ubuntu18.04无wifi图标安装Realtek RTL8111/8168/8411网卡驱动详解

    这是本人第一次写博客 xff0c Ubuntu系统网卡驱动问题也已经遇到过两次了 xff0c 本次写下博客作为记录 xff0c 希望对遇到同样问题的同学有所帮助 电脑 xff1a 联想E14 系统 xff1a Ubuntu18 04 使用网
  • ROS echo 命令出现报错Cannot load message class for .... Are your messages built?解决办法

    1 把别人录制的bag包拿到自己的系统环境中echo某个topic想查看内容 xff0c 键入echo命令后弹出Are your messages built 错误 xff0c 原因 xff1a 没有在自己的工作空间下的devel incl
  • Pixhawk---基于NSH的Firmware开发与调试

    xfeff xfeff 版权声明 xff1a 本文为博主 原创 文章 xff0c 未经博主允许可以转载 xff0c 注明博客出处 xff1a http blog csdn net FreeApe 目录 43 相关知识了解 1 Nuttx系统
  • Ubuntu18.04安装Terminator后切换回默认终端

    step1 打开一个终端 xff0c 键入以下命令 xff1a sudo update alternatives config x terminal emulator step2 输入你的密码 xff0c 会看到可选项 xff0c 选择 u
  • Ubuntu 安装Cmd Markdown

    Step1 官网下载压缩包 https www zybuluo com cmd Step2 解压缩包 span class token function tar span xvf cmd markdown linux64 tar gz sp
  • Ubuntu20.04 Firefox浏览器设置暗黑主题

    Ubuntu20 04 Firefox浏览器设置暗黑主题 浏览器右上角点击三横线 xff0c 选择扩展 xff0c 搜索Dark Reader 安装并启用即可
  • ROS运行Rviz显示Marker基本形状,修改FixedFrame后无显示问题解决

    终端运行Rviz报错如下图所示 WARN 1667616309 306792325 Invalid argument pub shapes passed to canTransform argument source frame in tf

随机推荐