一、前言

        串口作为最常用的调试工具之一,普及度应该是很高的,H743系列串口使用方法与中低端芯片一样,如果以前学习过标准库的串口,这篇文章理解起来应该没有多大难度。

        串口通信协议的介绍网上一搜一大堆,这里就不再赘述了,主要写一下应用过程,标准库和HAL库都有专门的串口收发函数,常见的发送函数就是改写fputc()函数,将printf()函数重映射到串口上,至于接收,我了解到的就有四种,第一种使用定时器超时接收,原理呢就是设置一个定时器,每当串口收到数据就刷新定时,当串口没有新数据到达后,定时器超时接收结束;第二种是状态机,江协科技在讲串口通信的时候用过这种方式,我个人感觉不太好用;第三种跟第四种分别是使用中断和DMA。本次记录一下在cubeMX生成的工程下移植正点原子中断串口的过程。

二、cubeMX工程配置

       按照惯例一般都是使用串口1的PA9、PA10引脚作为调试,但也有例外,所以使用之前我们需要打开原理图确认一下,如图,CH340接到了串口1,惯例设置。

接下来我们转到cube,配置一下所用串口,工程的基础设置不再赘述,都是通用流程。 串口参数使用的是默认配置。

然后按流程打开生成的代码即可。

三、keil代码编写

        打开工程,并将上次点灯新建的hardware文件夹移植进来,流程不再赘述。

 这里必须声明一下,由于cube生成的工程中已经有usart.c/h文件,所以我们新写的这俩文件不能叫这个名字,要不然编译时找不到我们写的文件。

        我先把代码贴在这,然后再来分析过程。

        头文件:

#ifndef __MYUSART_H__
#define __MYUSART_H__

#include "main.h"
#include "usart.h"

#define USART_REC_LEN   200                     /* 定义最大接收字节数 200 */
#define RXBUFFERSIZE    1                       /* 缓存大小 */


extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t g_usart_rx_sta;                 /* 接收状态标记 */
extern uint8_t g_rx_buffer[RXBUFFERSIZE];       /* HAL库USART接收Buffer */

void USART1_RX_Enble(void);
int USART1_GET_State(void);
int USART1_DATA_Len(void);
void USART1_GET_Data(uint8_t *buffer, uint16_t size);

#endif

源文件:

#include "MyUSART.h"
#include "stdio.h"
#include "string.h"

/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t g_usart_rx_buf[USART_REC_LEN];

/*  接收状态
 *  bit15,      接收完成标志
 *  bit14,      接收到0x0d
 *  bit13~0,    接收到的有效字节数目
*/
uint16_t g_usart_rx_sta = 0;

uint8_t g_rx_buffer[RXBUFFERSIZE];          /* HAL库使用的串口接收缓冲 */

//UART_HandleTypeDef g_uart1_handle;          /* UART句柄 */

/*重定向printf输出到串口1*/
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
    return ch;
}


