Arduino で SenseCAP Indicator の両チップを開発する
SenseCAP Indicator は、ESP32 と RP2040 デュアル MCU で駆動される 4 インチタッチスクリーンデバイスです。ESP32 と RP2040 は両方とも高性能なマイクロコントローラーで、幅広い機能を提供します。
このチュートリアルでは、Arduino フレームワークのシンプルさと柔軟性を使用して、Sensecap Indicator 用の独自のカスタムプロジェクト/ファームウェアを開発する方法をガイドします。
ハードウェアの準備
ここでは SenseCAP Indicator をハードウェアとして使用しており、その上に 4 種類のセンサー(CO2、温度、湿度、TVOC)があります。ここでの内容には以下が含まれます:
SenseCAP Indicator D1S |
---|
![]() |
ハードウェア概要と開発知識
Indicatorは2つのMCUで設計されており、RP2040とESP32S3が同時に動作します。

上記の図から以下のことがわかります:
- すべてのセンサーはI2Cプロトコルを使用してRP2040マイクロコントローラーに接続されています
- PCA9535 ICを使用したI2C IOエクスパンダーモジュールが1つあります
- スクリーンはESP32S3マイクロコントローラーに接続され、2つのピン(CS、RESET)がPCA9535 I2Cエクスパンダーに接続されています
- RP2040はESP32S3のピン20とピン19を介してUARTインターフェースを使用してESP32S3に接続されています
したがって、Sensecap Indicatorをコンピューターにプラグインすると、RP2040用とESP32S3用の2つのシリアルポートが表示されます。USB-SERIAL CH340の情報があるものがESP32S3に接続されているもので、これがチュートリアルの残りの部分で使用されるものです。

ソフトウェアの準備
ここではArduinoを使用します。
チュートリアルを続行する前に、Arduino IDEで以下の手順が完了していることを確認してください:
- ESP32ボード定義:ESP32ボード定義がインストールされ、最新バージョンに更新されていることを確認してください。ESP32ボードがまだArduino IDEにない場合は、このガイドに従ってください。
- ボード選択:ボード定義としてESP32S3 Dev Moduleを選択してください。
- PSRAM:スクリーンの適切な機能を確保するために、Arduino IDEでOPI PSRAM機能を有効にしてください。

使用するボード
プロジェクトとの互換性を確保するため、以下のバージョンのボードを使用してください:
- ESP32:バージョン 3.1.2
- Raspberry Pi Pico Arduino:バージョン 4.4.3
使用するライブラリ
TouchLib:バージョン 0.0.2
タッチドライバーを統合し、タッチインターフェースを統一するために、TouchLibライブラリが必要です。これはArduino IDEライブラリマネージャーでは利用できません。TouchLib GitHubリポジトリから手動でダウンロードし、Sketch > Include Library > Add .ZIP LibraryからArduino IDEに追加できます。
ライブラリがダウンロードされた後、Arduino IDEを開き、Sketchメニューに移動し、「Add .ZIP Library」を選択して、ダウンロードしたライブラリをIDEに追加します。

同様に、スムーズな統合のために、同じスケッチメニューを確認し、「Manage Libraries」を選択してから、必要なライブラリ(例:「PCA9535」、hidea kitaiが作成したものを選択)を検索してインストールし、他のすべての必要なライブラリについて以下のバージョンを確保してください:

