【蓝桥杯——物联网设计与开发】基础模块4 - 串口通信

目录

一、串口资源

(1)STM32L0 串口资源

(2)电路图

(3)串口收发单元功能框图

(4)实现方法

二、串口轮询方式

(1)接口函数

(2)STM32CubeMX 软件配置 

(3)代码编写

(4)实验现象

三、串口重定向

(1)设计思路

(2)代码编写

(3)实验现象

四、串口中断方式

(1)中断方式特点

(2)空闲中断

(3)接口函数

(4)STM32CubeMX 软件配置 

(5)代码编写

1. 普通串口中断方式

2. 定时器 + 串口中断方式

3. 串口空闲中断方式

(6)实验现象

五、串口 + DMA 方式

(1)DMA

(2)接口函数

(3)STM32CubeMX 软件配置 

(4)代码编写

(5)实验现象

六、踩坑日记

(1)代码不运作

(2)DMA 部分不能添加串口缓冲区清除函数


一、串口资源

(1)STM32L0 串口资源

        STM32L0 系列提供四个 USART 接口(USART1USART2USART4 和 USART5)能够以高达 4 Mit/s 的速度进行通信。
        USARTUniversa Synchronous/Asynchronous Receiver/Transmitter,通用同步/异步串行接收/发送器。它可以灵活的使用3根信号线来进行高速率同步通信,也可以使用2根信号线进行异步通信。 USART 可以使用相互独立的接收数据和发送数据方式的全双工操作。当处于同步操作时,可与主机时钟同步,也可与从机时钟同步,此时可以使用独立的高精度波特率发生器而不占用定时/计数器。USART 支持同步模式,因此 USART 需要同步时钟信号 USART CK。通常情况同步信号很少使用,因此一般的单片机 UART 和 USART 使用方式是一样的,都使用异步模式。USART 使用 TTL 电平,因此在与PC通信的时候需要进行电平转换。

(2)电路图

        🔵蓝桥杯物联网竞赛实训平台串口通信部分如下图1所示:

        实训平台左侧的芯片 GD32F350C8T6 是作为节点 A 和节点 B 的程序下载器和串口通信中继器。即 PC 机与芯片 GD32F350C8T6 相互连接,芯片 GD32F350C8T6 引出串口引脚 PA9/TXPA10/RX 通过芯片 CH443K (开关选择芯片)与节点的串口二引脚 PA2_TX PA3_RX 相互连接。

        故要实现节点与 PC 机串口通信,需要配置 USART2;

图1        CH443K 电路图
图2        USART2​​​

        USART1 根据电路图可知,与 Lora 芯片相连接,故本文不做探讨,会在 Lora 通信部分讲解;

图3       USART1 

(3)串口收发单元功能框图

        串口收发单元主要利用数据寄存器 DR,发送引脚 TX,接收引脚 RX,以及三个通信状态位 TXETC RXNE 来完成数据的接收和发送。

图4        串口收发单元
  • 数据寄存器 DR 在硬件上分为 TDR RDR 两个寄存器,通过数据的流向进行区分,在结构设计上采用了双缓冲结构;
  • 发送,数据通过数据总线送入 TDR 寄存器,然后传送到发送移位寄存器完成数据转换,从并行数据转为串行数据,最后通过 TX 引脚发送;
  • 接收,数据通过 RX 引脚逐位送入接收移位寄存器,8位数据接收完成后,送入 RDR 寄存器,供用户读取。

数据收发过程中,可同时写入新的数据或读取已接收的数据,提高数据的传输效率

⭐⭐⭐通信状态标志位

表1 通信状态标志位

标志位

名称

含义
TXE

发送数据寄存器空标志。TDR 寄存器的内容已经传送到发送移位寄存器时,该位由硬件置1。如果串口控制寄存器 CR1 中的 TXEIE 位为1,将会触发发送数据寄存器空中断。

注意:TXE 置1时,数据有可能还在发送。

