feat: native USB CDC-ACM support for Heltec V3
The default heltec_v3 feature now uses native USB OTG (same as V4), appearing as /dev/ttyACM0 with the DongLoRa VID:PID (1209:5741). This requires a hardware mod: solder R29/R3 and disconnect the CP2102 from the USB data lines. Stock (unmodified) V3 boards should use the new heltec_v3_uart feature which preserves the original UART-over-CP2102 behavior (/dev/ttyUSB0). - heltec_v3: switched from UART to USB OTG - heltec_v3_uart: new feature, UART fallback for stock boards - Removed #[cfg] gates on UsbParts/UartParts (both always defined) - Updated README with hardware mod instructions and board table - Updated justfile with heltec_v3_uart board definition
This commit is contained in:
1 parent
07651dce44
commit
6db287c241
9 files changed
+142
-20
No files matched your search
@@ -84,6 +84,12 @@ heltec_v3 = [
|
||||
"dep:esp-println",
|
||||
"dep:esp-rtos",
|
||||
]
|
||||
heltec_v3_uart = [
|
||||
"dep:esp-backtrace",
|
||||
"dep:esp-hal",
|
||||
"dep:esp-println",
|
||||
"dep:esp-rtos",
|
||||
]
|
||||
heltec_v4 = [
|
||||
"dep:esp-backtrace",
|
||||
"dep:esp-hal",
|
||||
|
||||
@@ -18,11 +18,39 @@ All commands are run from the `firmware/` directory.
|
||||
|
||||
## Supported Boards
|
||||
|
||||
| Board | MCU | Radio | Target |
|
||||
|-------|-----|-------|--------|
|
||||
| Heltec V3 | ESP32-S3 | SX1262 | xtensa-esp32s3-none-elf |
|
||||
| Heltec V4 | ESP32-S3 | SX1262 | xtensa-esp32s3-none-elf |
|
||||
| RAK WisBlock 4631 | nRF52840 | SX1262 | thumbv7em-none-eabihf |
|
||||
| Board | Feature | MCU | Radio | USB | Target |
|
||||
|-------|---------|-----|-------|-----|--------|
|
||||
| Heltec V3 | `heltec_v3` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
|
||||
| Heltec V3 (UART) | `heltec_v3_uart` | ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf |
|
||||
| Heltec V4 | `heltec_v4` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
|
||||
| RAK WisBlock 4631 | `rak_wisblock_4631` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
|
||||
| Wio Tracker L1 | `wio_tracker_l1` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
|
||||
|
||||
### Heltec V3 USB Note
|
||||
|
||||
The default `heltec_v3` build uses **native USB CDC-ACM**, which requires a
|
||||
hardware modification to your board. Stock Heltec V3 boards route USB through a
|
||||
CP2102 bridge chip to UART — the ESP32-S3's native USB pins (GPIO19/GPIO20) are
|
||||
routed to header pins but not connected to the USB connector.
|
||||
|
||||
**To enable native USB on a Heltec V3:**
|
||||
|
||||
1. Solder 0-ohm (or 22-ohm) resistors at pads **R29** (D+) and **R3** (D-)
|
||||
2. Disconnect the CP2102 from the USB connector's D+/D- lines (cut traces or
|
||||
remove the CP2102 bridge resistors)
|
||||
|
||||
After this mod, the board appears as `/dev/ttyACM0` with the DongLoRa VID:PID
|
||||
(1209:5741), just like the V4 and nRF boards.
|
||||
|
||||
**If you have a stock (unmodified) V3**, use `heltec_v3_uart` instead:
|
||||
|
||||
```sh
|
||||
just build heltec_v3_uart
|
||||
just flash heltec_v3_uart
|
||||
```
|
||||
|
||||
This builds firmware that communicates via the CP2102 bridge (appears as
|
||||
`/dev/ttyUSB0`).
|
||||
|
||||
## Adding a Board
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ set shell := ["bash", "-c"]
|
||||
# Board definitions: feature target chip
|
||||
# Xtensa targets use nightly cargo + esp rustc + -Zbuild-std=core.
|
||||
# Tool versions are pinned in mise.toml; run `just setup` to install everything.
|
||||
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v3_uart := "heltec_v3_uart xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
|
||||
rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA"
|
||||
wio_tracker_l1 := "wio_tracker_l1 thumbv7em-none-eabihf nRF52840_xxAA"
|
||||
|
||||
@@ -12,7 +13,7 @@ builds_dir := "builds"
|
||||
version := `sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml`
|
||||
|
||||
# All known boards
|
||||
boards := "heltec_v3 heltec_v4 rak_wisblock_4631 wio_tracker_l1"
|
||||
boards := "heltec_v3 heltec_v3_uart heltec_v4 rak_wisblock_4631 wio_tracker_l1"
|
||||
|
||||
# Install all required tools and toolchains
|
||||
setup:
|
||||
@@ -203,6 +204,7 @@ _flash_uf2 board:
|
||||
[private]
|
||||
_info name:
|
||||
@if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \
|
||||
elif [ "{{name}}" == "heltec_v3_uart" ]; then echo "{{heltec_v3_uart}}"; \
|
||||
elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \
|
||||
elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \
|
||||
elif [ "{{name}}" == "wio_tracker_l1" ]; then echo "{{wio_tracker_l1}}"; \
|
||||
|
||||
@@ -32,12 +32,12 @@ pub struct RadioParts {
|
||||
pub delay: Delay,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "heltec_v3"))]
|
||||
#[allow(dead_code)] // Not used by heltec_v3_uart
|
||||
pub struct UsbParts {
|
||||
pub driver: UsbDriver,
|
||||
}
|
||||
|
||||
#[cfg(feature = "heltec_v3")]
|
||||
#[allow(dead_code)] // Only used by heltec_v3_uart
|
||||
pub struct UartParts {
|
||||
pub driver: UartDriver,
|
||||
}
|
||||
|
||||
+17
-6
@@ -1,3 +1,11 @@
|
||||
//! Heltec WiFi LoRa 32 V3 — native USB (requires hardware mod).
|
||||
//!
|
||||
//! Stock V3 boards route USB through a CP2102 bridge to UART. To use native
|
||||
//! USB CDC-ACM, solder R29 (D+) and R3 (D-) on the PCB and disconnect the
|
||||
//! CP2102 from the USB data lines. See the project README for details.
|
||||
//!
|
||||
//! For stock (unmodified) V3 boards, use the `heltec_v3_uart` feature instead.
|
||||
|
||||
use esp_hal::gpio::{Level, Output, OutputConfig};
|
||||
|
||||
use super::esp32s3;
|
||||
@@ -6,8 +14,8 @@ use crate::hal::esp32s3 as mcu;
|
||||
|
||||
#[allow(unused_imports)] // Re-exported for other modules (display, radio, host tasks)
|
||||
pub use super::esp32s3::{
|
||||
create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UartDriver,
|
||||
UartParts,
|
||||
create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UsbDriver,
|
||||
UsbParts,
|
||||
};
|
||||
|
||||
pub struct Board {
|
||||
@@ -19,7 +27,7 @@ impl LoRaBoard for Board {
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
type RadioParts = RadioParts;
|
||||
type CommParts = UartParts;
|
||||
type CommParts = UsbParts;
|
||||
type DisplayParts = DisplayParts;
|
||||
type DisplayDriver = DisplayDriver;
|
||||
|
||||
@@ -32,7 +40,7 @@ impl LoRaBoard for Board {
|
||||
mcu::mac_address()
|
||||
}
|
||||
|
||||
fn into_parts(self) -> BoardParts<RadioParts, UartParts, DisplayParts> {
|
||||
fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts> {
|
||||
let p = self.p;
|
||||
|
||||
mcu::start_timer(p.TIMG0);
|
||||
@@ -43,8 +51,11 @@ impl LoRaBoard for Board {
|
||||
|
||||
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
|
||||
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
|
||||
let host = UartParts {
|
||||
driver: mcu::init_uart(p.UART0, p.GPIO43, p.GPIO44),
|
||||
|
||||
// Note: switches internal USB PHY from Serial-JTAG to OTG.
|
||||
// espflash --monitor will stop working after this point.
|
||||
let host = UsbParts {
|
||||
driver: mcu::init_usb(p.USB0, p.GPIO20, p.GPIO19),
|
||||
};
|
||||
|
||||
// SSD1306 display reset: pulse GPIO21 low->high before I2C init
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
//! Heltec WiFi LoRa 32 V3 — stock UART (via CP2102 bridge).
|
||||
//!
|
||||
//! This is for unmodified V3 boards where USB goes through the CP2102
|
||||
//! bridge to UART0. The board appears as /dev/ttyUSB* (not ttyACM*).
|
||||
//!
|
||||
//! If you've done the hardware mod (solder R29/R3, disconnect CP2102),
|
||||
//! use the `heltec_v3` feature instead for native USB CDC-ACM support.
|
||||
|
||||
use esp_hal::gpio::{Level, Output, OutputConfig};
|
||||
|
||||
use super::esp32s3;
|
||||
use super::traits::{BoardParts, LoRaBoard};
|
||||
use crate::hal::esp32s3 as mcu;
|
||||
|
||||
#[allow(unused_imports)] // Re-exported for other modules (display, radio, host tasks)
|
||||
pub use super::esp32s3::{
|
||||
create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UartDriver,
|
||||
UartParts,
|
||||
};
|
||||
|
||||
pub struct Board {
|
||||
p: esp_hal::peripherals::Peripherals,
|
||||
}
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "Heltec V3 (UART)";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
type RadioParts = RadioParts;
|
||||
type CommParts = UartParts;
|
||||
type DisplayParts = DisplayParts;
|
||||
type DisplayDriver = DisplayDriver;
|
||||
|
||||
fn init() -> Self {
|
||||
let p = esp_hal::init(esp_hal::Config::default());
|
||||
Self { p }
|
||||
}
|
||||
|
||||
fn mac_address() -> [u8; 6] {
|
||||
mcu::mac_address()
|
||||
}
|
||||
|
||||
fn into_parts(self) -> BoardParts<RadioParts, UartParts, DisplayParts> {
|
||||
let p = self.p;
|
||||
|
||||
mcu::start_timer(p.TIMG0);
|
||||
|
||||
// Vext power: GPIO36, active LOW to enable peripherals
|
||||
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
core::mem::forget(vext);
|
||||
|
||||
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
|
||||
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
|
||||
let host = UartParts {
|
||||
driver: mcu::init_uart(p.UART0, p.GPIO43, p.GPIO44),
|
||||
};
|
||||
|
||||
// SSD1306 display reset: pulse GPIO21 low->high before I2C init
|
||||
let mut display_rst = Output::new(p.GPIO21, Level::Low, OutputConfig::default());
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
display_rst.set_high();
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
core::mem::forget(display_rst);
|
||||
|
||||
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
|
||||
let display = Some(DisplayParts { i2c });
|
||||
|
||||
BoardParts {
|
||||
radio,
|
||||
host,
|
||||
display,
|
||||
mac: Self::mac_address(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//! Each submodule provides low-level bus and peripheral init for one MCU family.
|
||||
//! Board files call these with specific pins and construct higher-level drivers.
|
||||
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))]
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))]
|
||||
pub mod esp32s3;
|
||||
|
||||
#[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))]
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
pub mod framing;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "heltec_v3")] {
|
||||
if #[cfg(feature = "heltec_v3_uart")] {
|
||||
mod uart;
|
||||
pub use uart::host_task;
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))] {
|
||||
use defmt_rtt as _;
|
||||
use panic_probe as _;
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))] {
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] {
|
||||
use esp_backtrace as _;
|
||||
use esp_println as _;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ cfg_if::cfg_if! {
|
||||
async fn main(spawner: Spawner) {
|
||||
run(spawner).await;
|
||||
}
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))] {
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] {
|
||||
#[esp_rtos::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
run(spawner).await;
|
||||
|
||||
Reference in new issue
Block a user