STM32基础知识:串口通信-中断方式

发布时间:2024-04-15  

串口通信-中断方式

1 中断方式的串口通信

串口中断方式的特点:

  • 发送数据时,将一字节数据放入数据寄存器DR;接收数据时,将DR的内容存放到用户存储区;

  • 中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据;

  • 在传输数据量较大,且通信波特率较高(大于38400)时,如果采用中断方式,每收发一个字节的数据,CPU都会被打断,造成CPU无法处理其他事务。因此在批量数据传输,通信波特率较高时,建议采用DMA方式。

  1. 串口中断方式发送函数:HAL_UART_Transmit_IT


    函数原型 HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size)
    功能描述 在中断方式下发送一定数量的数据
    入口参数1 huart:串口句柄的地址
    入口参数 pData:待发送数据的首地址
    入口参数3 Size:待发送数据的个数
    入口参数4 Timeout:超时等待时间, 以ms为单位, HAL MAX DELAY表示无限等待
    返回值 HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用;
    注意事项 1. 函数将使能串口发送中断2. 函数将置位TXEIE和TCIE,使能发送数据寄存器空中断和发送完成中断。完成指定数量的数据发送后,将会关闭发送中断,即清零TXEIE和TCIE。因此用户采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启发送中断3. 当指定数量的数据发送完成后,将调用发送中断回调函数HAL_UART_TxCpltCallback进行后续处理4. 该函数由用户调用
  2. 串口中断方式接收函数:HAL_UART_Receive_IT

    函数原型 HAL_StatusTypeDef HAL_UART_Receive_IT(UART_Handle TypeDef *huart, uint 8_t *pData, uint 16_t Size, uint 32_t Timeout)
    功能描述 在中断方式下接收一定数量的数据
    入口参数1 huart:串口句柄的地址
    入口参数2 pData:存放数据的首地址
    入口参数3 Size:待接收数据的个数
    入口参数4 Timeout:超时等待时间, 以ms为单位, HAL MAX DELAY表示无限等待
    返回值 HAL状态值:HAL_OK表示发送成功;HAL_ERROR表示参数错误;HAL_BUSY表示串口被占用;
    注意事项 1. 函数将使能串口接收中断2. 函数将置位RXNEIE,使能接收数据寄存器非空中断RXNE。完成指定数量的数据接收后,将会关闭接收中断,即清零RXNEIE。因此用户采用中断方式连续接收数据时,要重复调用该函数,以重新开启接收中断3. 当指定数量的数据接收完成后,将调用接收中断回调函数HAL_UART_RxCpltCallback进行后续处理4. 该函数由用户调用
  3. 串口中断通用处理函数:HAL_UART_IRQHandler

    函数原型 void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
    功能描述 作为所有串口中断发生后的通用处理函数
    入口参数 htim:定时器句柄的地址
    返回值
    注意事项 1. 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成对应的中断处理2. 该函数由CubeMX自动生成
  4. 串口发送中断回调函数:HAL_UART_TxCpltCallback

    函数原型 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    功能描述 回调函数,用于处理所有串口的发送中断,用户在该函数内编写实际的任务处理程序
    入口参数 htim:定时器句柄的地址
    返回值
    注意事项 1. 函数由串口中断通用处理函数HAL_UART_IRQHandler调用,完成所有注意事项2.串口的发送中断任务处理函数内部需要根据串口句柄的实例来判断是哪一个串口产生的发送中断3. 函数由用户根据具体的处理任务编写
  5. 串口接收中断回调函数:HAL_UART_RxCpltCallback

    函数原型 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    功能描述 回调函数,用于处理所有串口的接收中断,用户在该函数内编写实际的任务处理程序
    入口参数 htim:定时器句柄的地址
    返回值
    注意事项 1. 函数由串口中断通用处理函数HAL_UART_IRQHandler调用,完成所有注意事项2.串口的发送中断任务处理函数内部需要根据串口句柄的实例来判断是哪一个串口产生的接收中断3. 函数由用户根据具体的处理任务编写
  6. 串口中断使能函数:__HAL_UART_ENABLE_IT

    函数原型 __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)
    功能描述 使能对应的串口中断类型
    入口参数 __INTERRUPT __ :串口中断类型,该参数几个常用的取值如下UART_IT_TXE :发送数据寄存器空中断UART_IT_TC :发送完成中断UART_IT_RXNE:接收数据寄存器非空中断UART_IT_IDLE :线路空闲中断
    返回值
    注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 函数需要由用户调用,用于使能对应的串口中断类型
  7. 串口中断标志查询函数:__HAL_UART_GET_FLAG

    函数原型 __HAL_UART_GET_FLAG (__HANDLE__, __INTERRUPT__)
    功能描述 查询对应的串口中断类型
    入口参数 __INTERRUPT __ :串口中断类型,该参数几个常用的取值如下UART_IT_TXE :发送数据寄存器空中断UART_IT_TC :发送完成中断UART_IT_RXNE:接收数据寄存器非空中断UART_IT_IDLE :线路空闲中断
    返回值 中断标志的状态值:SET表示中断标志置位;RESET表示中断标志没有置位
    注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 函数需要由用户调用,用于查询对应的串口中断类型
  8. 空闲中断标志清除函数:__HAL_UART_CLEAR_IDLEFLAG

    函数原型 __HAL_UART_CLEAR_IDLEFLAG
    功能描述 清除串口的空闲中断标志
    入口参数 HANDLE :串口句柄的地址
    返回值
    注意事项 1. 该函数是宏函数,进行宏替换,不发生函数调用2. 函数需要由用户调用,用于清除对应的串口空闲中断标志

