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

描述

串口通信-中断方式

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)
    功能描述在中断方式下发送一定数量的数据
    入口参数1huart:串口句柄的地址
    入口参数pData:待发送数据的首地址
    入口参数3Size:待发送数据的个数
    入口参数4Timeout:超时等待时间, 以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)
    功能描述在中断方式下接收一定数量的数据
    入口参数1huart:串口句柄的地址
    入口参数2pData:存放数据的首地址
    入口参数3Size:待接收数据的个数
    入口参数4Timeout:超时等待时间, 以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. 使能串口中断
  2. 编写代码
    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 */
    // -----------------------------------------------------------------------//
    

    实验现象

    中断

4 任务实践3

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

帧格式的概念:

  1. 帧(Frame)是数据传输的一种单位。一帧数据由多个字符组合而成,不同字段的字符代表不同的含义,执行不同的功能;
  2. 在实际的工程应用中,数据的传输常常以帧为单位来进行,如工控领域中最常用的Modbus通信协议中的消息帧;
  3. 发送方按照规定的帧格式发送一帧数据,接收方接收下这一帧数据后,再按照帧格式进行解析,最后完成后续的处理。

Modbus消息帧格式:

起始符设备地址功能代码数据校验结束符
1个字符2个字符1个字符n个字符2个字符1个字符
  • 起始符:表示一帧数据的开始
  • 设备地址:用于指定需要进行信息传递的设备
  • 功能代码:用于指定需要完成的操作
  • 数据:表示需要传输的数据
  • 校验:用于通信中的错误校验
  • 结束符:表示一帧数据的结束

自定义的帧格式设定:

帧头设备码功能码帧尾
0xaa1个字符(8bit)1个字符(8bit)0x55
  • 帧头 :0xaa表示一帧数据的开始
  • 设备码:0x01表示指示灯
  • 功能码:0x00表示关闭指示灯,0x01表示开启指示灯
  • 帧尾 :0x55表示一帧数据的结束
  1. 串口配置同任务实践1

  2. 配置PA1为GPIO_Output模式

  3. 编写代码

    // -----------------------------------------------------------------------//
    /* 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;
          RxBuffer[3] = 0;
        }
      }
      /* 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;  // 置位接收完成标志
        HAL_UART_Receive_IT(&huart1 , (uint8_t*)RxBuffer, 4);  // 使能串口中断
      }
    }
    /* USER CODE END 4 */
    

    实验现象

中断

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分