Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions boards/heltec-rcc6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"build": {
"arduino": {
"partitions": "default_16MB.csv"
},
"core": "esp32",
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32c6",
"variant": "heltec_rcc6"
},
"connectivity": ["bluetooth", "wifi", "lora"],
"debug": {
"openocd_target": "esp32c6.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec RCC6",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org/",
"vendor": "Heltec"
}
34 changes: 22 additions & 12 deletions src/graphics/TFTDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ static void rak14014_tpIntHandle(void)
_rak14014_touch_int = true;
}

#elif defined(HACKADAY_COMMUNICATOR)
#elif defined(HACKADAY_COMMUNICATOR) || defined(HELTEC_RCC6)
#include <Arduino_GFX_Library.h>
Arduino_GFX *tft = nullptr;

Expand Down Expand Up @@ -1307,7 +1307,7 @@ void TFTDisplay::display(bool fromBlank)
}
}
}
#if defined(HACKADAY_COMMUNICATOR)
#if defined(HACKADAY_COMMUNICATOR) || defined(HELTEC_RCC6)
tft->draw16bitBeRGBBitmap(0, yStart, repaintChunkBuffer, displayWidth, rowsThisChunk);
#else
tft->pushImage(0, yStart, displayWidth, rowsThisChunk, repaintChunkBuffer);
Expand Down Expand Up @@ -1407,7 +1407,7 @@ void TFTDisplay::display(bool fromBlank)
linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
#endif
}
#if defined(HACKADAY_COMMUNICATOR)
#if defined(HACKADAY_COMMUNICATOR) || defined(HELTEC_RCC6)
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
#else
Expand Down Expand Up @@ -1487,7 +1487,7 @@ void TFTDisplay::sendCommand(uint8_t com)
display(true);
if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
#elif defined(HACKADAY_COMMUNICATOR) || defined(HELTEC_RCC6)
tft->displayOn();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
Expand All @@ -1502,8 +1502,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(true); // using unPhone library
#endif
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
#elif !defined(M5STACK) && !defined(ST7789_CS) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_RCC6)
tft->setBrightness(172);
#endif
break;
Expand All @@ -1515,7 +1514,7 @@ void TFTDisplay::sendCommand(uint8_t com)
tft->clear();
if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
#elif defined(HACKADAY_COMMUNICATOR) || defined(HELTEC_RCC6)
tft->displayOff();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
Expand All @@ -1530,7 +1529,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(false); // using unPhone library
#endif
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_RCC6)
tft->setBrightness(0);
#endif
break;
Expand All @@ -1546,7 +1545,7 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
// todo
#elif !defined(HACKADAY_COMMUNICATOR)
#elif !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_RCC6)
tft->setBrightness(_brightness);
LOG_DEBUG("Brightness is set to value: %i ", _brightness);
#endif
Expand All @@ -1564,7 +1563,8 @@ bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
return true;
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && !defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1) && !defined(HELTEC_RCC6)
return tft->touch() != nullptr;
#else
return false;
Expand All @@ -1583,7 +1583,8 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
} else {
return false;
}
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && !defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1) && !defined(HELTEC_RCC6)
return tft->getTouch(x, y);
#else
return false;
Expand Down Expand Up @@ -1611,6 +1612,9 @@ bool TFTDisplay::connect()
tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */,
12 /* col offset 1 */, 0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */,
nv3007_279_init_operations, sizeof(nv3007_279_init_operations));
#elif defined(HELTEC_RCC6)
Arduino_DataBus *bus = new Arduino_SWSPI(TFT_RS, TFT_CS, TFT_SCL, TFT_SDA, GFX_NOT_DEFINED);
tft = new Arduino_NV3001B(bus, TFT_RST, 3, true, TFT_WIDTH, TFT_HEIGHT, 0, 0, 0, 0);
#else
tft = new LGFX;
#endif
Expand All @@ -1622,8 +1626,12 @@ bool TFTDisplay::connect()
#ifdef UNPHONE
unphone.backlight(true); // using unPhone library
#endif
#ifdef HACKADAY_COMMUNICATOR
#if defined(HACKADAY_COMMUNICATOR) || defined(HELTEC_RCC6)
#ifdef HELTEC_RCC6
bool beginStatus = tft->begin(SPI_FREQUENCY);
#else
bool beginStatus = tft->begin();
#endif
if (beginStatus)
LOG_DEBUG("TFT Success!");
else
Expand All @@ -1647,6 +1655,8 @@ bool TFTDisplay::connect()
tft->setRotation(2); // T-Watch S3 left-handed orientation
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
tft->setRotation(0); // use config.yaml to set rotation
#elif defined(HELTEC_RCC6)
tft->setRotation(3);
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
#endif
Expand Down
138 changes: 138 additions & 0 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,138 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_

