项目1 制作双通道示波器
-
通过STM32F072的ADC采集外部模拟信号,信号范围最大10Vpp,频率为DC - 100KHz
-
将采集到的波形显示在240*240的LCD上,并以触发的方式显示波形
-
执行FFT并将频谱显示在LCD上
-
能够自动测量波形的参数 - 峰峰值、平均值、频率/周期
-
能够通过按键来对波形进行缩放查看
硬件介绍
本次项目的芯片是STM32F072CBT6,,是主流ARM Cortex-M0 USB系列MCU,具有128 KB Flash、48 MHz CPU、USB、CAN和CEC功能。开发板上集成了一个波轮按键和两个用户按键以及一个240*240的LCD显示屏幕。
实现
我本来打算电赛之后完成这个项目,没想到电赛推迟导致实验室关门,东西被锁在实验室里,另一方面返乡心切,不想再耽搁时间找老师开门,就只好等待返校实验室重新开门再着手完成项目,这样一来就只剩5天时间,这5天虽然不是电赛,强度却胜似电赛,平均每天呆在实验室10个小时,算是提前感受了。这时候电子森林的网站上已经有了两个案例发布了,分别是信号发生源和直流电压源,个人感觉这两个项目比示波器简单,虽说只有5天,但我还是想试着挑战一下他们没做的示波器。很遗憾5天的时间还是太短了,我到最后也未能完成任务指标上的全部功能。
首先是最高频率,我的代码底层完全借鉴刚才说的那个直流电压源项目,其ADC使用的办法是轮询,速度很慢,对于直流电压源来说足够了,但对于示波器则是远远不够,我也尝试过用DMA的方式,速度会更快,但始终存在我无法解决的bug,无奈放弃。本次使用的平台使stm32f0也是一个比较小众的平台,网上信息较少,例程几乎没有,相比之下我之前用过的f1和f4系列正点原子都有非常详细的例程可供参考,可以说是被惯坏了,那么拿到这块板子要如何写底层呢?在电子森林网站上我才知道可以使用stm32cubemx生成代码,看起来挺高大上的样子,实则只有对底层有充分的了解才能生成正确的代码,在尝试了好多次失败后,只能照抄案例,使用慢速的轮询方式,实测采集20次数据至少需要1.2ms,支持的最大频率也就2kHz左右了,远远达不到要求的100kHz。
然后是傅里叶变换,一开始在网上找到的教程说导入几个汇编文件后,就可以调用函数实现傅里叶变换,尝试后发现汇编有报错,无法拒绝,可能是stm32f0不支持,后来在本网站有找到c语言的傅里叶变换文件,导入后可以正确运行,但是绘图代码会出现执行异常的问题,进入硬件错误的死循环里,尝试更换另一个版本的绘图函数代码,这次初始化就出现问题,直接黑屏,然后硬件错误,个人觉得硬件错误是内存泄漏造成的,只能减少代码量,减少变量所占内存空间,别人的软件没有问题,这只能说明是我自己写的代码优化太差,内存申请过多,时间有限,无法优化了。
那么有没有什么比较成功的地方呢,也是有的,屏幕驱动的速度很慢,刷一次屏要2秒钟,所以绘图就只有一种办法:只画变化的地方,对此我设计了两个数组存放上一次和这一次的波形轨迹,每次绘图时,将之前的轨迹点画上背景色,新的轨迹画上线的颜色,就能实现示波。按键操作在中断里完成,但实测发现在这个中断里调用绘图函数效果十分不稳定,很大概率不会执行,原因未知,解决办法在中断里改变标志位,再在其他函数读取标志位执行绘图。
for(int i=0;i<160;i++)
{
for(int j=0;j<2;j++)
{
if(i%20==0)
{
LCD_DrawPoint(i+20,100-old_points[j][i+old_trigger[j]],BLACK);
}
else if(old_points[j][i+old_trigger[j]]%20==0)
{
LCD_DrawPoint(i+20,100-old_points[j][i+old_trigger[j]],BLACK);
}
else
{
LCD_DrawPoint(i+20,100-old_points[j][i+old_trigger[j]],WHITE);
}
if(j==0)
LCD_DrawPoint(i+20,100-points[j][i+trigger[j]],RED);
else
LCD_DrawPoint(i+20,100-points[j][i+trigger[j]],BLUE);
}
}
绘制波形
for(int i=0;i<2;i++)
{
for(int j=0;j<256;j++)
{
old_points[i][j]=points[i][j];
}
}
for(int i=0;i<2;i++)
{
change_para(182,93+60*i,text[3],3,pp[i]);
change_para(182,12+93+60*i,text[4],5,freq[i]);
}
if(change_sign[0]==1)
{
if(old_chose_option<3)
pos=old_chose_option*12+33;
else
pos=92+(old_chose_option-3)*12+60*old_chose_line;
LCD_DrawLine(184,pos,230,pos,WHITE);
if(chose_option<3)
pos=chose_option*12+33;
else
pos=92+(chose_option-3)*12+60*chose_line;
LCD_DrawLine(184,pos,230,pos,BLACK);
old_chose_line = chose_line;
old_chose_option = chose_option;
change_sign[0]=0;
}
if(change_sign[1]==1)
{
change_para(184,20,text[0],5,time[159]/8.0);
change_sign[1]=0;
}
if(change_sign[2]==1)
{
change_para(184,32,text[1],5,scope);
change_sign[2]=0;
}
if(change_sign[3]==1)
{
if(chose_line==0)
{
LCD_Fill(185,128,194,137,WHITE);
LCD_Fill(185,68,194,77,RED);
}
else
{
LCD_Fill(185,68,194,77,WHITE);
LCD_Fill(185,128,194,137,BLUE);
}
change_sign[3]=0;
}
if(change_sign[4]==1)
{
change_para(184,80+60*chose_line,text[2],5,offset[chose_line]);
change_sign[4]=0;
}
参数更新
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static int flag = 0;
if(HAL_GetTick()-lasttick<=5)
{ //15msΪ½çÏÞ
return ;
}
else
{
lasttick=HAL_GetTick();
}
if(GPIO_Pin == KEY_1_Pin)
{
if(chose_option<=2)
{
old_chose_option=chose_option;
chose_option=chose_option+1;
change_sign[0]=1;
}
}
if(GPIO_Pin == KEY_2_Pin)
{
if(chose_option>=1)
{
old_chose_option=chose_option;
chose_option=chose_option-1;
change_sign[0]=1;
}
}
if(flag==0)
{
if(GPIO_Pin == KEY_R_Pin)
{/* KEY L */
if(chose_option==0)
{
if(times>1)
{
times -= 1;
change_sign[1]=1;
}
}
else if(chose_option==1)
{
scope = scope/2.0;
change_sign[2]=1;
}
else if(chose_option==2)
{
change_sign[5]=0;
}
else if(chose_option==3)
{
offset[chose_line] -= 10;
change_sign[4]=1;
}
}
if(GPIO_Pin == KEY_O_Pin)
{/* KEY O ²¦ÂÖ*/
old_chose_line=chose_line;
chose_line=1-chose_line;
change_sign[3]=1;
change_sign[0]=1;
}
if(GPIO_Pin == KEY_L_Pin)
{/* KEY R */
if(chose_option==0)
{
if(times<30)
{
times += 1;
change_sign[1]=1;
}
}
else if(chose_option==1)
{
scope = scope*2.0;
change_sign[2]=1;
}
else if(chose_option==2)
{
change_sign[5]=1;
}
else if(chose_option==3)
{
offset[chose_line] += 10;
change_sign[4]=1;
}
}
flag=1;
}
else
flag=0;
}
ADC读回来的数据需要进行处理,才能转换成电压值,首先在multisim里进行仿真,得出实际电压和ADC检测电压之间的关系。