- Adafruit TinyUSB:バージョン 3.4.2
- Anitracks_PCA95x5:バージョン 0.1.3
- GFX Library for Arduino:バージョン 1.5.3
- PacketSerial:バージョン 1.4.0
- lvgl:バージョン 9.2.2
- PCA95x5:バージョン 0.1.3
互換性の問題を避けるために、これらのライブラリとボードがArduino IDEにインストールされていることを確認してください。
はじめに
- New Tutorial (by LongDirtyAnimAlf)
- Old Tutorial (by Hendra)
必要なライブラリがすべてインストールされた後、以下のコードをアップロードして、スクリーンがArduino環境で動作するかテストしてください。以下のコードをアップロードできます:
#include <Arduino_GFX_Library.h>
#include <PCA95x5.h>
#define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin
/* More dev device declaration: https://github.com/moononournation/Arduino_GFX/wiki/Dev-Device-Declaration */
#if defined(DISPLAY_DEV_KIT)
Arduino_GFX *gfx = create_default_Arduino_GFX();
#else /* !defined(DISPLAY_DEV_KIT) */
#define GFX_DEV_DEVICE ESP32_S3_RGB
#define GFX_BL 45
Arduino_DataBus *bus = new Arduino_SWSPI(
GFX_NOT_DEFINED /* DC */, PCA95x5::Port::P04 /* CS */,
41 /* SCK */, 48 /* MOSI */, GFX_NOT_DEFINED /* MISO */);
// option 1:
// Uncomment for 4" rect display
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
18 /* DE */, 17 /* VSYNC */, 16 /* HSYNC */, 21 /* PCLK */,
4 /* R0 */, 3 /* R1 */, 2 /* R2 */, 1 /* R3 */, 0 /* R4 */,
10 /* G0 */, 9 /* G1 */, 8 /* G2 */, 7 /* G3 */, 6 /* G4 */, 5 /* G5 */,
15 /* B0 */, 14 /* B1 */, 13 /* B2 */, 12 /* B3 */, 11 /* B4 */,
1 /* hsync_polarity */, 10 /* hsync_front_porch */, 8 /* hsync_pulse_width */, 50 /* hsync_back_porch */,
1 /* vsync_polarity */, 10 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 20 /* vsync_back_porch */);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
480 /* width */, 480 /* height */, rgbpanel, 2 /* rotation */, true /* auto_flush */,
bus, GFX_NOT_DEFINED /* RST */, st7701_type1_init_operations, sizeof(st7701_type1_init_operations));
#endif /* !defined(DISPLAY_DEV_KIT) */
/*******************************************************************************
* End of Arduino_GFX setting
******************************************************************************/
void setup(void)
{
Serial.begin(115200);
// Serial.setDebugOutput(true);
// while(!Serial);
Serial.println("Arduino_GFX Hello World example");
#ifdef GFX_EXTRA_PRE_INIT
GFX_EXTRA_PRE_INIT();
#endif
// Init Display
if (!gfx->begin())
{
Serial.println("gfx->begin() failed!");
}
gfx->fillScreen(BLACK);
#ifdef GFX_BL
pinMode(GFX_BL, OUTPUT);
digitalWrite(GFX_BL, HIGH);
#endif
gfx->setCursor(10, 10);
gfx->setTextColor(RED);
gfx->println("Sensecap Indicator");
delay(5000); // 5 seconds
}
void loop()
{
gfx->setCursor(random(gfx->width()), random(gfx->height()));
gfx->setTextColor(random(0xffff), random(0xffff));
gfx->setTextSize(random(6) /* x scale */, random(6) /* y scale */, random(2) /* pixel_margin */);
gfx->println("Sensecap Indicator");
delay(1000); // 1 second
}
すべてが正常に動作すると、「Sensecap Indicator」のテキストが画面にランダムに表示されます。