TC发送完成标志。当发送移位寄存器的内容发送完成,同时 TDR 寄存器也为空时,该位由硬件置1,表示本次数据传输已经完成。如果串口控制寄存器 CR1 中的 TCIE 位为1,将会触发发送完成中断。
注意:TC 置1时,数据才是真正地发送完成。
RXNE接收数据寄存器不为空标志。当移位寄存器的内容已经传送到接收数据寄存器 RDR 时,该位由硬件置1。如果串口控制寄存器 CR1 中的 RXNEIE 位为1,将会触发接收数据寄存器不为空中断。
  • 轮询方式下,可以直接检测标志位;
  • 中断方式下,需要在中断服务程序中通过检测不同的中断标志位,来判断出中断类型,然后执行后续的任务处理;

(4)实现方法

        由于串口部分实现方法较多,后文将从以下几个部分展开讲解:

  • 串口轮询方式
  • 串口重定向
  • 串口中断方式(三种方式)
  • 串口 + DMA方式


二、串口轮询方式

(1)接口函数

        🔅发送接口函数

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)

  • 该函数连续发送数据,发送过程中通过判断 TXE 标志来发送下一个数据,通过判断 TC 标志来结束数据的发送;
  • 如果在等待时间内没有完成发送,则不再发送,返回超时标志;
  • 该函数由用户调用;

        🔅接收接口函数

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

  • 该函数连续接收数据,在接收过程中通过判断 RXNE 标志来接收新的数据;
  • 如果在超时时间内没有完成接收,则不再接收数据,返回超时标志;
  • 该函数由用户调用;

(2)STM32CubeMX 软件配置 

🔅“工程建立、时钟树配置、Debug 串行线配置、代码生成配置”【蓝桥杯——物联网设计与开发】基础模块1- GPIO输出  一文中有讲解,这里不再赘述❗️

1️⃣点击左侧 "Connectivity" → 选择 "USART2"  → 模式选择 "Asynchronous" (异步通信模式),不使能硬件流控制;

图5        USART2 模式配置

2️⃣在 "Parameter Settings" 中对串口参数进行修改(具体请根据题目要求修改);

  • 波特率9600Bit/s
  • 8位数据位
  • 无奇偶校验
  • 1位停止位
  • 使能接收和发送
  • 16倍过采样
图6        设置通信参数

3️⃣生成代码即可;

(3)代码编写

🟢️main 函数

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t puc_uart[10];		// 串口接收缓冲区定义
/* USER CODE END PV */
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* 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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		/* 接收 5 个字符完成 */
		if(HAL_UART_Receive(&huart2, puc_uart, 5, 10) == HAL_OK)
		{
			/* 把接收的字符原样发送回去 */
			HAL_UART_Transmit(&huart2, puc_uart, 5, 10);
			/* 清空串口接收缓冲区 */
			memset(puc_uart, 0, sizeof(puc_uart));
		}
		HAL_Delay(1);	// 1ms延时,防止芯片进入低功耗模式
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        在 while(1) 死循环中调用接收接口函数,固定判断5个字符是否接收完成,若接收完成,则将接收的字符原样传输回 PC 端,现象如下图所示:

图7        实验现象

(4)实验现象

  • PC 端发送5个字符后,节点将字符原样回传;

三、串口重定向

(1)设计思路

⭐在C语言中,printf 函数是将数据格式化输出到屏幕,scanf 函数是从键盘格式化输入数据;在嵌入式系统中,一般采用串口进行数据的输入和输出;
重定向是指用户改写C语言的库函数,当链接器检查到用户编写了与C库函数同名的函数时,将优先使用用户编写的函数,从而实现对库函数的修改;
⭐printf 函数内部通过调用 fputc 函数来实现数据输出,scanf 函数内部通过调用 fgetc 函数来实现数据输入,因此用户需要改写这两个函数实现串门重定向。

(2)代码编写

⚠️注意:实现重定向时,需要添加头文件 <stdio.h>

🟡️fputc 函数

#include <stdio.h>
int fputc(int ch, FILE *f)
{
	/* 采用轮询方式发送1字节数据,超时时间设置为无限等待 */
	HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
	return ch;
}

🟡️fgetc 函数