2 HAL库串口中断处理过程:

图片

  • HAL_UART_Receive_IT:开启中断,在中断方式下接收一定数量的数据。

  • USART2_IRQHandler:串口2的中断服务程序,调用串口中断通用处理函数HAL_UART_IRQHandler。

  • HAL_UART_IRQHandler:在函数 HAL_UART_IRQHandler内部通过判断中断类型是否为接收完成中断,确定是否调用UART_Receive_IT。

函数UART_Receive_IT的作用是把每次中断接收到的字符保存在串口句柄的缓存指针pRxBuffPtr中,同时每次接收一个字符,其计数器 RxXferCount 减 1,直到接收完成 RxXferSize 个字符之后 RxXferCount设置为0,同时调用接收完成回调函数 HAL_UART_RxCpltCallback进行处理。

  • HAL_UART_RxCpltCallback:函数由串口中断通用处理函数UART_Receive_IT调用,完成所有串口的接收中断任务处理,函数内部需要根据串口句柄的实例来判断是哪一个串口产生的接收中断,函数由用户根据具体的处理任务编写。

3 任务实践2

利用串口调试助手,从PC上发送10个字符到开发板,开发板收到后原样发回到PC。

前后台编程模式: 前台程序为中断服务程序,一旦数据接收完成,则设置一个标志位为1;后台程序为while(1)的死循环,在循环中不断检测标志位是否为1。如果为1,表明数据接收完成,并存放在接收缓冲区中。然后进行后续处理:先清除标志位,再把接收的数据原样发回。

  1. 串口外设配置
    图片
    图片

    1. 异步模式,无硬件流控

    2. 设置通信参数:波特率115200,8位数据位,无奇偶校验,1位停止位,使能接收和发送,16倍过采样(CubeMX默认配置)

    3. 使能串口中断

编写代码

printf和scanf重定向:略


// -----------------------------------------------------------------------//

/* USER CODE BEGIN PD */

#define LENGTH 10         // 接收数据缓冲区大小

/* USER CODE END PD */


// -----------------------------------------------------------------------//

/* USER CODE BEGIN PV */

uint8_t RxBuffer[LENGTH]; // 接收缓冲区

uint8_t RxFlag = 0;       // 接收完成标志,0未完成,1完成

/* USER CODE END PV */


// -----------------------------------------------------------------------//

  /* USER CODE BEGIN 2 */

  // 打印提示信息

  printf("UART Communication Using ITn");

  printf("Please enter 10 characters:n");

  HAL_UART_Receive_IT(&huart1, (uint8_t *)RxBuffer, LENGTH);  // 使能接收中断

  /* USER CODE END 2 */


// -----------------------------------------------------------------------//

  while (1)

  {

    /* USER CODE BEGIN 3 */

    if (RxFlag == 1)   // 判断接收是否完成

    {

      RxFlag = 0;      // 接收完成,清除标志位

      printf("Receive Successfully!n");  // 打印提示信息

      HAL_UART_Transmit_IT(&huart1, (uint8_t*)RxBuffer, LENGTH);  // 将接收的字符原样发回

    }

  }

  /* USER CODE END 3 */


// -----------------------------------------------------------------------//

/* USER CODE BEGIN 4 */

int fputc(int ch, FILE *f)

{

  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);

  return ch;

}


int fgetc(FILE *f)