SenseCap IndicatorでシンプルなGUIアプリケーションを作成する
SenseCap Indicatorは強力なESP32-S3マイクロコントローラーと高解像度480x480ディスプレイを搭載しており、グラフィカルユーザーインターフェースの作成に最適です。今度は、LVGLを使用してインタラクティブなGUIアプリケーションを作成する方法を探求することで、SenseCap Indicatorでの開発を続けていきます。ソースコードとヘッダーファイルを含む完全なプロジェクトをリポジトリからダウンロードできます: SenseCap Indicator LVGLプロジェクトをダウンロード
プロジェクトファイルをダウンロードして展開した後、以下のコードをアップロードして基本的なマルチスクリーンGUIアプリケーションを作成します:
/*Using LVGL with Arduino requires some extra steps:
*Be sure to read the docs here: https://docs.lvgl.io/master/get-started/platforms/arduino.html
Install: lvgl*/
// This define is sometimes missing when using old ESP32-IDF version
//#define ESP_INTR_CPU_AFFINITY_AUTO 0
#include <Arduino.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <PacketSerial.h>
#include "Indicator_Extender.h"
#include "Indicator_SWSPI.h"
#include "ui.h"
#include "touch.h"
#define HOR_RES 480
#define VER_RES 480
#define PACKET_UART_RXD 20
#define PACKET_UART_TXD 19
#define BUTTON_PIN 38
#define GFX_DEV_DEVICE ESP32_S3_RGB
#define RGB_PANEL
#define GFX_BL 45
Arduino_DataBus *bus = new Indicator_SWSPI(
GFX_NOT_DEFINED /* DC */, EXPANDER_IO_LCD_CS /* CS */,
SPI_SCLK /* SCK */, SPI_MOSI /* MOSI */, GFX_NOT_DEFINED /* MISO */);
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
18 /* DE */, 17 /* VSYNC */, 16 /* HSYNC */, 21 /* PCLK */,
4 /* R0 */, 3 /* R1 */, 2 /* R2 */, 1 /* R3 */, 0 /* R4 */,
10 /* G0 */, 9 /* G1 */, 8 /* G2 */, 7 /* G3 */, 6 /* G4 */, 5 /* G5 */,
15 /* B0 */, 14 /* B1 */, 13 /* B2 */, 12 /* B3 */, 11 /* B4 */,
1 /* hsync_polarity */, 10 /* hsync_front_porch */, 8 /* hsync_pulse_width */, 50 /* hsync_back_porch */,
1 /* vsync_polarity */, 10 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 20 /* vsync_back_porch */);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
HOR_RES /* width */, VER_RES /* height */, rgbpanel, 0 /* rotation */, false /* auto_flush */,
bus, GFX_NOT_DEFINED /* RST */, st7701_indicator_init_operations, sizeof(st7701_indicator_init_operations));
COBSPacketSerial myPacketSerial;
void onPacketReceived(const uint8_t* buffer, size_t size);
uint32_t millis_cb(void)
{
return millis();
}
/*Read the touchpad*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
if (touch_has_signal())
{
if (touch_touched())
{
data->state = LV_INDEV_STATE_PRESSED;
/*Set the coordinates*/
data->point.x = touch_last_x;
data->point.y = touch_last_y;
}
else if (touch_released())
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
// Main buttons event handler
static void event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * btn = lv_event_get_current_target_obj(e);
if (btn != NULL)
{
if(code == LV_EVENT_CLICKED)
{
void * btn_no_void = (void*)lv_event_get_user_data(e);
byte btn_no = (byte)((uintptr_t)btn_no_void);
lv_obj_t * screen = lv_obj_get_screen(btn);
if (screen != NULL)
{
Serial.println("Screen assigned");
if (screen == screen2)
{
Serial.println("Screen 2");
if (btn_no != 0)
{
Create_Screen3(event_handler);
lv_screen_load(screen3);
}
}
if (screen == screen3)
{
Serial.println("Screen 3");
if (btn_no == 0)
{
lv_screen_load(screen2);
lv_obj_delete(screen3);
}
}
}
}
}
}
void setup()
{
Serial.begin(115200);
Serial.println("SenseCap Indicator startup");
String LVGL_Arduino = String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.println(LVGL_Arduino);
pinMode(BUTTON_PIN, INPUT);
// Init Indicator hardware
extender_init();
myPacketSerial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, PACKET_UART_RXD, PACKET_UART_TXD);
myPacketSerial.setStream(&Serial1);
myPacketSerial.setPacketHandler(&onPacketReceived);
// Init Display
if (!gfx->begin(12000000L))
{
Serial.println("gfx->begin() failed!");
Serial.println("Expect sever errors !!!");
}
gfx->fillScreen(RGB565_BLACK);
#ifdef GFX_BL
pinMode(GFX_BL, OUTPUT);
digitalWrite(GFX_BL, HIGH);
#endif
lv_init();
/*Set a tick source so that LVGL will know how much time elapsed. */
lv_tick_set_cb(millis_cb);
/* register print function for debugging */
#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif
lv_screen_init(gfx, HOR_RES, VER_RES);
//lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_0);
//lv_display_set_antialiasing(disp,false);
// Init touch device
touch_init(HOR_RES, VER_RES, 0); // rotation will be handled by lvgl
/*Initialize the input device driver*/
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*Touchpad should have POINTER type*/
lv_indev_set_read_cb(indev, my_touchpad_read);
Screen2Create(event_handler);
lv_screen_load(screen2);
Serial.println("Setup done");
}
void loop()
{
static TickType_t xLastWakeTime = xTaskGetTickCount();
/*
unsigned long startTime = millis();
while (digitalRead(BUTTON_PIN) == LOW)
{
if (millis() - startTime >= 10000)
{
ESP.restart();
//esp_restart();
}
}
*/
myPacketSerial.update();
// Check for a receive buffer overflow (optional).
if (myPacketSerial.overflow())
{
// Send an alert via a pin (e.g. make an overflow LED) or return a
// user-defined packet to the sender.
}
lv_task_handler(); /* let the GUI do its work */
// Simple delay always 5ms
//delay(5);
// This delay will adapt to the time consumed in the above tasks
// If these tasks consume time, it will delay shorter
vTaskDelayUntil( &xLastWakeTime, ( 5 / portTICK_PERIOD_MS ) );
}
void onPacketReceived(const uint8_t* buffer, size_t size)
{
if (size < 1) {
return;
}
byte index = 0;
byte Command = buffer[index++];
if (Command == 0x55)
{
long Temperature = 0;
long Humidity = 0;
memcpy(&Temperature, &buffer[index], sizeof(Temperature));
index += sizeof(Temperature);
memcpy(&Humidity, &buffer[index], sizeof(Humidity));
index += sizeof(Humidity);
Screen2AddData(Temperature,Humidity);
}
}
コードをアップロードした後、シリアルモニターを開いてボーレートを115200に設定してください。初期化メッセージが表示され、ディスプレイにGUIが表示されます。UART接続を通じて受信した温度と湿度データと共にScreen2が表示されます。

