STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)

STM32CubeIDE开发笔记 MK.III - UART串口通信(查询/中断/DMA)

  • 前言
    • 更新日志
    • 简介
  • 查看原理图
  • CubeMX的配置
  • UART库与代码
    • 方案A printf 重定向
    • 方案B 函数
      • 查询模式
        • 串口错误置位 标志位清除函数
        • WriteData部分代码
        • ReadData部分代码
        • main部分
      • 中断模式
        • 使能中断
        • 调用一次receive使能中断
        • Rx接收回调函数
        • main测试部分
      • DMA模式
        • 添加DMA配置
        • USART.C部分
        • 回调函数部分
    • 全方案完整参考代码
      • USART.h
      • USART.c
      • MAIN.H
      • MAIN.C(测试部分)
  • 构建Debug与调试
  • 后记

前言

更新日志

版本:2022.03.08 Ver1.0.0
版本:2022.03.16 Ver1.1.0 完成查询模式函数部分
版本:2022.03.17 Ver1.2.0 完成中断模式函数部分
版本:2022.03.18 Ver1.3.0 完成DMA模式函数部分

简介

对于STM32的调试来说,通过串口打印数据往往是一个不错的选择。
那么,就让小编我们来编写一个初步的串口通信。

博主使用的是自研制的STM32L431RCT6试作型板。
其中板载了CH340G模块,可以直接通过type-c口连接PC USB来进行通信。

如果核心板或开发板中没有CH340G/TTL转USB模块,需要备一个CH340G /TTL转USB模块。

一般USART1 板件都是 PA9发射端 PA10接收端
与串口模块的连接示意图:
STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第1张图片

STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第2张图片

查看原理图

这步跳过吧
STM32的USART1都是PA9发射端 PA10接收端
除非有特殊处理或者说明

CubeMX的配置

配置USART1
STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第3张图片
配置 外部晶振 、下载方式 与 时钟
具体参考STM32集成开发环境 STM32CubeIDE 安装与配置指南

生成代码

UART库与代码

首先建立USART.h 和 USART.h
具体参考STM32CubeIDE开发笔记 MK.II - ST-LINK调试 与 建立用户驱动库

方案A printf 重定向

使用Keil可以参考以下文章的重定向方法
在勾选Keil的Use MicroLib选项后即可

STM32的printf函数重定向

STM32 Uart及其配置

【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解

由于GCC中没有MicroLib,不能对fputc()进行重定向
在参考以下文章后,得知需要对__write()进行重定向

重定向printf函数到串口输出的多种方法
【STM32Cube笔记】10-异步串口收发USART

那么GCC下的应该是这样
STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第4张图片

首先引入huart1

extern UART_HandleTypeDef huart1;   //引用main.c中的串口声明

再进行重定向

//
//CHANNEL A    重定向printf法
//

extern UART_HandleTypeDef huart1;   //引用main.c中的串口声明

//重定向 printf
int _write(int file, char *ptr, int len)
{
	HAL_UART_Transmit(&huart1,(uint8_t *)ptr,len, 0x10);
	return len;
}

int _read(int file, char *ptr, int len)
{
	HAL_UART_Receive(&huart1,(uint8_t *)ptr,len,0x10);
	return len;
}


//
//CHANNEL A
//


在main中添加输出测试

  #define user_buffersize 24
  char user_buffer[user_buffersize]="";
      //channel 1/
	  printf("\r\n");
	  printf("NND\r\n");
	  sprintf(user_buffer,"%s\r\n","WSM?");
	  printf("%s",user_buffer);

	  memset(user_buffer,0,user_buffersize);
	  scanf("%s",user_buffer);
	  printf("%s",user_buffer);
      //channel 1/

STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第5张图片
这边建议别用这种方法,经测试printf可正常使用,scanf有点问题哈,用起来有点不对劲(读不到直接跳)(直接瞎读) 或者直接卡死(keil里面用MicroLib就很正常),如果只使用串口输出是一点问题都没有的。建议回去转KEIL

