目录
前言
这篇文章跟着上一篇“STM32示波器设计”讨论一下具体的波形刷新。
主要内容就是以下三点:
一、实时刷新
二、触发模式刷新
三、利用缓存刷新LCD
一、实时刷新与触发模式刷新
实时刷新和触发模式刷新,都是从数据处理的角度的分类的。
注意,本篇文章,无论任何情况下都是在波形刷新之前启动下一次的ADC采集传输。
前提概念和参数:
采样点:每采样一个数据,即是一个采样点
波形点:由若干个采样点组成一个波形点,可以是1个,也可以是多个取平均。
波形记录长度:一条波形数据的波形点个数,即为波形记录长度
比如,我这里的硬件环境是:
1、
单通道ADC+240*320屏幕(波形显示区域为200*300),
那么,波形点就是一个采样点,波形记录长度为300。
2、
LCD写入时序设置为从左到右,从上到下,写入240*320即全屏需要28ms,写入200*300即波形显示区域需要22ms。
X\Y轴方向都以25为一小格,横向8格,纵向12格。
1.1 实时刷新
能不能实时刷新,取决于屏幕刷新率,也即屏幕刷新耗时。
这里刷新耗时22ms(固定22ms,实现方法在下面),则采完一条波形的耗时只要大于22ms,就能够实时刷新。
(为什么是大于?当采样耗时小于22ms,每次采样结束进行刷新时,这个刷新时间内本身就错过了一部分周期。当采样耗时大于22ms,由于ADC采集传输可以使用DMA与屏幕刷新同步进行,屏幕刷新时不会耽误ADC的采集)
对应的时基挡位就是,22*1000/300us=每个波形点73us,一格(25个点)1833us,接近2ms,
那么这里就取每格2ms为分界线。
在2ms及以上的挡位时,在波形刷新之前启动下一次的ADC采集传输,刷新结束之后等待ADC采集完成,这样就能保证固定的刷新频率。
最理想的情况就是,将采集耗时压到最大直至接近刷新时间,这样每当刷完一条数据,下一次的ADC数据就已经准备好了。
//配置之前先关闭ADC
ADC_Stop();
//设置ADC的DMA接收buf和长度
ADC_BufSet(AdcBuf, OSC_DEPTH);
//设置定时触发频率,即采集频率
ADC_TimeSet(List_psc[OscData.UnitT], List_cnt[OscData.UnitT]);
//启动ADC采集
ADC_Start();
对单通道ADC采集的实时模式刷新,OSC_DEPTH 存储深度可以设置为300,即采300个点刷300个点。
无论哪种方式,代码中都要有一个WaveBuf,缓存归一化之后的波形数据(数组长度即波形区域宽度),用于LCD刷新。
for(int i = 0; i < OSC_DEPTH; i++)
{
//将ADC采集数据归一化为0到200的Y值坐标
vol = buf[i]*3300/4096/OscData.UnitV;
if(vol > OSC_HEIGHT)
OscData.WaveBuf[j] = OSC_HEIGHT;
else
OscData.WaveBuf[j] = vol;
//计算最大值和最小值
if(Max < vol)
Max = vol;
if(Min > vol)
Min = vol;
}
1.2 触发模式刷新
对于2ms以上的,可以用实时模式进行刷新。到了2ms以下时,一般使用触发模式来进行刷新。
触发模式下:OSC_DEPTH 存储深度可以设置为300的整数倍,超过1K时长度随意。比如这里取600。其它的ADC参数还是一样的。
所不同的是数据处理方法,我们一般判断的是通过软件判断上升沿或下降沿并记录(硬件方式会造成的频繁的中断影响处理速度和刷新速度,只能适用于低频信号)。
如下代码,
处理逻辑:先找到第一个下降沿位置A,如果找到了则就从A位置开始取300个点,进行计算刷新。找不到则取前300个点数据进行刷新。
注意:处理时的起始索引并不是0,因为在原始的ADC采集时,总有前1到2个数据偏差较大,这里直接取到了10。
//Find第一个下降沿
Voltage = OscData.Trigger*4096/3300;
for(i = 10; i < OSC_DEPTH - OSC_WIDTH; i++){
if(buf[i-1] > Voltage && buf[i] < (Voltage + 1)){
//if(AdcBuf[i-1] > Voltage && AdcBuf[i] < Voltage){
Offset = i;
break;
}
}
//取从第一个下降沿开始的OSC_WIDTH个数据
Max = 0; Min = 4095;
for(i = Offset, j = 0; i < Offset + OSC_WIDTH; i++, j++){
vol = buf[i]*3300/4096/OscData.UnitV;
if(vol > OSC_HEIGHT)
OscData.WaveBuf[j] = OSC_HEIGHT;
else
OscData.WaveBuf[j] = vol;
if(Max < vol)
Max = vol;
if(Min > vol)
Min = vol;
}
void OscDrawWindow(uint8_t WaveBuf[]);
无论哪种方式,都是调用的同一个API进行屏幕刷新,外部只需要计算好归一化数据。
二、利用缓存刷新LCD
这里直接用了300*200*2大小的数组作为缓存,各位可以自己设计算法节省内存或者利用外部存储。
具体流程如下:
1. 第一步先清0
2. 先计算网格点,对网格点赋颜色
3. 第三步,根据X、Y值坐标,计算每两个点的Y坐标差的绝对值和最小Y坐标,进而计算出在该X轴上需要画几个点(赋颜色)
4. 将数据送入lcd_buf刷新函数取刷新
//波形显示区域|利用宏定义进行处理|320*240|300*200
#define OSC_HEIGHT 200 //波形显示窗口高度
#define OSC_WIDTH 300 //波形显示窗口宽度
#define OSC_DEPTH (OSC_WIDTH*2) //波形数据深度
#define X0 (0+9)
#define X1 (0+OSC_WIDTH+9)
#define Y0 (0+20)
#define Y1 (0+OSC_HEIGHT+20)
//像素点位置及个数计算
#define OSC_PIXEL_GetMin(X1,X0) ((X1>=X0)?(OSC_HEIGHT-X1):(OSC_HEIGHT-X0))
#define OSC_PIXEL_GetNum(X1,X0) ((X1>X0)?(X1-X0):((X1==X0)?1:X0-X1))
static uint16_t WaveBufBit[OSC_HEIGHT][OSC_WIDTH];
void OscDrawWindow(uint8_t WaveBuf[])
{
uint16_t i,j,X,y;
uint16_t LCD_Y_Min, LCD_Y_Num, LCD_Y_Line;
// //统计运行时间1
// static uint16_t LCD_Time1 = 0,LCD_Time2 = 0;
// static uint32_t LCD_Sum[2] = {0},LCD_Cnt[2] = {0};
// LCD_Time1 = OSTime;
//默认全灭
memset(WaveBufBit, List_c[OSC_M_WAVE].Back, OSC_WIDTH*OSC_HEIGHT*2);
//计算网格点
for(i = 1; i < 12; i++)
{
for(j = 1; j < 200 - 1; j++){
if(i!=6) j++;
WaveBufBit[j][25*i] = List_c[OSC_M_CELL].Point;
}
}
for(i = 1; i < 8; i++)
{
for(j = 1; j < 300 - 1; j++){
if(i!=4) j++;
WaveBufBit[25*i][j] = List_c[OSC_M_CELL].Point;
}
}
//计算点亮点
for(X = 2; X < OSC_WIDTH; X++)
{
LCD_Y_Min = OSC_PIXEL_GetMin(WaveBuf[X],WaveBuf[X-1]);//起始点亮点(极限点为0或200)
LCD_Y_Num = OSC_PIXEL_GetNum(WaveBuf[X],WaveBuf[X-1]);//点亮数目(最多200个bit|25byte)
for(y = LCD_Y_Min; y < LCD_Y_Min + LCD_Y_Num; y++){
WaveBufBit[y][X] = List_c[OSC_M_WAVE].Point;
}
}
// LCD_Time1 = OSTime - LCD_Time1;
// LCD_Sum[0] += LCD_Time1;
// LCD_Cnt[0] ++;
// if(LCD_Cnt[0] >= 10){
// OSC_Log("LCD_Time1:%d",(uint16_t)(1.0*LCD_Sum[0]/LCD_Cnt[0]));
// LCD_Cnt[0] = 0;
// LCD_Sum[0] = 0;
// }
//
// //统计运行时间2
// LCD_Time2 = OSTime;
// //刷新buf区
LCD_FillColor_Buf(X0,Y0,X1-1,Y1-1,&WaveBufBit[0][0]);
// LCD_Time2 = OSTime - LCD_Time2;
// LCD_Sum[1] += LCD_Time2;
// LCD_Cnt[1] ++;
// if(LCD_Cnt[1] >= 10){
// OSC_Log("LCD_Time2:%d",LCD_Sum[1]/LCD_Cnt[1]);
// LCD_Cnt[1] = 0;
// LCD_Sum[1] = 0;
// }
}
如下提供了两种lcd_buf刷新函数供各位参考。
/**************************************************************
函数名称 : LCD_WriteRAM_Prepare
函数功能 : 使能写入数据到RAM
**************************************************************/
/**************************************************************
函数名称 : LCD_SetCursor
函数功能 : 设置坐标
输入参数 : x1,y1:起始地址,x2,y2:终点地址
**************************************************************/
//在指定区域内填充颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色buf|方向是从左到右从上到下
void LCD_FillColor_Buf(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t *color)
{
uint16_t i,j;
LCD_SetCursor(x1,y1,x2,y2);
LCD_WriteRAM_Prepare();
for(j = 0; j < (y2-y1+1); j++)
for(i = 0; i < (x2-x1+1); i++)
LCD_WR_DATA( *color++ );
}
void LCD_FillColor_Bit(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t *color)
{
uint16_t i,j,bit = 0;
LCD_SetCursor(x1,y1,x2,y2);
LCD_WriteRAM_Prepare();
for(j = 0; j < (y2-y1+1); j++)
for(i = 0; i < (x2-x1+1); i++){
if(*color & (1<<bit) )
LCD_WR_DATA( POINT_COLOR );
else
LCD_WR_DATA( BACK_COLOR );
bit++;
if(bit>=8){
color++;
bit=0;
}
}
}