网站设计怎么做超链接,wordpress+企业库插件,骏域网站,网站建设行业地位1.DMA简介 外设一般指的是数据寄存器DR#xff08; Data Register#xff09;#xff0c;比如ADC的数据寄存器#xff0c;串口的数据寄存器等等#xff0c;这里存储器指的就是运行内存SRAM和程序存储器flash#xff0c;是我们存储变量数组和程序代码的地方#xff0c;在…1.DMA简介 外设一般指的是数据寄存器DR Data Register比如ADC的数据寄存器串口的数据寄存器等等这里存储器指的就是运行内存SRAM和程序存储器flash是我们存储变量数组和程序代码的地方在外设和存储器或者存储器和存储器之间进行数据转运就可以使用DMA来完成并且在转运的过程中无需CPU的参与节省了CPU的资源 如果执行的是存储器到存储器的转运比如我们想把flash里的一批数据转运到SRAM里去那就需要软件触发使用软件触发之后DMA就会迅速地把这批数据部转运完成如果DMA进行的是外设到存储器的数据转运就不能一股脑的转运了因为外设的数据是有一定时机的所以这时我们就需要用硬件触发比如转运ADC的数据那就得ADC每个通道AD转换完成后硬件触发一次DMA之后DMA再转运触发一次转运一次。总之就是存储器到存储器的数据转运我们一般使用软件触发外设到存储器的数据转运我们一般使用硬件触发
2.存储器映像 ROM就是只读存储器是一种非易失性、掉电不丢失的存储器 RAM是随机存储器可读可写是一种易失性、掉电丢失的存储器 3.DMA框图 仲裁器虽然多个通道可以独立转运数据但是最终DMA总线只有一条所以所有的通道都只能分时复用这一条DMA总线。如果产生了冲突那就会由仲裁器根据通道的优先级来决定谁先使用和后使用。另外在总线矩阵这里也会有个仲裁器如果DMA和CPU都要访问同一个目标那么DMA就会暂停CPU的访问以防止冲突。不过总线仲裁器仍然会保证CPU得到一半的总线带宽使CPU也能正常的工作 下面这里是AHB从设备也就是DMA自身的寄存器因为DMA作为一个外设它自己也会有相应的配置寄存器这里连接在了总线右边的AHB总线上所以DMA既是总线矩阵的主动单元可以读写各种存储器也是AHB总线上的被动单元。CPU通过这一条线路就可以对DMA进行配置了 接着继续看这里是DMA请求请求就是触发的意思这条线路右边的触发源是各个外设所以这个DMA请求就是DMA的硬件触发源。比如ADC转换完成、串口接收到数据需要触发DMA转运数据的时候就会通过这条线路向DMA发出硬件触发信号之后DMA就可以执行数据转运的工作了。这就是DMA请求的作用。 注意右上角的Flash是ROM只读存储器的一种如果通过总线直接访问的话无论是CPU还是DMA都是只读的只能读取数据而不能写入如果你DMA的目的地址填了Flash的区域那转运时就会出错。然后SRAM是运行内存可以任意读写。
4.DMA基本结构 第一个是起始地址有外设端的起始地址和存储器端的起始地址这两个参数决定了数据是从哪里来到哪里去的之后第二个参数是数据宽度这个参数的作用是指定一次转运要按多大的数据宽度来进行他可以选择字节byte、半字HalfWord和字word字节是8位就是一次转运一个uint8_t这么大的数据半字是16位就是一次转运一个uint16_t这么大的数据字是32位就是一次转运unit32_t这么大的数据。比如转运ADC的数据ADC的结果是unit16_t这么大所以这个参数就要选择半字一次转运一个unit16_t然后第三个参数是地址是否自增这个参数的作用是指定一次转移完成后下一次转运是不是要把地址移动到下一个位置去这就相当于是指针p这个意思比如ADC扫描模式用DMA进行数据转运外设地址是ADC_DR寄存器寄存器这边显然地址是不用指针的如果自增那下一次转运就跑到别的寄存器那里去了存储器这边地址就需要指针每转运一个数据后就往后挪个坑要不然下次再转就把上次的覆盖掉了这就是地址是否自增的作用就是指定是不是要转运一次挪个坑这个意思这就是外设站点和存储基站点各自的三个参数了。 传输计数器是用来指定我总共需要转运几次的这个传输计数器是一个自减计数器比如你给他写个5那DMA就只能进行5次数据转运转运过程中每转运一次计数器的数就会减一当传输计数器减到零之后DMA就不会再进行数据转运了另外它减到零之后之前自增的地址也会恢复到起始地址的位置以方便之后DMA开始新一轮的转换在传输计数器的右边有一个自动重装器这个自动重装器的作用就是传输计数器减到零之后是否要自动恢复到最初的值比如最初传输计数器给5如果不使用自动重装器那转运5次后DMA就结束了如果使用自动重装器那转运5次计数器减到零后就会立即重装到初始值5这个就是自动重装器它决定了转运的模式如果不重装就是正常的单次模式如果重装就是循环模式比如如果你想转运一个数组那一般就是单次模式转运一轮就结束了如果是ADC扫描模式加连续转换那为了配合ADCDMA也需要使用循环模式所以这个循环模式和ADC的连续模式差不多都是指定一轮工作完成后是不是立即开始下一轮工作。 触发就是决定DMA需要在什么时机进行转运的。触发源有硬件触发和软件触发具体选择哪个由M2M这个参数决定M2M就是memory to memory因为2的英文two和to同音所以M2M就是m to m存储器到存储器的意思当我们给M2M位1时DMA就会选择软件触发这个软件触发并不是调用某个函数一次触发一次这个软件触发的执行逻辑是以最快的速度连续不断地触发DMA争取早日把传输计数器清零完成这一轮的转换所以这里的软件触发和我们之前外部中断和ADC的软件出发可能不太一样你可以把它理解成连续触发那这个软件触发和循环模式不能同时用因为软件触发就是想把传输计数器清零循环模式是清零后自动重装如果同时用的话那DMA就停不下来了这就是软件触发软件触发一般适用于存储器到存储器的转运因为存储器到存储器的转运是软件启动不需要时机并且想尽快完成的任务所以上面这里M2M位给1就是软件触发就是应用在存储器到存储器转运的情况M2M位给0那就是使用硬件触发硬件触发源可以选择ADC、串口、定时器等等使用硬件触发的转运一般都是与外设有关的转运这些转运需要一定的时机比如ADC转换完成、串口收到数据、定时时间到等等所以需要使用硬件触发在硬件达到这些时机时传个信号过来来触发DMA进行转运这就是硬件触发。 DMA进行转运有几个条件第一就是开关控制DMA_Cmd必须使能第二就是传输计数器必须大于0第三就是触发源必须有触发信号触发一次转运一次传输计数器自减一次当传输计数器等于0且没有自动重装时这时无论是否触发DMA都不会再进行转运了此时就需要DMA_CMD给DISABLE关闭DMA再为传输计数器写一个大于0的数再DMA_Cmd给ENABLE开启DMADMA才能继续工作注意一下写传输计数器时必须要先关闭DMA再进行不能在DMA开启时写传输计数器这是手册里的规定。 选择硬件触发 需要在对应外设调用XXX_MDACmd 开启触发信号的输出 需要DMA中断 调用DMA_ITConfig 开启中断输出 再在NVIC里配置相应的中断通道 写入中断函数即可
5.DMA请求 EN位是开关控制EN等于0时不工作EN等于1时工作。每个通道的硬件触发源都是不同的如果你需要用ADC1来触发的话那就必须选择通道一如果需要定时器二的更新事件来触发的话那就必须选择通道二
6.数据宽度与对齐 7.数据转运DMA 首先是外设站点和存储器站点的起始地址、数据宽度、地址是否自增这三个参数。那在这个任务里外设地址显然应该填DataA数组的首地址存储器地址给DataB数组的首地址然后数据宽度两个数组的类型都是uint8_t所以数据宽度都是按8位的字节传输。之后地址是否自增在中间可以看到我们想要的效果是DataA[]转到DataB[]DataA[1]转DataB[1]等等。所以转运完DataA[0]和DataB[0]之后两个站点的地址都应该自增都移动到下一个数据的位置继续转运DataA[1]和DataB[1]这样来进行。
8.ADC扫描模式DMA 左边是ADC扫描模式的执行流程在这里有7个通道触发一次后7个通道依次进行AD转换然后转换结果都放到ADC_DR数据寄存器里面。那我们要做的就是在每个单独的通道转换完成后进行一个DMA数据转运并且目的地址进行自增这样数据就不会被覆盖了。所以在这里DMA的配置就是外设地址写入ADC_DR这个寄存器的地址存储器的地址可以在SRAM中定义一个数组ADValue然后把ADValue的地址当做存储器的地址。
9.相关API
9.1 DMA_Init
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
功能:根据 DMA_InitStruct 中指定的参数初始化 DMA 的通道 x 寄存器
参数:DMA Channelxx 可以是 12…或者 7 来选择 DMA 通道 xDMA_InitStruct指向结构 DMA_InitTypeDef 的指针包含了 DMA 通道 x 的配置信息
返回值:无 9.2 DMA_Cmd
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
功能:使能或者失能指定的通道 x
参数:DMA Channelxx 可以是 12…或者 7 来选择 DMA 通道 xNewStateDMA 通道 x 的新状态 这个参数可以取ENABLE 或者 DISABLE
返回值:无 9.3 DMA_SetCurrDataCounter
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
功能:设置DMA转换数据个数
参数:DMA Channelxx 可以是 12…或者 7 来选择 DMA 通道 xDataNumber数据个数
返回值:无
9.4 DMA_GetFlagStatus
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
功能:检查指定的 DMA 通道 x 标志位设置与否
参数:DMA_FLAG待检查的 DMA 标志位
返回值:DMA_FLAG 的新状态SET 或者 RESET 9.5 DMA_ClearFlag
void DMA_ClearFlag(uint32_t DMAy_FLAG);
功能:清除 DMA 通道 x 待处理标志位
参数:DMA_FLAG待清除的 DMA 标志位使用操作符“|”可以同时选中多个DMA 标志位
返回值:无 10.DMA数据转运
10.1 接线图 10.2 程序调试
//存储器映像的调试
const uint16_t aa 0x66;//const是C语言的关键字 为定义一个常量 常量就无法改变 对应Flash主闪存可读不可写//若这里没加const 在第13行代码执行完OLED显示的是2000开头的 即运行内存SRAM//这里加了const 在第13行代码执行完OLED显示的是0800开头的 即程序存储器FlashOLED_ShowHexNum(1,1,aa,2);
OLED_ShowHexNum(2,1,(uint32_t)aa,8);
寄存器的实际地址 起始地址 偏移
起始地址表 偏移表 10.3 相关代码
MyDMA.c
#include stm32f10x.h // Device headeruint16_t MyDMA_Size;//定义全局变量用于记住Init函数的Size供Transfer函数使用void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{MyDMA_Size Size;/*开启时钟*/RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//开启DMA的时钟 DMA是AHB的总线设备 要用AHB开启时钟的函数 开启DMA的时钟//互联型是STM32F105/107的型号 本次使用的是F103 所以在下面参数表选DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr AddrA;//外设基地址给定形参AddrADMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte;//外设数据宽度选择字节(uint8_t)DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Enable;//外设地址自增选择使能DMA_InitStructure.DMA_MemoryBaseAddr AddrB;//存储器基地址给定形参AddrBDMA_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;//指定传输计数器是否要自动重装 模式选择正常模式自减到0后停下来//注意自动重装与软件触发不能调试使用 若同时使用 则DMA不会停下来 会连续触发 DMA_InitStructure.DMA_M2M DMA_M2M_Enable;//存储器到存储器软件触发选择使能DMA_InitStructure.DMA_Priority DMA_Priority_Medium;//优先级选择中等DMA_Init(DMA1_Channel1,DMA_InitStructure);//将结构体变量交给DMA_Init配置DMA1的通道1/*DMA使能*/DMA_Cmd(DMA1_Channel1, DISABLE); //这里先不给使能初始化后不会立刻工作等后续调用Transfer后再开始
}void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1,DISABLE);//DMA失能在写入传输计数器之前需要DMA暂停工作DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//写入传输计数器指定将要转运的次数DMA_Cmd(DMA1_Channel1,ENABLE);//DMA使能开始工作while(DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET); //等待DMA工作完成DMA_ClearFlag(DMA1_FLAG_TC1);
}MyDMA.h
#ifndef __MYDME_H
#define __MYDMA_Hvoid MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size);
void MyDMA_Transfer(void);#endifmain.c
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include MyDMA.h/*存储器映像的调试
const uint16_t aa 0x66;//const是C语言的关键字 为定义一个常量 常量就无法改变 对应Flash主闪存可读不可写//若这里没加const 在第13行代码执行完OLED显示的是2000开头的 即运行内存SRAM//这里加了const 在第13行代码执行完OLED显示的是0800开头的 即程序存储器Flash
*/uint8_t DataA[] {0x01,0x02,0x03,0x04};
uint8_t DataB[] {0,0,0,0};int main(void)
{OLED_Init();// OLED_ShowHexNum(1,1,aa,2);
// OLED_ShowHexNum(2,1,(uint32_t)aa,8);
// OLED_ShowHexNum(2,1,(uint32_t)ADC1-DR,8);MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);//DMA初始化把源数组和目的数组的地址传入OLED_ShowString(1,1,DataA:);OLED_ShowString(3,1,DataB:);/*显示数组的首地址*/OLED_ShowHexNum(1,8,(uint32_t)DataA,8);OLED_ShowHexNum(3,8,(uint32_t)DataB,8);while (1){DataA[0];//变换测试数据DataA[1];DataA[2];DataA[3];OLED_ShowHexNum(2, 1, DataA[0], 2); //显示数组DataAOLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2); //显示数组DataBOLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000); //延时1s观察转运前的现象MyDMA_Transfer(); //使用DMA转运数组从DataA转运到DataBOLED_ShowHexNum(2, 1, DataA[0], 2); //显示数组DataAOLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2); //显示数组DataBOLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000); //延时1s观察转运后的现象}
}现象DataA数组中的数每隔1us自增一次 并将数据通过DMA数据传输到DataB DataB的数据也随之自增比同步
11.DMAAD多通道
11.1 接线图 11.2 相关代码
AD2.c
#include stm32f10x.h // Device headeruint16_t AD_Value[4]; //定义用于存放AD转换结果的全局数组/*** 函 数AD初始化* 参 数无* 返 回 值无*/
void AD_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1的时钟/*设置ADC时钟*/RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频ADCCLK 72MHz / 6 12MHz/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入/*规则组通道配置*/ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置配置为通道0ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置配置为通道1ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置配置为通道2ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置配置为通道3/*ADC初始化*/ADC_InitTypeDef ADC_InitStructure; //定义结构体变量ADC_InitStructure.ADC_Mode ADC_Mode_Independent; //模式选择独立模式即单独使用ADC1ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; //数据对齐选择右对齐ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; //外部触发使用软件触发不需要外部触发ADC_InitStructure.ADC_ContinuousConvMode ENABLE; //连续转换使能每转换一次规则组序列后立刻开始下一次转换ADC_InitStructure.ADC_ScanConvMode ENABLE; //扫描模式使能扫描规则组的序列扫描数量由ADC_NbrOfChannel确定ADC_InitStructure.ADC_NbrOfChannel 4; //通道数为4扫描规则组的前4个通道ADC_Init(ADC1, ADC_InitStructure); //将结构体变量交给ADC_Init配置ADC1/*DMA初始化*/DMA_InitTypeDef DMA_InitStructure; //定义结构体变量DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; //外设基地址给定形参AddrADMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; //外设数据宽度选择半字对应16为的ADC数据寄存器DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; //外设地址自增选择失能始终以ADC数据寄存器为源DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)AD_Value; //存储器基地址给定存放AD转换结果的全局数组AD_ValueDMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; //存储器数据宽度选择半字与源数据宽度对应DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; //存储器地址自增选择使能每次转运后数组移到下一个位置DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; //数据传输方向选择由外设到存储器ADC数据寄存器转到数组DMA_InitStructure.DMA_BufferSize 4; //转运的数据大小转运次数与ADC通道数一致DMA_InitStructure.DMA_Mode DMA_Mode_Circular; //模式选择循环模式与ADC的连续转换一致DMA_InitStructure.DMA_M2M DMA_M2M_Disable; //存储器到存储器选择失能数据由ADC外设触发转运到存储器DMA_InitStructure.DMA_Priority DMA_Priority_Medium; //优先级选择中等DMA_Init(DMA1_Channel1, DMA_InitStructure); //将结构体变量交给DMA_Init配置DMA1的通道1/*DMA和ADC使能*/DMA_Cmd(DMA1_Channel1, ENABLE); //DMA1的通道1使能ADC_DMACmd(ADC1, ENABLE); //ADC1触发DMA1的信号使能ADC_Cmd(ADC1, ENABLE); //ADC1使能/*ADC校准*/ADC_ResetCalibration(ADC1); //固定流程内部有电路会自动执行校准while (ADC_GetResetCalibrationStatus(ADC1) SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1) SET);/*ADC触发*/ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作由于ADC处于连续转换模式故触发一次后ADC就可以一直连续不断地工作
}AD2.h
#ifndef __AD_H
#define __AD_Hextern uint16_t AD_Value[4];void AD_Init(void);#endifmain.c
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include AD2.hint main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化AD_Init(); //AD初始化/*显示静态字符串*/OLED_ShowString(1, 1, AD0:);OLED_ShowString(2, 1, AD1:);OLED_ShowString(3, 1, AD2:);OLED_ShowString(4, 1, AD3:);while (1){OLED_ShowNum(1, 5, AD_Value[0], 4); //显示转换结果第0个数据OLED_ShowNum(2, 5, AD_Value[1], 4); //显示转换结果第1个数据OLED_ShowNum(3, 5, AD_Value[2], 4); //显示转换结果第2个数据OLED_ShowNum(4, 5, AD_Value[3], 4); //显示转换结果第3个数据Delay_ms(100); //延时100ms手动增加一些转换的间隔时间}
}现象与之前的AD多通道一样 上电之后光敏传感器、热敏传感器、反射式红外传感器以及电位器随着调试其数值会发生变化