@@ -0,0 +1,11 @@
|
||||
[build]
|
||||
target = "riscv32imac-unknown-none-elf"
|
||||
|
||||
[target.riscv32imac-unknown-none-elf]
|
||||
runner = "espflash flash --monitor"
|
||||
|
||||
[env]
|
||||
ESP_LOG = "info"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch(domain:crates.io)",
|
||||
"WebFetch(domain:docs.esp-rs.org)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:raw.githubusercontent.com)",
|
||||
"WebFetch(domain:docs.rs)",
|
||||
"WebFetch(domain:docs.espressif.com)",
|
||||
"WebFetch(domain:esp32.implrust.com)",
|
||||
"WebSearch"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Base image
|
||||
FROM debian:bookworm-slim
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
# Arguments
|
||||
ARG CONTAINER_USER=esp
|
||||
ARG CONTAINER_GROUP=esp
|
||||
ARG NIGHTLY_VERSION=nightly-2025-11-01
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
curl unzip git pkg-config gcc \
|
||||
libssl-dev libusb-1.0-0 libusb-1.0-0-dev libudev-dev \
|
||||
sudo \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN adduser --disabled-password --gecos "" ${CONTAINER_USER}
|
||||
|
||||
# Allow esp to sync the dialout group GID at container start
|
||||
RUN echo "${CONTAINER_USER} ALL=(root) NOPASSWD: /usr/sbin/groupadd, /usr/sbin/groupmod, /usr/sbin/usermod, /usr/bin/chmod" \
|
||||
>> /etc/sudoers.d/esp-dialout
|
||||
USER ${CONTAINER_USER}
|
||||
WORKDIR /home/${CONTAINER_USER}
|
||||
|
||||
# Install rustup + nightly toolchain with RISC-V target and rust-src component
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \
|
||||
--default-toolchain ${NIGHTLY_VERSION} -y --profile minimal \
|
||||
--component rust-src,clippy,rustfmt \
|
||||
--target riscv32imac-unknown-none-elf
|
||||
|
||||
ENV PATH=${PATH}:$HOME/.cargo/bin
|
||||
|
||||
# Install espflash (flash + monitor tool)
|
||||
RUN ARCH=$($HOME/.cargo/bin/rustup show | grep "Default host" | sed -e 's/.* //') && \
|
||||
curl -L "https://github.com/esp-rs/espflash/releases/latest/download/espflash-${ARCH}.zip" \
|
||||
-o "${HOME}/.cargo/bin/espflash.zip" && \
|
||||
unzip "${HOME}/.cargo/bin/espflash.zip" -d "${HOME}/.cargo/bin/" && \
|
||||
rm "${HOME}/.cargo/bin/espflash.zip" && \
|
||||
chmod u+x "${HOME}/.cargo/bin/espflash"
|
||||
|
||||
# Install probe-rs (on-chip debugger, flashing, RTT)
|
||||
RUN $HOME/.cargo/bin/cargo install probe-rs-tools --locked
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "esp32c6-dev",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"NIGHTLY_VERSION": "nightly-2025-11-01"
|
||||
}
|
||||
},
|
||||
// Privileged container to access /dev
|
||||
"privileged": true,
|
||||
// Mount USB bus so espflash can reach the connected board
|
||||
"mounts": [
|
||||
"source=/dev/bus/usb/,target=/dev/bus/usb/,type=bind"
|
||||
],
|
||||
"runArgs": [
|
||||
"--device=/dev/ttyACM0",
|
||||
"--device=/dev/ttyACM1"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "modifications",
|
||||
"files.watcherExclude": {
|
||||
"**/target/**": true
|
||||
},
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
}
|
||||
},
|
||||
"extensions": [
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"mutantdino.resourcemonitor",
|
||||
"probe-rs.probe-rs-debugger"
|
||||
]
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "DIALOUT_GID=$(stat -c '%g' /dev/ttyACM0 2>/dev/null || stat -c '%g' /dev/ttyACM1 2>/dev/null || echo 20) && (getent group dialout > /dev/null && sudo groupmod -g $DIALOUT_GID dialout || sudo groupadd -g $DIALOUT_GID dialout) && sudo usermod -aG dialout esp && USB_GID=$(stat -c '%g' /dev/bus/usb/*/* 2>/dev/null | sort -u | head -1 || echo 46) && (getent group plugdev > /dev/null && sudo groupmod -g $USB_GID plugdev || sudo groupadd -g $USB_GID plugdev) && sudo usermod -aG plugdev esp || true",
|
||||
"postStartCommand": "sudo chmod a+rw /dev/bus/usb/*/* 2>/dev/null; sudo chmod a+rw /dev/ttyACM* 2>/dev/null; true",
|
||||
"remoteUser": "esp",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
|
||||
"workspaceFolder": "/workspace"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
*.rs.bk
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "probe-rs-debug",
|
||||
"request": "launch",
|
||||
"name": "Debug ESP32-C6",
|
||||
"chip": "esp32c6",
|
||||
"flashingConfig": { "flashingEnabled": true },
|
||||
"coreConfigs": [{
|
||||
"programBinary": "target/riscv32imac-unknown-none-elf/debug/esp32c6-display"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+1404
File diff suppressed because it is too large
Load Diff
+39
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "esp32c6-display"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
# ESP32-C6 hardware abstraction layer
|
||||
esp-hal = { version = "1.0.0", features = ["esp32c6", "unstable"] }
|
||||
# Panic handler + backtrace over serial
|
||||
esp-backtrace = { version = "0.18", features = ["esp32c6", "panic-handler", "println"] }
|
||||
# Serial output / log backend
|
||||
esp-println = { version = "0.16", features = ["esp32c6", "log-04"] }
|
||||
# Heap allocator
|
||||
esp-alloc = "0.9"
|
||||
# Logging facade
|
||||
log = "0.4"
|
||||
# ESP-IDF app descriptor required by the on-chip bootloader (and probe-rs)
|
||||
esp-bootloader-esp-idf = { version = "0.2", features = ["esp32c6"] }
|
||||
|
||||
# embedded-hal 1.0 traits + bus utilities
|
||||
embedded-hal = "1.0"
|
||||
embedded-hal-bus = "0.2"
|
||||
|
||||
# MIPI DSI display driver (supports ST7789)
|
||||
mipidsi = "0.10"
|
||||
|
||||
# 2D graphics primitives and text rendering
|
||||
embedded-graphics = "0.8"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
opt-level = "s"
|
||||
lto = "fat"
|
||||
overflow-checks = false
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 318 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
||||
targets = ["riscv32imac-unknown-none-elf"]
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
use embedded_graphics::pixelcolor::Rgb565;
|
||||
use embedded_hal::delay::DelayNs;
|
||||
use mipidsi::{
|
||||
ConfigurationError,
|
||||
dcs::{
|
||||
BitsPerPixel, ExitSleepMode, InterfaceExt, PixelFormat, SetAddressMode, SetDisplayOn,
|
||||
SetInvertMode, SetPixelFormat, SetTearingEffect,
|
||||
},
|
||||
interface::{Interface, InterfaceKind},
|
||||
models::{Model, ModelInitError},
|
||||
options::{ColorInversion, ModelOptions, TearingEffect},
|
||||
};
|
||||
|
||||
/// JD9853 display driver in Rgb565 color mode.
|
||||
///
|
||||
/// The JD9853 has a 240-column internal framebuffer but the visible panel is
|
||||
/// 172×320, starting at column offset 34. Configure the [`Builder`] as:
|
||||
///
|
||||
/// ```ignore
|
||||
/// Builder::new(JD9853, di)
|
||||
/// .display_size(172, 320)
|
||||
/// .display_offset(34, 0)
|
||||
/// ```
|
||||
///
|
||||
/// This panel requires colour inversion; the driver always enables it.
|
||||
pub struct JD9853;
|
||||
|
||||
impl Model for JD9853 {
|
||||
type ColorFormat = Rgb565;
|
||||
|
||||
// Full internal framebuffer of the JD9853 controller.
|
||||
// Visible area is 172×320 at column offset 34 – set in the Builder.
|
||||
const FRAMEBUFFER_SIZE: (u16, u16) = (240, 320);
|
||||
|
||||
fn init<DELAY, DI>(
|
||||
&mut self,
|
||||
di: &mut DI,
|
||||
delay: &mut DELAY,
|
||||
options: &ModelOptions,
|
||||
) -> Result<SetAddressMode, ModelInitError<DI::Error>>
|
||||
where
|
||||
DELAY: DelayNs,
|
||||
DI: Interface,
|
||||
{
|
||||
if !matches!(
|
||||
DI::KIND,
|
||||
InterfaceKind::Serial4Line
|
||||
| InterfaceKind::Parallel8Bit
|
||||
| InterfaceKind::Parallel16Bit
|
||||
) {
|
||||
return Err(ModelInitError::InvalidConfiguration(
|
||||
ConfigurationError::UnsupportedInterface,
|
||||
));
|
||||
}
|
||||
|
||||
let madctl = SetAddressMode::from(options);
|
||||
|
||||
// --- Exit sleep and wait for internal oscillator ---
|
||||
di.write_command(ExitSleepMode)?;
|
||||
delay.delay_us(120_000);
|
||||
|
||||
// --- Unlock manufacturer commands ---
|
||||
di.write_raw(0xDF, &[0x98, 0x53])?;
|
||||
|
||||
// --- Bank 0: panel / power / gamma settings ---
|
||||
di.write_raw(0xB2, &[0x23])?;
|
||||
di.write_raw(0xB7, &[0x00, 0x47, 0x00, 0x6F])?;
|
||||
di.write_raw(0xBB, &[0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0])?;
|
||||
di.write_raw(0xC0, &[0x44, 0xA4])?;
|
||||
di.write_raw(0xC1, &[0x16])?;
|
||||
di.write_raw(0xC3, &[0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77])?;
|
||||
di.write_raw(
|
||||
0xC4,
|
||||
&[
|
||||
0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82,
|
||||
],
|
||||
)?;
|
||||
// Gamma table (positive + negative, 16 bytes each)
|
||||
di.write_raw(
|
||||
0xC8,
|
||||
&[
|
||||
0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28,
|
||||
0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00,
|
||||
0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28,
|
||||
0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00,
|
||||
],
|
||||
)?;
|
||||
di.write_raw(0xD0, &[0x04, 0x06, 0x6B, 0x0F, 0x00])?;
|
||||
di.write_raw(0xD7, &[0x00, 0x30])?;
|
||||
di.write_raw(0xE6, &[0x14])?;
|
||||
|
||||
// --- Bank 1: power settings ---
|
||||
di.write_raw(0xDE, &[0x01])?;
|
||||
di.write_raw(0xB7, &[0x03, 0x13, 0xEF, 0x35, 0x35])?;
|
||||
di.write_raw(0xC1, &[0x14, 0x15, 0xC0])?;
|
||||
di.write_raw(0xC2, &[0x06, 0x3A])?;
|
||||
di.write_raw(0xC4, &[0x72, 0x12])?;
|
||||
di.write_raw(0xBE, &[0x00])?;
|
||||
|
||||
// --- Bank 2: internal settings (first pass) ---
|
||||
di.write_raw(0xDE, &[0x02])?;
|
||||
di.write_raw(0xE5, &[0x00, 0x02, 0x00])?;
|
||||
di.write_raw(0xE5, &[0x01, 0x02, 0x00])?;
|
||||
|
||||
// --- Return to bank 0 ---
|
||||
di.write_raw(0xDE, &[0x00])?;
|
||||
|
||||
// --- Standard DCS: tearing effect + pixel format ---
|
||||
di.write_command(SetTearingEffect::new(TearingEffect::Vertical))?;
|
||||
|
||||
let pf = PixelFormat::with_all(BitsPerPixel::from_rgb_color::<Self::ColorFormat>());
|
||||
di.write_command(SetPixelFormat::new(pf))?;
|
||||
|
||||
// Seed the column / page address window (mipidsi will update this before
|
||||
// each draw, but the second bank-2 block below requires these to have been
|
||||
// written first to latch internal timing registers).
|
||||
di.write_raw(0x2A, &[0x00, 0x22, 0x00, 0xCD])?; // cols 34..205
|
||||
di.write_raw(0x2B, &[0x00, 0x00, 0x01, 0x3F])?; // rows 0..319
|
||||
|
||||
// --- Bank 2: internal settings (second pass, after window init) ---
|
||||
di.write_raw(0xDE, &[0x02])?;
|
||||
di.write_raw(0xE5, &[0x00, 0x02, 0x00])?;
|
||||
di.write_raw(0xDE, &[0x00])?;
|
||||
|
||||
// --- Address mode + colour inversion ---
|
||||
di.write_command(madctl)?;
|
||||
|
||||
// This panel always requires colour inversion to display correct colours.
|
||||
di.write_command(SetInvertMode::new(ColorInversion::Inverted))?;
|
||||
|
||||
// 10 ms settle time before display-on (matches reference init ordering).
|
||||
delay.delay_us(10_000);
|
||||
|
||||
di.write_command(SetDisplayOn)?;
|
||||
|
||||
Ok(madctl)
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_backtrace as _;
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
use esp_hal::{
|
||||
delay::Delay,
|
||||
gpio::{Level, Output, OutputConfig},
|
||||
main,
|
||||
spi::master::{Config, Spi},
|
||||
time::Rate,
|
||||
};
|
||||
use esp_println::println;
|
||||
|
||||
mod jd9853;
|
||||
|
||||
use jd9853::JD9853;
|
||||
use mipidsi::{interface::SpiInterface, Builder};
|
||||
|
||||
use embedded_graphics::{
|
||||
mono_font::{ascii::FONT_10X20, MonoTextStyle},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::*,
|
||||
primitives::{PrimitiveStyleBuilder, Rectangle},
|
||||
text::Text,
|
||||
};
|
||||
|
||||
// TODO: verify pin assignments against your board schematic
|
||||
//
|
||||
// Typical wiring for a 1.47" JD9853 breakout on ESP32-C6:
|
||||
//
|
||||
// Display pin │ ESP32-C6 GPIO
|
||||
// ─────────────┼───────────────
|
||||
// SCL / SCK │ GPIO6
|
||||
// SDA / MOSI │ GPIO7
|
||||
// CS │ GPIO10
|
||||
// DC │ GPIO4
|
||||
// RST │ GPIO5
|
||||
// BLK / BL │ GPIO22 (set high to enable backlight)
|
||||
//
|
||||
const LCD_W: u16 = 172;
|
||||
const LCD_H: u16 = 320;
|
||||
|
||||
#[main]
|
||||
fn main() -> ! {
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
// 72 KB heap for mipidsi internal buffers, alloc, etc.
|
||||
esp_alloc::heap_allocator!(size: 72 * 1024);
|
||||
|
||||
println!("ESP32-C6 display demo starting…");
|
||||
|
||||
// --- SPI bus -----------------------------------------------------------
|
||||
let spi = Spi::new(
|
||||
peripherals.SPI2,
|
||||
Config::default()
|
||||
.with_frequency(Rate::from_mhz(40))
|
||||
.with_mode(esp_hal::spi::Mode::_0),
|
||||
)
|
||||
.unwrap()
|
||||
.with_sck(peripherals.GPIO1)
|
||||
.with_mosi(peripherals.GPIO2);
|
||||
|
||||
// Manual chip-select via GPIO
|
||||
let cs = Output::new(peripherals.GPIO14, Level::High, OutputConfig::default());
|
||||
|
||||
// Wrap bus + CS into an embedded-hal SpiDevice
|
||||
let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new(spi, cs, Delay::new()).unwrap();
|
||||
|
||||
// --- Display -----------------------------------------------------------
|
||||
let dc = Output::new(peripherals.GPIO15, Level::Low, OutputConfig::default());
|
||||
let rst = Output::new(peripherals.GPIO22, Level::High, OutputConfig::default());
|
||||
|
||||
// Backlight — drive high to enable
|
||||
let _bl = Output::new(peripherals.GPIO23, Level::High, OutputConfig::default());
|
||||
|
||||
// mipidsi 0.10 requires a small scratch buffer owned by the interface
|
||||
let mut buf = [0u8; 512];
|
||||
let di = SpiInterface::new(spi_device, dc, &mut buf);
|
||||
|
||||
// JD9853 has a 240-wide internal framebuffer; the 172-pixel panel starts at column 34.
|
||||
let mut display = Builder::new(JD9853, di)
|
||||
.reset_pin(rst)
|
||||
.display_size(LCD_W, LCD_H)
|
||||
.display_offset(34, 0)
|
||||
.init(&mut Delay::new())
|
||||
.expect("display init failed");
|
||||
|
||||
// --- Drawing -----------------------------------------------------------
|
||||
display.clear(Rgb565::BLACK).unwrap();
|
||||
|
||||
// Blue rectangle as background banner
|
||||
let bg_style = PrimitiveStyleBuilder::new()
|
||||
.fill_color(Rgb565::new(0, 0, 20))
|
||||
.build();
|
||||
Rectangle::new(Point::new(0, 0), Size::new(LCD_W as u32, 40))
|
||||
.into_styled(bg_style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// White text
|
||||
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
||||
Text::new("ESP32-C6", Point::new(10, 28), text_style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
println!("Display ready.");
|
||||
|
||||
loop {}
|
||||
}
|
||||
Reference in New Issue
Block a user