一、方案
先用 LCD制作 一个格子图形,然后定时器触发ADC采样。再将采集到的数据绘制成曲线显示在格子图形上,通过读取 图形上的点来测量信号。 本文使用的是原子哥的F103ZET6的战舰开发板。
二、具体实施
1.LCD初步显示
LCD的配置代码我是直接复制原子哥的,直接调用了它里面的函数。 因为我的屏幕是480*800,所以为了布局采用了横屏显示。 首先根据方案,我要先制作一个格子图。并且为了观察波形的数据,加入了时间和数值显示。绘制格子是在函数display里面。格子的话时 每隔20绘制一条线,可以根据 自己情况 修改。
void display(void)
{
uint16_t t;
LCD_Fill(0,0,800,340,WHITE);
POINT_COLOR=GREEN;
for(t = 0;t<360;t=t+20)
LCD_DrawLine(0, t, 800, t);
for(t = 0;t<800;t=t+20)
LCD_DrawLine(t,0, t,340 );
POINT_COLOR=BLUE;
LCD_DrawLine(0, 180, 800, 180);
LCD_DrawLine(400,0, 400,340 );
LCD_ShowNum(84,376,k,2,24);
}
void display(void)
{
...
LCD_Init();
LCD_Clear(WHITE);
LCD_Display_Dir(1);
POINT_COLOR=BLUE;
LCD_ShowString(60,400,24*3,24,24,"t1:");
LCD_ShowString(24,376,24*2,24,24,"move:");
LCD_ShowString(60,424,24*3,24,24,"t2:");
LCD_ShowString(0,448,24*4,24,24,"|t1-t2|:");
LCD_ShowString(146,400,24*2,24,24,"ms");
LCD_ShowString(146,424,24*2,24,24,"ms");
LCD_ShowString(146,448,24*2,24,24,"ms");
LCD_ShowString(260,400,24*3,24,24,"V1:");
LCD_ShowString(260,424,24*3,24,24,"V2:");
LCD_ShowString(200,448,24*4,24,24,"|V1-V2|:");
LCD_ShowString(358,400,24*3,24,24,"mV");
LCD_ShowString(358,424,24*3,24,24,"mV");
LCD_ShowString(358,448,24*3,24,24,"mV");
display ();
...
}
效果展示:
2.ADC配置
接下来就是ADC配置 。先上代码:
void adc_init(void)
{
GPIO_InitTypeDef adc1io;
ADC_InitTypeDef adc1;
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
adc1io.GPIO_Mode = GPIO_Mode_AIN ;
adc1io.GPIO_Pin = GPIO_Pin_1 ;
GPIO_Init (GPIOA,&adc1io);
ADC_DeInit(ADC1);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
adc1.ADC_Mode = ADC_Mode_Independent ;
adc1.ADC_ContinuousConvMode = ENABLE ;
adc1.ADC_ScanConvMode = DISABLE;
adc1.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None ;
adc1.ADC_DataAlign = ADC_DataAlign_Right ;
adc1.ADC_NbrOfChannel = 1;
ADC_Init (ADC1,&adc1);
ADC_Cmd(ADC1,ENABLE );
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_RegularChannelConfig(ADC1, 1, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd (ADC1,ENABLE);
}
uint16_t Get_adc()
{
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
return ADC_GetConversionValue(ADC1);
}
uint16_t Get_adcAverage(uint8_t times)
{
uint32_t t;
uint16_t a[255]={0};
int i,g1,g2;
for(i=0;i<times;i++)
{
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
a[i] = ADC_GetConversionValue(ADC1);
}
g1= g2 = a[0];
for(i = 0;i<times-1;i++)
{
if(g1>a[i+1])g1 = a[i+1];
if(g2<a[i+1])g2 = a[i+1];
}
for(i = 0;i<times;i++)
{
t += a[i];
}
return t = (t-g1-g2)/(times-2);
}
ADC配置简单介绍: 因为只打算做一个通道,所以配置成独立模式 ,开启连续转换模式。这里 的ADC采集我打算放在 主函数里面,所以也是配置成由软件触发。 这里使用了ADC1的通道1。为了采样准确率上升我把 采样时间 配置 239.5个周期 。因为是72M6分频,所以ADCCLK是12M。总转换时间公式是:Tcovn=采样时间+12.5 个周期,计算出来我采样一次时间是18us。计算这个只要是因为使用定时器触发,防止触发时间低于采样时间。 然后我编了一个提高采样精准的函数:连续采样多次(至少3次),去掉最大值和最小值,然后取平均。
3.定时器配置
同样先上代码:
void TIM_Init(void)
{
TIM_TimeBaseInitTypeDef Tim3;
NVIC_InitTypeDef NVIC_Tim;
RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3 ,ENABLE);
Tim3.TIM_Period = 10;
Tim3.TIM_CounterMode = TIM_CounterMode_Up ;
Tim3.TIM_Prescaler = 7199;
Tim3 .TIM_ClockDivision = TIM_CKD_DIV1 ;
TIM_TimeBaseInit (TIM3,&Tim3 );
TIM_ITConfig (TIM3,TIM_IT_Update ,ENABLE );
NVIC_Tim .NVIC_IRQChannel = TIM3_IRQn ;
NVIC_Tim .NVIC_IRQChannelPreemptionPriority = 0;
NVIC_Tim .NVIC_IRQChannelSubPriority = 0;
NVIC_Tim .NVIC_IRQChannelCmd = ENABLE ;
NVIC_Init (&NVIC_Tim);
TIM_Cmd (TIM3,ENABLE);
}
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
Z=1;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
ms++;
}
}
我这里是打算 1ms采集一个点 ,所以我的采集信号频率只能在 1KHz以下,由于是绘图,所以在一个周期采集越多的点效果回越好。 因为中断不好执行占用CPU过长的任务,所以我在主函数 里面使用ADC采集,这里 只是 给它一个判断标志。
5.按键配置
为了测量周期和峰峰值,我用可移动的两根线在波形上测量。这里就需要 用到按键。
void EXTI0_IRQHandler(void)
{
for(time = 0;time<40000;time++);
if(GPIO_ReadInputDataBit (WKUP_GPIO ,WKUP_GPIO_PIN ) == SET)
{
mode ++;
if(mode ==5)mode =0;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI2_IRQHandler(void)
{
for(time = 0;time<40000;time++);
if(GPIO_ReadInputDataBit (KEY2_GPIO ,KEY2_GPIO_PIN ) == RESET)
{
node =1;
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI3_IRQHandler(void)
{
for(time = 0;time<40000;time++);
if(GPIO_ReadInputDataBit (KEY1_GPIO ,KEY1_GPIO_PIN ) == RESET)
{
k++;
if(k>10)k=1;
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI4_IRQHandler(void)
{
for(time = 0;time<40000;time++);
if(GPIO_ReadInputDataBit (KEY0_GPIO ,KEY0_GPIO_PIN ) == RESET)
{
node = 2;
}
EXTI_ClearITPendingBit(EXTI_Line4);
}
这里也是通过标志来让单片机运行相应功能。 KEY_UP:第一次按下 :暂停波形,选定第一个竖直线。 第二次按下 :选定第二个数值线。 第三次按下 :选定第一个水平线。 第四次按下 :选定第二 个水平线。 第五次按下 :恢复运行波形。 KEY1:修改线移动的格数,这里设置是1~10。 KEY0:**当选定水平线时:向下移动k。 当选定竖直线时:向左移动k。 KEY2:当选定水平线时:向上移动k。 当选定竖直线时:向右移动k。
6.整合
首先是先采集并且 绘制波形 ,这里的 方法 是1ms采集一个点并且采集800个点后开始绘制波形。
void main(void)
{
...
while(1)
{
while( mode ==0)
{
if(Z==1)
{
Z = 0;
adcx = Get_adcAverage (5);
temp[ms] = adcx*(3.3/4096)*100;
while(temp[ms]>330)
{
adcx = Get_adcAverage (3);
temp[ms] =adcx*(3.3/4096)*100;
}
}
if(ms == 799)
{
TIM_Cmd (TIM3,DISABLE);
display ();
for(ms=0;ms<800;ms++)xemp[ms]=temp[ms];
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
ms = 0;
TIM_Cmd (TIM3,ENABLE);
}
}
...
}
}
这里的话就是1ms触发定时器中断,然后Z置1,并且变量ms++。Z为 1开启ADC采集 ,将采集的数据存储在数组temp中。当ms为 799时,暂时关闭定时器,将数组temp中的数据转移到xemp中,再根据 xemp绘制曲线(转移到xemp中是为了保证后面绘制的曲线数据 在同一采集周期内)。因为波形需要更新 所以先调用 display更新下次波形。然后调用LCD_DrawLine绘制波形。这样就能将上个0.8s的信号数据绘制出来。将 变为 0并且再次开启定时器。至于要把这些放在while(mode==0)里,是为了后面服务的。 再看后面的代码:
if(mode == 1)
{
TIM_Cmd (TIM3,DISABLE);
while(mode == 1)
{
if(node ==1)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
node = 0;
POINT_COLOR=RED;
X=X+k;
if(X > 800)X = 0;
LCD_DrawLine(X, 0, X, 340);
LCD_ShowNum(110,400,(u32)X,3,24);
}
if(node ==2)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
node = 0;
POINT_COLOR=RED;
X=X-k;
if(X > 800)X = 800;
LCD_DrawLine(X, 0, X, 340);
LCD_ShowNum(110,400,(u32)X,3,24);
}
}
}
while(mode == 2)
{
if(node ==1)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
POINT_COLOR=RED;
LCD_DrawLine(X, 0, X, 340);
node = 0;
X1=X1+k;
if(X1 > 800)X1 = 0;
LCD_DrawLine(X1, 0, X1, 340);
LCD_ShowNum(110,424,(u32)X1,3,24);
}
if(node ==2)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
POINT_COLOR=RED;
LCD_DrawLine(X, 0, X, 340);
node = 0;
X1=X1-k;
if(X1 > 800)X1 = 800;
LCD_DrawLine(X1, 0, X1, 340);
LCD_ShowNum(110,424,(u32)X1,3,24);
}
if(X1>X)LCD_ShowNum(110,448,(u32)X1-X,3,24);
else LCD_ShowNum(110,448,(u32)X-X1,3,24);
}
while(mode == 3)
{
if(node ==1)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
node = 0;
POINT_COLOR=RED;
X=X+k;
if(X > 340)X = 0;
LCD_DrawLine(0,X, 800,X);
LCD_ShowNum(310,400,(u32)(340-X)*10,4,24);
}
if(node ==2)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
node = 0;
POINT_COLOR=RED;
X=X-k;
if(X > 340)X = 340;
LCD_DrawLine(0,X,800,X);
LCD_ShowNum(310,400,(u32)(340-X)*10,4,24);
}
}
if(mode == 4)
{
while(mode == 4)
{
if(node ==1)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
POINT_COLOR=RED;
LCD_DrawLine(0, X, 800, X);
node = 0;
X1=X1+k;
if(X1 > 340)X1 = 0;
LCD_DrawLine(0,X1, 800, X1);
LCD_ShowNum(310,424,(u32)(340-X1)*10,4,24);
}
if(node ==2)
{
display ();
POINT_COLOR=BLACK;
for(ms=0;ms<799;ms++)LCD_DrawLine(ms, 480-xemp[ms]-150, ms+1, 480-xemp[ms+1]-150);
POINT_COLOR=RED;
LCD_DrawLine(0, X, 800, X);
node = 0;
X1=X1-k;
if(X1 > 340)X1 = 340;
LCD_DrawLine(0,X1, 800, X1);
LCD_ShowNum(310,424,(u32)(340-X1)*10,4,24);
}
if(X1>X)LCD_ShowNum(310,448,(u32)(X1-X)*10,4,24);
else LCD_ShowNum(310,448,(u32)(X-X1)*10,4,24);
}
ms = 0;
TIM_Cmd (TIM3,ENABLE);
}
这部分代码思路其实都一样,因 为要关闭和开启 定时器,所以再在mode为1和mode为4外面先加了if语句。这里介绍一下mode为一的情况: 当按下第一次WK_UP的时候,mode为1,主函数进入while(mode==1)的循环。当你不 进行人格 操作 时,此时LCD页面 时 静止的,因为 不会进入里面 的if语句,while相当于空内容。当你按下相应功能按键,比如 KEY0,变量node变为2,进入if:先将 node置0。然后调用 display,更新显示,绘制波形,显示线和数值。图中红线 既是选定 的线。可按KEY2和 KEY0移动,move表示移动的大小。其他mode差不多 是一样的情况。
7.实测效果
用信号发生器产生10Hz的峰峰值为2.8V的正弦波形(因为没搞采集负电压所以需要将低电平调为0):
5Hz1.5V方波:
2Hz三角波1V
总结
这个代码我也只是测了几组,不知道还有没有问题,有的话欢迎大家指出。 然后误差也有,这跟32采集的精准度以及对他的处理还有我设置的分辨率都有关系,再者这里是将采集的两个点直接连线连起来,所以图形也会有误差。不过这个代码可改进的 方面有很多,例如用DMA传输,改变触发采集 时间(应该只要保证采样时间小于触发时间就行) 等等 。
|