#include <stdio.h>
int fgetc(FILE *f)
{
	uint8_t ch;
	/* 采用轮询方式接收1字节数据,超时时间设置为无限等待 */
	HAL_UART_Receive(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
	return ch;
}

🟢️main 函数

uint8_t dat_rec;
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* 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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  /* 测试字符串 */
  printf("Test uart\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		/* 判断是否接收1个字符的字符串 */
		if(scanf("%c", &dat_rec) == 1)
		{
			/* 返回接收的字符串 */
			printf("Received: %c", dat_rec);
		}
		else
		{
			/* 如果字符串不等于1个字符,则返回错误 */
			printf("Error!");
		}
		HAL_Delay(1);  // 1ms延时,防止芯片进入低功耗模式
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        在 while(1) 死循环中调用重定向后的 scanf() 函数,固定判断1个字符是否接收完成,若接收完成,则将接收的字符传输回 PC 端,现象如下图所示:

图8        实验现象

(3)实验现象

  • PC 端发送1个字符后,节点将字符回传;

四、串口中断方式

(1)中断方式特点

  1. 发送数据时,将一字节数据放入数据寄存器 DR;接收数据时,将 DR 的内容存放到用户存储区;
  2. 中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字节数据或者读取接收到的一字节数据;
  3. 在传输数据量较大,且通信波特率较高 (大于38400) 时,如果采用中断方式,每收发一个字节的数据,CPU 都会被打断,造成 CPU 无法处理其他事务。因此在批量数据传输,通信波特率较高时,建议采用 DMA 方式。

(2)空闲中断

  1. 在一帧数据传输结束后,通信线路将会维持高电平,这个状态称为空闲状态;
  2. CPU 检测到通信线路处于空闲状态时,且空闲状态持续时间大于一个字节传输时间时,空闲状态标志 IDLE 将由硬件置1。如果串口控制寄存器 CR1 中的 IDLEIE 位为1,将会触发空闲中断(IDLE 中断);
  3. 由于空闲标志是在一帧数据传输完成后才置位,在有效数据传输过程中不会置位,因此借助空闲中断,可以实现不定长数据的收发

(3)接口函数

        🔅中断方式发送接口函数

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)

  • 函数将使能串口发送中断;
  • 函数将置位 TXEIE TCIE,使能发送数据寄存器空中断和发送完成中断。完成指定数量的数据发送后,将会关闭发送中断,即清零 TXEIE TCIE因此用户采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启发送中断;
  • 当指定数量的数据发送完成后,将调用发送中断回调函数 HAL_UART_TxCpltCallback 进行后续处理;
  • 该函数由用户调用;

发送过程:每发送一个数据进入一次中断,在中断中根据发送数据的个数来判断数据是否发送完成

        🔅中断方式接收接口函数

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

  • 函数将使能串口接收中断;
  • 函数将置位 RXNEIE,使能接收数据寄存器非空中断 RXNE。完成指定数量的数据接收后,将会关闭接收中断,即清零 RXNEIE因此用户采用中断方式连续接收数据时,要重复调用该函数,以重新开启接收中断;
  • 当指定数量的数据接收完成后,将调用接收中断回调函数 HAL_UART_RxCpltCallback 进行后续处理;
  • 该函数由用户调用;

接收过程:每接收一个数据进入一次中断,在中断中根据接收数据的个数来判断数据是否接收完成

        🔅发送中断回调函数

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);

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

        🔅接收中断回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

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

(4)STM32CubeMX 软件配置 

🔅“工程建立、时钟树配置、Debug 串行线配置、代码生成配置”【蓝桥杯——物联网设计与开发】基础模块1- GPIO输出  一文中有讲解,这里不再赘述❗️

1️⃣点击左侧 "Connectivity" → 选择 "USART2"  → 模式选择 "Asynchronous" (异步通信模式),不使能硬件流控制;

图9        USART2 模式配置

2️⃣在 "Parameter Settings" 中对串口参数进行修改(具体请根据题目要求修改);

  • 波特率9600Bit/s
  • 8位数据位
  • 无奇偶校验
  • 1位停止位
  • 使能接收和发送
  • 16倍过采样
图10        设置通信参数

3️⃣在 "NVIC Settings" 中,勾选使能 USART2 中断;

图11        USART2 中断使能

4️⃣生成代码即可;

(5)代码编写

1. 普通串口中断方式

🟢️main 函数