/**
 * @brief       Rx传输回调函数
 * @param       huart: UART句柄类型指针
 * @retval      无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)                             /* 如果是串口1 */
    {
        if ((g_usart_rx_sta & 0x8000) == 0)                    /* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)                       /* 接收到了0x0d */
            {
                if (g_rx_buffer[0] != 0x0a) 
                {
                    g_usart_rx_sta = 0;                       /* 接收错误,重新开始 */
                }
                else 
                {
                    g_usart_rx_sta |= 0x8000;                 /* 接收完成了 */
                }
            }
            else                                              /* 还没收到0X0D */
            {
                if(g_rx_buffer[0] == 0x0d)
                {
                    g_usart_rx_sta |= 0x4000;
                }
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0] ;
                    g_usart_rx_sta++;
                    if(g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;                   /* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }
        HAL_UART_Receive_IT(&huart1, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
    }
}


/**
 * @brief       串口1中断服务函数
 * @param       无
 * @retval      无
 */
void USART_UX_IRQHandler(void)
{ 
    HAL_UART_IRQHandler(&huart1); /* 调用HAL库中断处理公用函数 */
}

/*开启串口1接收功能*/
void USART1_RX_Enble(void)
{
    HAL_UART_Receive_IT(&huart1,(uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}

/*串口1收到数据?0否1是*/
int USART1_GET_State(void)
{
    int state = 0;
    if (g_usart_rx_sta & 0x8000)
        state = 1;
    return state;
}

/*串口1收到数据的长度*/
int USART1_DATA_Len(void)
{
    int len = 0;
    len = g_usart_rx_sta & 0x3fff;
    return len;
}

/*转存串口1的数据,并清空串口1的接收buffer*/
void USART1_GET_Data(uint8_t *buffer, uint16_t size)
{
    memset(buffer, '\0',size);
    strcpy((char *)buffer, (char *)g_usart_rx_buf);    //转存数据
    memset(g_usart_rx_buf,'\0',USART_REC_LEN);          //清空接收缓存
    g_usart_rx_sta = 0;                             //清空接收标志位
}


这段代码接收部分移植自正点原子的串口实验,由于我使用的是cube生成的工程,引脚初始化这部分cube已经帮我们做了,所以不需要关心。前面说过HAL有专门的串口函数,所以无论怎么改,都还是基于HAL库的函数,顶多就是二次甚至是三次封装罢了。

(1)重映射printf()函数

        这部分的原理已经有很多人讲过,就是重写fputc()函数,将其映射到串口1上,上面的代码里有,我就不啰嗦了,底层还是用的HAL库提供的串口函数。

(2)中断接收

        我们在cube中使能了串口1的全局中断,但在工程当中改中断默认是关闭状态(具体什么原因我不太清楚,知道到小伙伴可以私信我一下让我长长见识),所以使用之前需要手动开启一下,要不然接受函数没反应。

        正点原子在串口的头文件中定义了几个变量,分别是接收数组及最大长度、接收状态以及缓存大小等,我们直接拿过来用一下,在源文件中主要就是两个函数,一个是串口中断服务函数,另一个是串口接收回调函数,上面的源文件中有完整贴出,有兴趣的小伙伴可以自行研究。

        剩下的就是我二次封装的一些函数了,主要是为了方便使用和移植,主要功能有使能串口1接收、获取接收状态、获取接收长度和数据转存,在正点原子的实验中,接受数组其实是可以外部调用的,但是需要手动清楚标志位,我嫌麻烦,于是我重新封装了一下。

函数不难理解,我在这简单说一下(我水平有限可能会有错误,欢迎批评指正),这里传进来一个数组和大小,首先将要用的空间清空,然后将接受缓冲中的数据复制到这块空间中,再清空缓冲区和接受标志位,这样我们调用的时候就不用管这些乱七八糟的东西了,直接定义一个数组,调用函数拿到我们所需的数据即可。

(3)主函数调用

        这里根据正点原子实验改的,直接把主函数贴到这,有兴趣的小伙伴自己看吧。下篇学习看门狗。

int main(void)
{

  /* USER CODE BEGIN 1 */
    int Rx_MaxSize = 256;
    uint8_t len;
    uint16_t times = 0;
    uint8_t rx_buff[Rx_MaxSize];
  /* USER CODE END 1 */

  /* MPU Configuration--------------------------------------------------------*/
  MPU_Config();

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    USART1_RX_Enble();
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
      if (USART1_GET_State())                                                    /* 接收到了数据? */
        {
            len = USART1_DATA_Len();                                              /* 得到此次接收到的数据长度 */
            printf("\r\n您发送的消息为:\r\n");
            /*117-120三行代码效果等同于123-124*两行*/
            USART1_GET_Data(rx_buff, Rx_MaxSize);
            HAL_UART_Transmit(&huart1, (uint8_t *)rx_buff, len, 1000);
            printf("\r\n");
            /*转存之前的数据在g_usart_rx_buf数组内,该数组可以被外部调用,但需要手动清空标志位*/
//            printf("\r\ng_usart_rx_buf:%s\r\n",g_usart_rx_buf);
//            USART1_GET_Data(rx_buff);
//            printf("\r\n%s\r\n",rx_buff);

        }
        else
        {
            times++;

            if (times % 5000 == 0)
            {
                printf("\r\nSTM32H743开发板 串口实验\r\n");
            }

            if (times % 200 == 0)
            {
                printf("请输入数据,以回车键结束\r\n");
            }

            if (times % 30  == 0) 
            {
                LED_TURN(100);                   /* 闪烁LED,提示系统正在运行 */
            }

            HAL_Delay(10);
        }
  }
  /* USER CODE END 3 */
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