【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA

 串口通信是单片机学习和应用中非常重要而且非常实用的一个外设。在项目开发中,常常需要使用到串口调试,来查看程序运行的状态。

一、简述:

 首先来看一看NUCLEO_F767开发板的串口原理图。

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第1张图片

以往开发板上串口常用USART1的PA9和PA10,这里却没有将这两个引脚继续作为串口,而是将他们作为USB的引脚来使用。

查看用户手册,对于USART的相关信息如图所示:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第2张图片

 Nucleo板没有使用USART1作为串口,而是使用了USART3,但也没有直接将这两个引脚引出来,而是默认连接到了ST-Link上面,和USB结合做出了一个虚拟串口。也可以根据自己的需要将接口外接,不过需要手动的改动锡桥。

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第3张图片

在这里可以注意到:USART3的串口引脚引到ST-Link的时候,在CN5这个端子上留了两个插针,可以利用这两个针脚来外接串口线和其他设备通信。但应当看清楚原理图里的引脚对应关系,CN5上面印的“TX和RX”其实指的是ST-Link上的,F767上的引脚和这个丝印标注恰好是相反的。

 

将开发板连接电脑,打开“设备管理器”可以看到“端口(COM和LPT)"一栏下有一个"STMicroelectronics STLink Virtual COM Port"

 

二、查询模式

  打开上一节CubeMX的配置,添加下列内容。

 USART3配置成异步模式,并且选择PD8和PD9的串口复用功能。

 

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第4张图片

 

配置 USART1 的初始化参数,包括波特率停止位等等。这里将 USART3 配置为:波特率 115200,8 位字长模式,无奇偶 校验位,1 个停止位,发送/接收均开启。生成代码

在"usart.c"文件下,有初始化的函数:

 

/* USART3 init function */

void MX_USART3_UART_Init(void)
{

  huart3.Instance = USART3;
  huart3.Init.BaudRate = 115200;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

 

为了使用printf函数,在"usart.c"文件的下面用户代码区添加如下重定向程序:

 

/* USER CODE BEGIN 1 */


#ifdef __GNUC__
  /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
 
  return ch;
}


/* USER CODE END 1 */

 

在主循环下添加如下代码:

 

 

  /* USER CODE BEGIN 2 */
	
	printf("Hello,Nucleo!\n");
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		
		//超时时间是以ms为单位计数的,此处时间太长,会让程序一直停在这里判断,
		//导致串口状态一直是忙,外部中断回调函数里的发送程序无法正常执行
		//缩短Timeout的时间。
		if(HAL_UART_Receive(&huart3, &data, 1, 1) == HAL_OK)
		{
			printf("USART Receive data: %c\n",data);
		}
		
		HAL_Delay(500);
		HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);

  /* USER CODE END WHILE */

 

 

将上一节的HAL_GPIO_EXTI_Callback函数修改为如下代码:

 

 

 

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	static uint8_t Led_Set=0;
	if( HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == GPIO_PIN_SET )
	{
		while(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == GPIO_PIN_SET){}
		Led_Set++;
		if(Led_Set>=255) Led_Set=1;
		if(Led_Set%2 == 1)
		{
			HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
			printf("LED2 Toggle\n");
		}
		else
		{
			HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin);
			printf("LED3 Toggle\n");
		}
	}
}

编译下载到开发板:打开串口调试助手:按下按键,可以收到F767发来的信息,发送一个字符'h',就可以收到“USART Receive data: h ”:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第5张图片

说明查询模式下实现了串口的发送和接收。

 

三、中断方式

在刚刚 CubeMX工程的基础上做下修改:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第6张图片

开启串口中断。

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第7张图片

配置USART3的中断分组,抢占优先级设为1,响应优先级设为2 。生成代码。

 

 在主函数里添加如下代码:

 

 定义存储 发送接收 数据的数组,大小为10 。

/* USER CODE BEGIN 0 */

	uint8_t data_TX[10]="abcdefghi\n";
	uint8_t data_RX[10]={0};


/* USER CODE END 0 */

主函数里在用户代码区使用下面的代码:

 

  /* USER CODE BEGIN 2 */
	
	printf("Hello,Nucleo!\n");
	HAL_UART_Receive_IT(&huart3, data_RX, 10);	//配置串口接收中断
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		
		HAL_Delay(500);
		HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);

  /* USER CODE END WHILE */

