打印
[其他ST产品]

STM32F1常用外设介绍

[复制链接]
楼主: 范德萨发额
手机看帖
扫描二维码
随时随地手机跟帖
201
范德萨发额|  楼主 | 2023-2-28 20:49 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
DMA的数据转运可以从外设到存储器,也可以是从存储器到外设,也可以从存储器转运到存储器,外设和存储器两个站点,都有3个参数,第一个是起始地址,有外设端的起始地址,和存储器端的起始地址,这两个参数决定了数据时从哪里来,到哪里去的,第二个参数是数据宽度,这个参数的作用是,指定一次转运要按多大的数据宽度来进行,可以选择字节Byte、半字节HalfWord和字Word每,字节就是8位转运一个uint8_t,半字节是16位uint16_t,字是32位uint32_t,例如ADC的数据,ADC的数据是uint16_t,所以参数就要选择半字节,依次转运一个uint16_t,第三个参数是地址是否自增,这个参数的作用是,指定一次转运完成后,下一次转运,是不是要把地址移动到下一个位置去,相当于是指针p++,比如ADC扫描模式,用DMA转运数据,外设地址是ADC_DR寄存器,寄存器这边,显然地址是不用自增的,如果自增下一次转运就跑到别的寄存器那里了,存储器这边地址就需要自增,每转运一个数据后,就往后挪个坑,要不然下次再转就把上次的覆盖掉了,这就是地址是否自增的作用,就是指定是否转运一次就挪个坑。

使用特权

评论回复
202
范德萨发额|  楼主 | 2023-2-28 20:49 | 只看该作者
传输存储器:用来指定,总共转运几次,这个传输计数器是个自减计数器,比如写个5,那DMA就只能进行5次数据转运,转运过程中,每转运一次计数器的值就会减1,当传输计数器减到0之后,DMA就不会再进行数据转运了,减到0之后之前自增的地址,也会恢复到起始地址的位置,以方便之后DMA新一轮的转运。传输计数器的右边的自动重装器的作用就是,传输计数器减到0之后,是否要自动恢复到最初的值。比如传输计数器给5,如果不使用自动红装器,那转运5次后,DMA就结束了,如果使用自动重装器,那转运5次,计数器减到0后,就会立即重装到初始值5,自动重装器决定了转运的模式,如果不重装,就是正常的单次模式,如果重装就是循环模式,如果你想转运一个数组,那一般是单次模式,转运一轮就结束了,如果是ADC扫描模式+连续转换那为了配合ADC,DMA也需要使用循环模式,这个循环模式和ADC的连续模式差不多。

使用特权

评论回复
203
范德萨发额|  楼主 | 2023-2-28 20:49 | 只看该作者
DMA的触发控制,触发就是决定DMA在什么时机进行转运的,触发源,有硬件触发,和软件触发,具体选择由M2M(Memory to Memory )这个参数决定,当给M2M位1时,DMA就会选择软件触发,这个软件触发不是调用某个函数一次就触发一次,而是,以最快的速度,连续不断地出发DMA,指一直到传输计数器清0,软件触发和循环模式不能同时用,因为软件触发是想把传输计数器清零,循环模式是清零后自动重装,如果同时用,那DMA就停不下了,软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动不需要时机,当M2M位给0,那就是使用硬件触发了,硬件触发源可以选择ADC、串口、定时器等等,使用硬件触发的转运一般是与外设有关的转运,这些转运需要一定的时机,比如ADC转换完成、串口收到数据、定时时间到等等,当硬件达到这些时机时,传一个信号过来,来触发DMA进行转运。

当给DMA使能后,DMA就准备就绪,可以进行转运了。

使用特权

