STM32F3系列 ADC取樣單端取樣模式(基於LL庫)

2023-10-29 12:00:21

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的框圖,黑色箭頭我自己加的開關。其轉換流程大概可以簡略為:

  1. 接收到觸發訊號,摁下開關
  2. 外部電壓經過外部電阻(\(R_{AIN}\)),雜散電容(\(C_{parastitic}\))、ADC內部電阻(\(R_{ADC}\)),給取樣電容\(C_{ADC}\)充電。
  3. 充電完成,鬆開開關,轉換核心開始將電容上的電壓值轉為數位訊號。
  4. 轉換完成,轉換結果存入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還有很多其他功能,待之後有時間再記錄。本文記錄難免有錯誤,如有錯誤,歡迎指出。