{

  uint8_t ch = 0;

  HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);

  return ch;

}


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

  if (huart- >Instance == USART1)  // 判断发生接收中断的串口

  {

    RxFlag = 1;  // 置位接收完成b标志

    HAL_UART_Receive_IT(&huart1, (uint8_t*)RxBuffer, LENGTH);  // 使能接收中断

  }

}

/* USER CODE END 4 */

// -----------------------------------------------------------------------//


  1. 实验现象

    图片

4 任务实践3

实现简单的帧格式通信:PC按照自定义的帧格式发送指令开启或关闭开发板上的LED1。

帧格式的概念:

  1. 帧(Frame)是数据传输的一种单位。一帧数据由多个字符组合而成,不同字段的字符代表不同的含义,执行不同的功能;

  2. 在实际的工程应用中,数据的传输常常以帧为单位来进行,如工控领域中最常用的Modbus通信协议中的消息帧;

  3. 发送方按照规定的帧格式发送一帧数据,接收方接收下这一帧数据后,再按照帧格式进行解析,最后完成后续的处理。

Modbus消息帧格式:

起始符 设备地址 功能代码 数据 校验 结束符
1个字符 2个字符 1个字符 n个字符 2个字符 1个字符
  • 起始符:表示一帧数据的开始

  • 设备地址:用于指定需要进行信息传递的设备

  • 功能代码:用于指定需要完成的操作

  • 数据:表示需要传输的数据

  • 校验:用于通信中的错误校验

  • 结束符:表示一帧数据的结束

自定义的帧格式设定:

帧头 设备码 功能码 帧尾
0xaa 1个字符(8bit) 1个字符(8bit) 0x55
  • 帧头 :0xaa表示一帧数据的开始

  • 设备码:0x01表示指示灯

  • 功能码:0x00表示关闭指示灯,0x01表示开启指示灯

  • 帧尾 :0x55表示一帧数据的结束

  1. 串口配置同任务实践1

  2. 配置PA1为GPIO_Output模式


编写代码


// -----------------------------------------------------------------------//

/* USER CODE BEGIN PV */

uint8_t RxBuffer[4];  // 接收缓冲区

uint8_t RxFlag = 0;   // 接收完成标志,0位完成,1完成

uint8_t ErrFlag = 0;  // 指令错误标志,0正确,1错误

/* USER CODE END PV */


// -----------------------------------------------------------------------//

  /* USER CODE BEGIN 2 */

  // 打印提示信息

  printf("*****  Communication Frame  *****n");

  printf("Please enter instruction:n");

  printf("Head- >0xaa, Device- >0x01, Operation- >0x00/0x10, Tail- >0x55n");

  HAL_UART_Receive_IT(&huart1, (uint8_t*)RxBuffer, 4);  // 使能接收中断

  /* USER CODE END 2 */


// -----------------------------------------------------------------------//

  while(1)

  {

    /* USER CODE BEGIN 3 */

    // Determine if reception is complete

    if (RxFlag == 1)  // 判断接收是否完成              

    {

      RxFlag = 0;     // 完成,清除标志位   


      // 帧格式解析

      printf("head = RxBuffer[0] = %xn", RxBuffer[0]);

      printf("tail = RxBuffer[3] = %xn", RxBuffer[3]);

      printf("device = RxBuffer[1] = %xn", RxBuffer[1]);

      printf("function = RxBuffer[2] = %xn", RxBuffer[2]);


      if (RxBuffer[0] == 0xaa && RxBuffer[3] == 0x55)  // 判断帧头帧尾

      {

        if (RxBuffer[1] == 0x01)          // 判断设备码

        {

          if (RxBuffer[2] == 0x00)        // 判断功能码

          {

            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

            printf("LED1 is close!n");

          }

          else if (RxBuffer[2] == 0x01)   // 判断功能码

          {

            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);

            printf("LED1 is open!n");

          }

          else              // 功能码错误

          {

            ErrFlag = 3;

          }

        }

        else                // 设备码错误

        {

          ErrFlag = 2;      

        }

      }

      else                  // 帧头帧尾错误

      {

        ErrFlag = 1;

      }


      // 发送错误提示信息

      switch (ErrFlag)

      {

        case 1: 

          printf("Head and tail error! Please send again!n");

          break;

        case 2: 

          printf("Device code error! Please send again!n");  

          break;

        case 3: 

          printf("Function code error! Please send again!n");  

          break;   

        default:

          break;

      }


      // 清除接收缓冲区和错误标志,准备下一次接收

      ErrFlag = 0;

      RxBuffer[0] = 0;

      RxBuffer[1] = 0;

      RxBuffer[2] = 0;

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>