方案B 函数

但是与其重定向这么麻烦,各个编译器之间还不同……我为什么不在HAL库基础上自己写一个通用呢???在前面浪费了那么多时间的我怕不是个傻子吧???
第二种方法不采用重定义,在USART.c/.h内编写相应改版换皮函数

查询模式

首先我们来说件事
那个……先加个这个函数

串口错误置位 标志位清除函数

void UART_FlagClear(UART_HandleTypeDef *huart_num)
{
	//解决串口错误 恢复置位 清除flag
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_PE);
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_FE);
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_NE);
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_ORE);
}

事情的起因是这样的,按照普通的串口HAL_UART_TransmitHAL_UART_Receive,在使用串口助手调试的时候,在Transmit未结束前,向mcu发送数据时,会卡死!会卡死!会卡死!这个问题困了我三天我是真的吐了。原因是这样做会使串口进入错误回馈函数,并且把相关标志位置位,这之后无论往串口塞什么数据,全都没反应!全都没反应!全都没反应!

这种情况下,就等超时结束。但是如果你把 Transmit或Receive 塞进了while里面……抱歉,你可能出不来了,会一直卡在里面。

感谢这位大佬,成功解决上述问题,即清除标志位。

STM32 HAL_UART_Receive HAL库串口接受清空错误标志

WriteData部分代码

void USART_WriteData(UART_HandleTypeDef *huart_num, const uint8_t *wData)
{
    int len = strlen((const char *)wData);
	while(HAL_UART_Transmit(huart_num,wData,len, 0x10)!= HAL_OK)
	{
		//解决串口错误 恢复置位 清除flag
		UART_FlagClear(huart_num);
	}
}

ReadData部分代码

void USART_ReadData(UART_HandleTypeDef *huart_num,uint8_t *rData, uint16_t len)
{
	USART_WriteData(huart_num,(uint8_t *)"Ready to receive data!\r\n",1);
	while(HAL_UART_Receive(huart_num,rData,len,0x10)!= HAL_OK)
	{
		//解决串口错误 恢复置位 清除flag
		UART_FlagClear(huart_num);
		//清空接收数组 重新接收
		memset(rData,0,len);
	}
}

之后在usart.c中添加下头文件(stdio.h,stm32l4xx_hal.h,string.h),在usart.h中声明下这两个函数

main部分

  #define user_buffersize 24

  uint8_t RXdata_size;
  char user_buffer[user_buffersize]="";
	  //channel 2/
	  memset(user_buffer,0,user_buffersize);
	  sprintf(user_buffer,"\r\nStr:%s\r\n"," Test is ready!");
	  USART_WriteData(&huart1,(uint8_t *)user_buffer,1);

	  memset(user_buffer,0,user_buffersize);
	  sprintf(user_buffer,"%s\r\n","Ready to Receive!");
	  USART_WriteData(&huart1,(uint8_t *)user_buffer,1);

	  RXdata_size=12;

	  memset(user_buffer,0,user_buffersize);
	  USART_ReadData(&huart1,(uint8_t *)user_buffer,RXdata_size,1);
	  USART_WriteData(&huart1,(uint8_t *)user_buffer,1);
	  //channel 2/

正常收发的情况是这样的 唯一的缺点可能就是指定长度接收吧 = =
STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第6张图片
经压力测试,串口不会卡死
STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第7张图片

中断模式

使能中断

STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第8张图片

调用一次receive使能中断

//user_buffersize 在main.h中 #define user_buffersize 26
extern char user_buffer[user_buffersize];
#define IT_MODE   0X01
uint8_t USART_TX_MODE;
uint8_t USART_RX_MODE;
void UART_IT_MODE(UART_HandleTypeDef *huart_num)
{
	USART_RX_MODE=IT_MODE;
	USART_TX_MODE=IT_MODE;
	HAL_UART_Receive_IT(huart_num, (uint8_t *)user_buffer,user_buffersize);
	HAL_UART_Transmit_IT(huart_num, (uint8_t *)"USART_IT_Mode_Ready!\r\n",22);
	memset(user_buffer,0,user_buffersize);
}