#endif

#ifdef HELTEC_RCC6

constexpr uint8_t NV3001B_RDDID = 0x04;
constexpr uint8_t NV3001B_RDID1 = 0xDA;
constexpr uint8_t NV3001B_RDID2 = 0xDB;
constexpr uint8_t NV3001B_RDID3 = 0xDC;
constexpr uint32_t NV3001B_EXPECTED_ID = 0x300101;
constexpr uint32_t NV3001B_FLOATING_ID = 0xFFFFFF;
constexpr uint32_t NV3001B_HELD_LOW_ID = 0x000000;

void nv3001bSpiDelay()
{
delayMicroseconds(1);
}

void writeNv3001bCommandForRead(uint8_t command, uint8_t sck, uint8_t mosi, uint8_t dc)
{
pinMode(mosi, OUTPUT);
digitalWrite(dc, LOW);
for (uint8_t mask = 0x80; mask; mask >>= 1) {
digitalWrite(sck, LOW);
digitalWrite(mosi, (command & mask) ? HIGH : LOW);
nv3001bSpiDelay();
digitalWrite(sck, HIGH);
nv3001bSpiDelay();

if (mask == 0x01) {
digitalWrite(dc, HIGH);
pinMode(mosi, INPUT);
nv3001bSpiDelay();
}
}

digitalWrite(sck, LOW);
nv3001bSpiDelay();
}

uint8_t readNv3001bBit(uint8_t sck, uint8_t mosi)
{
digitalWrite(sck, HIGH);
nv3001bSpiDelay();
uint8_t bit = digitalRead(mosi) ? 1 : 0;
digitalWrite(sck, LOW);
nv3001bSpiDelay();
return bit;
}

uint8_t readNv3001bByte(uint8_t sck, uint8_t mosi)
{
uint8_t data = 0;
for (uint8_t bit = 0; bit < 8; bit++)
data = (data << 1) | readNv3001bBit(sck, mosi);
return data;
}

void readNv3001bRegister(uint8_t command, uint8_t *data, uint8_t len, uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc)
{
digitalWrite(cs, LOW);

writeNv3001bCommandForRead(command, sck, mosi, dc);

if (command == NV3001B_RDDID)
(void)readNv3001bBit(sck, mosi);

for (uint8_t i = 0; i < len; i++)
data[i] = readNv3001bByte(sck, mosi);

pinMode(mosi, OUTPUT);
digitalWrite(mosi, HIGH);
digitalWrite(cs, HIGH);
digitalWrite(dc, HIGH);
digitalWrite(sck, LOW);
}

uint32_t packNv3001bId(const uint8_t *id)
{
return (static_cast<uint32_t>(id[0]) << 16) | (static_cast<uint32_t>(id[1]) << 8) | id[2];
}

uint32_t get_nv3001b_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_t rst, uint8_t en, uint8_t bl)
{
pinMode(en, OUTPUT);
digitalWrite(en, TFT_EN_ON);
delay(100);
pinMode(bl, OUTPUT);
digitalWrite(bl, TFT_BACKLIGHT_ON);
delay(100);

pinMode(cs, OUTPUT);
pinMode(sck, OUTPUT);
pinMode(mosi, OUTPUT);
pinMode(dc, OUTPUT);
pinMode(rst, OUTPUT);
digitalWrite(cs, HIGH);
digitalWrite(dc, HIGH);
digitalWrite(sck, LOW);
digitalWrite(mosi, HIGH);
digitalWrite(rst, HIGH);
delay(100);
digitalWrite(rst, LOW);
delay(120);
digitalWrite(rst, HIGH);
delay(120);

uint8_t rddidBytes[3] = {};
uint8_t rdidBytes[3] = {};
readNv3001bRegister(NV3001B_RDDID, rddidBytes, sizeof(rddidBytes), cs, sck, mosi, dc);
readNv3001bRegister(NV3001B_RDID1, &rdidBytes[0], 1, cs, sck, mosi, dc);
readNv3001bRegister(NV3001B_RDID2, &rdidBytes[1], 1, cs, sck, mosi, dc);
readNv3001bRegister(NV3001B_RDID3, &rdidBytes[2], 1, cs, sck, mosi, dc);

uint32_t rddid = packNv3001bId(rddidBytes);
uint32_t rdid = packNv3001bId(rdidBytes);
uint32_t id = rddid == NV3001B_EXPECTED_ID ? rddid : rdid;
bool noResponse = (rddid == NV3001B_FLOATING_ID && rdid == NV3001B_FLOATING_ID) ||
(rddid == NV3001B_HELD_LOW_ID && rdid == NV3001B_HELD_LOW_ID);

LOG_INFO("Heltec RCC6 NV3001B RDDID=%06x RDID=%06x", rddid, rdid);
if (id != NV3001B_EXPECTED_ID || noResponse) {
LOG_INFO("Heltec RCC6 NV3001B display not detected");
digitalWrite(bl, TFT_BACKLIGHT_OFF);
digitalWrite(en, TFT_EN_OFF);
delay(1);
pinMode(en, INPUT);
} else {
LOG_INFO("Heltec RCC6 NV3001B display detected");
}
return id;
}