複数画面とデータ可視化を備えた高度なGUIアプリケーション
2番目の例は、基本的なアプリケーションをベースに、バッテリー監視、動的データ可視化、色分けされたステータスインジケーターなど、より洗練された機能を追加しています。ソースコードとヘッダーファイルを含む完全なプロジェクトをリポジトリからダウンロードできます: SenseCap Indicator LVGL プロジェクトをダウンロード
このバージョンを実装するには、以下のコードをアップロードしてください:
/*Using LVGL with Arduino requires some extra steps:
*Be sure to read the docs here: https://docs.lvgl.io/master/get-started/platforms/arduino.html
Install: lvgl*/
// This define is sometimes missing when using old ESP32-IDF version
//#define ESP_INTR_CPU_AFFINITY_AUTO 0
#include <Arduino.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <PacketSerial.h>
#include "Indicator_Extender.h"
#include "Indicator_SWSPI.h"
#include "ui.h"
#include "touch.h"
#include "shared.h"
#define HOR_RES 480
#define VER_RES 480
#define PACKET_UART_RXD 20
#define PACKET_UART_TXD 19
#define BUTTON_PIN 38
#define GFX_DEV_DEVICE ESP32_S3_RGB
#define RGB_PANEL
#define GFX_BL 45
Arduino_DataBus *bus = new Indicator_SWSPI(
GFX_NOT_DEFINED /* DC */, EXPANDER_IO_LCD_CS /* CS */,
SPI_SCLK /* SCK */, SPI_MOSI /* MOSI */, GFX_NOT_DEFINED /* MISO */);
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
18 /* DE */, 17 /* VSYNC */, 16 /* HSYNC */, 21 /* PCLK */,
4 /* R0 */, 3 /* R1 */, 2 /* R2 */, 1 /* R3 */, 0 /* R4 */,
10 /* G0 */, 9 /* G1 */, 8 /* G2 */, 7 /* G3 */, 6 /* G4 */, 5 /* G5 */,
15 /* B0 */, 14 /* B1 */, 13 /* B2 */, 12 /* B3 */, 11 /* B4 */,
1 /* hsync_polarity */, 10 /* hsync_front_porch */, 8 /* hsync_pulse_width */, 50 /* hsync_back_porch */,
1 /* vsync_polarity */, 10 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 20 /* vsync_back_porch */);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
HOR_RES /* width */, VER_RES /* height */, rgbpanel, 0 /* rotation */, false /* auto_flush */,
bus, GFX_NOT_DEFINED /* RST */, st7701_indicator_init_operations, sizeof(st7701_indicator_init_operations));
TBatteryBoard BatteryBoards[DAUGHTERBOARDCOUNT] = {0};
COBSPacketSerial myPacketSerial;
//PacketSerial_<COBS, 0, 1024> myPacketSerial;
void onPacketReceived(const uint8_t* buffer, size_t size);
#if LV_USE_LOG != 0
void my_print(lv_log_level_t level, const char *buf)
{
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}
#endif
uint32_t millis_cb(void)
{
return millis();
}
/*Read the touchpad*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
if (touch_has_signal())
{
if (touch_touched())
{
data->state = LV_INDEV_STATE_PRESSED;
/*Set the coordinates*/
data->point.x = touch_last_x;
data->point.y = touch_last_y;
}
else if (touch_released())
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * btn = lv_event_get_current_target_obj(e);
if (btn != NULL)
{
if(code == LV_EVENT_CLICKED)
{
void * btn_no_void = (void*)lv_event_get_user_data(e);
byte btn_no = (byte)((uintptr_t)btn_no_void);
lv_obj_t * screen = lv_obj_get_screen(btn);
if (screen != NULL)
{
Serial.println("Screen assigned");
if (screen == screen1)
{
Serial.println("Screen 1");
Screen2SetActive(btn_no);
lv_screen_load(screen2);
//Screen2SetActive(5);
}
if (screen == screen2)
{
Serial.println("Screen 2");
if (btn_no == 0)
{
lv_screen_load(screen1);
}
else
{
Create_Screen3(event_handler);
lv_screen_load(screen3);
}
}
if (screen == screen3)
{
Serial.println("Screen 3");
if (btn_no == 0)
{
lv_screen_load(screen2);
lv_obj_delete(screen3);
}
}
}
}
}
}
void setup()
{
Serial.begin(115200);
// Serial.setDebugOutput(true);
// while(!Serial);
Serial.println("SenseCap Indicator startup");
String LVGL_Arduino = String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.println(LVGL_Arduino);
pinMode(BUTTON_PIN, INPUT);
// Init Indicator hardware
extender_init();
myPacketSerial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, PACKET_UART_RXD, PACKET_UART_TXD);
myPacketSerial.setStream(&Serial1);
myPacketSerial.setPacketHandler(&onPacketReceived);
// Init Display
if (!gfx->begin(12000000L))
{
Serial.println("gfx->begin() failed!");
Serial.println("Expect sever errors !!!");
}
gfx->fillScreen(RGB565_BLACK);
#ifdef GFX_BL
pinMode(GFX_BL, OUTPUT);
digitalWrite(GFX_BL, HIGH);
#endif
lv_init();
/*Set a tick source so that LVGL will know how much time elapsed. */
lv_tick_set_cb(millis_cb);
/* register print function for debugging */
#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif
lv_screen_init(gfx, HOR_RES, VER_RES);
//lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_0);
//lv_display_set_antialiasing(disp,false);
// Init touch device
touch_init(HOR_RES, VER_RES, 0); // rotation will be handled by lvgl
/*Initialize the input device driver*/
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*Touchpad should have POINTER type*/
lv_indev_set_read_cb(indev, my_touchpad_read);
Create_Screen1(event_handler);
Screen2Create(event_handler);
Screen2InitData();
lv_screen_load(screen1);
Serial.println("Setup done");
}
void loop()
{
static TickType_t xLastWakeTime = xTaskGetTickCount();
/*
unsigned long startTime = millis();
while (digitalRead(BUTTON_PIN) == LOW)
{
if (millis() - startTime >= 10000)
{
ESP.restart();
//esp_restart();
}
}
*/
myPacketSerial.update();
// Check for a receive buffer overflow (optional).
if (myPacketSerial.overflow())
{
// Send an alert via a pin (e.g. make an overflow LED) or return a
// user-defined packet to the sender.
}
lv_task_handler(); /* let the GUI do its work */
// Simple delay always 5ms
//delay(5);
// This delay will adapt to the time consumed in the above tasks
// If these tasks consume time, it will delay shorter
vTaskDelayUntil( &xLastWakeTime, ( 5 / portTICK_PERIOD_MS ) );
}
void onPacketReceived(const uint8_t* buffer, size_t size)
{
#ifndef YOLO
Serial.printf("<--- recv len:%d, data: ", size);
for (int i = 0; i < size; i++) {
Serial.printf("0x%x ", buffer[i]);
}
Serial.println("");
#endif
if (size < 1) {
return;
}
byte index = 0;
TCommands Command = (TCommands)buffer[index++];
if ((Command == CMD_get_data) || (Command == CMD_set_value))
{
byte BatteryNumber = buffer[index++];
if (Command == CMD_get_data)
{
dword tempcalc;
word Volt = 0;
word Amps = 0;
memcpy(&Volt, &buffer[index], 2);
index += 2;
memcpy(&Amps, &buffer[index], 2);
index += 2;
Screen2AddData((BatteryNumber+1),Volt,Amps);
// Put data on screen 1
tempcalc = Volt * 3300u;
tempcalc /= (dword)((1u << BITS)-1u);
SetVoltageScreen1mV(BatteryNumber,(word)tempcalc);
tempcalc = Amps * 6000u;
tempcalc /= (dword)((1u << BITS)-1u);
SetCurrentScreen1mA(BatteryNumber,(word)tempcalc);
}
if (Command == CMD_set_value)
{
lv_color_t c = LV_COLOR_MAKE(0,0,0);
TBatteryStatus Status = (TBatteryStatus)buffer[index++];
switch (Status)
{
case BSCurrent:
case BSPower:
case BSResistor:
{
c = lv_palette_main(LV_PALETTE_DEEP_ORANGE);
break;
}
case BSCharge:
case BSVoltage:
case BSPulse:
{
c = lv_palette_main(LV_PALETTE_PURPLE);
break;
}
case BSOff:
{
c = LV_COLOR_MAKE(0X00,0xFF,0xFF);
break;
}
default:
{
c = lv_palette_main(LV_PALETTE_YELLOW);
}
}
SetLedScreen1(BatteryNumber,c);
}
}
}
このコードにより、SenseCap Indicatorは3画面のアプリケーションを表示します。Screen1はバッテリーデータの概要を色分けされたステータスインジケーターで表示し、Screen2は個々のバッテリーの詳細情報を提供し、Screen3は追加のコントロールや情報を提供します。

