(需要源码私)
前言
基于正点原子STM32F103ZET6精英板、TFTLCD(320*240)、74hc165级联按键、信号发生器、示波器等制作的简易示波器。通过ADC+TIM+DMA的方式采样,并且按键可以控制采样频率,来实现波形的放大和缩小。对于波形的频率计算,一种是定时器的输入捕获,另一种是用快速傅里叶变换(FFT)计算。———————————————————————————————
运行图片




功能介绍
1.通过改变定时器的配置来改变采样频率
//TIM2配置,arr为重加载值,psc为预分频系数
void TIM2_Init(u16 arr,u16 psc)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
//定时器TIM2初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
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); //根据指定的参数初始化TIMx的时间基数单位
TIM_OCInitTypeDef TIM_OCInitStructure; //输出比较 配置TIM2PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1 pwm
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
TIM_OCInitStructure.TIM_Pulse = (arr+1)/2;
TIM_OC2Init(TIM2, & TIM_OCInitStructure); //初始化外设TIM2_CH2
TIM_CtrlPWMOutputs(TIM2, ENABLE); //PWM输出使能 这个函数是高级定时器使用的,通用定时器用了可能会让程序出现问题
TIM_Cmd(TIM2, ENABLE); //使能TIMx
}
void Control_sampling(void) //改变采样频率
{
_74hc165_init();
key_num=MatrixKey();
switch(key_num) //不同的按键代表不同的频率
{
case 8: TIM2_Init(9,17); //400k
psc_arr=180; break;
case 7: TIM2_Init(9,22); //313043.478Hz
psc_arr=230; break;
case 6: TIM2_Init(9,34); //205714.2857Hz
psc_arr=350; break;
case 13: TIM2_Init(9,69); //102857.142857Hz
psc_arr=700; break;
case 14: TIM2_Init(140,9); //51428.5714Hz
psc_arr=1410; break;
case 15: TIM2_Init(702,9); //10241.820768Hz
psc_arr=7030; break;
}
}
2.两种频率计算
(a)适用于计算方波的频率和占空比——输入捕获
#include "tim.h"
void IC_Init(void) //输入捕获
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能 TIM3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA 时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //PA6 TIM3CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM3); //选择内部时钟
//初始化定时器3 TIM3
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =5; //预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化 TIMx 的时间基数单位
//初始化 TIM3 输入捕获参数
TIM_ICInitTypeDef TIM3_ICInitStructure;
TIM3_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上
TIM3_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM3_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上,直连通道
TIM3_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM3_ICInitStructure.TIM_ICFilter = 0xF;//配置输入滤波器
TIM_ICInit(TIM3, &TIM3_ICInitStructure);
TIM_PWMIConfig(TIM3,&TIM3_ICInitStructure); //把通道2的配置设置成与通道1相反的配置
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1); //触发源
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset); //从模式 将CNT自动清零
TIM_Cmd(TIM3,ENABLE ); //使能定时器3
}
uint32_t IC_GetFreq(void)
{
uint32_t Freq1,Freq2;
Freq1 =12000000/ (TIM_GetCapture1(TIM3)); //计算频率 1k以下测量频率准
Freq2 =12000000/ (TIM_GetCapture1(TIM3)+1); //计算频率 1k以上测量频率准 分频后的除以计次 (测周法) 误差:对于高频信号用测频法好,对于低频信号用测周法好
if(Freq1>1000)
return Freq2;
else
return Freq1;
}
uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3)+1 ) *100/(TIM_GetCapture1(TIM3)+1 ); //计算占空比
}
(b)快速傅里叶变换 (FFT)
void GetPowerMag(void)
{
signed short lX,lY;
float X,Y,Mag;
unsigned short i;
for(i=0; i<NPT/2; i++)
{
lX = (lBufOutArray[i] << 16) >> 16; //取低16位
lY = (lBufOutArray[i] >> 16); //取高16位
//除以32768再乘65536是为了符合浮点数计算规律
X = NPT * ((float)lX) / 32768;
Y = NPT * ((float)lY) / 32768;
Mag = sqrt(X * X + Y * Y)*1.0/ NPT;
if(i == 0)
lBufMagArray[i] = (unsigned long)(Mag * 32768);
else
lBufMagArray[i] = (unsigned long)(Mag * 65536);
}
}
void ADC_sample(void)
{
uint16_t i;
for (i = 0; i < NPT; i++)
{
lBufInArray[i] =ADC_ConvertedValue[i];
}
cr4_fft_1024_stm32(lBufOutArray,lBufInArray, NPT);
GetPowerMag();
for(uint16_t a=0;a<512;a++ )
{
FFT_Date[a]=(float)lBufMagArray[a]/1024*2;
// printf("%d\tvalue: %f \r\n",a,FFT_Date[a]);
// printf("%d\tvalue: %d \r\n",a,lBufMagArray);
}
}
uint32_t Freq_Count(void) //通过FFT计算的信号频率
{
float FFT_max=0;
uint32_t Freq=0;
for(uint16_t a=1;a<512;a++) //a从1开始,去掉直流分量后,比较出幅值的最大值
{
if(FFT_Date[a]>FFT_max)
{
FFT_max=FFT_Date[a];
Freq =(72000000)/(psc_arr*1024)*a; //信号输入频率=采样频率/1024*a
}
}
return Freq;
}
Freq =(72000000)/(psc_arr*1024)*a; 这个计算出来的频率还是有一点误差的,原因是72M除以了psc*arr*1024,它不是一个整数。
3.按键
我一共使用了8个按键,板子自带的2个按键使用EXTI外部中断,KEY0按下显示波形,KEY1按下显示FFT频谱,剩下的6个按键控制采样频率,通过扫描按键电平的方式
4.LCD显示部分
屏幕刷新问题:全部清屏还是局部清屏显得很不自然,屏幕一卡一卡的
解决方法:制作一个背景板,每次刷完就用背景板覆盖上一次的
void Show_background(void)
{
for(uint8_t j=0;j<7;j++)
{
for(uint8_t i=0;i<6;i++)
{
LCD_Color_Fill(0+i*40,120+j*30,39+i*40,149+j*30,background);
}
}
}
填充指定颜色制作一个“背景板”
***********************************************************************************
//在指定区域内填充指定颜色块
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{
u16 height,width;
u16 i,j;
width=ex-sx+1; //得到填充的宽度
height=ey-sy+1; //高度
for(i=0;i<height;i++)
{
LCD_SetCursor(sx,sy+i); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
for(j=0;j<width;j++)LCD->LCD_RAM=color[i*width+j];//写入数据
}
}
1073

被折叠的 条评论
为什么被折叠?



