关于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 << 0;//DMA1时钟//2.配置DMADMA1_Channel5->CCR = 0;//先清零DMA1_Channel5->CCR |= 1 << 7;//执行存储器地址增量操作DMA1_Channel5->CCR |= 1 << 12;//中等优先级//设置DMA数据接收大小DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//允许最大接收数据缓存大小,注意,必须在DMA停止时设置//设置外设数据地址DMA1_Channel5->CPAR = (u32) &USART1->DR;//设置内存缓存器的地址DMA1_Channel5->CMAR = (u32) USART1_REC;//3.使能DMADMA1_Channel5->CCR |= 1 << 0;}u16 USART_GetBound(USART_TypeDef* USARTx, int bound) {uint32_t integerdivider = 0x00, apbclock = 0x00;uint32_t tmpreg = 0x00;uint32_t fractionaldivider = 0x00;apbclock = 72000000; //如是USART1是72M,否则是36M/* Determine the integer part */if ((USARTx->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;/* Determine the fractional part */fractionaldivider = integerdivider - (100 * (tmpreg >> 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 << 2;//GPIOA 时钟使能RCC->APB2ENR |= 1 << 14;//USART1 时钟使能//2.初始化GPIOGPIOA->CRH &= ~(0x0F << 4);GPIOA->CRH |= 0x0B << 4;//设置GPIOA.9 -> 50MHz,复用推免GPIOA->CRH &= ~(0x0F << 8);GPIOA->CRH |= 0x04 << 8;//设置GPIOA.10 -> 浮空输入模式//3.初始化USART1//3.1 设置波特率USART1->BRR = USART_GetBound(USART1, bound);// 0x1D4 << 4 | 0x4B;//设置波特率9600//3.2 设置校验位USART1->CR1 &= ~(1 << 10);//不使用校验位//3.3 数据位USART1->CR1 &= ~(1 << 12);//8位长度//3.4 停止位USART1->CR2 &= ~(0x02 << 12);//1个停止位//4.使能USART1->CR1 |= 1 << 3;//使能TXUSART1->CR1 |= 1 << 2;//使能RXUSART1->CR1 |= 1 << 13;//使能USART1USART1->CR3 |= 1 << 6;//开启USART1的RX DMA//配置数据接收时候,需要用到中断//5.配置NVIC//5.1 先分组NVIC_SetPriorityGrouping(5);NVIC_EnableIRQ(USART1_IRQn);//使能USART1的全局中断NVIC_SetPriority(USART1_IRQn, 10);//设置USART1的中断优先级是10//6 使能接受数据中断寄存器
//  USART1->CR1 |= 1 << 5;    ,如果使用DMA方式,需要开启数据接收中断USART1->CR1 |= 1 << 4;//开启IDLE中断USART_DMA_Init();
}u8 USART1_REC[USART1_REC_MAX] = { 0 };
//USART1_STA[15] 标识是否接收完成状态位,1:标识接收完成;0:未接收完成;
//USART1_STA[14] 标识是否为第一个数据,1:非第一个数据;0:表示第一个数据;
//USART1_STA[13:0] 标识有效的数据接收长度
u16 USART1_STA = 0;void USART1_IRQHandler(void) {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 << 0);//先关闭DMADMA1_Channel5->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 < len; i++) {if (*(data + i) == '\0')    //空白符号无需发送return;//判断是否允许发送数据while ((USART1->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 << 7;//执行存储器地址增量操作
DMA1_Channel5->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 << 0);//先关闭DMA
DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//还原允许的数据缓冲区的大小
DMA1_Channel5->CCR |= (1 << 0);//启动DMA

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