在主函数下面添加:

 

/* USER CODE BEGIN 4 */


//按键外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	static uint8_t Led_Set=0;
	if( HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == GPIO_PIN_SET )
	{
		while(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == GPIO_PIN_SET){}
		Led_Set++;
		if(Led_Set>=255) Led_Set=1;
		if(Led_Set%2 == 1)
		{
			HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
			
			HAL_UART_Transmit_IT(&huart3, data_TX, 10 );
			
		}
		else
		{
			HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin);
			printf("LED3 Toggle\n");
		}
	}
}

//串口发送完成回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	printf("Send OK!\n");
}


//串口接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	HAL_UART_Transmit_IT(&huart3, data_RX, 10 );
	HAL_UART_Receive_IT(&huart3, data_RX, 10);
}


/* USER CODE END 4 */


 程序复位之后,首先会发送一个“Hello,Nucleo!”来测试串口是否正常,接着开启串口接收中断,指定存储区,确定要接 收的数据的长度。当接受完这些长度的数据之后,就会进入中断,最终接收完成回调函数,来讲刚刚接收到的数据发送出 去。发送的函数同样也开启了中断,当发送完成后,同样进入回调函数,输出一个“Send OK!\n”。按下按键同样也会触 发发送。

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第8张图片

 

四、DMA方式。

 

在刚刚的CubeMX工程里,将串口DMA开启:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第9张图片

生成代码,在"usart.c"里就有了配置DMA的过程:

 

    /* USART3 DMA Init */
    /* USART3_RX Init */
    hdma_usart3_rx.Instance = DMA1_Stream1;
    hdma_usart3_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart3_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart3_rx.Init.Mode = DMA_NORMAL;
    hdma_usart3_rx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_usart3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_usart3_rx) != HAL_OK)
    {
      _Error_Handler(__FILE__, __LINE__);
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart3_rx);

主函数里添加如下代码:

 

  /* USER CODE BEGIN 2 */
	
	printf("Hello,Nucleo!\n");		//打印提示信息
	HAL_UART_Receive_DMA(&huart3,data_RX,10);		//开启接收DMA,有数据直接存入内存
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		 
		if(data_RX[0] != 0 )//判断存储接收数据的数组首位是否为空
		{
			HAL_Delay(10);		//保障数据完整,等待DMA接收完成
			printf("DMA receive: %s\n",data_RX);	//输出
			data_RX[0]=0;													//清楚数居首位
			while(  HAL_DMA_GetState(&hdma_usart3_rx) != HAL_DMA_STATE_READY  ){;}	//检测DMA状态
			HAL_UART_Receive_DMA( &huart3, data_RX, 10);	//开启接收DMA,有数据直接存入内存
		}

  /* USER CODE END WHILE */


另外删除中断接收方式中的发送完成、接收完成回调函数。编译下载:

 

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第10张图片


2019/4/14日补充:

DMA方式接收串口数据详细配置过程,此次配置不使用F767,而使用F103C8T6进行:

在CubeMX(此时使用的版本为最新版:5.1.0)里面配置好串口引脚,我这里配置了两个串口,分别是串口1和串口3

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第11张图片

两个串口的模式配置成这个样子:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第12张图片

配置两个串口的DMA通道、优先级、模式和字长。

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第13张图片

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第14张图片

开启两个DMA的中断,之后生成代码,打开工程。

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第15张图片

可以在usart.c文件的用户代码区加入串口重定向的代码段,这样就可以使用printf函数进行串口输出了

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第16张图片

为了确认程序正常执行,可以在主函数开始的地方加一句输出提示:

主函数中添加串口输出,检查串口工作是否正常:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第17张图片

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第18张图片

接着打开DMA接收,这里设置DMA接收定长数据包,为10个字节

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第19张图片

在stm32f1xx_hal_uart.h中有这么一个函数:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第20张图片

在main文件中定义,写如下代码,因为开启了两个DMA,所以在进入函数时需要先判断一下是哪个串口接收到了数据,这里判断串口1,

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第21张图片

下载到板子上,现象如下:

【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA_第22张图片

你可能感兴趣的:(【NUCLEO_F767ZI开发板系列】五、串口通信——查询+中断+DMA)