STM32F3系列 ADC 單端取樣(基於LL庫)
- 晶片型號:STM32f303RBT6
- 開發軟體:MDK5 & CubeMX & VS Code
目錄
引言
STM32F303系列微控制器一般具有多個12位逐次逼近型(Successive approximatio)模數轉換器(ADC,analog-to-digital converter)。STM32的ADC功能很多:單端取樣、差分取樣、主從模式、雙ADC模式、注入模式等。本文作為學習筆記,記錄最簡單的單端(single-end)模式.
1 基礎知識
1.1ADC轉換基本流程
上圖是STM32ADC的框圖,黑色箭頭我自己加的開關。其轉換流程大概可以簡略為:
- 接收到觸發訊號,摁下開關
- 外部電壓經過外部電阻(\(R_{AIN}\)),雜散電容(\(C_{parastitic}\))、ADC內部電阻(\(R_{ADC}\)),給取樣電容\(C_{ADC}\)充電。
- 充電完成,鬆開開關,轉換核心開始將電容上的電壓值轉為數字訊號。
- 轉換完成,轉換結果存入ADC的資料暫存器中。
1.2 時鐘樹
STM32F3系列的微控制器的ADC時鐘有兩路,一路為同步時鐘(黑色),一路為非同步時鐘(藍色)。非同步時鐘除了比同步時鐘具有更多的分頻選擇,其他沒有大的區別,其時鐘最高頻率也是相同的。
但是建議採用同步時鐘,使用非同步時鐘時或許可能轉換會出現問題,我也不知道什麼原因。
1.3 關鍵引數
1.3.1 位數
ADC一般可以分為10bit、12bit、16bit等,這個bit就是指的位數。位數的含義就是能把參考電平分為2的多少次方份。
比如:10bit的ADC可以把參考電平分為1024份; 12bit的ADC可以把參考電平分為4096份。若是參考電壓為3.3V,則10bitADC可以分辨的最小電壓為\(3.3/(2^{10}) = 3.22mV\),12bitADC可以分辨的最小電壓為\(3.3/(2^{12}) = 0.806mV\)。
由此可見,ADC位數越大,其解析度越高,採集到的電壓相對越準確。
1.3.2 觸發訊號
觸發訊號對於ADC轉換流程中的第一步,即告訴ADC什麼時候開始轉換。觸發訊號有很多種,常用的有:軟體觸發、定時器訊號觸發和外部觸發等。
1.3.3 取樣時間
取樣時間對應ADC轉換流程中的第二步,即開關摁下多久,外部訊號對\(C_{ADC}\)充電多長時間。一般來說取樣時間以ADC時鐘週期\(T_{ADC}\)的倍數,如:\(1.5T_{ADC}\)、\(2.5T_{ADC}\)、\(19.5T_{ADC}\)等。一般來說取樣時間越長,訊號取樣越準確,一般是根據外電阻的大小來選擇取樣時間,具體取樣時間選擇可以參照下表:
例如:外電阻為5k,外電阻介入2.7k~8.2k之間,則可以選擇的取樣時間為\(61.5T_{ADC}\)
1.3.4 轉換時間
轉換時間指的是ADC將制定電壓轉為為數字訊號所用的時間,這個時間一般不可以控制,與ADC的時鐘週期有關,時鐘週期越長則轉換時間越小。
2 CubeMx 配置步驟
2.1 確定輸入通道
選取ADC1-IN1通道作為檢測通道,選擇單端模式Single-ended。
2.2 配置ADC
重要引數介紹:
- Mode:independence即獨立模式;
- Clock Prescale:選取同步時鐘最為ADC的時鐘,分頻係數為1,即ADC時鐘為72M
- Resolution(解析度):選取12bit
- Data Alignment(資料對其):一般選右對齊
- End of Conversion selection(轉換完成訊號):這個引數指定了何時ADC觸發DMA和中斷,有兩個引數End of single conversion(EOC) 與 End of sequence of conversion(EOS),即單次轉換完成和順序轉換完成,由於我們只有一個通道選擇這兩個一樣的,本次選擇EOC。
- OverRun behavior(覆寫行為):若使能這個功能,則在ADC上次資料還沒有讀取的時候,新的輸出產生時,會直接覆寫上次資料。
- Lower Power Auto Wait(低功耗自動等待):用於低功耗的功能。
- Enable Regular Conversations(使能常規轉換組):字面意思,使能常規組轉換。
- Number of Conversion(轉換數量):需要轉換的訊號有幾個。
- External Trigger Conversion Source(外部觸發源):觸發訊號是什麼?軟體、或定時器等。
- External Trigger Conversion Edge(外部觸發邊沿):指定觸發型別,上升沿觸發、或下降沿觸發等
- Rank 1:
- Channal(通道號):對應Channel1
- Sampling Time(取樣時間):對應上文的取樣時間;
- Offset Number(通道偏移):指定那個通道需要資料偏移。
- Offset(數值偏移):即在採集到的資料減去一個數字偏置。
- ADC_Injected Conversions(ADC注入模式):暫時不需要。
- Analog Watchdog1~3():看門狗功能,暫時不需要。
2.3 輸出設定
使用LL庫,剩下的按照常規配置就行。
2.4 MD5 設定
勾選Reset and Run,否則下載程式後微控制器不會自動執行,復位後才會執行。
3 程式解讀
3.1 ADC初始化
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
LL_ADC_InitTypeDef ADC_InitStruct = {0};
LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Peripheral clock enable */
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_ADC12); // 使能ADC時鐘
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); // 使能GPIOA時鐘
/**ADC1 GPIO Configuration
PA0 ------> ADC1_IN1
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; // 12bit解析度
ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;// 資料右對齊
ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;// 不使用低功耗模式
LL_ADC_Init(ADC1, &ADC_InitStruct);
ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;// 軟體觸發
ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;// 不使用掃描模式
ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;// 不使用斷續模式
ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;// 單次觸發單次轉換
ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_LIMITED;// 不使用DMA
ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; // 資料覆寫使能
LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV1;// 使能內部時鐘1分頻作為ADC時鐘
ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT;// ADC採取獨立模式
LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);
/* Enable ADC internal voltage regulator *//*使能ADC內部的穩壓器*/
LL_ADC_EnableInternalRegulator(ADC1);
/* Delay for ADC internal voltage regulator stabilization. */
/* Compute number of CPU cycles to wait for, from delay in us. */
/* Note: Variable divided by 2 to compensate partially */
/* CPU processing cycles (depends on compilation optimization). */
/* Note: If system core clock frequency is below 200kHz, wait time */
/* is only a few CPU processing cycles. */
uint32_t wait_loop_index;
wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
while(wait_loop_index != 0)
{
wait_loop_index--;
}
/** Configure Regular Channel
*/
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_1); // 設定通道1轉換次序為1
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SAMPLINGTIME_181CYCLES_5);// 取樣時間為181.5個ADC週期
LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SINGLE_ENDED);// 取樣模式為單端取樣
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
3.2 校準和啟動ADC
上述配置完後,使用LL庫還需要一些額外的程式碼,ADC才可以正常工作。
void Mx_ADC_Start(void)
{
uint8_t TimeDelta = LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES;
LL_ADC_StartCalibration(ADC1,LL_ADC_SINGLE_ENDED); // 開始校準
while(LL_ADC_IsCalibrationOnGoing(ADC1)) // 等待校準完成
;
LL_ADC_SetCalibrationFactor(ADC1,LL_ADC_SINGLE_ENDED LL_ADC_GetCalibrationFactor(ADC1,LL_ADC_SINGLE_ENDED));// 將校準向量寫 ADC1中
while(TimeDelta > 0) // 校準後延時
{
TimeDelta--;
}
LL_ADC_Enable(ADC1); // 使能ADC
}
3.3 主函式配置
主函式就比較簡單,使用軟體觸發ADC轉換,待ADC轉換完成後,使用視窗將轉換資料經串列埠上傳到上位機。串列埠程式用的為匿名上位機。
while (1)
{
LL_ADC_REG_StartConversion(ADC1); // 用軟體觸發ADC轉換
while(LL_ADC_REG_IsConversionOngoing(ADC1)) // 等待ADC轉換完成
;
sent_data(LL_ADC_REG_ReadConversionData12(ADC1),0,0,0);// 使用匿名上機 ADC資料傳送到電腦
while(count) // 適當延遲
{
count--;
}
}
3.4 匿名上位機程式
uint8_t BUFF[30];
void sent_data(uint16_t A,uint16_t B,uint16_t C,uint16_t D)
{
int i;
uint8_t sumcheck = 0;
uint8_t addcheck = 0;
uint8_t _cnt=0;
BUFF[_cnt++]=0xAA;//幀頭
BUFF[_cnt++]=0xFF;//目標地址
BUFF[_cnt++]=0XF1;//功能碼
BUFF[_cnt++]=0x08;//資料長度
BUFF[_cnt++]=(A&0x00ff);//資料內容,小段模式,低位在前
BUFF[_cnt++]=(A&0xff00)>>8;//需要將位元組進行拆分,呼叫上面的宏定義即可。
BUFF[_cnt++]=(B&0x00ff);
BUFF[_cnt++]=(B&0xff00)>>8;
BUFF[_cnt++]=(C&0x00ff);//資料內容,小段模式,低位在前
BUFF[_cnt++]=(C&0xff00)>>8;//需要將位元組進行拆分,呼叫上面的宏定義即可。
BUFF[_cnt++]=(D&0x00ff);
BUFF[_cnt++]=(D&0xff00)>>8;
//SC和AC的校驗直接抄最上面上面簡介的即可
for(i=0;i<BUFF[3]+4;i++)
{
sumcheck+=BUFF[i];
addcheck+=sumcheck;
}
BUFF[_cnt++]=sumcheck;
BUFF[_cnt++]=addcheck;
for(i=0;i<_cnt;i++)
{
while ((USART1->ISR & 0X40) == 0)
; /* 等待上一個字元傳送完成 */
USART1->TDR=BUFF[i];
}//串列埠逐個傳送資料
}
4 實驗波形
使用函式訊號發生器,產生頻率為1kHz,幅值為3V的正弦波,經兩個2k電阻分壓後傳入ADC採集通道,電路圖如圖:
實驗波形在匿名上位機上顯示如圖:
可以看到:
- 波形為正弦波
- 最高值 1863 對應 1.501V
證明ADC採集正確。
5 總結
至此完成了STM32最簡單的ADC單端取樣,STM32的ADC還有很多其他功能,待之後有時間再記錄。本文記錄難免有錯誤,如有錯誤,歡迎指出。