芯片型号:STM32F103RC
软件开发包:标准外设库
如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。
DMA 具有 12 个独立可编程的通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
当多个 DMA 通道请求同时发生时,仲裁器负责管理其处理顺序。
仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先级越高,比如通道 0 高于通道 1。此外,在大容量产品和互联型产品中, DMA1 控制器的优先级高于 DMA2 控制器。
如图,外设寄存器、Flash 和 SRAM 分别是数据转运的两大站点,前者是外设寄存器站点,后两者组成存储器站点。在 STM32 中,存储器一般特指 Flash 和 SRAM,不包含外设寄存器,所以就描述成外设到存储器、存储器到存储器。
DMA 的数据转运可以是从外设到存储器,也可以从存储器到外设,通过配置参数来控制方向。另外还可以从存储器转运到存储器,比如 Flash 到 SRAM或者 SRAM 到 SRAM。由于 Flash 是只读的,所以 DMA 不可以进行 SRAM 到 Flash,或者 Flash 到 Flash 的转运操作。
进行数据转运就需要指定起始点和终点以及转运方式,对此,外设和存储器都有 3 个参数,分别是起始地址,数据宽度和地址是否自增。
起始地址分为外设端的起始地址和存储器端的起始地址,决定了数据起始点和终点;数据宽度指定了一次转运要按多大的数据宽度来进行,可以选择字节、半字和字;地址是否自增指定一次转运完成后下一次转运是否要移动地址。
如果要进行存储器到存储器的数据转运,就需要把其中一个存储器的地址放在外设的站点。只要在外设起始地址里写 Flash 或者 SRAM 的地址,那它就会去Flash 或者 SRAM 找数据。这个站点只是叫做外设寄存器,并没限制只能指定为外设,存储器站点也是如此。
下面有个传输计数器,用于指定转运次数的,是一个自减计数器,当减到 0 之后,DMA 就不会再进行数据转运了。另外,它减到 0 之后,之前自增的地址也会恢复到起始地址,以方便新一轮的转换。
在传输寄存器的右边有一个自动重装器,作用为传输计数器减到 0 之后,是否要自动恢复到最初的值。比如最初传输计数器值为 5,如果不使用自动重装器,转运 5 次后,DMA 就停止了;如果使用自动重装器,转运 5 次,计数器减到 0 后,就会立即恢复到 5。所以,自动重装器决定了转运的模式,单次模式或循环模式。
再下面就是 DMA 的触发控制,有硬件触发和软件触发, 由 DMA_CCRx 寄存器的 MEM2MEM 位控制。
软件触发不是调用某个函数一次,触发一次,而是以最快的速度,连续不断地触发 DMA,尽快使传输计数器清零,完成一轮的转换。**软件触发和循环模式不能同时使用,因为软件触发就是想把传输计数器清零,循环模式是清零后自动重装,如果同时用的话,DMA 就停不下来了。**软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动,不需要时机,并且想尽快完成。
硬件触发源可以选择 ADC、串口、定时器等,使用硬件触发的转运,一般都与外设有关,这些转运需要一定的时机,比如 ADC 转换完成。串口收到数据。定时时间到等,所以需要使用硬件触发,在硬件达到这些时机时,传递信号来触发 DMA 进行转运。
最后还有一个开关控制,也就是 DMA_Cmd
函数。当 DMA 使能后,DMA 就准备就绪了,可以进行转运了。
DMA 进行转运有几个条件:
DMA_Cmd
给 DISABLE,关闭 DMA,再为传输计数器写如一个大于 0 的数,再 DMA_Cmd
给 ENABLE,使能 DMA。注意:写传输计数器时,必须先关闭 DMA,再进行。不能在 DMA 开启时,写传输计数器。
如图是 DMA1 的请求映像,有 7 个通道,每个通道都有一个数据选择器,可以选择软件触发或硬件触发。每个通道的硬件触发源不同,所以需要根据触发源选择对应的通道。如果使用软件触发,就可以任意选择通道,因为每个通道的软件触发都是一样的。
如图,如果源端宽度等于目标宽度,数据正常转运;如果源端宽度小于目标宽度,目标数据高位就会补 0;如果源端宽度大于目标宽度,目标数据高位就会舍去。
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; // 外设地址
uint32_t DMA_PeripheralDataSize; // 外设数据宽度
uint32_t DMA_PeripheralInc; // 外设地址是否自增
uint32_t DMA_MemoryBaseAddr; // 存储器地址
uint32_t DMA_MemoryDataSize; // 存储器数据宽度
uint32_t DMA_MemoryInc; // 存储器地址是否自增
uint32_t DMA_DIR; // 传输方向
uint32_t DMA_BufferSize; // 传输数目,对应结构图的传输计数器的值
uint32_t DMA_Mode; // 模式选择,对应结构图就是是否使用自动重装
uint32_t DMA_M2M; // 选择是否使用存储器到存储器模式,其实就是选择硬件触发还是软件触发
uint32_t DMA_Priority; // 通道优先级
} DMA_InitTypeDef;
对照 DMA 的基本结构分析该任务:
外设站点和存储器站点的三个参数。
数组有 7 个元素,要转运 7 次,所以传输计数器写 7,自动重装暂时不需要。
触发选择部分,因为是存储器到存储器,所以使用软件触发。
使能 DMA,这样数据就会从 DataA 转运到 DataB。
这里的数据转运是一种复制转运,转运完成后,DataA 的数据并不会消失。
为了提高程序的复用功能,将外设地址、存储器地址和传输数据数目设为初始化函数 UserDMA_Init
的参数,之后,在调用初始化函数 UserDMA_Init
时,就可以根据具体情况进行传参。同时,为了可以在数据更改后,可以继续使用 DMA 转运数据,编写函数 UserDMA_Transfer
,并将使能 DMA 的工作交给该函数。
static uint16_t transfer_size; // 静态变量,存储 DMA 传输数目
void UserDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t size)
{
DMA_InitTypeDef DMA_InitStructure;
transfer_size = size; // 保存传输大小
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能 DMA1 时钟
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; // 设置外设基地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度为字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 使能外设地址自增
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; // 设置存储器基地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 存储器数据宽度为字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 使能存储器地址自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 设置数据传输方向为外设到存储器
DMA_InitStructure.DMA_BufferSize = size; // 设置传输数据数目
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 设置为单次模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 使能存储器到存储器模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 设置优先级为中等
DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 初始化 DMA1 的通道1
}
void UserDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, ENABLE); // 使能 DMA1 的通道1
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); // 等待传输完成标志位置位
DMA_ClearFlag(DMA1_FLAG_TC1); // 清除传输完成标志位
DMA_Cmd(DMA1_Channel1, DISABLE); // 失能 DMA1 的通道1
DMA_SetCurrDataCounter(DMA1_Channel1, transfer_size); // 设置当前数据传输数目
}
注意: 传给初始化函数 UserDMA_Init
的前两个参数只是表示地址的整形数,并不是真的地址,因为 C 语言中使用指针变量保存地址的,如果直接传地址,会因为类型不一致,而产生警告。比如,想将数组 DataA 的数据转运到数组 DataB 中,那么应该传递的参数是 (uint32_t)DataA
和 (uint32_t)DataB
,而不是直接传 DataA 和 DataB。
参考视频源于B站up主: 野火科技、江协科技
参考文档:《STM32库开发实战指南——基于野火MINI开发板》
文章浏览阅读794次,点赞2次,收藏5次。XFRM框架_linux的xfrm框架
文章浏览阅读774次。DedeCMS常用标签讲解笔记整理 今天我们主要将模板相关内容,在前面的几节课中已经基本介绍过模板标签的相关内容,大家可以下载天工开物老师的讲课记录:http://bbs.dedecms.com/132951.html,这次课程我们主要讲解模板具体的标签使用,并且结合一些实例来介绍这些标签。 先前课程介绍了,网站的模板就如同一件衣服,衣服的好坏直接决定了网站的好坏,很多网站一看界面_织梦中什么页面用什么标签教学
文章浏览阅读2.5k次,点赞2次,收藏15次。编译是大部分工程师的烦恼,大家普遍喜欢去写业务代码。但我觉得基本的编译流程,我们还是需要掌握的,希望遇到相关问题,不要退缩,尝试去解决。天下文章一大抄,百度能解决我们90%的问题。_gdb编译
文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬
文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim
文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp
文章浏览阅读472次。点击上方 "程序员小乐"关注,星标或置顶一起成长后台回复“大礼包”有惊喜礼包!关注订阅号「程序员小乐」,收看更多精彩内容每日英文Sometimes you play a..._挑战安卓和ios!华为官宣鸿蒙手机版,p40搭载演示曝光!高管表态:我们准备好了
文章浏览阅读3.8w次,点赞107次,收藏993次。点击上方“Python爬虫与数据挖掘”,进行关注回复“书籍”即可获赠Python从入门到进阶共10本电子书今日鸡汤昔闻洞庭水,今上岳阳楼。大家好,我是小F。Python是目前最好的编程语言之一。由于其可读性和对初学者的友好性,已被广泛使用。那么要想学会并掌握Python,可以实战的练习项目是必不可少的。接下来,我将给大家介绍20个非常实用的Python项目,帮助大家更好的..._python项目
文章浏览阅读1.3k次。在网站的导航资源里看到了一个非常好用的东西:Android Asset Studio,可以在线生成各种图标。之前一直在用一个叫做Android Icon Creator的插件,可以直接在Android Studio的插件里搜索,这个工具的优点是可以生成适应各种分辨率的一套图标,有好几种风格的图标资源,遗憾的是虽然有很多套图标风格,毕竟是有限的。Android Asset Studio可以自己选择其..._在线 android 图标
文章浏览阅读514次。无限轮播广告位没有录屏,将就将就着看,效果就是这样主要代码KsBanner.java/** * 广告位 * * Created by on 2016/12/20. */public class KsBanner extends FrameLayout implements ViewPager.OnPageChangeListener { private List
文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪
文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码