アナログ風ゲージ
2023.05.15
YouTube でも紹介しています。画像をクリックすると再生できます。

12 Useful & Interesting ESP32 Projects for Beginners!
YouTube でスタイリッシュな計器を見つけました。
この動画では、ESP32ボードに240x135 ST7789 SPIディスプレイが搭載されているTTGO T-DISPLAYが使用されています。
ロータリー エンコーダーの回転度合いを表示するとともに PC ファンを制御しています。
注目したのはこの表示部分です。色々と活用できそうなので、gitHubにアップされていたソースコードを変更して動くようにしてみました。
ディスプレイとマイコンボード一体型は使い勝手がよくないので、動作環境にはマイコンボードとディスプレイを別々に用意しました。
表示に使用されているフォントは、ビルド時、SPI FLASHに保存されるので、Arduino UNOなどは使用できません。
4MBのSPI FLASHを実装しているESP8266ボードのWeMos D1 mini V3.0を使用しました。
●WeMos D1 Mini V3.0
A mini wifi(802.11) board with 4MB flash based on ESP-8266EX(80MHz).
MCUに80MHzのESP-8266EX、4MBのSPI FLASH、80KBのRAM、オンボードWiFiと非常にコスパに優れていて、日本ではあまり知られていませんが海外では人気のボードです。

Note. All of the IO pins run at 3.3V.
LOLIN D1 mini v3.1.0
Getting Started with WeMos D1 Mini
ソースコードのビルドには、PlatformIOを使用しています。
Arduino開発環境構築 PlatformIO
プロジェクトを作成します。
$ mkdir ~/WeMosD1Mini
$ cd ~/WeMosD1Mini
$ pio init -b d1_mini
●Adafruit 1.14" 240x135 Color TFT Display + MicroSD Card Breakout - ST7789
The TFT driver (ST7789) is very similar to the popular ST7735, and our Arduino library supports it well. There was a little space so Adafruit placed a microSD card holder so you can easily load full color bitmaps from a FAT16/FAT32 formatted microSD card. (GREENTAB)
上記のAdafruit製ディスプレイも製造終了品のようなのですが、MOUSER.JPで大量に在庫があったのでストックしておきました。
●ロータリーエンコーダ
ロータリーエンコーダには、壊れたマウスから部品取りしてあったものをブレッドボード上に配線しやすいように加工しました。
●配線

| ST7789 | - | D1 Mini | - | Encoder |
| TFTCS | - | [D8]HCS | | |
| DC | - | [D3] | | |
| RST | - | [D4] | | |
| LIT | - | [D0] | | |
| SCK | - | [D5]HSCK | | |
| MISO | - | [D6]HMISO | | |
| MOSI | - | [D7]HMOSI | | |
| Vin | - | 3.3V | | |
| GND | - | GND | - | GND |
| | | D1 | - | ENC_A |
| | | D2 | - | ENC_B |
●EspGauge ソースコード

