Android中自定义示波器通过展示高采样率的数据(数据来源串口)
特以此博客记录高采样率波形绘制中遇到的坑,首先Android帧率以及刷新率相关的概念可以自行百度,博主遇到的情况是串口采样率255的情况下,在使用串口数据会出现波形图绘制卡顿,有严重延时,开始一直怀疑是示波器的问题换了N种实现方式还是卡顿,直到最后才找到原因,是串口数据读取的性能瓶颈,读取串口缓冲区数据频率过快会造成比较严重的数据延时
/**
*该示波器满足的需求为X轴向绘制1000点
*Y轴向绘制1500点
*同时绘制五路波形数据
*具体绘制点数可以根据自己需求配置TOTLE_X 与TOTLE_Y轴字段
*/
public class LineChartSurfaceFourView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "LineChartSurfaceView";
private static final float TOTLE_X = 1000;//X轴秒点数
private static final float TOTLE_Y = 1500;//Y轴秒点数
private float totalWidth = 800;//默认宽度
private float totalHeight = 190;//默认高度
private float xValue = totalWidth / TOTLE_X;//X缩放比
private float yValue;//Y缩放比
private float density = getResources().getDisplayMetrics().density;//屏幕分辨率
private int mSpaceHeight = 10;//间隔线高度
private int oneHeight;//单个示波器高度
private boolean isCreate = false;//view是否创建
private static DataFilter dataFilter1 = new DataFilter();//滤波器,不需要滤波器可以直接去掉
private static DataFilter dataFilter2 = new DataFilter();//滤波器
private static DataFilter dataFilter3 = new DataFilter();//滤波器
private static DataFilter dataFilter4 = new DataFilter();//滤波器
private static DataFilter dataFilter5 = new DataFilter();//滤波器
/**
* 曲线画笔
*/
private Paint linePaint;
/**
* 矩形画笔
*/
private Paint rectPaint;
/**
* 路径数据
*/
private List<PointF> line1Data = Collections.synchronizedList(new ArrayList<>());//示波器1数据源
private List<PointF> line2Data = Collections.synchronizedList(new ArrayList<>());//示波器2数据源
private List<PointF> line3Data = Collections.synchronizedList(new ArrayList<>());//示波器3数据源
private List<PointF> line4Data = Collections.synchronizedList(new ArrayList<>());//示波器4数据源
private List<PointF> line5Data = Collections.synchronizedList(new ArrayList<>());//示波器5数据源
/**
* 构造函数
*
* @param context 上下文对象
*/
public LineChartSurfaceFourView(Context context) {
this(context, null);
}
/**
* Instantiates a new Line chart surface four view.
*
* @param context the context
* @param attrs the attrs
*/
public LineChartSurfaceFourView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
dataFilter1.init();
dataFilter2.init();
dataFilter3.init();
dataFilter4.init();
dataFilter5.init();
}
private Rect rect1 = new Rect();//矩形绘制数据
private Rect rect2 = new Rect();//矩形绘制数据
private Rect rect3 = new Rect();//矩形绘制数据
private Rect rect4 = new Rect();//矩形绘制数据
private Rect rect5 = new Rect();//矩形绘制数据
/**
* 自定义初始化View
*/
public void initView() {
// 曲线画笔
linePaint = new Paint();
linePaint.setStrokeWidth(1);
linePaint.setStyle(Style.STROKE);
linePaint.setAntiAlias(true);
rectPaint = new Paint();
rectPaint.setStrokeWidth(1);
rectPaint.setColor(getResources().getColor(R.color.common_black));
rectPaint.setStyle(Style.STROKE);
rectPaint.setAntiAlias(true);
getHolder().addCallback(this);
}
/**
* The Surface Create callback.
*/
SurfaceCreateCallback mCallback;
/**
* Sets callback.
*
* @param callback the callback
*/
public void setCallback(SurfaceCreateCallback callback) {
mCallback = callback;
}
/**
* 绘制曲线数据
*/
private void drawLineData(Canvas canvas) {
Path path = new Path();
for (int i = 0; i < line1Data.size(); i++) {
PointF startPoint = line1Data.get(i);
if (i == 0) {
path.moveTo(startPoint.x, startPoint.y);
} else {
path.lineTo(startPoint.x, startPoint.y);
}
}
for (int i = 0; i < line2Data.size(); i++) {
PointF startPoint = line2Data.get(i);
if (i == 0) {
path.moveTo(startPoint.x, startPoint.y);
} else {
path.lineTo(startPoint.x, startPoint.y);
}
}
for (int i = 0; i < line3Data.size(); i++) {
PointF startPoint = line3Data.get(i);
if (i == 0) {
path.moveTo(startPoint.x, startPoint.y);
} else {
path.lineTo(startPoint.x, startPoint.y);
}
}
for (int i = 0; i < line4Data.size(); i++) {
PointF startPoint = line4Data.get(i);
if (i == 0) {
path.moveTo(startPoint.x, startPoint.y);
} else {
path.lineTo(startPoint.x, startPoint.y);
}
}
for (int i = 0; i < line5Data.size(); i++) {
PointF startPoint = line5Data.get(i);
if (i == 0) {
path.moveTo(startPoint.x, startPoint.y);
} else {
path.lineTo(startPoint.x, startPoint.y);
}
}
canvas.drawPath(path, linePaint);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
/**
* 计算矩形框位置
* Calc rect.
*/
public void calcRect() {
rect1.top = 0;
rect1.left = 0;
rect1.right = (int) totalWidth;
rect1.bottom = oneHeight;
rect2.top = oneHeight + mSpaceHeight;
rect2.left = 0;
rect2.right = (int) totalWidth;
rect2.bottom = oneHeight * 2 + mSpaceHeight;
rect3.top = (oneHeight + mSpaceHeight) * 2;
rect3.left = 0;
rect3.right = (int) totalWidth;
rect3.bottom = (oneHeight + mSpaceHeight) * 2 + oneHeight;
rect4.top = (oneHeight + mSpaceHeight) * 3;
rect4.left = 0;
rect4.right = (int) totalWidth;
rect4.bottom = (oneHeight + mSpaceHeight) * 3 + oneHeight;
rect5.top = (oneHeight + mSpaceHeight) * 4;
rect5.left = 0;
rect5.right = (int) totalWidth;
rect5.bottom = (oneHeight + mSpaceHeight) * 4 + oneHeight;
}
/**
* Draw rect.
* 调用该函数可以实现边框,canvas的调用函数就能实现比如填充颜色绘制等,具体自行发挥
* @param canvas the canvas
*/
public void drawRect(Canvas canvas) {
canvas.drawRect(rect1, rectPaint);
canvas.drawRect(rect2, rectPaint);
canvas.drawRect(rect3, rectPaint);
canvas.drawRect(rect4, rectPaint);
canvas.drawRect(rect5, rectPaint);
}
/**
* 提供给外部调用的单点更新数据
*
* @param data the data
*/
public void updateUi(PulseData data) {
if (!isCreate) {
return;
}
if (line1Data.size() >= TOTLE_X) {
line1Data.clear();
line2Data.clear();
line3Data.clear();
line4Data.clear();
line5Data.clear();
float y1 = (rect1.top + oneHeight - Math.min(dataFilter1.filter(data.CunShang), TOTLE_Y) * yValue);
float y2 = (rect2.top + oneHeight - Math.min(dataFilter2.filter(data.Cun), TOTLE_Y) * yValue);
float y3 = (rect3.top + oneHeight - Math.min(dataFilter3.filter(data.Guan), TOTLE_Y) * yValue);
float y4 = (rect4.top + oneHeight - Math.min(dataFilter4.filter(data.Chi), TOTLE_Y) * yValue);
float y5 = (rect5.top + oneHeight - Math.min(dataFilter5.filter(data.ChiXia), TOTLE_Y) * yValue);
line1Data.add(new PointF(0, y1));
line2Data.add(new PointF(0, y2));
line3Data.add(new PointF(0, y3));
line4Data.add(new PointF(0, y4));
line5Data.add(new PointF(0, y5));
} else {
float y1 = (rect1.top + oneHeight - Math.min(dataFilter1.filter(data.CunShang), TOTLE_Y) * yValue);
float y2 = (rect2.top + oneHeight - Math.min(dataFilter2.filter(data.Cun), TOTLE_Y) * yValue);
float y3 = (rect3.top + oneHeight - Math.min(dataFilter3.filter(data.Guan), TOTLE_Y) * yValue);
float y4 = (rect4.top + oneHeight - Math.min(dataFilter4.filter(data.Chi), TOTLE_Y) * yValue);
float y5 = (rect5.top + oneHeight - Math.min(dataFilter5.filter(data.ChiXia), TOTLE_Y) * yValue);
float x1 = line1Data.size() * xValue;
line1Data.add(new PointF(x1, y1));
line2Data.add(new PointF(x1, y2));
line3Data.add(new PointF(x1, y3));
line4Data.add(new PointF(x1, y4));
line5Data.add(new PointF(x1, y5));
}
reDraw();
}
/**
* 提供给外部调用的多点更新数据
*
* @param datas the datas
*/
public void updateUi(List<PulseData> datas) {
if (!isCreate) {
return;
}
if (line1Data.size() >= TOTLE_X) {
line1Data.clear();
line2Data.clear();
line3Data.clear();
line4Data.clear();
line5Data.clear();
updateUi(datas);
} else {
for (int i = 0; i < datas.size(); i++) {
PulseData data = datas.get(i);
float y1 = (rect1.top + oneHeight - Math.min(dataFilter1.filter(data.CunShang), TOTLE_Y) * yValue);
float y2 = (rect2.top + oneHeight - Math.min(dataFilter2.filter(data.Cun), TOTLE_Y) * yValue);
float y3 = (rect3.top + oneHeight - Math.min(dataFilter3.filter(data.Guan), TOTLE_Y) * yValue);
float y4 = (rect4.top + oneHeight - Math.min(dataFilter4.filter(data.Chi), TOTLE_Y) * yValue);
float y5 = (rect5.top + oneHeight - Math.min(dataFilter5.filter(data.ChiXia), TOTLE_Y) * yValue);
float x1 = line1Data.size() * xValue;
line1Data.add(new PointF(x1, y1));
line2Data.add(new PointF(x1, y2));
line3Data.add(new PointF(x1, y3));
line4Data.add(new PointF(x1, y4));
line5Data.add(new PointF(x1, y5));
}
}
reDraw();
}
/**
* 重绘画布
*/
private void reDraw() {
Canvas canvas = getHolder().lockCanvas();
try {
if (canvas != null) {
canvas.drawColor(Color.parseColor("#F2F2F2"));
drawLineData(canvas);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
getHolder().unlockCanvasAndPost(canvas);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 清空绘制曲线
*/
public void cleanDrawLine() {
line1Data.clear();
line2Data.clear();
line3Data.clear();
line4Data.clear();
line5Data.clear();
postInvalidate();
}
/**
* view的大小测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* view的高度测量
*/
private int measureHeight(int measureSpec) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 75;
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
totalHeight = result;
int totalSpaceHeight = mSpaceHeight * 4;
oneHeight = (int) ((totalHeight - totalSpaceHeight) / 5);
yValue = ((float) oneHeight) / TOTLE_Y;
calcRect();
return size;
}
/**
* view的宽度测量
*/
private int measureWidth(int measureSpec) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 75;//根据自己的需要更改
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
totalWidth = result;
xValue = totalWidth / TOTLE_X;
Log.e("wl", "总高:" + totalHeight + "xValue:" + xValue);
return result;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.parseColor("#F2F2F2"));
holder.unlockCanvasAndPost(canvas);
if (mCallback != null)
mCallback.surfaceCreated(holder);
isCreate = true;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isCreate = false;
}
/**
* The interface Surface create callback.
*/
public interface SurfaceCreateCallback {
/**
* Surface created.
*
* @param holder the holder
*/
void surfaceCreated(SurfaceHolder holder);
}
}
代码实际比较简单,楼主拿手机测试实际1S绘制数据完全能达到1000点。
关于结合串口后卡顿的问题,设计项目底层通讯的原因无法贴出代码,但是问题点就是拿到串口inputstream后,一般是开线程去循环读取,如果单次读取buff比较小,而下位机抛数据速度比你读取速度快的话,就会造成数据延时,具体的表现就是绘制数据比实际数据会延时几秒,对于这个问题,楼主是采取单次加大读取数据,加大 读取间隔,目前通过该方式,在下位机1S255次数据返回的情况下,示波器没有延时,更高采样率没有试验,具体可以根据自己需求去探索