ESP32S3チップに接続された画面で開発し、RP2040チップにリンクされたセンサーを読み取ることができるようになりました。最終的にそれらを組み合わせます。
ESP32-S3チップに接続された画面での開発
Sensecap IndicatorはST7701モジュールを画面に使用しており、パラレルインターフェースを使用し、すでにESP32S3 MCUのピンに接続されています。 画面を駆動できるようにするために、いくつかのarduinoライブラリが必要です。こちらからダウンロードできます。
ライブラリがダウンロードされたら、Arduinoを開き、スケッチメニューでzipライブラリの追加を選択します。

ダウンロードしたライブラリをArduino IDEに追加します。

一方、同じスケッチメニューを確認し、「ライブラリを管理」を選択してから「PCA9535」を検索し、hidea kitaiが作成したものを選択してインストールする必要があります。

PCA9535ライブラリが必要なのは、ST7701のCSピンがPCA9535 i2cエキスパンダーモジュールに接続されているためです。具体的には、i2cモジュールのPin 4です。
必要なライブラリがすべてインストールされた後、画面がArduino環境で動作するかテストするために以下のコードをアップロードします。以下のコードをアップロードできます:
#include <Arduino_GFX_Library.h>
#include <PCA95x5.h>
#define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin
/* More dev device declaration: https://github.com/moononournation/Arduino_GFX/wiki/Dev-Device-Declaration */
#if defined(DISPLAY_DEV_KIT)
Arduino_GFX *gfx = create_default_Arduino_GFX();
#else /* !defined(DISPLAY_DEV_KIT) */
#define GFX_DEV_DEVICE ESP32_S3_RGB
#define GFX_BL 45
Arduino_DataBus *bus = new Arduino_SWSPI(
GFX_NOT_DEFINED /* DC */, PCA95x5::Port::P04 /* CS */,
41 /* SCK */, 48 /* MOSI */, GFX_NOT_DEFINED /* MISO */);
// option 1:
// Uncomment for 4" rect display
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
18 /* DE */, 17 /* VSYNC */, 16 /* HSYNC */, 21 /* PCLK */,
4 /* R0 */, 3 /* R1 */, 2 /* R2 */, 1 /* R3 */, 0 /* R4 */,
10 /* G0 */, 9 /* G1 */, 8 /* G2 */, 7 /* G3 */, 6 /* G4 */, 5 /* G5 */,
15 /* B0 */, 14 /* B1 */, 13 /* B2 */, 12 /* B3 */, 11 /* B4 */,
1 /* hsync_polarity */, 10 /* hsync_front_porch */, 8 /* hsync_pulse_width */, 50 /* hsync_back_porch */,
1 /* vsync_polarity */, 10 /* vsync_front_porch */, 8 /* vsync_pulse_width */, 20 /* vsync_back_porch */);
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
480 /* width */, 480 /* height */, rgbpanel, 2 /* rotation */, true /* auto_flush */,
bus, GFX_NOT_DEFINED /* RST */, st7701_type1_init_operations, sizeof(st7701_type1_init_operations));
#endif /* !defined(DISPLAY_DEV_KIT) */
/*******************************************************************************
* End of Arduino_GFX setting
******************************************************************************/
void setup(void)
{
Serial.begin(115200);
// Serial.setDebugOutput(true);
// while(!Serial);
Serial.println("Arduino_GFX Hello World example");
#ifdef GFX_EXTRA_PRE_INIT
GFX_EXTRA_PRE_INIT();
#endif
// Init Display
if (!gfx->begin())
{
Serial.println("gfx->begin() failed!");
}
gfx->fillScreen(BLACK);
#ifdef GFX_BL
pinMode(GFX_BL, OUTPUT);
digitalWrite(GFX_BL, HIGH);
#endif
gfx->setCursor(10, 10);
gfx->setTextColor(RED);
gfx->println("Sensecap Indicator");
delay(5000); // 5 seconds
}
void loop()
{
gfx->setCursor(random(gfx->width()), random(gfx->height()));
gfx->setTextColor(random(0xffff), random(0xffff));
gfx->setTextSize(random(6) /* x scale */, random(6) /* y scale */, random(2) /* pixel_margin */);
gfx->println("Sensecap Indicator");
delay(1000); // 1 second
}
すべてがうまくいけば、「Sensecap Indicator」のテキストが画面にランダムに表示されます。

