一、ADC模數轉換簡介
ADC(Analog-to-Digital Converter,模數轉換器) 是将連續變化的模拟信号轉換為離散的數字信号的器件。真實世界的模拟信号,例如溫度、壓力、聲音或者圖像等,需要轉換成更容易儲存、處理和發射的數字形式。模數轉換器可以實作這個功能,在各種不同的産品中都可以找到它的身影。與之相對應的 DAC(Digital-to-Analog Converter),它是 ADC 模數轉換的逆向過程。
我們在采集外界信号轉換為電氣(電平)信号時,通常有兩種輸入方式:邏輯電平輸入和ADC輸入。例如現有輸入電壓值采集,前者隻能讀取高、低電平(0或1)的二态信号,該态勢信号是與某閥值比較輸出的真假數字信号,而ADC輸入方式可以讀取出電壓值大小的離散信号,這個将輸入的連續模拟量電壓轉換為數字離散電壓的過程就叫模數轉換,模數轉換一般要經過采樣、保持和量化、編碼這幾個步驟。
【1】采樣,将時間上連續變化的模拟信号轉換為時間上離散的模拟信号。
【2】保持,模拟信号轉換為數字信号都需要一定時間,為了給後續處理過程提供一個穩定的值,在采樣電路後要求将所采樣的模拟信号保持一段時間。
【3】量化,采樣保持電路的輸出電壓還需要按照某種近似方式歸化到與之相應的離散電平上,任何數字量隻能是某個最小數量機關的整數倍。
【4】編碼,量化後的數值其後還需要編碼過程,也就是 A/D 轉換器輸出的數字量。
二、ADC工程建立
【1】本文采用STM32L496VGTx的ali聯合上海諾行的開發闆,其支援ADC功能的原理框圖及引腳描述如下:

引腳描述:
本文不按開發闆資料設定來配置ADC,隻采用到PC2、PC3兩個引腳。
【2】本文基于原有的已經實作序列槽lpusart調試輸出和LCD螢幕輸出的就工程為基礎建立一個adc工程,并将舊工程已經實作的功能遷移到新功能工程。
完成新工程建立後,進入cubeMX配置界面,配置ADC功能
開啟ADC1的IN3:
開啟ADC2的IN4
前兩部配置開啟ADC功能時,時鐘會自動開啟ADC時鐘源
【3】點選儲存輸出
在ICore目錄下建立adc檔案夾,并在該目錄下建立adc.h和adc.c源檔案,項目整體目錄結構如下:
三、輪詢讀取單路ADC數值代碼設計
目前隻配置了ADC輪詢模式讀取資料,在stm32l4xx_hal_adc.c中關于輪詢模式讀取ADC資料說明如下:
(++) ADC conversion by polling:
(+++) Activate the ADC peripheral and start conversions
using function HAL_ADC_Start()
(+++) Wait for ADC conversion completion
using function HAL_ADC_PollForConversion()
(+++) Retrieve conversion results
using function HAL_ADC_GetValue()
(+++) Stop conversion and disable the ADC peripheral
using function HAL_ADC_Stop()
是以現讀取adc資料驅動: adc.h
#ifndef ADC_ADC_H_
#define ADC_ADC_H_
#include "stm32l4xx_hal.h" //HAL庫檔案聲明
extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc2;
uint16_t ADC_IN_1(void);
uint16_t ADC_IN_2(void);
#endif /* ADC_ADC_H_ */
adc.c
#include "adc.h"
uint16_t ADC_IN_1(void) //ADC采集程式
{
HAL_ADC_Start(&hadc1);//開始ADC采集
HAL_ADC_PollForConversion(&hadc1,500);//等待采集結束
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))//讀取ADC完成标志位
{
return HAL_ADC_GetValue(&hadc1);//讀出ADC數值
}
return 0;
}
uint16_t ADC_IN_2(void) //ADC采集程式
{
HAL_ADC_Start(&hadc2);//開始ADC采集
HAL_ADC_PollForConversion(&hadc2,500);//等待采集結束
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))//讀取ADC完成标志位
{
return HAL_ADC_GetValue(&hadc2);//讀出ADC數值
}
return 0;
}
在main.c檔案中,添加acd.h驅動檔案
/* USER CODE BEGIN Includes */
#include "../../ICore/key/key.h"
#include "../../ICore/led/led.h"
#include "../../ICore/print/print.h"
#include "../../ICore/usart/usart.h"
#include "../../ICore/oled/oled.h"
#include "../../ICore/adc/adc.h"
/* USER CODE END Includes */
在主函數内初始化兩個獨立ADC引腳
/* USER CODE BEGIN 2 */
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再開啟接收中斷
HLPUSART_RX_STA = 0;
//ADC相關,ADC_SINGLE_ENDED=單端模式,ADC_DIFFERENTIAL_ENDED=差分模式,CubeMX配置了單端
HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);//ADC采樣校準
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//ADC采樣校準
//LCD
OLED_init();
//設定OLED藍色背景顯示
BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
printf("OLED_Clear_DMA\r\n");
/* USER CODE END 2 */
在主函數循環體内,通過按鍵2擷取兩個ADC數值
/* USER CODE BEGIN WHILE */
while (1)
{
if(HLPUSART_RX_STA&0xC000){//溢出或換行,重新開始
//printf("%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
OLED_printf(10,10,"%.*s",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA=0;//接收錯誤,重新開始
HAL_Delay(100);//等待
}
if(KEY_0())
{
BSP_LCD_login(24,108);
}
if(KEY_1())
{
BSP_LCD_img_DMA();
}
if(KEY_2())
{
printf("ADC1=%04X ADC2=%04X \r\n",ADC_IN_1(),ADC_IN_2());
OLED_printf(10,108,"ADC1=%04X ADC2=%04X",ADC_IN_1(),ADC_IN_2());//向lcd發送字元串
}
/* USER CODE END WHILE */
編譯及下載下傳效果如下,目前兩個ADC引腳沒有外接裝置,每次取得數值是一定範圍随機的:
四、DMA方式讀取單路ADC代碼設計
回到cubeMX配置界面,開啟ADC的連續轉換功能
開啟ADC的DMA功能
确認ADC的DMA的終端功能是否開啟。
確定DMA初始化排序在ADC前面
點選儲存輸出生成代碼,設定ADC1支援DMA模式,ADC2依然是輪詢模式。
關于ADC的DMA讀取資料,HAL标準庫描述如下:
(++) ADC conversion with transfer by DMA:
(+++) Activate the ADC peripheral and start conversions
using function HAL_ADC_Start_DMA()
(+++) Wait for ADC conversion completion by call of function
HAL_ADC_ConvCpltCallback() or HAL_ADC_ConvHalfCpltCallback()
(these functions must be implemented in user program)
(+++) Conversion results are automatically transferred by DMA into
destination variable address.
(+++) Stop conversion and disable the ADC peripheral
using function HAL_ADC_Stop_DMA()
現在來實作ADC的DMA讀取資料,在main.c主函數内,調整如下:
/* USER CODE BEGIN 2 */
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再開啟接收中斷
HLPUSART_RX_STA = 0;
//ADC相關,ADC_SINGLE_ENDED=單端模式,ADC_DIFFERENTIAL_ENDED=差分模式,CubeMX配置了單端
HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);//ADC采樣校準
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//ADC采樣校準
//本次新調整
uint16_t a1 = 0, a2 = 0; //緩存ADC1數值
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&a1,1);//啟動DMA,采集資料存入的變量位址,長度1
//LCD
OLED_init();
//設定OLED藍色背景顯示
BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
printf("OLED_Clear_DMA\r\n");
/* USER CODE END 2 */
在主函數循環體内調整如下:
/* USER CODE BEGIN WHILE */
while (1)
{
if(HLPUSART_RX_STA&0xC000){//溢出或換行,重新開始
//printf("%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
OLED_printf(10,10,"%.*s",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA=0;//接收錯誤,重新開始
HAL_Delay(100);//等待
}
if(KEY_0())
{
BSP_LCD_login(24,108);
}
if(KEY_1())
{
BSP_LCD_img_DMA();
}
if(KEY_2())
{
//本次調整
a2 = ADC_IN_2();
printf("ADC1=%04X ADC2=%04X \r\n",a1,a2);
OLED_printf(10,108,"ADC1=%04X ADC2=%04X",a1,a2);//向lcd發送字元串
}
/* USER CODE END WHILE */
編譯及下載下傳,也能實作資料讀取:
五、DMA模式讀取ADC多路資料
再回到cubeMX配置界面,關閉ADC2
再開啟ADC1的4通道對PC3的支援,參數設定頁面,設定通道數量為2,rank1支援3通道,rank2支援4通道
確定掃描模式已經自動開啟(設定多通道會自動開啟)
點選儲存生成輸出代碼
DMA讀取多路ADC資料和單路資料步驟幾乎一緻,隻是給出不同大緩存空間而已,調整代碼如下:
在main.c主函數調整ADC初始化
/* USER CODE BEGIN 2 */
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再開啟接收中斷
HLPUSART_RX_STA = 0;
//ADC相關,ADC_SINGLE_ENDED=單端模式,ADC_DIFFERENTIAL_ENDED=差分模式,CubeMX配置了單端
HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);//ADC采樣校準
//本次調整,注釋hadc2相關,給hadc1的DMA讀取2個緩存空間
// HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//ADC采樣校準
// uint16_t a1 = 0, a2 = 0; //緩存ADC1數值
// HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&a1,1);//啟動DMA,采集資料存入的變量位址,長度1
uint16_t adc_val[2] = {0};
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&adc_val,2);//啟動DMA,采集資料存入的變量位址,長度2
//LCD
OLED_init();
//設定OLED藍色背景顯示
BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
printf("OLED_Clear_DMA\r\n");
/* USER CODE END 2 */
在main.c的主函數循環體中,調整如下:
/* USER CODE BEGIN WHILE */
while (1)
{
if(HLPUSART_RX_STA&0xC000){//溢出或換行,重新開始
//printf("%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
OLED_printf(10,10,"%.*s",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA=0;//接收錯誤,重新開始
HAL_Delay(100);//等待
}
if(KEY_0())
{
BSP_LCD_login(24,108);
}
if(KEY_1())
{
BSP_LCD_img_DMA();
}
if(KEY_2())
{
//本次調整,直接讀取數組資料列印輸出
// a2 = ADC_IN_2();
printf("ADC1=%04X ADC2=%04X \r\n",adc_val[0],adc_val[1]);
OLED_printf(10,108,"ADC1=%04X ADC2=%04X",adc_val[0],adc_val[1]);//向lcd發送字元串
}
/* USER CODE END WHILE */
編譯及下載下傳,同樣能順利讀取到資料:
六、ADC中斷讀取模式
再次回到cubeMX配置界面,再次開啟ADC2支援PC3引腳,參數保持預設并開啟中斷功能。
儲存輸出生成代碼
在自定義的驅動檔案adc.c(非圖形配置生成輸出代碼),添加對回調函數的處理:
extern uint16_t Adc_Value;
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)//回調函數
{
if(&hadc2==hadc){
Adc_Value = HAL_ADC_GetValue(&hadc2);
}
}
在main.c檔案中,加入全局變量聲明:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint16_t Adc_Value = 0;
/* USER CODE END 0 */
在mian.c檔案的主函數中,改寫ADC初始化
/* USER CODE BEGIN 2 */
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再開啟接收中斷
HLPUSART_RX_STA = 0;
//ADC相關,ADC_SINGLE_ENDED=單端模式,ADC_DIFFERENTIAL_ENDED=差分模式,CubeMX配置了單端
HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);//ADC采樣校準
// HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//ADC采樣校準
// uint16_t a1 = 0, a2 = 0; //緩存ADC1數值
// HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&a1,1);//啟動DMA,采集資料存入的變量位址,長度1
uint16_t adc_val[2] = {0};
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&adc_val,1);//啟動DMA,采集資料存入的變量位址,長度1
//本次改寫
Adc_Value = 0;
HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);
HAL_ADC_Start_IT(&hadc2);
//LCD
OLED_init();
//設定OLED藍色背景顯示
BSP_LCD_Clear_DMA(LCD_DISP_BLUE);
printf("OLED_Clear_DMA\r\n");
/* USER CODE END 2 */
在主函數循環體内,調整輸出顯示
/* USER CODE BEGIN WHILE */
while (1)
{
if(HLPUSART_RX_STA&0xC000){//溢出或換行,重新開始
//printf("%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
OLED_printf(10,10,"%.*s",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA=0;//接收錯誤,重新開始
HAL_Delay(100);//等待
}
if(KEY_0())
{
BSP_LCD_login(24,108);
}
if(KEY_1())
{
BSP_LCD_img_DMA();
}
if(KEY_2())
{
// a2 = ADC_IN_2();
HAL_ADC_Start_IT(&hadc2);//無外接裝置,需master端自驅動重新整理資料
printf("ADC1=%04X ADC2=%04X ADC2=%04X \r\n",adc_val[0],adc_val[1],Adc_Value);
OLED_printf(10,108,"ADC1=%04X ADC2=%04X ADC2=%04X ",adc_val[0],adc_val[1],Adc_Value);//向lcd發送字元串
}
/* USER CODE END WHILE */
編譯下載下傳,資料隻有中斷讀取的變更了,而DMA讀取的不重新整理變化,顯然PC3引腳共用帶來的問題(先留個疑問,哈哈):