结束语

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

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

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

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

  1. 禁止查看写好的宏_【收藏】亚马逊Listing不知道怎么写??看完此篇,即刻破单!...

    [收藏]亚马逊Listing不知道怎么写??看完此篇,即刻破单! 众所周知,在亚马逊平台上大部分交易来自于排位靠前的listing决定的,可见listing的重要性.这里有不少新卖家在选完品之后,不知 ...

  2. 神了!!看完这篇文章我不仅学会了手撸vue三开关组件,还搞懂了父子组件传值

    神了!!看完这篇文章我不仅学会了手撸vue三开关组件,还搞懂了父子组件传值 引子 前置知识 什么是vue组件 父子组件传值 父传子 子传父 model选项的引入 三开关组件(three-switch) ...

  3. 手把手教你完成CSDN对接百度统计 看完这篇文章你还不会对接 欢迎您提刀顺着网线来砍我!!!!

    大家好,我是:じ☆ve朽木,开发经验都是一步一步慢慢积累的,没有谁生来就具有的,只要我们付出了努力,肯定就会有收获!进入我的博客,带你了解Java知识,js小技巧,带你玩转高端物联网.博客地址为:じ☆ ...

  4. 97年世界编程大赛第一名写的程序……看完很无奈 太牛了

    97年世界编程大赛第一名写的程序--看完很无奈 太牛了 保险起见大家运行完了还是删除哈,然后运行之前最好也看看..俺不懂,万一有啥问题俺能不负责吧 这个程序是97年Mekka '97 4K Intro ...

  5. 收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!

    Python Web全栈开发入门实战教程教程    大家好,我叫亓官劼(qí guān jié ),这个<Python Web全栈开发入门实战教程教程>是一个零基础的实战教程,手把手带你开 ...

  6. 【系统架构设计师】软考高级职称,一次通过,倾尽所有,看完这篇就够了,方法和技巧这里全都有。

    目录 背景 报考条件 通过率 考试时间要求 系统架构设计师考试内容 证书的价值 备考建议 报班&自学 分享下我的自学方法 必胜法宝 分享我考试的心态 [系统架构设计师]软考高级职称,一次通过, ...

  7. 为何看完这篇RxHttp Http请求框架会觉得如此销魂,全文干货建议收藏!

    前言 RxHttp相较于retrofit,功能上,两者均能实现,并无多大差异,更多的差异体现功能的使用上,也就是易用性,如对文件上传/下载/进度监听的操作上,RxHttp用及简的API,可以说碾压re ...

  8. 程序员要怎么高效学习Java,大学生or小白的你看完这篇的你离BAT又近了一大步

    这篇文章大体上会从以下几个部分展开: 认清自己. 学习目的. 时间管理. 学习方法. 学习的步骤. 获取知识的途径 影响学习的几个因素 自己的心态. 外物的影响. 其他想说的 大学生的学习 一些感悟 ...

  9. 看完这篇分析,还不懂分布式事物,请给我差评

    看完这篇分析,还不懂分布式事物,请给我差评 咖啡拿铁 架构师小秘圈 今天 作者:咖啡拿铁,现就职于美团点评,后端研发 来自:公众号咖啡拿铁(ID:code_3092860495) 0 题记 又或者在网 ...

最新文章

  1. Windows下基于python3使用word2vec训练中文维基百科语料(一)
  2. Java基础语法 第2节 Java语言基本语法
  3. 13行满分代码:1038 统计同成绩学生 (20分)
  4. 自动化运维-Ansible (第三部:Playbook 介绍)
  5. 开源编译工具和编译软件
  6. IDEA配置java开发环境
  7. 网狐6603服务器文档,【整理发布】网狐 6603 棋牌平台搭建图文详解(二)
  8. 多媒体计算机技术2017年四月,自考软件工程:2017年10月07167多媒体计算机技术历年真题及答案...
  9. [SCU 4507] 奶牛情书 (AC自动机)
  10. html js 图片左右切换代码,Js图片切换特效中的左右箭头功能实现代码
  11. Texstduio+Miktex遇到ntx-Italic-tlf-t1 could not be created问题
  12. 少女口述:我跟一个小混混那不堪回首的感情
  13. UltraEdit mac版破解方法
  14. win10升级win11后电源设置里面找不到休眠功能
  15. [RK3568 Android11] 开发之开发者选项中添加USB OTG模式切换(一)
  16. 平阴县实验学校计算机老师,人物 | 平阴县实验学校邢珍:认认真真做事,踏踏实实做人...
  17. 跨平台API对接(Java)
  18. 白月黑羽教python之pytest:课后练习
  19. Android知识点 405 -- Dropbox
  20. Linux下用Samba作PDC配置方法

热门文章

  1. web实时视频流从0到1(ffmpeg+nginx-http-flv-module+flv.js)
  2. 从程序员到项目经理(18):不要试图和下属做朋友【转载】
  3. vuex的五大辅助函数使用技巧
  4. 各种软件破解/安装网址及说明
  5. 肌营养不良日常饮食要注意什么
  6. python snownlp情感分析_白杨数说 | 不会做文本情感分析?试试这两个Python包
  7. Linux国内常用软件源的介绍
  8. AOE 工程实践-银行卡 OCR 里的图像处理
  9. mysql 创建用户名及密码
  10. 数据仓库之电商数仓-- 5、即席查询Kylin