RP2040チップに接続されたセンサーを読み取る
上記の準備セクションで述べたように、すべてのセンサーはRP2040に接続されています。RP2040にまだデフォルトのファームウェアがインストールされていると仮定すると、センサーデータはUARTインターフェースを使用してESP32S3に送信されます。
ESP32S3がデータを読み取れるようにするには、PacketSerialというライブラリをインストールする必要があります。

ライブラリがインストールされた後、以下のコードをアップロードしてESP32S3でセンサーデータを取得できます:
//
// Copyright (c) 2012 Christopher Baker <https://christopherbaker.net>
//
// SPDX-License-Identifier: MIT
//
#include <PacketSerial.h>
PacketSerial myPacketSerial;
#define RXD2 20
#define TXD2 19
#define PKT_TYPE_SENSOR_SCD41_CO2 0XB2
#define PKT_TYPE_SENSOR_SHT41_TEMP 0XB3
#define PKT_TYPE_SENSOR_SHT41_HUMIDITY 0XB4
#define PKT_TYPE_SENSOR_TVOC_INDEX 0XB5
#define DEBUG 0
void setup()
{
// We begin communication with our PacketSerial object by setting the
// communication speed in bits / second (baud).
myPacketSerial.begin(115200);
// If we want to receive packets, we must specify a packet handler function.
// The packet handler is a custom function with a signature like the
// onPacketReceived function below.
Serial1.begin(115200, SERIAL_8N1, RXD2, TXD2);
myPacketSerial.setStream(&Serial1);
myPacketSerial.setPacketHandler(&onPacketReceived);
}
void loop()
{
// Do your program-specific loop() work here as usual.
// The PacketSerial::update() method attempts to read in any incoming serial
// data and emits received and decoded packets via the packet handler
// function specified by the user in the void setup() function.
//
// The PacketSerial::update() method should be called once per loop(). Failure
// to call the PacketSerial::update() frequently enough may result in buffer
// serial overflows.
myPacketSerial.update();
// Check for a receive buffer overflow (optional).
if (myPacketSerial.overflow())
{
// Send an alert via a pin (e.g. make an overflow LED) or return a
// user-defined packet to the sender.
//
// Ultimately you may need to just increase your recieve buffer via the
// template parameters (see the README.md).
}
}
void onPacketReceived(const uint8_t *buffer, size_t size) {
Serial.printf("<--- recv len:%d, data: ", size);
if (size < 1) {
return;
}
//byte serbytes[] = buffer[i];
float dataval;
switch (buffer[0]) {
case PKT_TYPE_SENSOR_SCD41_CO2:
{
memcpy(&dataval, &buffer[1], sizeof(float));
Serial.print("CO2 Level: ");
Serial.println(dataval);
break;
}
default:
break;
}
switch (buffer[0]) {
case PKT_TYPE_SENSOR_SHT41_TEMP:
{
memcpy(&dataval, &buffer[1], sizeof(float));
Serial.print("sht temp: ");
Serial.println(dataval, 2);
break;
}
default:
break;
}
switch (buffer[0]) {
case PKT_TYPE_SENSOR_SHT41_HUMIDITY:
{
memcpy(&dataval, &buffer[1], sizeof(float));
Serial.print("sht humidity: ");
Serial.println(dataval, 2);
break;
}
default:
break;
}
switch (buffer[0]) {
case PKT_TYPE_SENSOR_TVOC_INDEX:
{
memcpy(&dataval, &buffer[1], sizeof(float));
Serial.print("TVOC INDEX: ");
Serial.println(dataval);
break;
}
default:
break;
}
}
シリアルモニターをクリックして開き、ボーレートを115200に設定すると、RP2040からのセンサーデータが表示されます。

