寫這個文章是用來學習的,記錄一下我的學習過程。希望我能一直堅持下去,我只是一個小白,只是想好好學習,我知道這會很難,但我還是想去做!
本文寫于:2025.04.07
STM32開發板學習——第22節: [7-2] AD單通道&AD多通道
- 前言
- 開發板說明
- 引用
- 解答和科普
- 一、AD單通道
- 二、AD多通道
- 問題
- 總結
前言
? ?本次筆記是用來記錄我的學習過程,同時把我需要的困難和思考記下來,有助于我的學習,同時也作為一種習慣,可以督促我學習,是一個激勵自己的過程,讓我們開始32單片機的學習之路。
? ?歡迎大家給我提意見,能給我的嵌入式之旅提供方向和路線,現在作為小白,我就先學習32單片機了,就跟著B站上的江協科技開始學習了.
? ?在這里會記錄下江協科技32單片機開發板的配套視頻教程所作的實驗和學習筆記內容,因為我之前有一個開發板,我大概率會用我的板子模仿著來做.讓我們一起加油!
? ?另外為了增強我的學習效果:每次筆記把我不知道或者問題在后面提出來,再下一篇開頭作為解答!
開發板說明
? ?本人采用的是慧凈的開發板,因為這個板子是我N年前就買的板子,索性就拿來用了。另外我也購買了江科大的學習套間。
? ?原理圖如下
1、開發板原理圖
2、STM32F103C6和51對比
3、STM32F103C6核心板
視頻中的都用這個開發板來實現,如果有資源就利用起來。另外也計劃實現江協科技的套件。
下圖是實物圖
引用
【STM32入門教程-2023版 細致講解 中文字幕】
還參考了下圖中的書籍:
STM32庫開發實戰指南:基于STM32F103(第2版)
數據手冊
解答和科普
一、AD單通道
PA0口,PA0到PB1這10個引腳是ADC的10個通道,所以可以任意接。
第一步,開啟RCC時鐘,包括ADC和GPIO的時鐘,另外這里ADCCLK的分頻器,也需要設置一下。
第二步,配置GPIO,把需要用的GPIO配置成模擬輸入的模式。
第三步,配置這里的多路開關,把左邊的通道接入到右邊的規則組列表里,這個就是我們之前說的點菜,把各個通道的菜,列在菜單里。
第四步,配置ADC轉換器了,在庫函數里,是用結構體來配置的,可以配置這一大塊電路的參數,包括ADC是單次轉換還是連續轉換,掃描還是非掃描、有幾個通道,觸發源是什么,數據對齊是左對齊還是右對齊。
如果你需要模擬看門狗,那會有幾個函數用來配置閾值和監測通道的,如果你想開啟中斷,那就在中斷輸出控制里用ITConfig函數開啟對應的中斷輸出,然后再在NVIC中,配置一下優先級,這樣就能觸發中斷了。
第五步,開關控制,調用一下ADC_Cmd函數,開啟ADC。這樣ADC就配置完成了。
當然在開啟ADC的時候,還可以進行校準,這樣可以減小誤差。
在ADC工作的時候,如果想要軟件觸發轉換,那會有函數可以觸發,如果想讀取轉換結果,那也會有函數可以讀取結果。
配置ADCCLK分頻器,可以對APB2的72Mhz時鐘選擇2、4、6、8分頻,輸入到ADCCLK,這就是這個函數的作用,是在RCC庫函數里面,不要忘了配置。
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
用來給ADC上電的,也就是開關控制;
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
開啟DMA輸出信號的,如果使用DMA轉運數據,那就得調用這個函數;
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
用于控制某個中斷,能不能通向NVIC;
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
控制校準
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
軟件觸發
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
不能判斷是不是轉換結束,轉換開始后馬上清除此位,這個函數是返回SWSTART的狀態,由于在轉換開始后立刻就清零了,所以這個函數的返回值和轉換是否結束,毫無關系。
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
判斷EOC標志位是否為1,判斷轉換是否結束;
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
間斷模式
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
給序列的每個組填寫指定的通道,就是填寫點菜菜單的過程。
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
是否允許外部觸發轉換;
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
ADC獲取轉換值,就是獲取AD轉換的數據寄存器,讀取轉換結果就要使用這個函數,
uint32_t ADC_GetDualModeConversionValue(void);
ADC獲取雙模式轉換值,這個是雙ADC模式讀取轉換結果的函數;
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
模擬看門狗配置
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
時鐘開啟
void AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //開啟ADC1的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //開啟GPIOA的時鐘
}
配置ADC CLOCK
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //分頻:72Mhz/6=12Mhz
配置GPIO
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);
選擇規則組的通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
現在的配置是:在規則組菜單列表的第一個位置,寫入通道0這個通道,就是在序列1的位置,寫入通道0,如果你還想在序列2的位置寫入其他的通道,那就復制一下這個代碼,把這個序列數改為2,然后指定你想要的通道,如果還行繼續填充菜單,那就在復制進行配置。
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1,ADC_Channel_3,2,ADC_SampleTime_55Cycles5);
結構體初始化ADC
ADC_InitStructure.ADC_ExternalTrigConv=;
ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ; //獨立 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //右對齊ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //軟件觸發沒有外部觸發 ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //連續轉換還是單詞轉換ADC_InitStructure.ADC_ScanConvMode=DISABLE; //掃描模式ADC_InitStructure.ADC_NbrOfChannel=1; //通道數目:總共用到幾個通道ADC_Init(ADC1,&ADC_InitStructure);ADC_Cmd(ADC1,ENABLE);ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1)== SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1)== SET);
獲取轉換值
uint16_t AD_GetValue(void)
{ADC_SoftwareStartConvCmd(ADC1,ENABLE); //軟件觸發,ADC開始轉換while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET); //查看是否轉換完成 68個周期 5.6usreturn ADC_GetConversionValue(ADC1); //讀取DR寄存器,會自動清理EOC標志位,不需要手動清除了
}
代碼
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "AD.h"uint16_t ADValue;
float Voltage;
int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1,2,"Hello STM32 MCU");OLED_ShowString(2,1,"ADValue:");OLED_ShowString(3,1,"Voltage:0.00V");while(1){ADValue=AD_GetValue();Voltage=(float) ADValue/4095 *3.3 ;OLED_ShowNum(2,9,ADValue,4);OLED_ShowNum(3,9,Voltage,1);OLED_ShowNum(3,11,(uint16_t)(Voltage*100)%100,2);Delay_ms(100);}
}
AD.CH
#include "stm32f10x.h" // Device headervoid AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //開啟ADC1的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //開啟GPIOA的時鐘RCC_ADCCLKConfig(RCC_PCLK2_Div6); //分頻:72Mhz/6=12MhzGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure); ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ; //獨立 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //右對齊ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //軟件觸發沒有外部觸發 ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //連續轉換還是單詞轉換ADC_InitStructure.ADC_ScanConvMode=DISABLE; //掃描模式ADC_InitStructure.ADC_NbrOfChannel=1; //通道數目:總共用到幾個通道ADC_Init(ADC1,&ADC_InitStructure);ADC_Cmd(ADC1,ENABLE);ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1)== SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1)== SET);
}uint16_t AD_GetValue(void)
{ADC_SoftwareStartConvCmd(ADC1,ENABLE); //軟件觸發,ADC開始轉換while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET); //查看是否轉換完成 68個周期 5.6usreturn ADC_GetConversionValue(ADC1); //讀取DR寄存器,會自動清理EOC標志位,不需要手動清除了
}
#ifndef __AD_H
#define __AD_Hvoid AD_Init(void);
uint16_t AD_GetValue(void);#endif
實驗現象
AD單通道
連續模式,非掃描
ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ; //獨立 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //右對齊ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //軟件觸發沒有外部觸發 ADC_InitStructure.ADC_ContinuousConvMode=ENABLE; //連續轉換還是單詞轉換ADC_InitStructure.ADC_ScanConvMode=DISABLE; //掃描模式ADC_InitStructure.ADC_NbrOfChannel=1; //通道數目:總共用到幾個通道ADC_Init(ADC1,&ADC_InitStructure);ADC_Cmd(ADC1,ENABLE);ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1)== SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1)== SET);ADC_SoftwareStartConvCmd(ADC1,ENABLE);
uint16_t AD_GetValue(void)
{return ADC_GetConversionValue(ADC1);
}
二、AD多通道
AO分別接在PA1、PA2、PA3口;
在掃描 模式下,你啟動列表之后,它里面每一個單獨的通道轉換完成之后不會產生標志位,也不會觸發中斷,你不知道某一個通道是不是轉換完成了,它只有在整個列表都轉換完成后,才會產生一次EOC標志位,才能觸發中斷,而這時前面的數據已經被覆蓋丟失了。
第二個問題是,AD轉換是非常快的,幾us,手動轉移太高,可以使用間斷模式,每轉換一個通道就暫停一次,等我們手動轉運之后,再繼續觸發,繼續下一次轉換;只能通過延遲足夠的時間,太蠻煩;
只需要在每次出發轉換之前,手動更改一下列表第一個位置的通道就行了,在轉換前,先制定一下通道,再啟動轉換,就可以實現多通道了。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "AD.h"uint16_t AD0,AD1,AD2,AD3;int main(void)
{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){AD0=AD_GetValue( ADC_Channel_0);AD1=AD_GetValue( ADC_Channel_1);AD2=AD_GetValue( ADC_Channel_2);AD3=AD_GetValue( ADC_Channel_3);OLED_ShowNum(1,5,AD0,4);OLED_ShowNum(2,5,AD1,4);OLED_ShowNum(3,5,AD2,4);OLED_ShowNum(4,5,AD3,4);Delay_ms(100);}
}
AD.ch
#include "stm32f10x.h" // Device headervoid AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //開啟ADC1的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //開啟GPIOA的時鐘RCC_ADCCLKConfig(RCC_PCLK2_Div6); //分頻:72Mhz/6=12MhzGPIO_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); ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ; //獨立 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //右對齊ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //軟件觸發沒有外部觸發 ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //連續轉換還是單詞轉換ADC_InitStructure.ADC_ScanConvMode=DISABLE; //掃描模式ADC_InitStructure.ADC_NbrOfChannel=1; //通道數目:總共用到幾個通道ADC_Init(ADC1,&ADC_InitStructure);ADC_Cmd(ADC1,ENABLE);ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1)== SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1)== SET);
}uint16_t AD_GetValue(uint8_t ADC_Channel)
{ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);ADC_SoftwareStartConvCmd(ADC1,ENABLE); //軟件觸發,ADC開始轉換while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET); //查看是否轉換完成 68個周期 5.6usreturn ADC_GetConversionValue(ADC1); //讀取DR寄存器,會自動清理EOC標志位,不需要手動清除了
}
#ifndef __AD_H
#define __AD_Hvoid AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);#endif
實驗現象
AD多通道
問題
總結
本節課主要是學習了AD通道的代碼配置,如何對每個部分配置:
第一步,開啟RCC時鐘,包括ADC和GPIO的時鐘,另外這里ADCCLK的分頻器,也需要設置一下。
第二步,配置GPIO,把需要用的GPIO配置成模擬輸入的模式。
第三步,配置這里的多路開關,把左邊的通道接入到右邊的規則組列表里,這個就是我們之前說的點菜,把各個通道的菜,列在菜單里。
第四步,配置ADC轉換器了,在庫函數里,是用結構體來配置的,可以配置這一大塊電路的參數,包括ADC是單次轉換還是連續轉換,掃描還是非掃描、有幾個通道,觸發源是什么,數據對齊是左對齊還是右對齊。
如果你需要模擬看門狗,那會有幾個函數用來配置閾值和監測通道的,如果你想開啟中斷,那就在中斷輸出控制里用ITConfig函數開啟對應的中斷輸出,然后再在NVIC中,配置一下優先級,這樣就能觸發中斷了。
第五步,開關控制,調用一下ADC_Cmd函數,開啟ADC。這樣ADC就配置完成了。
當然在開啟ADC的時候,還可以進行校準,這樣可以減小誤差。
最后啟動轉換,讀取轉換完成后的值。
多通道是單此轉換,把序列1的位置換成要讀的通道,實現了AD多通道的讀取。