一、前言
該項目是基于正點原子精英板制作的一個簡易示波器,可以讀取信號的頻率和幅值,并可以通過按鍵改變采樣頻率和控制屏幕的更新暫停。
二、硬件接線
將PA6與PA4相連,可觀察到正弦波。
將PA6與PA5相連,可觀察到三角波/噪聲(默認三角波)。
KEY_UP控制波形的更新和暫停。
KEY_1降低采樣率。
KEY_0提高采樣率。
三、信號的采集
信號的采集主要是依靠ADC(通過定時器觸發(fā)采樣,與在定時器中斷中開啟一次采樣的效果類似,以此來控制采樣的間隔時間相同),然后通過DMA將所采集的數(shù)據(jù)從ADC的DR寄存器轉移到一個變量中,此時完成一次采樣。
由于設定采集一次完整的波形需要1024個點,即需要連續(xù)采集1024次才算一次完整的波形采樣(需要采集1024個點的原因在后面會提到)。
因此我們還需創(chuàng)建一個數(shù)組用于存儲這些數(shù)據(jù),并在DMA中斷中,將成功轉移到變量中的數(shù)據(jù)依次存儲進數(shù)組(注意此數(shù)組中存入的數(shù)據(jù)是12位的數(shù)字量,還未做回歸處理),完成1024個數(shù)據(jù)的采樣和儲存,用于后續(xù)在LCD上進行波形的顯示和相關參數(shù)的處理。
此案例用到的是ADC1的通道6(即PA6口)進行數(shù)據(jù)的采樣,主要需注意將ADC轉換的觸發(fā)方式改為定時器觸發(fā)(我用的是定時器2的通道2進行觸發(fā),由于STM32手冊提示只有在上升沿時可以觸發(fā)ADC,因此我們需要讓定時器2的通道2每隔固定的時間產(chǎn)生一個上升沿)。
將定時器2設置成PWM模式,即可令ADC1在定時器2的通道2每產(chǎn)生一次上升沿時觸發(fā)采樣,后續(xù)即可通過改變PWM的頻率(即定時器的溢出頻率),便可控制采樣的頻率。
四、代碼配置
ADC的配置:
/********************************************************** 簡介:ADC1-CH6初始化函數(shù) ***********************************************************/?????????????????? void??Adc_Init(void) {?? ?ADC_InitTypeDef?ADC_InitStructure;? ?GPIO_InitTypeDef?GPIO_InitStructure; ?RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA?|RCC_APB2Periph_ADC1,?ENABLE?);???//使能ADC1通道時鐘 ? ?RCC_ADCCLKConfig(RCC_PCLK2_Div6);???//設置ADC分頻因子6?72M/6=12,ADC最大時間不能超過14M ?//PA6?作為模擬通道輸入引腳????????????????????????? ?GPIO_InitStructure.GPIO_Pin?=?GPIO_Pin_6; ?GPIO_InitStructure.GPIO_Mode?=?GPIO_Mode_AIN;??//模擬輸入 ?GPIO_InitStructure.GPIO_Speed?=?GPIO_Speed_50MHz; ?GPIO_Init(GPIOA,?&GPIO_InitStructure);? ?ADC_DeInit(ADC1);??//復位ADC1,將外設?ADC1?的全部寄存器重設為缺省值 ?ADC_InitStructure.ADC_Mode?=?ADC_Mode_Independent;?//ADC工作模式:ADC1工作在獨立模式 ?ADC_InitStructure.ADC_ScanConvMode?=?DISABLE;?//模數(shù)轉換工作在單通道模式 ?ADC_InitStructure.ADC_ContinuousConvMode?=?DISABLE;?//模數(shù)轉換工作在非連續(xù)轉換模式 ?ADC_InitStructure.ADC_ExternalTrigConv?=?ADC_ExternalTrigConv_T2_CC2;?//轉換由定時器2的通道2觸發(fā)(只有在上升沿時可以觸發(fā)) ?ADC_InitStructure.ADC_DataAlign?=?ADC_DataAlign_Right;?//ADC數(shù)據(jù)右對齊 ?ADC_InitStructure.ADC_NbrOfChannel?=?1;?//順序進行規(guī)則轉換的ADC通道的數(shù)目 ?ADC_Init(ADC1,?&ADC_InitStructure);?//根據(jù)ADC_InitStruct中指定的參數(shù)初始化外設ADCx的寄存器??? ?ADC_Cmd(ADC1,?ENABLE);?//使能指定的ADC1 ? ?ADC_DMACmd(ADC1,?ENABLE);?//ADC的DMA功能使能 ? ?ADC_ResetCalibration(ADC1);?//使能復位校準?? ?? ?ADC_RegularChannelConfig(ADC1,?ADC_Channel_6,?1,?ADC_SampleTime_1Cycles5?);//ADC1通道6,采樣時間為239.5周期?? ?? ?ADC_ResetCalibration(ADC1);//復位較準寄存器 ?? ?while(ADC_GetResetCalibrationStatus(ADC1));?//等待復位校準結束 ? ?ADC_StartCalibration(ADC1);??//開啟AD校準 ? ?while(ADC_GetCalibrationStatus(ADC1));??//等待校準結束 ? ?ADC_SoftwareStartConvCmd(ADC1,?ENABLE);??//使能指定的ADC1的軟件轉換啟動功能 }???
定時器的配置:
/****************************************************************** 函數(shù)名稱:TIM2_PWM_Init(u16?arr,u16?psc) 函數(shù)功能:定時器3,PWM輸出模式初始化函數(shù) 參數(shù)說明:arr:重裝載值 ?? psc:預分頻值 備????注:通過TIM2-CH2的PWM輸出觸發(fā)ADC采樣 *******************************************************************/?? void?TIM2_PWM_Init(u16?arr,u16?psc) {?? ?GPIO_InitTypeDef?GPIO_InitStructure; ?TIM_TimeBaseInitTypeDef??TIM_TimeBaseStructure; ?TIM_OCInitTypeDef??TIM_OCInitStructure; ? ?RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,?ENABLE);?//使能定時器2時鐘 ??RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA??|?RCC_APB2Periph_AFIO,?ENABLE);??//使能GPIO外設和AFIO復用功能模塊時鐘 ? ???//設置該引腳為復用輸出功能,輸出TIM2?CH2的PWM脈沖波形?GPIOA.1 ?GPIO_InitStructure.GPIO_Pin?=?GPIO_Pin_1;?//TIM_CH2 ?GPIO_InitStructure.GPIO_Mode?=?GPIO_Mode_AF_PP;??//復用推挽輸出 ?GPIO_InitStructure.GPIO_Speed?=?GPIO_Speed_50MHz; ?GPIO_Init(GPIOA,?&GPIO_InitStructure);//初始化GPIO ? ???//初始化TIM3 ?TIM_TimeBaseStructure.TIM_Period?=?arr;?//設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 ?TIM_TimeBaseStructure.TIM_Prescaler?=psc;?//設置用來作為TIMx時鐘頻率除數(shù)的預分頻值? ?TIM_TimeBaseStructure.TIM_ClockDivision?=?0;?//設置時鐘分割:TDTS?=?Tck_tim ?TIM_TimeBaseStructure.TIM_CounterMode?=?TIM_CounterMode_Up;??//TIM向上計數(shù)模式 ?TIM_TimeBaseInit(TIM2,?&TIM_TimeBaseStructure);?//根據(jù)TIM_TimeBaseInitStruct中指定的參數(shù)初始化TIMx的時間基數(shù)單位 ? ?//初始化TIM2?Channel2?PWM模式?? ?TIM_OCInitStructure.TIM_OCMode?=?TIM_OCMode_PWM1;?//選擇定時器模式:TIM脈沖寬度調制模式2 ??TIM_OCInitStructure.TIM_OutputState?=?TIM_OutputState_Enable;?//比較輸出使能 ?TIM_OCInitStructure.TIM_OCPolarity?=?TIM_OCPolarity_Low;?//輸出極性:TIM輸出比較極性高 ?TIM_OCInitStructure.TIM_Pulse=1000;?//發(fā)生反轉時的計數(shù)器數(shù)值,用于改變占空比 ?TIM_OC2Init(TIM2,?&TIM_OCInitStructure);??//根據(jù)T指定的參數(shù)初始化外設TIM2 ?TIM_CtrlPWMOutputs(TIM2,?ENABLE);//使能PWM輸出 ? ?TIM_Cmd(TIM2,?ENABLE);??//使能TIM2 }
DMA配置:
/****************************************************************** 函數(shù)名稱:MYDMA1_Config() 函數(shù)功能:DMA1初始化配置 參數(shù)說明:DMA_CHx:DMA通道選擇 ?? cpar:DMA外設ADC基地址 ?? cmar:DMA內存基地址 ???cndtrDMA通道的DMA緩存的大小 備????注: *******************************************************************/ void?MYDMA1_Config(DMA_Channel_TypeDef*?DMA_CHx,u32?cpar,u32?cmar,u16?cndtr) { ?DMA_InitTypeDef?DMA_InitStructure; ?NVIC_InitTypeDef?NVIC_InitStructure; ? ??RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,?ENABLE);?//使能DMA傳輸 ? ????DMA_DeInit(DMA_CHx);???//將DMA的通道1寄存器重設為缺省值 ?DMA_InitStructure.DMA_PeripheralBaseAddr?=?cpar;??//DMA外設ADC基地址 ?DMA_InitStructure.DMA_MemoryBaseAddr?=?cmar;??//DMA內存基地址 ?DMA_InitStructure.DMA_DIR?=?DMA_DIR_PeripheralSRC;??//數(shù)據(jù)傳輸方向,從外設讀取發(fā)送到內存// ?DMA_InitStructure.DMA_BufferSize?=?cndtr;??//DMA通道的DMA緩存的大小 ?DMA_InitStructure.DMA_PeripheralInc?=?DMA_PeripheralInc_Disable;??//外設地址寄存器不變 ?DMA_InitStructure.DMA_MemoryInc?=?DMA_MemoryInc_Enable;??//內存地址寄存器遞增 ?DMA_InitStructure.DMA_PeripheralDataSize?=?DMA_PeripheralDataSize_HalfWord;??//數(shù)據(jù)寬度為16位 ?DMA_InitStructure.DMA_MemoryDataSize?=?DMA_MemoryDataSize_HalfWord;?//數(shù)據(jù)寬度為16位 ?DMA_InitStructure.DMA_Mode?=?DMA_Mode_Circular;??//工作在循環(huán)模式 ?DMA_InitStructure.DMA_Priority?=?DMA_Priority_High;?//DMA通道?x擁有高優(yōu)先級? ?DMA_InitStructure.DMA_M2M?=?DMA_M2M_Disable;??//DMA通道x沒有設置為內存到內存?zhèn)鬏??DMA_Init(DMA_CHx,?&DMA_InitStructure);??//ADC1匹配DMA通道1 ? ?DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE);?//使能DMA傳輸中斷? ? ?//配置中斷優(yōu)先級 ?NVIC_InitStructure.NVIC_IRQChannel?=?DMA1_Channel1_IRQn; ?NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0?; ?NVIC_InitStructure.NVIC_IRQChannelSubPriority?=?0;?? ?NVIC_InitStructure.NVIC_IRQChannelCmd?=?ENABLE;??? ?NVIC_Init(&NVIC_InitStructure);? ?DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA通道 }
注意:
由于在設置PWM時將TIM_Pulse默認設置為1000,因此在初始化定時器2時,TIM_Period的值不能小于該值,可自行修改。TIM_Pulse的值并不會影響采樣頻率。
采樣頻率= 定時器2溢出頻率=SYSCLK/預分頻值/溢出值因此如果將TIM_Pulse設為1,TIM_Period設為2,TIM_Prescaler設為1,理論上采樣頻率最高可達36Mhz。
五、數(shù)據(jù)的處理
數(shù)據(jù)的處理主要是要求出信號的頻率和幅值等相關參數(shù)。幅值可以通過找出之前存儲1024個點的數(shù)組中最大最小值,回歸處理過后算出差值。
難點主要在于頻率的求取。一個信號中可能包含多種頻率成分,而我顯示的是幅值最大的頻率分量(當然其他頻率也可獲得)。這里便用到了STM32提供的DSP庫中的FFT(快速傅里葉變換),DSP庫在最后的源碼中有。
需要采樣1024個點的原因:FFT算法要求樣本數(shù)為2的n次方,而DSP庫中提供了64,256和1024樣本數(shù)對應的庫函數(shù),因此選用1024最大樣本數(shù)可以使頻率分辨率最小,更加精確。(定義頻率分辨率f0=fs/N,其中fs等于采樣率,N為采樣點數(shù))
需注意:FFT后的輸出不是實際的信號頻率,需要經(jīng)過轉換。f(k)=k*(fs/N),其中f(k)是實際頻率,k是實際信號的最大幅度頻率所對應的數(shù)。(詳見下面代碼,分享的源代碼中公式有誤,未重新上傳)
獲取頻率的函數(shù):
#define?NPT?1024//一次完整采集的采樣點數(shù) /****************************************************************** 函數(shù)名稱:GetPowerMag() 函數(shù)功能:計算各次諧波幅值 參數(shù)說明: 備??注:先將lBufOutArray分解成實部(X)和虛部(Y),然后計算幅值(sqrt(X*X+Y*Y) *******************************************************************/ void?GetPowerMag(void) { ????float?X,Y,Mag,magmax;//實部,虛部,各頻率幅值,最大幅值 ????u16?i; ? ?//調用自cr4_fft_1024_stm32 ?cr4_fft_1024_stm32(fftout,?fftin,?NPT);? ?//fftin為傅里葉輸入序列數(shù)組,ffout為傅里葉輸出序列數(shù)組 ? ????for(i=1;?i>?16; ??Y?=?(fftout[i]?>>?16); ?? ??Mag?=?sqrt(X?*?X?+?Y?*?Y);? ??FFT_Mag[i]=Mag;//存入緩存,用于輸出查驗 ??//獲取最大頻率分量及其幅值 ??if(Mag?>?magmax) ??{ ???magmax?=?Mag; ???temp?=?i; ??} ????} ?F=(u16)(temp*(fre*1.0/NPT));//源代碼中此公式有誤,將此復制進去 ? ?LCD_ShowNum(280,180,F,5,16); }?
六、模擬正弦波輸出
此正弦波輸出是用于調試示波器,觀察顯示和實際是否相同。主要利用DAC輸出,在定時器3的中斷中不斷改變DAC的輸出值,產(chǎn)生一個正弦波。因此改變正弦波的頻率可以通過更改定時器3的溢出頻率。(采用的PA4口進行輸出)
在初始化時,我將定時器3的重裝載值設置為40,預分頻值設置為72,正弦波輸出頻率為72Mhz/40/72/1024≈24.5Hz(1024是因為將一個周期正弦波均分成1024個輸出點,詳見下面函數(shù)InitBufInArray())。
經(jīng)采樣處理后顯示為24-25Hz,與實際值接近。(但是當采樣頻率提高到最大3.6kHz時,頻率顯示為32Hz左右,原因未知)
下面是相關代碼:
u16?magout[NPT]; /****************************************************************** 函數(shù)名稱:InitBufInArray() 函數(shù)功能:正弦波值初始化,將正弦波各點的值存入magout[]數(shù)組中 參數(shù)說明: 備????注: *******************************************************************/ void?InitBufInArray(void) { ????u16?i; ????float?fx; ????for(i=0;?i=NPT) ??i=0; }
七、模擬噪聲或三角波輸出
模擬噪聲或三角波輸出可直接通過配置DAC,利用芯片內部的發(fā)生器產(chǎn)生。DAC2的轉換由定時器4的TRGO觸發(fā)(事件觸發(fā))。同時需要注意設置TRGO由更新事件產(chǎn)生。
若為三角波輸出,頻率=72Mhz/定時器重裝載值/預分頻系數(shù)/幅值/2;
例如:初始化定時器的重裝載值為2,預分頻系數(shù)為36,幅值為最大(4096),即Freq=72Mhz/2/36/4096/2≈122Hz;
具體代碼如下所示:
void?Dac2_Init(void)
{ ????GPIO_InitTypeDef?GPIO_InitStructure; ????DAC_InitTypeDef?DAC_InitType; ????RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,?ENABLE?);???//使能PORTA通道時鐘 ????RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,?ENABLE?);???//使能DAC通道時鐘? ????GPIO_InitStructure.GPIO_Pin?=?GPIO_Pin_5;?????//?端口配置 ????GPIO_InitStructure.GPIO_Mode?=?GPIO_Mode_AIN;????//模擬輸入 ????GPIO_InitStructure.GPIO_Speed?=?GPIO_Speed_50MHz; ????GPIO_Init(GPIOA,?&GPIO_InitStructure); ????? ????DAC_InitType.DAC_Trigger=DAC_Trigger_T4_TRGO;?//定時器4觸發(fā) ????DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_Noise;//產(chǎn)生噪聲 ????//DAC_WaveGeneration_Triangle產(chǎn)生三角波 ????DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude?=??DAC_TriangleAmplitude_4095;//幅值設置為最大,即3.3V ????DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable?;?//DAC1輸出緩存關閉?BOFF1=1 ????DAC_Init(DAC_Channel_2,&DAC_InitType);??//初始化DAC通道2 ????DAC_Cmd(DAC_Channel_2,?ENABLE);??//使能DAC-CH2 ? ????DAC_SetChannel1Data(DAC_Align_12b_R,?0);??//12位右對齊數(shù)據(jù)格式設置DAC值? }
void?TIM4_Int_Init(u16?arr,u16?psc) { ????TIM_TimeBaseInitTypeDef??TIM_TimeBaseStructure; ????RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,?ENABLE);?//時鐘使能 ????TIM_TimeBaseStructure.TIM_Period?=?arr;?//設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值??計數(shù)到5000為500ms ????TIM_TimeBaseStructure.TIM_Prescaler?=psc;?//設置用來作為TIMx時鐘頻率除數(shù)的預分頻值??10Khz的計數(shù)頻率?? ????TIM_TimeBaseStructure.TIM_ClockDivision?=?0;?????//設置時鐘分割:TDTS?=?Tck_tim ????TIM_TimeBaseStructure.TIM_CounterMode?=?TIM_CounterMode_Up;??//TIM向上計數(shù)模式 ????TIM_TimeBaseInit(TIM4,?&TIM_TimeBaseStructure);?//根據(jù)TIM_TimeBaseInitStruct中指定的參數(shù)初始化TIMx的時間基數(shù)單位 ????TIM_SelectOutputTrigger(TIM4,?TIM_TRGOSource_Update);//觸發(fā)外設方式為更新觸發(fā) ? ????TIM_Cmd(TIM4,?ENABLE);??//使能TIMx外設 ???????? }
八、顯示函數(shù)與按鍵控制
顯示波形只需將所獲得的1024個采樣數(shù)據(jù)選擇一部分進行顯示大致思路如下:
u16?pre_vol;//當前電壓值對應點的縱坐標 u16?past_vol;//前一個電壓值對應點的縱坐標 //adcx[]數(shù)組及通過DMA存入的1024個原始數(shù)據(jù) pre_vol?=?50+adcx[x]/4096.0*100; LCD_DrawLine(x,past_vol,x+1,pre_vol);//根據(jù)實際,打點位置可進行相應更改 past_vol?=?pre_vol;
按鍵的控制是在外部中斷中進行(正點原子資料中提供相應參考代碼)比較重要的是改變采樣頻率。
編輯:黃飛
?
評論
查看更多