评论回复
204
范德萨发额|  楼主 | 2023-2-28 20:49 | 只看该作者
DMA进行转运的条件:第一,开关控制,DMA_Cmd必须使能,第二,传输计数器必须大于0,第三,触发源,必须有触发信号,触发一次,转运一次,传输计数器自减一次,当传输计数器等于0,且没有自动重装时,无论是否触发,DMA都不会再进行转运了,此时需要DMA_Cmd,给DISABLE,关闭DMA,再为传输计数器写入一个大于0的数,再DMA_Cmd,给ENABLE,开启DMA,DMA才能继续工作,写传输计数器时,必须要先关闭DMA,再进行,不能在DMA开启时,写传输计数器。

使用特权

评论回复
205
范德萨发额|  楼主 | 2023-2-28 20:50 | 只看该作者
DMA请求

此图是DMA1的请求映像,下面是DMA的7个通道,每个通道都有一个数据选择器,可以选择一年触发和软件触发,左边的硬件触发源,每个通道的硬件触发源都是不同的,如果想选择ADC1来触发必须选择通道1,如果想选择TIM2的更新事件来触发的话,那就必须选择通道2,每个通道的硬件触发源都不同,如果想使用某个硬件触发源的话,就必须使用它所在的通道。如果使用软件触发那通道就可以任意选择。如果要使用ADC1,那就有个库函数ADC_DMACmd,必须使用这个库函数开启ADC1的这一路输出,它才有效,如果想要选择定时器2的通道3那也会有个TIM_DMACmd函数,用来进行DMA输出控制,触发源具体选择哪个,取决于你把哪个外设的DMA输出开启了,如果都开启了,那是一个或门,理论上三个硬件都可以触发,一般情况下,都是开启其中一个,这7个触发源,进入到仲裁器,进行优先级判断,最终产生内部的DMA1请求,默认优先级是通道号越小,优先级越高,也可以在程序中配置优先级

使用特权

评论回复
206
范德萨发额|  楼主 | 2023-2-28 20:50 | 只看该作者
数据宽度与对齐

使用特权

评论回复
207
范德萨发额|  楼主 | 2023-2-28 20:51 | 只看该作者
第一列是源端宽度,第二列是目标宽度,第三列是传输数目,当源端宽度和目标宽度都是8位时,转运第一步在源端的0位置,读数据B0,在目标的0位置,写数据B0,之后就是把B1,从左边挪到右边,接着B2、B3,这是源端和目标都是8位的情况,操作也很正常,继续就是源端是8位,目标是16位,它的操作就是,在源端读B0,在目标写00B0,之后读B1写00B1,等等,意思就是如果目标宽度,比源端的数据宽度大那就在目标数据前面多出来的空位补0,之后8位转运到32位,也是一样的处理,前面空出来的都补0,当目标数据宽度,比源端数据宽度小时,比如由16位转到8位现象就是,读B1B0,只写入B0,读B3B2,只写入B2,把多出来的高位舍弃掉,意思就是如果你把小的数据转到大的里面,高位就会补0,如果把大的数据转到小的里面去,高位就会舍弃掉,如果数据宽度一样,那就没事。

使用特权

评论回复
208
范德萨发额|  楼主 | 2023-2-28 20:51 | 只看该作者
数据转运+DMA



将SRAM中的数组DataA,转运到另一个数组DataB中,参数配置:外设地址是DataA数组的首地址,存储器地址,给DataB数组的首地址,数据宽度,两个数组的类型都是uint8_t,所以数据宽度都是按8位的字节传输,两个站点的地址都自增,转运完成后DataB数组的所有数据。就会等于DataA数组。如果左边不自增,右边自增,,转运完成后,DataB的所有数据都会等于DataA[0],如果左边自增,右边不自增,DataB[0]等于DataA的最后一个数,DataB其他的数不变,如果左右都不自增,那就是DataA[0]转到DataB[0],其他的数据不变。方向参数,是外设站点转运到存储器站点。传输计数器给7,不需要自动重装,触发选择部分选择软件触发,最后调用DMA_Cmd,给DMA使能,转运7次后,传输计数器自减到0,DMA停止,转运完成,这里的数据转运是一种复制转运,转运完成后的DataA的数据并不会消失。

