魏永生
(吉林通用航空職業技術學院,吉林 吉林 132000)
制作一個模擬量定時采集轉換數據采集卡,其采集電壓在5V以內,要求定時控制采集,根據控制硬件要求,單片機芯片選用STM32F103C8T6芯片,該芯片具有ARM32-bit Cortex-M3內核,2個12bit的ADC,型號中的C代表引腳數目是48腳,8代表閃存存儲容量為64K,T代表封裝,6代表工業級,工作溫度最低為-40℃,可在北方冬季野外使用。STM32系列的單片機適用于C語言進行編程燒錄[1],所以本次程序設計采用C語言編程,編譯環境為keil5編程軟件,下載器為ST-LINK。
STM32的ADC輸入通道和AVR類似,采用PA口引腳作為ADC輸入端,此次數據采集的接口ADC通道0在PA0上,要設置PA0為模擬輸入模式,這里先完成STM32 I/O口的初始化與編程,以設置PA0為模擬輸入模為例,程序為:
GPIO_InitTypeDef GPIO_InitStructure;
//定義一個結構體變量
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//賦值0X0,模擬輸入模式
第一條語句定義了一個變量名為G P I O _InitStructure的變量,是一個私有數據聲明,變量定義了之后,可將某一GPIO口的對應位初始化,之所以這么定義,是因為STM32的初始化函數多采用結構體形式,C語言中,typedef是用于定義新的類型名,在編譯時可以用新定義的類型名來代替已有的類型名,在STM32的頭文件中已有的GPIO初始化程序段:
Typedef struct{GPIOSpeed_TypeDef GPIO_Speed;GPIOMode_TypeDefGPIO_Mode;}
GPIO_InitTypeDef;
上面的初始化IO口函數中,用typedef定義了一個GPIO_InitTypeDef類型名的結構體,所以在編程時可以在主函數開頭用這個結構體再定義一個變量。初始化定義后還要通過
GPIO_Init(GPIOA , &GPIO_InitStructure)程序將結構體變量地址以實參的形式送給形參
GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct),其中* GPIO_InitStruct是指向結構GPIO_InitTypeDef的指針。同樣,ADC的定時觸發還要設置TIM2的CH2通道為復用推挽輸出,此時對應的IO位的輸出數據不是由GPIO的輸出數據寄存器ODR提供,而是由對應的功能模塊來提供數據進行推挽輸出。
STM32的單片機引腳允許輸入電壓為3.3V,對于要采集的最大值為5V的電壓模擬量要通過兩個電阻串聯分壓,這里采用一個100K精密可調電位器來完成電壓變換。
要繼續編程,還要清楚單片機時鐘源,而STM32的5個時鐘從來源可分為內部RC振蕩器和石英晶體振蕩器兩類。石英晶體能夠產生一種穩定的正弦波,但頻率比較低,為了獲得較高頻率,STM32單片機采用了PLL鎖相環倍頻輸出,PLL一般選高速外部時鐘HSE作為時鐘源,連接關系如圖1所示,當HSE為8M晶振時,通過設置PLL的倍頻因子為9,得到72MHz官方推薦的穩定運行PLLCLK時鐘,當系統時鐘SYSCLK通過時鐘監視系統選擇PLLCLK時鐘時,SYSCLK就為穩定的72MHz。接下來AHB總線(advanced high performance bus)的分頻器將系統時鐘SYSCLK分頻后得到AHB總線時鐘,一般設置分頻系數為1,AHB總線時鐘也是72MHz。AHB總線時鐘送給5大模塊使用,本次項目涉及的是外圍總線APB(advanced peripheral bus)時鐘,其按外設使用速度又分為APB1和APB2兩個外設總線時鐘。

