Skip to main content

使用 Arduino 开发 SenseCAP Indicator 双芯片

SenseCAP Indicator 是一款 4 英寸触摸屏设备,由 ESP32 和 RP2040 双 MCU 驱动。ESP32 和 RP2040 都是功能强大的微控制器,提供了丰富的特性和功能。

本教程将指导您使用 Arduino 框架的简洁性和灵活性为 Sensecap Indicator 开发自己的定制项目/固件。

硬件准备

我在这里使用 SenseCAP Indicator 作为硬件,它上面有四种类型的传感器(CO2、温度、湿度、TVOC)。这里的内容应该包括:

SenseCAP Indicator D1S

硬件概述和开发知识

Indicator 设计有两个 MCU,分别是 RP2040 和 ESP32S3,它们同时工作。

根据上面的图表,我们知道:

  1. 所有传感器都通过 I2C 协议连接到 RP2040 微控制器
  2. 有一个使用 PCA9535 IC 的 I2C IO 扩展器模块
  3. 屏幕连接到 ESP32S3 微控制器,其中 2 个引脚(CS、RESET)连接到 PCA9535 I2C 扩展器
  4. RP2040 通过 ESP32S3 的引脚 20 和引脚 19 使用 UART 接口连接到 ESP32S3

因此,如果将 Sensecap Indicator 插入计算机,您将看到 2 个串口,一个用于 RP2040,一个用于 ESP32S3。带有 USB-SERIAL CH340 信息的那个是连接到 ESP32S3 的,这是本教程其余部分将使用的串口。

软件准备

我们在这里使用 Arduino。

note

在继续本教程之前,请确保在 Arduino IDE 中完成以下步骤:

  1. ESP32 板定义:确保 ESP32 板定义已安装并更新到最新版本。如果 Arduino IDE 中还没有 ESP32 板,您可以按照此指南进行操作。
  2. 板选择:选择 ESP32S3 Dev Module 作为板定义。
  3. 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 中。

同样,为了顺利集成,您需要检查相同的 sketch 菜单并选择"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 中,以避免兼容性问题。

入门

安装所有必要的库后,上传下面的代码来测试屏幕是否在 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将出现在显示屏上,显示Screen2以及通过UART连接接收到的任何温度和湿度数据。

具有多屏幕和数据可视化的高级GUI应用程序

第二个示例在基础应用程序的基础上构建,添加了更复杂的功能,包括电池监控、动态数据可视化和颜色编码状态指示器。您可以从存储库下载包含源代码和头文件的完整项目: 下载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 将显示一个三屏应用程序。Screen1 显示电池数据概览和颜色编码的状态指示器,Screen2 提供单个电池的详细信息,Screen3 提供额外的控制或信息。

特别感谢

感谢 github 用户 u4mzu4 提供支持 Sensecap indicator 的 SWSPI 配置文件

感谢 LongDirtyAnimAlf 帮助更新 SenseCAP indicator 的 Arduino 库,包括触摸屏支持。

✨ 贡献者项目

技术支持与产品讨论

感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...