DMA
存儲器到存儲器一般使用軟件觸發,外設到存儲器用硬件觸發(特定硬件)
(資料圖片僅供參考)
存儲器映像
運行從主閃存Flash中開始
選項字節:刷新程序時可以保持不變,存的主要是同Flash的讀保護、寫保護,看門狗等
內核外設:NVIC 和 SysTick
DMA框圖
總線矩陣左邊是主動單元,右邊是被動單元
DCode總線專門訪問Flash(CPU或DMA直接訪問的話一般是只讀的,但可以通過配置其接口控制器進行寫入【對FLASH按頁進行擦除,再寫入】),系統總線訪問其他
DMA每個通道都可以設置他們的源地址和目的地址,仲裁器根據優先級決定哪個通道使用唯一的一條DMA總線
總線上也有一個仲裁器,如果DMA和CPU都要訪問同一個目標,那么DMA就會暫停CPU的訪問。以防沖突,不過總線仲裁器。仍然會保證CPU得到一半的總線帶寬,使CPU正常工作
AHB從設備,是DMA自身的寄存器,這樣就可以被CPU配置,既是主動單元也是被動單元;DMA的硬件觸發源可觸發DMA,比如APB2里面的外設數據準備完成就觸發DMA執行數據轉運
數據寬度分為8(Byte) 16(HalfWord) 32位(Word)
外設寄存器(只是個名字,這里面的配置可以是外設也可以是存儲器【flash SRAM】) 反正兩個地址轉運數據 怎么設置都行
傳輸計數器:自減 (寫幾就轉運計次,為0時就不再轉運,而且自增的地址也會恢復到起始位置)
自動重裝器:計數器值到0后是否恢復到最初設置給計數器的值(比如計數器從5計數到0,是結束呢還是重新讓他從5再次計數)
M2M 存儲器到存儲器,
給M2M位置1時,選擇軟件觸發(以最快的速度,連續不斷觸發DMA,將計數值清零,完成一輪轉換),不能和循環模式一起用,一般用在存儲器到存儲器
0硬件觸發,源可以選擇ADC 定時器 串口等,與外設有關,這些轉運需要一定的時機
三個條件:CMD使能,計數器大于0,有觸發條件。 當計數器等于0且沒有自動重裝時,無論是都觸發都不再轉運,此時需要CMD關閉DMA,再給計數器寫個值,再次啟動DMA
每個通道的硬件觸發源都不一樣(特定的硬件觸發),軟件觸發的話都一樣可任意選擇
自動重裝與軟件觸發不能同時使用
#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_Medium; DMA_Init(DMA1_Channel1, &DMA_InitStructure); //選擇DMAx的Channelx 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);}View Code
DMA轉運的三個條件:計數器不為0,觸發源有觸發信號(軟件觸發或硬件觸發),使能
DMA單次運轉:RCCDMA_Init DMA使能
DMA連續運轉:在單次的基礎上:DMA失能,重新設置DMA計數器,使能DMA,等待轉運完成后清除其標志位(為了下次轉運)
若使用ADC1 則有個庫函數 ADC1_DMACmd() 用來開啟ADC1的這一路觸發源 (開啟某個外設的DMA輸出)
通道號越小,優先級越高,也可以自定義配置
數據寬度與對其
轉運雙方的數據寬度一樣的話就正常轉運,不一樣的話就看下表(不夠就補0,超了就舍棄高位)
案例:復制轉運,數組數據轉運(左自增右不自增的時候)
ADC掃描+DMA (外設地址不自增,存儲器地址自增)
在每個單獨的通道轉換完成后,把其產生在ADC_DR(外設地址)里面的值轉運到DMA目的地(存儲器地址【可在SRAM中定義一個數組暫存】),并且目的地址自增
觸發選擇:DMA轉運的時機。需要和ADC單個通道轉換完成同步,所以選擇ADC的硬件觸發
單個通道轉換完成后沒有任何標志位可供查詢,但是會觸發單通道的DMA請求
這里采用4個ADC通道,依次轉換,結果會存在同一個DR寄存器中,然后由DMA及時轉運出去(源地址固定,目的地遞增)避免數據覆蓋
步驟: 開啟DMA ADC和對應的GPIO時鐘并配置CLK,初始化4個GPIO口,選擇ADC4個通道,ADC初始化,DMA初始化(注意配置時候的硬件關系對照),使能
ADC和DMA都是單次的時候需要每次觸發
ADC_InitStructure.ADC_ContinuousConvMode = DISABLEDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
當調用這個函數,ADC開始轉換,連續掃描4個通道,數值依次寫到DR寄存器中,DMA在數據覆蓋之前會將每次的數據及時轉運出去,目的地自增
都是循環模式的話就自動不用管
uint16_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);//點菜,通道0放在序列1的位置 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_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//連續轉換 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //開啟掃描模式(從序列1-序列4) ADC_InitStructure.ADC_NbrOfChannel = 4;//前4個序列有效 ADC_Init(ADC1, &ADC_InitStructure); DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//要DR寄存器低16位的數據 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_Circular;//可一般模式,可循環 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //不使用軟件觸發,使用硬件ADC觸發 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // ADC1硬件固定對應DMA1的通道1 DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE);//開啟ADC到DMA的輸出 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); //校準ADC while (ADC_GetResetCalibrationStatus(ADC1) == SET); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); ADC_SoftwareStartConvCmd(ADC1, ENABLE);//軟件觸發ADC開始連續轉換,DMA也連續轉運}View Code
此外,還可以加個定時器,定時器觸發ADC,ADC觸發DMA (硬件自動化)
TODU: 串口數據,使用DMA進行存儲器到外設的轉運
位段:用來單獨操作寄存器或SRAM的某一位,給他新編一個地址(在新開辟的地址區域,有大把空閑地址)
在程序中的臨時變量放在RAM區,地址以20開頭;使用const修飾的常量放在flash區內,地址以08開頭(常用來修飾不變的數據)
SMT32中利用結構體來訪問寄存器,結構體內的成員順序(內存)與各個寄存器的地址(內存)一一對應,比如指定結構體A的起始地址為ADC1外設寄存器的起始地址,訪問結構體成員就相當于訪問外設的某個寄存器; &ADC1->DR ADC1的結構體指針指向的是ADC1外設的起始地址,訪問結構體成員就相當于加一個偏移,這樣也是可以訪問對應的寄存器
需要DMA的中斷就調用DMA_ITConfig,配置NVIC
關鍵詞: