Alternative RMK firmware

This commit is contained in:
Jakub Hlusička 2025-12-24 02:07:21 +01:00
parent b3216575fd
commit 4a3a6db684
14 changed files with 5885 additions and 0 deletions

View file

@ -0,0 +1,19 @@
[target.'cfg(all(any(target_arch = "riscv32", target_arch = "xtensa"), target_os = "none"))']
runner = "espflash flash --monitor"
[build]
target = "xtensa-esp32s3-none-elf"
rustflags = [
# Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.)
# NOTE: May negatively impact performance of produced code
"-C", "force-frame-pointers",
]
[env]
ESP_LOG = "info"
# Xtensa only:
# Needed for nightly, until llvm upstream has support for Rust Xtensa.
[unstable]
build-std = ["alloc", "core"]

3199
firmware2/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

73
firmware2/Cargo.toml Normal file
View file

@ -0,0 +1,73 @@
[package]
name = "acid-firmware"
version = "0.1.0"
authors = ['Jakub "Limeth" Hlusička']
description = "Firmware for the ACID keyboard"
homepage = "https://github.com/haobogu/rmk"
repository = "https://github.com/haobogu/rmk"
edition = "2024"
[features]
no_usb = ["rmk/_no_usb"]
[dependencies]
rmk = { version = "0.8.2", default-features = false, features = [
"esp32s3_ble",
"log",
"storage",
"vial",
] }
embassy-executor = { version = "0.9" }
esp-backtrace = { version = "0.18", features = [
"esp32s3",
"panic-handler",
"println",
] }
esp-hal = { version = "1.0", features = ["esp32s3", "unstable", "psram"] }
esp-storage = { version = "0.8.0", features = ["esp32s3"] }
esp-alloc = { version = "0.9.0" }
esp-println = { version = "0.16.0", features = ["esp32s3", "log-04"] }
esp-radio = { version = "0.17", features = ["esp32s3", "unstable", "ble"] }
esp-rtos = { version = "0.2", features = ["esp32s3", "esp-radio", "embassy"] }
esp-bootloader-esp-idf = { version = "0.4", features = ["esp32s3", "log-04"] }
bt-hci = { version = "0.6" }
rand_core = { version = "0.6", default-features = false }
static_cell = "2"
lazy_static = { version = "1.5.0", features = ["spin_no_std"], default-features = false }
log = "0.4.29"
bitflags = "2.10.0"
paste = "1.0.15"
[build-dependencies]
xz2 = "0.1.7"
json = "0.12"
const-gen = "1.6"
embuild = "0.33"
cc = "1.2.9"
[[bin]]
name = "acid-firmware"
test = false
bench = false
[profile.release-with-debug]
inherits = "release"
debug = true
[profile.dev.package.esp-storage]
opt-level = 3
[profile.dev]
# Rust debug is too slow.
# For debug builds always builds with some optimization
opt-level = "s"
[profile.release]
codegen-units = 1 # LLVM can perform better optimizations using a single thread
debug = 2
debug-assertions = false
incremental = false
lto = 'thin'
opt-level = 3
overflow-checks = false

65
firmware2/README.md Normal file
View file

@ -0,0 +1,65 @@
# esp32s3 BLE example
To run this example, you should have latest Rust in **esp** channel installed. The full instruction of installing esp Rust toolchain can be found [here](https://docs.esp-rs.org/book/installation/index.html).
[`espflash`](https://github.com/esp-rs/espflash) should also be installed:
```
cargo install cargo-espflash espflash
```
After having everything installed, use the following command to run the example:
```
cd examples/use_rust/esp32s3_ble
cargo +esp run --release
```
If everything is good, you'll see the log as the following:
```shell
cargo run --release
Compiling ...
...
...
Finished `release` profile [optimized + debuginfo] target(s) in 11.70s
Running `espflash flash --monitor --port /dev/cu.usbmodem211401 target/xtensa-esp32s3-none-elf/release/rmk-esp32s3`
[2025-04-10T10:01:23Z INFO ] Serial port: '/dev/cu.usbmodem211401'
[2025-04-10T10:01:23Z INFO ] Connecting...
[2025-04-10T10:01:23Z INFO ] Using flash stub
Chip type: esp32s3 (revision v0.1)
Crystal frequency: 40 MHz
Flash size: 4MB
Features: WiFi 6, BT 5
MAC address: 40:4c:ca:5b:c7:dc
App/part. size: 768,944/4,128,768 bytes, 18.62%
[2025-04-10T10:01:23Z INFO ] Segment at address '0x0' has not changed, skipping write
[2025-04-10T10:01:23Z INFO ] Segment at address '0x8000' has not changed, skipping write
[00:00:06] [========================================] 411/411 0x10000 [2025-04-10T10:01:31Z INFO ] Flashing has completed!
```
If espflash reports the following error:
```
Error: espflash::connection_failed
× Error while connecting to device
╰─▶ Serial port not found
```
You should to identify which serial port are connected to your esp board, and use `--port` to specify the used serial port:
```
# Suppose that the esp board are connected to /dev/cu.usbmodem211401
cargo run --release -- --port /dev/cu.usbmodem211401
```
If you want to get some insight of segments of your binary, [`espsegs`](https://github.com/bjoernQ/espsegs) would help:
```
# Install it first
cargo install --git https://github.com/bjoernQ/espsegs
# Check all segments
espsegs target/xtensa-esp32s3-none-elf/release/rmk-esp32s3 --chip esp32s3
```

47
firmware2/build.rs Normal file
View file

@ -0,0 +1,47 @@
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::{env, fs};
use const_gen::*;
use xz2::read::XzEncoder;
fn main() {
// Generate vial config at the root of project
println!("cargo:rerun-if-changed=vial.json");
generate_vial_config();
println!("cargo:rustc-link-arg-bins=-Tlinkall.x");
// Set the extra linker script from defmt
// println!("cargo:rustc-link-arg=-Tdefmt.x");
}
fn generate_vial_config() {
// Generated vial config file
let out_file = Path::new(&env::var_os("OUT_DIR").unwrap()).join("config_generated.rs");
let p = Path::new("vial.json");
let mut content = String::new();
match File::open(p) {
Ok(mut file) => {
file.read_to_string(&mut content).expect("Cannot read vial.json");
}
Err(e) => println!("Cannot find vial.json {p:?}: {e}"),
};
let vial_cfg = json::stringify(json::parse(&content).unwrap());
let mut keyboard_def_compressed: Vec<u8> = Vec::new();
XzEncoder::new(vial_cfg.as_bytes(), 6)
.read_to_end(&mut keyboard_def_compressed)
.unwrap();
let keyboard_id: Vec<u8> = vec![0xB9, 0xBC, 0x09, 0xB2, 0x9D, 0x37, 0x4C, 0xEA];
let const_declarations = [
const_declaration!(pub VIAL_KEYBOARD_DEF = keyboard_def_compressed),
const_declaration!(pub VIAL_KEYBOARD_ID = keyboard_id),
]
.map(|s| "#[allow(clippy::redundant_static_lifetimes)]\n".to_owned() + s.as_str())
.join("\n");
fs::write(out_file, const_declarations).unwrap();
}

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "esp"

19
firmware2/src/keymap.rs Normal file
View file

@ -0,0 +1,19 @@
use rmk::types::action::KeyAction;
use rmk::{a, k, layer, mo};
use crate::matrix::{MATRIX_COLS, MATRIX_ROWS};
pub const NUM_LAYER: usize = 1;
#[rustfmt::skip]
pub const fn get_default_keymap() -> [[[KeyAction; MATRIX_COLS]; MATRIX_ROWS]; NUM_LAYER] {
[
layer!([
[k!(Escape), k!(Kc1), k!(Kc2), k!(Kc3), k!(Kc4), k!(Kc5), k!(Kc6), k!(Kc7), k!(Kc8), k!(Kc9), k!(Kc0), k!(Backspace)],
[k!(Tab), k!(Q), k!(W), k!(E), k!(R), k!(T), k!(Z), k!(U), k!(I), k!(O), k!(P), k!(Delete)],
[k!(LCtrl), k!(A), k!(S), k!(D), k!(F), k!(G), k!(H), k!(J), k!(K), k!(L), k!(Comma), k!(Enter)],
[k!(LShift), k!(Y), k!(X), k!(C), k!(V), k!(B), k!(N), k!(M), a!(No), a!(No), k!(Up), k!(RShift)],
[a!(No), a!(No), k!(LGui), k!(LAlt), k!(TriLayerLower), k!(Space), k!(Space), k!(TriLayerLower), k!(RAlt), k!(Left), k!(Down), k!(Right)]
])
]
}

472
firmware2/src/lcd.rs Normal file
View file

@ -0,0 +1,472 @@
use alloc::{boxed::Box, vec, vec::Vec};
use core::iter::once;
use esp_hal::{
delay::Delay,
gpio::{Flex, Level, Output},
};
use lazy_static::lazy_static;
use crate::st7701s::Command;
fn spi_delay() {
Delay::new().delay_micros(1); // TODO: Async?
}
fn spi_dummy_bit(sck: &mut Output) {
sck.set_low();
spi_delay();
sck.set_high();
spi_delay();
}
fn spi_write_bit(bit: bool, mosi: &mut Flex, sck: &mut Output) {
mosi.set_level(if bit { Level::High } else { Level::Low });
sck.set_low();
spi_delay();
sck.set_high();
spi_delay();
}
fn spi_read_bit(mosi: &mut Flex, sck: &mut Output) -> bool {
sck.set_low();
spi_delay();
sck.set_high();
spi_delay();
mosi.is_high()
}
fn spi_write_bits(bits: impl Iterator<Item = bool>, mosi: &mut Flex, sck: &mut Output) {
for bit in bits {
spi_write_bit(bit, mosi, sck);
}
}
fn spi_write_word(is_param: bool, data: u8, mosi: &mut Flex, sck: &mut Output) {
assert!(sck.is_set_high());
spi_write_bits(
once(is_param).chain((0..8).map(|i| (data >> i) & 1 != 0).rev()),
mosi,
sck,
);
}
pub fn spi_write(
command: u8,
args: impl IntoIterator<Item = u8>,
mosi: &mut Flex,
sck: &mut Output,
cs: &mut Output,
) {
cs.set_low();
spi_write_word(false, command, mosi, sck);
for arg in args {
spi_write_word(true, arg, mosi, sck);
}
cs.set_high();
}
fn spi_read(
command: u8,
dummy_cycle: bool,
mosi: &mut Flex,
sck: &mut Output,
cs: &mut Output,
output_buffer: &mut [u8],
) {
output_buffer.fill(0);
cs.set_low();
spi_write_word(false, command, mosi, sck);
mosi.set_output_enable(false);
mosi.set_input_enable(true);
if dummy_cycle {
spi_dummy_bit(sck);
}
for output_byte in output_buffer {
for i in (0..8).rev() {
if spi_read_bit(mosi, sck) {
*output_byte |= 1 << i;
}
}
}
mosi.set_input_enable(false);
mosi.set_output_enable(true);
cs.set_high();
}
use crate::st7701s::*;
lazy_static! {
pub static ref INIT_SEQUENCE_COMMANDS: Vec<(Vec<Box<dyn Command + Send + Sync>>, u64)> = vec![
(vec![
Box::new(CmdCn2bkxsel(
CmdCn2bkxselArg0::new(),
CmdCn2bkxselArg1::new(),
CmdCn2bkxselArg2::new(),
CmdCn2bkxselArg3::new(),
CmdCn2bkxselArg4::new().with_bksel(0x3).with_cn2(true),
)),
Box::new(CustomCommand {
address: 0xEF,
args: &[0x08],
}),
Box::new(CmdCn2bkxsel(
CmdCn2bkxselArg0::new(),
CmdCn2bkxselArg1::new(),
CmdCn2bkxselArg2::new(),
CmdCn2bkxselArg3::new(),
CmdCn2bkxselArg4::new().with_bksel(0x0).with_cn2(true),
)),
Box::new(CmdLneset(
CmdLnesetArg0::new().with_bar(119).with_lde_en(false),
CmdLnesetArg1::new().with_line_delta(0),
)),
Box::new(CmdPorctrl(
CmdPorctrlArg0::new().with_vbp(17),
CmdPorctrlArg1::new().with_vfp(12),
)),
Box::new(CmdInvsel(
CmdInvselArg0::new().with_nlinv(0b111),
CmdInvselArg1::new().with_rtni(2),
)),
Box::new(CustomCommand {
address: 0xCC,
args: &[0x30],
}),
Box::new(CmdPvgamctrl(
CmdPvgamctrlArg0::new().with_vc0p(6).with_aj0p(0),
CmdPvgamctrlArg1::new().with_vc4p(15).with_aj1p(3),
CmdPvgamctrlArg2::new().with_vc8p(14).with_aj2p(0),
CmdPvgamctrlArg3::new().with_vc16p(12),
CmdPvgamctrlArg4::new().with_vc24p(15).with_aj3p(0),
CmdPvgamctrlArg5::new().with_vc52p(3),
CmdPvgamctrlArg6::new().with_vc80p(0),
CmdPvgamctrlArg7::new().with_vc108p(10),
CmdPvgamctrlArg8::new().with_vc147p(7),
CmdPvgamctrlArg9::new().with_vc175p(27),
CmdPvgamctrlArg10::new().with_vc203p(3),
CmdPvgamctrlArg11::new().with_vc231p(18).with_aj4p(0),
CmdPvgamctrlArg12::new().with_vc239p(16),
CmdPvgamctrlArg13::new().with_vc247p(37).with_aj5p(0),
CmdPvgamctrlArg14::new().with_vc251p(54).with_aj6p(0),
CmdPvgamctrlArg15::new().with_vc255p(30).with_aj7p(0),
)),
Box::new(CmdNvgamctrl(
CmdNvgamctrlArg0::new().with_vc0n(12).with_aj0n(0),
CmdNvgamctrlArg1::new().with_vc4n(14).with_aj1n(3),
CmdNvgamctrlArg2::new().with_vc8n(18).with_aj2n(0),
CmdNvgamctrlArg3::new().with_vc16n(12),
CmdNvgamctrlArg4::new().with_vc24n(14).with_aj3n(0),
CmdNvgamctrlArg5::new().with_vc52n(6),
CmdNvgamctrlArg6::new().with_vc80n(3),
CmdNvgamctrlArg7::new().with_vc108n(6),
CmdNvgamctrlArg8::new().with_vc147n(8),
CmdNvgamctrlArg9::new().with_vc175n(35),
CmdNvgamctrlArg10::new().with_vc203n(6),
CmdNvgamctrlArg11::new().with_vc231n(18).with_aj4n(0),
CmdNvgamctrlArg12::new().with_vc239n(16),
CmdNvgamctrlArg13::new().with_vc247n(48).with_aj5n(0),
CmdNvgamctrlArg14::new().with_vc251n(47).with_aj6n(0),
CmdNvgamctrlArg15::new().with_vc255n(31).with_aj7n(0),
)),
Box::new(CmdCn2bkxsel(
CmdCn2bkxselArg0::new(),
CmdCn2bkxselArg1::new(),
CmdCn2bkxselArg2::new(),
CmdCn2bkxselArg3::new(),
CmdCn2bkxselArg4::new().with_bksel(0x1).with_cn2(true),
)),
Box::new(CmdVrhs(
CmdVrhsArg0::new().with_vrha(115),
)),
Box::new(CmdVcoms(
CmdVcomsArg0::new().with_vcom(124),
)),
Box::new(CmdVghss(
// The first bit is set to 1 in the original init code, but not here.
CmdVghssArg0::new().with_vghss(0x3), // 13 V
)),
Box::new(CmdTescmd(
CmdTescmdArg0::new(),
)),
Box::new(CmdVgls(
CmdVglsArg0::new().with_vgls(0x9) // -10.17 V
)),
Box::new(CmdPwctrl1(
CmdPwctrl1Arg0::new()
.with_apos(0x3) // Max
.with_apis(0x1) // Min
.with_ap(0x2) // Middle
)),
Box::new(CmdPwctrl2(
CmdPwctrl2Arg0::new()
.with_avcl(0x3) // -5 V
.with_avdd(0x3) // 6.8 V
)),
Box::new(CmdPwctrl3(
CmdPwctrl3Arg0::new()
.with_svno_pum(0) // Cell setting 4
.with_svpo_pum(0x1) // Cell setting 5
)),
Box::new(CmdPclks2(
CmdPclks2Arg0::new().with_sbstcks(0x3)
)),
Box::new(CmdPdr1(
CmdPdr1Arg0::new().with_t2d(8) // 1.6 us
)),
Box::new(CmdPdr2(
CmdPdr2Arg0::new().with_t3d(8) // 6.4 us
)),
Box::new(CmdMipiset1(
CmdMipiset1Arg0::new().with_err_sel(0).with_eotp_en(true),
)),
Box::new(CustomCommand {
address: 0xE0,
args: &[
0x00,
0x00,
0x02,
0x00,
0x00,
0x0C,
]
}),
Box::new(CustomCommand {
address: 0xE1,
args: &[
0x05,
0x96,
0x07,
0x96,
0x06,
0x96,
0x08,
0x96,
0x00,
0x44,
0x44,
]
}),
Box::new(CustomCommand {
address: 0xE2,
args: &[
0x00,
0x00,
0x03,
0x03,
0x00,
0x00,
0x02,
0x00,
0x00,
0x00,
0x02,
0x00,
]
}),
Box::new(CustomCommand {
address: 0xE3,
args: &[
0x00,
0x00,
0x33,
0x33,
]
}),
Box::new(CustomCommand {
address: 0xE4,
args: &[
0x44,
0x44,
]
}),
Box::new(CustomCommand {
address: 0xE5,
args: &[
0x0D,
0xD4,
0x28,
0x8C,
0x0F,
0xD6,
0x28,
0x8C,
0x09,
0xD0,
0x28,
0x8C,
0x0B,
0xD2,
0x28,
0x8C,
]
}),
Box::new(CustomCommand {
address: 0xE6,
args: &[
0x00,
0x00,
0x33,
0x33,
]
}),
Box::new(CustomCommand {
address: 0xE7,
args: &[
0x44,
0x44,
]
}),
Box::new(CustomCommand {
address: 0xE8,
args: &[
0x0E,
0xD5,
0x28,
0x8C,
0x10,
0xD7,
0x28,
0x8C,
0x0A,
0xD1,
0x28,
0x8C,
0x0C,
0xD3,
0x28,
0x8C,
]
}),
Box::new(CustomCommand {
address: 0xEB,
args: &[
0x00,
0x01,
0xE4,
0xE4,
0x44,
0x00,
]
}),
Box::new(CustomCommand {
address: 0xED,
args: &[
0xF3,
0xC1,
0xBA,
0x0F,
0x66,
0x77,
0x44,
0x55,
0x55,
0x44,
0x77,
0x66,
0xF0,
0xAB,
0x1C,
0x3F,
]
}),
Box::new(CustomCommand {
address: 0xEF,
args: &[
0x10,
0x0D,
0x04,
0x08,
0x3F,
0x1F,
]
}),
Box::new(CmdCn2bkxsel(
CmdCn2bkxselArg0::new(),
CmdCn2bkxselArg1::new(),
CmdCn2bkxselArg2::new(),
CmdCn2bkxselArg3::new(),
CmdCn2bkxselArg4::new().with_bksel(0x3).with_cn2(true),
)),
Box::new(CustomCommand {
address: 0xE8,
args: &[
0x00,
0x0E,
],
}),
Box::new(CmdSlpout()),
], 120),
(vec![
Box::new(CustomCommand {
address: 0xE8,
args: &[
0x00,
0x0C,
],
}),
], 10),
(vec![
Box::new(CustomCommand {
address: 0xE8,
args: &[
0x40,
0x00,
],
}),
Box::new(CmdCn2bkxsel(
CmdCn2bkxselArg0::new(),
CmdCn2bkxselArg1::new(),
CmdCn2bkxselArg2::new(),
CmdCn2bkxselArg3::new(),
CmdCn2bkxselArg4::new().with_bksel(0).with_cn2(false),
)),
Box::new(CmdMadctl(
CmdMadctlArg0::new().with_bgr(false).with_ml(false),
)),
Box::new(CmdColmod(
CmdColmodArg0::new().with_vipf(6) // 18-bit pixel
)),
Box::new(CmdDispon()),
], 20),
// (vec![
// Box::new(CmdCn2bkxsel(
// CmdCn2bkxselArg0::new(),
// CmdCn2bkxselArg1::new(),
// CmdCn2bkxselArg2::new(),
// CmdCn2bkxselArg3::new(),
// CmdCn2bkxselArg4::new().with_bksel(0).with_cn2(true),
// )),
// Box::new(CmdPorctrl(
// CmdPorctrlArg0::new().with_vbp(0xFF),
// CmdPorctrlArg1::new().with_vfp(0x0),
// )),
// // Box::new(CmdRgbctrl(
// // CmdRgbctrlArg0::new()
// // .with_ep(false)
// // .with_dp(false)
// // .with_hsp(false)
// // .with_vsp(false)
// // .with_de_hv(true),
// // CmdRgbctrlArg1::new()
// // .with_hbp_hvrgb(16),
// // CmdRgbctrlArg2::new()
// // .with_vbp_hvrgb(8),
// // )),
// Box::new(CmdCn2bkxsel(
// CmdCn2bkxselArg0::new(),
// CmdCn2bkxselArg1::new(),
// CmdCn2bkxselArg2::new(),
// CmdCn2bkxselArg3::new(),
// CmdCn2bkxselArg4::new().with_bksel(0).with_cn2(false),
// )),
// ], 20),
];
}

12
firmware2/src/macros.rs Normal file
View file

@ -0,0 +1,12 @@
macro_rules! config_matrix_pins_esp {
(peripherals: $p:ident, input: [$($in_pin:ident), *], output: [$($out_pin:ident), +]) => {
{
let mut output_pins = [$(Output::new($p.$out_pin, Level::Low, OutputConfig::default())), +];
let input_pins = [$(Input::new($p.$in_pin, InputConfig::default().with_pull(Pull::Down))), +];
output_pins.iter_mut().for_each(|p| {
let _ = p.set_low();
});
(input_pins, output_pins)
}
};
}

463
firmware2/src/main.rs Normal file
View file

@ -0,0 +1,463 @@
#![no_std]
#![no_main]
#![feature(macro_metavar_expr)]
extern crate alloc;
mod keymap;
#[macro_use]
mod macros;
mod lcd;
mod matrix;
mod st7701s;
mod vial;
use core::alloc::Layout;
use core::ptr::addr_of_mut;
use alloc::boxed::Box;
use alloc::vec;
use bt_hci::controller::ExternalController;
use embassy_executor::Spawner;
use esp_alloc::{HeapRegion, MemoryCapability};
use esp_hal::clock::{CpuClock, RtcClock};
use esp_hal::delay::Delay;
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
use esp_hal::i2c::master::{I2c, I2cAddress};
use esp_hal::lcd_cam::LcdCam;
use esp_hal::lcd_cam::lcd::dpi::{Dpi, Format, FrameTiming};
use esp_hal::lcd_cam::lcd::{ClockMode, Phase, Polarity};
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
use esp_hal::otg_fs::Usb;
use esp_hal::otg_fs::asynch::{Config, Driver};
// use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
use esp_hal::rng::TrngSource;
use esp_hal::rtc_cntl::sleep::RtcSleepConfig;
use esp_hal::rtc_cntl::{Rtc, sleep};
use esp_hal::time::Rate;
use esp_hal::timer::systimer::SystemTimer;
use esp_hal::timer::timg::TimerGroup;
use esp_radio::Controller;
use esp_radio::ble::controller::BleConnector;
use esp_storage::FlashStorage;
use log::{LevelFilter, info};
use rmk::ble::build_ble_stack;
use rmk::channel::EVENT_CHANNEL;
use rmk::config::{BehaviorConfig, PositionalConfig, RmkConfig, StorageConfig, VialConfig};
use rmk::debounce::default_debouncer::DefaultDebouncer;
use rmk::futures::future::join3;
use rmk::input_device::Runnable;
use rmk::keyboard::Keyboard;
use rmk::matrix::Matrix;
use rmk::storage::async_flash_wrapper;
use rmk::{HostResources, initialize_keymap_and_storage, run_devices, run_rmk};
use static_cell::StaticCell;
use {esp_alloc as _, esp_backtrace as _};
use crate::keymap::*;
use crate::lcd::spi_write;
use crate::matrix::IoeMatrix;
use crate::vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID};
// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();
#[esp_rtos::main]
async fn main(spawner: Spawner) {
esp_println::logger::init_logger_from_env();
info!("Logger initialized!");
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
// .with_psram(PsramConfig {
// size: PsramSize::AutoDetect,
// core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m),
// flash_frequency: FlashFreq::default(),
// ram_frequency: SpiRamFreq::Freq80m,
// });
let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config);
info!("System initialized!");
// Use the internal DRAM as the heap.
esp_alloc::heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 64 * 1024);
info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());
// let timer0 = SystemTimer::new(peripherals.SYSTIMER);
// esp_hal_embassy::init(timer0.alarm0);
// info!("Embassy initialized!");
// // Initialize the PSRAM allocator.
// {
// let (psram_offset, psram_size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM);
// unsafe {
// PSRAM_ALLOCATOR.add_region(HeapRegion::new(
// psram_offset,
// psram_size,
// MemoryCapability::External.into(),
// ));
// }
// }
// const BUFFER_LEN: usize = core::mem::size_of::<u16>()
// * (360 + /* TODO: Figure out why more bytes are needed: */ 8)
// * 960;
// // Note: We just leak this buffer.
// let buffer_ptr = unsafe {
// // ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM
// // have extra alignment requirements. The address and size of the buffer pointed to by each
// // descriptor must be a multiple of the cache line (block) size. This is 32 bytes on
// // ESP32-S3.
// PSRAM_ALLOCATOR.alloc_caps(
// MemoryCapability::External.into(),
// Layout::from_size_align(BUFFER_LEN, 32).unwrap(),
// )
// };
// let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_ptr, BUFFER_LEN) };
// let burst_config: BurstConfig = ExternalBurstConfig::Size16.into();
// info!(
// "PSRAM SPI burst config: max_compatible_chunk_size={}",
// burst_config.max_compatible_chunk_size()
// );
// let dma_buf_descs_len =
// esp_hal::dma::descriptor_count(BUFFER_LEN, burst_config.max_compatible_chunk_size(), false);
// // Descriptors are initialized by `DmaTxBuf::new`.
// let dma_buf_descs = Box::leak(vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice());
// let mut dma_buf = DmaTxBuf::new(dma_buf_descs, buffer).unwrap();
// // let mut dma_buf = dma_tx_buffer!(BUFFER_LEN).unwrap();
// TODO: Spawn some tasks
let _ = spawner;
// Enable pull-up on GPIO0 to prevent booting into download mode.
let gpio0 = Output::new(
peripherals.GPIO0,
Level::High,
OutputConfig::default().with_pull(Pull::Up),
);
// Enable LDO2
let _ = Output::new(peripherals.GPIO17, Level::High, OutputConfig::default());
// Enable antenna
let _ = Output::new(peripherals.GPIO11, Level::Low, OutputConfig::default());
// TODO: Use PWM to control the pwm_pin.
let mut _pwm = McPwm::new(peripherals.MCPWM0, PeripheralClockConfig::with_prescaler(1));
let mut _pwm_pin = Output::new(peripherals.GPIO21, Level::High, OutputConfig::default());
let mut matrix_interrupt = Input::new(
peripherals.GPIO7,
InputConfig::default().with_pull(Pull::Up),
);
// esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
#[cfg(target_arch = "riscv32")]
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start(
timg0.timer0,
#[cfg(target_arch = "riscv32")]
software_interrupt.software_interrupt0,
);
let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
let mut rng = esp_hal::rng::Trng::try_new().unwrap();
static RADIO: StaticCell<Controller<'static>> = StaticCell::new();
let radio = RADIO.init(esp_radio::init().unwrap());
let bluetooth = peripherals.BT;
let connector = BleConnector::new(radio, bluetooth, Default::default()).unwrap();
let controller: ExternalController<_, 20> = ExternalController::new(connector);
let central_addr = [0x18, 0xe2, 0x21, 0x80, 0xc0, 0xc7];
let mut host_resources = HostResources::new();
let stack = build_ble_stack(controller, central_addr, &mut rng, &mut host_resources).await;
// // Initialize USB
#[cfg(not(feature = "no_usb"))]
let usb_driver = {
static mut EP_MEMORY: [u8; 1024] = [0; 1024];
let usb = Usb::new(peripherals.USB0, peripherals.GPIO20, peripherals.GPIO19);
// Create the driver, from the HAL.
let config = Config::default();
Driver::new(usb, unsafe { &mut *addr_of_mut!(EP_MEMORY) }, config)
};
// Initialize the flash
let flash = FlashStorage::new(peripherals.FLASH);
let flash = async_flash_wrapper(flash);
let mut sck = Output::new(peripherals.GPIO36, Level::High, OutputConfig::default());
let mut mosi = Flex::new(peripherals.GPIO35);
let mut cs = Output::new(peripherals.GPIO6, Level::High, OutputConfig::default());
mosi.set_input_enable(false);
mosi.set_output_enable(true);
// info!("init sequence writing");
for (subsequence, delay_ms) in &*lcd::INIT_SEQUENCE_COMMANDS {
for command in subsequence {
spi_write(
command.address(),
command.args_iter().copied(),
&mut mosi,
&mut sck,
&mut cs,
);
// info!("COMM 0x{:02X}", command.address());
// for arg in command.args_iter() {
// info!("DATA 0x{arg:02X}");
// }
}
Delay::new().delay_millis(*delay_ms as u32); // TODO: async?
// Timer::after_millis(*delay_ms).await
}
// info!("init sequence written");
let mut lcd = LcdCam::new(peripherals.LCD_CAM).into_async().lcd;
let lcd_config = esp_hal::lcd_cam::lcd::dpi::Config::default()
// Internal memory can use the full 16 MHz, but when external PSRAM is used, it cannot keep up with the display.
// For that reason, we choose the highest value for which it doesn't glitch by showing black
// stripes on the screen.
//
// There are three knobs you can turn to improve the bandwidth situation.
// - increase psram frequency
// - decrease the peripheral's frequency
// - prevent flash from being used whilst your program is running. (There's a PR to make
// this easy to do)
// https://github.com/esp-rs/esp-hal/pull/3024
.with_frequency(Rate::from_mhz(11)) // From Adafruit
.with_clock_mode(ClockMode {
polarity: Polarity::IdleLow, // From Adafruit
phase: Phase::ShiftHigh, // From Adafruit
})
.with_format(Format {
enable_2byte_mode: true,
..Default::default()
})
.with_timing({
// Adafruit's config for this LCD:
// https://github.com/adafruit/Adafruit_CircuitPython_Qualia/blob/742d336e05e6a4d8bdaa46e15bbf60c9f30d2eba/adafruit_qualia/displays/bar240x960.py#L81-L97
// https://github.com/adafruit/Adafruit_CircuitPython_Qualia/blob/742d336e05e6a4d8bdaa46e15bbf60c9f30d2eba/adafruit_qualia/displays/__init__.py#L59-L62
// CircuitPython code handling Adafruit's config
// https://github.com/adafruit/circuitpython/blob/97c6617817e95b1f6aa2ce458778aaa8371de39b/ports/espressif/common-hal/dotclockframebuffer/DotClockFramebuffer.c#L63
// ESP-IDF peripheral configuration code:
// https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/esp_lcd/rgb/esp_lcd_panel_rgb.c#L556
// Espressif's docs:
// https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32s3/api-reference/peripherals/lcd/rgb_lcd.html#structures
// TODO: Investigate PORCTRL instruction in datasheet of ST7701
let horizontal_resolution: usize = 240;
let vertical_resolution = 960;
let overscan_left = 120;
let vsync_width = 8;
let hsync_width = 8;
let horizontal_blank_front_porch = 20;
let horizontal_blank_back_porch = 20;
let vertical_blank_front_porch = 20;
let vertical_blank_back_porch = 20;
let hsync_position = 0;
let horizontal_active_width = (horizontal_resolution + overscan_left).div_ceil(16) * 16; // Round up to a multiple of 16.
let vertical_active_height = vertical_resolution;
FrameTiming {
horizontal_total_width: hsync_width
+ horizontal_blank_back_porch
+ horizontal_active_width
+ horizontal_blank_front_porch,
vertical_total_height: vsync_width
+ vertical_blank_back_porch
+ vertical_active_height
+ vertical_blank_front_porch,
horizontal_blank_front_porch: horizontal_blank_front_porch + hsync_width,
vertical_blank_front_porch: vertical_blank_front_porch + vsync_width,
horizontal_active_width,
vertical_active_height,
vsync_width,
hsync_width,
hsync_position,
}
})
.with_hsync_idle_level(Level::High)
.with_vsync_idle_level(Level::High)
.with_de_idle_level(Level::Low);
let mut dpi = Dpi::new(lcd, peripherals.DMA_CH2, lcd_config)
.unwrap()
.with_de(peripherals.GPIO37)
.with_pclk(peripherals.GPIO34)
.with_hsync(peripherals.GPIO44)
.with_vsync(peripherals.GPIO43)
// Blue
.with_data0(peripherals.GPIO38)
.with_data1(peripherals.GPIO39)
.with_data2(peripherals.GPIO40)
.with_data3(peripherals.GPIO41)
.with_data4(peripherals.GPIO42)
// Green
.with_data5(peripherals.GPIO5)
.with_data6(peripherals.GPIO12)
.with_data7(peripherals.GPIO13)
.with_data8(peripherals.GPIO14)
.with_data9(peripherals.GPIO15)
.with_data10(peripherals.GPIO16)
// Red
.with_data11(gpio0)
.with_data12(peripherals.GPIO1)
.with_data13(peripherals.GPIO2)
.with_data14(peripherals.GPIO3)
.with_data15(peripherals.GPIO4);
// RMK config
let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]);
let storage_config = StorageConfig {
start_addr: 0x3f0000,
num_sectors: 16,
..Default::default()
};
let rmk_config = RmkConfig {
vial_config,
storage_config,
..Default::default()
};
// Initialze keyboard stuffs
// Initialize the storage and keymap
let mut default_keymap = keymap::get_default_keymap();
let mut behavior_config = BehaviorConfig::default();
let mut per_key_config = PositionalConfig::default();
let (keymap, mut storage) = initialize_keymap_and_storage(
&mut default_keymap,
flash,
&storage_config,
&mut behavior_config,
&mut per_key_config,
)
.await;
// Initialize the matrix and keyboard
const I2C_ADDR_MATRIX_LEFT: I2cAddress = I2cAddress::SevenBit(0b0100000);
const I2C_ADDR_MATRIX_RIGHT: I2cAddress = I2cAddress::SevenBit(0b0100001);
let i2c = I2c::new(peripherals.I2C0, Default::default())
.unwrap()
.with_sda(peripherals.GPIO8)
.with_scl(peripherals.GPIO9);
let mut matrix = IoeMatrix::new(
i2c.into_async(),
DefaultDebouncer::new(),
[I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT],
)
.await;
let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller
join3(
run_devices! (
(matrix) => EVENT_CHANNEL,
),
keyboard.run(), // Keyboard is special
run_rmk(
&keymap,
#[cfg(not(feature = "no_usb"))]
usb_driver,
&stack,
&mut storage,
rmk_config,
),
)
.await;
}
// #[esp_rtos::main]
// async fn main(_s: Spawner) {
// // Initialize the peripherals and bluetooth controller
// esp_println::logger::init_logger(LevelFilter::max());
// let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max()));
// esp_alloc::heap_allocator!(size: 72 * 1024);
// let timg0 = TimerGroup::new(peripherals.TIMG0);
// #[cfg(target_arch = "riscv32")]
// let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
// esp_rtos::start(
// timg0.timer0,
// #[cfg(target_arch = "riscv32")]
// software_interrupt.software_interrupt0,
// );
// let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
// let mut rng = esp_hal::rng::Trng::try_new().unwrap();
// static RADIO: StaticCell<Controller<'static>> = StaticCell::new();
// let radio = RADIO.init(esp_radio::init().unwrap());
// let bluetooth = peripherals.BT;
// let connector = BleConnector::new(radio, bluetooth, Default::default()).unwrap();
// let controller: ExternalController<_, 20> = ExternalController::new(connector);
// let central_addr = [0x18, 0xe2, 0x21, 0x80, 0xc0, 0xc7];
// let mut host_resources = HostResources::new();
// let stack = build_ble_stack(controller, central_addr, &mut rng, &mut host_resources).await;
// // Initialize the flash
// let flash = FlashStorage::new(peripherals.FLASH);
// let flash = async_flash_wrapper(flash);
// // Initialize the IO pins
// let (row_pins, col_pins) = config_matrix_pins_esp!(peripherals: peripherals, input: [GPIO6, GPIO7, GPIO21, GPIO35], output: [GPIO3, GPIO4, GPIO5]);
// // RMK config
// let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]);
// let storage_config = StorageConfig {
// start_addr: 0x3f0000,
// num_sectors: 16,
// ..Default::default()
// };
// let rmk_config = RmkConfig {
// vial_config,
// storage_config,
// ..Default::default()
// };
// // Initialze keyboard stuffs
// // Initialize the storage and keymap
// let mut default_keymap = keymap::get_default_keymap();
// let mut behavior_config = BehaviorConfig::default();
// let mut per_key_config = PositionalConfig::default();
// let (keymap, mut storage) = initialize_keymap_and_storage(
// &mut default_keymap,
// flash,
// &storage_config,
// &mut behavior_config,
// &mut per_key_config,
// )
// .await;
// // Initialize the matrix and keyboard
// let debouncer = DefaultDebouncer::new();
// let mut i2c = I2c::new(
// peripherals.I2C0,
// esp_hal::i2c::master::Config::default().with_frequency(Rate::from_khz(400)),
// )
// .unwrap()
// .with_sda(peripherals.GPIO8)
// .with_scl(peripherals.GPIO9);
// const I2C_ADDR_MATRIX_LEFT: I2cAddress = I2cAddress::SevenBit(0b0100000);
// const I2C_ADDR_MATRIX_RIGHT: I2cAddress = I2cAddress::SevenBit(0b0100001);
// let mut matrix = IoeMatrix::new(
// i2c.into_async(),
// debouncer,
// [I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT],
// )
// .await;
// // let mut matrix = Matrix::<_, _, _, ROW, COL, true>::new(row_pins, col_pins, debouncer);
// // let mut matrix = rmk::matrix::TestMatrix::<ROW, COL>::new();
// let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller
// join3(
// run_devices! (
// (matrix) => EVENT_CHANNEL,
// ),
// keyboard.run(), // Keyboard is special
// run_rmk(&keymap, &stack, &mut storage, rmk_config),
// )
// .await;
// }

215
firmware2/src/matrix.rs Normal file
View file

@ -0,0 +1,215 @@
use esp_hal::{
Async,
i2c::master::{BusTimeout, I2c, I2cAddress, SoftwareTimeout},
time::Rate,
};
use log::info;
use rmk::{
debounce::{DebounceState, DebouncerTrait},
event::{Event, KeyboardEvent},
input_device::InputDevice,
matrix::{KeyState, MatrixTrait},
};
pub struct RaiiGuard<F: FnOnce()> {
on_drop: Option<F>,
}
impl<F: FnOnce()> RaiiGuard<F> {
pub fn new(on_drop: F) -> Self {
Self {
on_drop: Some(on_drop),
}
}
}
impl<F: FnOnce()> Drop for RaiiGuard<F> {
fn drop(&mut self) {
if let Some(on_drop) = self.on_drop.take() {
(on_drop)();
}
}
}
pub const MATRIX_ROWS: usize = 5;
pub const MATRIX_COLS: usize = 12;
pub const MATRIX_AREA: usize = MATRIX_ROWS * MATRIX_COLS;
/// IO Expander Matrix
pub struct IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
i2c: I2c<'static, Async>,
/// IO expander addresses.
ioe_addresses: [I2cAddress; 2],
debouncer: D,
/// States of key switches as detected by IO expanders.
input_states: [[bool; MATRIX_ROWS]; MATRIX_COLS],
/// Debounced key states.
key_states: [[KeyState; MATRIX_ROWS]; MATRIX_COLS],
/// Index of the current key switch being processed: row * columns + column
scan_pos: usize,
/// Re-scan needed flag
#[cfg(feature = "async_matrix")]
rescan_needed: bool,
}
impl<D> IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
pub async fn new(
mut i2c: I2c<'static, Async>,
debouncer: D,
ioe_addresses: [I2cAddress; 2],
) -> Self {
i2c.apply_config(
&esp_hal::i2c::master::Config::default()
.with_frequency(Rate::from_khz(400))
.with_timeout(BusTimeout::Disabled)
.with_software_timeout(SoftwareTimeout::None),
)
.unwrap();
for addr in ioe_addresses {
i2c.write_async(addr, &[0b10010000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
.await
.unwrap();
}
Self {
i2c,
ioe_addresses,
debouncer,
input_states: Default::default(),
key_states: Default::default(),
scan_pos: 0,
#[cfg(feature = "async_matrix")]
rescan_needed: false,
}
}
}
impl<D> InputDevice for IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
async fn read_event(&mut self) -> Event {
loop {
if self.scan_pos == 0 {
// Load data from IO expanders.
let mut input_register_banks = [0_u8; 10];
loop {
let Err(esp_hal::i2c::master::Error::Timeout) = self
.i2c
.write_read_async(
self.ioe_addresses[0],
&[0x80],
&mut input_register_banks[..5],
)
.await
else {
break;
};
info!("I2C timed out when reading the left IO expander.");
}
loop {
let Err(esp_hal::i2c::master::Error::Timeout) = self
.i2c
.write_read_async(
self.ioe_addresses[1],
&[0x80],
&mut input_register_banks[5..],
)
.await
else {
break;
};
info!("I2C timed out when reading the right IO expander.");
}
for (row_idx, (left, right)) in (0..).zip(core::iter::zip(
&input_register_banks[..5],
&input_register_banks[5..],
)) {
for col_idx_half in 0..(MATRIX_COLS / 2) {
self.input_states[col_idx_half][row_idx] = left & (1 << col_idx_half) != 0;
self.input_states[col_idx_half + MATRIX_COLS / 2][row_idx] =
right & (1 << col_idx_half) != 0;
}
}
// info!("rows: {rows:x?}")
}
let (row_idx, col_idx) = (self.scan_pos / MATRIX_COLS, self.scan_pos % MATRIX_COLS);
let on_iteration_end = RaiiGuard::new(|| {
// Process the next key switch.
self.scan_pos = (self.scan_pos + 1) % MATRIX_AREA;
});
let debounce_state = self.debouncer.detect_change_with_debounce(
row_idx,
col_idx,
self.input_states[col_idx][row_idx],
&self.key_states[col_idx][row_idx],
);
if let DebounceState::Debounced = debounce_state {
self.key_states[col_idx][row_idx].toggle_pressed();
#[cfg(feature = "async_matrix")]
{
self.rescan_needed = true;
}
return Event::Key(KeyboardEvent::key(
row_idx as u8,
col_idx as u8,
self.key_states[col_idx][row_idx].pressed,
));
}
// // Process the next key switch.
// self.scan_pos = (self.scan_pos + 1) % MATRIX_AREA;
// If there's key still pressed, always refresh the self.scan_start
#[cfg(feature = "async_matrix")]
if self.key_states[col_idx][row_idx].pressed {
self.rescan_needed = true;
}
#[cfg(feature = "async_matrix")]
{
if !self.rescan_needed {
self.wait_for_key().await;
}
self.rescan_needed = false;
}
drop(on_iteration_end);
}
}
}
impl<D> MatrixTrait<MATRIX_ROWS, MATRIX_COLS> for IoeMatrix<D>
where
D: DebouncerTrait<MATRIX_ROWS, MATRIX_COLS>,
{
#[cfg(feature = "async_matrix")]
async fn wait_for_key(&mut self) {
// TODO
// First, set all output pins to high
for out in self.get_output_pins_mut().iter_mut() {
out.set_high().ok();
}
// Wait for any key press
self.wait_input_pins().await;
// Set all output pins back to low
for out in self.get_output_pins_mut().iter_mut() {
out.set_low().ok();
}
}
}

1212
firmware2/src/st7701s.rs Normal file

File diff suppressed because it is too large Load diff

3
firmware2/src/vial.rs Normal file
View file

@ -0,0 +1,3 @@
// Vial config is automatically generated by `build.rs`, according to `vial.json`
// Please put `vial.json` at your project's root
include!(concat!(env!("OUT_DIR"), "/config_generated.rs"));

84
firmware2/vial.json Normal file
View file

@ -0,0 +1,84 @@
{
"name": "ACID Keyboard",
"vendorId": "0x1209",
"productId": "0x9400",
"lighting": "none",
"matrix": {
"rows": 5,
"cols": 12
},
"layouts": {
"keymap": [
[
"0,0",
"0,1",
"0,2",
"0,3",
"0,4",
"0,5",
"0,6",
"0,7",
"0,8",
"0,9",
"0,10",
"0,11"
],
[
"1,0",
"1,1",
"1,2",
"1,3",
"1,4",
"1,5",
"1,6",
"1,7",
"1,8",
"1,9",
"1,10",
"1,11"
],
[
"2,0",
"2,1",
"2,2",
"2,3",
"2,4",
"2,5",
"2,6",
"2,7",
"2,8",
"2,9",
"2,10",
"2,11"
],
[
"3,0",
"3,1",
"3,2",
"3,3",
"3,4",
"3,5",
"3,6",
"3,7",
"3,8",
"3,9",
"3,10",
"3,11"
],
[
"4,0",
"4,1",
"4,2",
"4,3",
"4,4",
"4,5",
"4,6",
"4,7",
"4,8",
"4,9",
"4,10",
"4,11"
]
]
}
}