STM32CubeMX实战教程(六)——串口通信(为啥你的中文会乱码)

前言

串口通信可谓是所有单片机都具备的一种最基础的通信方式了,那么在本节中,我先将对单片机的通信原理进行初步讲解,再示范如何在STM32CubeMX中进行串口通信的配置。

通信接口

从通信接口上分,通信方式可分为并行通信串行通信两种

并行通信

  • 通信原理:数据各个位同时传输
  • 优点:速度快
  • 缺点:占用引脚资源多

串行通信

  • 通信原理:数据按位顺序传输
  • 优点:占用引脚资源少
  • 缺点:速度相对慢
    串口通信就是一种串行通信方式,所以我们这里重点讲解串行通信
按照数据传送方向分类
  • 单工: 数据传输只支持数据在一个方向上传输
  • 半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
  • 全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
串行通信的通信方式
  • 同步通信:带时钟同步信号传输。
    SPI,IIC通信接口
  • 异步通信:不带时钟同步信号。
    UART(通用异步收发器),单总线
USART和UART的区别

很多人开始学习的时候都分不清楚USARTUART有什么区别,这里我稍微解释一下
它们两个是同步和异步的区别。

  1. UART:universal asynchronous receiver and transmitter通用异步收发器

  2. USART:universal synchronous asynchronous receiver and transmitter通用同步异步收发器

一般而言,单片机中,名称为UART的接口一般只能用于异步串行通讯,而名称为USART的接口既可以用于同步串行通讯,也能用于异步串行通讯

工程配置

这次做一个返回接收到数据的实验,非常经典的串口通信时钟配置,老样子,不多说
时钟树
外设选择USART1,配置成异步通信模式
在这里插入图片描述
在这里插入图片描述
通信配置如下

  • 波特率设置(Baud Rate),没有哪种波特率最好,根据实际情况进行修改,要与串口调试助手上一致
  • 数据位数(Word Length),如果使能了奇偶校验,那么实际数据将在该位数上减一
  • 校验(Parity),可选择奇偶校验或不校验
  • 停止位(Stop Bits),额外一位或两位用于作为发送或接收完毕信号位
  • 数据方向(Data Direction),可选择仅发送,仅接收或收发模式
  • 过采样(Over Sampling),8倍或16倍采样率可以有效防止数据出错
    在这里插入图片描述
    另外使能串口中断,如果对中断仍有不理解的可阅读《STM32CubeMX实战教程(三)——外部中断(中断及HAL_Delay函数避坑)》
    在这里插入图片描述
    完事之后直接生成代码,进入工程。

进入代码

按照我的习惯,生成代码后都会看一下外设的初始化部分,如果说是检查一下是否出错的话倒不至于,一方面是详细看看STM32CubeMX已经帮我们完成了哪些步骤,另一方面是看看有哪些是我们一会儿可以用得上的。我觉得这其实是一个好习惯。

比如在这个工程的初始化代码中,huart1是已经初始化的串口句柄,而USART1是串口1地址,这些是需要用到的。

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }

}

另外,我们看到stm32f4xx_it.c 这个文件,这个文件里包含了所有中断的处理过程,在《STM32CubeMX实战教程(三)——外部中断(中断及HAL_Delay函数避坑)》中我已经教过大家怎样去寻找中断回调函数,这里再次做一个示范。
首先,找到中断服务函数,可以看到里面调用了一个HAL_UART_IRQHandler

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

追踪到函数原型,可以看到这个函数还是比较长的

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  uint32_t isrflags   = READ_REG(huart->Instance->SR);
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  uint32_t errorflags = 0x00U;
  uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if (errorflags == RESET)
  {
    /* UART in mode Receiver -------------------------------------------------*/
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      UART_Receive_IT(huart);
      return;
    }
  }

  /* If some errors occur */
  if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    /* UART parity error interrupt occurred ----------------------------------*/
    if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }

    /* UART noise error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }

    /* UART frame error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }

    /* UART Over-Run interrupt occurred --------------------------------------*/
    if (((isrflags & USART_SR_ORE) != RESET) && (((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_EIE) != RESET)))
    {
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    /* Call UART Error Call back function if need be --------------------------*/
    if (huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
      /* UART in mode Receiver -----------------------------------------------*/
      if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
      {
        UART_Receive_IT(huart);
      }

      /* If Overrun error occurs, or if any error occurs in DMA mode reception,
         consider error as blocking */
      dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
      if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
      {
        /* Blocking error : transfer is aborted
           Set the UART state ready to be able to start again the process,
           Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
        UART_EndRxTransfer(huart);

        /* Disable the UART DMA Rx request if enabled */
        if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
        {
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

          /* Abort the UART DMA Rx stream */
          if (huart->hdmarx != NULL)
          {
            /* Set the UART DMA Abort callback :
               will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
            huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
            if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
            {
              /* Call Directly XferAbortCallback function in case of error */
              huart->hdmarx->XferAbortCallback(huart->hdmarx);
            }
          }
          else
          {
            /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
            /*Call registered error callback*/
            huart->ErrorCallback(huart);
#else
            /*Call legacy weak error callback*/
            HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
          }
        }
        else
        {
          /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
          /*Call registered error callback*/
          huart->ErrorCallback(huart);
#else
          /*Call legacy weak error callback*/
          HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
        }
      }
      else
      {
        /* Non Blocking error : transfer could go on.
           Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered error callback*/
        huart->ErrorCallback(huart);
#else
        /*Call legacy weak error callback*/
        HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

  /* UART in mode Transmitter ------------------------------------------------*/
  if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }

  /* UART in mode Transmitter end --------------------------------------------*/
  if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  {
    UART_EndTransmit_IT(huart);
    return;
  }
}

根据以往的经验,直接去寻找带Callback的就是回调函数,但是这里面只出现的ErrorCallback,这是报错回调,当系统出错后才会进入的回调,可用于调试,显然不是我们需要的,其实,我们直接进UART_Receive_IT就可以发现里面有一个HAL_UART_RxCpltCallback,这个就是我们要找的回调函数了。接下来我们还是回到stm32f4xx_it.c 并在最下面加入代码

  • Buffer是在main.c中定义的uint8_t类型全局变量
  • 每接受的一个字节后产生中断,将该字节数据返回并重新开启中断
  • 函数不懂如何使用的话可以参考HAL库函数开发手册
  • 这里的数字1可以改成别的数字以一次性接受更多字节,但Buffer需要更改成更大的数组
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance==USART1)
    {
        HAL_UART_Transmit(&huart1, &Buffer, 1, 0xff);
        HAL_UART_Receive_IT(&huart1,&Buffer,1);
    }
}
/* USER CODE END 1 */

另外在main.c中,while循环前,串口初始化后,添加接收中断开启函数,这样在第一次接收到数据的时候才会触发中断,毕竟这些工作STM32CubeMX是没有帮我们完成的

  HAL_UART_Receive_IT(&huart1,&Buffer,1);

printf重定向

玩过STM32的人应该都很清楚printf重定向的意思,不知道也没关系,因为其实非常简单。说白了就是把printf函数用在STM32中,而printf的输出终端是串口。那么,怎么做到这一点呢,那就需要将printf函数重定向一下。稍微解释一下呢,就是printf函数底层是调用了fputc函数,而这个函数是一个弱化函数,在之前《STM32CubeMX实战教程(三)——外部中断(中断及HAL_Delay函数避坑)》中我也介绍过弱化函数的意思,所以我们只需要在usart.c中重写这个函数并使其作用于串口即可,具体如下

/* USER CODE BEGIN 1 */
int fputc(int ch,FILE *f)
{
	HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,100);
	return ch;
}
/* USER CODE END 1 */

这时候我们只需要包含stdio.h即可调用printf函数

下载验证

根据上面的工程,下载验证的话会出现一个问题,就是英文正常,如果输入中文则会返回乱码,解决方法是添加一个标志位,标记一组数据的末尾,并将数据接收完毕后一次性发送,而不是每接收到一帧数据后立刻发送。这点已在上传的工程中进行修改。工程文件已经上传,在《基于STM32CubeMX串口通信实验》

非常抱歉由于CSDN官网上传的资源必须要设定积分,否则几乎无法通过审核,这里就没有办法免费开放给大家,不过源码在教程里已经非常详细了。

在这里插入图片描述
在这里插入图片描述

结语

非常感谢大家的阅读,如有不当或者错误的地方,欢迎指正,谢谢支持。
一个字一个字敲出来不容易,如果觉得有帮助,点个赞再走呗~
祝大家事业蒸蒸日上!

奥里给~

  • 81
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值