VolosR/EspGauge
取り合えず、ビルドだけ通したいのであれば、設定ファイルは下記のような感じです。
TFT_eSPI の設定で、User_Setup.h 内のピン番号を書き換えて使用する方法を見かけますが、お薦めできません。
$ vi platformio.ini
[env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
lib_deps =
adafruit/Adafruit GFX Library
bodmer/TFT_eSPI @ ^2.5.2
build_flags =
-fpermissive
-D SERIAL_BAUD=115200
-D USER_SETUP_LOADED
-D LOAD_GLCD
-D ST7789_DRIVER
-D TFT_WIDTH=135
-D TFT_HEIGHT=240
-D TFT_DC=D3
-D TFT_CS=-1
-D TFT_RST=D4
-D TFT_BL=-1
-D TFT_ROTATION=2
-D SPI_FREQUENCY=40000000
ビルドしてコードをマイコンにアップロードできますが、フォント情報を取り込めず不具合が発生してしまいました。
EspGauge.ino (抜粋)
void setup() {
........
img.setSwapBytes(true);
img.createSprite(220, 105);
img.setTextDatum(4);
........
}
void loop() {
........
img.fillSprite(TFT_BLACK);
img.fillCircle(sx,sy,124,color5);
img.setTextColor(TFT_WHITE,color5);
img.setFreeFont(&FreeSans9pt7b);
for(int i=0;i<10;i++)
if(start[i]+angle<360){
img.drawString(cc[i],x[start[i]+angle],y[start[i]+angle]);
........
img.pushSprite(10, 30);
ESPGaugeでは、Adafruit GFX ライブラリをコアとして、機能拡張された TFT_eSPIライブラリが使用されています。
制作者の意図を考えると、毎回再描画される決まりきった部分をテンプレート化するために、スプライト機能を用いたように思えるのですが、
そのテンプレート部分にも更新が発生しており、最適化されていないようです。
また、スプライト機能は小さなキャラクタを1つ描いておいて、それを複製させるのには有効ですが、画面の大半をスプライトしようとすると
膨大なRAMを消費するため、ESP32-S2,S3のような数MBのPSRAMを実装していないマイコンでは対応できません。
●ソースコードの変更点
・TFT_eSPIライブラリは使わないように変更
・ロータリーエンコーダの検出ロジック変更
【Arduino】マウスホイール(ロータリーエンコーダ)の回転量を取得する
・割り込み処理→loop()内処理に変更
ソースコードは変更点が多過ぎて説明する気になれません。
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include "FreeSans9pt7b.h"
#include "fonts.h"
#define TFT_CS D8
#define TFT_RST D4
#define TFT_DC D3
#define TFT_BL D0
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
#define WHITE 0xFFFF
#define BLACK 0x0000
#define ORANGE 0xFBE0
#define SKYBLUE 0x8410
#define color4 0x15B3
#define color5 0x00A3
volatile int counter = 0;
float gauge;
double rad=0.01745;
float x[360];
float y[360];
float px[360];
float py[360];
float lx[360];
float ly[360];
int r = 100;
int sx = 110 + 10;
int sy = 116 + 30;
char *cc[10]={" 0","10","20","30","40","50","60","70","80","90"};
int start[10];
int startP[60];
#define ENC_A D1
#define ENC_B D2
bool debounce=true;
uint8_t prev = 0;
int angle = 270;
int lastAngle = 0;
void drawGauge()
{
char buf[16];
gauge = ((angle-270)/3.60)*-1;
if(gauge < 0) gauge = gauge + 100;
// tft.fillCircle(sx,sy,124,color5);
tft.fillCircle(sx,sy,112,color5);
// tft.setTextColor(color5);
tft.setFont(&FreeSans9pt7b);
tft.setTextColor(WHITE);
for(int i=0;i<10;i++) {
if(start[i]+angle<360){
drawString(cc[i],x[start[i]+angle]-10,y[start[i]+angle]+9);
tft.drawLine(px[start[i]+angle],py[start[i]+angle],lx[start[i]+angle],ly[start[i]+angle],WHITE);
} else {
drawString(cc[i],x[(start[i]+angle)-360]-10,y[(start[i]+angle)-360]+9);
tft.drawLine(px[(start[i]+angle)-360],py[(start[i]+angle)-360],lx[(start[i]+angle)-360],ly[(start[i]+angle)-360],WHITE);
}
}
tft.setFont(&DSEG7_Modern_Bold_20);
sprintf(buf, "%d", (uint8_t)gauge);
if (gauge < 10) {
drawString(buf,sx-4,sy-16);
} else {
drawString(buf,sx-20,sy-16);
}
for(int i=0;i<60;i++) {
if(startP[i]+angle<360)
tft.fillCircle(px[startP[i]+angle],py[startP[i]+angle],1,SKYBLUE);
else
tft.fillCircle(px[(startP[i]+angle)-360],py[(startP[i]+angle)-360],1,SKYBLUE);
}
tft.fillTriangle(sx-1,sy-70,sx-5,sy-56,sx+4,sy-56,ORANGE);
tft.setTextColor(WHITE);
tft.setFont();
drawString("POWER",104,96);
tft.setFont(&FreeSans9pt7b);
drawString("%",sx+16,sy-16);
}
void updateEncoder()
{
int value = 0;
uint8_t ab, encoded;
if (debounce) {
for (uint8_t i=0; i < 20; ++i) {
uint8_t a = digitalRead(ENC_A);
uint8_t b = digitalRead(ENC_B);
ab = (a << 1) | b;
encoded = (prev << 2) | ab;
if(encoded == 0b1101 || encoded == 0b0100 || encoded == 0b0010 || encoded == 0b1011){
value++;
} else if(encoded == 0b1110 || encoded == 0b0111 || encoded == 0b0001 || encoded == 0b1000) {
value--;
}
prev = ab;
delay(10);
}
if (value > 0) {
angle = angle + 6;
} else if (value < 0) {
angle = angle - 6;
}
if(angle>=360) angle=0;
if(angle< 0) angle=354;
// Serial.print("angle:");
// Serial.println(angle);
}
}
void drawOnOFF(bool onOff)
{
tft.fillRect(192,11,50,16,BLACK);
tft.drawCircle(202,18,7,WHITE);
tft.drawCircle(222,18,7,WHITE);
tft.drawLine(202,11,222,11,WHITE);
tft.drawLine(202,25,222,25,WHITE);
tft.fillRect(203,12,18,13,BLACK);
if (onOff) {
tft.fillCircle(202,18,4,ORANGE);
} else {
tft.fillCircle(222,18,4,ORANGE);
}
}
void drawString(char *str, uint8_t x, uint8_t y)
{
tft.setCursor(x, y);
tft.print(str);
}
void setup()
{
Serial.begin(9600);
pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
/*
ledcSetup(pwmLedChannelTFT, 5000, 8);
ledcAttachPin(TFT_BL, 0);
ledcWrite(0, 90);
*/
pinMode(TFT_BL,OUTPUT);
analogWrite(TFT_BL, 64); // 0 - 255
tft.init(135, 240);
tft.setRotation(3);
tft.fillScreen(BLACK);
// tft.setFont(&FreeSans9pt7b);
drawString("ON/OFF",196,4);
drawString("CONTROL",20,20);
drawString("MOTOR",20,6);
tft.setTextColor(color4);
drawString("REVOLUTIONS PER MINUTE",60,4);
drawOnOFF(true);
int b=0;
int b2=0;
for(int i=0;i<360;i++) {
x[i]=(r*cos(rad*i))+sx;
y[i]=(r*sin(rad*i))+sy;
px[i]=((r-16)*cos(rad*i))+sx;
py[i]=((r-16)*sin(rad*i))+sy;
lx[i]=((r-24)*cos(rad*i))+sx;
ly[i]=((r-24)*sin(rad*i))+sy;
if(i%36==0) {
start[b]=i;
b++;
}
if(i%6==0) {
startP[b2]=i;
b2++;
}
}
tft.fillCircle(sx,sy,124,color5);
tft.setTextColor(ORANGE);
tft.setFont(&FreeSans9pt7b);
// tft.setFont(&Slackey_Regular_16);
drawString("32465",92,30);
drawGauge();
}
void loop()
{
static int lastCounter = 0;
updateEncoder();
debounce = false;
if(angle != lastCounter) lastCounter = angle;
if(angle!=lastAngle) {
lastAngle = angle;
drawGauge();
}
debounce=true;
}
コードは全然ブラッシュアップされていません。適時修正してください。
●デフォルトフォントの変更
デフォルトフォントを変更します。
~/WeMosD1mini/ESP32Gauge/.pio/libdeps/d1_mini/'Adafruit GFX Library'/glcdfont.c
static const unsigned char font[] PROGMEM = {
・・・・・・・・
};
↓配列の中身を下記のデータに置き換えます
https://github.com/Bodmer/TFT_eSPI/blob/master/Fonts/glcdfont.c
static const unsigned char font[] PROGMEM = {
・・・・・・・・
};
デフォルトフォントを追加します。
https://github.com/Bodmer/TFT_eSPI/tree/master/Fonts/GFXFF/FreeSans9pt7b.h
をソースディレクトリにコピーします。
●検証

電源をいれた直後の状態です。

ロータリーエンコーダのローラーをくるくる回すとゲージも回転します。
今回はロータリーエンコーダーの数値表示に用いましたが、色々な用途への応用は容易です。
■参考文献
・Gauge for ESP32 and TFT (LilyGo T-Display)
・ロータリーエンコーダを使う(1)
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|