2つの例を組み合わせて、センサーデータを画面に表示する
Arduino IDEのサンプルメニューを開き、GFX library for Arduinoに移動して、SI_displaysensordataサンプルを選択してアップロードします。

正常にアップロードされると、画面にセンサーデータが表示されます。

おめでとうございます!これでArduino IDEを使用してSensecap Indicatorを開発できるようになりました!
さらなる情報
- これはまだ開発のフェーズONEであり、このチュートリアルで設定されていないのはタッチスクリーン部分です。FT6336モジュール用のいくつかのArduinoライブラリを試しましたが、成功した結果は得られませんでした。
- これは、FT6366モジュールのINTピンとRESETピンがPCA9535 I2Cエクスパンダーに接続されており、ライブラリで手動で設定する必要があるためです。将来的にこれを再試行するかもしれません。
- ちなみに、Arduino GFXライブラリの使用についてより理解を深めるには、Arduino_GFX githubページをご覧ください。
特別な謝辞
Sensecap indicatorをサポートするSWSPI設定ファイルを提供してくれたgithubユーザーu4mzu4に感謝します。
タッチスクリーンサポートを含む、SenseCAP indicator用のArduinoライブラリの更新を支援してくれたLongDirtyAnimAlfに感謝します。
✨ コントリビュータープロジェクト
- このプロジェクトは、Seeed Studio Contributor Projectによってサポートされています。
- LongDirtyAnimAlf、Hendra、u4mzu4の努力に感謝し、あなたの作品が展示されます。
技術サポート & 製品ディスカッション
私たちの製品をお選びいただき、ありがとうございます!私たちは、お客様の製品体験が可能な限りスムーズになるよう、さまざまなサポートを提供しています。異なる好みやニーズに対応するため、複数のコミュニケーションチャンネルを提供しています。