Rx接收回调函数

HAL_UART_RxCpltCallback在hal库中被弱定义,在USART.c中编写这个函数
STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第9张图片

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	char  IT_RecTMPbuffer[user_buffersize]="";
	if(huart->Instance == USART1)
	{
		if(USART_RX_MODE== IT_MODE)
		{
		    //IT  RX-CALLBACK
			strcpy(IT_RecTMPbuffer,user_buffer);
			HAL_UART_Transmit_IT(huart, (uint8_t *)IT_RecTMPbuffer,user_buffersize);

			//IT  RX-CALLBACK
			memset(user_buffer,0,sizeof(user_buffer));
			//再次使能中断
			HAL_UART_Receive_IT(huart, (uint8_t *)user_buffer,user_buffersize);
		}
	}
}

这里的IT_RecTMPbuffer是为了传递接受的user_buffer数据,随后清空user_buffer数据并使能下一次接收中断。
其实这个有没有都可以,一般来说

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
		if(USART_RX_MODE== IT_MODE)
		{
		    //IT  RX-CALLBACK
			HAL_UART_Transmit_IT(huart, (uint8_t *)user_buffer,user_buffersize);

			//IT  RX-CALLBACK
			//再次使能中断
			HAL_UART_Receive_IT(huart, (uint8_t *)user_buffer,user_buffersize);
		}
	}
}

这样已经足够

main测试部分

	  //channel 2B
	  UART_IT_MODE(&huart1);
	  //channel 2B

	  while(1)
	  {
	  }

STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第10张图片

DMA模式

添加DMA配置

STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第11张图片
这里以TX normal RX circular为例

USART.C部分

//DMA//
void USART_WriteData_DMA(UART_HandleTypeDef *huart_num, const uint8_t *wData)
{
	USART_RX_MODE=DMA_MODE;
	HAL_UART_Transmit_DMA(huart_num,wData, strlen((const char *)wData));
	return;
}

void USART_ReadData_DMA(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len)
{
	HAL_UART_Receive_DMA(huart_num,rData,len);
	return;
}

回调函数部分

#define DMA_MODE  0X02
uint8_t USART_TX_MODE;
uint8_t USART_RX_MODE;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	char  IT_RecTMPbuffer[user_buffersize]="";
	if(huart->Instance == USART1)
	{
		if(USART_RX_MODE == DMA_MODE)
		{
			//DMA RX-CALLBACK
			//DMA RX-CALLBACK
			//回显接收内容
			USART_WriteData_DMA(huart,(uint8_t*)user_buffer);
		}
	}
}
	  //channel 2C
	  strcpy(user_buffer,"DMA_Trans_Ready!\r\n");

	  USART_WriteData_DMA(&huart1,(uint8_t*)user_buffer);
	  USART_ReadData_DMA(&huart1,(uint8_t*)user_buffer,user_buffersize);
	  //channel 2C

	  while(1)
	  {
		  osDelay(100);
	  }

STM32CubeIDE开发笔记 MK.III - UART串口通信(普通/中断/DMA)_第12张图片

全方案完整参考代码

注意,完整部分与上面部分有所不同,不可无脑直接套入

USART.h

/*
 * USART.h
 *
 *  Created on: Mar 8, 2022
 *      Author: KeeganKei
 */

#ifndef INC_USART_H_
#define INC_USART_H_

#define IT_MODE   0X01
#define DMA_MODE  0X02

//重定向
int _write(int file, char *ptr, int len);
int _read(int file, char *ptr, int len);

void UART_FlagClear(UART_HandleTypeDef *huart_num);

//EX 函数
void USART_WriteData(UART_HandleTypeDef *huart_num, const uint8_t *wData);
void USART_ReadData(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len);

void UART_IT_MODE(UART_HandleTypeDef *huart_num);

void USART_WriteData_DMA(UART_HandleTypeDef *huart_num, const uint8_t *wData);
void USART_ReadData_DMA(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len);

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

