diff --git a/README.md b/README.md index 37369ca..1f94941 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ The library declares these Arduino dependencies: - `Adafruit BME280 Library` - `BH1750` version 1.3.0 or newer from [claws/BH1750](https://github.com/claws/BH1750) +- `SparkFun MMC5983MA Magnetometer Arduino Library` version 1.1.5 or newer + for the onboard MMC5983MA magnetometer +- `ICM42670P` version 1.0.8 or newer from TDK InvenSense as the compatible + driver used by the onboard ICM42607P example - `Adafruit NeoPixel` - `ESP32Servo` for ESP32-based RadioCore boards - `NonBlockingRTTTL` version 1.4.0 or newer for non-blocking buzzer melodies @@ -47,6 +51,23 @@ that fork manually in the Arduino sketchbook `libraries` directory. It is not listed in `library.properties` because NV3001B support has not been merged into the Arduino Library Manager version of `GFX Library for Arduino`. +`DEPG1020BNS770F1_Display`, `E0213A367_Display`, and +`DEPG0290BNS800_Display` require the development version of +`heltec-eink-modules` containing the external RD02E constructors and RC52 +platform support. The public Library Manager `4.6.0` package does not contain +these interfaces. From the Arduino sketchbook `libraries` directory, install +the verified revision with: + +```powershell +git clone https://github.com/Quency-D/heltec-eink-modules.git +git -C heltec-eink-modules checkout 7f5f0dafe3bce0a39350901ae3cc8c745463045f +``` + +`GFX_Root` is bundled with `heltec-eink-modules` and does not need a separate +installation. The Heltec Arduino core used to select RC32 or RC52 is a board +environment requirement, not an Arduino library dependency; no minimum core +version is asserted here. + ## Examples Open the examples from `File > Examples > RadioCore_Kit` in the Arduino @@ -56,11 +77,17 @@ compile time. | Example | Purpose | Documentation | | --- | --- | --- | | `BH1750_Read` | Read ambient light over Sensor I2C | [Guide](examples/BH1750_Read/README.md) | +| `MMC5983MA_Read` | Read the onboard three-axis magnetic field in gauss on RC32 and RC52 | [Guide](examples/MMC5983MA_Read/README.md) | +| `ICM42607P_Read` | Read onboard acceleration in g and angular rate in degrees per second on RC32 and RC52 | [Guide](examples/ICM42607P_Read/README.md) | | `CH01_Read` | Estimate CH01 gas concentration and trend | [Guide](examples/CH01_Read/README.md) | | `CO01_Read` | Run the CO01 warm-up cycle and estimate concentration | [Guide](examples/CO01_Read/README.md) | | `VO01_Read` | Estimate VO01 gas concentration and trend | [Guide](examples/VO01_Read/README.md) | | `Soil_Moisture_Read` | Read and calibrate an analog soil moisture sensor | [Guide](examples/Soil_Moisture_Read/README.md) | | `NV3001B_Display` | Exercise the 128-by-220 color TFT | [Guide](examples/NV3001B_Display/README.md) | +| `DEPG1020BNS770F1_Display` | Startup black/white test, full baseline, and partial-window updates on the 960-by-640 panel (RC32/RC52) | [Guide](examples/DEPG1020BNS770F1_Display/README.md) | +| `E0213A367_Display` | Refresh the 122-by-250 drawable E0213A367 e-paper panel on RC32 or RC52 | [Guide](examples/E0213A367_Display/README.md) | +| `DEPG0290BNS800_Display` | Test full refresh and fast-window updates on the 296-by-128 landscape panel (RC32/RC52) | [Guide](examples/DEPG0290BNS800_Display/README.md) | +| `Rotary_Encoder` | Report rotary direction, signed count, and user-button presses on RC32 and RC52 | [Sketch](examples/Rotary_Encoder/Rotary_Encoder.ino) | | `WS2812` | Run a three-color breathing animation | [Sketch](examples/WS2812/WS2812.ino) | | `Buzzer_Control` | Play and repeat an RTTTL melody through the board-designated buzzer output | [Sketch](examples/Buzzer_Control/Buzzer_Control.ino) | | `Motor_Control` | Drive two direction inputs on an external motor driver | [Sketch](examples/Motor_Control/Motor_Control.ino) | @@ -81,10 +108,19 @@ A new board declares only the capabilities it supports: - Sensor I2C instance, pins, frequency, and optional sensor-power control for `BH1750_Read`. +- Onboard MMC5983MA and ICM42607P capability flags plus any shared sensor-reset + pin and release level required by their examples. - ADC pins and optional power controls for `CH01_Read`, `VO01_Read`, `CO01_Read`, and `Soil_Moisture_Read`; CO01 also requires its control pin. - NV3001B pins, active levels, SPI frequency, and one supported display transport for `NV3001B_Display`. +- RD02E driver-board pins and active levels for the shared paged + DEPG1020BNS770F1, E0213A367, and DEPG0290BNS800 examples. Document the + connector mapping and shared-pin conflicts. +- A rotary encoder I2C address and phase masks for `Rotary_Encoder`, plus an + independently declared user-button pin, active level, and input mode. The + current RC32 and RC52 mappings share the configured Sensor I2C bus and power + rail. - A data pin for `WS2812`, a buzzer output pin and any shared peripheral power control required by `Buzzer_Control`, two motor-driver inputs for `Motor_Control`, a relay output and active level for `Relay_Control`, and two diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md index aa3fba0..4419e57 100644 --- a/docs/CONTEXT.md +++ b/docs/CONTEXT.md @@ -10,7 +10,7 @@ The shared Arduino package for the RadioCore hardware family. _Avoid_: RadioCore Library, sensor driver bundle, full board support package **Sensor I2C**: -The board-designated I2C bus used by RadioCore examples for external sensors. +The board-designated I2C bus used by RadioCore onboard and external sensors. It may map to different `TwoWire` instances or pins on different boards. _Avoid_: default Wire, I2C0 @@ -19,6 +19,16 @@ The board-controlled power domain that supplies external sensors and may be shared with other peripherals. _Avoid_: sensor VCC pin, sensor enable pin, Vext +**MMC5983MA magnetometer**: +The board-integrated three-axis magnetic-field sensor on Sensor I2C in RC32 +and RC52. +_Avoid_: MMC5983, compass module, external magnetometer + +**ICM42607P motion sensor**: +The board-integrated inertial sensor on Sensor I2C in RC32 and RC52; its +compatible Arduino driver dependency is named ICM42670P. +_Avoid_: ICM42670P sensor, external IMU + **Board configuration**: Compile-time hardware facts for one RadioCore board, without peripheral initialization or example behavior. @@ -29,10 +39,51 @@ The 128-by-220 color TFT panel configured by RadioCore display examples. _Avoid_: OLED, generic screen **Display transport**: -The board-selected SPI mechanism used to send commands and pixels to the -NV3001B display. +The board-selected SPI mechanism used to send commands and pixels to a +configured display device, including the NV3001B display or an e-paper driver +board. _Avoid_: default SPI, display bus +**DEPG1020BNS770F1 e-paper panel**: +The 960-by-640 monochrome e-paper panel driven by an SSD1677 controller. +_Avoid_: generic 10.2-inch display, TFT, grayscale panel + +**E0213A367 e-paper panel**: +The 128-by-250 monochrome e-paper panel with a 122-by-250 drawable area, +connected through the RD02E e-paper driver board in RadioCore examples. +_Avoid_: generic 2.13-inch display, Wireless Paper display + +**DEPG0290BNS800 e-paper panel**: +The 128-by-296 monochrome e-paper panel identified by its FPC-7519 rev.b +connector marking and supported by the matching heltec-eink-modules driver. +_Avoid_: generic 2.9-inch display, DEPG0290BNS75A, Vision Master display + +**RD02E e-paper driver board**: +The external board that supplies switched power and high-voltage drive circuits +for compatible e-paper panels, including DEPG1020BNS770F1, E0213A367, and +DEPG0290BNS800, and exposes their controller logic interface. +_Avoid_: bare panel, passive adapter, TFT connector + +**Full-screen e-paper refresh**: +A monochrome update that refreshes the complete e-paper image in one operation. +_Avoid_: partial refresh, fast refresh, grayscale refresh + +**Partial-window e-paper refresh**: +A monochrome update that uses the panel's partial-refresh waveform to update +an explicitly selected region while preserving the surrounding displayed image. +_Avoid_: full-screen refresh, automatic change detection, grayscale refresh + +**Fast-window e-paper update**: +A differential fast refresh whose image-RAM writes are restricted to a selected +window while the surrounding displayed image is preserved; it does not imply +that the controller drives only the selected gate region. +_Avoid_: proven local gate scan, full-screen refresh, grayscale refresh + +**E-paper reference image**: +Controller-side image data that provides a baseline for subsequent partial +refreshes, distinct from the MCU drawing buffer and the physically visible image. +_Avoid_: MCU framebuffer, visible image + **Analog gas sensor**: A CH01, VO01, or CO01 device that represents gas concentration as an analog output voltage. @@ -90,6 +141,16 @@ The board-designated digital signal connected to the logic input of an external relay module or driver. _Avoid_: relay coil output, load power output +**Rotary encoder input**: +The board-designated two-channel user control whose rotation produces previous +or next steps. +_Avoid_: rotary button, generic GPIO input + +**User button input**: +The board-designated momentary digital input reported independently from +rotary encoder movement. +_Avoid_: rotary encoder press, enter key + **Servo control interface**: The board-designated pair of servo PWM signals and enable signal used to connect an external dual-servo driver such as the RS-SV01. diff --git a/docs/adr/0001-keep-e-paper-drivers-outside-radiocore-kit.md b/docs/adr/0001-keep-e-paper-drivers-outside-radiocore-kit.md new file mode 100644 index 0000000..bb106ce --- /dev/null +++ b/docs/adr/0001-keep-e-paper-drivers-outside-radiocore-kit.md @@ -0,0 +1,7 @@ +# Keep e-paper panel drivers outside RadioCore_Kit + +RadioCore_Kit owns RadioCore board configuration and board-adaptive examples, +while reusable e-paper panel drivers remain in `heltec-eink-modules`. This keeps +the e-paper library independently usable and avoids coupling panel-controller +behavior to RadioCore hardware, at the cost of coordinating compatible releases +across the two repositories. diff --git a/examples/DEPG0290BNS800_Display/DEPG0290BNS800_Display.ino b/examples/DEPG0290BNS800_Display/DEPG0290BNS800_Display.ino new file mode 100644 index 0000000..8e3fd08 --- /dev/null +++ b/examples/DEPG0290BNS800_Display/DEPG0290BNS800_Display.ino @@ -0,0 +1,174 @@ +/* + * RadioCore DEPG0290BNS800 e-paper example + * + * Uses an RD02E driver board and the existing DEPG0290BNS800 display class + * from heltec-eink-modules. A successful full refresh establishes the image + * baseline before repeated fast-window updates. + */ +#include +#include + +#if !RADIOCORE_HAS_DEPG0290BNS800_DISPLAY +#error "DEPG0290BNS800_Display requires a supported RadioCore board configuration." +#else + +#include + +namespace { + +constexpr uint16_t kPageHeight = 32; +constexpr uint32_t kStartupFrameHoldMs = 1000UL; +constexpr uint32_t kRefreshRestMs = 2000UL; +constexpr uint8_t kFastUpdatesBeforeFull = 10; +// In landscape orientation, logical Y maps to the controller's byte-addressed +// X axis. This window is byte aligned and stays inside the 296 x 128 canvas. +constexpr uint16_t kWindowLeft = 8; +constexpr uint16_t kWindowTop = 72; +constexpr uint16_t kWindowWidth = 280; +constexpr uint16_t kWindowHeight = 48; + +DEPG0290BNS800 display( + RADIOCORE_DEPG0290BNS800_DC, + RADIOCORE_DEPG0290BNS800_CS, + RADIOCORE_DEPG0290BNS800_BUSY, + RADIOCORE_DEPG0290BNS800_RST, + RADIOCORE_DEPG0290BNS800_ENABLE, + static_cast(RADIOCORE_DEPG0290BNS800_ENABLE_ACTIVE), + RADIOCORE_DEPG0290BNS800_MOSI, + RADIOCORE_DEPG0290BNS800_SCK, + kPageHeight); + +uint32_t refreshCount = 0; +uint8_t fastUpdateCount = 0; +bool hasBaseline = false; + +bool showStartupFrame(uint16_t color) +{ + display.fastmodeOff(); + display.fullscreen(); + if (!display.timedOut()) { + DRAW(display) { + display.fillScreen(color); + } + } + return !display.timedOut(); +} + +void drawUpdateRegion(uint32_t count) +{ + // DRAW runs twice in fast mode. Never advance state inside this function. + display.fillRect(kWindowLeft, kWindowTop, kWindowWidth, kWindowHeight, WHITE); + display.setTextColor(BLACK); + display.setTextSize(2); + display.setCursor(12, 84); + display.print(F("Refresh: ")); + display.println(count); + + if (count & 1UL) { + display.fillRect(248, 80, 32, 32, BLACK); + } else { + display.drawRect(248, 80, 32, 32, BLACK); + } +} + +void drawTestPage(uint32_t count) +{ + display.drawRect(0, 0, display.width(), display.height(), BLACK); + display.drawRect(4, 4, display.width() - 8, display.height() - 8, BLACK); + + display.setTextColor(BLACK); + display.setTextSize(2); + display.setCursor(12, 12); + display.println(F("RadioCore")); + + display.setTextSize(1); + display.setCursor(176, 16); + display.println(F("FAST WINDOW UPDATE")); + display.setCursor(12, 40); + display.print(F("Board: ")); + display.println(F(RADIOCORE_DEPG0290BNS800_BOARD_NAME)); + display.setCursor(12, 54); + display.println(F("DEPG0290BNS800 296 x 128 BW")); + display.drawLine(8, 68, display.width() - 9, 68, BLACK); + + drawUpdateRegion(count); +} + +} // namespace + +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println(); + Serial.print(F("RadioCore DEPG0290BNS800 example: ")); + Serial.println(F(RADIOCORE_DEPG0290BNS800_BOARD_NAME)); + + display.landscape(); + + Serial.println(F("Startup self-test: full-screen black.")); + if (!showStartupFrame(BLACK)) { + Serial.println(F("E-paper BUSY timeout during the black startup frame.")); + Serial.println(F("Startup self-test aborted; the next cycle will try a normal full refresh.")); + delay(kRefreshRestMs); + return; + } + delay(kStartupFrameHoldMs); + + Serial.println(F("Startup self-test: full-screen white.")); + if (!showStartupFrame(WHITE)) { + Serial.println(F("E-paper BUSY timeout during the white startup frame.")); + Serial.println(F("Startup self-test aborted; the next cycle will try a normal full refresh.")); + delay(kRefreshRestMs); + return; + } + delay(kStartupFrameHoldMs); +} + +void loop() +{ + const bool fullRefresh = !hasBaseline || + fastUpdateCount >= kFastUpdatesBeforeFull; + const uint32_t nextCount = refreshCount + 1; + Serial.print(fullRefresh ? F("Starting full refresh ") : F("Starting fast window update ")); + Serial.println(nextCount); + + if (fullRefresh) { + display.fastmodeOff(); + display.fullscreen(); + } else { + display.fastmodeOn(false); + display.setWindow(kWindowLeft, kWindowTop, kWindowWidth, kWindowHeight); + } + + if (!display.timedOut()) { + DRAW(display) { + if (fullRefresh) { + drawTestPage(nextCount); + } else { + drawUpdateRegion(nextCount); + } + } + } + + if (display.timedOut()) { + hasBaseline = false; + fastUpdateCount = 0; + Serial.println(F("E-paper BUSY timeout; the driver board was powered down.")); + Serial.println(F("The next cycle will rebuild the image with a full refresh.")); + } else { + refreshCount = nextCount; + hasBaseline = true; + if (fullRefresh) { + fastUpdateCount = 0; + } else { + ++fastUpdateCount; + } + Serial.println(F("Refresh complete; driver-board power remains on. Resting 2 s.")); + } + + delay(kRefreshRestMs); +} + +#endif // RADIOCORE_HAS_DEPG0290BNS800_DISPLAY diff --git a/examples/DEPG0290BNS800_Display/README.md b/examples/DEPG0290BNS800_Display/README.md new file mode 100644 index 0000000..ad15417 --- /dev/null +++ b/examples/DEPG0290BNS800_Display/README.md @@ -0,0 +1,106 @@ +# DEPG0290BNS800 E-Paper Display + +This example drives the monochrome DEPG0290BNS800 panel through an RD02E +e-paper driver board on Heltec RC32 and RC52. It performs cold-start coverage +tests, builds a full-screen image baseline, then repeatedly updates a selected +window with the driver's differential fast-refresh mode. + +## Requirements + +- A Heltec RC32 or RC52. +- An RD02E e-paper driver board and an electrically compatible + DEPG0290BNS800 panel marked FPC-7519 rev.b. + +## Library dependency + +This example requires `heltec-eink-modules` from the Quency-D development fork +at commit `7f5f0dafe3bce0a39350901ae3cc8c745463045f`. The public Library Manager +`4.6.0` package does not contain the required DEPG0290BNS800 external-driver +constructor or RC52 platform support. + +From the Arduino sketchbook `libraries` directory, install the verified +revision with: + +```powershell +git clone https://github.com/Quency-D/heltec-eink-modules.git +git -C heltec-eink-modules checkout 7f5f0dafe3bce0a39350901ae3cc8c745463045f +``` + +`GFX_Root` is included inside `heltec-eink-modules`; do not install it +separately. The Heltec Arduino core that provides the RC32 or RC52 board target +is a board environment requirement rather than an Arduino library dependency. +This example does not claim a minimum core version. + +The host-side connector mapping is shared with the other RD02E examples, but +that does not by itself prove panel-side compatibility. Before first power-up, +confirm the FPC pinout, panel voltage and high-voltage waveform requirements +against the hardware supplied for the panel. Do not connect the panel FPC +directly to RC32 or RC52. + +## Wiring + +RD02E P1 mates pin-for-pin with RC32 P3 or RC52 P1. Confirm connector pin 1 +against the PCB silkscreen before applying power. + +| Signal | RD02E P1 | RC32 P3 | RC52 P1 | +| --- | ---: | ---: | ---: | +| SCK/SCL | 1 | GPIO4 | P0.10 / D10 | +| CS | 2 | GPIO6 | P1.13 / D45 | +| MOSI/SDA | 3 | GPIO5 | P0.09 / D9 | +| DC | 8 | GPIO17 | P0.30 / D30 | +| RST/RES | 10 | GPIO16 | P0.28 / D28 | +| BUSY | 24 | GPIO38 | P1.02 / D34 | +| EN/VEINK_Ctrl | 26 | GPIO39 | P1.04 / D36 | + +`VEINK_Ctrl` is active low, BUSY is active high, and RST and CS are active low. +BS1 is tied to ground on RD02E, selecting 4-wire, 8-bit SPI. There is no +independent MISO signal. RC52 uses `SPI1`, explicitly remapped through the full +display constructor to SCK D10 and MOSI D9; CS remains D45. + +## Runtime behavior + +The sketch uses the driver's native 128-by-296 panel definition and selects a +296-by-128 landscape canvas. Cold startup displays full-screen black and white, +holding each successful frame for 1 second. These coverage frames do not change +the refresh counter. A BUSY timeout aborts the remaining startup test, powers +off RD02E and lets the next loop attempt the normal page as a full refresh. + +The normal page contains static identification text above a logical window at +`(8, 72, 280, 48)`. After a successful full-screen baseline, only this window +is rewritten with the refresh count and an alternating black/white block. After +10 successful fast-window updates, the sketch performs another full refresh. +Each completed operation is followed by a 2-second rest. + +`FAST WINDOW UPDATE` means that image-RAM writes are restricted to the selected +window while differential fast-refresh processing preserves the surrounding +displayed image. Available local documentation does not prove that the panel +controller drives only the selected gate region during this operation. + +All drawing uses a 32-row page buffer: `128 * 32 / 8 = 512` bytes. The paging +loop clips the final page to the remaining 8 rows and does not allocate the +4,736-byte full framebuffer. The existing driver retains its panel-specific +one-byte X RAM offset and custom 153-byte fast-refresh LUT. + +Each full or fast-mode setup asserts RD02E power, waits 100 ms, pulses reset low +for 10 ms and high for 10 ms, then sends software reset. Successful operations +leave RD02E powered so the controller image reference remains available. BUSY +waits time out after 60 seconds; timeout switches `VEINK_Ctrl` inactive, stops +later transfers and invalidates the example's baseline so the next cycle starts +with a complete full refresh. + +## Shared-pin restrictions + +Run this as a standalone example. Its GPIOs overlap soil-moisture ADC or power, +WS2812 and other display functions. Do not operate those features at the same +time. + +RD02E does not isolate MCU logic signals when its switched e-paper supply is +off. After a BUSY timeout, control signals may still create a reverse-current +path; this example does not stop SPI or place every control line in a high- +impedance state. + +The 10-update full-refresh interval, 2-second rest and supplied custom fast LUT +are example policies, not manufacturer guarantees for ghosting, temperature, +lifetime or safe update frequency. Validate full-screen coverage, orientation, +unchanged surrounding content and both black-to-white and white-to-black window +transitions on the actual RC32 and RC52 hardware. diff --git a/examples/DEPG1020BNS770F1_Display/DEPG1020BNS770F1_Display.ino b/examples/DEPG1020BNS770F1_Display/DEPG1020BNS770F1_Display.ino new file mode 100644 index 0000000..e6c1e78 --- /dev/null +++ b/examples/DEPG1020BNS770F1_Display/DEPG1020BNS770F1_Display.ino @@ -0,0 +1,184 @@ +/* + * RadioCore DEPG1020BNS770F1 e-paper example + * + * Requires a heltec-eink-modules version with DEPG1020BNS770F1 + * partial-window refresh support. + */ +#include +#include + +#if !RADIOCORE_HAS_DEPG1020BNS770F1_DISPLAY +#error "DEPG1020BNS770F1_Display requires a supported RadioCore board configuration." +#else + +#include + +namespace { + +constexpr uint16_t kPageHeight = 32; +constexpr uint32_t kStartupFrameHoldMs = 1000UL; +constexpr uint32_t kRefreshRestMs = 2000UL; +constexpr uint8_t kPartialRefreshesBeforeFull = 10; +// Byte-aligned, entirely inside 960 x 640; keep static content outside it. +constexpr uint16_t kWindowLeft = 40; +constexpr uint16_t kWindowTop = 216; +constexpr uint16_t kWindowWidth = 600; +constexpr uint16_t kWindowHeight = 56; + +DEPG1020BNS770F1 display( + RADIOCORE_DEPG1020BNS770F1_DC, + RADIOCORE_DEPG1020BNS770F1_CS, + RADIOCORE_DEPG1020BNS770F1_BUSY, + RADIOCORE_DEPG1020BNS770F1_RST, + RADIOCORE_DEPG1020BNS770F1_ENABLE, + RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE, + RADIOCORE_DEPG1020BNS770F1_MOSI, + RADIOCORE_DEPG1020BNS770F1_SCK, + kPageHeight); + +uint32_t refreshCount = 0; +uint8_t partialRefreshCount = 0; +bool hasBaseline = false; + +bool showStartupFrame(uint16_t color) +{ + display.fastmodeOff(); + display.fullscreen(); + if (!display.timedOut()) { + DRAW(display) { + display.fillScreen(color); + } + } + return !display.timedOut(); +} + +void drawUpdateRegion(uint32_t count) +{ + // DRAW runs twice in partial mode. Never advance state inside this function. + display.fillRect(kWindowLeft, kWindowTop, kWindowWidth, kWindowHeight, WHITE); + display.setTextColor(BLACK); + display.setTextSize(2); + display.setCursor(40, 220); + display.print(F("Refresh count: ")); + display.println(count); + if (count & 1UL) { + display.fillRect(576, 220, 32, 32, BLACK); + } else { + display.drawRect(576, 220, 32, 32, BLACK); + } +} + +void drawTestPage(uint32_t count) +{ + display.drawRect(0, 0, display.width(), display.height(), BLACK); + display.drawRect(8, 8, display.width() - 16, display.height() - 16, BLACK); + + display.setTextColor(BLACK); + display.setTextSize(4); + display.setCursor(40, 48); + display.println(F("RadioCore")); + + display.setTextSize(2); + display.setCursor(656, 56); + display.println(F("PARTIAL-WINDOW REFRESH")); + display.setCursor(40, 112); + display.print(F("Board: ")); + display.println(F(RADIOCORE_DEPG1020BNS770F1_BOARD_NAME)); + display.setCursor(40, 148); + display.println(F("DEPG1020BNS770F1 / SSD1677")); + display.setCursor(40, 184); + display.println(F("960 x 640 monochrome")); + drawUpdateRegion(count); + + constexpr int16_t blockTop = 300; + constexpr int16_t blockHeight = 180; + constexpr int16_t blockWidth = 180; + display.fillRect(60, blockTop, blockWidth, blockHeight, BLACK); + display.drawRect(260, blockTop, blockWidth, blockHeight, BLACK); + display.fillCircle(570, blockTop + blockHeight / 2, 90, BLACK); + display.drawCircle(790, blockTop + blockHeight / 2, 90, BLACK); + + display.drawLine(40, 540, display.width() - 40, 540, BLACK); + display.setCursor(40, 568); + display.println(F("Window refresh; 2 s rest; full after 10 partials")); +} + +} // namespace + +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println(); + Serial.print(F("RadioCore DEPG1020BNS770F1 example: ")); + Serial.println(F(RADIOCORE_DEPG1020BNS770F1_BOARD_NAME)); + + display.setFlip(Flip::VERTICAL); + + Serial.println(F("Startup self-test: full-screen black.")); + if (!showStartupFrame(BLACK)) { + Serial.println(F("E-paper BUSY timeout during the black startup frame.")); + Serial.println(F("Startup self-test aborted; the next cycle will try a normal full refresh.")); + delay(kRefreshRestMs); + return; + } + delay(kStartupFrameHoldMs); + + Serial.println(F("Startup self-test: full-screen white.")); + if (!showStartupFrame(WHITE)) { + Serial.println(F("E-paper BUSY timeout during the white startup frame.")); + Serial.println(F("Startup self-test aborted; the next cycle will try a normal full refresh.")); + delay(kRefreshRestMs); + return; + } + delay(kStartupFrameHoldMs); +} + +void loop() +{ + const bool fullRefresh = !hasBaseline || + partialRefreshCount >= kPartialRefreshesBeforeFull; + const uint32_t nextCount = refreshCount + 1; + Serial.print(fullRefresh ? F("Starting full refresh ") : F("Starting window refresh ")); + Serial.println(nextCount); + + if (fullRefresh) { + // Always restore the full window, including after a failed partial update. + display.fastmodeOff(); + display.fullscreen(); + } else { + // The first full image must succeed before selecting partial mode. + display.fastmodeOn(false); + display.setWindow(kWindowLeft, kWindowTop, kWindowWidth, kWindowHeight); + } + if (!display.timedOut()) { + DRAW(display) { + if (fullRefresh) { + drawTestPage(nextCount); + } else { + drawUpdateRegion(nextCount); + } + } + } + + if (display.timedOut()) { + hasBaseline = false; + partialRefreshCount = 0; + Serial.println(F("E-paper BUSY timeout; the driver board was powered down.")); + Serial.println(F("The next cycle will rebuild the image with a full refresh.")); + } else { + refreshCount = nextCount; + hasBaseline = true; + if (fullRefresh) { + partialRefreshCount = 0; + } else { + ++partialRefreshCount; + } + Serial.println(F("Refresh complete; driver-board power remains on. Resting 2 s.")); + } + + delay(kRefreshRestMs); +} + +#endif // RADIOCORE_HAS_DEPG1020BNS770F1_DISPLAY diff --git a/examples/DEPG1020BNS770F1_Display/README.md b/examples/DEPG1020BNS770F1_Display/README.md new file mode 100644 index 0000000..b39d3a0 --- /dev/null +++ b/examples/DEPG1020BNS770F1_Display/README.md @@ -0,0 +1,115 @@ +# DEPG1020BNS770F1 E-Paper Display + +## Purpose + +This example drives a DEPG1020BNS770F1 960-by-640 monochrome e-paper panel +through an RD02E SSD1677 driver board. It uses 32-row paged drawing, builds a +full-screen baseline, then updates a small window with the manufacturer's +partial-refresh waveform. At cold startup it first shows full-screen black and +white test frames. It rests for 2 seconds after each completed normal refresh +and performs a full-screen refresh after every 10 successful partial updates. + +## Requirements + +- RadioCore RC32 or RC52 board configuration +- A DEPG1020BNS770F1 driver board that includes the panel power and high-voltage + circuitry; do not connect the bare 24-pin panel directly to the MCU +- A `heltec-eink-modules` version with DEPG1020BNS770F1 partial-refresh support + +Install the updated `heltec-eink-modules` library manually in the Arduino +sketchbook `libraries` directory. The currently published Library Manager +version does not contain this panel driver. + +## RD02E wiring + +RD02E P1 mates pin-for-pin with RC32 P3 or RC52 P1. The table uses the updated +RD02E signal assignment and the RadioCore connector mappings; it is not the +RadioCore TFT SPI mapping. + +| Signal | RD02E P1 | Heltec RC32 P3 | Heltec RC52 P1 | +| --- | ---: | ---: | ---: | +| RST/RES | 10 | GPIO16 | P0.28 (28) | +| BUSY | 24 | GPIO38 | P1.02 (34) | +| DC | 8 | GPIO17 | P0.30 (30) | +| EN/VEINK_Ctrl | 26 | GPIO39 | P1.04 (36) | +| CS | 2 | GPIO6 | P1.13 (45) | +| SCK/SCL | 1 | GPIO4 | P0.10 (10) | +| MOSI/SDA | 3 | GPIO5 | P0.09 (9) | + +`VEINK_Ctrl` is active low: pulling it low turns on the RD02E P-channel power +switch. BUSY is active high, while RST and CS are active low. BS1 is tied to +ground on RD02E, fixing the panel in 4-wire, 8-bit SPI mode. There is no separate +MISO signal; SDA is only used as MOSI by this write-only driver. + +RC52 uses `SPI1`, explicitly remapped to SCK D10 and MOSI D9 by the display +constructor. Do not rely on the board variant's TFT SPI defaults. + +The example owns these connector pins while it runs. Do not simultaneously use +the conflicting soil-moisture ADC/power, WS2812, TFT, +or other connector functions. Check the PCB and RD02E silkscreen pin-1 marks +before applying power so the board-to-board connector is not inserted mirrored. + +## Behavior and limitations + +At cold startup, the sketch performs one full-screen black refresh and one +full-screen white refresh, holding each completed frame for 1 second. It then +draws the identification page and test geometry over the complete 960-by-640 +screen. The startup frames do not increment the displayed refresh counter and +are not repeated by periodic full refreshes or timeout recovery. If either +startup frame reaches the 60-second BUSY timeout, the driver board is powered +down, the remaining startup frame is skipped, and the next loop cycle attempts +the normal identification page as a full refresh. + +The normal page keeps `RadioCore` at the upper left and displays +`PARTIAL-WINDOW REFRESH` at the upper right so the top edge is visibly exercised. +Both labels are static full-refresh content. Subsequent partial updates select +the byte-aligned rectangle `(40, 216, 600, 56)`, clear that rectangle to white, +and redraw only the refresh counter and an alternating black/white test block. +The border, board name, header, and other geometry remain unchanged. Coordinates +are absolute screen coordinates, not relative to the window. + +The sketch selects `Flip::VERTICAL` before the startup test. With the verified +panel scan direction, this keeps text readable while rotating the physical +layout by 180 degrees, so the former upper-left content appears at the lower +right. The same mapping is applied to full-screen and partial-window updates. + +All startup and normal drawing uses a 3,840-byte page buffer +(`960 * 32 / 8`), not a 76,800-byte full framebuffer. Partial mode runs the +`DRAW` body twice through the shared library paging loop; the DEPG1020 driver +skips hardware writes on the second pass. The counter and block state stay +constant throughout both passes and advance only after a successful refresh. +Every 10 partial updates, the sketch explicitly restores `fullscreen()` and +refreshes the full image before continuing. + +Both full and partial refreshes now keep RD02E powered, with active-low +`VEINK_Ctrl` held low. Normal completion does not send deep sleep or turn power +off. This is a driver-wide behavior change, not an example-only option, and +increases standby power consumption. No public sleep/power-off API is added. +The driver waits at most 60 seconds for BUSY to return low. On timeout it +disables driver-board power and stops subsequent transfers. After the 2-second +rest, the next cycle powers on, resets, and rebuilds a full-screen baseline; +failed updates do not advance the displayed counter or the partial-update count. + +The port uses the supplied top-level SSD1677 demo's `WF_PARTIAL` (105 LUT bytes +plus five voltage parameters), `0x37` configuration, border `0x80`, and `0xCF` +activation. Full-refresh drawing writes the same paged image to both `0x24` +and `0x26`, restoring the page address counters before the second write to +initialize a consistent full-image reference. Partial windows still use +only `0x24`, with the second hardware pass skipped. The sketch does not detect +changed pixels automatically or use the nested SSD1685 project or OTP readback. +On 2026-10-08, the user confirmed normal display after adding the full-refresh +`0x26` write to resolve the reported window-external noise on the RC32 setup. +Temporary diagnostic logging and its API have been removed. +The existing Y-decreasing address mapping +is retained: the vendor window example has inconsistent Y direction and an +out-of-bounds demonstration rectangle, so its coordinates are not copied. + +Partial refresh remains experimental beyond this initial user-confirmed RC32 +test. RC52 hardware, long-term residual images, BUSY duration, and timeout +recovery still need validation; check window position, unchanged surrounding +content, and both black-to-white and white-to-black transitions on each setup. +Ten partial updates between full refreshes is an adjustable example policy, not +a manufacturer-guaranteed limit. Grayscale and Turbo mode are not implemented. +RD02E has no isolation between the MCU control signals and the powered-down +SSD1677 domain. This implementation does not place the SPI and control pins in +high impedance after disabling `VDD_EINK`; hardware backfeed remains unverified. diff --git a/examples/E0213A367_Display/E0213A367_Display.ino b/examples/E0213A367_Display/E0213A367_Display.ino new file mode 100644 index 0000000..99e67bf --- /dev/null +++ b/examples/E0213A367_Display/E0213A367_Display.ino @@ -0,0 +1,101 @@ +/* + * RadioCore E0213A367 e-paper example + * + * Uses the RD02E driver board and the E0213A367 display class from + * heltec-eink-modules. The example performs a full refresh, keeps the driver + * powered for 60 seconds, then repeats with a new refresh count. + */ +#include +#include + +#if !RADIOCORE_HAS_E0213A367_DISPLAY +#error "E0213A367_Display requires a supported RadioCore board configuration." +#else + +#include + +namespace { + +constexpr uint16_t kPageHeight = 32; +constexpr uint32_t kRefreshRestMs = 60000UL; + +E0213A367 display( + RADIOCORE_E0213A367_DC, + RADIOCORE_E0213A367_CS, + RADIOCORE_E0213A367_BUSY, + RADIOCORE_E0213A367_RST, + RADIOCORE_E0213A367_ENABLE, + RADIOCORE_E0213A367_ENABLE_ACTIVE, + RADIOCORE_E0213A367_MOSI, + RADIOCORE_E0213A367_SCK, + kPageHeight); + +uint32_t refreshCount = 0; + +void drawTestPage() +{ + display.drawRect(0, 0, display.width(), display.height(), BLACK); + display.drawRect(3, 3, display.width() - 6, display.height() - 6, BLACK); + + display.setTextColor(BLACK); + display.setTextSize(2); + display.setCursor(10, 14); + display.println(F("RadioCore")); + + display.setTextSize(1); + display.setCursor(10, 46); + display.println(F(RADIOCORE_E0213A367_BOARD_NAME)); + display.setCursor(10, 62); + display.println(F("E0213A367")); + display.setCursor(10, 78); + display.println(F("122 x 250 BW")); + display.setCursor(10, 94); + display.print(F("Refresh: ")); + display.println(refreshCount); + + display.fillRect(12, 122, 38, 38, BLACK); + display.drawRect(70, 122, 38, 38, BLACK); + display.fillCircle(31, 190, 19, BLACK); + display.drawCircle(89, 190, 19, BLACK); + + display.drawLine(10, 222, display.width() - 10, 222, BLACK); + display.setCursor(10, 232); + display.println(F("Full refresh")); +} + +} // namespace + +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println(); + Serial.print(F("RadioCore E0213A367 example: ")); + Serial.println(F(RADIOCORE_E0213A367_BOARD_NAME)); +} + +void loop() +{ + ++refreshCount; + Serial.print(F("Starting full refresh ")); + Serial.println(refreshCount); + + // Reasserts RD02E power, hard-resets the panel and loads full-refresh mode. + display.fastmodeOff(); + if (!display.timedOut()) { + DRAW(display) { + drawTestPage(); + } + } + + if (display.timedOut()) { + Serial.println(F("E-paper BUSY timeout; RD02E power is off.")); + } else { + Serial.println(F("Refresh complete; RD02E power remains on.")); + } + + delay(kRefreshRestMs); +} + +#endif // RADIOCORE_HAS_E0213A367_DISPLAY diff --git a/examples/E0213A367_Display/README.md b/examples/E0213A367_Display/README.md new file mode 100644 index 0000000..0eacd55 --- /dev/null +++ b/examples/E0213A367_Display/README.md @@ -0,0 +1,56 @@ +# E0213A367 E-Paper Display + +This example drives the monochrome E0213A367 panel through the RD02E e-paper +driver board on Heltec RC32 and RC52. It renders a changing test page using a +32-row page buffer, performs a full-screen refresh, then rests for 60 seconds. + +## Requirements + +- A Heltec RC32 or RC52. +- An RD02E e-paper driver board and compatible E0213A367 panel. +- A development version of `heltec-eink-modules` that provides the external + E0213A367 constructor and RC52 platform support. + +## Wiring + +RD02E P1 mates pin-for-pin with RC32 P3 or RC52 P1. Confirm the connector pin 1 +orientation against the PCB silkscreen before applying power. The table uses +the updated RD02E host signal assignment. + +| Signal | RD02E P1 | RC32 P3 | RC52 P1 | +| --- | ---: | ---: | ---: | +| RST/RES | 10 | GPIO16 | P0.28 / D28 | +| BUSY | 24 | GPIO38 | P1.02 / D34 | +| DC | 8 | GPIO17 | P0.30 / D30 | +| EN/VEINK_Ctrl | 26 | GPIO39 | P1.04 / D36 | +| CS | 2 | GPIO6 | P1.13 / D45 | +| SCK/SCL | 1 | GPIO4 | P0.10 / D10 | +| MOSI/SDA | 3 | GPIO5 | P0.09 / D9 | + +`VEINK_Ctrl` is active low, BUSY is active high, and RST and CS are active low. +BS1 is tied to ground on RD02E, selecting 4-wire, 8-bit SPI. The interface has +no independent MISO signal. RC52 uses `SPI1`, explicitly remapped to SCK D10 and +MOSI D9; CS remains the separate D45 GPIO. + +## Runtime behavior + +Each loop asserts RD02E power, waits 100 ms, hard-resets the panel, selects full +refresh mode and draws a new refresh count. The 128-pixel controller width and +32-row page height use a 512-byte image buffer; the visible drawing area is +122 by 250 pixels. After a successful refresh, RD02E remains powered during the +60-second rest period. + +BUSY waits time out after 60 seconds. A timeout switches `VEINK_Ctrl` inactive, +prints an error, rests for 60 seconds and retries the complete power/reset +sequence on the next loop. + +## Shared-pin restrictions + +Run this as a standalone example. Its GPIOs overlap +soil-moisture ADC or power, WS2812 and other display +functions. Do not operate those features at the same time. + +RD02E does not isolate MCU logic signals when its switched e-paper supply is +off. After a BUSY timeout, control signals may still create a reverse-current +path; this example does not stop SPI or place every control line in a high- +impedance state. diff --git a/examples/ICM42607P_Read/ICM42607P_Read.ino b/examples/ICM42607P_Read/ICM42607P_Read.ino new file mode 100644 index 0000000..925616e --- /dev/null +++ b/examples/ICM42607P_Read/ICM42607P_Read.ino @@ -0,0 +1,240 @@ +#include +#include +#include + +#if !RADIOCORE_HAS_ICM42607P +#error "ICM42607P_Read requires an onboard RadioCore ICM42607P configuration." +#elif !RADIOCORE_HAS_SENSOR_I2C +#error "ICM42607P_Read requires a RadioCore Sensor I2C board configuration." +#else + +#include + +namespace { + +constexpr uint8_t kIcm42607pAddresses[] = {0x68, 0x69}; +constexpr uint8_t kWhoAmIRegister = 0x75; +constexpr uint8_t kIcm42607pWhoAmI = 0x60; +constexpr uint16_t kAccelerometerFrequencyHz = 50; +constexpr uint16_t kAccelerometerRangeG = 2; +constexpr uint16_t kGyroscopeFrequencyHz = 50; +constexpr uint16_t kGyroscopeRangeDps = 2000; +constexpr float kSignedSensorCounts = 32768.0F; +constexpr uint32_t kPrintIntervalMs = 100; + +ICM42670 imuAtPrimaryAddress( + RADIOCORE_SENSOR_I2C_INSTANCE, + false, + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); +ICM42670 imuAtAlternateAddress( + RADIOCORE_SENSOR_I2C_INSTANCE, + true, + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); +ICM42670* imu = nullptr; +bool sensorReady = false; +uint8_t detectedAddress = 0; +uint32_t lastPrintTime = 0; + +void enableSensorHardware() +{ +#if RADIOCORE_HAS_SENSOR_POWER_CTRL + pinMode(RADIOCORE_SENSOR_POWER_CTRL_PIN, OUTPUT); + digitalWrite( + RADIOCORE_SENSOR_POWER_CTRL_PIN, + RADIOCORE_SENSOR_POWER_ON); +#endif + +#if RADIOCORE_HAS_SENSOR_RESET + pinMode(RADIOCORE_SENSOR_RESET_PIN, OUTPUT); + digitalWrite( + RADIOCORE_SENSOR_RESET_PIN, + RADIOCORE_SENSOR_RESET_RELEASE_LEVEL); +#endif + +#if RADIOCORE_HAS_SENSOR_POWER_CTRL + delay(RADIOCORE_SENSOR_POWER_WARMUP_MS); +#endif +} + +bool beginSensorI2C() +{ +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_WIFI_KIT_SERIES) + return RADIOCORE_SENSOR_I2C_INSTANCE.begin( + RADIOCORE_SENSOR_I2C_SDA, + RADIOCORE_SENSOR_I2C_SCL, + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); +#elif defined(ARDUINO_ARCH_HELTEC_NRF52) + RADIOCORE_SENSOR_I2C_INSTANCE.setPins( + RADIOCORE_SENSOR_I2C_SDA, + RADIOCORE_SENSOR_I2C_SCL); + RADIOCORE_SENSOR_I2C_INSTANCE.begin(); + RADIOCORE_SENSOR_I2C_INSTANCE.setClock( + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); + return true; +#else +#error "ICM42607P_Read supports only the configured ESP32 and nRF52 I2C API types." +#endif +} + +bool readRegister(uint8_t address, uint8_t reg, uint8_t& value) +{ + RADIOCORE_SENSOR_I2C_INSTANCE.beginTransmission(address); + RADIOCORE_SENSOR_I2C_INSTANCE.write(reg); + if (RADIOCORE_SENSOR_I2C_INSTANCE.endTransmission(false) != 0) { + return false; + } + + if (RADIOCORE_SENSOR_I2C_INSTANCE.requestFrom( + address, + static_cast(1)) != 1) { + return false; + } + + value = RADIOCORE_SENSOR_I2C_INSTANCE.read(); + return true; +} + +bool detectImu() +{ + for (size_t index = 0; + index < sizeof(kIcm42607pAddresses) / + sizeof(kIcm42607pAddresses[0]); + ++index) { + const uint8_t address = kIcm42607pAddresses[index]; + uint8_t whoAmI = 0; + if (!readRegister(address, kWhoAmIRegister, whoAmI) || + whoAmI != kIcm42607pWhoAmI) { + continue; + } + + detectedAddress = address; + imu = address == kIcm42607pAddresses[0] + ? &imuAtPrimaryAddress + : &imuAtAlternateAddress; + return true; + } + + return false; +} + +bool beginImu() +{ + if (!detectImu()) { + Serial.println( + F("Onboard ICM42607P was not found at address 0x68 or 0x69.")); + return false; + } + + const int beginStatus = imu->begin(); + // ICM42670P 1.0.8 returns -3 for the ICM42607P WHO_AM_I value. The + // register map remains compatible, and the identity was verified above. + if (beginStatus != 0 && beginStatus != -3) { + Serial.print(F("ICM42607P initialization failed with status ")); + Serial.println(beginStatus); + return false; + } + + const int accelStatus = imu->startAccel( + kAccelerometerFrequencyHz, + kAccelerometerRangeG); + if (accelStatus != 0) { + Serial.print(F("ICM42607P accelerometer start failed with status ")); + Serial.println(accelStatus); + return false; + } + + const int gyroStatus = imu->startGyro( + kGyroscopeFrequencyHz, + kGyroscopeRangeDps); + if (gyroStatus != 0) { + Serial.print(F("ICM42607P gyroscope start failed with status ")); + Serial.println(gyroStatus); + return false; + } + + return true; +} + +float accelerationToG(int16_t rawValue) +{ + return static_cast(rawValue) * kAccelerometerRangeG / + kSignedSensorCounts; +} + +float angularRateToDps(int16_t rawValue) +{ + return static_cast(rawValue) * kGyroscopeRangeDps / + kSignedSensorCounts; +} + +} // namespace + +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println(); + Serial.println(F("RadioCore onboard ICM42607P read example")); + + enableSensorHardware(); + + if (!beginSensorI2C()) { + Serial.println(F("Sensor I2C initialization failed.")); + return; + } + + sensorReady = beginImu(); + if (!sensorReady) { + return; + } + + Serial.print(F("ICM42607P initialized at address 0x")); + Serial.println(detectedAddress, HEX); +} + +void loop() +{ + if (!sensorReady) { + return; + } + + const uint32_t now = millis(); + if (now - lastPrintTime < kPrintIntervalMs) { + return; + } + lastPrintTime = now; + + inv_imu_sensor_event_t event = {}; + const int readStatus = imu->getDataFromRegisters(event); + if (readStatus != 0) { + Serial.print(F("ICM42607P read failed with status ")); + Serial.println(readStatus); + return; + } + + if (event.accel[0] == 0 && + event.accel[1] == 0 && + event.accel[2] == 0 && + event.gyro[0] == 0 && + event.gyro[1] == 0 && + event.gyro[2] == 0) { + return; + } + + Serial.print(F("Acceleration (g) X:")); + Serial.print(accelerationToG(event.accel[0]), 3); + Serial.print(F(" Y:")); + Serial.print(accelerationToG(event.accel[1]), 3); + Serial.print(F(" Z:")); + Serial.println(accelerationToG(event.accel[2]), 3); + + Serial.print(F("Angular rate (deg/s) X:")); + Serial.print(angularRateToDps(event.gyro[0]), 2); + Serial.print(F(" Y:")); + Serial.print(angularRateToDps(event.gyro[1]), 2); + Serial.print(F(" Z:")); + Serial.println(angularRateToDps(event.gyro[2]), 2); +} + +#endif // RADIOCORE_HAS_ICM42607P diff --git a/examples/ICM42607P_Read/README.md b/examples/ICM42607P_Read/README.md new file mode 100644 index 0000000..a6d08a6 --- /dev/null +++ b/examples/ICM42607P_Read/README.md @@ -0,0 +1,35 @@ +# ICM42607P Read + +## Purpose + +This example reads the onboard ICM42607P accelerometer and gyroscope on a RadioCore RC32 or RC52 and prints both three-axis measurements in physical units ten times per second. + +## Requirements + +- RadioCore RC32 or RC52 board configuration +- [TDK InvenSense ICM42670P](https://github.com/tdk-invn-oss/motion.arduino.ICM42670P) version 1.0.8 or newer + +The physical sensor is an ICM42607P. The ICM42670P library is a compatible driver dependency used by the reference firmware; it is not the detected sensor model. The dependency is declared in `library.properties` and can be installed automatically by dependency-aware Arduino tools. + +## Wiring and board differences + +The ICM42607P is integrated into the supported boards, so no external wiring is required. + +| Board | Sensor I2C SDA | Sensor I2C SCL | Sensor power control | Sensor reset | +| --- | ---: | ---: | --- | --- | +| Heltec RC32 | GPIO21 | GPIO18 | GPIO46, active high | GPIO2, high to release | +| Heltec RC52 | P1.11 (43) | P0.02 (2) | P0.12 (12), active high | P1.15 (47), high to release | + +Both boards wait 100 ms after enabling the Sensor power rail and releasing reset before starting I2C. The sketch detects the ICM42607P at `0x68` or `0x69` and verifies that register `0x75` contains its `0x60` identity value. + +## Behavior + +After identity verification, the sketch accepts driver initialization status `0` or `-3`. Version 1.0.8 of the ICM42670P driver returns `-3` because it expects the ICM42670P identity value, while the ICM42607P has a compatible register map and reports `0x60`. Other initialization errors stop the example. + +The accelerometer runs at 50 Hz with a +/-2 g full-scale range, and the gyroscope runs at 50 Hz with a +/-2000 degrees-per-second full-scale range. Every 100 ms, the sketch polls the latest event, converts signed 16-bit readings with `raw * full_scale / 32768`, and prints acceleration in g and angular rate in degrees per second at 115200 baud. + +## Limitations + +The values use the configured full-scale ranges but do not include offset calibration or temperature compensation. The example does not read temperature, calculate orientation, or enable wake-on-motion. + +The board Sensor interrupt is deliberately unused because RC32 and RC52 share that signal with the onboard rotary-encoder expander. The example intentionally supports only the onboard ICM42607P configurations declared by RC32 and RC52. diff --git a/examples/MMC5983MA_Read/MMC5983MA_Read.ino b/examples/MMC5983MA_Read/MMC5983MA_Read.ino new file mode 100644 index 0000000..2007c90 --- /dev/null +++ b/examples/MMC5983MA_Read/MMC5983MA_Read.ino @@ -0,0 +1,162 @@ +#include +#include +#include + +#if !RADIOCORE_HAS_MMC5983MA +#error "MMC5983MA_Read requires an onboard RadioCore MMC5983MA configuration." +#elif !RADIOCORE_HAS_SENSOR_I2C +#error "MMC5983MA_Read requires a RadioCore Sensor I2C board configuration." +#else + +#include + +namespace { + +constexpr float kZeroFieldCounts = 131072.0F; +constexpr float kCountsPerGauss = 16384.0F; +constexpr uint16_t kContinuousFrequencyHz = 50; +constexpr uint32_t kPrintIntervalMs = 100; + +SFE_MMC5983MA magnetometer; +bool sensorReady = false; +bool continuousMode = false; +uint32_t lastPrintTime = 0; + +void enableSensorHardware() +{ +#if RADIOCORE_HAS_SENSOR_POWER_CTRL + pinMode(RADIOCORE_SENSOR_POWER_CTRL_PIN, OUTPUT); + digitalWrite( + RADIOCORE_SENSOR_POWER_CTRL_PIN, + RADIOCORE_SENSOR_POWER_ON); +#endif + +#if RADIOCORE_HAS_SENSOR_RESET + pinMode(RADIOCORE_SENSOR_RESET_PIN, OUTPUT); + digitalWrite( + RADIOCORE_SENSOR_RESET_PIN, + RADIOCORE_SENSOR_RESET_RELEASE_LEVEL); +#endif + +#if RADIOCORE_HAS_SENSOR_POWER_CTRL + delay(RADIOCORE_SENSOR_POWER_WARMUP_MS); +#endif +} + +bool beginSensorI2C() +{ +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_WIFI_KIT_SERIES) + return RADIOCORE_SENSOR_I2C_INSTANCE.begin( + RADIOCORE_SENSOR_I2C_SDA, + RADIOCORE_SENSOR_I2C_SCL, + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); +#elif defined(ARDUINO_ARCH_HELTEC_NRF52) + RADIOCORE_SENSOR_I2C_INSTANCE.setPins( + RADIOCORE_SENSOR_I2C_SDA, + RADIOCORE_SENSOR_I2C_SCL); + RADIOCORE_SENSOR_I2C_INSTANCE.begin(); + RADIOCORE_SENSOR_I2C_INSTANCE.setClock( + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); + return true; +#else +#error "MMC5983MA_Read supports only the configured ESP32 and nRF52 I2C API types." +#endif +} + +bool beginMagnetometer() +{ + if (!magnetometer.begin(RADIOCORE_SENSOR_I2C_INSTANCE)) { + return false; + } + + magnetometer.softReset(); + magnetometer.setFilterBandwidth(100); + magnetometer.performSetOperation(); + magnetometer.enableAutomaticSetReset(); + + continuousMode = + magnetometer.setContinuousModeFrequency(kContinuousFrequencyHz); + continuousMode &= magnetometer.enableContinuousMode(); + if (!continuousMode) { + magnetometer.disableContinuousMode(); + Serial.println( + F("MMC5983MA continuous mode unavailable; using single-shot reads.")); + } + + return true; +} + +bool readMagneticField(float& xGauss, float& yGauss, float& zGauss) +{ + uint32_t rawX = 0; + uint32_t rawY = 0; + uint32_t rawZ = 0; + + const bool readSucceeded = continuousMode + ? magnetometer.readFieldsXYZ(&rawX, &rawY, &rawZ) + : magnetometer.getMeasurementXYZ(&rawX, &rawY, &rawZ); + if (!readSucceeded) { + return false; + } + + xGauss = (static_cast(rawX) - kZeroFieldCounts) / kCountsPerGauss; + yGauss = (static_cast(rawY) - kZeroFieldCounts) / kCountsPerGauss; + zGauss = (static_cast(rawZ) - kZeroFieldCounts) / kCountsPerGauss; + return true; +} + +} // namespace + +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println(); + Serial.println(F("RadioCore onboard MMC5983MA read example")); + + enableSensorHardware(); + + if (!beginSensorI2C()) { + Serial.println(F("Sensor I2C initialization failed.")); + return; + } + + sensorReady = beginMagnetometer(); + if (!sensorReady) { + Serial.println(F("Onboard MMC5983MA was not found at address 0x30.")); + return; + } + + Serial.println(F("MMC5983MA initialized at address 0x30.")); +} + +void loop() +{ + if (!sensorReady) { + return; + } + + const uint32_t now = millis(); + if (now - lastPrintTime < kPrintIntervalMs) { + return; + } + lastPrintTime = now; + + float xGauss = 0.0F; + float yGauss = 0.0F; + float zGauss = 0.0F; + if (!readMagneticField(xGauss, yGauss, zGauss)) { + Serial.println(F("MMC5983MA read failed.")); + return; + } + + Serial.print(F("Magnetic field (G) X:")); + Serial.print(xGauss, 4); + Serial.print(F(" Y:")); + Serial.print(yGauss, 4); + Serial.print(F(" Z:")); + Serial.println(zGauss, 4); +} + +#endif // RADIOCORE_HAS_MMC5983MA diff --git a/examples/MMC5983MA_Read/README.md b/examples/MMC5983MA_Read/README.md new file mode 100644 index 0000000..0637076 --- /dev/null +++ b/examples/MMC5983MA_Read/README.md @@ -0,0 +1,35 @@ +# MMC5983MA Read + +## Purpose + +This example reads the onboard MMC5983MA three-axis magnetometer on a RadioCore RC32 or RC52 and prints the magnetic field in gauss ten times per second. + +## Requirements + +- RadioCore RC32 or RC52 board configuration +- [SparkFun MMC5983MA Magnetometer Arduino Library](https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library) version 1.1.5 or newer + +The dependency is declared in `library.properties` and can be installed automatically by dependency-aware Arduino tools. + +## Wiring and board differences + +The MMC5983MA is integrated into the supported boards, so no external wiring is required. + +| Board | Sensor I2C SDA | Sensor I2C SCL | Sensor power control | Sensor reset | +| --- | ---: | ---: | --- | --- | +| Heltec RC32 | GPIO21 | GPIO18 | GPIO46, active high | GPIO2, high to release | +| Heltec RC52 | P1.11 (43) | P0.02 (2) | P0.12 (12), active high | P1.15 (47), high to release | + +Both boards wait 100 ms after enabling the Sensor power rail and releasing reset before starting I2C. The MMC5983MA uses address `0x30`. + +## Behavior + +The sketch configures the MMC5983MA with a 100 Hz filter bandwidth, performs a SET operation, enables automatic SET/RESET, and requests 50 Hz continuous measurements. If continuous mode cannot be enabled, it reports the condition and uses single-shot measurements instead. + +Every 100 ms, the sketch converts each 18-bit unsigned reading to gauss using `(raw - 131072) / 16384` and prints the X, Y, and Z fields at 115200 baud. Initialization or read failures are reported over Serial. + +## Limitations + +The output is an uncalibrated magnetic-field vector. The example does not calculate a compass heading, remove hard-iron bias, correct soft-iron scaling, or compensate for board tilt. + +The example intentionally supports only the onboard MMC5983MA configurations declared by RC32 and RC52. A board that merely provides a Sensor I2C bus is not considered compatible. diff --git a/examples/Rotary_Encoder/Rotary_Encoder.ino b/examples/Rotary_Encoder/Rotary_Encoder.ino new file mode 100644 index 0000000..501b5a6 --- /dev/null +++ b/examples/Rotary_Encoder/Rotary_Encoder.ino @@ -0,0 +1,317 @@ +/* + * Reads the onboard rotary encoder on Heltec RC32 and RC52 through the + * TCA6408 I2C expander. RC32 uses Sensor I2C on GPIO21/GPIO18 with shared + * peripheral power on GPIO46; RC52 uses P1.11/P0.02 with shared peripheral + * power on P0.12. Turning the encoder prints Previous/Next and a signed count. + * + * The board-designated user button is reported independently from rotation: + * GPIO0 on RC32 and P1.10 on RC52. It is not treated as an encoder push event. + * Avoid holding RC32 GPIO0 low while resetting or uploading because it is also + * a boot-strapping pin. + */ +#include +#include +#include + +#if !RADIOCORE_HAS_ROTARY_ENCODER +#error "Rotary_Encoder requires a RadioCore rotary encoder board configuration." +#elif !RADIOCORE_HAS_SENSOR_I2C +#error "Rotary_Encoder requires a RadioCore Sensor I2C board configuration." +#else + +namespace { + +constexpr uint8_t kInputRegister = 0x00; +constexpr uint8_t kPolarityRegister = 0x02; +constexpr uint8_t kConfigRegister = 0x03; +constexpr uint8_t kRotaryMask = + RADIOCORE_ROTARY_ENCODER_PHASE_A_MASK | + RADIOCORE_ROTARY_ENCODER_PHASE_B_MASK; +constexpr uint32_t kRotaryEventIntervalMs = 5; +constexpr uint32_t kEncoderRetryIntervalMs = 1000; +constexpr uint32_t kButtonDebounceMs = 20; + +enum class RotaryEvent : uint8_t { + None, + Previous, + Next, +}; + +bool encoderReady = false; +bool encoderWasReady = false; +bool activeLowPhase = false; +uint8_t inputState = kRotaryMask; +uint32_t lastRotaryEventTime = 0; +uint32_t nextEncoderRetryTime = 0; +int32_t rotaryCount = 0; + +#if RADIOCORE_HAS_USER_BUTTON +uint8_t buttonRawState = HIGH; +uint8_t buttonStableState = HIGH; +uint32_t buttonChangedAt = 0; +#endif + +bool timeReached(uint32_t now, uint32_t deadline) +{ + return static_cast(now - deadline) >= 0; +} + +void enableSharedPower() +{ +#if RADIOCORE_HAS_SENSOR_POWER_CTRL + pinMode(RADIOCORE_SENSOR_POWER_CTRL_PIN, OUTPUT); + digitalWrite( + RADIOCORE_SENSOR_POWER_CTRL_PIN, + RADIOCORE_SENSOR_POWER_ON); + delay(RADIOCORE_SENSOR_POWER_WARMUP_MS); +#endif +} + +bool beginSensorI2C() +{ +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_WIFI_KIT_SERIES) + return RADIOCORE_SENSOR_I2C_INSTANCE.begin( + RADIOCORE_SENSOR_I2C_SDA, + RADIOCORE_SENSOR_I2C_SCL, + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); +#elif defined(ARDUINO_ARCH_HELTEC_NRF52) + RADIOCORE_SENSOR_I2C_INSTANCE.setPins( + RADIOCORE_SENSOR_I2C_SDA, + RADIOCORE_SENSOR_I2C_SCL); + RADIOCORE_SENSOR_I2C_INSTANCE.begin(); + RADIOCORE_SENSOR_I2C_INSTANCE.setClock( + RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY); + return true; +#else +#error "Rotary_Encoder supports only the configured ESP32 and nRF52 I2C API types." +#endif +} + +bool writeExpanderRegister(uint8_t reg, uint8_t value) +{ + RADIOCORE_SENSOR_I2C_INSTANCE.beginTransmission( + RADIOCORE_ROTARY_ENCODER_I2C_ADDRESS); + RADIOCORE_SENSOR_I2C_INSTANCE.write(reg); + RADIOCORE_SENSOR_I2C_INSTANCE.write(value); + return RADIOCORE_SENSOR_I2C_INSTANCE.endTransmission() == 0; +} + +bool readExpanderRegister(uint8_t reg, uint8_t& value) +{ + RADIOCORE_SENSOR_I2C_INSTANCE.beginTransmission( + RADIOCORE_ROTARY_ENCODER_I2C_ADDRESS); + RADIOCORE_SENSOR_I2C_INSTANCE.write(reg); + if (RADIOCORE_SENSOR_I2C_INSTANCE.endTransmission(false) != 0) { + return false; + } + + if (RADIOCORE_SENSOR_I2C_INSTANCE.requestFrom( + RADIOCORE_ROTARY_ENCODER_I2C_ADDRESS, + static_cast(1)) != 1) { + return false; + } + + value = RADIOCORE_SENSOR_I2C_INSTANCE.read(); + return true; +} + +bool updateExpanderBits(uint8_t reg, uint8_t setMask, uint8_t clearMask) +{ + uint8_t currentValue = 0; + if (!readExpanderRegister(reg, currentValue)) { + return false; + } + + const uint8_t newValue = (currentValue | setMask) & ~clearMask; + return newValue == currentValue || writeExpanderRegister(reg, newValue); +} + +bool beginEncoder() +{ + encoderReady = false; + activeLowPhase = false; + inputState = kRotaryMask; + + if (!updateExpanderBits(kPolarityRegister, 0, kRotaryMask) || + !updateExpanderBits(kConfigRegister, kRotaryMask, 0)) { + return false; + } + + uint8_t state = 0; + if (!readExpanderRegister(kInputRegister, state)) { + return false; + } + + inputState = state & kRotaryMask; + encoderReady = true; + return true; +} + +bool tryBeginEncoder(uint32_t now) +{ + if (!beginSensorI2C() || !beginEncoder()) { + encoderReady = false; + nextEncoderRetryTime = now + kEncoderRetryIntervalMs; + Serial.println(F("Rotary encoder unavailable; retrying in 1 s.")); + return false; + } + + Serial.println( + encoderWasReady ? F("Rotary encoder recovered.") + : F("Rotary encoder ready.")); + encoderWasReady = true; + return true; +} + +RotaryEvent handleTransition(uint8_t newState, uint32_t now) +{ + const uint8_t changed = (inputState ^ newState) & kRotaryMask; + const bool aLow = + (newState & RADIOCORE_ROTARY_ENCODER_PHASE_A_MASK) == 0; + const bool bLow = + (newState & RADIOCORE_ROTARY_ENCODER_PHASE_B_MASK) == 0; + RotaryEvent event = RotaryEvent::None; + + if (!aLow && !bLow) { + activeLowPhase = false; + } + + if (!activeLowPhase && + (changed & RADIOCORE_ROTARY_ENCODER_PHASE_A_MASK) && + aLow && !bLow) { + event = RotaryEvent::Previous; + activeLowPhase = true; + } else if (!activeLowPhase && + (changed & RADIOCORE_ROTARY_ENCODER_PHASE_B_MASK) && + bLow && !aLow) { + event = RotaryEvent::Next; + activeLowPhase = true; + } + + if (event == RotaryEvent::None && !activeLowPhase && + (changed & RADIOCORE_ROTARY_ENCODER_PHASE_A_MASK) && + !aLow && bLow) { + event = RotaryEvent::Previous; + } + + if (event == RotaryEvent::None && !activeLowPhase && + (changed & RADIOCORE_ROTARY_ENCODER_PHASE_B_MASK) && + !bLow && aLow) { + event = RotaryEvent::Next; + } + + if (event == RotaryEvent::None || + now - lastRotaryEventTime < kRotaryEventIntervalMs) { + return RotaryEvent::None; + } + + lastRotaryEventTime = now; + return event; +} + +RotaryEvent pollEncoder(uint32_t now, bool& readSucceeded) +{ + uint8_t newState = 0; + readSucceeded = readExpanderRegister(kInputRegister, newState); + if (!readSucceeded) { + return RotaryEvent::None; + } + + newState &= kRotaryMask; + const RotaryEvent event = handleTransition(newState, now); + inputState = newState; + return event; +} + +void printRotaryEvent(RotaryEvent event) +{ + if (event == RotaryEvent::Previous) { + --rotaryCount; + Serial.print(F("Previous, count: ")); + } else if (event == RotaryEvent::Next) { + ++rotaryCount; + Serial.print(F("Next, count: ")); + } else { + return; + } + + Serial.println(rotaryCount); +} + +#if RADIOCORE_HAS_USER_BUTTON +void beginUserButton(uint32_t now) +{ + pinMode( + RADIOCORE_USER_BUTTON_PIN, + RADIOCORE_USER_BUTTON_PIN_MODE); + buttonRawState = digitalRead(RADIOCORE_USER_BUTTON_PIN); + buttonStableState = buttonRawState; + buttonChangedAt = now; +} + +void pollUserButton(uint32_t now) +{ + const uint8_t state = digitalRead(RADIOCORE_USER_BUTTON_PIN); + if (state != buttonRawState) { + buttonRawState = state; + buttonChangedAt = now; + } + + if (buttonRawState == buttonStableState || + now - buttonChangedAt < kButtonDebounceMs) { + return; + } + + buttonStableState = buttonRawState; + if (buttonStableState == RADIOCORE_USER_BUTTON_ACTIVE_LEVEL) { + Serial.println(F("User button pressed")); + } +} +#endif + +} // namespace + +void setup() +{ + Serial.begin(115200); + delay(100); + + Serial.println(); + Serial.println(F("RadioCore rotary encoder example")); + + const uint32_t now = millis(); +#if RADIOCORE_HAS_USER_BUTTON + beginUserButton(now); +#endif + enableSharedPower(); + tryBeginEncoder(millis()); +} + +void loop() +{ + const uint32_t now = millis(); + +#if RADIOCORE_HAS_USER_BUTTON + pollUserButton(now); +#endif + + if (!encoderReady) { + if (timeReached(now, nextEncoderRetryTime)) { + tryBeginEncoder(now); + } + return; + } + + bool readSucceeded = false; + const RotaryEvent event = pollEncoder(now, readSucceeded); + if (!readSucceeded) { + encoderReady = false; + nextEncoderRetryTime = now + kEncoderRetryIntervalMs; + Serial.println(F("Rotary encoder read failed; retrying in 1 s.")); + return; + } + + printRotaryEvent(event); +} + +#endif // RADIOCORE_HAS_ROTARY_ENCODER diff --git a/library.properties b/library.properties index 411457a..4d19958 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Device Control url=https://github.com/HelTecAutomation/RadioCore_Library architectures=* includes=RadioCore_Kit.h -depends=Adafruit BME280 Library, BH1750 (>=1.3.0), Adafruit NeoPixel, ESP32Servo, NonBlockingRTTTL (>=1.4.0) +depends=Adafruit BME280 Library, BH1750 (>=1.3.0), SparkFun MMC5983MA Magnetometer Arduino Library (>=1.1.5), ICM42670P (>=1.0.8), Adafruit NeoPixel, ESP32Servo, NonBlockingRTTTL (>=1.4.0) diff --git a/src/RadioCoreBoardConfig.h b/src/RadioCoreBoardConfig.h index 35300f7..14df15f 100644 --- a/src/RadioCoreBoardConfig.h +++ b/src/RadioCoreBoardConfig.h @@ -9,7 +9,10 @@ #include "boards/heltec_rcc6.h" #else #define RADIOCORE_HAS_SENSOR_I2C 0 +#define RADIOCORE_HAS_MMC5983MA 0 +#define RADIOCORE_HAS_ICM42607P 0 #define RADIOCORE_HAS_SENSOR_POWER_CTRL 0 +#define RADIOCORE_HAS_SENSOR_RESET 0 #define RADIOCORE_HAS_CH01_ADC 0 #define RADIOCORE_HAS_CH01_POWER_CTRL 0 #define RADIOCORE_HAS_VO01_ADC 0 @@ -22,11 +25,16 @@ #define RADIOCORE_NV3001B_USE_ESP32_SPI 0 #define RADIOCORE_NV3001B_USE_HARDWARE_SPI 0 #define RADIOCORE_NV3001B_USE_SOFTWARE_SPI 0 +#define RADIOCORE_HAS_DEPG1020BNS770F1_DISPLAY 0 +#define RADIOCORE_HAS_E0213A367_DISPLAY 0 +#define RADIOCORE_HAS_DEPG0290BNS800_DISPLAY 0 #define RADIOCORE_HAS_WS2812 0 #define RADIOCORE_HAS_BUZZER 0 #define RADIOCORE_HAS_MOTOR_CONTROL 0 #define RADIOCORE_HAS_RELAY_CONTROL 0 #define RADIOCORE_HAS_SERVO_CONTROL 0 +#define RADIOCORE_HAS_ROTARY_ENCODER 0 +#define RADIOCORE_HAS_USER_BUTTON 0 #endif #endif // RADIOCORE_BOARD_CONFIG_H diff --git a/src/boards/heltec_rc32.h b/src/boards/heltec_rc32.h index 5f01046..705cea7 100644 --- a/src/boards/heltec_rc32.h +++ b/src/boards/heltec_rc32.h @@ -11,11 +11,28 @@ #define RADIOCORE_SENSOR_I2C_SCL 18 #define RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY 100000UL +#define RADIOCORE_HAS_MMC5983MA 1 +#define RADIOCORE_HAS_ICM42607P 1 + #define RADIOCORE_HAS_SENSOR_POWER_CTRL 1 #define RADIOCORE_SENSOR_POWER_CTRL_PIN 46 #define RADIOCORE_SENSOR_POWER_ON HIGH #define RADIOCORE_SENSOR_POWER_WARMUP_MS 100UL +#define RADIOCORE_HAS_SENSOR_RESET 1 +#define RADIOCORE_SENSOR_RESET_PIN 2 +#define RADIOCORE_SENSOR_RESET_RELEASE_LEVEL HIGH + +#define RADIOCORE_HAS_ROTARY_ENCODER 1 +#define RADIOCORE_ROTARY_ENCODER_I2C_ADDRESS 0x20 +#define RADIOCORE_ROTARY_ENCODER_PHASE_A_MASK 0x01 +#define RADIOCORE_ROTARY_ENCODER_PHASE_B_MASK 0x02 + +#define RADIOCORE_HAS_USER_BUTTON 1 +#define RADIOCORE_USER_BUTTON_PIN 0 +#define RADIOCORE_USER_BUTTON_ACTIVE_LEVEL LOW +#define RADIOCORE_USER_BUTTON_PIN_MODE INPUT_PULLUP + #define RADIOCORE_HAS_CH01_ADC 1 #define RADIOCORE_CH01_ADC_PIN 2 #define RADIOCORE_HAS_CH01_POWER_CTRL 1 @@ -63,6 +80,45 @@ #define RADIOCORE_NV3001B_BACKLIGHT_ACTIVE HIGH #define RADIOCORE_NV3001B_SPI_FREQUENCY 8000000UL +// Updated RD02E P1 host mapping, pin-for-pin with RC32 P3. +#define RADIOCORE_HAS_DEPG1020BNS770F1_DISPLAY 1 +#define RADIOCORE_DEPG1020BNS770F1_BOARD_NAME "Heltec RC32" +#define RADIOCORE_DEPG1020BNS770F1_SCK 4 // RD02E P1 pin 1 +#define RADIOCORE_DEPG1020BNS770F1_MOSI 5 // RD02E P1 pin 3 +#define RADIOCORE_DEPG1020BNS770F1_CS 6 // RD02E P1 pin 2 +#define RADIOCORE_DEPG1020BNS770F1_DC 17 // RD02E P1 pin 8 +#define RADIOCORE_DEPG1020BNS770F1_RST 16 // RD02E P1 pin 10 +#define RADIOCORE_DEPG1020BNS770F1_ENABLE 39 // RD02E P1 pin 26 +#define RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE LOW +#define RADIOCORE_DEPG1020BNS770F1_BUSY 38 // RD02E P1 pin 24 +#define RADIOCORE_DEPG1020BNS770F1_BUSY_ACTIVE HIGH + +// E0213A367 uses the same RD02E host interface as DEPG1020BNS770F1. +#define RADIOCORE_HAS_E0213A367_DISPLAY 1 +#define RADIOCORE_E0213A367_BOARD_NAME RADIOCORE_DEPG1020BNS770F1_BOARD_NAME +#define RADIOCORE_E0213A367_SCK RADIOCORE_DEPG1020BNS770F1_SCK +#define RADIOCORE_E0213A367_MOSI RADIOCORE_DEPG1020BNS770F1_MOSI +#define RADIOCORE_E0213A367_CS RADIOCORE_DEPG1020BNS770F1_CS +#define RADIOCORE_E0213A367_DC RADIOCORE_DEPG1020BNS770F1_DC +#define RADIOCORE_E0213A367_RST RADIOCORE_DEPG1020BNS770F1_RST +#define RADIOCORE_E0213A367_ENABLE RADIOCORE_DEPG1020BNS770F1_ENABLE +#define RADIOCORE_E0213A367_ENABLE_ACTIVE RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE +#define RADIOCORE_E0213A367_BUSY RADIOCORE_DEPG1020BNS770F1_BUSY +#define RADIOCORE_E0213A367_BUSY_ACTIVE RADIOCORE_DEPG1020BNS770F1_BUSY_ACTIVE + +// DEPG0290BNS800 uses the same RD02E host interface as DEPG1020BNS770F1. +#define RADIOCORE_HAS_DEPG0290BNS800_DISPLAY 1 +#define RADIOCORE_DEPG0290BNS800_BOARD_NAME RADIOCORE_DEPG1020BNS770F1_BOARD_NAME +#define RADIOCORE_DEPG0290BNS800_SCK RADIOCORE_DEPG1020BNS770F1_SCK +#define RADIOCORE_DEPG0290BNS800_MOSI RADIOCORE_DEPG1020BNS770F1_MOSI +#define RADIOCORE_DEPG0290BNS800_CS RADIOCORE_DEPG1020BNS770F1_CS +#define RADIOCORE_DEPG0290BNS800_DC RADIOCORE_DEPG1020BNS770F1_DC +#define RADIOCORE_DEPG0290BNS800_RST RADIOCORE_DEPG1020BNS770F1_RST +#define RADIOCORE_DEPG0290BNS800_ENABLE RADIOCORE_DEPG1020BNS770F1_ENABLE +#define RADIOCORE_DEPG0290BNS800_ENABLE_ACTIVE RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE +#define RADIOCORE_DEPG0290BNS800_BUSY RADIOCORE_DEPG1020BNS770F1_BUSY +#define RADIOCORE_DEPG0290BNS800_BUSY_ACTIVE RADIOCORE_DEPG1020BNS770F1_BUSY_ACTIVE + #define RADIOCORE_HAS_WS2812 1 #define RADIOCORE_WS2812_DATA_PIN 5 diff --git a/src/boards/heltec_rc52.h b/src/boards/heltec_rc52.h index d4a5b55..6d28ec6 100644 --- a/src/boards/heltec_rc52.h +++ b/src/boards/heltec_rc52.h @@ -11,11 +11,28 @@ #define RADIOCORE_SENSOR_I2C_SCL (0 + 2) // P0.02 #define RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY 100000UL +#define RADIOCORE_HAS_MMC5983MA 1 +#define RADIOCORE_HAS_ICM42607P 1 + #define RADIOCORE_HAS_SENSOR_POWER_CTRL 1 #define RADIOCORE_SENSOR_POWER_CTRL_PIN (0 + 12) #define RADIOCORE_SENSOR_POWER_ON HIGH #define RADIOCORE_SENSOR_POWER_WARMUP_MS 100UL +#define RADIOCORE_HAS_SENSOR_RESET 1 +#define RADIOCORE_SENSOR_RESET_PIN (32 + 15) // P1.15 +#define RADIOCORE_SENSOR_RESET_RELEASE_LEVEL HIGH + +#define RADIOCORE_HAS_ROTARY_ENCODER 1 +#define RADIOCORE_ROTARY_ENCODER_I2C_ADDRESS 0x20 +#define RADIOCORE_ROTARY_ENCODER_PHASE_A_MASK 0x01 +#define RADIOCORE_ROTARY_ENCODER_PHASE_B_MASK 0x02 + +#define RADIOCORE_HAS_USER_BUTTON 1 +#define RADIOCORE_USER_BUTTON_PIN (32 + 10) // P1.10 +#define RADIOCORE_USER_BUTTON_ACTIVE_LEVEL LOW +#define RADIOCORE_USER_BUTTON_PIN_MODE INPUT_PULLUP + #define RADIOCORE_HAS_CH01_ADC 1 #define RADIOCORE_CH01_ADC_PIN 2 #define RADIOCORE_HAS_CH01_POWER_CTRL 0 @@ -54,6 +71,45 @@ #define RADIOCORE_NV3001B_BACKLIGHT_ACTIVE HIGH #define RADIOCORE_NV3001B_SPI_FREQUENCY 8000000UL +// Updated RD02E P1 host mapping, pin-for-pin with RC52 P1. +#define RADIOCORE_HAS_DEPG1020BNS770F1_DISPLAY 1 +#define RADIOCORE_DEPG1020BNS770F1_BOARD_NAME "Heltec RC52" +#define RADIOCORE_DEPG1020BNS770F1_SCK (0 + 10) // P0.10, RD02E P1 pin 1 +#define RADIOCORE_DEPG1020BNS770F1_MOSI (0 + 9) // P0.09, RD02E P1 pin 3 +#define RADIOCORE_DEPG1020BNS770F1_CS (32 + 13) // P1.13, RD02E P1 pin 2 +#define RADIOCORE_DEPG1020BNS770F1_DC (0 + 30) // P0.30, RD02E P1 pin 8 +#define RADIOCORE_DEPG1020BNS770F1_RST (0 + 28) // P0.28, RD02E P1 pin 10 +#define RADIOCORE_DEPG1020BNS770F1_ENABLE (32 + 4) // P1.04, RD02E P1 pin 26 +#define RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE LOW +#define RADIOCORE_DEPG1020BNS770F1_BUSY (32 + 2) // P1.02, RD02E P1 pin 24 +#define RADIOCORE_DEPG1020BNS770F1_BUSY_ACTIVE HIGH + +// E0213A367 uses the same RD02E host interface as DEPG1020BNS770F1. +#define RADIOCORE_HAS_E0213A367_DISPLAY 1 +#define RADIOCORE_E0213A367_BOARD_NAME RADIOCORE_DEPG1020BNS770F1_BOARD_NAME +#define RADIOCORE_E0213A367_SCK RADIOCORE_DEPG1020BNS770F1_SCK +#define RADIOCORE_E0213A367_MOSI RADIOCORE_DEPG1020BNS770F1_MOSI +#define RADIOCORE_E0213A367_CS RADIOCORE_DEPG1020BNS770F1_CS +#define RADIOCORE_E0213A367_DC RADIOCORE_DEPG1020BNS770F1_DC +#define RADIOCORE_E0213A367_RST RADIOCORE_DEPG1020BNS770F1_RST +#define RADIOCORE_E0213A367_ENABLE RADIOCORE_DEPG1020BNS770F1_ENABLE +#define RADIOCORE_E0213A367_ENABLE_ACTIVE RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE +#define RADIOCORE_E0213A367_BUSY RADIOCORE_DEPG1020BNS770F1_BUSY +#define RADIOCORE_E0213A367_BUSY_ACTIVE RADIOCORE_DEPG1020BNS770F1_BUSY_ACTIVE + +// DEPG0290BNS800 uses the same RD02E host interface as DEPG1020BNS770F1. +#define RADIOCORE_HAS_DEPG0290BNS800_DISPLAY 1 +#define RADIOCORE_DEPG0290BNS800_BOARD_NAME RADIOCORE_DEPG1020BNS770F1_BOARD_NAME +#define RADIOCORE_DEPG0290BNS800_SCK RADIOCORE_DEPG1020BNS770F1_SCK +#define RADIOCORE_DEPG0290BNS800_MOSI RADIOCORE_DEPG1020BNS770F1_MOSI +#define RADIOCORE_DEPG0290BNS800_CS RADIOCORE_DEPG1020BNS770F1_CS +#define RADIOCORE_DEPG0290BNS800_DC RADIOCORE_DEPG1020BNS770F1_DC +#define RADIOCORE_DEPG0290BNS800_RST RADIOCORE_DEPG1020BNS770F1_RST +#define RADIOCORE_DEPG0290BNS800_ENABLE RADIOCORE_DEPG1020BNS770F1_ENABLE +#define RADIOCORE_DEPG0290BNS800_ENABLE_ACTIVE RADIOCORE_DEPG1020BNS770F1_ENABLE_ACTIVE +#define RADIOCORE_DEPG0290BNS800_BUSY RADIOCORE_DEPG1020BNS770F1_BUSY +#define RADIOCORE_DEPG0290BNS800_BUSY_ACTIVE RADIOCORE_DEPG1020BNS770F1_BUSY_ACTIVE + #define RADIOCORE_HAS_WS2812 1 #define RADIOCORE_WS2812_DATA_PIN (0 + 9) // P0.09 diff --git a/src/boards/heltec_rcc6.h b/src/boards/heltec_rcc6.h index 401ead5..0bd9d13 100644 --- a/src/boards/heltec_rcc6.h +++ b/src/boards/heltec_rcc6.h @@ -10,7 +10,14 @@ #define RADIOCORE_SENSOR_I2C_SCL SCL #define RADIOCORE_SENSOR_I2C_DEFAULT_FREQUENCY 100000UL +#define RADIOCORE_HAS_MMC5983MA 0 +#define RADIOCORE_HAS_ICM42607P 0 + #define RADIOCORE_HAS_SENSOR_POWER_CTRL 0 +#define RADIOCORE_HAS_SENSOR_RESET 0 + +#define RADIOCORE_HAS_ROTARY_ENCODER 0 +#define RADIOCORE_HAS_USER_BUTTON 0 #define RADIOCORE_HAS_CH01_ADC 0 #define RADIOCORE_HAS_CH01_POWER_CTRL 0