/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t puc_uart[10];		// 串口接收缓冲区定义
uint8_t	flag_uart;			// 串口接收完成标志
/* USER CODE END PV */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* 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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  /* 使能接收中断 */
  HAL_UART_Receive_IT(&huart2, (uint8_t *)puc_uart, 1);
  /* 测试字符串 */
  printf("Test uart_it\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		/* 判断数据是否接收完成 */
		if(flag_uart)
		{
			/* 清除标志位 */
			flag_uart = 0;
			/* 将接收的字符发回 */
			HAL_UART_Transmit_IT(&huart2, (uint8_t *)puc_uart, 1);
			/* 清空接收缓冲区 */
			memset(puc_uart, 0, sizeof(puc_uart));
		}
		HAL_Delay(1);		// 1ms延时,防止芯片进入低功耗模式
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        在 while(1) 中循环判断接收完成标志位,当接收完成时,清除标志位,并且将接收的字符通过中断方式发回 PC 端,然后清空接收缓冲区;

🟠️串口接收中断回调函数

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* 串口接收回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	/* 判断发生接收中断的串口 */
	if(huart->Instance == USART2)
	{
		/* 置位接收完成标志位 */
		flag_uart = 1;
		/* 使能接收中断 */
		HAL_UART_Receive_IT(&huart2, (uint8_t *)puc_uart, 1);
	}
}
/* USER CODE END 0 */

        ⚠️注意:在中断回调函数中一定要再次使能接收中断,否则无法再次触发中断;

2. 定时器 + 串口中断方式

🟢️main 函数

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* 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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  /* 使能串口接收中断 */
  HAL_UART_Receive_IT(&huart2, puc_uart, 1);
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        ⚠️注意:此处使能串口接收中断为接收1个字符触发一次中断,用于不定长字符串接收;

🔴串口任务处理函数

void Task_Uart(void)
{
    /* 判断接收标志位 */
	if(flag_uart == 0)	return;
    /* 判断是否接收完毕 */
	if(cnt_uart > 0)	return;
    /* 清除接收标志位 */
	flag_uart = 0;
    /* 此处判断接收字符是否超过1位 */
	if(index_uart > 1)
		printf("error\r\n");
	else
	{
        /* 将接收的字符发回 */
		printf("%c\r\n", puc_uart[1]);
	}
    /* 清空接收缓冲区索引 */
	index_uart = 0;
    /* 清空接收缓冲区 */
	memset(puc_uart, 0, 10);
}
  1. 首先判断接收标志位是否置位;
  2. 然后判断1帧是否接收完成;
  3. 对接收标志位清除;
  4. 处理串口逻辑;
  5. 清空接收缓冲区索引;
  6. 清空接收缓冲区;

🟠️串口接收中断回调函数

/* USER CODE BEGIN 0 */
/* 串口接收回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    /* 判断是否是串口2 */
	if(huart->Instance == USART2)
	{
        /* 将接收的字符放在数组中 */
		puc_uart[++index_uart] = puc_uart[0];
        /* 接收标志位置1 */
		flag_uart = 1;
        /* 串口10ms计时赋值 */
		cnt_uart = 10;
        /* 使能串口接收中断 */
		HAL_UART_Receive_IT(&huart2, puc_uart, 1);
	}
}
/* USER CODE END 0 */
  1. 由于每接收一个字符进入一次中断,每次接收的字符存放的位置在 puc_uart[0] 处,故将接收的字符存放到数组后面以防被接收覆盖;
  2. 随后将接收标志位置位1,表示串口接收到字符;
  3. 将串口 10ms 计时标志位赋值,如若 10ms 内未进入中断,则 cnt_uart 值变为0,表示 10ms 内没有新的字符接收,即一帧接收完毕;
  4. 使能串口接收中断;

🟠️Systick 中断函数

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  /* 如果cnt_uart 值>0,则每1ms减少1 */
  if(cnt_uart > 0) --cnt_uart;
  /* USER CODE END SysTick_IRQn 1 */
}
  • 在 Systick 中断函数中对 cnt_uart 值进行判断:若大于0,则每 1ms 较少1,从而进行 10ms 计时;

3. 串口空闲中断方式

