网站上的动态背景怎么做的,福建移动网站设计,o2o网站开发价格,什么做网站做个多少钱啊参考视频#xff1a;[8-1] DMA直接存储器存取_哔哩哔哩_bilibili
DMA简介
DMA#xff08;Direct Memory Access#xff09;直接存储器存取
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输#xff0c;无须CPU干预#xff0c;节省了CPU的资源
12个独立可…参考视频[8-1] DMA直接存储器存取_哔哩哔哩_bilibili
DMA简介
DMADirect Memory Access直接存储器存取
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输无须CPU干预节省了CPU的资源
12个独立可配置的通道DMA17个通道DMA25个通道
每个通道都支持软件触发和特定的硬件触发 存储器到存储器的转运一般使用软件触发 外设到存储器的数据转运一般使用硬件触发 STM32F103C8T6 DMA资源DMA17个通道
存储器映像
ROM是只读存储器是一种非易失性、掉电不丢失的存储器。RAM是随机存储器是一种易失性、掉电丢失的存储器。 DMA框图
Cortex-M3内核包含了CPU和内核外设其余的都可以看成存储器。
Flash是主闪存SRAM是运行内存。各个外设可以看成是寄存器也是一种SRAM存储器。
寄存器可以看成是一种特殊的存储器 一方面CPU可以对寄存器进行读写。 另一方面寄存器的每一位背后都连接了一根导线这些导线可以用于控制外设电路的状态比如置高低电平、导通和断开开关、切换数据选择器等等。
所以寄存器是连接软件和硬件的桥梁。软件读写寄存器就当于在控制硬件的执行。
总线矩阵的左端是主动单元也就是拥有存储器的访问权右端的是被动单元它们的存储器只能被左边的主动单元读写。 DCode总线是专门访问Flash的系统总线是访问其他东西的。 由于DMA要转运数据DMA也必须要有访问的主动权。 DMA1、DMA2、以太网MAC都有一条DMA总线。 DMA1有七条通道DMA2有五条通道各个通道可以分别设置它们转运数据的源地址和目的地址。 仲裁器存在的原因虽然有多个通道可以独立转运数据但是最终DMA总线只有一条所以所有的通道都只能分时复用这一条DMA总线如果产生了冲突那就会由仲裁器根据通道的优先级来决定谁先用谁后用。 另外在总线矩阵这里也有仲裁器如果DMA和CPU都要访问同一个目标那么DMA就会暂停CPU的访问以防止冲突不过总线仲裁器仍然会保证CPU得到一半的总线带宽保证CPU也会正常的工作。 AHB从设备也就是DMA自身的寄存器。因为DMA作为一个外设它自己也会有相应的配置寄存器。AHB从设备连接在了总线右边的AHB总线上。
所以DMA既是总线矩阵的主动单元可以读写各种存储器也是AHB总线上的被动单元。
DMA请求也就是DMA触发线路右边的触发源是各个外设所以DMA请求就是DMA的硬件触发源比如ADC转换完成串口接收到数据。需要触发DMA转运数据的时候就会通过这条线路向DMA发出硬件触发信号之后DMA就可以执行数据转运的工作了。 DMA基本结构
方向参数是用来控制从外设到存储器还是从存储器到外设。另外还有一种转运方式就是存储器到存储器。由于Flash是只读的所以只能进行从SRAM到SRAM或者Flash到SRAM。
外设和存储器两个站点都有三个参数 起始地址有外设端的起始地址存储器端的起始地址。这两个参数决定了数据是从哪里来到哪里去的。 数据宽度指定一次转运要按多大的数据宽度来进行可以选择字节、半字和字。字节是8位半字是16位字是32位。 地址是否自增指定一次转运完成后下一次转运是不是要把地址移动到下一个位置去。 传输计数器这个是用来指定总共需要转运几次是一个自减计数器。当减到0之后之前自增的地址也会恢复到起始地址的位置。
自动重装器传输计数器减到0之后是否要自动恢复到最初的值。不重装就是单次模式重装就是循环模式。
触发就是决定DMA需要在什么时机进行转运的。触发源有硬件触发和软件触发具体选择哪个由M2M来决定。 给M2M位1时DMA就会选择软件触发软件触发是指以最快的速度连续不断地触发DMA争取早日把传输计数器清零完成这一轮的转换这里的软件触发和之前的ADC和外部中断的软件触发不太一样。软件触发和循环模式不能同时用。软件触发一般是存储器到存储器的转运。 硬件触发源可以选择ADC、串口、定时器等等。硬件触发源的转运一般都是与外设有关的转运这些转运需要一定的时机比如ADC转换完成、串口收到数据、定时时间到等等在硬件达到这些时机时传一个信号过来来触发DMA进行转运。 开关控制DMA_Cmd函数使能之后就可以进行DMA转运了。
DMA进行转运的几个条件 1. 开关控制DMA_Cmd必须使能。 2. 传输计数器必须大于0。 3. 触发源必须有触发信号。 触发一次转运一次传输计数器自减一次当传输计数器等于0且没有自动重装时这时无论是否触发DMA都不会再进行DMA转运了。此时就需要DMA_Cmd给DISABLE关闭DMA再为传输计数器写一个大于0的数再DMA_Cmd给ENABLE开启DMADMA才能继续工作。注意一下写传输计数器时必须要先关闭DMA再进行。 DMA1请求映像
DMA1的七个通道每个通道都有一个数据选择器可以选择硬件触发或者软件触发。EN是开关控制M2M位是数据选择器的控制位用于选择是硬件触发还是软件触发。
外设请求信号那里每个通道的硬件触发源都是不同的如果需要用ADC1来触发的话就必须选择通道1如果需要定时器2的更新事件来触发的话就必须选择通道2。如果用软件触发就可以任意选择通道了。
通道1选择哪个硬件触发源是由对应的外设是否开启了DMA输出来决定的比如要使用DMA1那会有个库函数ADC_DMACmd必须使用这个库函数开启ADC1的这一路输出它才有效。如果都开启了理论上3个硬件都可以触发。
之后七个通道进入仲裁器进行优先级判断最终产生内部的DMA1请求。 数据宽度与对齐
此部分是为了解决外设寄存器和存储器的数据宽度不一致问题。
当目标宽度比源端宽度大就在目标数据前面多出来的空位补0。
当目标宽度比源端宽度小把源端多出来的高位舍弃掉。 数据转运DMA
首先是外设站点和存储器站点的起始地址、数据宽度、地址是否自增。 在这个例子里外设地址显然应该填DataA数组的首地址。存储器地址给DataB数组的首地址。 数据宽度两个数组的类型都是uint8_t所以数据宽度都是按8位的字节传输。 由下图可以看出两边地址都应该自增。 方向参数是外设站点转移到存储器站点。
显然要转运7次所以传输计数器给7自动重装暂时不需要。
这里选择软件触发因为是存储器到存储器的转运不需要等待硬件时机。
最后调用DMA_Cmd给DMA使能。 ADC扫描模式DMA
触发一次后七个通道依次进行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_DR的值是在ADC单个通道转换完成后才会有效所以DMA转运的时机需要和ADC单个通道转换完成同步。所以DMA的触发要选择ADC的硬件触发。ADC扫描模式在每个单独的通道转换完成后没有任何标志位也不会触发中断但是它会产生DMA请求去触发DMA转运。 DMA.h中的函数
// 恢复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
// 初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
// 结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
// 使能
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
// 中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
// DMA设置当前数据寄存器
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
// 给传输计数器写入数据之后DMA获取当前数据寄存器
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
// 获取标志位状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
// 清除标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);
// 获取中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
// 清除中断挂起位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);DMA数据转运
RCC开启DMA的时钟调用DMA_Init初始化各个参数进行开关控制DMA_Cmd
接线图 MyDMA.c
#include stm32f10x.h // Device headeruint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{MyDMA_Size Size;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);DMA_InitTypeDef DMA_InitStructure;// 外设站点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_VeryHigh;DMA_Init(DMA1_Channel1, DMA_InitStructure);DMA_Cmd(DMA1_Channel1, DISABLE);
}void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);DMA_Cmd(DMA1_Channel1, ENABLE);while (DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET);DMA_ClearFlag(DMA1_FLAG_TC1);
}main.c
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include MyDMA.huint8_t DataA[] {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] {0, 0, 0, 0};int main(void)
{OLED_Init();MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);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);OLED_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);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000);MyDMA_Transfer();OLED_ShowHexNum(2, 1, DataA[0], 2);OLED_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);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);Delay_ms(1000);}
}DMAAD多通道
接线图 AD.c
#include stm32f10x.h // Device headeruint16_t AD_Value[4];void AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);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);ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 非连续转换模式ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; // 对齐方式ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; // 触发方式ADC_InitStructure.ADC_Mode ADC_Mode_Independent;ADC_InitStructure.ADC_NbrOfChannel 4; // 通道数目ADC_InitStructure.ADC_ScanConvMode DISABLE; // 扫描转换模式ADC_Init(ADC1, ADC_InitStructure);DMA_InitTypeDef DMA_InitStructure;// 外设站点DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR;DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable;// 存储器DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)AD_Value;DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable;// 传输方向DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC;// 缓存区大小传输计数器DMA_InitStructure.DMA_BufferSize 4;// 传输模式是否使用自动重装DMA_InitStructure.DMA_Mode DMA_Mode_Normal;// 是否是存储器到存储器选择硬件触发还是软件触发DMA_InitStructure.DMA_M2M DMA_M2M_Disable;// 优先级DMA_InitStructure.DMA_Priority DMA_Priority_VeryHigh;DMA_Init(DMA1_Channel1, DMA_InitStructure);DMA_Cmd(DMA1_Channel1, ENABLE);ADC_DMACmd(ADC1, ENABLE);ADC_Cmd(ADC1, ENABLE);// 调用复位校准ADC_ResetCalibration(ADC1);// 等待复位校准完成该函数是返回复位校准的状态所以要等待复位校准完成的话要加一个while循环while (ADC_GetResetCalibrationStatus(ADC1) SET) ;// 开始校准ADC_StartCalibration(ADC1);// 等待校准完成while (ADC_GetCalibrationStatus(ADC1) SET);
}// 首先软件触发然后等待转换完成也就是等待EOC标志位置1最后读取ADC数据寄存器
void AD_GetValue(void)
{DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, 4);DMA_Cmd(DMA1_Channel1, ENABLE);ADC_SoftwareStartConvCmd(ADC1, ENABLE);while (DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET);DMA_ClearFlag(DMA1_FLAG_TC1);
}main.c
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include AD.hint main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, AD0:);OLED_ShowString(2, 1, AD1:);OLED_ShowString(3, 1, AD2:);OLED_ShowString(4, 1, AD3:);while(1){AD_GetValue();OLED_ShowNum(1, 5, AD_Value[0], 4);OLED_ShowNum(2, 5, AD_Value[1], 4);OLED_ShowNum(3, 5, AD_Value[2], 4);OLED_ShowNum(4, 5, AD_Value[3], 4);Delay_ms(100);}
}