Closed10
M5Stackで波形表示
こんな感じのものを作りたい
枠まではできた
Waveform.hpp
#ifndef __M5WAVEFORM_HPP__
#define __M5WAVEFORM_HPP__
#include <M5Unified.h>
#include <M5GFX.h>
namespace m5wf
{
namespace figure_constants
{
static constexpr int MARGIN = 4;
static constexpr int LABEL_HEIGHT = 16;
static constexpr int YAXIS_DIV_LABLE_WIDTH = 40;
static constexpr int YAXIS_DIV_LABLE_HEIGHT = LABEL_HEIGHT * 2;
static constexpr int YAXIS_POS_LABLE_WIDTH = 40;
static constexpr int YAXIS_POS_LABLE_HEIGHT = LABEL_HEIGHT;
static constexpr int XAXIS_DIV_LABLE_WIDTH = 80;
static constexpr int XAXIS_DIV_LABLE_HEIGHT = LABEL_HEIGHT;
}
class M5Waveform
{
public:
typedef enum
{
Y_DIV = 1,
Y_POS,
X_DIV
} EditSelection;
// typedef struct{
// uint32_t ticks;
// }
M5Waveform();
M5Waveform(M5Canvas *canvas);
~M5Waveform();
M5Canvas *figureCanvas;
void init(int32_t width, int32_t height);
void init(int32_t width, int32_t height, uint8_t xAxisDivCount, uint8_t yAxisDivCount);
private:
/// @brief 波形領域全体の幅
int32_t _figureWidth;
/// @brief 波形領域全体の高さ
int32_t _figureHeight;
uint16_t _waveRegionX = m5wf::figure_constants::MARGIN + m5wf::figure_constants::YAXIS_DIV_LABLE_WIDTH;
uint16_t _waveRegionY = m5wf::figure_constants::MARGIN;
uint16_t _waveRegionWidth;
uint16_t _waveRegionHeight;
EditSelection _selection = Y_DIV;
uint8_t _yAxisDivCount = 1; // 0は分割無し, 1で二等分, 2,3,...255
uint8_t _xAxisDivCount = 4; // 0は分割無し, 1で二等分, 2,3,...255
uint16_t _yAxisDiv = 20;
uint16_t _yAxisPos = 100;
uint16_t _xAxisDiv = 50;
void _drawXAxisRulerLine(void);
void _drawYAxisRulerLine(void);
void _drawWaveformBorder(void);
void _drawParamEditBorder(void);
void _drawYAxisDivLabel(void);
void _drwaYAxisPosLabel(void);
void _drawXAxisDivLabel(void);
void _drawDashedLine(int x0, int y0, int x1, int y1, int segmentLength, int spaceLength, uint16_t color);
};
}
#endif
Waveform.cpp
#include "M5Waveform.hpp"
using namespace m5wf::figure_constants;
namespace m5wf
{
M5Waveform::M5Waveform()
{
}
M5Waveform::M5Waveform(M5Canvas *canvas)
{
figureCanvas = canvas;
}
M5Waveform::~M5Waveform()
{
// memo: 本クラス内でnewした領域などはここで解放する
}
void M5Waveform::init(int32_t width, int32_t height, uint8_t xAxisDivCount, uint8_t yAxisDivCount)
{
_xAxisDivCount = xAxisDivCount;
_yAxisDivCount = yAxisDivCount;
M5Waveform::init(width, height);
}
void M5Waveform::init(int32_t width, int32_t height)
{
_figureWidth = width;
_figureHeight = height;
_waveRegionWidth = _figureWidth - MARGIN - MARGIN - YAXIS_DIV_LABLE_WIDTH;
_waveRegionHeight = _figureHeight - MARGIN - MARGIN - XAXIS_DIV_LABLE_HEIGHT;
figureCanvas->createSprite(width, height);
figureCanvas->setPivot(_figureWidth / 2, _figureHeight / 2);
figureCanvas->setColorDepth(8);
// DEBUG
figureCanvas->drawRect(
0,
0,
_figureWidth,
_figureHeight,
WHITE);
// 分割点線
_drawXAxisRulerLine();
_drawYAxisRulerLine();
// 枠
_drawWaveformBorder();
// 選択枠
_drawParamEditBorder();
// 軸ラベル
_drawXAxisDivLabel();
_drawYAxisDivLabel();
_drwaYAxisPosLabel();
}
void M5Waveform::_drawXAxisRulerLine(void)
{
for (int i = 0; i < _xAxisDivCount; i++)
{
auto xPos = _waveRegionWidth / (_xAxisDivCount + 1) * (i + 1) + _waveRegionX;
_drawDashedLine(xPos, _waveRegionY, xPos, _waveRegionY + _waveRegionHeight, 6, 2, BLUE);
}
}
void M5Waveform::_drawYAxisRulerLine(void)
{
for (int i = 0; i < _yAxisDivCount; i++)
{
auto yPos = _waveRegionHeight / (_yAxisDivCount + 1) * (i + 1) + _waveRegionY;
_drawDashedLine(_waveRegionX, yPos, _waveRegionX + _waveRegionWidth, yPos, 6, 2, BLUE);
}
}
void M5Waveform::_drawWaveformBorder(void)
{
figureCanvas->drawRect(
_waveRegionX,
_waveRegionY,
_waveRegionWidth,
_waveRegionHeight,
BLUE);
}
void M5Waveform::_drawParamEditBorder(void)
{
switch (_selection)
{
case Y_DIV:
figureCanvas->drawRect(MARGIN, _waveRegionY, YAXIS_DIV_LABLE_WIDTH, YAXIS_DIV_LABLE_HEIGHT, BLUE);
break;
case Y_POS:
figureCanvas->drawRect(MARGIN, _waveRegionY + _waveRegionHeight - YAXIS_POS_LABLE_HEIGHT, YAXIS_POS_LABLE_WIDTH, YAXIS_POS_LABLE_HEIGHT, BLUE);
break;
case X_DIV:
figureCanvas->drawRect(_waveRegionX + _waveRegionWidth - XAXIS_DIV_LABLE_WIDTH, _waveRegionY + _waveRegionHeight, XAXIS_DIV_LABLE_WIDTH, XAXIS_DIV_LABLE_HEIGHT, BLUE);
break;
default:
break;
}
}
void M5Waveform::_drawYAxisDivLabel(void)
{
figureCanvas->setCursor(MARGIN + MARGIN, _waveRegionY + MARGIN);
figureCanvas->printf("%d", _yAxisDiv);
figureCanvas->setCursor(MARGIN + MARGIN, _waveRegionY + MARGIN + LABEL_HEIGHT);
figureCanvas->printf("/div");
}
void M5Waveform::_drwaYAxisPosLabel(void)
{
figureCanvas->setCursor(MARGIN + MARGIN, _waveRegionY + _waveRegionHeight - YAXIS_POS_LABLE_HEIGHT + MARGIN);
figureCanvas->printf("%d", _yAxisPos);
}
void M5Waveform::_drawXAxisDivLabel(void)
{
figureCanvas->setCursor(_waveRegionX + _waveRegionWidth - XAXIS_DIV_LABLE_WIDTH + MARGIN, _waveRegionY + _waveRegionHeight + MARGIN);
figureCanvas->printf("%d /div", _xAxisDiv);
}
void M5Waveform::_drawDashedLine(int x0, int y0, int x1, int y1, int segmentLength, int spaceLength, uint16_t color)
{
float distance = sqrt(sq(x1 - x0) + sq(y1 - y0));
float dashCount = distance / (segmentLength + spaceLength);
float dx = (x1 - x0) / dashCount;
float dy = (y1 - y0) / dashCount;
float x = x0;
float y = y0;
for (int i = 0; i < dashCount; i++)
{
if (i % 2 == 0)
{ // セグメントを描画
figureCanvas->drawLine(x, y, x + dx * segmentLength / (segmentLength + spaceLength), y + dy * segmentLength / (segmentLength + spaceLength), color);
}
x += dx;
y += dy;
}
}
}
データの持ち方案
- N点のリングバッファを確保する
- 各要素には表示用データの構造体が収まる
- 表示用データの構造体は前回データ追加時からの時刻差分、データの値が収まる
- 波形のX軸表示範囲に含まれるデータのみを画面にマッピングして描画する
Pythonでデータ追加部分を試作
from datetime import datetime
from random import randint
import time
#npts = 128
npts = 16 #debug
# バッファの確保
buff = [None] * npts
# ポインタ初期化
w_ptr = 0# 次回書き込みインデックス 閉区間
r_ptr = 0# 最終読み出しインデックス 開区間
start_ptr = 0# 左端のインデックス 閉区間
# タイムスタンプ初期化
prev_t = None
curr_t = None
for i in range(0, 20):
# 値のみエンキューされた場合 <= タイムスタンプも渡された場合、datatime.now()を置き換えるだけ
aVal = randint(0, 100)
# タイムスタンプを世代交代
prev_t = curr_t
curr_t = datetime.now()
# 初回の場合
if prev_t == None:
# 初回なので0に収まる
p = GraphData(0, aVal)
buff[w_ptr] = p
else:
# 前回タイムスタンプとの差分
dt = curr_t - prev_t
p = GraphData(dt.total_seconds(), aVal)
buff[w_ptr] = p
# ポインタ更新
w_ptr += 1
# 循環する
if w_ptr >= npts:
w_ptr = 0
dt_l = [f"(dt={p.dt:.2f}, v={p.v})" if p != None else "Nan" for p in buff]
print(dt_l)
time.sleep(1.1)
期待する動作となることを確認できた。
時系列データを逐次追加するタイプの描画はハードルが高いので、まずはx,yの配列を受け取って値から画素位置を特定してプロットするタイプの描画を実装する。
点をプロットするメソッド
void M5Waveform::drawWaveform(point_f *points, uint16_t length)
{
_waveSprite.fillScreen(BLACK);
// point
uint16_t x_pt, y_pt;
for (int i = 0; i < length; i++)
{
// auto p = points[i * sizeof(point_f)];
auto p = points[i];
if (_point2px(p, &x_pt, &y_pt) == 0)
{
_waveSprite.fillCircle(x_pt, y_pt, 2, WHITE);
}
_waveSprite.pushSprite(&_canvas, (int32_t)_waveRegionX, (int32_t)_waveRegionY, BLACK);
}
uint8_t M5Waveform::_point2px(point_f point, uint16_t *x_px, uint16_t *y_px)
{
float xStart = (float)_xAxisPos;
float xEnd = (float)_xAxisPos + (float)_xAxisDiv * ((float)_xAxisDivCount + (float)1);
float yStart = (float)_yAxisPos;
float yEnd = (float)_yAxisPos + (float)_yAxisDiv * ((float)_yAxisDivCount + (float)1);
if (point.x < xStart || xEnd < point.x || point.y < yStart || yEnd < point.y)
{
return 1;
}
float dx = (float)_waveRegionWidth / (xEnd - xStart);
float dy = (float)_waveRegionHeight / (yEnd - yStart);
*x_px = (uint16_t)((point.x - xStart) * dx);
*y_px = (uint16_t)(_waveRegionHeight - (point.y - yStart) * dy);
return 0;
}
あとやること。
MUST
- 線の描画
- 軸範囲の変更メソッド
- 軸範囲変更UIサンプル
WANT
- フォントサイズ大中小ぐらいで切り替え
- 描画タイプ切り替え 点、線、点+線
- 色変更 点、線、ルーラー
- 凡例
最低限の波形描画、軸調整機能の実装は完了した。
逐次的な時系列データの描画は別途検討する。
PlatformIOのライブラリとして登録してみた
ライブラリの公開方法参考:
このスクラップは2024/02/27にクローズされました