使用特权

评论回复
209
范德萨发额|  楼主 | 2023-2-28 20:52 | 只看该作者
ADC扫描模式+DMA


左边是ADC扫描模式的转运流程,触发一次,7个通道依次进行AD转换,然后把转换结果都放在ADC_DR寄存器里面,在每个单独的通道转换完成后,进行一次DMA数据转运,并且目的地址进行自增,防止数据被覆盖,DMA的配置,外设地址,写入ADC_DR这个寄存器的地址,存储器的地址,可以在SRAM中定义一个数组ADValue然后把ADValue的地址当做存储器的地址,之后数据宽度,因为ADC_DR和SRAM数组需要uint16_t的数据,所以数据宽度都是16位的半字传输,外设地址不自增,存储器地址自增,传输方向,是外设站点到存储器站点,传输计数器和通道数一样,通道有7个,所以计数7次,计数器知否重装,看ADC的配置,ADC如果是单次扫描,那DMA的传输计数器可以不自动重装,转换一轮就停止,如果ADC是连续扫描,那DMA就可以选择使用自动重装,在ADC启动下一轮的转换的时候,DMA也启动下一轮的转运,ADC和DMA同步工作,触发选择ADC的硬件触发,ADC扫描模式在单个通道完成转换后,不会置任何标志位,也不会产生中断,但是会产生DMA请求,去触发DMA转运。一般来说DMA最常用的用途就是配合ADC的扫描模式,来解决ADC固有的缺陷,数据覆盖的问题。

使用特权

评论回复
210
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
初始化DMA步骤:

第一步,RCC开启DMA的时钟,AHB总线的设别

第二步,直接调用DMA_Init,初始化配置的参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级

第三步,DMA_Cmd给指定通道使能,如果使用的是硬件触发,要在对应外设调用XXX_DMACmd,开启一下触发信号的输出,需要DMA的中断,就调用DMA_ITConfig,开启中断输出,再在NVIC中配置相应的中断通道,然后写中断函数就行了,如果传输计数器清0,再想给传输计数器赋值,就DMA失能、写传输计数器、DMA使能,就可以了

使用特权

评论回复
211
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
初始化DMA步骤:

第一步,RCC开启DMA的时钟,AHB总线的设别

第二步,直接调用DMA_Init,初始化配置的参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级

第三步,DMA_Cmd给指定通道使能,如果使用的是硬件触发,要在对应外设调用XXX_DMACmd,开启一下触发信号的输出,需要DMA的中断,就调用DMA_ITConfig,开启中断输出,再在NVIC中配置相应的中断通道,然后写中断函数就行了,如果传输计数器清0,再想给传输计数器赋值,就DMA失能、写传输计数器、DMA使能,就可以了

使用特权

评论回复
212
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
DMA的库函数
恢复缺省配置

void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);

使用特权

评论回复
213
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
初始化

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

使用特权

评论回复
214
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
结构体初始化

void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);

使用特权

评论回复
215
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
使能

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

使用特权

评论回复
216
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
中断输出使能

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

使用特权

评论回复
217
范德萨发额|  楼主 | 2023-2-28 21:00 | 只看该作者
DMA_设置当前数据寄存器
给传输计数器写数据的

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);

使用特权

评论回复
218
范德萨发额|  楼主 | 2023-2-28 21:01 | 只看该作者
DMA获取当前数据寄存器
返回传输计数器的值

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

使用特权

评论回复
219
范德萨发额|  楼主 | 2023-2-28 21:01 | 只看该作者
获取标志位状态

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

使用特权

评论回复
220
范德萨发额|  楼主 | 2023-2-28 21:01 | 只看该作者
清除标志位状态

void DMA_ClearFlag(uint32_t DMAy_FLAG);

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则