#endif /* INC_USART_H_ */

USART.c

/*
 * USART.c
 *
 *  Created on: Mar 8, 2022
 *      Author: KeeganKei
 */

#include 
#include 
#include   //import HAL_UART_Transmit
#include 
#include 

// 
// //CHANNEL A    重定向printf法
// 

//extern UART_HandleTypeDef huart1;   //引用main.c中的串口声明
//
// //重定向 printf
//int _write(int file, char *ptr, int len)
//{
//	HAL_UART_Transmit(&huart1,(uint8_t *)ptr,len, 0x0A);
//	return len;
//}
//
//int _read(int file, char *ptr, int len)
//{
//	HAL_UART_Receive(&huart1,(uint8_t *)ptr,len,0x0A);
//	return len;
//}


// 
// //CHANNEL A
// 


// 
// //CHANNEL B
// 
extern char User_Tx_Buffer[User_TX_BufferSize];
extern char User_Rx_Buffer[User_RX_BufferSize];

uint8_t USART_TX_MODE;
uint8_t USART_RX_MODE;

// FLAG_CLEAR
void UART_FlagClear(UART_HandleTypeDef *huart_num)
{
	//解决串口错误 恢复置位 清除flag
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_PE);
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_FE);
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_NE);
	__HAL_UART_CLEAR_FLAG(huart_num, UART_FLAG_ORE);
}

// //
// //
// //NORMAL//
void USART_WriteData(UART_HandleTypeDef *huart_num, const uint8_t *wData)
{
	while(HAL_UART_Transmit(huart_num,wData,strlen((const char *)wData), 0x10)!= HAL_OK)
	{
		//解决串口错误 恢复置位 清除flag
		UART_FlagClear(huart_num);
	}

	return;
}

void USART_ReadData(UART_HandleTypeDef *huart_num,uint8_t *rData, uint8_t len)
{
	while(HAL_UART_Receive(huart_num,rData,len,0x10)!= HAL_OK)
	{
		//解决串口错误 恢复置位 清除flag
		UART_FlagClear(huart_num);
		//清空接收数组 重新接收
		memset(rData,0,len);
	}

	return;
}


// IT
void UART_IT_MODE(UART_HandleTypeDef *huart_num)
{
	USART_RX_MODE=IT_MODE;
	USART_TX_MODE=IT_MODE;
	HAL_UART_Receive_IT(huart_num, (uint8_t *)User_Rx_Buffer,User_RX_BufferSize);
	HAL_UART_Transmit_IT(huart_num, (uint8_t *)"USART_IT_Mode_Ready!\r\n",22);
	memset(User_Rx_Buffer,0,User_RX_BufferSize);
}


// //DMA//
void USART_WriteData_DMA(UART_HandleTypeDef *huart_num, const uint8_t *wData)
{
	USART_TX_MODE=DMA_MODE;
	HAL_UART_Transmit_DMA(huart_num,wData, strlen((const char *)wData));
	return;
}

void USART_ReadData_DMA(UART_HandleTypeDef *huart_num,uint8_t *rData,uint8_t len)
{
	USART_RX_MODE=DMA_MODE;
	HAL_UART_Receive_DMA(huart_num,rData,len);
	return;
}

// //
// //

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
		if(USART_TX_MODE == IT_MODE)
		{
		    // //IT  TX-CALLBACK
			// //IT  TX-CALLBACK
		}
		else if(USART_TX_MODE == DMA_MODE)
		{
			// //DMA TX-CALLBACK
			// //DMA TX-CALLBACK
		}
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	char  IT_RecTMPbuffer[User_RX_BufferSize]="";
	if(huart->Instance == USART1)
	{
		if(USART_RX_MODE == IT_MODE)
		{
		    // //IT  RX-CALLBACK
			//回显接收数据,有小bug
			strcpy(IT_RecTMPbuffer,User_Rx_Buffer);
			HAL_UART_Transmit_IT(huart, (uint8_t *)IT_RecTMPbuffer,User_RX_BufferSize);

			// //IT  RX-CALLBACK
			memset(User_Rx_Buffer,0,User_RX_BufferSize);
			HAL_UART_Receive_IT(huart, (uint8_t *)User_Rx_Buffer,User_RX_BufferSize);
		}
		else if(USART_RX_MODE == DMA_MODE)
		{
			// //DMA RX-CALLBACK
			// //DMA RX-CALLBACK
			//回显接收数据
			USART_WriteData_DMA(huart,(uint8_t*)User_Rx_Buffer);
		}
	}
}




