Compare commits

..

16 Commits

Author SHA1 Message Date
b76d026f20 Fast update done 2024-10-14 12:36:50 +02:00
91f3382b15 Trying to make partial update 2024-10-04 16:34:48 +02:00
a340c9083a WOrking on partial refresh 2024-10-03 16:25:41 +02:00
021a611df2 Working maze 2024-10-03 10:43:04 +02:00
1b4dab55c6 Fix binary map visitor 2024-10-03 07:11:40 +02:00
bb350d3ec7 Correct Test 2024-10-03 06:10:27 +02:00
2e2bf8e35a Fixing binary map 2024-10-01 18:31:43 +02:00
6e5d7a7fb0 Building partial update 2024-10-01 09:57:55 +02:00
09f4b17674 fmt & clippy 2024-10-01 05:34:16 +02:00
14b63b5ad3 Fix render 2024-10-01 05:31:59 +02:00
ffde674b89 Fix update and render 2024-10-01 05:18:42 +02:00
eb9dd99b96 Working buttons & menu 2024-09-30 16:09:05 +02:00
a6ccd36ab5 Working buttons & menu 2024-09-30 16:09:01 +02:00
97ca331f34 Fix keypad 2024-09-26 16:09:27 +02:00
b5da325946 Keypad mapping 2024-09-26 16:08:49 +02:00
2fb089fd03 Create keypad handler 2024-09-26 16:08:48 +02:00
15 changed files with 1157 additions and 362 deletions

73
Cargo.lock generated
View File

@ -160,7 +160,7 @@ version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"quote",
"syn 2.0.77",
@ -359,7 +359,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"quote",
"syn 2.0.77",
@ -393,6 +393,7 @@ dependencies = [
"bit_field",
"embedded-graphics-core",
"embedded-hal 1.0.0",
"log",
]
[[package]]
@ -585,12 +586,6 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
@ -670,19 +665,6 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "maybe-async-cfg"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e083394889336bc66a4eaf1011ffbfa74893e910f902a9f271fa624c61e1b2"
dependencies = [
"proc-macro-error",
"proc-macro2",
"pulldown-cmark",
"quote",
"syn 1.0.109",
]
[[package]]
name = "maze"
version = "0.1.0"
@ -851,17 +833,6 @@ dependencies = [
"embedded-graphics",
]
[[package]]
name = "pulldown-cmark"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.37"
@ -913,7 +884,6 @@ dependencies = [
"rand",
"shared",
"strum",
"weact-studio-epd",
]
[[package]]
@ -952,18 +922,6 @@ dependencies = [
"twox-hash",
]
[[package]]
name = "sealed"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.77",
]
[[package]]
name = "serde"
version = "1.0.210"
@ -1034,7 +992,7 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"quote",
"rustversion",
@ -1116,15 +1074,6 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
@ -1167,20 +1116,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "weact-studio-epd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e531b21e70dfc6294be2429e4f616f634c8ca1a328325dceefc4f92c12d6e9b"
dependencies = [
"display-interface",
"embedded-graphics",
"embedded-hal 1.0.0",
"embedded-hal-async",
"maybe-async-cfg",
"sealed",
]
[[package]]
name = "winapi-util"
version = "0.1.9"

View File

@ -8,14 +8,13 @@ display-interface-spi = "0.5.0"
embedded-graphics = "0.8.1"
embedded-hal-bus = "0.2.0"
esp-backtrace = { version = "0.13.0", features = ["esp32c6", "exception-handler", "panic-handler", "println"] }
esp-hal = { version = "0.20", features = ["esp32c6", "embedded-hal"] }
esp-hal = { version = "0.20.1", features = ["esp32c6", "embedded-hal"] }
esp-println = { version = "0.10.0", default-features = false, features = ["esp32c6", "log", "auto"] }
heapless = "0.8.0"
log = { version = "0.4.21" }
nutype = { version = "0.5.0", default-features = false }
profont = "0.7.0"
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
weact-studio-epd = { version = "0.1.2", features = ["blocking"] }
shared = { path = "./shared", features = ['trng'] }
maze = { path = "./maze" }

View File

@ -19,6 +19,7 @@ edition = "2021"
embedded-graphics-core = { version = "0.4", optional = true }
embedded-hal = "1.0.0"
bit_field = "0.10.1"
log = { version = "0.4.21" }
[dev-dependencies]
embedded-graphics = "0.8"

View File

@ -289,7 +289,7 @@ where
}
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
let _ = self.interface.wait_until_idle(delay, IS_BUSY_LOW);
Ok(())
}
}

View File

