大家好我是麥鴿。
今天分享一個適合新手的單片機項目。學習單片機,通過做簡單的小項目,是成長最快的一種方法。今天就給大家分享一個制作簡易示波器的小項目,代碼開源,希望對初學者有幫助。
https://gitee.com/silent-rookie/Simple-Oscilloscope

將PA6與PA4相連,可觀察到正弦波。
將PA6與PA5相連,可觀察到三角波/噪聲(默認三角波)。
KEY_UP控制波形的更新和暫停。
KEY_1降低采樣率。
KEY_0提高采樣率。
ADC的配置:
/**********************************************************
簡介:ADC1-CH6初始化函數
***********************************************************/??????????????????
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;?//模數轉換工作在單通道模式
?ADC_InitStructure.ADC_ContinuousConvMode?=?DISABLE;?//模數轉換工作在非連續轉換模式
?ADC_InitStructure.ADC_ExternalTrigConv?=?ADC_ExternalTrigConv_T2_CC2;?//轉換由定時器2的通道2觸發(只有在上升沿時可以觸發)
?ADC_InitStructure.ADC_DataAlign?=?ADC_DataAlign_Right;?//ADC數據右對齊
?ADC_InitStructure.ADC_NbrOfChannel?=?1;?//順序進行規則轉換的ADC通道的數目
?ADC_Init(ADC1,?&ADC_InitStructure);?//根據ADC_InitStruct中指定的參數初始化外設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的軟件轉換啟動功能
}???
定時器的配置:
/******************************************************************
函數名稱:TIM2_PWM_Init(u16?arr,u16?psc)
函數功能:定時器3,PWM輸出模式初始化函數
參數說明:arr:重裝載值
?? psc:預分頻值
備????注:通過TIM2-CH2的PWM輸出觸發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時鐘頻率除數的預分頻值?
?TIM_TimeBaseStructure.TIM_ClockDivision?=?0;?//設置時鐘分割:TDTS?=?Tck_tim
?TIM_TimeBaseStructure.TIM_CounterMode?=?TIM_CounterMode_Up;??//TIM向上計數模式
?TIM_TimeBaseInit(TIM2,?&TIM_TimeBaseStructure);?//根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位
?
?//初始化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;?//發生反轉時的計數器數值,用于改變占空比
?TIM_OC2Init(TIM2,?&TIM_OCInitStructure);??//根據T指定的參數初始化外設TIM2
?TIM_CtrlPWMOutputs(TIM2,?ENABLE);//使能PWM輸出
?
?TIM_Cmd(TIM2,?ENABLE);??//使能TIM2
}
DMA配置:
/******************************************************************
函數名稱:MYDMA1_Config()
函數功能:DMA1初始化配置
參數說明: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;??//數據傳輸方向,從外設讀取發送到內存//
?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;??//數據寬度為16位
?DMA_InitStructure.DMA_MemoryDataSize?=?DMA_MemoryDataSize_HalfWord;?//數據寬度為16位
?DMA_InitStructure.DMA_Mode?=?DMA_Mode_Circular;??//工作在循環模式
?DMA_InitStructure.DMA_Priority?=?DMA_Priority_High;?//DMA通道?x擁有高優先級?
?DMA_InitStructure.DMA_M2M?=?DMA_M2M_Disable;??//DMA通道x沒有設置為內存到內存傳輸
?DMA_Init(DMA_CHx,?&DMA_InitStructure);??//ADC1匹配DMA通道1
?
?DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE);?//使能DMA傳輸中斷?
?
?//配置中斷優先級
?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通道
}
TIM_Pulse默認設置為1000,因此在初始化定時器2時,TIM_Period的值不能小于該值,可自行修改。TIM_Pulse的值并不會影響采樣頻率。采樣頻率= 定時器2溢出頻率=SYSCLK/預分頻值/溢出值因此如果將TIM_Pulse設為1,TIM_Period設為2,TIM_Prescaler設為1,理論上采樣頻率最高可達36Mhz。f0=fs/N,其中fs等于采樣率,N為采樣點數)f(k)=k*(fs/N),其中f(k)是實際頻率,k是實際信號的最大幅度頻率所對應的數。(詳見下面代碼,分享的源代碼中公式有誤,未重新上傳)#define?NPT?1024//一次完整采集的采樣點數
/******************************************************************
函數名稱:GetPowerMag()
函數功能:計算各次諧波幅值
參數說明:
備??注:先將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為傅里葉輸入序列數組,ffout為傅里葉輸出序列數組
?
????for(i=1;?i2;?i++)
????{
??X?=?(fftout[i]?<16)?>>?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);
}?
u16?magout[NPT];
/******************************************************************
函數名稱:InitBufInArray()
函數功能:正弦波值初始化,將正弦波各點的值存入magout[]數組中
參數說明:
備????注:
*******************************************************************/
void?InitBufInArray(void)
{
????u16?i;
????float?fx;
????for(i=0;?i????{
????????fx?=?sin((PI2*i)/NPT);
????????magout[i]?=?(u16)(2048+2048*fx);
????}
}
/******************************************************************
函數名稱:sinout()
函數功能:正弦波輸出
參數說明:
備????注:將此函數置于定時器中斷中,可模擬輸出正弦波
*******************************************************************/
void?sinout(void)
{
?static?u16?i=0;
?DAC_SetChannel1Data(DAC_Align_12b_R,magout[i]);
?i++;
?if(i>=NPT)
??i=0;
}
頻率=72Mhz/定時器重裝載值/預分頻系數/幅值/2;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觸發
????DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_Noise;//產生噪聲
????//DAC_WaveGeneration_Triangle產生三角波
????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位右對齊數據格式設置DAC值?
}
void?TIM4_Int_Init(u16?arr,u16?psc)
{
????TIM_TimeBaseInitTypeDef??TIM_TimeBaseStructure;
????RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,?ENABLE);?//時鐘使能
????TIM_TimeBaseStructure.TIM_Period?=?arr;?//設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值??計數到5000為500ms
????TIM_TimeBaseStructure.TIM_Prescaler?=psc;?//設置用來作為TIMx時鐘頻率除數的預分頻值??10Khz的計數頻率??
????TIM_TimeBaseStructure.TIM_ClockDivision?=?0;?????//設置時鐘分割:TDTS?=?Tck_tim
????TIM_TimeBaseStructure.TIM_CounterMode?=?TIM_CounterMode_Up;??//TIM向上計數模式
????TIM_TimeBaseInit(TIM4,?&TIM_TimeBaseStructure);?//根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位
????TIM_SelectOutputTrigger(TIM4,?TIM_TRGOSource_Update);//觸發外設方式為更新觸發
?
????TIM_Cmd(TIM4,?ENABLE);??//使能TIMx外設
????????
}
顯示波形只需將所獲得的1024個采樣數據選擇一部分進行顯示大致思路如下:
u16?pre_vol;//當前電壓值對應點的縱坐標
u16?past_vol;//前一個電壓值對應點的縱坐標
//adcx[]數組及通過DMA存入的1024個原始數據
pre_vol?=?50+adcx[x]/4096.0*100;
LCD_DrawLine(x,past_vol,x+1,pre_vol);//根據實際,打點位置可進行相應更改
past_vol?=?pre_vol;
按鍵的控制是在外部中斷中進行(正點原子資料中提供相應參考代碼)比較重要的是改變采樣頻率。
??兄弟們!一個人單打獨斗確實能沖得挺快,但要想走得更遠、更穩,還得靠一群志同道合的伙伴啊!
?? 麥鴿的知識星球現在已經聚集了一波人,大家都在這里互相學習、共同進步。
如果你也想找個靠譜的學習圈子,趕緊???戳鏈接 ?? 加入我們吧!
在這里,你能讀到星球專欄的干貨,優質教程,練手項目,隨時向麥鴿提問,還能幫你定制學習計劃。別猶豫了,兄弟,一起沖!??
往期推薦