🟢️main 函数

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* 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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
	/* 清除空闲标志位 */
	__HAL_UART_CLEAR_IDLEFLAG(&huart2);
	/* 使能IDLE中断 */
	__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
	/* 使能串口接收中断 */
	__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
	/* 测试字符串 */
	printf("Test uart_idle\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		/* 判断数据是否接收完成 */
		if(flag_uart)
		{
			/* 清除标志位 */
			flag_uart = 0;
			/* 将接收的字符串发回 */
			printf("%s\r\n", puc_uart);
			/* 清除缓冲区索引 */
			index_uart = 0;
			/* 清空接收缓冲区 */
			memset(puc_uart, 0, sizeof(puc_uart));
		}
		HAL_Delay(1);		// 1ms延时,防止芯片进入低功耗模式
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

while(1) 中检测接收完成标志位:

  1. 清除标志位;
  2. 将接收的字符串发回;
  3. 清除缓冲区索引;
  4. 清空接收缓冲区;

🟠️串口中断函数

/**
  * @brief This function handles USART2 global interrupt / USART2 wake-up interrupt through EXTI line 26.
  */
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
	/* 检查接收标志位 */
	if(__HAL_UART_GET_IT(&huart2,UART_IT_RXNE) !=RESET)
	{
		/* 将串口寄存器的值放入接收缓冲区 */
		puc_uart[index_uart++] = USART2->RDR;
	}
	/* 添加IDLE中断处理 */
	if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)	//判断是否发送IDLE中断
	{
		/* 清除IDLE中断标志 */
		__HAL_UART_CLEAR_IDLEFLAG(&huart2);
		/* 调用用户编写的IDLE中断回调函数 */
		HAL_UART_IdleCpltCallback(&huart2);
	}
	/* USER CODE END USART2_IRQn 1 */
}

在串口中断函数中分别检查两个标志位:接收标志位 RXNE 和空闲标志位 IDLE

  • 如果接收到字符,则从串口寄存器中取出字符放入接收缓冲区;
  • 如果检测到线路空闲,则调用用户编写的 IDLE 中断回调函数;

 🟠️IDLE 中断回调函数

/* 串口空闲中断回调函数 */
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
	/* 置位接收完成标志位 */
	flag_uart = 1;
}

        在 IDLE 中断回调函数中置位接收标志位,表示一帧数据接收完成;

  • 此处不做逻辑是为了简洁中断,防止中断时间过长!

(6)实验现象

  • PC 端发送1个字符后,节点将字符回传;
图12        实验现象

五、串口 + DMA 方式

(1)DMA

        DMA(直接存储器访问): 用于在外设与存储器之间以及存储器与存储器之间进行高速数据传输。DMA 传输过程的初始化和启动由 CPU 完成,传输过程由 DMA 控制器来执行,无需 CPU 参与,从而节省 CPU 资源,提高利用率。

(2)接口函数

        🔅DMA 方式发送接口函数

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)

  • 该函数将启动 DMA 方式的串口数据发送;
  • 完成指定数量的数据发送后,可以触发 DMA 中断,在中断中将调用发送中断回调函数 HAL_ UART_TxCpltCallback 进行后续处理;
  • 该函数由用户调用;

        🔅DMA 方式接收接口函数

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

  • 该函数将启动DMA方式的串口数据接收;
  • 完成指定数量的数据接收后,可以触发 DMA 中断,在中断中将调用接收中断回调函数 HAL_ UART_RxCpltCallback 进行后续处理;
  • 该函数由用户调用;

(3)STM32CubeMX 软件配置 

🔅“工程建立、时钟树配置、Debug 串行线配置、代码生成配置”【蓝桥杯——物联网设计与开发】基础模块1- GPIO输出  一文中有讲解,这里不再赘述❗️

1️⃣点击左侧 "Connectivity" → 选择 "USART2"  → 模式选择 "Asynchronous" (异步通信模式),不使能硬件流控制;

图13       USART2 模式配置

2️⃣在 "Parameter Settings" 中对串口参数进行修改(具体请根据题目要求修改);

  • 波特率9600Bit/s
  • 8位数据位
  • 无奇偶校验
  • 1位停止位
  • 使能接收和发送
  • 16倍过采样
图14        设置通信参数

 3️⃣在 "NVIC Settings" 中,勾选使能 USART2 中断;

图15        USART2 中断使能

4️⃣在 "DMA Settings" 中 → 点击 "Add",分别选择 USART2_TX 和 USART2_RX

图16        DMA 设置

5️⃣配置 "USART2_TX "参数;

  • 传输模式:普通模式
  • 地址递增:外设不递增,存储器递增
  • 数据宽度:字节
图17        USART2_TX 参数配置