@ -15,6 +15,7 @@ pub(crate) const LUT_1GRAY_GC: [u8; 105] = [
// This LUT updates only the pixels that have changed.
pub(crate) const LUT_1GRAY_DU: [u8; 105] = [
/*
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //1
0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //2
0x0A, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //3
@ -25,5 +26,18 @@ pub(crate) const LUT_1GRAY_DU: [u8; 105] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //9
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //10
0x22, 0x22, 0x22, 0x22, 0x22,*/
//
//
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //1
0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2
0x0A, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //5
0x00, 0x00, 0x05, 0x05, 0x00, 0x05, 0x03, 0x05, 0x05, 0x00, // 6
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //8
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //9
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //10
0x22, 0x22, 0x22, 0x22, 0x22,
];

View File

@ -29,6 +29,7 @@ pub const HEIGHT: u32 = 480;
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = false;
const IS_BUSY_HIGH: bool = true;
const SINGLE_BYTE_WRITE: bool = true;
@ -60,32 +61,51 @@ where
DELAY: DelayNs,
{
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
log::info!("self.interface.reset(delay, 30, 10)");
// reset the device
self.interface.reset(delay, 30, 10);
log::info!("self.interface.cmd(spi, Command::SwReset)?");
self.interface.cmd(spi, Command::SwReset)?;
log::info!("delay.delay_us(300000u32)");
delay.delay_us(300000u32);
log::info!(".cmd_with_data(spi, Command::AutoWriteRedRamRegularPattern, &[0xF7])?");
self.interface
.cmd_with_data(spi, Command::AutoWriteRedRamRegularPattern, &[0xF7])?;
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
log::info!("1 .wait_until_idle(delay, IS_BUSY_LOW)");
if let Err(e) = self.interface.wait_until_idle(delay, IS_BUSY_LOW) {
log::error!("{e}");
panic!("{e}");
};
log::info!(".cmd_with_data(spi, Command::AutoWriteBwRamRegularPattern, &[0xF7])?");
self.interface
.cmd_with_data(spi, Command::AutoWriteBwRamRegularPattern, &[0xF7])?;
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
log::info!("2 .wait_until_idle(delay, IS_BUSY_LOW)");
if let Err(e) = self.interface.wait_until_idle(delay, IS_BUSY_LOW) {
log::error!("{e}");
panic!("{e}");
}
log::info!(".cmd_with_data(spi, Command::GateSetting, &[0xDF, 0x01, 0x00])?");
self.interface
.cmd_with_data(spi, Command::GateSetting, &[0xDF, 0x01, 0x00])?;
log::info!(".cmd_with_data(spi, Command::GateVoltage, &[0x00])?");
self.interface
.cmd_with_data(spi, Command::GateVoltage, &[0x00])?;
log::info!(".cmd_with_data(spi, Command::GateVoltageSource, &[0x41, 0xA8, 0x32])?");
self.interface
.cmd_with_data(spi, Command::GateVoltageSource, &[0x41, 0xA8, 0x32])?;
log::info!(".cmd_with_data(spi, Command::DataEntrySequence, &[0x03])?");
self.interface
.cmd_with_data(spi, Command::DataEntrySequence, &[0x03])?;
log::info!(".cmd_with_data(spi, Command::BorderWaveformControl, &[0x03])?");
self.interface
.cmd_with_data(spi, Command::BorderWaveformControl, &[0x03])?;
log::info!(".cmd_with_data( spi, Command::BoosterSoftStartControl, &[0xAE, 0xC7, 0xC3, 0xC0, 0xC0],)?");
self.interface.cmd_with_data(
spi,
Command::BoosterSoftStartControl,
@ -123,6 +143,72 @@ where
}
}
#[allow(dead_code)]
impl<SPI, BUSY, DC, RST, DELAY> EPD3in7<SPI, BUSY, DC, RST, DELAY>
where
SPI: SpiDevice,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayNs,
{
pub(crate) fn set_ram_area(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
start_x: u32,
start_y: u32,
end_x: u32,
end_y: u32,
) -> Result<(), SPI::Error> {
self.wait_until_idle(spi, delay)?;
assert!(start_x < end_x);
assert!(start_y < end_y);
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
// aren't relevant
self.interface.cmd_with_data(
spi,
Command::SetRamXAddressStartEndPosition,
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
)?;
// 2 Databytes: A[7:0] & 0..A[8] for each - start and end
self.interface.cmd_with_data(
spi,
Command::SetRamYAddressStartEndPosition,
&[
start_y as u8,
(start_y >> 8) as u8,
end_y as u8,
(end_y >> 8) as u8,
],
)?;
Ok(())
}
pub(crate) fn set_ram_counter(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
x: u32,
y: u32,
) -> Result<(), SPI::Error> {
self.wait_until_idle(spi, delay)?;
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
// aren't relevant
self.interface
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
// 2 Databytes: A[7:0] & 0..A[8]
self.interface.cmd_with_data(
spi,
Command::SetRamYAddressCounter,
&[y as u8, (y >> 8) as u8],
)?;
Ok(())
}
}
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
for EPD3in7<SPI, BUSY, DC, RST, DELAY>
where
@ -142,11 +228,13 @@ where
delay: &mut DELAY,
delay_us: Option<u32>,
) -> Result<Self, SPI::Error> {
log::info!("DisplayInterface::new(busy, dc, rst, delay_us)");
let mut epd = EPD3in7 {
interface: DisplayInterface::new(busy, dc, rst, delay_us),
background_color: DEFAULT_BACKGROUND_COLOR,
};
log::info!("init");
epd.init(spi, delay)?;
Ok(epd)
}
@ -197,25 +285,56 @@ where
Ok(())
}
/**
void EPD_3IN7_1Gray_Display_Part(const UBYTE *Image, UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend)
{
UWORD i, Width;
Width = (Xend-Xstart)%8 == 0 ? (Xend-Xstart)/8 : (Xend-Xstart)/8+1;
UWORD IMAGE_COUNTER = Width * (Yend-Ystart);
EPD_3IN7_SendCommand(0x44);
EPD_3IN7_SendData(Xstart & 0xff);
EPD_3IN7_SendData((Xstart>>8) & 0x03);
EPD_3IN7_SendData(Xend & 0xff);
EPD_3IN7_SendData((Xend>>8) & 0x03);
EPD_3IN7_SendCommand(0x45);
EPD_3IN7_SendData(Ystart & 0xff);
EPD_3IN7_SendData((Ystart>>8) & 0x03);
EPD_3IN7_SendData(Yend & 0xff);
EPD_3IN7_SendData((Yend>>8) & 0x03);
EPD_3IN7_SendCommand(0x24);
for (i = 0; i < IMAGE_COUNTER; i++)
{
EPD_3IN7_SendData(Image[i]);
}
EPD_3IN7_Load_LUT(2);
EPD_3IN7_SendCommand(0x20);
EPD_3IN7_ReadBusy_HIGH();
}
*/
#[allow(unused)]
fn update_partial_frame(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
buffer: &[u8],
x: u32,
y: u32,
start_x: u32,
start_y: u32,
width: u32,
height: u32,
) -> Result<(), SPI::Error> {
todo!()
// broken beyond repart
Ok(())
}
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
//self.interface
// .cmd_with_data(spi, Command::WRITE_LUT_REGISTER, &LUT_1GRAY_GC)?;
self.interface.cmd(spi, Command::DisplayUpdateSequence)?;
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
let _ = self.interface.wait_until_idle(delay, IS_BUSY_LOW);
Ok(())
}
@ -260,7 +379,7 @@ where
}
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
self.interface.wait_until_idle(delay, IS_BUSY_LOW).unwrap();
Ok(())
}
}

View File

@ -35,7 +35,9 @@ where
/// If no delay is given, a default delay of 10ms is used.
pub fn new(busy: BUSY, dc: DC, rst: RST, delay_us: Option<u32>) -> Self {
// default delay of 10ms
log::info!("delay_us.unwrap_or(10_000)");
let delay_us = delay_us.unwrap_or(10_000);
log::info!("delay_us.unwrap_or(10_000) done");
DisplayInterface {
_spi: PhantomData,
_delay: PhantomData,
@ -134,8 +136,20 @@ where
/// - FALSE for epd2in9, epd1in54 (for all Display Type A ones?)
///
/// Most likely there was a mistake with the 2in9 busy connection
pub(crate) fn wait_until_idle(&mut self, delay: &mut DELAY, is_busy_low: bool) {
pub(crate) fn wait_until_idle(
&mut self,
delay: &mut DELAY,
is_busy_low: bool,
) -> Result<(), &'static str> {
let mut times = 0;
while self.is_busy(is_busy_low) {
times += 1;
if times % 10 == 0 {
log::warn!("wait_until_idle {times}");
}
if times == 1_000 {
return Err("Failed to wait 1 000 times despite idle");
}
// This has been removed and added many time :
// - it is faster to not have it
// - it is complicated to pass the delay everywhere all the time
@ -146,6 +160,7 @@ where
delay.delay_us(self.delay_us);
}
}
Ok(())
}
/// Same as `wait_until_idle` for device needing a command to probe Busy pin

View File

@ -409,7 +409,7 @@ impl<const X: usize, const Y: usize, const SIZE: usize> AsciiBroad<X, Y, SIZE> {
// const LINE_SIZE: usize = X * Self::CELL_SIZE + 1;
// const BUFFER_SIZE: usize = Y + Self::CELL_SIZE * Self::LINE_SIZE;
fn format(&self, grid: &Grid<X, Y, SIZE>, output: &mut impl Write) {
pub fn format(&self, grid: &Grid<X, Y, SIZE>, output: &mut impl Write) {
write!(output, "#").unwrap();
(0..X).into_iter().for_each(|_| {
write!(output, "##").unwrap();
@ -426,16 +426,16 @@ impl<const X: usize, const Y: usize, const SIZE: usize> AsciiBroad<X, Y, SIZE> {
write!(bottom_line, "#").unwrap();
for x in 0..X {
write!(top_line, " ").unwrap();
write!(top_line, ".").unwrap();
let east_boundary = if grid.is_carved((x, y), Cell::EAST) {
" "
"."
} else {
"#"
};
write!(top_line, "{east_boundary}").unwrap();
let south_boundary = if grid.is_carved((x, y), Cell::SOUTH) {
" "
"."
} else {
"#"
};
@ -456,10 +456,12 @@ impl<'s> BufWriter<'s> {
pub fn new(b: &'s mut [MazePart]) -> Self {
Self(b, 0)
}
pub fn push(&mut self, part: MazePart) {
self.0[self.1] = part;
self.1 += 1;
}
pub fn copy(&mut self, src: &[MazePart]) {
src.iter()
.take_while(|p| **p != MazePart::Noop)
@ -575,10 +577,8 @@ impl<const X: usize, const Y: usize, const SIZE: usize> BinaryMapVisitor<X, Y, S
let mut bw = BufWriter::new(buffer);
bw.push(MazePart::Wall);
(0..X).into_iter().for_each(|_| {
bw.push(MazePart::Wall);
bw.push(MazePart::Wall);
});
bw.copy(&[MazePart::Wall; X]);
bw.copy(&[MazePart::Wall; X]);
let mut top_line = [MazePart::Noop; SIZE];
let mut bottom_line = [MazePart::Noop; SIZE];
@ -605,12 +605,33 @@ impl<const X: usize, const Y: usize, const SIZE: usize> BinaryMapVisitor<X, Y, S
} else {
MazePart::Wall
});
bl.push(MazePart::Wall);
}
bw.copy(&top_line);
bw.copy(&bottom_line);
}
}
}
pub fn binary_to_ascii_broad<const X: usize, const Y: usize>(b: &[MazePart], f: &mut impl Write) {
use MazePart::Passage as P;
use MazePart::Wall as W;
use MazePart::*;
write!(f, "\n").unwrap();
for y in 0..Y {
for x in 0..X {
let c = match b[y * X + x] {
P => '.',
W => '#',
Noop => '?',
Player => 'O',
};
write!(f, "{c}").unwrap();
}
write!(f, "\n").unwrap();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MazePart {
@ -678,6 +699,90 @@ mod print_tests {
let mut map = BinaryMap::<122, 122, 14884>::new();
BinaryMapVisitor.format(&mut grid, &mut map.0);
}
#[test]
fn binary_map() {
let mut g = Grid::<5, 5, 25>::new();
g.cells[0] = Cell::EAST | Cell::SOUTH;
g.cells[1] = Cell::EAST | Cell::SOUTH;
g.cells[2] = Cell::WEST | Cell::SOUTH;
g.cells[3] = Cell::EAST | Cell::SOUTH;
g.cells[4] = Cell::WEST | Cell::SOUTH;
g.cells[5] = Cell::WEST | Cell::SOUTH;
g.cells[6] = Cell::WEST | Cell::SOUTH;
g.cells[7] = Cell::WEST | Cell::NORTH;
g.cells[8] = Cell::WEST | Cell::SOUTH;
g.cells[9] = Cell::WEST | Cell::SOUTH;
g.cells[10] = Cell::WEST | Cell::SOUTH;
g.cells[11] = Cell::EAST | Cell::NORTH;
g.cells[12] = Cell::EAST | Cell::SOUTH;
g.cells[13] = Cell::WEST | Cell::NORTH;
g.cells[14] = Cell::WEST | Cell::SOUTH;
g.cells[15] = Cell::EAST | Cell::SOUTH;
g.cells[16] = Cell::WEST | Cell::NORTH;
g.cells[17] = Cell::EAST | Cell::NORTH;
g.cells[18] = Cell::WEST | Cell::SOUTH;
g.cells[19] = Cell::WEST | Cell::SOUTH;
g.cells[20] = Cell::EAST | Cell::NORTH;
g.cells[21] = Cell::EAST | Cell::NORTH;
g.cells[22] = Cell::EAST | Cell::NORTH;
g.cells[23] = Cell::WEST | Cell::NORTH;
g.cells[24] = Cell::WEST | Cell::NORTH;
let mut map = BinaryMap::<11, 11, 121>::new();
BinaryMapVisitor.format(&g, &mut map.0);
let mut s = heapless::String::<144>::new();
AsciiBroad.format(&g, &mut s);
println!("{s}");
use MazePart::Passage as P;
use MazePart::Wall as W;
use MazePart::*;
#[derive(PartialEq)]
struct MB([MazePart; 121]);
impl std::fmt::Debug for MB {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\n");
for y in 0..11 {
for x in 0..11 {
let c = match self.0[y * 11 + x] {
P => '.',
W => '#',
Noop => '?',
Player => 'O',
};
write!(f, "{c}");
}
write!(f, "\n");
}
Ok(())
}
}
assert_eq!(
MB(map.0),
MB([
W, W, W, W, W, W, W, W, W, W, W, //
W, P, P, P, P, P, W, P, P, P, W, //
W, P, W, P, W, P, W, P, W, P, W, //
W, P, W, P, W, P, W, P, W, P, W, //
W, P, W, P, W, W, W, P, W, P, W, //
W, P, W, P, P, P, P, P, W, P, W, //
W, P, W, W, W, P, W, W, W, P, W, //
W, P, P, P, W, P, P, P, W, P, W, //
W, P, W, W, W, W, W, P, W, P, W, //
W, P, P, P, P, P, P, P, W, P, W, //
W, W, W, W, W, W, W, W, W, W, W,
])
);
}
}
#[cfg(test)]

View File

@ -0,0 +1 @@

View File

@ -1,91 +1,179 @@
use maze::{BinaryMapVisitor, Direction};
use epd_waveshare::color::Color;
use maze::{binary_to_ascii_broad, BinaryMapVisitor, Direction, MazePart};
use crate::Button;
use super::*;
pub const MAZE_WIDTH: usize = 25;
pub const MAZE_HEIGHT: usize = 25;
pub struct MazeGame {
map: maze::BinaryMap<122, 122, 14884>,
player: (u16, u16),
map: maze::BinaryMap<MAZE_WIDTH, MAZE_HEIGHT, 625>,
player: Point,
old_player: Option<Point>,
}
impl MazeGame {
pub fn new() -> Self {
Self {
map: maze::BinaryMap::new(),
player: (0, 1),
player: Point { x: 0, y: 1 },
old_player: None,
}
}
}
impl MazeGame {
const X_OFFSET: i32 = 4;
const Y_OFFSET: i32 = 4;
const CELL_SIZE: i32 = 10;
fn player_pos(&self) -> (u16, u16) {
(self.player.x as u16, self.player.y as u16)
}
fn draw_walls(&self, ctx: &mut Context) {
let wall_style = PrimitiveStyleBuilder::new()
.fill_color(Color::Black)
.build();
let passage_style = PrimitiveStyleBuilder::new()
.fill_color(Color::White)
.build();
for y in 0..MAZE_HEIGHT {
for x in 0..MAZE_WIDTH {
let style = match self.map.at(x as u16, y as u16) {
maze::MazePart::Wall => &wall_style,
_ => &passage_style,
};
Rectangle::new(
Point::new(
(x as i32 * Self::CELL_SIZE) + Self::X_OFFSET,
(y as i32 * Self::CELL_SIZE) + Self::Y_OFFSET,
),
Size::new(Self::CELL_SIZE as u32, Self::CELL_SIZE as u32),
)
.draw_styled(style, &mut ctx.epaper.display)
.unwrap();
}
}
}
fn draw_player(&self, ctx: &mut Context) {
let player_style = PrimitiveStyleBuilder::new()
.stroke_color(Color::Black)
.stroke_width(1)
.fill_color(Color::Black)
.build();
Rectangle::new(
Point::new(
(self.player.x * Self::CELL_SIZE) + Self::X_OFFSET + 1,
(self.player.y * Self::CELL_SIZE) + Self::Y_OFFSET + 1,
),
Size::new((Self::CELL_SIZE - 2) as u32, (Self::CELL_SIZE - 2) as u32),
)
.draw_styled(&player_style, &mut ctx.epaper.display)
.unwrap();
}
fn generate_map(&mut self, trng: &mut Trng) {
let mut grid = maze::Grid::<12, 12, 144>::new();
maze::RecursiveDivision.generate(&mut grid, trng);
BinaryMapVisitor.format(&mut grid, &mut self.map.0);
for x in 0..MAZE_WIDTH {
let part = &mut self.map.0[(MAZE_WIDTH + x) as usize];
if *part != MazePart::Wall {
break;
}
*part = MazePart::Passage;
}
for x in (0..MAZE_WIDTH).into_iter().rev() {
let part = &mut self.map.0[((MAZE_WIDTH * (MAZE_HEIGHT - 2)) + x) as usize];
if *part != MazePart::Wall {
break;
}
*part = MazePart::Passage;
}
let mut result = heapless::String::<4_000>::new();
// AsciiBroad.format(&grid, &mut result);
binary_to_ascii_broad::<MAZE_WIDTH, MAZE_HEIGHT>(&self.map.0, &mut result);
println!("{result}");
}
}
impl App for MazeGame {
fn start(&mut self, trng: &mut Trng) {
let mut grid = maze::Grid::<60, 60, 3600>::new();
maze::RecursiveDivision.generate(&mut grid, trng);
BinaryMapVisitor.format(&mut grid, &mut self.map.0);
self.generate_map(trng);
}
fn draw(&self, ctx: &mut Context) {
/*
let wall_style = PrimitiveStyleBuilder::new()
.stroke_color(TriColor::Black)
.stroke_width(3)
.fill_color(TriColor::Black)
.build();
let player_style = PrimitiveStyleBuilder::new()
.stroke_color(TriColor::Red)
.stroke_width(3)
.fill_color(TriColor::Red)
.build();
self.draw_walls(ctx);
self.draw_player(ctx);*/
// FIXME: Partial update
let Some(_old) = &self.old_player else {
self.draw_walls(ctx);
self.draw_player(ctx);
return;
};
const X_OFFSET: i32 = 2;
const Y_OFFSET: i32 = 2;
for x in 0..122 {
for y in 0..122 {
match self.map.at(x, y) {
maze::MazePart::Wall => {
let p = Rectangle::new(
Point::new(x as i32 + X_OFFSET, y as i32 + Y_OFFSET),
Size::new(1, 1),
);
p.draw_styled(&wall_style, display).unwrap();
}
_ => {}
}
}
let p = Rectangle::new(
Point::new(
self.player.0 as i32 + X_OFFSET,
self.player.1 as i32 + Y_OFFSET,
),
Size::new(1, 1),
);
p.draw_styled(&player_style, ctx.display).unwrap();
}
*/
ctx.epaper.fast_update();
ctx.epaper.clear_frame();
self.draw_walls(ctx);
self.draw_player(ctx);
ctx.epaper.full_update();
// ctx.epaper.partial_update(
// *old,
// Size {
// width: 1,
// height: 1,
// },
// &[Color::White as u8],
// );
// ctx.epaper.partial_update(
// self.player,
// Size {
// width: 1,
// height: 1,
// },
// &[Color::Black as u8],
// );
}
fn update(&mut self, ctx: &mut Context) -> Option<Action> {
let Some(button) = ctx.button_pressed else {
return None;
};
Some(match button {
Button::Up if self.map.can_move(self.player, Direction::North) => {
self.player.1 -= 1;
let player_old = self.player;
match button {
Button::Up if self.map.can_move(self.player_pos(), Direction::North) => {
self.player.y -= 1;
self.old_player = Some(player_old);
Some(Action::PartialRender)
}
Button::Down if self.map.can_move(self.player, Direction::South) => {
self.player.1 += 1;
Button::Down if self.map.can_move(self.player_pos(), Direction::South) => {
self.player.y += 1;
self.old_player = Some(player_old);
Some(Action::PartialRender)
}
Button::Left if self.map.can_move(self.player, Direction::West) => {
self.player.0 -= 1;
Button::Left if self.map.can_move(self.player_pos(), Direction::West) => {
self.player.x -= 1;
self.old_player = Some(player_old);
Some(Action::PartialRender)
}
Button::Right if self.map.can_move(self.player, Direction::East) => {
self.player.0 += 1;
Button::Right if self.map.can_move(self.player_pos(), Direction::East) => {
self.player.x += 1;
self.old_player = Some(player_old);
Some(Action::PartialRender)
}
Button::Back => {}
_ => {}
});
None
Button::Back => Some(Action::GoToMenu),
_ => None,
}
}
}

View File

@ -1,6 +1,7 @@
use crate::Button;
use super::*;
use epd_waveshare::color::Color;
use profont::PROFONT_18_POINT;
use strum::IntoEnumIterator;
@ -48,31 +49,30 @@ impl App for Menu {
};
match button {
Button::Up => match self.selected {
MenuEntry::Labirynth => return None,
MenuEntry::Labirynth => None,
MenuEntry::Pairs => {
self.selected = MenuEntry::Labirynth;
None
Some(Action::Render)
}
},
Button::Down => match self.selected {
MenuEntry::Labirynth => {
self.selected = MenuEntry::Pairs;
None
Some(Action::Render)
}
MenuEntry::Pairs => None,
},
Button::Circle => match self.selected {
MenuEntry::Labirynth => return Some(Action::StartMaze),
MenuEntry::Pairs => return Some(Action::StartPairs),
MenuEntry::Labirynth => Some(Action::StartMaze),
MenuEntry::Pairs => Some(Action::StartPairs),
},
_ => None,
}
}
fn draw(&self, ctx: &mut Context) {
/*
let style = MonoTextStyle::new(&PROFONT_18_POINT, TriColor::Black);
let selected_style = MonoTextStyle::new(&PROFONT_18_POINT, TriColor::Red);
let style = MonoTextStyle::new(&PROFONT_18_POINT, Color::Black);
let selected_style = MonoTextStyle::new(&PROFONT_18_POINT, Color::Black);
MenuEntry::iter().for_each(|entry| {
if entry == self.selected {
@ -82,7 +82,7 @@ impl App for Menu {
selected_style,
TextStyle::default(),
)
.draw(display)
.draw(&mut ctx.epaper.display)
.unwrap();
};
@ -96,9 +96,8 @@ impl App for Menu {
},
TextStyle::default(),
)
.draw(display)
.draw(&mut ctx.epaper.display)
.unwrap();
});
*/
}
}

View File

@ -6,9 +6,9 @@ use embedded_graphics::{
Drawable,
};
use esp_hal::rng::Trng;
use esp_println::println;
use maze_game::MazeGame;
use menu::Menu;
use weact_studio_epd::{graphics::Display290TriColor, TriColor};
use crate::Context;
@ -16,10 +16,13 @@ pub mod guess_game;
pub mod maze_game;
pub mod menu;
#[derive(Debug, Clone, Copy)]
pub enum Action {
GoToMenu,
StartMaze,
StartPairs,
Render,
PartialRender,
}
pub enum Application {
@ -27,47 +30,72 @@ pub enum Application {
Maze(maze_game::MazeGame),
}
impl Default for Application {
fn default() -> Self {
Self::Menu(Menu::new())
}
}
impl Application {
pub fn update(&mut self, ctx: &mut Context, trng: &mut Trng) {
pub fn draw(&self, ctx: &mut Context, full: bool) {
if full {
ctx.epaper.slow_update();
ctx.epaper.full_erase();
}
match self {
Self::Menu(menu) => menu.draw(ctx),
Self::Maze(maze) => maze.draw(ctx),
};
if full {
ctx.epaper.full_update();
}
}
pub fn update_and_draw(&mut self, ctx: &mut Context, trng: &mut Trng) {
let Some(action) = (match self {
Self::Menu(menu) => menu.update(ctx),
Self::Maze(maze) => maze.update(ctx),
Self::Menu(menu) => menu.update_and_draw(ctx),
Self::Maze(maze) => maze.update_and_draw(ctx),
}) else {
println!("No action");
return;
};
println!("Action: {action:?}");
match action {
Action::StartMaze => {
let mut maze = MazeGame::new();
maze.start(trng);
*self = Application::Maze(maze);
self.draw(ctx, true);
}
Action::GoToMenu => {
let mut menu = Menu::new();
menu.start(trng);
*self = Application::Menu(menu);
self.draw(ctx, true);
}
Action::StartPairs => {}
}
}
pub fn draw(&self, ctx: &mut Context) {
match self {
Self::Menu(menu) => menu.draw(ctx),
Self::Maze(maze) => maze.draw(ctx),
}
Action::Render => {
self.draw(ctx, true);
}
Action::PartialRender => {
self.draw(ctx, false);
}
};
}
}
pub trait App {
fn start(&mut self, trng: &mut Trng);
fn draw(&self, display: &mut Context);
fn draw(&self, ctx: &mut Context);
fn update(&mut self, ctx: &mut Context) -> Option<Action>;
fn draw_and_update(&mut self, ctx: &mut Context) -> Option<Action> {
fn update_and_draw(&mut self, ctx: &mut Context) -> Option<Action> {
let action = self.update(ctx);
self.draw(ctx);
if action.is_none() {
self.draw(ctx);
}
action
}
}

208
src/epaper.rs Normal file
View File

@ -0,0 +1,208 @@
use embedded_graphics::{
geometry::{Point, Size},
mono_font::MonoTextStyleBuilder,
primitives::{Primitive, PrimitiveStyleBuilder, Rectangle},
text::{Baseline, Text, TextStyleBuilder},
Drawable,
};
use embedded_hal::delay::DelayNs;
use embedded_hal_bus::spi::ExclusiveDevice;
use epd_waveshare::epd3in7::*;
use epd_waveshare::prelude::*;
use esp_hal::{
clock::Clocks,
delay::Delay,
gpio::{Gpio18, Gpio19, Gpio20, Gpio21, Gpio22, Gpio23, Input, Level, Output, NO_PIN},
prelude::*,
spi::{master::Spi, FullDuplexMode, SpiMode},
};
pub type CS<'b> = Output<'b, Gpio20>;
pub type BUS<'b> = Input<'b, Gpio23>;
pub type RST<'b> = Output<'b, Gpio22>;
pub type DC<'b> = Output<'b, Gpio21>;
pub type SpiBus<'b> = Spi<'b, esp_hal::peripherals::SPI2, FullDuplexMode>;
pub type SPI<'b> = ExclusiveDevice<SpiBus<'b>, CS<'b>, Delay>;
pub type Driver<'b> = EPD3in7<SPI<'b>, BUS<'b>, DC<'b>, RST<'b>, Delay>;
pub struct Epaper<'d> {
pub display: Display3in7,
pub driver: Driver<'d>,
pub spi: SPI<'d>,
pub delay: Delay,
}
/**
* Board mapping:
*
*
* _________
* __| |__
* | |
* 3.3v O |
* | |
* | |
* | |
* | |
* | O 23 - BUSY
* | O 22 - RESET (RST)
* | O 21 - DC
* | O 20 - CS
* | O 19 - CLK / SCLK
* | O 18 - DIM
* | |
* | |
* | |
* |__ ___ __|
* | | | |
* CH343 ESP32C6
*
* Cannonical wiring
* * BUSY - purple - Gpio23
* * RST - white - Gpio22
* * DC - green - Gpio21
* * CS - orange - Gpio20
* * CLK - yellow - Gpio19
* * DIM - blue - Gpio18
* * GND - brown - ANY GND
* * VCC - gray - 3.3v
*
*/
// let mut epaper = epaper::Epaper::new(mosi, sclk, cs, dc, rst, busy, peripherals.SPI2, &clocks);
impl<'d> Epaper<'d> {
pub fn new(
din: Gpio18,
sclk: Gpio19,
cs: Gpio20,
dc: Gpio21,
rst: Gpio22,
busy: Gpio23,
spi: esp_hal::peripherals::SPI2,
clocks: &'d Clocks,
) -> Self {
let mut delay = Delay::new(clocks);
let spi_bus: SpiBus = Spi::new(spi, 4_000.kHz(), SpiMode::Mode0, clocks).with_pins(
Some(sclk),
Some(din),
NO_PIN,
NO_PIN, // cs is handled by the exclusive device
);
let cs: CS = Output::new(cs, Level::High);
let busy: BUS = Input::new(busy, esp_hal::gpio::Pull::Up);
let dc: DC = Output::new(dc, Level::High);
let rst = Output::new(rst, Level::High);
log::info!("Intializing SPI Device...");
let mut spi: SPI =
ExclusiveDevice::new(spi_bus, cs, delay).expect("SPI device initialize error");
log::info!("SPI ExclusiveDevice done");
let mut driver =
EPD3in7::new(&mut spi, busy, dc, rst, &mut delay, None).expect("eink initalize error");
log::info!("Driver DONE");
let mut display = Display3in7::default();
log::info!("Display DONE");
display.set_rotation(DisplayRotation::Rotate90);
log::info!("Display ratation DONE");
driver.set_background_color(Color::White);
driver.clear_frame(&mut spi, &mut delay).unwrap();
Self {
display,
driver,
spi,
delay,
}
}
pub fn full_update(&mut self) {
// self.driver
// .clear_frame(&mut self.spi, &mut self.delay)
// .unwrap();
self.driver
.update_and_display_frame(&mut self.spi, self.display.buffer(), &mut self.delay)
.expect("display frame new graphics");
}
pub fn full_erase(&mut self) {
self.driver
.clear_frame(&mut self.spi, &mut self.delay)
.unwrap();
log::info!("Clearing display");
let _ = Rectangle::new(Point::new(0, 0), Size::new(480, 280))
.into_styled(
PrimitiveStyleBuilder::new()
.fill_color(Color::White)
.build(),
)
.draw(&mut self.display);
log::info!("Clearing display DONE");
}
pub fn fast_update(&mut self) {
self.driver
.set_lut(&mut self.spi, &mut self.delay, Some(RefreshLut::Quick))
.unwrap();
}
pub fn slow_update(&mut self) {
self.driver
.set_lut(&mut self.spi, &mut self.delay, Some(RefreshLut::Full))
.unwrap();
}
pub fn print_welcome(&mut self) {
// draw white on black background
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_9X15_BOLD)
.text_color(Color::Black)
.background_color(Color::White)
.build();
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();
let _ = Text::with_text_style("Booting...", Point::new(90, 10), style, text_style)
.draw(&mut self.display);
self.driver
.update_and_display_frame(&mut self.spi, self.display.buffer(), &mut self.delay)
.expect("display frame new graphics");
self.delay.delay_ms(500);
}
pub fn wake_up(&mut self) {
self.driver.wake_up(&mut self.spi, &mut self.delay).unwrap();
}
pub fn sleep(&mut self) {
let _ = self.driver.sleep(&mut self.spi, &mut self.delay);
}
pub fn clear_frame(&mut self) {
self.driver
.clear_frame(&mut self.spi, &mut self.delay)
.unwrap();
}
pub fn partial_update(&mut self, pos: Point, size: Size, buffer: &[u8]) {
let _ = self.driver.update_partial_frame(
&mut self.spi,
&mut self.delay,
buffer,
pos.x as u32,
pos.y as u32,
size.width,
size.height,
);
}
pub fn partial_update_buffer(&mut self, pos: Point, size: Size) {
let _ = self.driver.update_partial_frame(
&mut self.spi,
&mut self.delay,
self.display.buffer(),
pos.x as u32,
pos.y as u32,
size.width,
size.height,
);
}
}

351
src/keypad.rs Normal file
View File

@ -0,0 +1,351 @@
#[doc(hidden)]
pub extern crate core as _core;
use core::cell::RefCell;
use esp_hal::gpio::{Input, InputPin, OutputOpenDrain, OutputPin};
/// A virtual `embedded-hal` input pin representing one key of the keypad.
///
/// A `KeypadInput` stores references to one row and one column pin. When you
/// read from it with `.is_low()` or `.is_high()`, it secretly sets the column
/// pin low, reads from the row pin, and then sets the column pin high again.
/// The column pin is actually stored inside a `RefCell` in the keypad struct,
/// so that multiple `KeypadInput`s can mutate the column pin's state as needed,
/// even though they only have a shared/immutable reference to it.
///
/// This has several implications.
///
/// 1) Reading from `KeypadInput`s is not reentrant. If we were in the middle
/// of reading a `KeypadInput` and entered an interrupt service routine that
/// read any `KeypadInput` of the same keypad, we might read an incorrect value
/// or cause a `panic`.
///
/// 2) Reading from a `KeypadInput` is slower than reading from a real input
/// pin, because it needs to change the output pin state twice for every read.
pub struct KeypadInput<'a> {
row: &'a RefCell<Input<'a, InputPin>>,
col: &'a RefCell<OutputOpenDrain<'a, dyn InputPin + OutputPin>>,
}
impl<'a, E> KeypadInput<'a, E> {
/// Create a new `KeypadInput`. For use in macros.
pub fn new(row: &'a dyn InputPin, col: &'a RefCell<dyn OutputPin>) -> Self {
Self { row, col }
}
}
impl<'a, E> InputPin for KeypadInput<'a, E> {
type Error = E;
/// Read the state of the key at this row and column. Not reentrant.
fn is_high(&self) -> Result<bool, E> {
Ok(!self.is_low()?)
}
/// Read the state of the key at this row and column. Not reentrant.
fn is_low(&self) -> Result<bool, E> {
self.col.borrow_mut().set_low()?;
let out = self.row.is_low()?;
self.col.borrow_mut().set_high()?;
Ok(out)
}
}
/// Define a new struct representing your keypad matrix circuit.
///
/// Every pin has a unique type, depending on its pin number and its current
/// mode. This struct is where you specify which pin types will be used in the
/// rows and columns of the keypad matrix. All the row pins must implement the
/// `InputPin` trait, and the column pins must implement the `OutputPin` trait.
/// The associated `Error` type of the `InputPin` and `OutputPin` traits must be
/// the same for every row and column pin, and you must specify it after your
/// struct name with `<Error = ...>`
///
/// You can specify the visibility of the struct (eg. `pub`) as usual, and add
/// doc comments using the `#[doc="..."]` attribute.
///
/// Don't access or modify the struct's fields directly. Instead, use
/// the methods implemented by this macro, documented here:
/// [`example_generated::ExampleKeypad`](./example_generated/struct.ExampleKeypad.html)
///
/// # Example
///
/// ```
/// # #![cfg_attr(docs_rs_workaround, feature(macro_vis_matcher))]
/// #[macro_use]
/// extern crate keypad;
///
/// use keypad::mock_hal::{self, Input, OpenDrain, Output, PullUp};
/// use core::convert::Infallible;
///
/// keypad_struct! {
/// #[doc="My super-special keypad."]
/// pub struct ExampleKeypad<Error = Infallible> {
/// rows: (
/// mock_hal::gpioa::PA0<Input<PullUp>>,
/// mock_hal::gpioa::PA1<Input<PullUp>>,
/// mock_hal::gpioa::PA2<Input<PullUp>>,
/// mock_hal::gpioa::PA3<Input<PullUp>>,
/// ),
/// columns: (
/// mock_hal::gpioa::PA4<Output<OpenDrain>>,
/// mock_hal::gpioa::PA5<Output<OpenDrain>>,
/// mock_hal::gpioa::PA6<Output<OpenDrain>>,
/// mock_hal::gpioa::PA7<Output<OpenDrain>>,
/// mock_hal::gpioa::PA8<Output<OpenDrain>>,
/// ),
/// }
/// }
///
/// # fn main() {
/// # }
/// ```
///
/// # Safety
///
/// This macro uses `unsafe` to create an array with uninitialized memory, which
/// is then immediately initialized in a loop. This is fine as long as there is
/// not a bug in how the macro calculates the dimensions of the array.
// There are two reasons why this big, scary macro is necessary:
//
// 1) Every single pin has a unique type, and we don't know which pins will be used. We know that
// they all implement a certain trait, but that doesn't help much because it doesn't tell us the
// size of the type, so we can't directly stick it into the struct. If we could use dynamic
// allocation, we could just store pins on the heap as boxed trait objects. But this crate needs
// to work on embedded platforms without an allocator! So, we use this macro to generate a
// struct containing the exact, concrete pin types the user provides.
//
// 2) We don't know how many pins there will be, because the keypad could have any number of rows
// and columns. That makes it hard to implement `decompose()`, which needs to iterate over the
// row and column pins. We can't store pins in arrays because they all have unique types, but we
// can store them in tuples instead. The problem is that you can't actually iterate over a tuple,
// and even indexing into arbitrary fields of a tuple using a macro is stupidly hard. The best
// approach I could come up with was to repeatedly destructure the tuple with different patterns,
// like this:
//
// let tuple = (0, 1, 2);
// let array = [
// {
// let (ref x, ..) = tuple;
// x
// },
// {
// let (_, ref x, ..) = tuple;
// x
// },
// {
// let (_, _, ref x, ..) = tuple;
// x
// },
// ];
// for reference in array.into_iter() {
// // ...
// }
//
// So that's how the `keypad_struct!()` macro iterates over tuples of pins. It counts the length of
// the tuple (also tricky!), creates patterns with increasing numbers of underscores, and uses them
// to build up a temporary array of references that it can iterate over. Luckily this code only
// needs to be run once, in `decompose()`, and not every time we read from a pin.
//
// I can't think of any simpler design that still has a convenient API and allows the keypad struct
// to own the row and column pins. If they weren't owned, the crate would be less convenient to use
// but could provide a generic Keypad struct like this:
//
// pub struct Keypad<'a, E, const R: usize, const C: usize> {
// rows: [&'a dyn InputPin<Error = E>; R],
// columns: [&'a RefCell<dyn OutputPin<Error = E>>; C],
// }
//
#[macro_export]
macro_rules! keypad_struct {
(
$(#[$attributes:meta])* $visibility:vis struct $struct_name:ident {
rows: ( $($row_type:ty),* $(,)* ),
columns: ( $($col_type:ty),* $(,)* ),
}
) => {
compile_error!("You must specify the associated `Error` type of the row and column pins'\
`InputPin` and `OutputPin` traits.\n\
Example: `struct MyStruct <Error = Infallible> { ... }`");
};
(
$(#[$attributes:meta])* $visibility:vis struct $struct_name:ident <Error = $error_type:ty> {
rows: ( $($row_type:ty),* $(,)* ),
columns: ( $($col_type:ty),* $(,)* ),
}
) => {
$(#[$attributes])* $visibility struct $struct_name {
/// The input pins used for reading each row.
rows: ($($row_type),* ,),
/// The output pins used for scanning through each column. They're
/// wrapped in RefCells so that we can change their state even if we
/// only have shared/immutable reference to them. This lets us
/// actively scan the matrix when reading the state of a virtual
/// `KeypadInput` pin.
columns: ($($crate::_core::cell::RefCell<$col_type>),* ,),
}
impl $struct_name {
/// Get a 2d array of embedded-hal input pins, each representing one
/// key in the keypad matrix.
#[allow(dead_code)]
$visibility fn decompose<'a>(&'a self) ->
keypad_struct!(
@array2d_type
$crate::KeypadInput<'a, $error_type>,
($($row_type),*)
($($crate::_core::cell::RefCell<$col_type>),*)
)
{
let rows: [
&dyn $crate::embedded_hal::digital::v2::InputPin<Error = $error_type>;
keypad_struct!(@count $($row_type)*)
]
= keypad_struct!(@tuple self.rows, ($($row_type),*));
let columns: [
&$crate::_core::cell::RefCell<dyn $crate::embedded_hal::digital::v2::OutputPin<Error = $error_type>>;
keypad_struct!(@count $($col_type)*)
]
= keypad_struct!(@tuple self.columns, ($($col_type),*));
// Create an uninitialized 2d array of MaybeUninit.
let mut out: keypad_struct!(
@array2d_type
$crate::_core::mem::MaybeUninit<$crate::KeypadInput<'a, $error_type>>,
($($row_type),*)
($($crate::_core::cell::RefCell<$col_type>),*)
) = unsafe {
$crate::_core::mem::MaybeUninit::uninit().assume_init()
};
// Initialize each element with a KeypadInput struct
for r in 0..rows.len() {
for c in 0..columns.len() {
out[r][c].write($crate::KeypadInput::new(rows[r], columns[c]));
}
}
// All elements are initialized. Transmute the array to the initialized type.
unsafe { $crate::_core::mem::transmute::<_, _>(out) }
}
/// Give back ownership of the row and column pins.
///
/// This consumes the keypad struct. All references to its virtual
/// `KeypadInput` pins must have gone out of scope before you try to
/// call `.release()`, or it will fail to compile.
///
/// The column pins will be returned inside of `RefCell`s (because
/// macros are hard). You can use `.into_inner()` to extract
/// each column pin from its `RefCell`.
#[allow(dead_code)]
$visibility fn release(self) ->(($($row_type),* ,), ($($crate::_core::cell::RefCell<$col_type>),* ,)) {
(self.rows, self.columns)
}
}
};
(@array2d_type $element_type:ty, ($($row:ty),*) ($($col:ty),*) ) => {
[keypad_struct!(@array1d_type $element_type, ($($col),*)) ; keypad_struct!(@count $($row)*)]
};
(@array1d_type $element_type:ty, ($($col:ty),*)) => {
[$element_type ; keypad_struct!(@count $($col)*)]
};
(@count $($token_trees:tt)*) => {
0usize $(+ keypad_struct!(@replace $token_trees 1usize))*
};
(@replace $_t:tt $sub:expr) => {
$sub
};
(@underscore $unused:tt) => {
_
};
(@destructure_ref $tuple:expr, ($($repeat_n:ty),*)) => {
{
let (
$(keypad_struct!(@underscore $repeat_n),)*
ref nth, ..) = $tuple;
nth
}
};
(@tuple_helper $tuple:expr, ($head:ty), ($($result:expr),* $(,)*)) => {
[
keypad_struct!(@destructure_ref $tuple, ()),
$($result),*
]
};
(@tuple_helper $tuple:expr, ($head:ty $(,$repeats:ty)* $(,)*), ($($result:expr),* $(,)*)) => {
keypad_struct!(
@tuple_helper $tuple, ($($repeats),*),
(
keypad_struct!(@destructure_ref $tuple, ($($repeats),*)),
$($result),*
)
)
};
(@tuple $tuple:expr, ($($repeats:ty),*)) => {
keypad_struct!(@tuple_helper $tuple, ($($repeats),*) , ())
};
}
/// Create an instance of the struct you defined with the `keypad_struct!()` macro..
///
/// The pin numbers and modes will need to match the ones you specified with `keypad_struct!()`.
///
/// ```
/// # #![cfg_attr(docs_rs_workaround, feature(macro_vis_matcher))]
/// # #[macro_use]
/// # extern crate keypad;
/// # use core::convert::Infallible;
/// # use keypad::mock_hal::{self, Input, OpenDrain, Output, PullUp};
/// # use keypad::mock_hal::{GpioExt, GPIOA};
/// # keypad_struct!{
/// # pub struct ExampleKeypad<Error = Infallible>{
/// # rows: (
/// # mock_hal::gpioa::PA0<Input<PullUp>>,
/// # mock_hal::gpioa::PA1<Input<PullUp>>,
/// # mock_hal::gpioa::PA2<Input<PullUp>>,
/// # mock_hal::gpioa::PA3<Input<PullUp>>,
/// # ),
/// # columns: (
/// # mock_hal::gpioa::PA4<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA5<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA6<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA7<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA8<Output<OpenDrain>>,
/// # ),
/// # }
/// # }
/// # fn main() {
/// let pins = GPIOA::split();
///
/// let keypad = keypad_new!(ExampleKeypad {
/// rows: (
/// pins.pa0.into_pull_up_input(),
/// pins.pa1.into_pull_up_input(),
/// pins.pa2.into_pull_up_input(),
/// pins.pa3.into_pull_up_input(),
/// ),
/// columns: (
/// pins.pa4.into_open_drain_output(),
/// pins.pa5.into_open_drain_output(),
/// pins.pa6.into_open_drain_output(),
/// pins.pa7.into_open_drain_output(),
/// pins.pa8.into_open_drain_output(),
/// ),
/// });
/// # }
/// ```
#[macro_export]
macro_rules! keypad_new {
( $struct_name:ident {
rows: ( $($row_val:expr),* $(,)* ),
columns: ( $($col_val:expr),* $(,)* ),
}) => {
$struct_name {
rows: ($($row_val),* ,),
columns: ($($crate::_core::cell::RefCell::new($col_val)),* ,),
}
};
}

View File

@ -1,16 +1,12 @@
#![no_std]
#![no_main]
use apps::{menu::Menu, Application};
use display_interface_spi::SPIInterface;
use embedded_graphics::{
geometry::Point,
mono_font::{MonoTextStyle, MonoTextStyleBuilder},
text::{Baseline, Text, TextStyle, TextStyleBuilder},
Drawable,
};
use embedded_hal::delay::DelayNs;
mod epaper;
use apps::Application;
use embedded_graphics::Drawable;
use embedded_hal_bus::spi::ExclusiveDevice;
use epaper::Epaper;
use epd_waveshare::epd3in7::*;
use epd_waveshare::prelude::*;
use esp_backtrace as _;
@ -18,13 +14,13 @@ use esp_hal::{
clock::ClockControl,
delay::Delay,
gpio::{
Gpio10, Gpio2, Gpio20, Gpio21, Gpio22, Gpio23, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8,
Input, Io, Level, Output, Pull, NO_PIN,
Gpio0, Gpio1, Gpio10, Gpio20, Gpio21, Gpio22, Gpio23, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8,
Input, Io, Level, Output, OutputOpenDrain, Pull,
},
peripherals::Peripherals,
prelude::*,
rng::Trng,
spi::{master::Spi, FullDuplexMode, SpiMode},
spi::{master::Spi, FullDuplexMode},
system::SystemControl,
};
use esp_println::println;
@ -46,7 +42,7 @@ fn main() -> ! {
let system = SystemControl::new(peripherals.SYSTEM);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let clocks = ClockControl::max(system.clock_control).freeze();
let mut delay = Delay::new(&clocks);
let delay = Delay::new(&clocks);
esp_println::logger::init_logger(LevelFilter::Trace);
// esp_println::logger::init_logger_from_env();
@ -57,7 +53,7 @@ fn main() -> ! {
// Configure SPI
// Settings are taken from
// Pins for WeAct
// Pins for e-paper
let mosi /* sda */ = io.pins.gpio18; // D10 / GPIO18
let sclk = io.pins.gpio19; // D8 / GPIO19
let cs = io.pins.gpio20; // D9 / GPIO20
@ -65,109 +61,45 @@ fn main() -> ! {
let rst = io.pins.gpio22; // D4 / GPIO22
let busy = io.pins.gpio23; // D5 / GPIO23
let spi_bus: SpiBus = Spi::new(peripherals.SPI2, 4_000.kHz(), SpiMode::Mode0, &clocks)
.with_pins(
Some(sclk),
Some(mosi),
NO_PIN,
NO_PIN, // cs is handled by the exclusive device
);
let mut epaper = epaper::Epaper::new(mosi, sclk, cs, dc, rst, busy, peripherals.SPI2, &clocks);
// Convert pins into InputPins and OutputPins
/*
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
*/
epaper.full_erase();
epaper.print_welcome();
let cs: CS = Output::new(cs, Level::High);
let busy: BUS = Input::new(busy, esp_hal::gpio::Pull::Up);
let dc: DC = Output::new(dc, Level::Low);
let rst = Output::new(rst, Level::High);
log::info!("Intializing SPI Device...");
let mut spi: SPI =
ExclusiveDevice::new(spi_bus, cs, delay).expect("SPI device initialize error");
// let spi_interface = SPIInterface::new(spi_device, dc);
let mut driver =
EPD3in7::new(&mut spi, busy, dc, rst, &mut delay, None).expect("eink initalize error");
let mut display = Display3in7::default();
display.set_rotation(DisplayRotation::Rotate90);
// draw white on black background
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_6X10)
.text_color(Color::White)
.background_color(Color::Black)
.build();
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();
let _ =
Text::with_text_style("Witamy!", Point::new(90, 10), style, text_style).draw(&mut display);
driver
.update_and_display_frame(&mut spi, display.buffer(), &mut delay)
.expect("display frame new graphics");
delay.delay_ms(500);
// epd2in13
// .set_refresh(&mut spi, &mut delay, RefreshLut::Quick)
// .unwrap();
driver.clear_frame(&mut spi, &mut delay).unwrap();
let mut kbd = Keyboard {
rows: Rows {
gpio2: Input::new(io.pins.gpio2, Pull::Up),
gpio3: Input::new(io.pins.gpio3, Pull::Up),
gpio4: Input::new(io.pins.gpio4, Pull::Up),
gpio5: Input::new(io.pins.gpio5, Pull::Up),
},
cols: Cols {
gpio6: Output::new(io.pins.gpio6, Level::Low),
gpio7: Output::new(io.pins.gpio7, Level::Low),
gpio8: Output::new(io.pins.gpio8, Level::Low),
gpio10: Output::new(io.pins.gpio10, Level::Low),
},
let mut kbd = Keypad {
i1: Input::new(io.pins.gpio4, Pull::Up),
i2: Input::new(io.pins.gpio5, Pull::Up),
i3: Input::new(io.pins.gpio6, Pull::Up),
i4: Input::new(io.pins.gpio7, Pull::Up),
//---------------------------
o1: OutputOpenDrain::new(io.pins.gpio0, Level::Low, Pull::Up),
o2: OutputOpenDrain::new(io.pins.gpio1, Level::Low, Pull::Up),
o3: OutputOpenDrain::new(io.pins.gpio8, Level::Low, Pull::Up),
o4: OutputOpenDrain::new(io.pins.gpio10, Level::Low, Pull::Up),
};
let mut ctx = Context {
button_pressed: None,
driver,
display,
spi,
epaper,
delay,
};
let mut app = Application::Menu(Menu::new());
let mut app = Application::default();
let mut trng = Trng::new(peripherals.RNG, peripherals.ADC1);
//app.draw(&mut display);
//driver.full_update(&display).unwrap();
//driver.sleep().unwrap();
ctx.driver.clear_frame(&mut ctx.spi, &mut delay).unwrap();
ctx.driver
.update_and_display_frame(&mut ctx.spi, ctx.display.buffer(), &mut delay)
.expect("display frame new graphics");
let _ = ctx.driver.sleep(&mut ctx.spi, &mut delay);
app.draw(&mut ctx, true);
app.update_and_draw(&mut ctx, &mut trng);
// ctx.epaper.sleep();
loop {
ctx.button_pressed = kbd.pressed();
if !ctx.button_pressed.is_none() {
if ctx.button_pressed.is_some() {
// ctx.epaper.clear_frame();
log::info!("Wake up!");
ctx.driver.wake_up(&mut ctx.spi, &mut delay).unwrap();
//display.clear(TriColor::White);
//app.update(&ctx, &mut trng);
// app.draw(&mut display);
//driver.full_update(&display).unwrap();
ctx.driver.clear_frame(&mut ctx.spi, &mut delay).unwrap();
ctx.driver
.update_and_display_frame(&mut ctx.spi, ctx.display.buffer(), &mut delay)
.expect("display frame new graphics");
let _ = ctx.driver.sleep(&mut ctx.spi, &mut delay);
app.update_and_draw(&mut ctx, &mut trng);
}
log::info!("Sleeping for 100ms...");
//driver.sleep().unwrap();
delay.delay(300.millis());
delay.delay(100.millis());
}
}
@ -203,107 +135,107 @@ pub enum Button {
Back,
}
impl Button {
pub fn from_idx(idx: usize) -> Option<Button> {
Some(match idx {
0 => Button::A,
1 => Button::Up,
2 => Button::B,
3 => Button::X,
//
4 => Button::Left,
5 => Button::Circle,
6 => Button::Right,
7 => Button::Y,
//
8 => Button::C,
9 => Button::Down,
10 => Button::D,
11 => Button::Z,
//
12 => Button::M1,
13 => Button::M2,
14 => Button::M3,
15 => Button::Back,
_ => return None,
})
}
}
pub struct Context<'d> {
pub button_pressed: Option<Button>,
pub display: Display3in7,
pub driver: Driver<'d>,
pub spi: SPI<'d>,
pub epaper: Epaper<'d>,
pub delay: Delay,
}
struct Rows<'d> {
gpio2: Input<'d, Gpio2>, //
gpio3: Input<'d, Gpio3>, //
gpio4: Input<'d, Gpio4>, //
gpio5: Input<'d, Gpio5>, //
struct Keypad<'d> {
// COLS
o1: OutputOpenDrain<'d, Gpio0>, //
o2: OutputOpenDrain<'d, Gpio1>, //
o3: OutputOpenDrain<'d, Gpio8>, //
o4: OutputOpenDrain<'d, Gpio10>, //
// --------------------------------------
// ROWS
i1: Input<'d, Gpio4>, //
i2: Input<'d, Gpio5>, //
i3: Input<'d, Gpio6>, //
i4: Input<'d, Gpio7>, //
}
struct Cols<'d> {
gpio6: Output<'d, Gpio6>, //
gpio7: Output<'d, Gpio7>, //
gpio8: Output<'d, Gpio8>, //
gpio10: Output<'d, Gpio10>, //
}
struct Keyboard<'d> {
rows: Rows<'d>,
cols: Cols<'d>,
}
impl<'d> Keyboard<'d> {
impl<'d> Keypad<'d> {
pub fn pressed(&mut self) -> Option<Button> {
self.cols.gpio6.set_high();
let col1 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio6.set_high();
let res: [[bool; 4]; 4] = core::array::from_fn(|x| {
core::array::from_fn(|y| {
self.set_low(x as u8);
let v = self.is_low(y as u8);
self.set_high(x as u8);
v
})
});
self.cols.gpio7.set_high();
let col2 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio7.set_low();
self.cols.gpio8.set_high();
let col3 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio8.set_low();
self.cols.gpio10.set_high();
let col4 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio10.set_low();
println!("***************************************");
println!("col1 {col1:?}");
println!("col2 {col2:?}");
println!("col3 {col3:?}");
println!("col4 {col4:?}");
None
}
}
/*
struct Keyboard<'d>(
[(
core::cell::RefCell<dyn InputPin + 'd>,
core::cell::RefCell<dyn OutputPin + 'd>,
); 16],
);
impl<'d> Keyboard<'d> {
pub fn new(cols: [impl InputPin; 4], rows: [impl OutputPin; 4]) {
core::array::from_fn(|x| )
for (x, c) in cols.into_iter().enumerate() {
for (y, r) in rows.into_iter().enumerate() {
;
if res.iter().any(|a| a.iter().any(|b| *b)) {
println!("***************************************");
for a in res {
println!("col {a:?}");
}
}
let c = res
.into_iter()
.flat_map(|v| v.into_iter())
.enumerate()
.find(|(_idx, b)| *b)
.and_then(|(idx, _)| Button::from_idx(idx));
if c.is_some() {
println!("Button: {c:?}");
}
c
}
}
trait KeyboardInput<'d> {
fn is_low(&mut self) -> bool;
fn is_high(&mut self) -> bool {
!self.is_low()
fn set_high(&mut self, o: u8) {
match o {
0 => self.o1.set_high(),
1 => self.o2.set_high(),
2 => self.o3.set_high(),
3 => self.o4.set_high(),
_ => {}
};
}
fn set_low(&mut self, o: u8) {
match o {
0 => self.o1.set_low(),
1 => self.o2.set_low(),
2 => self.o3.set_low(),
3 => self.o4.set_low(),
_ => {}
};
}
fn is_low(&mut self, o: u8) -> bool {
match o {
0 => self.i1.is_low(),
1 => self.i2.is_low(),
2 => self.i3.is_low(),
3 => self.i4.is_low(),
_ => false,
}
}
}
impl<'d, C: InputPin, R: OutputPin> KeyboardInput<'d> for (Input<'d, C>, Output<'d, R>) {
fn is_low(&mut self) -> bool {
self.1.set_low();
let s = self.0.is_low();
self.1.set_high();
s
}
}*/