#endif

// When armed by loadFromDisk, the decode callback writes satellite entries
// straight into these maps instead of the temp vectors. Nullptr = legacy
// push_back-to-vector path for backup/restore and other decoders.
Expand Down Expand Up @@ -1010,6 +1142,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
hasScreen = false;
}
#endif // HELTEC_MESH_NODE_T114
#ifdef HELTEC_RCC6
uint32_t nv3001b_id = get_nv3001b_id(TFT_CS, TFT_SCL, TFT_SDA, TFT_RS, TFT_RST, TFT_EN, TFT_BL);
if (nv3001b_id != NV3001B_EXPECTED_ID) {
hasScreen = false;
}
#endif // HELTEC_RCC6
Comment on lines +1145 to +1150

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Propagate the absent-display state into TFT startup. hasScreen only affects Bluetooth pairing mode here; RCC6 still builds with HAS_SCREEN, so TFTDisplay::connect() can still initialize a panel that was just detected as absent and powered off. Thread that state into display startup or gate the module connection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/NodeDB.cpp` around lines 1139 - 1144, Update the HELTEC_RCC6 display
initialization flow so the false value assigned to hasScreen after the NV3001B
ID check prevents TFTDisplay::connect() from starting when no panel is detected.
Thread hasScreen into the TFT startup path or gate the module connection, while
preserving normal initialization when the expected display is present.

#elif ARCH_PORTDUINO
bool hasScreen = false;
if (portduino_config.displayPanel)
Expand Down
4 changes: 3 additions & 1 deletion src/platform/esp32/architecture.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV
#elif defined(MESHNOLOGY_W10)
#define HW_VENDOR meshtastic_HardwareModel_MESHNOLOGY_W10
#elif defined(HELTEC_RCC6)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_RCC6
#else
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
#endif
Expand Down Expand Up @@ -244,4 +246,4 @@
// Setup flag, which indicates if our device supports dynamic light sleep
#if defined(HAS_ESP32_PM_SUPPORT) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE)
#define HAS_ESP32_DYNAMIC_LIGHT_SLEEP 1
#endif
#endif
20 changes: 20 additions & 0 deletions variants/esp32c6/heltec_rcc6/pins_arduino.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h

#include <stdint.h>

#define USB_VID 0x303A
#define USB_PID 0x1001

static const uint8_t TX = 16;
static const uint8_t RX = 17;

static const int8_t SDA = -1;
static const int8_t SCL = -1;

static const uint8_t MISO = 20;
static const uint8_t SCK = 21;
static const uint8_t MOSI = 22;
static const uint8_t SS = 23;

#endif /* Pins_Arduino_h */
31 changes: 31 additions & 0 deletions variants/esp32c6/heltec_rcc6/platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[env:heltec-rcc6]
custom_meshtastic_hw_model = 143
custom_meshtastic_hw_model_slug = HELTEC_RCC6
custom_meshtastic_architecture = esp32-c6
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec RCC6
custom_meshtastic_tags = Heltec
custom_meshtastic_has_mui = false

extends = esp32c6_base
board = heltec-rcc6
board_level = pr
board_build.partitions = default_16MB.csv
upload_protocol = esptool

build_flags =
${esp32c6_base.build_flags}
-D HELTEC_RCC6
-I variants/esp32c6/heltec_rcc6
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DMESHTASTIC_EXCLUDE_AUDIO=1

lib_deps =
${esp32c6_base.lib_deps}
https://github.com/Quency-D/Arduino_GFX/archive/4d5afb05ce70a51895fe65efc7838d606b2a95a7.zip

lib_ignore =
${esp32c6_base.lib_ignore}
ESP32 Codec2
Loading