目錄
前言
?編輯
技術實現
?連線圖
代碼實現?
技術要點
實驗結果
問題記錄
前言
DMA(Direct Memory Access)直接存儲器存取,用來提供在外設和存儲器 之間或者存儲器和存儲器之間的高速數據傳輸。無需CPU干預,數據可以通過DMA快速地移動,這樣可以節省CPU的資源進行其他操作。兩個DMA控制器有12個通道(DMA1有7個通道,DMA2有5個通道),每個通道專門用來管理來自與一個或多個外設對存儲器訪問的請求。還有一個仲裁器來協調各個DMA請求的優先權。
DMA的主要特性
- 12個獨立的可配置的通道(請求):DMA1有7個 通道,DMA2有5個通道
- 每個通道都直接連接專用的硬件DMA請求,每個通道都同樣支持軟件觸發。這些功能通過軟件來配置。
- 在同一個DMA模塊上多個請求見的優先權可以通過軟件編程設置(共有四級:很高、高、中等和低),優先權設置相等時由硬件決定(請求0優先與請求1,以此類推)。
- 獨立數據源和目標數據區的傳輸字節寬度(字節、半字、字),模擬打包和拆包的過程。源和目標地址必須按數據傳輸寬度對齊。
- 支持循環的緩沖器管理
- 每個通道都有3個事件標志(DMA半傳輸、DMA傳輸完成和DMA傳輸出錯),這三個事件標志邏輯或成為一個單獨的中斷請求。
- 存儲器和存儲器之間的傳輸。
- 外設和存儲器、存儲器和外設之間的傳輸。
- Flash、SRAM、外設的SRAM、APB1、APB2和AHB外設均可作為訪問的源和目標。
- 可編程的數據傳輸數目。
?ADC(Analog-Digital Converter)模擬-數字轉換器,ADC可以將引腳上連續變化的模擬電壓轉換為內存中存儲的數字變量,建立模擬電路到數字電路的橋梁.12位ADC是一種逐次逼近型模擬數字轉換器。它有多達18個通道,可測量16個外部和兩個內部信號源。各通道的A/D轉換可以單次、連續、掃描或間斷模式執行。ADC的結果可以左對齊或右對齊對齊方式存儲在16位數據寄存器中。
逐次逼近型ADC通過二進制搜索算法逐步確定輸入模擬信號的數值,具體步驟如下:
- 初始化:
- 逐次逼近寄存器(SAR)將最高有效位(MSB)設為1,其余位設為0,形成一個初始猜測值。
- 該值通過內部DAC(數模轉換器)轉換為模擬電壓,并與輸入信號進行比較。
- 比較與調整:
- 比較器將DAC輸出的電壓與輸入模擬信號進行比較:
- 若DAC電壓 < 輸入電壓 → 保持該位為1,繼續測試下一位(次高位)。
- 若DAC電壓 > 輸入電壓 → 將該位清零(設為0),繼續測試下一位。
- 重復上述步驟,逐位確定每一位的值(從MSB到LSB),直到所有位完成判斷。
- 輸出結果:
- 最終,SAR寄存器中保存的二進制數值即為輸入模擬信號的數字表示。
ADC的輸入時鐘不得超過14MHz,它是由PCLK2經分頻產生。
ADC主要特征:
????????● 12位分辨率
????????● 轉換結束、注入轉換結束和發生模擬看門狗事件時產生中斷
????????● 單次和連續轉換模式
????????● 從通道0到通道n的自動掃描模式
????????● 自校準
????????● 帶內嵌數據一致性的數據對齊
????????● 采樣間隔可以按通道分別編程
????????● 規則轉換和注入轉換均有外部觸發選項
????????● 間斷模式
????????● 雙重模式(帶2個或以上ADC的器件)
????????● ADC轉換時間:
????????─
????????STM32F103xx增強型產品:時鐘為56MHz時為1μs(時鐘為72MHz為1.17μs)
????????─
????????STM32F101xx基本型產品:時鐘為28MHz時為1μs(時鐘為36MHz為1.55μs)
????????─
????????STM32F102xxUSB型產品:時鐘為48MHz時為1.2μs
????????─
????????STM32F105xx和STM32F107xx產品:時鐘為56MHz時為1μs(時鐘為72MHz為1.17μs)
????????● ADC供電要求:2.4V到3.6V
????????● ADC輸入范圍:VREF- ≤ VIN ≤ VREF+
????????● 規則通道轉換期間有DMA請求產生。
ADC功能描述:
????????
????????
技術實現
?連線圖
代碼實現?
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h" //延時函數
#include "OLED.h"
#include "AD.h"int main(void)
{/*OLED初始化*/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);}
}
?AD.h
#ifndef AD_H
#define AD_H#include "stm32f10x.h"extern uint16_t AD_Value[4];void AD_Init(void);
void AD_GetValue(void);#endif
?AD.c
#include "AD.h"uint16_t AD_Value[4]; //將數據存儲在SRAM /*** @brief AD Initialization * @param None* @retval None* @note Initialize AD basic structure*/
void AD_Init(void)
{//開啟時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //開啟DMA時鐘//配置ADC時鐘 12MHzRCC_ADCCLKConfig(RCC_PCLK2_Div6);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; //模擬輸入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//配置ADC規則組輸入通道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);//初始化ADCADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;ADC_InitStruct.ADC_ScanConvMode = ENABLE; //掃描模式ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; //單次轉換ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //軟件觸發,非外部觸發ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; //數據右對齊ADC_InitStruct.ADC_NbrOfChannel = 4; //共4個序列ADC_Init(ADC1,&ADC_InitStruct);//初始化DMA DMA_InitTypeDef DMA_InitStruct;DMA_InitStruct.DMA_BufferSize = 4; //緩沖區的大小,由于CNDTR寄存器的高16位保留,這里緩沖區大小的變量的大小使用//uint16_t即可DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //外設站點是數據源DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存儲器站點的基地址,將數據轉運到SRAMDMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //接收的數據以半字形式傳輸DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //DAM傳輸計數器不使用自動重裝模式DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //DMA為外設到存儲器傳輸的方式,使用硬件觸發DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //存儲器站點自增DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設站點地址不自增DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //ADC規則數據寄存器(ADC_DR)的地址為要傳輸的數據的基地址DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //傳輸的數據以半字節形式傳輸DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //只有一個通道,DMA優先級隨便選DMA_Init(DMA1_Channel1,&DMA_InitStruct); //使用硬件觸發,通道為固定的通道1,不可更改DMA_Cmd(DMA1_Channel1,ENABLE);ADC_DMACmd(ADC1,ENABLE); //開啟ADC的DMA觸發源//關閉ADC電源ADC_Cmd(ADC1,DISABLE);//維持ADC處于掉電狀態至少兩個周期Delay_us(1);//ADC校準ADC_ResetCalibration(ADC1); //復位校準,將ADC_CR2寄存器中的RSTCAL位置1,初始化校準寄存器while(ADC_GetResetCalibrationStatus(ADC1)); //等待復位校準完成,校準寄存器被初始化后RSTAL位由硬件清零ADC_StartCalibration(ADC1); //AD校準,將ADC_CR2寄存器中的CAL位置1,開始校準while(ADC_GetCalibrationStatus(ADC1)); //等待AD校準完成,校準完成后CAL位由硬件清零//開啟ADC電源ADC_Cmd(ADC1,ENABLE);
}/*** @brief AD觸發,DMA計數器賦值* @param None* @retval None* @note 為DMA計數器重新賦值*/
void AD_GetValue(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,4); //傳輸計數器賦值,非自動重裝模式,若使用自動重裝模式,則只需在初始化中進行一次賦值DMA_Cmd(DMA1_Channel1,ENABLE); ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ADC為單次轉換模式,仍需軟件觸發AD轉換。循環轉換則只需在初始化時觸發一次while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA轉運完成DMA_ClearFlag(DMA1_FLAG_TC1);
}
??OLED部分代碼參照文章《STM32基礎教程——OLED顯示》http://【STM32基礎教程 ——OLED顯示 - CSDN App】https://blog.csdn.net/2301_80319641/article/details/145837521?sharetype=blog&shareId=145837521&sharerefer=APP&sharesource=2301_80319641&sharefrom=link
技術要點
//開啟時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //開啟DMA時鐘
本實驗設計到DMA和ADC1,而GPIO作為ADC1的通道,故應分別開啟他們的時鐘,ADC1和GPIO隸屬于APB2外設,DMA隸屬AHB外設。
//配置ADC時鐘 12MHzRCC_ADCCLKConfig(RCC_PCLK2_Div6);
系統時鐘為72MHz,這里分頻因子選擇6分頻,經分頻后ADC時鐘為12MHz?。
ADC輸入時鐘有PCLK2分頻得到,最大不得超過14MHz。
GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; //模擬輸入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);
?由于進行AD轉換,GPIO的輸入量為模擬量,故應將GPIO的輸入模式配置為模擬輸入模式, 由于使用AD轉換通道0,通道1,通道2,通道3,這里GPIO配置PA0、PA1、PA2、PA3引腳。AD通道與GPIO引腳對應見下圖:
//配置ADC規則組輸入通道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多個輸入通道。之前的ADC多通道是單次轉換,非掃描模式,利用了多個通道,但是只利用了轉換序列1.這里ADC使用單次轉換,掃描模式。每次轉換可以掃描多個通道,利用DMA,可以防止只有一個數據寄存器而數據無法及時運出導致多個通道轉換后數據被覆蓋。
//初始化ADCADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;ADC_InitStruct.ADC_ScanConvMode = ENABLE; //掃描模式ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; //單次轉換ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//軟件觸發,非外部觸發ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; //數據右對齊ADC_InitStruct.ADC_NbrOfChannel = 4; //共4個序列ADC_Init(ADC1,&ADC_InitStruct);
初始化ADC,將ADC設置為單次轉換,掃描模式。同樣觸發方式選擇軟件觸發,數據右對齊,要轉換的ADC的通道的數量為4。
//初始化DMA DMA_InitTypeDef DMA_InitStruct;DMA_InitStruct.DMA_BufferSize = 4; //緩沖區的大小,由于CNDTR寄存器的高16位保留,這里緩沖區大小的變量的大小使用//uint16_t即可DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //外設站點是數據源DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存儲器站點的基地址,將數據轉運到SRAMDMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //接收的數據以半字形式傳輸DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //DAM傳輸計數器不使用自動重裝模式DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //DMA為外設到存儲器傳輸的方式,使用硬件觸發DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //存儲器站點自增DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設站點地址自增DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //ADC規則數據寄存器(ADC_DR)的地址為要傳輸的數據的基地址DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //傳輸的數據以半字節形式傳輸DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //只有一個通道,DMA優先級隨便選DMA_Init(DMA1_Channel1,&DMA_InitStruct); //使用硬件觸發,通道為固定的通道1,不可更改
將數據緩沖區(DMA通道的DMA緩存的大小)的大小設置為4。本實驗是讀取AD轉換后的值,DMA轉運的方向應為外設到存儲器。結構體成員DMA_DIR指示數據傳輸方向,故其值設置為DMA_DIR_PeripheralSRC,即從存儲器讀。實驗使用數組存儲AD轉換的值,故存儲器地址設置為AD_Value,AD_Value為指向數組首地址的指針,其值為數組的地址,應強轉為uint32_t類型。同時存儲器站點自增。
ADC_DR寄存器為規則組轉換的數據寄存器,存儲的數據寬度為半字,故外設基地址設置為&ADC->DR外設和存儲器的數據寬度都設置為半字。
DMA數據傳輸由外設到存儲器,故結構體成員DMA_M2M的值應設置為DMA_M2M_Disable,且DMA由硬件觸發(因為數據傳輸方向是外設到存儲器)。
ADC_DMACmd(ADC1,ENABLE); //開啟ADC的DMA觸發源
開啟ADC的DMA觸發源,通過ADC進行硬件觸發DMA數據轉運。
void AD_GetValue(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,4); //傳輸計數器賦值,非自動重裝模式,若使用自動重裝模式,則只需在初始化中進行一次賦值DMA_Cmd(DMA1_Channel1,ENABLE); ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ADC為單次轉換模式,仍需軟件觸發AD轉換。循環轉換則只需在初始化時觸發一次while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA轉運完成DMA_ClearFlag(DMA1_FLAG_TC1);
}
ADC為單次轉換模式,故每次循環開始轉換前都應使用軟件觸發的方式觸發ADC轉換。
實驗結果
DMA+ADC多通道
問題記錄
1.江科大的教學視頻中ADC校驗是在開啟ADC電源后開始校驗,ADC校準應先關閉ADC電源并維持兩個周期以上,然后開啟ADC電源。