目录
前言
本文主要讲述了利用STM32Cubemx来配置DMA串口收发,会手把手一步一步的进行教学,量大管饱,放心食用。
本文使用的芯片为STM32F103C8T6(经典老演员了),使用的工具为STM32Cubemx。
本文主要面向初学者,详细讲述了配置过程。如果你有任何疑问,都可以在文章后面留言(虽然我也不一定回答就是了doge)。
1.基础知识
1.1DMA简介
在正式配置之前,我们先来一起简单了解一下DMA。
DMA(Direct Memory Access,直接内存访问)是一种用于处理器和外设之间传输数据的技术,通过DMA,外设可以直接访问内存中的数据,而不需要处理器的干预,从而提高数据传输的效率。
举个例子:
我是元始天尊,我把灵珠给太乙真人,然后通过太乙真人把灵珠给殷夫人,这是常规情况的数据传输,太乙真人就是CPU,灵珠就是数据,这样做会占用太乙真人的精力(消耗CPU的资源)。
然而实际上,太乙真人在这中间仅仅只是充当了一个大自然的搬运工,这样太大材小用了,像太乙真人这样的十二金仙,有捍卫人间正道,斩妖除魔的大事儿要处理,哪儿能天天当快递员呀。
于是,伟大的元始天尊(也就是我),想了一个办法,我直接用法术,把灵珠传送到殷夫人肚子里面,这就是DMA了。灵珠就是数据,法术就相当于是DMA通道,这样不但太乙真人可以去干大事儿了,灵珠也能更快到达殷夫人肚子里面了,两全其美,皆大欢喜。
这张图就是我上面描述的过程,有细心的小伙伴可能发现了,我这里用的是双向箭头,因为这里的数据传输,也是双向的,殷夫人收到灵珠了,但是她不会用呀,那她也可以通过DMA通道,找我要使用说明书;太乙真人那边同理啊,太乙真人就相当于一个客服,他可以把殷夫人的问题转述给我。所以这里数据传输是双向的,因为他只是一个通道,这个通道双方都可以使用。
看懂了上面那张图,那我们接下来看看STM32参考手册的图:
乍一看是不是感觉好复杂?不要害怕,我们一起来仔细看一下:
红色的部分,就是元始天尊,我把数据通过DMA通道(蓝色部分),直接传递给殷夫人(绿色部分),这就是DMA传输,很好理解,对吧?是不是和上面那张哪吒的图一摸一样呀?
1.2 串口简介
这里就不再赘述啦,不清楚的同学可以看看我这篇文章:
https://blog.csdn.net/Jin_Apple/article/details/142100390?spm=1001.2014.3001.5502
2.STM32CUBEMX配置(工程创建)
2.1 基础配置
首先打开STM32CUBEMX,选择我们的芯片(我这里用的是STM32F103C8T6)
然后是时钟配置,这里选择外部高速时钟,然后配置时钟频率72Mhz(这里输入完成之后直接按回车就可以了)
然后是系统配置
2.2 串口配置
接下来我们来配置串口:
选择USART1,将模式设置为异步通信,下面的波特率、传输长度等,我们用默认的就可以了。
2.3 DMA配置
为串口添加DMA,选择normal模式(正常模式)
2.4 工程创建
给你的工程起个名字,然后选择你的保存路径,选择MDK-ARM
然后点击右上角的GEAERATE CODE就可以了。
3. 库函数简介
接下来我们来介绍一下相关的函数:
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
这个是HAL库利用DMA发送数据的函数,我们先来看看官方对他的注释:
@brief就是简单介绍了一下这个函数的功能,以DMA模式发送数据。
@note 进行了一些补充说明,当UART奇偶校验没有启用,且字长为9位时,发送的数据被视作uint16的数据,在这种情况下,size必须通过pdata提供uint16。是不是看着花里胡哨的?其实就这一大坨英文就表达了一个意思,如果UART配置为9位数据长度,就应该以uint16为单位进行处理,而不是uint8。(多啰嗦一句,uint8指的是无符号的8位整型,由8个二进制位组成,其最大值是1111 1111 ,也就是255,uint16是和它类似,不过是16位的)。
接下来我们来看参数:
huart:这是一个指向UART_HandleTypeDef
结构体的指针。
pData:这是一个指向数据缓冲区的指针,可以是uint8或uint16
类型的数据元素。根据UART配置,这个缓冲区可能包含8位或16位的数据。
Size:这是一个无符号整数,表示要发送的数据元素的数量。如果UART配置为9位数据长度,则这个参数应该表示uint16
的数量。
我们用更简单的话来说明一下,pData,就是你要发送的数据,Size,就是你要发送的数据长度。
接下来我们再来看看接收函数:
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
可以看到这个其实和发送函数类似,简单来说,pData就是数据,Size就是接收的长度。
4.DMA空闲中断收发串口数据
接下来就到了改写代码的环节了,我们例程的功能是:收到1,则回复2。
在usart.c文件夹中添加如上代码,分别用于表示接受数据的长度、数据、以及接受完成的标志。
uint8_t Gu8RxLen = 0;
uint8_t Gu8RxDataBuf[16];
uint8_t Gu8RxEndFlag = 0;
这里开启空闲中断,并且用到了我们上面提到的接收函数。
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能空闲中断
HAL_UART_Receive_DMA(&huart1,Gu8RxDataBuf,16);
之后我们在usart.h文件里面,将我们上面定义的三个整型加上extern 关键字,这样只要其他文件里面引用了usart.h这个头文件,就可以使用这三个整型。
接下来我们来改一下中断部分:
先引用头文件,因为我们这里需要用到上面的整型
然后我们改写一下USART1_IRQHandler(void)这个函数
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t Lu32Itflag = 0;
uint32_t Lu32Num = 0;
Lu32Itflag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取空闲中断标志位
if((Lu32Itflag != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输
Lu32Num = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
Gu8RxLen = 16 - Lu32Num; //总计数减去未传输的数据个数,得到已经接收的数据个数
Gu8RxEndFlag = 1; // 接受完成标志位置1
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
这下该准备的都准备好了,我们可以来实现我们上面需要的功能了。
在main.c里面定义一个数组,用来发送数据:
uint8_t Gu8TxBuf[16];
Gu8TxBuf[0] = 0x02;
此后在循环里面,加入如下代码:
while (1)
{
if(Gu8RxEndFlag == 1)//如果接收完成
{
if(Gu8RxDataBuf[0] == 0x01)//收到1
{
HAL_UART_Transmit_DMA(&huart1,Gu8TxBuf,1);//发送2
}
Gu8RxEndFlag = 0;//清除接收完成标志位
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
最后我们选择自己的烧录器,编译下载即可。
5.实验现象
可以看到我们这边已经成功了,当我发送1的时候,自动回复2。
本文参考了如下文章: