首先我們對ADC及DMA的基礎知識作一下簡單介紹。
一、?GD32/STM32? ADC模塊的核心要點
一)、ADC基礎特性
- ?12位逐次逼近型?
GD32/STM32 ADC為12位分辨率,最大量化值為4095(對應3.3V參考電壓),支持0-3.3V模擬輸入范圍。 - ?多通道架構?
- 外部通道:16個(部分型號支持24個外部通道)
- 內部通道:2個(溫度傳感器、內部參考電壓)
- ?時鐘系統?
- 模擬時鐘(ADCCLK):由APB2時鐘分頻(/2、/4、/6、/8)生成,上限14MHz
- 數字時鐘:等同于APB2時鐘
二)、核心功能模式
- ?轉換模式?
- ?單次轉換?:手動觸發單次采樣
- ?連續轉換?:自動循環采樣同一通道
- ?掃描模式?:自動遍歷多通道
- ?通道分組機制?
- ?規則組?:常規轉換隊列(最多16路)
- ?注入組?:高優先級中斷式轉換(最多4路)
- ?觸發方式?
- 軟件觸發
- 硬件觸發(TIMER、EXTI等)
三)、數據管理
- ?對齊方式?
- 右對齊:默認模式,數據存于寄存器低12位
- 左對齊:便于快速讀取高8位數據
- ?校準機制?
- 上電自校準
- 溫度傳感器校準(需單獨使能)
四)、典型應用配置
// HAL庫配置示例(單通道連續轉換)
ADC_HandleTypeDef hadc;
hadc.Instance = ADC1;
hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // ADCCLK=21MHz
hadc.Init.Resolution = ADC_RESOLUTION_12B;
hadc.Init.ContinuousConvMode = ENABLE; // 連續轉換模式
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
HAL_ADC_Init(&hadc);
五)、進階應用方案
- ?DMA傳輸?
實現多通道數據自動搬運,減少CPU開銷 - ?定時器觸發?
精確控制采樣間隔(適用于波形采集) - ?模擬看門狗?
設置電壓閾值觸發中斷(過壓/欠壓保護)
六)、關鍵注意事項
- 輸入阻抗匹配:建議源阻抗≤10kΩ
- 抗干擾設計:
- 獨立模擬供電引腳(VDDA、VSSA)
- 添加RC濾波電路
- 避免數字信號線平行走線
- 開啟DMA雙緩沖模式提升吞吐量
- 使用過采樣技術提高有效分辨率
- 低溫漂外部基準電壓(如REF3030)提升精度
七)、關于ADC轉換時間計算
轉換時間 = (采樣周期 + 12.5周期) / ADCCLK
例如:當ADCCLK=21MHz,采樣周期=84周期時,總轉換時間≈4.59μs37
二、GD32/STM32? DMA?介紹
?一)、DMA基礎概念
-
?定義與作用?
DMA(Direct Memory Access)是一種無需CPU參與的硬件數據傳輸機制,可實現外設與內存之間(如ADC數據存入數組)、內存與外設之間(如UART發送數據)或內存與內存之間的高速數據搬運56。其核心作用是釋放CPU資源,提升系統實時性與效率。 -
?硬件架構?
- ?雙控制器架構?:STM32F1系列包含DMA1(7通道)和DMA2(5通道,僅大容量型號支持)
- ?總線矩陣?:DMA通過總線矩陣連接AHB總線,實現與外設、存儲器的并行訪問
- ?數據流與通道?:每個DMA控制器包含獨立的數據流(Stream),每個數據流可映射至8個硬件通道(如DMA1_Channel4對應UART1_TX)
二)、DMA核心特性
-
?傳輸方向?
- 外設→存儲器(如ADC采集數據)
- 存儲器→外設(如SPI發送數據)
- 存儲器→存儲器(高速數據拷貝)
-
?數據管理?
- ?數據寬度?:支持8/16/32位,源與目標寬度可獨立配置(自動填充/截斷)
- ?地址遞增?:傳輸后自動遞增源/目標地址,支持非連續數據傳輸
- ?循環模式?:自動重置傳輸計數器,實現環形緩沖區(適用于連續采樣場景)
-
?優先級與仲裁?
- 軟件可設4級優先級(很高、高、中、低)
- 硬件仲裁器自動處理同優先級請求
三)、DMA配置流程(以HAL庫為例)
// 示例:配置UART通過DMA發送數據
// 1. 使能DMA時鐘
__HAL_RCC_DMA1_CLK_ENABLE(); // DMA1時鐘使能:ml-citation{ref="4,8" data="citationList"}// 2. 初始化DMA參數
DMA_HandleTypeDef hdma_uart_tx;
hdma_uart_tx.Instance = DMA1_Channel4; // 選擇通道
hdma_uart_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; // 存儲器→外設
hdma_uart_tx.Init.PeriphInc = DMA_PINC_DISABLE; // 外設地址固定
hdma_uart_tx.Init.MemInc = DMA_MINC_ENABLE; // 存儲器地址遞增
hdma_uart_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_uart_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_uart_tx.Init.Mode = DMA_NORMAL; // 單次傳輸模式
HAL_DMA_Init(&hdma_uart_tx);// 3. 綁定外設與DMA
__HAL_LINKDMA(&huart1, hdmatx, hdma_uart_tx); // 關聯UART1發送端:ml-citation{ref="4,8" data="citationList"}
四)、典型應用場景
場景 | 描述 | 關鍵配置要點 |
---|---|---|
?ADC多通道采樣? | 循環采集多個傳感器數據并存入數組 | 掃描模式 + DMA循環傳輸 |
?串口大數據傳輸? | 高速發送/接收數據包(如Modbus通信) | 雙緩沖機制 + 傳輸完成中斷 |
?內存高速拷貝? | 內部Flash到SRAM的固件升級 | 存儲器到存儲器模式 |
五)、關鍵注意事項
-
?時鐘使能?
- 必須使能DMA控制器時鐘(如
__HAL_RCC_DMA1_CLK_ENABLE()
)和外設時鐘
- 必須使能DMA控制器時鐘(如
-
?中斷管理?
- 使能傳輸完成/半傳輸/錯誤中斷,并在中斷服務函數中清除標志位
-
?數據對齊?
- 確保源/目標地址對齊(如32位傳輸時地址需4字節對齊)
三、ADC校正?
一)、校準核心流程
-
?基礎校準(標準模式)?
- 上電后執行基礎校準,消除電容誤差
- ?操作步驟?:
① ADC上電(ADON=1)但未啟動轉換
② 執行校準命令(ADC_StartCalibration(ADC1))
③ 等待校準完成標志位
-
?內部參考電壓校準?
- 利用內置VREFINT通道(通道17)實現動態精度補償
- ?實現邏輯?:
① 測量VREFINT通道原始值(典型值約1.2V)
② 計算比例因子:Scale = VREFINT_實際值 / ADC_VREFINT原始值
③ 應用比例因子至其他通道
? ??????考慮到電壓校準需要手動測量VREFINT,加上我們大多應用并不需要獲取準確電壓值,只需要知道ADC值的變化,所以通常我們可以不做電壓校準。
?四、ADC程序實現示例
?一)、單通道輪詢模式(標準庫)
// 引用[1][8]的配置邏輯
#include "stm32f10x.h"void ADC1_Init(void) {// 開啟時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);// 配置ADC時鐘為PCLK2的6分頻(ADCCLK=12MHz)RCC_ADCCLKConfig(RCC_PCLK2_Div6); // GPIO配置為模擬輸入GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(GPIOA, &GPIO_InitStruct);// ADC參數配置ADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // 獨立模式ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 右對齊ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 單次轉換ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 非掃描模式ADC_Init(ADC1, &ADC_InitStruct);// 校準ADCADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1));ADC_StartCalibration(ADC1);while(ADC_GetCalibrationStatus(ADC1));
}uint16_t Get_ADC_Value(void) {ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 啟動轉換while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待轉換完成return ADC_GetConversionValue(ADC1); // 返回12位轉換結果
}
?二)、多通道DMA傳輸(標準庫)
void ADC1_Init(void)
{
#if 1GPIO_InitTypeDef GPIO_InitStructure;DMA_InitTypeDef DMA_InitStructure;ADC_InitTypeDef ADC_InitStructure;u8 rank = 1;/* Enable DMA clock */RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* Enable ADC1 clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);/* Configure PB0 & PB1 as analog input */GPIO_InitStructure.GPIO_Pin = PAPER_END_SEN | PAPER_IN_SEN;// | GPIO_BM_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(GPIO_SENSOR, &GPIO_InitStructure); // PB0 & PB1,輸入時不用設置速率/* DMA channel1 configuration */DMA_DeInit(DMA1_Channel1);DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;; //ADC地址DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SensorNow.ADC_Val;//內存地址DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_BufferSize = ADC_CH_MAX;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設地址固定DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址增加DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //半字DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循環傳輸DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_Init(DMA1_Channel1, &DMA_InitStructure);/* Enable DMA channel1 */DMA_Cmd(DMA1_Channel1, ENABLE);/* ADC1 configuration */ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //獨立ADC模式ADC_InitStructure.ADC_ScanConvMode = ENABLE ; //使能掃描模式,掃描模式用于多通道采集ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //開啟連續轉換模式ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部觸發轉換ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集數據右對齊ADC_InitStructure.ADC_NbrOfChannel = ADC_CH_MAX; //通道數目ADC_Init(ADC1, &ADC_InitStructure);/*配置ADC時鐘,為PCLK2的8分頻,即9MHZ*/RCC_ADCCLKConfig(RCC_PCLK2_Div8); /*配置ADC的通道采樣周期及序列 */ ADC_RegularChannelConfig(ADC1, ADC_Channel_15, rank ++, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_14, rank ++, ADC_SampleTime_55Cycles5);
/* Enable ADC1 DMA */ADC_DMACmd(ADC1, ENABLE);/* Enable ADC1 */ADC_Cmd(ADC1, ENABLE);/*復位校準寄存器*/ ADC_ResetCalibration(ADC1);/*等待校準寄存器復位完成*/while(ADC_GetResetCalibrationStatus(ADC1));/* ADC校準*/ADC_StartCalibration(ADC1);/* 等待校準完成*/while(ADC_GetCalibrationStatus(ADC1));/* 前面設置為不使用外部觸發轉換,所以使用軟件觸發ADC轉換 */ ADC_SoftwareStartConvCmd(ADC1, ENABLE);
#endif
}
?三)、定時器觸發ADC采樣(標準庫)
? ?1、TIM定時器配置?
- ?時鐘使能與模式設置?
選擇TIM工作模式(如PWM模式或基本定時模式),配置預分頻器(PSC)和自動重載值(ARR)以設定觸發頻率。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3時鐘TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 72 - 1; // 72MHz/72=1MHz
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_Period = 1000 - 1; // 觸發頻率1kHz (1MHz/1000)
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_InitStruct);TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // 更新事件觸發TRGO
TIM_Cmd(TIM3, ENABLE); // 啟動定時器
2. ?ADC觸發源配置
ADC初始化與外部觸發選擇?
啟用ADC外部觸發,選擇TIM的TRGO事件作為觸發源。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 使能ADC1時鐘ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // 獨立模式
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 單通道掃描
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 單次轉換模式
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO; // TIM3觸發
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 右對齊
ADC_Init(ADC1, &ADC_InitStruct);ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // 配置通道0
ADC_Cmd(ADC1, ENABLE); // 啟動ADC
五、完整工程文件?
【免費】GD32/stm32ADC、DMA、UART、SPI、Flash讀寫、DRV8812/DRV8813步進馬達驅動、TIMER中斷、GPIO控制資源-CSDN文庫