6️⃣配置 "USART2_RX "参数;

  • 传输模式:普通模式
  • 地址递增:外设不递增,存储器递增
  • 数据宽度:字节
图18        USART2_RX 参数配置

7️⃣生成代码即可;

(4)代码编写

🟢️main 函数

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
extern DMA_HandleTypeDef hdma_usart2_tx;
extern DMA_HandleTypeDef hdma_usart2_rx;
uint8_t puc_uart[LENGTH];		// 串口接收缓冲区定义
uint8_t	flag_uart;			// 串口接收完成标志
uint8_t index_uart;			// 串口接收缓冲区索引
/* USER CODE END PV */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* 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_DMA_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
	/* 清除空闲标志位 */
	__HAL_UART_CLEAR_IDLEFLAG(&huart2);
	/* 使能IDLE中断 */
	__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
	/* 启动DMA接收 */
	HAL_UART_Receive_DMA(&huart2, (uint8_t *)puc_uart, LENGTH);
	/* 测试字符串 */
	printf("Test uart_DMA\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		/* 判断数据是否接收完成 */
		if(flag_uart)
		{
			/* 清除标志位 */
			flag_uart = 0;
			/* 关闭串口DMA */
			HAL_UART_DMAStop(&huart2);
			/* 计算接收字符串长度,实现不定长字符接收 */
			index_uart = LENGTH - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
			/* 将接收的字符发回 */
			HAL_UART_Transmit_DMA(&huart2, (uint8_t *)puc_uart, index_uart);
			/* 清空缓冲区索引 */
			index_uart = 0;
			/* 重新使能DMA接收 */
			HAL_UART_Receive_DMA(&huart2, (uint8_t *)puc_uart, LENGTH);
			/* 清空接收缓冲区 */
//			memset(puc_uart, 0, sizeof(puc_uart));
		}
		HAL_Delay(1);		// 1ms延时,防止芯片进入低功耗模式
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

while(1) 中对接收标志位进行判断,判断成功后进行如下操作:

  1. 清除接收完成标志位;
  2. 关闭串口DMA;
  3. 计算接收字符串长度,实现不定长字符接收;
  4. 将接收的字符发回;
  5. 清除缓冲区索引;
  6. 重新使能DMA接收;

⚠️注意:此处不能清空接收缓冲区(会在踩坑日记里具体说明)❗️

🟠️串口中断函数

/**
  * @brief This function handles USART2 global interrupt / USART2 wake-up interrupt through EXTI line 26.
  */
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
	/* 添加IDLE中断处理 */
	if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)	//判断是否发送IDLE中断
	{
		/* 清除IDLE中断标志 */
		__HAL_UART_CLEAR_IDLEFLAG(&huart2);
		/* 调用用户编写的IDLE中断回调函数 */
		HAL_UART_IdleCpltCallback(&huart2);
	}
  /* USER CODE END USART2_IRQn 1 */
}

        在串口中断函数中判断空闲标志位,如果空闲则清除空闲中断标志位,并且调用用户编写的 IDLE 中断回调函数

🟠️IDLE 中断回调函数

/* 串口空闲中断回调函数 */
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
	/* 置位接收完成标志位 */
	flag_uart = 1;
}

        在 IDLE 中断回调函数中置位接收标志位,表示一帧数据接收完成;

  • 此处不做逻辑是为了简洁中断,防止中断时间过长!

(5)实验现象

图19        实验现象
  • PC 端发送不定长字符后,节点将字符回传;

六、踩坑日记

(1)代码不运作

        🔅这款芯片属于低功耗类芯片,当 while(1) 中判断条件没有及时发生时,会进入低功耗状态,无法处理逻辑。通过 Debug 查找汇编代码,发现程序会卡在这一行汇编代码中:

图20        汇编查询

        B 是汇编中的跳转指令,而后面是跳转的指令地址,仔细观察可以发现,该指令地址和需要跳转的指令地址是一样的,即代码死循环卡在这条指令

⭐当前解决方案:

        当需要处理的逻辑较少时,在 while(1) 循环中添加代码 HAL_Delay(1); 让 CPU “忙起来”,即可解决这个 bug

(2)DMA 部分不能添加串口缓冲区清除函数

        CPU 执行代码到139行时,CPU 将发送任务转交给 DMA 进行,而 CPU 继续执行代码;如果有第145行清空接收缓冲区的代码,则 DMA 还未发送完成,而 CPU 将缓冲区清除,那么 DMA 后续发的字符都为空,此时串口助手接收字符数没有问题,但是显示为空!

