mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 23:38:18 +00:00
Merge pull request #20 from Calvario/t114
Add T114 variants (screen and screenless)
This commit is contained in:
@@ -222,7 +222,7 @@ set(ZEPHCORE_COMMON_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/zephcore_com
|
||||
# Detect platform from board name and add platform-specific common config
|
||||
# For boards with qualifiers like "wio_tracker_l1/nrf52840", check both the full BOARD
|
||||
# string and the BOARD_QUALIFIERS which contains just the qualifier (e.g., "nrf52840")
|
||||
if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo")
|
||||
if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "heltec_t114")
|
||||
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf")
|
||||
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf52.*")
|
||||
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf")
|
||||
@@ -540,6 +540,12 @@ if(CONFIG_ZEPHCORE_UI_DISPLAY)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ZEPHCORE_DISPLAY_MONO_TFT)
|
||||
target_sources(app PRIVATE
|
||||
helpers/ui/display_mono_tft.c
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ZEPHCORE_UI_BUTTONS OR CONFIG_ZEPHCORE_UI_BUZZER OR CONFIG_ZEPHCORE_UI_DISPLAY)
|
||||
target_sources(app PRIVATE
|
||||
helpers/ui/ui_task.c
|
||||
|
||||
@@ -583,6 +583,17 @@ config ZEPHCORE_DISPLAY_LARGE_FONT
|
||||
no per-page changes are needed. Requires Zephyr's default CFB
|
||||
fonts to be compiled in (CONFIG_CHARACTER_FRAMEBUFFER_USE_DEFAULT_FONTS).
|
||||
|
||||
config ZEPHCORE_DISPLAY_MONO_TFT
|
||||
bool "Monochrome TFT wrapper (MONO01 => RGB565)"
|
||||
default y if DT_HAS_ZEPHCORE_MONO_TFT_ENABLED
|
||||
depends on DT_HAS_ZEPHCORE_MONO_TFT_ENABLED
|
||||
help
|
||||
Wraps an RGB TFT (e.g. ST7789V) as a PIXEL_FORMAT_MONO01 display.
|
||||
CFB allocates x*y/8 bytes (~4 KB for 240×135) instead of ~64 KB RGB565,
|
||||
making a colour TFT viable on RAM-constrained devices like the nRF52840.
|
||||
On display_write(), converts 1bpp row-major MONO01 to RGB565 row by row.
|
||||
Auto-enabled when a "zephcore,mono-tft" device exists in the devicetree.
|
||||
|
||||
config ZEPHCORE_EASTER_EGG_DOOM
|
||||
bool "Enable Doom raycaster easter egg"
|
||||
default n
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config BOARD_HELTEC_T114
|
||||
select SOC_NRF52840_QIAA
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
|
||||
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
|
||||
|
||||
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
|
||||
|
||||
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Heltec T114 — nRF52840 + SX1262
|
||||
# Board-specific configuration
|
||||
#
|
||||
# Hardware:
|
||||
# - SX1262 LoRa (22 dBm, DIO2 RF switch, TCXO 1.8V, RX boosted)
|
||||
# - ATGM336H GPS on UART0 (9600 baud, enable P0.21, reset P1.06 active-LOW)
|
||||
# - Battery AIN2 (P0.04), 4.9:1 voltage divider, enable P0.06
|
||||
# - I2C0 (SDA=P0.07, SCL=P0.08) for sensors
|
||||
# - ST7789V 1.14" 240×135 TFT (SPI3, MIPI-DBI, mono-tft wrapper)
|
||||
# - LED: P1.03 (active-LOW, LoRa TX)
|
||||
# - Button: P1.10 (active-LOW)
|
||||
|
||||
# Board identification (matches Arduino MeshCore variant name)
|
||||
CONFIG_ZEPHCORE_BOARD_NAME="Heltec T114"
|
||||
|
||||
# Device Information Service model name
|
||||
CONFIG_BT_DIS_MODEL_NUMBER_STR="Heltec T114"
|
||||
|
||||
# SoftDevice firmware ID (s140 v6.1.1, same as RAK4631)
|
||||
CONFIG_ZEPHCORE_SD_FWID=0x00B6
|
||||
|
||||
# ========== RAM budget ==========
|
||||
# CFB mono-tft framebuffer = 240×135/8 = 4050 bytes, allocated from k_malloc.
|
||||
# Default heap (2048) is sized for SSD1306 128×64 (1024 bytes).
|
||||
# Screenless builds: pass no_display.conf to restore the 2048 default.
|
||||
CONFIG_HEAP_MEM_POOL_SIZE=6144
|
||||
|
||||
# ========== UI / display ==========
|
||||
# 1.14" 240×135 TFT - the default 6×8 font is illegible at this pixel density.
|
||||
# Switch to the smallest Zephyr built-in CFB font >= 16 px tall (typically 10×16).
|
||||
CONFIG_ZEPHCORE_DISPLAY_LARGE_FONT=y
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
board:
|
||||
name: heltec_t114
|
||||
full_name: Heltec T114
|
||||
vendor: heltec
|
||||
socs:
|
||||
- name: nrf52840
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Heltec T114 pin control definitions
|
||||
* Copyright (c) 2026 ZephCore
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Pin mappings from MeshCore variants/heltec_t114 (variant.cpp, platformio.ini):
|
||||
* - SPI2 (SX1262 LoRa): SCK=P0.19, MOSI=P0.22, MISO=P0.23
|
||||
* - SPI3 (ST7789 display): SCK=P1.08 (PIN_TFT_SCL=40), MOSI=P1.09 (PIN_TFT_SDA=41)
|
||||
* - I2C0 (Wire1, sensors): SDA=P0.07, SCL=P0.08
|
||||
* - UART0 (GPS): TX=P1.07, RX=P1.05
|
||||
*/
|
||||
|
||||
&pinctrl {
|
||||
spi2_default: spi2_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 0, 19)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 0, 22)>,
|
||||
<NRF_PSEL(SPIM_MISO, 0, 23)>;
|
||||
};
|
||||
};
|
||||
|
||||
spi2_sleep: spi2_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 0, 19)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 0, 22)>,
|
||||
<NRF_PSEL(SPIM_MISO, 0, 23)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
spi3_default: spi3_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 1, 8)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 1, 9)>;
|
||||
};
|
||||
};
|
||||
|
||||
spi3_sleep: spi3_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 1, 8)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 1, 9)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
i2c0_default: i2c0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 0, 7)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 8)>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c0_sleep: i2c0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 0, 7)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 8)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
uart0_default: uart0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(UART_TX, 1, 7)>,
|
||||
<NRF_PSEL(UART_RX, 1, 5)>;
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
|
||||
uart0_sleep: uart0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(UART_TX, 1, 7)>,
|
||||
<NRF_PSEL(UART_RX, 1, 5)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Heltec T114 — nRF52840 + SX1262 + ST7789 1.14" 240×135 TFT + ATGM336H GPS
|
||||
* Copyright (c) 2026 ZephCore
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
#include <nordic/nrf52840_qiaa.dtsi>
|
||||
#include <nordic/nrf52840_partition.dtsi>
|
||||
#include <zephyr/dt-bindings/lora/sx126x.h>
|
||||
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
|
||||
#include <zephyr/dt-bindings/input/input-event-codes.h>
|
||||
#include "heltec_t114_nrf52840-pinctrl.dtsi"
|
||||
|
||||
/* Delete default partitions from nrf52840_partition.dtsi */
|
||||
/delete-node/ &boot_partition;
|
||||
/delete-node/ &slot0_partition;
|
||||
/delete-node/ &slot1_partition;
|
||||
/delete-node/ &storage_partition;
|
||||
|
||||
/ {
|
||||
model = "Heltec T114";
|
||||
compatible = "heltec,t114";
|
||||
|
||||
chosen {
|
||||
zephyr,code-partition = &code_partition;
|
||||
zephyr,console = &cdc_acm_uart;
|
||||
zephyr,shell-uart = &cdc_acm_uart;
|
||||
zephyr,display = &mono_tft;
|
||||
};
|
||||
|
||||
leds {
|
||||
compatible = "gpio-leds";
|
||||
|
||||
/* LED_BUILTIN on P1.03, active-LOW (LED_STATE_ON = LOW) */
|
||||
tx_led: led_0 {
|
||||
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
|
||||
label = "LoRa TX LED";
|
||||
};
|
||||
};
|
||||
|
||||
buttons: buttons {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
user_button: button_0 {
|
||||
gpios = <&gpio1 10 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_0>;
|
||||
label = "User Button";
|
||||
};
|
||||
};
|
||||
|
||||
/* Input filter: user button long-press detection (1000ms) */
|
||||
user_btn_longpress {
|
||||
compatible = "zephyr,input-longpress";
|
||||
input = <&buttons>;
|
||||
input-codes = <INPUT_KEY_0>;
|
||||
short-codes = <INPUT_KEY_A>;
|
||||
long-codes = <INPUT_KEY_POWER>;
|
||||
long-delay-ms = <1000>;
|
||||
};
|
||||
|
||||
/* Input filter: user button multi-tap detection
|
||||
* 1 tap (400ms wait) → KEY_1 = page next on TFT
|
||||
* 2 taps (400ms wait) → KEY_B = flood advert
|
||||
* 3 taps (400ms wait) → KEY_D = buzzer mute toggle
|
||||
* 4 taps (400ms wait) → KEY_C = GPS on/off
|
||||
* 5 taps (immediate) → KEY_E = LED heartbeat toggle */
|
||||
user_btn_multitap {
|
||||
compatible = "zephcore,input-multi-tap";
|
||||
/* No 'input' phandle — listen to ALL devices.
|
||||
* KEY_A is emitted by the longpress pseudo-device,
|
||||
* not by &buttons, so we must not filter by device. */
|
||||
input-codes = <INPUT_KEY_A>;
|
||||
tap-codes = <INPUT_KEY_1 INPUT_KEY_B INPUT_KEY_D INPUT_KEY_C INPUT_KEY_E>;
|
||||
tap-delay-ms = <400>;
|
||||
};
|
||||
|
||||
/* GPS power enable — P0.21, HIGH = GPS powered on */
|
||||
gps_en: gps-enable {
|
||||
compatible = "gpio-leds";
|
||||
gps_enable_pin: gps_enable {
|
||||
gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS Enable";
|
||||
};
|
||||
};
|
||||
|
||||
/* GPS hardware reset — P1.06, active-LOW (PIN_GPS_RESET_ACTIVE=LOW) */
|
||||
gps_rst: gps-reset {
|
||||
compatible = "gpio-leds";
|
||||
gps_reset_pin: gps_reset {
|
||||
gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
|
||||
label = "GPS Reset";
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* Battery ADC enable — P0.06, HIGH = enable divider for measurement.
|
||||
* ZephyrBoard::getBattMilliVolts() calls regulator_{enable,disable}
|
||||
* around each reading to save power.
|
||||
*/
|
||||
vbat_enable: vbat-enable {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "vbat-enable";
|
||||
enable-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
/*
|
||||
* TFT power rail — P0.03, active-LOW (PIN_TFT_VDD_CTL LOW = display on).
|
||||
* Owned by display.c: enabled before CFB init, never disabled.
|
||||
* Screenless builds (no_display.conf) never call display.c so this
|
||||
* pin stays HIGH and the panel VDD rail is never powered.
|
||||
*/
|
||||
tft_pwr_enable: tft-pwr-enable {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "tft-pwr-enable";
|
||||
enable-gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
/*
|
||||
* TFT backlight — P0.15, active-LOW (PIN_TFT_LEDA_CTL LOW = backlight on).
|
||||
* Named disp_pwr_enable so display.c auto-gates it with the display on/off.
|
||||
*/
|
||||
disp_pwr_enable: disp-pwr-enable {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "disp-pwr-enable";
|
||||
enable-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
/*
|
||||
* Monochrome wrapper around the ST7789V.
|
||||
* Reports PIXEL_FORMAT_MONO01 to CFB → ~4 KB framebuffer instead of ~64 KB.
|
||||
* display.c picks this up via the zephyr,display chosen node.
|
||||
*/
|
||||
mono_tft: mono-tft {
|
||||
compatible = "zephcore,mono-tft";
|
||||
tft-dev = <&tft>;
|
||||
width = <240>;
|
||||
height = <135>;
|
||||
};
|
||||
|
||||
/*
|
||||
* MIPI-DBI SPI bus for ST7789V.
|
||||
* DC=P0.12 (PIN_TFT_DC=12), RST=P0.02 (PIN_TFT_RST=2, active-LOW).
|
||||
* write-only: no MISO on display SPI.
|
||||
*/
|
||||
mipi_dbi {
|
||||
compatible = "zephyr,mipi-dbi-spi";
|
||||
spi-dev = <&spi3>;
|
||||
dc-gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
|
||||
reset-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
|
||||
write-only;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
tft: st7789v@0 {
|
||||
compatible = "sitronix,st7789v";
|
||||
mipi-max-frequency = <20000000>;
|
||||
reg = <0>;
|
||||
width = <240>;
|
||||
height = <135>;
|
||||
x-offset = <40>;
|
||||
y-offset = <53>;
|
||||
/* MADCTL=0x60: MX|MV, RGB order (landscape, derived from MeshCore landscapeScreen()) */
|
||||
mdac = <0x60>;
|
||||
colmod = <0x55>;
|
||||
vcom = <0x20>;
|
||||
gctrl = <0x35>;
|
||||
vrhs = <0x0b>;
|
||||
vdvs = <0x20>;
|
||||
gamma = <0x01>;
|
||||
lcm = <0x2c>;
|
||||
porch-param = [0c 0c 00 33 33];
|
||||
cmd2en-param = [5a 69 02 01];
|
||||
pwctrl1-param = [a4 a1];
|
||||
pvgam-param = [70 2c 2e 15 10 09 48 33 53 0b 19 18 20 25];
|
||||
nvgam-param = [70 2c 2e 15 10 09 48 33 53 0b 19 18 20 25];
|
||||
ram-param = [00 f0];
|
||||
rgb-param = [40 02 14];
|
||||
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
|
||||
};
|
||||
};
|
||||
|
||||
zephyr,user {
|
||||
io-channels = <&adc 2>;
|
||||
/*
|
||||
* Full-scale = 3600 mV (ADC_GAIN_1_6 + ADC_REF_INTERNAL),
|
||||
* voltage divider ratio = 4.9 (ADC_MULTIPLIER from variant.h).
|
||||
* multiplier = 3600 * 4.9 = 17640
|
||||
*/
|
||||
vbat-mv-multiplier = <17640>;
|
||||
};
|
||||
|
||||
aliases {
|
||||
led0 = &tx_led;
|
||||
lora-tx-led = &tx_led;
|
||||
sw0 = &user_button;
|
||||
lora0 = &lora;
|
||||
watchdog0 = &wdt0;
|
||||
gps-enable = &gps_enable_pin;
|
||||
gps-reset = &gps_reset_pin;
|
||||
};
|
||||
};
|
||||
|
||||
®0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
/* DC-DC converter (NRF52BoardDCDC) */
|
||||
®1 {
|
||||
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
|
||||
};
|
||||
|
||||
&adc {
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Battery voltage on P0.04 (AIN2) — 4.9:1 divider, 3.0V ref in Arduino */
|
||||
channel@2 {
|
||||
reg = <2>;
|
||||
zephyr,gain = "ADC_GAIN_1_6";
|
||||
zephyr,reference = "ADC_REF_INTERNAL";
|
||||
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
|
||||
zephyr,input-positive = <NRF_SAADC_AIN2>;
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
};
|
||||
|
||||
&gpiote {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&gpio0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&gpio1 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&i2c0 {
|
||||
compatible = "nordic,nrf-twim";
|
||||
status = "okay";
|
||||
clock-frequency = <I2C_BITRATE_FAST>;
|
||||
pinctrl-0 = <&i2c0_default>;
|
||||
pinctrl-1 = <&i2c0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
#include "../../common/sensors-i2c.dtsi"
|
||||
};
|
||||
|
||||
&uart0 {
|
||||
compatible = "nordic,nrf-uarte";
|
||||
status = "okay";
|
||||
current-speed = <9600>;
|
||||
pinctrl-0 = <&uart0_default>;
|
||||
pinctrl-1 = <&uart0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
gnss: gnss {
|
||||
compatible = "gnss-nmea-generic";
|
||||
};
|
||||
};
|
||||
|
||||
/* SPI2 - SX1262 LoRa radio */
|
||||
&spi2 {
|
||||
compatible = "nordic,nrf-spim";
|
||||
status = "okay";
|
||||
cs-gpios = <&gpio0 24 GPIO_ACTIVE_LOW>;
|
||||
pinctrl-0 = <&spi2_default>;
|
||||
pinctrl-1 = <&spi2_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
lora: lora@0 {
|
||||
compatible = "semtech,sx1262";
|
||||
reg = <0>;
|
||||
reset-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
|
||||
busy-gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>;
|
||||
dio1-gpios = <&gpio0 20 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
|
||||
dio2-tx-enable;
|
||||
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_1V8>;
|
||||
tcxo-power-startup-delay-ms = <5>;
|
||||
spi-max-frequency = <8000000>;
|
||||
rx-boosted;
|
||||
};
|
||||
};
|
||||
|
||||
/* SPI3 - ST7789 1.14" 240×135 TFT display */
|
||||
&spi3 {
|
||||
compatible = "nordic,nrf-spim";
|
||||
status = "okay";
|
||||
pinctrl-0 = <&spi3_default>;
|
||||
pinctrl-1 = <&spi3_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
cs-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
/*
|
||||
* QSPI Flash (MX25R1635F 2MB) — DISABLED.
|
||||
*/
|
||||
&qspi {
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
zephyr_udc0: &usbd {
|
||||
compatible = "nordic,nrf-usbd";
|
||||
status = "okay";
|
||||
|
||||
cdc_acm_uart: cdc_acm_uart {
|
||||
compatible = "zephyr,cdc-acm-uart";
|
||||
};
|
||||
};
|
||||
|
||||
/* SoftDevice s140 v6.1.1 flash layout (app@0x26000, bootloader@0xF4000) */
|
||||
#include "../../common/nrf52_partitions_sdv6.dtsi"
|
||||
#include "../../common/nrf52_wakeup.dtsi"
|
||||
@@ -0,0 +1,20 @@
|
||||
identifier: heltec_t114/nrf52840
|
||||
name: Heltec T114
|
||||
type: mcu
|
||||
arch: arm
|
||||
toolchain:
|
||||
- zephyr
|
||||
- gnuarmemb
|
||||
- xtools
|
||||
ram: 256
|
||||
flash: 1024
|
||||
supported:
|
||||
- adc
|
||||
- ble
|
||||
- gpio
|
||||
- i2c
|
||||
- spi
|
||||
- usb_device
|
||||
- lora
|
||||
- gnss
|
||||
vendor: heltec
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Enable MPU
|
||||
CONFIG_ARM_MPU=y
|
||||
|
||||
# Enable hardware stack protection
|
||||
CONFIG_HW_STACK_PROTECTION=y
|
||||
|
||||
# Enable GPIO
|
||||
CONFIG_GPIO=y
|
||||
|
||||
# Enable UART driver
|
||||
CONFIG_SERIAL=y
|
||||
|
||||
# Enable console
|
||||
CONFIG_CONSOLE=y
|
||||
|
||||
# Build UF2 by default
|
||||
CONFIG_BUILD_OUTPUT_UF2=y
|
||||
CONFIG_USE_DT_CODE_PARTITION=y
|
||||
@@ -0,0 +1,13 @@
|
||||
# Heltec T114 - nRF52840 + SX1262 (screenless) build overlay
|
||||
#
|
||||
# Pass via -DEXTRA_CONF_FILE when building for units without the TFT module.
|
||||
# TFT panel VDD (P0.03) stays off for the entire session.
|
||||
#
|
||||
# west build -b heltec_t114 zephcore --pristine -- \
|
||||
# -DEXTRA_CONF_FILE="boards/common/prod.conf;boards/nrf52840/heltec_t114/no_display.conf"
|
||||
|
||||
CONFIG_ZEPHCORE_UI_DISPLAY=n
|
||||
CONFIG_ZEPHCORE_DISPLAY_MONO_TFT=n
|
||||
|
||||
# Restore default heap => No framebuffer to allocate without the display.
|
||||
CONFIG_HEAP_MEM_POOL_SIZE=2048
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
Monochrome wrapper for RGB TFT displays (ST7789V, etc.)
|
||||
|
||||
Reports PIXEL_FORMAT_MONO01 to CFB, so the framebuffer is ~4 KB
|
||||
(1 bit per pixel) instead of the ~64 KB RGB565 buffer that CFB
|
||||
would otherwise allocate. On flush, converts row-major 1bpp to
|
||||
RGB565 and pushes to the underlying display row by row.
|
||||
|
||||
compatible: "zephcore,mono-tft"
|
||||
|
||||
properties:
|
||||
tft-dev:
|
||||
type: phandle
|
||||
required: true
|
||||
description: The underlying RGB TFT display device (e.g. ST7789V. etc.).
|
||||
|
||||
width:
|
||||
type: int
|
||||
required: true
|
||||
description: Display width in pixels.
|
||||
|
||||
height:
|
||||
type: int
|
||||
required: true
|
||||
description: Display height in pixels.
|
||||
@@ -59,6 +59,16 @@ static const struct device *backlight_reg;
|
||||
|
||||
static bool backlight_on;
|
||||
|
||||
/* Optional TFT panel VDD regulator (e.g. T114 P0.03).
|
||||
* Enabled once before CFB init and never disabled => panel VDD must stay on.
|
||||
* Screenless builds omit display.c entirely so this is never called. */
|
||||
#if DT_NODE_EXISTS(DT_NODELABEL(tft_pwr_enable))
|
||||
static const struct device *panel_vdd_reg =
|
||||
DEVICE_DT_GET_OR_NULL(DT_NODELABEL(tft_pwr_enable));
|
||||
#else
|
||||
static const struct device *panel_vdd_reg;
|
||||
#endif
|
||||
|
||||
/* EPD frame change detection (Arduino-style):
|
||||
* hash draw calls across a frame and skip hardware flush if unchanged. */
|
||||
static uint32_t epd_frame_hash;
|
||||
@@ -96,6 +106,13 @@ static inline void backlight_set(bool on)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void panel_vdd_enable(void)
|
||||
{
|
||||
if (panel_vdd_reg && device_is_ready(panel_vdd_reg)) {
|
||||
regulator_enable(panel_vdd_reg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Auto-off work */
|
||||
static struct k_work_delayable auto_off_work;
|
||||
|
||||
@@ -202,6 +219,10 @@ int mc_display_init(void)
|
||||
display_blanking_on(disp_dev);
|
||||
}
|
||||
|
||||
/* Enable panel VDD before CFB init so the controller is powered
|
||||
* when the init sequence is sent over SPI. */
|
||||
panel_vdd_enable();
|
||||
|
||||
/* Initialize CFB */
|
||||
int ret = cfb_framebuffer_init(disp_dev);
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* ZephCore - Monochrome wrapper for RGB TFT displays
|
||||
* Copyright (c) 2026 ZephCore
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Reports PIXEL_FORMAT_MONO01 to CFB so the framebuffer is ~4 KB
|
||||
* (240×135/8 = 4050 bytes) instead of the ~64 KB RGB565 buffer that
|
||||
* CFB would otherwise allocate for an ST7789V.
|
||||
*
|
||||
* On display_write(), converts row major 1bpp MONO01 (LSB first) to
|
||||
* RGB565 one row at a time and forwards each row to the underlying TFT.
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT zephcore_mono_tft
|
||||
|
||||
#include <zephyr/drivers/display.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LOG_LEVEL CONFIG_DISPLAY_LOG_LEVEL
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(display_mono_tft);
|
||||
|
||||
struct mono_tft_config {
|
||||
const struct device *tft;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
};
|
||||
|
||||
/* Hardcoded to instance 0. Only one display wrapper per board is supported. */
|
||||
#define MONO_TFT_MAX_WIDTH DT_INST_PROP(0, width)
|
||||
|
||||
struct mono_tft_data {
|
||||
/* Static allocation avoids ~480 bytes of stack pressure per display_write() call. */
|
||||
uint16_t line_buf[MONO_TFT_MAX_WIDTH];
|
||||
};
|
||||
|
||||
static int mono_tft_blanking_on(const struct device *dev)
|
||||
{
|
||||
const struct mono_tft_config *cfg = dev->config;
|
||||
|
||||
return display_blanking_on(cfg->tft);
|
||||
}
|
||||
|
||||
static int mono_tft_blanking_off(const struct device *dev)
|
||||
{
|
||||
const struct mono_tft_config *cfg = dev->config;
|
||||
|
||||
return display_blanking_off(cfg->tft);
|
||||
}
|
||||
|
||||
static int mono_tft_write(
|
||||
const struct device *dev,
|
||||
const uint16_t x_start, const uint16_t y_start,
|
||||
const struct display_buffer_descriptor *desc,
|
||||
const void *buf
|
||||
){
|
||||
const struct mono_tft_config *cfg = dev->config;
|
||||
struct mono_tft_data *data = dev->data;
|
||||
const uint8_t *mono_buf = buf;
|
||||
/* Pitch is in pixels. Convert to bytes by rounding up to the nearest boundary. */
|
||||
uint16_t pitch_bytes = (desc->pitch + 7U) / 8U;
|
||||
|
||||
const struct display_buffer_descriptor line_desc = {
|
||||
.buf_size = (uint32_t)desc->width * 2U,
|
||||
.width = desc->width,
|
||||
.height = 1U,
|
||||
.pitch = desc->width,
|
||||
};
|
||||
|
||||
int err = 0;
|
||||
|
||||
for (uint16_t row = 0; row < desc->height; row++) {
|
||||
const uint8_t *mono_row = mono_buf + (size_t)row * pitch_bytes;
|
||||
|
||||
for (uint16_t col = 0; col < desc->width; col++) {
|
||||
/* CFB MONO01 is row major and LSB first. Pixel at col uses bit (col%8) of byte (col/8). */
|
||||
bool pixel_on = (mono_row[col / 8U] >> (col & 7U)) & 1U;
|
||||
/* ST7789V expects RGB565 in big endian byte order over SPI. */
|
||||
data->line_buf[col] = pixel_on ? sys_cpu_to_be16(0xFFFFU) : 0x0000U;
|
||||
}
|
||||
|
||||
err = display_write(cfg->tft, x_start, y_start + row, &line_desc, data->line_buf);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mono_tft_get_capabilities(
|
||||
const struct device *dev,
|
||||
struct display_capabilities *caps
|
||||
){
|
||||
const struct mono_tft_config *cfg = dev->config;
|
||||
|
||||
memset(caps, 0, sizeof(*caps));
|
||||
caps->x_resolution = cfg->width;
|
||||
caps->y_resolution = cfg->height;
|
||||
caps->supported_pixel_formats = PIXEL_FORMAT_MONO01;
|
||||
caps->current_pixel_format = PIXEL_FORMAT_MONO01;
|
||||
}
|
||||
|
||||
static int mono_tft_set_pixel_format(
|
||||
const struct device *dev,
|
||||
const enum display_pixel_format pixel_format
|
||||
){
|
||||
ARG_UNUSED(dev);
|
||||
return (pixel_format == PIXEL_FORMAT_MONO01) ? 0 : -ENOTSUP;
|
||||
}
|
||||
|
||||
static int mono_tft_init(const struct device *dev)
|
||||
{
|
||||
const struct mono_tft_config *cfg = dev->config;
|
||||
|
||||
if (!device_is_ready(cfg->tft)) {
|
||||
LOG_ERR("underlying TFT device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct display_driver_api mono_tft_api = {
|
||||
.blanking_on = mono_tft_blanking_on,
|
||||
.blanking_off = mono_tft_blanking_off,
|
||||
.write = mono_tft_write,
|
||||
.get_capabilities = mono_tft_get_capabilities,
|
||||
.set_pixel_format = mono_tft_set_pixel_format,
|
||||
};
|
||||
|
||||
static struct mono_tft_data mono_tft_data;
|
||||
static const struct mono_tft_config mono_tft_config = {
|
||||
.tft = DEVICE_DT_GET(DT_INST_PHANDLE(0, tft_dev)),
|
||||
.width = DT_INST_PROP(0, width),
|
||||
.height = DT_INST_PROP(0, height),
|
||||
};
|
||||
|
||||
DEVICE_DT_INST_DEFINE(
|
||||
0,
|
||||
mono_tft_init,
|
||||
NULL,
|
||||
&mono_tft_data,
|
||||
&mono_tft_config,
|
||||
POST_KERNEL,
|
||||
CONFIG_DISPLAY_INIT_PRIORITY,
|
||||
&mono_tft_api
|
||||
);
|
||||
Reference in New Issue
Block a user