教你手写DMA传输数据(看完这篇你就会手动写啦,保姆级讲解)---- 2020.3.31

Virginia ·
更新时间:2024-11-10
· 853 次阅读

关于DMA与串口原理方面的文章:

嵌入式stm32 复习(工作用)— USART(串口)通信原理知识 2020.3.23
添加链接描述

教你手写串口收发数据(看完这篇你就会手动写啦,保姆级讲解)---- 2020.3.28
添加链接描述

嵌入式stm32 复习(工作用)— DMA控制器知识 2020.3.30
添加链接描述

先上完整DMA串口收发部分代码!!! #define CR1_OVER8_Set ((u16)0x8000) /* USART OVER8 mode Enable Mask */ void USART_DMA_Init(void) { //1.开启时钟 RCC->AHBENR |= 1 <CCR = 0;//先清零 DMA1_Channel5->CCR |= 1 <CCR |= 1 <CNDTR = USART1_REC_MAX - 1;//允许最大接收数据缓存大小,注意,必须在DMA停止时设置 //设置外设数据地址 DMA1_Channel5->CPAR = (u32) &USART1->DR; //设置内存缓存器的地址 DMA1_Channel5->CMAR = (u32) USART1_REC; //3.使能DMA DMA1_Channel5->CCR |= 1 <CR1 & CR1_OVER8_Set) != 0) { /* Integer part computing in case Oversampling mode is 8 Samples */ integerdivider = ((25 * apbclock) / (2 * bound)); } else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */ { /* Integer part computing in case Oversampling mode is 16 Samples */ integerdivider = ((25 * apbclock) / (4 * bound)); } tmpreg = (integerdivider / 100) <> 4)); /* Implement the fractional part in the register */ if ((USARTx->CR1 & CR1_OVER8_Set) != 0) { tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t) 0x07); } else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */ { tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t) 0x0F); } return tmpreg; } void USART1_Init(int bound) { //1.使能时钟 RCC->APB2ENR |= 1 <APB2ENR |= 1 <CRH &= ~(0x0F <CRH |= 0x0B < 50MHz,复用推免 GPIOA->CRH &= ~(0x0F <CRH |= 0x04 < 浮空输入模式 //3.初始化USART1 //3.1 设置波特率 USART1->BRR = USART_GetBound(USART1, bound);// 0x1D4 <CR1 &= ~(1 <CR1 &= ~(1 <CR2 &= ~(0x02 <CR1 |= 1 <CR1 |= 1 <CR1 |= 1 <CR3 |= 1 <CR1 |= 1 <CR1 |= 1 <SR & 0x10) { //总线空闲 USART1_STA = (USART1_REC_MAX - 1) - DMA1_Channel5->CNDTR;//实际接收的数据大小 USART1_REC[USART1_STA & 0x3FFF] = '\0';//保证有效字符串 USART1_STA |= 0x8000;//标识接收成功 USART1->DR;//清空状态 DMA1_Channel5->CCR &= ~(1 <CNDTR = USART1_REC_MAX - 1;//还原允许的数据缓冲区的大小 DMA1_Channel5->CCR |= (1 << 0);//启动DMA } } void USART1_SendData(u8 *data, u8 len) { u8 i = 0; for (i = 0; i SR & 0x40) == 0) ; USART1->DR = *(data + i); //等效于下面的函数 // USART_SendData(USART1, *(data + i)); } } int fputc(int ch, FILE* f) { while ((USART1->SR & 0x40) == 0) ; USART1->DR = (u8) ch; //等效于下面的函数 return ch; } 好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!

串口DMA初始化部分

//1.开启时钟 RCC->AHBENR |= 1 << 0;//DMA1时钟

//
在这里插入图片描述//由上图可知DMA是在AHB总线下的,所以需要开启AHB时钟,并且我们这里使用的是DMA1。
//
在这里插入图片描述//
在这里插入图片描述

//2.配置DMA DMA1_Channel5->CCR = 0;//先清零 DMA1_Channel5->CCR |= 1 <CCR |= 1 << 12;//中等优先级

//
在这里插入图片描述//咦?这里为啥是反的?大家凑合看吧~
在这里插入图片描述
//
在这里插入图片描述//因为这里使用过的就是USART1的读取,因为我们今天使用的过程就是通过RX读取外设上的数据,然后再通过DMA通道5来发送数据到存储器中。

//在这里插入图片描述
在这里插入图片描述//所以这里我们先清零,然后才能将我们需要写入的数据放到该寄存器中。其实另一方面也相当于设置成我们想要的配置了,大家往下看就知道了。
//
在这里插入图片描述//因为我们这里是外设到存储器,所以该位为0。

//在这里插入图片描述//串口传输的长度是8位,所以11:10位也为0。
//
在这里插入图片描述//这里我们外设地址不加一,所以该位为0。
//
在这里插入图片描述
//这里我们不执行循环操作,因为如果是循环操作的话,那么DMA写数据到存储器中,是不会管存储器缓存空间是否溢出,所以我们等等需要设置的就是得自己认为关闭掉DMA,然后再清空缓存区里面的数据,最后再开启,相当于是人为进行循环了。

//在这里插入图片描述//因为我们这里就是从外设读数据到存储器中,所以该位为0。

//由上图我们可以得知,存储器地址是加一的,外设地址是不加一的。
//
在这里插入图片描述//在这里插入图片描述//这里我们设置为中等优先级,如果我们在用DMA传输的数据比较重要的话,在这里可以改成高或者最高优先级。

//设置DMA数据接收大小 DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//允许最大接收数据缓存大小, //注意,必须在DMA停止时设置

//
在这里插入图片描述//在前几篇关于串口收发数据的文章中我们可以得知USART1_REC_MAX是可以接收的最大数据长度,但是由于最后我们要表示该数据为有效数据,往往在最后加一个“/0”停止符。所以我们这里还是减一,留出一个空间来存放“/0”。

//设置外设数据地址 DMA1_Channel5->CPAR = (u32) &USART1->DR;

//在这里插入图片描述//这里就是将串口的数据赋值给这个寄存器,然后DMA再进行传输。

//设置内存缓存器的地址 DMA1_Channel5->CMAR = (u32) USART1_REC;

//
在这里插入图片描述//最后存储器中的缓冲区为USART1_REC。

//3.使能DMA DMA1_Channel5->CCR |= 1 << 0;

//在这里插入图片描述//这里就是开启DMA对应的通道,也相当于是使能。

串口DMA读取数据中断服务函数部分

if (USART1->SR & 0x10) { //总线空闲

//
在这里插入图片描述//当检测到串口总线空闲,那么就代表数据传输完成。
//
在这里插入图片描述

USART1_STA = (USART1_REC_MAX - 1) - DMA1_Channel5->CNDTR;//实际接收的数据大小

//
在这里插入图片描述//由上图我们可以得知,该寄存器代表的是现在的缓存区中还有多少个空余的缓存区。
那么我们想知道具体到底传输了多少个数据,那就可以用我们自己设置的最大可接受数据长度减掉这个寄存器值。

USART1_REC[USART1_STA & 0x3FFF] = '\0';//保证有效字符串

//由于上一篇文章中也讲了,所以这里就不再赘述了。

USART1_STA |= 0x8000;//标识接收成功 USART1->DR;//清空状态

//同理不再赘述。

DMA1_Channel5->CCR &= ~(1 <CNDTR = USART1_REC_MAX - 1;//还原允许的数据缓冲区的大小 DMA1_Channel5->CCR |= (1 << 0);//启动DMA

//在前面我们也说过我们设置的不是循环模式,所以如果想要源源不断的传输数据并且保证最终的存储区不溢出,那么我们就需要先关闭DMA,然后还原允许的数据缓冲区的大小,最后再启动DMA即可。

结束语

个人认为大家如果细心看完这篇文章,并且结合上一篇文章一起看(在文章的刚开始会将前几篇关于USART和DMA原理部分的文章链接发出来),我相信大家会彻底掌握DMA了!!!如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!

以后我会继续推出关于嵌入式(stm32)的协议方面的讲解,下一讲会推出PWM部分的文章!敬请期待!!!

**我先休息去了~~╭(╯^╰)╮


作者:致敬!!!



dma 数据

需要 登录 后方可回复, 如果你还没有账号请 注册新账号