Alternative RMK firmware
This commit is contained in:
parent
b3216575fd
commit
4a3a6db684
19
firmware2/.cargo/config.toml
Normal file
19
firmware2/.cargo/config.toml
Normal 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
3199
firmware2/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
73
firmware2/Cargo.toml
Normal file
73
firmware2/Cargo.toml
Normal 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
65
firmware2/README.md
Normal 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
47
firmware2/build.rs
Normal 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();
|
||||
}
|
||||
2
firmware2/rust-toolchain.toml
Normal file
2
firmware2/rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "esp"
|
||||
19
firmware2/src/keymap.rs
Normal file
19
firmware2/src/keymap.rs
Normal 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
472
firmware2/src/lcd.rs
Normal 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
12
firmware2/src/macros.rs
Normal 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
463
firmware2/src/main.rs
Normal 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
215
firmware2/src/matrix.rs
Normal 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
1212
firmware2/src/st7701s.rs
Normal file
File diff suppressed because it is too large
Load diff
3
firmware2/src/vial.rs
Normal file
3
firmware2/src/vial.rs
Normal 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
84
firmware2/vial.json
Normal 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"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue