STM32F303系列微控制器一般具有多個12位元逐次逼近型(Successive approximatio)模數轉換器(ADC,analog-to-digital converter)。STM32的ADC功能很多:單端取樣、差分取樣、主從模式、雙ADC模式、注入模式等。本文作為學習筆記,記錄最簡單的單端(single-end)模式.
上圖是STM32ADC的框圖,黑色箭頭我自己加的開關。其轉換流程大概可以簡略為:
STM32F3系列的微控制器的ADC時鐘有兩路,一路為同步時鐘(黑色),一路為非同步時鐘(藍色)。非同步時鐘除了比同步時鐘具有更多的分頻選擇,其他沒有大的區別,其時鐘最高頻率也是相同的。
但是建議採用同步時鐘,使用非同步時鐘時或許可能轉換會出現問題,我也不知道什麼原因。
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位數越大,其解析度越高,採集到的電壓相對越準確。
觸發訊號對於ADC轉換流程中的第一步,即告訴ADC什麼時候開始轉換。觸發訊號有很多種,常用的有:軟體觸發、定時器訊號觸發和外部觸發等。
取樣時間對應ADC轉換流程中的第二步,即開關摁下多久,外部訊號對\(C_{ADC}\)充電多長時間。一般來說取樣時間以ADC時鐘週期\(T_{ADC}\)的倍數,如:\(1.5T_{ADC}\)、\(2.5T_{ADC}\)、\(19.5T_{ADC}\)等。一般來說取樣時間越長,訊號取樣越準確,一般是根據外電阻的大小來選擇取樣時間,具體取樣時間選擇可以參照下表:
例如:外電阻為5k,外電阻介入2.7k~8.2k之間,則可以選擇的取樣時間為\(61.5T_{ADC}\)
轉換時間指的是ADC將制定電壓轉為為數位訊號所用的時間,這個時間一般不可以控制,與ADC的時鐘週期有關,時鐘週期越長則轉換時間越小。
選取ADC1-IN1通道作為檢測通道,選擇單端模式Single-ended。
重要引數介紹:
使用LL庫,剩下的按照常規設定就行。
勾選Reset and Run,否則下載程式後微控制器不會自動執行,復位後才會執行。
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 */
}
上述設定完後,使用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
}
主函數就比較簡單,使用軟體觸發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--;
}
}
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];
}//串列埠逐個傳送資料
}
使用函數訊號發生器,產生頻率為1kHz,幅值為3V的正弦波,經兩個2k電阻分壓後傳入ADC採集通道,電路圖如圖:
實驗波形在匿名上位機上顯示如圖:
可以看到:
至此完成了STM32最簡單的ADC單端取樣,STM32的ADC還有很多其他功能,待之後有時間再記錄。本文記錄難免有錯誤,如有錯誤,歡迎指出。