一、DMA:
1.簡介:
DMA,直接存儲區存取
DMA可以提供外設和存儲器或存儲器與存儲器見的高速數據傳輸,無需CPU干預。
12個通道:DMA1(7個通道),DMA2(5個通道)
每個通道支持軟件觸發,和特定硬件觸發(每個外設有固定通道)。
本芯片只有DMA1。
2.存儲器映像:
3.功能圖:
外設寄存器與Flash和SRAM(存儲器)為兩大數據存儲地點。
轉運數據由兩個結構體決定,每個包含三個參數:
起始地址,數據寬度,地址是否自增(一次轉運完成后,指針是否挪到下個地址)。
(ps:這兩個結構體其實是完全相同的,并非外設結構體只能放外設地址等等)
傳輸計數器:觸發后,進行數據轉運的次數,在傳輸完成后,兩個結構體的指針回到初始地址。
自動重裝器:決定了是否循環傳輸,在傳輸完成后,傳輸寄存器恢復到自動重裝器的值,配合上面結構體指針回到初始地址,可以做到對對同一塊地址數據循環傳輸。
M2M:決定觸發邏輯,為1則軟件觸發,為0硬件觸發(MtoM名稱意義為存儲器到存儲器)
軟件觸發:與前面不同的是,并非由一個函數觸發,而是不斷連續觸發DMA,直到將傳輸計數器清零,完成傳輸,即使能DMA就自動開始轉運。(軟件觸發和循環模式不能同時使用)
硬件觸發:就是外設給出信號觸發
開關控制:給DMA使能,寫傳輸計數器時,需要將DMA關閉。
4.ADC與DMA:
前文介紹了ADC的功能,ADC有一個掃描模式,可以同時轉換多個通道的值,但在規則組中,卻只有一個寄存器存放結果,就需要DMA,在一次轉換完成后,就將寄存器的數據轉運到存儲器中,這樣就不會被下一個通道的結果覆蓋而丟失。
二、實戰:DMA數據轉運
DMA.c
#include "stm32f10x.h" // Device headeruint16_t MyDMA_Size; //定義全局變量,用于記住Init函數的Size,供Transfer函數使用/*** 函 數:DMA初始化* 參 數:AddrA 原數組的首地址* 參 數:AddrB 目的數組的首地址* 參 數:Size 轉運的數據大小(轉運次數),1代表字節* 返 回 值:無*/
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{MyDMA_Size = Size; //將Size寫入到全局變量,記住參數Size/*開啟時鐘*/RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //開啟DMA的時鐘/*DMA初始化*/DMA_InitTypeDef DMA_InitStructure; //定義結構體變量DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外設基地址,給定形參AddrADMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外設數據寬度,選擇字節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; //模式,選擇正常模式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后,再開始
}/*** 函 數:啟動DMA數據轉運* 參 數:無* 返 回 值:無*/
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); //清除工作完成標志位
}
三、實戰:DMA+AD多通道
問:如何訪問外設寄存器?
答:無需具體地址,可通過結構體來訪問,如想訪問ADC1的DR寄存器,直接ADC1->DR即可。
新AD.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(只能是通道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就可以一直連續不斷地工作
}