圖1 STM32 單片機外設時鐘源示意圖
如圖1所示,高速設備使用的時鐘PCLK2來自APB2分頻器,主要供給UART1、SPI1、Timer1、ADC1、ADC2、PA~PE普通IO口等,分頻因子一般設置為1分頻,即PCLK2等于系統時鐘72M。低速外設使用的時鐘為PCLK1,是由APB1預分頻2分頻得到的低速總線時鐘,所以最高為36M,供給通用定時器、CAN、USB、I2C1、I2C2、UART2、3等,模擬接口PA0的時鐘來自于APB2,時鐘是通過開關電路供給的,開啟接口A時鐘程序程如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//開啟GPIOA的時鐘
定時采集要用到定時器,STM32F103C8T6是中容量的芯片,有1個高級16位定時器TIME1和3個16位通用定時器 TIME2~TIME4。通用定時器主要由16位計數器和與其相關的自動裝載寄存器、16位預分頻器和4個獨立通道構成。計數器可以向上計數、向下計數或者向上向下雙向計數。如圖1所示,通用定時器的內部時鐘CK_INT(Clock Internal)不是直接來自APB1,而是來自于APB1的后一級的一個倍頻器,如TIM 2MUL,當APB1的預分頻系數為1時,這個倍頻器不起作用,定時器的時鐘頻率等于APB1的頻率;當APB1的預分頻系數為其他數值(即預分頻系數為2、4、8或16)時,這個倍頻器起作用,定時器的時鐘頻率等于APB1的頻率兩倍[2]。例如AHB總線時鐘為72M時,APB1 總線時鐘為36M,那么TIME2的內部時鐘CK_INT就是APB1的2倍頻,即72M。此時,通用定時器的內部時鐘頻率等于系統時鐘頻率。
本項目采用TIME2定時器,由于 TIM2 是掛載在 APB1之下,APB1總線時鐘經TIM2MUL倍頻后還要通過RCC開關控制送到TIM2,成為TIM2內部時鐘CK_INT,所以我們通過APB1 總線下的使能函數來使能TIM2時鐘,程序如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //時鐘使能
定時器內部時鐘CK_INT在定時器內部還要經過PSC預分頻器,PSC 預分頻器輸出的是最終的時基時鐘CK_CNT,用來驅動計數器計數。PSC是一個16 位的預分頻器,可以對定時器時鐘 CK_INT 進行 1~65536 之間的任何一個數進行分頻。預分頻器工作原理其實是個計數器,其輸入的定時器時鐘脈沖每觸發一次,預分頻器計數器值加1,直到達到預分頻器的設定值,本系統中,通用定時器選擇TIM2,由TIM2預分頻器對內部的時鐘CK_INT進行向上加計數,內部的時鐘CK_INT的頻率在上面已經分析過了,等于系統時鐘頻率72MHz,要得到10kHz的脈沖需要72MHz/7200,一個脈沖周期T是0.1mS,那么是不是就設置定時器的預分頻器PSC=7200呢?當然不是,因為計數從0-7200需要7200個脈沖,7200-0還要再計一個脈沖計數器才歸零,這樣加起來就是7201個脈沖,所以分頻值=PSC+1。分頻后時基脈沖頻率是
CK_CNT=TIM2CLK/(PSC+1)=72M/7199=10kHz,其倒數即為周期,值為0.1mS,編寫TIM2初始程序可以調用子程序TIM2_Int_Init(9999,7199)賦值,也可以直接使用賦值語句:
TIM_TimeBaseStructure.TIM_Prescaler =7199;
//送給計數器的時鐘頻率10kHz
TIM_TimeBaseStructure.TIM_Period = 9999; //ARR初值,一周期是10000個脈沖
第二條程序中的9999是計數值,累計起來就是一個計數周期的時間。這里通用定時TIM2采用向上計數模式,最大計數值為 65535。在向上計數模式時,自動重載寄存器TIM2_ARR值來設置計數器上限值,自動重載寄存器有兩個,一個是用來存放ARR值,我們把它叫作TIM2_ARR,另一個是用來和計數器CNT比較,我們把它叫作實際自動重裝載寄存器,若計數器CNT值達到實際自動重裝載寄存器的值時產生更新事件,自動把自動重載寄存器TIM2_ARR值裝到實際的自動重裝載寄存器,即使TIM2_ARR的值不變也要重裝,計數器也溢出清零從頭開始計數。定時器的定時時間等于計數器的一個脈沖周期乘以脈沖數,脈沖數也就是ARR的值。計數器在CK_CNT的驅動下,計一個數的時間則是CK_CNT的倒數,
T=1/(CK_CNT/(PSC+1))=(PSC+1)/CK_CNT,本次編程中一個脈沖周期T=7200/72M=0.1mS,要產生一次中斷的時間是1s時,ARR初值=10000-1=9999,因為到9999還需要一個脈沖回零,一共是10000個脈沖,由此我們得到了定時時間的計算公式:
Tout=((ARR+1)*(PSC+1 ))/CK_CNT[3],其中CK_CNT是定時器時鐘頻率。
STM32103C8包含2個12位的ADC轉換器,把VREF+和VDDA接3V3,一般把VSSA和VREF-接地,ADC轉換所需的輸入時鐘ADC_CLK來自APB2總線的72M時鐘,那么APB2總線的72M時鐘要經過ADC_PSC分頻,分頻因子可以是2/4/6/8分頻,由于受最小轉換時間限制,ADC時鐘頻率最大不能超過14M,分頻因子要大于6才行,72M/6得到12M的ADC時鐘ADC_CLK,程序如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPROA,ENABLE);//使能ADC1和通道時鐘。
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//設置ADC分頻因子為6,
ADC 使用若干個 ADC_CLK 周期對輸入的電壓進行采樣,每個通道可以分別用不同的時間采樣。其中采樣周期最小是 1.5 個,即如果我們要達到最快的采樣,那么應該設置采樣周期為 1.5 個周期,這里說的周期就是 1/ADC_CLK。
因單片機ADC的轉換時間要1μs以上,所以最大轉換速率是1MHz,不同的程序中,轉換時間是不同的,只要大于1μs就可以,轉換時間不同主要是設置的采樣周期不同,也就是說ADC的轉換時間和采樣時間有關,轉換時間Tconv=采樣時間+12.5個周期[3]。當ADCLK=14MHz(最高)時,采樣時間設置為1.5周期(最快),那么總的轉換時間(最短)Tconv=1.5周期+12.5周期=14周期=1μs。其中12.5個周期是采集12位AD時間是固定的。若PCLK2=72M,經過ADC預分頻器能分頻到最大的時鐘是12M,采樣周期設置為最慢239.5個周期,ADC周期是239.5+12.5個周期,算出最短的轉換時間為21μs,也就是ADC得到一次轉換結果需要耗時21μs。
STM32的 ADC常規通道有6個外部觸發信號,外部是相對ADC而言,這6個觸發信號有5個來自芯片內部,包括TIM1的CC1-CC3和TM2的CC2事件等。這里選用TIM2的CC2來觸發ADC,TIM2首先要配置為PWM比較輸出模式才能生效,TIM2的CH2通道便成了ADC規則通道的觸發源,特別要強調的是需要每次比較匹配時,在TIM2_CH2上產生一次上升沿,當TIM2_CNT≦TIM2_CCR2時,通道CH2為低電平,TIM2_CNT≥TIM2_CCR2時,通道CH2為高電平。
TIM_OCInitStructure.TIM_Pulse = 5000;//設置TIM2_CCR2的值
上一程序也是定時器PWM的初始化程序,第一條語句是設置PWM比較寄存器的值為5000,在第5000個脈沖時定時器CH2通道進行電平變換,產生脈沖,由于PWM模式1和模式2的區別是輸出電平極性相反,PWM1在向上計數時,TIM2_CNT
TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_Low;//有效電平為低電平
CC2P輸入/捕獲2輸出極性( output polarity),OC2通道配置為輸出:0對應OC2高電平有效,1對應OC2低電平有效,TIM_OCPolarity_Low是低電平有效,所以CC2P值為1,要是PWM2模式則相反。
STM32的ADC轉換有規則通道和注入通道兩種通道,注入通道可以搶占式地打斷規則通道的采樣,和中斷類似,執行注入通道采樣后,再執行之前的規則通道采樣。在使用ADC的時候需要配置幾個參數,一是工作模式ADC_Mode,這里設置為獨立模式,在這個模式下,雙ADC不能同步,每個ADC接口獨立工作。掃描模式ADC_ScanConvMode,這里只是用了一個通道,DISABLE就可以了,如果使用了多個通道的話,則必須將其設置為ENABLE。ADC連續轉換模式,ADC_ContinuousConvMode,這里因為是定時采集,所以設置為DISABLE,即單次轉換,只轉換一次數據就停止,由定時器再次觸發轉換。ADC_ExternalTrigConv 是外部觸發模式設置,ADC_ExternalTrigConv_T2_CC2設置選擇外部觸發模式中的定時器2通道輸出觸發。ADC_DataAlign_Right設置為右對齊方式,這樣處理從低位開始傳輸的數據會比較方便,由于數據采集只有一個通道,所以ADC_NbrOfChannel 值為1。下面是ADC數據轉換的初始化程序:

上一初始化程序中的ADC_Cmd(ADC1,ENABLE)語句執行后使ADC_CR2寄存器的ADON位為1,ADON是開/關A/D轉換器,當該位為1時,寫入1將啟動轉換。
外部觸發允許后,產生對應的外部觸發脈沖,就會開始ADC1的規則通道轉換啟動功能。while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC))是等待轉換結束,ADC狀態寄存器ADC_SR的第1位EOC是轉換結束位,轉換結束后EOC=1,我們可查詢該位來判斷轉換結束,該位不用軟件清零,當從ADC_DR讀取數據后,該位自動清0。
讀出ADC_DR數據的程序可以用賦值語句data1=ADC_GetConversionValue(ADC1);
由于是12位ADC,所以讀取的值最大是4095,還要進行運算處理。
ADC的輸入電壓范圍設定在0~3.3V,因為ADC是12位的,數據采用右對齊方式時,12位滿量程對應的數字值0xFFF即4095,此時對應的輸入電平并非AD的參考電壓值VREF,而是參考電壓值減去一個LSB,1LSB=VREF+/4096。數值0對應的就是0V。如果轉換后的數值X對應的模擬電壓為Y,那么會有4096/3.3=X/Y,Y=(3.3*X)/4096。
如果說編程是理論基礎,程序調試是理實結合的關鍵環節,好的調試方案可以起到事半功倍的效果,該項目采用三步走,先調試TIM2通道2的PWM輸出,調試時注意CCR2給定值要在定時器ARR范圍內,為了便于觀測占空比,PWM頻率可臨時調高一些,該項目設置CCR2為ARR的1/2,占空比為50%,便于用示波器測量比較,之后再恢復1Hz,用萬用表或示波器測量,顯示電路調試時可先用一個整數測試。最后的是ADC采集數據調試,可測量3.3V,顯示采集到的4095說明ADC電路正常工作,再調試計算電壓值程序,ADC調試的前提是要保證硬件芯片外圍的ADC電壓正確,再逐條從ADC初始化和讀取程序上查找問題,在ADC查詢等待時可把顯示子程序放入等待語句中執行,防止數碼管黑屏。
通過數據采集系統的編程和調試,深切體會到STM32單片機系統時鐘源分配在定時數據采集項目設計和編程上的重要性,它關系到各部分的參數設置和使用。項目采用外部定時器觸發ADC采集到的數據時間間隔準確,定時和數據采集都由單片機硬件完成,軟件只需初始化相關硬件即可,運行中不過多占用軟件資源、沒有中斷,高效可靠。該項目探索了STM32F103C8單片機外部定時器觸發ADC轉換的條件和實現方法,總結起來就是先要將定時器配置為比較輸出PWM模式,OC通道復用推挽輸出,TIM2_CH2一定是上升沿才能實現預定要求觸發。TIM2_CH2輸出的電壓信號內部接至ADC電路,不需要連線,但TIM2_CH2的PA1引腳可作為調試的測試點,調試PWM功能。ADC初始化很重要,要設置ADC_ExternalTrigConvCmd(ADC1,E N A B L E)外部觸發啟動功能,此時就不需要A D C_SoftwareStartConvCmd(ADC1, DISABLE)軟件啟動語句了,在查詢等待時可把顯示子程序放入等待語句中執行,防止數碼管黑屏,這個項目通過ADC外部觸發功能實現了高精度的模擬量定時采集和轉換,是一次STM32單片機定時、PMW和ADC功能的實踐探索和綜合應用。