⚠️注意:CPU DMA 是两个硬件单元,可以同时展开工作,所以需要考虑临界资源运用的情况!!!

图21        代码

⭐当前解决方案:

        注释掉清空接收缓冲区的代码;

  • 60
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 蓝桥杯物联网设计开发考试不会要求考生编写 OLED 和 LoRa 的驱动程序。而是更注重考察考生在物联网设计开发方面的综合能力和技术应用能力。 物联网设计开发是指将各种物理设备和传感器通过互联网连接起来,实现数据的采集、传输、处理和应用。在这个过程中,OLED 和 LoRa 是常见的硬件设备和无线通信技术。 OLED(Organic Light-Emitting Diode)属于一种发光二极管显示器。它具有自发光、超薄、高对比度和低功耗等优点。在物联网应用中,OLED 可以用于显示各种传感器的数据或者设备的工作状态。 LoRa(Long Range)是一种低功耗、远距离的无线通信技术。它适用于物联网场景下的远距离传输和低功耗要求。LoRa 可以实现传感器数据的远程传输和远程控制等功能。 在蓝桥杯物联网设计开发考试中,考生可能会遇到使用 OLED 和 LoRa 的实际应用案例。但不要求考生编写硬件驱动程序。考察的重点更多在于考察考生对物联网的整体设计能力、对数据传输和处理的技术理解和实践经验。 因此,考生需要掌握物联网系统的搭建和调试技巧、熟悉各种通信协议和传输技术、了解各种传感器的工作原理和数据处理方法。掌握这些基础知识和技能,能够实际应用到物联网实际项目中,才能在蓝桥杯物联网设计开发考试中取得好的成绩。 ### 回答2: 蓝桥杯物联网设计开发考试会给oled和lora驱动。 蓝桥杯物联网设计开发考试是一个对物联网相关知识的综合考核,其中会考察对各种传感器、驱动设备的使用能力。而oled和lora作为物联网应用中常用的两种驱动设备,理应在考试中被考察。 OLED是一种有机发光二极管显示技术,可以实现高对比度、高亮度、宽视角的显示效果。在物联网设计中,使用OLED可以将各种数据或者信息以直观的方式呈现出来,提高用户体验。因此,考试中会对OLED的使用方法进行考察,包括初始化、显示文本或图片等相关操作。 Lora是一种低功耗远距离无线通信技术,适用于物联网中传感器节点之间的数据传输。它具有长距离传输能力和低功耗的特点,可以实现在数百米至几公里范围内的无线通信。在物联网设计中,使用Lora可以实现传感器的数据采集和上传,从而构建起一个完整的无线传感器网络。因此,考试中也会对Lora的使用方法进行考察,包括Lora模块的初始化、数据发送和接收等相关操作。 综上所述,蓝桥杯物联网设计开发考试会考察对于oled和lora的驱动。通过对这些驱动设备的了解和掌握,可以更好地实现物联网应用的开发设计。 ### 回答3: 蓝桥杯物联网设计开发考试不会直接给出 OLED 和 Lora 驱动程序。然而,物联网设计开发考试通常会涉及对各种传感器、通信模块以及外设的使用和集成,包括 OLED 和 Lora。对于 OLED 和 Lora 驱动程序的使用,考试题目可能要求考生自行编写或集成相应的驱动程序。 对于 OLED,考生可能会被要求了解 OLED 的工作原理和驱动方式,并实现相关功能,如显示文本、图像等。考试中可能会要求考生使用相应的开发平台和库来编写 OLED 驱动代码,并结合实际应用场景进行相应的功能实现和调试。 对于 Lora,考生可能会要求了解 Lora 的工作原理和通信协议,并实现与其他设备之间的远程通信。考试中可能会要求考生使用开发板和相应的 Lora 模块来实现通信功能,并结合具体场景对其进行应用和调试。 总之,蓝桥杯物联网设计开发考试鼓励考生在实际应用中掌握和使用各种硬件驱动和通信模块。虽然不会直接给出 OLED 和 Lora 驱动程序,但通过考试的实践和学习,考生可以提升自己的硬件驱动和通信模块的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逝灮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值