一、實驗簡介
正常單通道ADC采集順序是先開啟ADC采集,然后等待ADC轉換完成,也就是判斷EOC位置1,然后再讀取數據寄存器的值。
如果配置了DMA功能,在EOC位被硬件置1后,自動產生DMA請求,然后DMA進行數據搬運。
1,功能描述?通過DMA讀取數據
通過ADC1通道1(PA1)采集電位器的電壓,并顯示ADC轉換的數字量及換算后的電壓值
2、確定最小刻度
VREF+ = 3.3V ---》 0V?≤?VIN?≤??3.3V ---》最小刻度 = 3.3 / 4096 ,F1的分辨率是12位的,也就是把3.3V分為4096份。F4/F7/H7還可以自己配置分辨率,例如H7可以把分辨率配置為16位的,也就是把3.3V進行65536等分。
3,確定轉換時間
采樣時間239.5個ADC時鐘周期為例,可以得到轉換時間為21us。例如配置為最長的采樣時間239.5個采樣周期,那么采樣時間就是239.5 + 12.5? = 252個時鐘周期。配置ADC的時鐘的12M,則轉換時間為252 * (1 / 12000000) = 21us,采樣時間設置的越大,準確度越高,設置的越小,準確度越低。
4、模式組合
由于使用了DMA搬運,所以使用連續轉換模式、不使用掃描模式
二、單通道ADC采集實驗配置步驟?
1、HAL_DMA_Init()函數,初始化DMA
2、__HAL_LINKDMA()宏定義,將DMA和ADC句柄聯系起來
3、HAL_ADC_Init()函數,用于初始化ADC,配置ADC工作參數。
4、HAL_ADCEx_Calibration_Start()函數,用于ADC校準的。
5、HAL_ADC_MspInit()函數,?配置NVIC、CLOCK、GPIO
6、HAL_ADC_ConfigChannel()函數,配置ADC相應通道相關參數
7、HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()函數,使能DMA數據流傳輸完成中斷
8、DMAx_Channely_IRQHandler()函數,編寫DMA數據流中斷服務函數
9、HAL_DMA_Start_IT()函數,啟動DMA,開啟傳輸完成中斷
10、HAL_ADC_Start_DMA()函數,觸發ADC轉換,DMA傳輸數據
三、?實驗程序
1、寄存器版本
dma.c源程序
#include "./BSP/DMA/dma.h"
#include <string.h>uint16_t ADC_data[20];
//配置ADC1的DMA1的通道1請求
void DMA_Init(void)
{//開啟DMA1時鐘RCC->AHBENR |= (1 << 0);//MEM2MEM 設置為非存儲器到存儲器模式DMA1_Channel1->CCR &= ~(1 << 14);//PL 設置通道優先級為中DMA1_Channel1->CCR |= (1 << 12);//MSIZE 設置存儲器數據寬度為16位DMA1_Channel1->CCR |= (1 << 10);//PSIZE 設置外設數據寬度為16位DMA1_Channel1->CCR |= (1 << 8);//MINC 設置存儲器增量模式DMA1_Channel1->CCR |= (1 << 7);//PINC 設置外設不增量模式DMA1_Channel1->CCR &= ~(1 << 6);//CIRC 不執行循環模式//DMA1_Channel1->CCR &= ~(1 << 5);DMA1_Channel1->CCR |= (1 << 5);//DIR 從外設讀取DMA1_Channel1->CCR &= ~(1 << 4); //設置傳輸數量DMA1_Channel1->CNDTR = 20;//設置DMA的外地址DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;//清空ADC_datamemset((void*)ADC_data,0,20);DMA1_Channel1->CMAR = (uint32_t)ADC_data;//EN 開啟DMA 這個必須放在最后 不然CNDTR寄存器不能寫入DMA1_Channel1->CCR |= (1 << 0);
}
adc.c源程序
#include "./BSP/ADC/adc.h"//配置ADC1的通道1 PA1進行
void ADC_Init(void)
{//開啟ADC1時鐘RCC->APB2ENR |= (1 << 9);//關閉掃描模式ADC1->CR1 &= ~(1 << 8);//開啟外部觸發轉換ADCADC1->CR2 |= (1 << 20);//EXTSEL 設置軟件觸發ADC轉換ADC1->CR2 |= (7 << 17);//ALIGN 數據右對齊ADC1->CR2 &= ~(1 << 11);//DMA 開啟DMA轉換ADC1->CR2 |= (1 << 8);//CONT 開啟連續轉換模式ADC1->CR2 |= (1 << 1);//設置ADC1通道1轉換時間為239.5個周期ADC1->SMPR2 |= (7 << 3);//L 設置總共1一個轉換通道ADC1->SQR1 |= (1 << 20);//設置ADC第一個轉換為通道1ADC1->SQR3 |= (1 << 0);//EXTTRIG 開啟GPIOA時鐘RCC->APB2ENR |= (1 << 2);//設置PA1為輸入模式GPIOA->CRL &= ~(0XF << 4);//ADON 開啟ADC功能ADC1->CR2 |= (1 << 0);//RSTCAL 初始化ADC校準寄存器ADC1->CR2 |= (1 << 3);//等待ADC校準寄存器初始化完畢while(ADC1->CR2 & (1 << 3));//開啟ADC校準ADC1->CR2 |= (1 << 2);//等待ADC校準完成while(ADC1->CR2 & (1 << 2));//SWSTART 開啟規則組轉換 軟件觸發ADC1->CR2 |= (1 << 22);
}
2、庫函數版本
dam.c源程序
#include "./BSP/DMA/dma.h"
#include <string.h>extern ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma;
//配置ADC1的DMA1的通道1請求
void DMA_Init(void)
{//開啟DMA1時鐘__HAL_RCC_DMA1_CLK_ENABLE();//配置DMA1通道 因為ADC1連接在DMA1的通道1 這個在使用手冊可以查找到hdma.Instance = DMA1_Channel1;//外設到內存hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;//內存為16位hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;//內存遞增hdma.Init.MemInc = DMA_MINC_ENABLE;//循環搬運 //當啟動了循環模式,數據傳輸的數目變為0時,將會自動地被恢復成配置通道時設置的初值,DMA操作將會繼續進行。hdma.Init.Mode = DMA_NORMAL;//外設16位hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//外設地址不遞增hdma.Init.PeriphInc = DMA_PINC_DISABLE;//通道1優先級為中hdma.Init.Priority = DMA_PRIORITY_MEDIUM;//配置DMA的通道1為外設到內存HAL_DMA_Init(&hdma);//將DMA句柄于ADC句柄連接起來 可以理解為將這個DMA句柄拷貝到ADC句柄里的DMA句柄上__HAL_LINKDMA(&hadc,DMA_Handle,hdma);
}//標志DMA數據搬運完
uint8_t state = 0;
void DMA1_Channel1_IRQHandler(void)
{if(DMA1->ISR & (1 << 1)){state = 1;//清除中斷標志位DMA1->IFCR |= (1 << 1);}DMA1->IFCR |= (1 << 0);
}
adc.c源程序
#include "./BSP/ADC/adc.h"
#include "string.h"extern DMA_HandleTypeDef hdma;
uint16_t ADC_data[20];
ADC_HandleTypeDef hadc;
//配置ADC1的通道1 PA1進行
void ADC_Init(void)
{hadc.Instance = ADC1;//配置ADC連續轉換模式 就是ADC轉換完成一次后會自動下一次轉換hadc.Init.ContinuousConvMode = ENABLE;//轉換結果采用右對齊hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;//不開啟間斷模式hadc.Init.DiscontinuousConvMode = DISABLE;//ADC觸發選用軟件觸發hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;//設置ADC轉換數量的 SQR1的L位hadc.Init.NbrOfConversion = 1;//設置間斷模式寫轉換一次轉換數量hadc.Init.NbrOfDiscConversion = 0;//不開啟掃描模式 因為就一個通道hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;//設置ADC為軟件觸發轉換HAL_ADC_Init(&hadc);//開啟ADC校準HAL_ADCEx_Calibration_Start(&hadc);ADC_ChannelConfTypeDef sConfig;//配置通道1sConfig.Channel = ADC_CHANNEL_1;//通道1第一個轉換sConfig.Rank = ADC_REGULAR_RANK_1;//采樣周期采用239.5sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;//配置ADC通道一為第一個轉換HAL_ADC_ConfigChannel(&hadc, &sConfig);memset((void*)ADC_data,0,20);//這句話最好寫 不然下面那個會把半傳輸中斷也開開 這里就會開一個中斷 因為下面那個函數回把ADC半傳輸回調函數復制 //這句話不寫也行 但是中斷里邊要清除所有位 不然卡在中斷HAL_DMA_Start_IT(&hdma, (uint32_t)ADC1->DR, (uint32_t)ADC_data, 20);//開啟DMA傳輸 這個函數里邊會把中斷都打開HAL_ADC_Start_DMA(&hadc, (uint32_t* )ADC_data, 20);
}void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{//開啟ADC1時鐘__HAL_RCC_ADC1_CLK_ENABLE();//開啟GPIOA時鐘__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitTypeDef GPIO_Init;GPIO_Init.Mode = GPIO_MODE_ANALOG;GPIO_Init.Pin = GPIO_PIN_1;GPIO_Init.Pull = GPIO_NOPULL;//設置PA1為模擬輸入模式HAL_GPIO_Init(GPIOA,&GPIO_Init);//使能DMA1通道1中斷HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);//設置中斷優先級HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,2,2);
}
這里主要是要注意先配置DMA中斷,再調用ADC的DMA函數,具體原因在代碼里寫了