// 
// //CHANNEL B
// 


MAIN.H

// 24 char +\r\n = 26
#define User_TX_BufferSize 26
#define User_RX_BufferSize 26

MAIN.C(测试部分)

PART.A

#include 
#include 
#include 

char User_Tx_Buffer[User_TX_BufferSize]="";
char User_Rx_Buffer[User_RX_BufferSize]="";
extern uint8_t USART_TX_MODE;
extern uint8_t USART_RX_MODE;

PART.B
测试时CHANNEL 1/2A/2B/2C 选择测试时注释掉其他三大块的内容

      // ///channel 1/
	  //不推荐
//	  memset(User_Tx_Buffer,0,User_TX_BufferSize);
//	  printf("\r\n");
//	  printf("NND\r\n");
//	  sprintf(User_Tx_Buffer,"%s\r\n","WSM?");
//	  printf("%s",User_Tx_Buffer);
//
//	  memset(User_Rx_Buffer,0,User_RX_BufferSize);
//	  scanf("%s",User_Rx_Buffer);
//	  printf("%s",User_Rx_Buffer);
      // ///channel 1/

	  // /channel 2A
	  //新手推荐,且足够一般情况使用
//	  memset(User_Tx_Buffer,0,User_TX_BufferSize);
//	  sprintf(User_Tx_Buffer,"\r\nStr:%s\r\n"," Test is ready!");
//	  USART_WriteData(&huart1,(uint8_t *)User_Tx_Buffer);
//
//	  memset(User_Tx_Buffer,0,User_TX_BufferSize);
//	  sprintf(User_Tx_Buffer,"%s\r\n","Ready to Receive!");
//	  USART_WriteData(&huart1,(uint8_t *)User_Tx_Buffer);
//
//	  uint8_t RXdata_size = 12;
//	  memset(User_Rx_Buffer,0,User_RX_BufferSize);
//	  USART_ReadData(&huart1,(uint8_t *)User_Rx_Buffer,RXdata_size);
//	  USART_WriteData(&huart1,(uint8_t *)User_Rx_Buffer);
	  // /channel 2A

	  // /channel 2B
	  //有小bug
//	  UART_IT_MODE(&huart1);
//	  while(1)
//	  {
//		  osDelay(100);
//	  }
	  // /channel 2B

	  // /channel 2C
	  //按指定长度输送不会有Bug
//	  USART_TX_MODE=DMA_MODE;
//	  USART_RX_MODE=DMA_MODE;
//
//	  memset(User_Tx_Buffer,0,User_TX_BufferSize);
//	  strcpy(User_Tx_Buffer,"DMA_Trans_Ready!\r\n");
//
//	  USART_WriteData_DMA(&huart1,(uint8_t*)User_Tx_Buffer);
//	  USART_ReadData_DMA(&huart1,(uint8_t*)User_Rx_Buffer,User_RX_BufferSize);
//	  while(1)
//	  {
//		  osDelay(100);
//	  }
	  // /channel 2C

存在部分细微BUG = =,待施工完毕

构建Debug与调试

参考STM32CubeIDE开发笔记 MK.II - ST-LINK调试 与 建立用户驱动库进行debug与调试

后记

搞着搞着,开始怀疑我到底会不会串口了= =
这部分类型转换的锅比较多 嗯。。。
测试了几天总算完毕了
那…就这样吧

你可能感兴趣的:(stm32,单片机,arm)