Compare commits
No commits in common. "1d575d06b1c2a686fbce8f28418e69efa01028cc" and "6196d41c930a072fe327d515e7d7062ff63c124a" have entirely different histories.
1d575d06b1
...
6196d41c93
@ -1,11 +0,0 @@
|
||||
[build]
|
||||
target = "avr-none"
|
||||
rustflags = ["-C", "target-cpu=atmega328p", "--emit=llvm-ir"]
|
||||
|
||||
[target.'cfg(target_arch = "avr")']
|
||||
runner = "ravedude"
|
||||
# To run in simulator, replace the line above with this:
|
||||
# runner = "simavr -m atmega328p"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
206
Cargo.lock
generated
206
Cargo.lock
generated
@ -3,117 +3,10 @@
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "atmega-hal"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Rahix/avr-hal?tab=readme-ov-file?rev=cd3edea#cd3edea5529c46fa7a5650fd0e768600ab2356a4"
|
||||
dependencies = [
|
||||
"avr-device",
|
||||
"avr-hal-generic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "avr-device"
|
||||
version = "0.7.0"
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f2031240156132bd83639d86aeb5b1907e6a228d9a4b44c3e9699827e6dae"
|
||||
dependencies = [
|
||||
"avr-device-macros",
|
||||
"bare-metal",
|
||||
"cfg-if",
|
||||
"vcell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "avr-device-macros"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47c26fd925156183eb10e821b2ef7e06f8163f5a64a0bbe52fc896be2c6cbd3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "avr-hal-generic"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Rahix/avr-hal?tab=readme-ov-file?rev=cd3edea#cd3edea5529c46fa7a5650fd0e768600ab2356a4"
|
||||
dependencies = [
|
||||
"avr-device",
|
||||
"embedded-hal 0.2.7",
|
||||
"embedded-hal 1.0.0",
|
||||
"embedded-hal-bus",
|
||||
"embedded-storage",
|
||||
"nb 1.1.0",
|
||||
"paste",
|
||||
"ufmt",
|
||||
"unwrap-infallible",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bare-metal"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
|
||||
dependencies = [
|
||||
"nb 0.1.3",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal-bus"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57b4e6ede84339ebdb418cd986e6320a34b017cdf99b5cc3efceec6450b06886"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"embedded-hal 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-storage"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
|
||||
|
||||
[[package]]
|
||||
name = "nb"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
||||
dependencies = [
|
||||
"nb 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nb"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "panic-halt"
|
||||
@ -122,98 +15,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slideshow"
|
||||
name = "udp-tests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"atmega-hal",
|
||||
"avr-device",
|
||||
"embedded-hal 1.0.0",
|
||||
"libc",
|
||||
"panic-halt",
|
||||
"ufmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ufmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a64846ec02b57e9108d6469d98d1648782ad6bb150a95a9baac26900bbeab9d"
|
||||
dependencies = [
|
||||
"ufmt-macros",
|
||||
"ufmt-write",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ufmt-macros"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ufmt-write"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unwrap-infallible"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb"
|
||||
|
||||
[[package]]
|
||||
name = "vcell"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
17
Cargo.toml
17
Cargo.toml
@ -1,25 +1,14 @@
|
||||
[package]
|
||||
name = "slideshow"
|
||||
name = "udp-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
libc = { version = "0.2.175", default-features = false }
|
||||
panic-halt = "1.0.0"
|
||||
avr-device = { version = "*", features = ["rt"] }
|
||||
embedded-hal = "1.0.0"
|
||||
atmega-hal = { git = "https://github.com/Rahix/avr-hal?tab=readme-ov-file", rev="cd3edea", version = "0.1.0", features=["atmega328p"] }
|
||||
|
||||
ufmt = "0.2.0"
|
||||
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s" # Size is more important than performance on MSP430.
|
||||
codegen-units = 1 # Better size optimization.
|
||||
lto = "fat" # _Much_ better size optimization.
|
||||
|
||||
[build]
|
||||
rustflags = ["-C", "link-args=-lc", "--emit=llvm-ir"]
|
||||
rustflags = ["-C", "link-args=-lc"]
|
||||
|
@ -1,14 +0,0 @@
|
||||
[general]
|
||||
port = "/dev/ttyUSB0"
|
||||
serial-baudrate = 57600
|
||||
open-console = true
|
||||
|
||||
[board.avrdude]
|
||||
# avrdude configuration
|
||||
programmer = "arduino"
|
||||
partno = "m328p"
|
||||
baudrate = 57600
|
||||
do-chip-erase = true
|
||||
|
||||
# For documentation about this file, check here:
|
||||
# https://github.com/Rahix/avr-hal/blob/main/ravedude/README.md#ravedudetoml-format
|
BIN
images/cat.qoi
BIN
images/cat.qoi
Binary file not shown.
Binary file not shown.
BIN
images/rick.qoi
BIN
images/rick.qoi
Binary file not shown.
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2025-04-27"
|
||||
components = [
|
||||
"rustc",
|
||||
"cargo",
|
||||
"clippy",
|
||||
"rustfmt",
|
||||
"rust-src",
|
||||
"rust-analyzer",
|
||||
]
|
||||
profile = "minimal"
|
280
src/display.rs
280
src/display.rs
@ -1,280 +0,0 @@
|
||||
use core::ops::Mul;
|
||||
|
||||
use atmega_hal::{
|
||||
Spi,
|
||||
port::{self, Pin, PinOps, mode},
|
||||
spi::ChipSelectPin,
|
||||
};
|
||||
use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiBus};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Rgb565(pub u8, pub u8, pub u8);
|
||||
|
||||
impl Rgb565 {
|
||||
pub fn as_color(&self) -> Color {
|
||||
let r = ((self.0 / 8) as u16) << 11;
|
||||
let g = ((self.1 / 4) as u16) << 5;
|
||||
let b = (self.2 / 8) as u16;
|
||||
|
||||
Color::from(r | b | g)
|
||||
}
|
||||
|
||||
pub fn red() -> Rgb565 {
|
||||
Rgb565(255, 0, 0)
|
||||
}
|
||||
|
||||
pub fn green() -> Rgb565 {
|
||||
Rgb565(0, 255, 0)
|
||||
}
|
||||
|
||||
pub fn blue() -> Rgb565 {
|
||||
Rgb565(0, 0, 255)
|
||||
}
|
||||
|
||||
pub fn yellow() -> Rgb565 {
|
||||
Rgb565(255, 255, 0)
|
||||
}
|
||||
|
||||
pub fn magenta() -> Rgb565 {
|
||||
Rgb565(255, 0, 255)
|
||||
}
|
||||
|
||||
pub fn cyan() -> Rgb565 {
|
||||
Rgb565(0, 255, 255)
|
||||
}
|
||||
|
||||
pub fn qoi_hash(&self) -> usize {
|
||||
((self.0 as u32 * 3 + self.1 as u32 * 5 + self.2 as u32 * 7 + 255 * 11) % 64) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Rgb565 {
|
||||
type Output = Rgb565;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Rgb565(
|
||||
(self.0 as f32 * rhs) as u8,
|
||||
(self.1 as f32 * rhs) as u8,
|
||||
(self.2 as f32 * rhs) as u8,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct Position {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct Color {
|
||||
pub bytes: [u8; 2],
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn from(number: u16) -> Color {
|
||||
Color {
|
||||
bytes: number.to_be_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display<T: DelayNs, DCPin: PinOps, RSTPin: PinOps> {
|
||||
pub spi: Spi,
|
||||
pub cs: ChipSelectPin<port::PB2>,
|
||||
pub dc: Pin<mode::Output, DCPin>,
|
||||
pub rst: Pin<mode::Output, RSTPin>,
|
||||
pub delay: T,
|
||||
}
|
||||
|
||||
pub enum Command {
|
||||
NOP = 0x0,
|
||||
SoftReset = 0x1,
|
||||
RddID = 0x4,
|
||||
RddST = 0x9,
|
||||
|
||||
SleepIn = 0x10,
|
||||
SleepOut = 0x11,
|
||||
PTLON = 0x12,
|
||||
NoRon = 0x13,
|
||||
|
||||
InversionOff = 0x20,
|
||||
InversionOn = 0x21,
|
||||
DisplayOff = 0x28,
|
||||
DisplayOn = 0x29,
|
||||
ColumnAlignmentSet = 0x2A,
|
||||
RowAlignmentSet = 0x2B,
|
||||
RamWR = 0x2C,
|
||||
RamRD = 0x2E,
|
||||
|
||||
PTLAR = 0x30,
|
||||
VSCRDEF = 0x33,
|
||||
ColorMode = 0x3A,
|
||||
MADCTL = 0x36,
|
||||
VSCSAD = 0x37,
|
||||
// MadCTLMY = 0x80,
|
||||
// MadCTLMX = 0x40,
|
||||
// MadCTLMV = 0x20,
|
||||
// MadCTLML = 0x10,
|
||||
// MadCTLBGR = 0x08,
|
||||
// MadCTLMH = 0x04,
|
||||
// MadCTLRGB = 0x00,
|
||||
|
||||
// RDID1 = 0xDA,
|
||||
// RDID2 = 0xDB,
|
||||
// RDID3 = 0xDC,
|
||||
// RDID4 = 0xDD,
|
||||
|
||||
// ColorMode65K = 0x50,
|
||||
// ColorMode262K = 0x60,
|
||||
// ColorMode12BIT = 0x03,
|
||||
// ColorMode16BIT = 0x05,
|
||||
// ColorMode18BIT = 0x06,
|
||||
// ColorMode16M = 0x07,
|
||||
}
|
||||
|
||||
enum ColorMode {
|
||||
ColorMode65K = 0x50,
|
||||
ColorMode262K = 0x60,
|
||||
ColorMode12BIT = 0x03,
|
||||
ColorMode16BIT = 0x05,
|
||||
ColorMode18BIT = 0x06,
|
||||
ColorMode16M = 0x07,
|
||||
}
|
||||
|
||||
pub enum Writeable<'d> {
|
||||
Command(Command),
|
||||
Data(&'d [u8]),
|
||||
}
|
||||
|
||||
enum Rotation {
|
||||
Portrait = 0x00,
|
||||
Landscape = 0x60,
|
||||
InvertedPortrait = 0xc0,
|
||||
InvertedLandscape = 0xa0,
|
||||
}
|
||||
|
||||
impl<T: DelayNs, DCPin: PinOps, RSTPin: PinOps> Display<T, DCPin, RSTPin> {
|
||||
pub fn write(&mut self, writeable: Writeable) {
|
||||
self.cs.set_low().unwrap();
|
||||
match writeable {
|
||||
Writeable::Command(cmd) => {
|
||||
self.dc.set_low();
|
||||
SpiBus::write(&mut self.spi, &[cmd as u8]).unwrap()
|
||||
}
|
||||
Writeable::Data(data) => {
|
||||
self.dc.set_high();
|
||||
SpiBus::write(&mut self.spi, data).unwrap();
|
||||
self.cs.set_high().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.hard_reset();
|
||||
self.soft_reset();
|
||||
self.set_sleep(false);
|
||||
self.set_color_mode((ColorMode::ColorMode65K as u8) | (ColorMode::ColorMode16BIT as u8));
|
||||
self.delay.delay_ms(50);
|
||||
self.set_inversion(true);
|
||||
self.write(Writeable::Command(Command::NoRon));
|
||||
self.draw_rect(
|
||||
Position { x: 0, y: 0 },
|
||||
Position { x: 240, y: 240 },
|
||||
Color::default(),
|
||||
);
|
||||
|
||||
self.write(Writeable::Command(Command::DisplayOn));
|
||||
|
||||
self.delay.delay_ms(500);
|
||||
}
|
||||
|
||||
pub fn hard_reset(&mut self) {
|
||||
self.cs.set_low().unwrap();
|
||||
self.rst.set_high();
|
||||
self.delay.delay_ms(50);
|
||||
|
||||
self.rst.set_low();
|
||||
self.delay.delay_ms(50);
|
||||
|
||||
self.rst.set_high();
|
||||
self.delay.delay_ms(150);
|
||||
|
||||
self.cs.set_high().unwrap();
|
||||
}
|
||||
|
||||
pub fn soft_reset(&mut self) {
|
||||
self.write(Writeable::Command(Command::SoftReset));
|
||||
self.delay.delay_ms(150);
|
||||
}
|
||||
|
||||
pub fn set_sleep(&mut self, sleep: bool) {
|
||||
match sleep {
|
||||
true => self.write(Writeable::Command(Command::SleepIn)),
|
||||
false => self.write(Writeable::Command(Command::SleepOut)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_inversion(&mut self, inversion: bool) {
|
||||
match inversion {
|
||||
true => self.write(Writeable::Command(Command::InversionOn)),
|
||||
false => self.write(Writeable::Command(Command::InversionOff)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rotation(&mut self, rotation: Rotation) {
|
||||
self.write(Writeable::Command(Command::MADCTL));
|
||||
self.write(Writeable::Data(&[rotation as u8]));
|
||||
}
|
||||
|
||||
pub fn set_color_mode(&mut self, mode: u8) {
|
||||
self.write(Writeable::Command(Command::ColorMode));
|
||||
self.write(Writeable::Data(&[mode & 0x77]));
|
||||
}
|
||||
|
||||
fn set_columns(&mut self, start: u16, end: u16) {
|
||||
let [start1, start2] = start.to_be_bytes();
|
||||
let [end1, end2] = end.to_be_bytes();
|
||||
self.write(Writeable::Command(Command::ColumnAlignmentSet));
|
||||
self.write(Writeable::Data(&[start1, start2, end1, end2]));
|
||||
}
|
||||
|
||||
fn set_rows(&mut self, start: u16, end: u16) {
|
||||
let [start1, start2] = start.to_be_bytes();
|
||||
let [end1, end2] = end.to_be_bytes();
|
||||
self.write(Writeable::Command(Command::RowAlignmentSet));
|
||||
self.write(Writeable::Data(&[start1, start2, end1, end2]));
|
||||
}
|
||||
|
||||
pub fn set_window(&mut self, pos0: Position, pos1: Position) {
|
||||
self.set_columns(pos0.x, pos1.x);
|
||||
self.set_rows(pos0.y, pos1.y);
|
||||
self.write(Writeable::Command(Command::RamWR));
|
||||
}
|
||||
|
||||
pub fn pixel(&mut self, position: Position, color: Color) {
|
||||
self.set_window(position, position);
|
||||
self.write(Writeable::Data(&color.bytes));
|
||||
}
|
||||
|
||||
pub fn draw_rect(&mut self, pos0: Position, pos1: Position, color: Color) {
|
||||
self.set_window(pos0, pos1);
|
||||
self.dc.set_high();
|
||||
let width = pos1.x - pos0.x;
|
||||
let height = pos1.y - pos0.y;
|
||||
let pixels = width * height;
|
||||
let chunks = pixels / 256;
|
||||
let mut full_buf = [0; 512];
|
||||
let [col1, col2] = color.bytes;
|
||||
for i in 0..256 {
|
||||
full_buf[i * 2] = col1;
|
||||
full_buf[i * 2 + 1] = col2;
|
||||
}
|
||||
for _ in 0..chunks {
|
||||
self.write(Writeable::Data(&full_buf));
|
||||
}
|
||||
for _ in 0..(pixels % 256) {
|
||||
self.write(Writeable::Data(&color.bytes));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
fn letter(character: char) -> [u8; 8] {
|
||||
match character {
|
||||
'a' => [
|
||||
0b01111110, 0b01000010, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b10000001,
|
||||
0b10000001,
|
||||
],
|
||||
_ => [0; 8],
|
||||
}
|
||||
}
|
176
src/main.rs
176
src/main.rs
@ -1,113 +1,89 @@
|
||||
/*!
|
||||
* Blink the builtin LED - the "Hello World" of embedded programming.
|
||||
*/
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(iter_array_chunks)]
|
||||
#![feature(const_slice_make_iter)]
|
||||
#![feature(slice_as_chunks)]
|
||||
#![feature(asm_experimental_arch)]
|
||||
|
||||
use core::ptr::addr_of;
|
||||
|
||||
use atmega_hal::{
|
||||
Usart,
|
||||
spi::{self, Settings},
|
||||
usart::Baudrate,
|
||||
use core::{
|
||||
alloc::{GlobalAlloc, Layout},
|
||||
ffi::CStr,
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use panic_halt as _;
|
||||
|
||||
use crate::{
|
||||
display::{Display, Position},
|
||||
peripherals::Button,
|
||||
qoi::{Image, LARGE_CAT_UNSAFE, PRESS_BTN_UNSAFE, RICK_UNSAFE, XP_DESKTOP_UNSAFE, draw_image},
|
||||
};
|
||||
|
||||
mod display;
|
||||
mod font;
|
||||
mod peripherals;
|
||||
mod qoi;
|
||||
|
||||
type CoreClock = atmega_hal::clock::MHz8;
|
||||
|
||||
#[avr_device::entry]
|
||||
fn main() -> ! {
|
||||
let dp = atmega_hal::Peripherals::take().unwrap();
|
||||
let pins = atmega_hal::pins!(dp);
|
||||
|
||||
let rx = pins.pd0;
|
||||
let tx = pins.pd1;
|
||||
|
||||
let mut serial = Usart::new(
|
||||
dp.USART0,
|
||||
rx,
|
||||
tx.into_output(),
|
||||
Baudrate::<CoreClock>::new(57600),
|
||||
);
|
||||
|
||||
// let eeprom = Eeprom::new(dp.EEPROM);
|
||||
// ufmt::uwriteln!(serial, "Eeprom capacity: {}", eeprom.capacity()).unwrap();
|
||||
|
||||
let cs = pins.pb2.into_output();
|
||||
let (spi, cs) = spi::Spi::new(
|
||||
dp.SPI,
|
||||
pins.pb5.into_output(),
|
||||
pins.pb3.into_output(),
|
||||
pins.pb4.into_pull_up_input(),
|
||||
cs,
|
||||
Settings {
|
||||
data_order: spi::DataOrder::MostSignificantFirst,
|
||||
clock: spi::SerialClockRate::OscfOver2,
|
||||
mode: embedded_hal::spi::Mode {
|
||||
polarity: embedded_hal::spi::Polarity::IdleHigh,
|
||||
phase: embedded_hal::spi::Phase::CaptureOnFirstTransition,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let mut display = Display {
|
||||
spi,
|
||||
dc: pins.pb1.into_output(),
|
||||
cs,
|
||||
rst: pins.pd7.into_output(),
|
||||
delay: atmega_hal::delay::Delay::<CoreClock>::new(),
|
||||
};
|
||||
|
||||
display.init();
|
||||
|
||||
let mut button = Button::from(pins.pd5.into_pull_up_input());
|
||||
|
||||
let mut idx = 0;
|
||||
let images = [
|
||||
Image::from(addr_of!(LARGE_CAT_UNSAFE)),
|
||||
Image::from(addr_of!(XP_DESKTOP_UNSAFE)),
|
||||
Image::from(addr_of!(RICK_UNSAFE)),
|
||||
];
|
||||
let len = images.len();
|
||||
|
||||
match draw_image(
|
||||
&mut serial,
|
||||
&Image::from(addr_of!(PRESS_BTN_UNSAFE)),
|
||||
&mut display,
|
||||
Position { x: 0, y: 0 },
|
||||
) {
|
||||
Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(),
|
||||
Err(e) => ufmt::uwriteln!(serial, "Error: {:?}", e).unwrap(),
|
||||
fn handle_error(code: i32, err: &'static core::ffi::CStr) {
|
||||
unsafe {
|
||||
if code == -1 {
|
||||
libc::perror(err.as_ptr());
|
||||
libc::exit(libc::EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MyAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for MyAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
unsafe { libc::malloc(layout.size()) as *mut u8 }
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
unsafe { libc::free(ptr as *mut libc::c_void) }
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: MyAllocator = MyAllocator;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
// Since we are passing a C string the final null character is mandatory
|
||||
unsafe {
|
||||
libc::printf(c"Hello world!\n".as_ptr());
|
||||
|
||||
let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_UDP);
|
||||
|
||||
let address = libc::sockaddr_in {
|
||||
sin_family: libc::AF_INET as u16,
|
||||
sin_port: libc::htons(8080),
|
||||
sin_addr: libc::in_addr {
|
||||
s_addr: libc::INADDR_ANY,
|
||||
},
|
||||
sin_zero: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
};
|
||||
|
||||
handle_error(
|
||||
libc::bind(
|
||||
socket,
|
||||
&raw const address as *const libc::sockaddr,
|
||||
core::mem::size_of::<libc::sockaddr_in>() as u32,
|
||||
),
|
||||
c"bind",
|
||||
);
|
||||
|
||||
let mut otus = Vec::with_capacity(10);
|
||||
|
||||
loop {
|
||||
if button.poll() {
|
||||
match draw_image(
|
||||
&mut serial,
|
||||
&images[idx],
|
||||
&mut display,
|
||||
Position { x: 0, y: 0 },
|
||||
) {
|
||||
Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(),
|
||||
Err(e) => ufmt::uwriteln!(serial, "Error: {:?}", e).unwrap(),
|
||||
}
|
||||
let buf = libc::malloc(16) as *mut u8;
|
||||
|
||||
idx = (idx + 1) % len;
|
||||
let mut other = libc::malloc(core::mem::size_of::<libc::sockaddr_in>());
|
||||
let mut other_size = 0;
|
||||
|
||||
libc::recvfrom(
|
||||
socket,
|
||||
buf as *mut libc::c_void,
|
||||
1,
|
||||
0,
|
||||
&raw mut other as *mut libc::sockaddr,
|
||||
&mut other_size,
|
||||
);
|
||||
otus.push(*buf);
|
||||
|
||||
libc::printf(c"Data: %s\n".as_ptr(), buf);
|
||||
|
||||
libc::free(buf as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
use atmega_hal::{
|
||||
Adc, Atmega,
|
||||
adc::{AdcChannel, AdcOps},
|
||||
pac::ADC,
|
||||
port::{
|
||||
Pin, PinOps,
|
||||
mode::{self, PullUp},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::CoreClock;
|
||||
|
||||
pub struct Button<T: PinOps> {
|
||||
pin: Pin<mode::Input<PullUp>, T>,
|
||||
was_pressed: bool,
|
||||
pub is_pressed: bool,
|
||||
}
|
||||
|
||||
impl<T: PinOps> Button<T> {
|
||||
pub fn from(pin: Pin<mode::Input<PullUp>, T>) -> Button<T> {
|
||||
Button {
|
||||
pin,
|
||||
is_pressed: false,
|
||||
was_pressed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> bool {
|
||||
self.is_pressed = self.pin.is_low();
|
||||
if self.is_pressed {
|
||||
if !self.was_pressed {
|
||||
self.was_pressed = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.was_pressed = false;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Knob<T: PinOps> {
|
||||
pub pin: Pin<mode::Analog, T>,
|
||||
pub adc: Adc<CoreClock>,
|
||||
}
|
||||
|
||||
impl<T: PinOps> Knob<T>
|
||||
where
|
||||
Pin<mode::Analog, T>: AdcChannel<Atmega, ADC>,
|
||||
{
|
||||
pub fn poll(&mut self) -> f32 {
|
||||
let read = self.pin.analog_read(&mut self.adc);
|
||||
read as f32 / 1024f32
|
||||
}
|
||||
}
|
286
src/qoi.rs
286
src/qoi.rs
@ -1,286 +0,0 @@
|
||||
use core::{arch::asm, ptr::addr_of};
|
||||
|
||||
use atmega_hal::{
|
||||
Usart,
|
||||
pac::USART0,
|
||||
port::{PD0, PD1, Pin, PinOps, mode},
|
||||
};
|
||||
use embedded_hal::delay::DelayNs;
|
||||
use ufmt::derive::uDebug;
|
||||
|
||||
use crate::{
|
||||
CoreClock,
|
||||
display::{Display, Position, Rgb565, Writeable},
|
||||
};
|
||||
|
||||
// https://qoiformat.org/qoi-specification.pdf
|
||||
|
||||
#[unsafe(link_section = ".progmem.data")]
|
||||
pub static LARGE_CAT_UNSAFE: [u8; 8765] = *include_bytes!("../images/cat.qoi");
|
||||
#[unsafe(link_section = ".progmem.data")]
|
||||
pub static XP_DESKTOP_UNSAFE: [u8; 7360] = *include_bytes!("../images/xp_desktop.qoi");
|
||||
#[unsafe(link_section = ".progmem.data")]
|
||||
pub static RICK_UNSAFE: [u8; 2391] = *include_bytes!("../images/rick.qoi");
|
||||
#[unsafe(link_section = ".progmem.data")]
|
||||
pub static PRESS_BTN_UNSAFE: [u8; 1225] = *include_bytes!("../images/press_btn.qoi");
|
||||
|
||||
pub struct Image {
|
||||
ptr: *const u8,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
pub struct ImageIterator {
|
||||
ptr: *const u8,
|
||||
length: usize,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub const fn from<const N: usize>(addr: *const [u8; N]) -> Image {
|
||||
Image {
|
||||
ptr: addr.cast(),
|
||||
length: N,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> ImageIterator {
|
||||
ImageIterator {
|
||||
ptr: self.ptr,
|
||||
length: self.length,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ImageIterator {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.index >= self.length {
|
||||
return None;
|
||||
}
|
||||
let elem_ptr = self.ptr.wrapping_add(self.index);
|
||||
let res: u8;
|
||||
unsafe {
|
||||
asm!(
|
||||
"lpm {}, Z",
|
||||
out(reg) res,
|
||||
in("Z") elem_ptr,
|
||||
)
|
||||
}
|
||||
self.index += 1;
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, uDebug)]
|
||||
pub enum QoiErr {
|
||||
InvalidMagicNumber,
|
||||
UnexpectedEOF,
|
||||
}
|
||||
|
||||
pub fn draw_image<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
|
||||
serial: &mut Usart<USART0, Pin<mode::Input, PD0>, Pin<mode::Output, PD1>, CoreClock>,
|
||||
image: &Image,
|
||||
display: &mut Display<T, DCPin, RSTPin>,
|
||||
position: Position,
|
||||
) -> Result<(), QoiErr> {
|
||||
let mut iter = image.iter();
|
||||
if let (Some(113), Some(111), Some(105), Some(102)) =
|
||||
(iter.next(), iter.next(), iter.next(), iter.next())
|
||||
{
|
||||
fn next(iter: &mut dyn Iterator<Item = u8>) -> Result<u8, QoiErr> {
|
||||
iter.next().ok_or(QoiErr::UnexpectedEOF)
|
||||
}
|
||||
|
||||
let width = u32::from_be_bytes([
|
||||
next(&mut iter)?,
|
||||
next(&mut iter)?,
|
||||
next(&mut iter)?,
|
||||
next(&mut iter)?,
|
||||
]) as u16;
|
||||
let height = u32::from_be_bytes([
|
||||
next(&mut iter)?,
|
||||
next(&mut iter)?,
|
||||
next(&mut iter)?,
|
||||
next(&mut iter)?,
|
||||
]) as u16;
|
||||
let _channels = next(&mut iter)?;
|
||||
let _colorspace = next(&mut iter)?;
|
||||
|
||||
ufmt::uwriteln!(serial, "Successfully read QOI header").unwrap();
|
||||
|
||||
let scale_factor = 240 / width;
|
||||
|
||||
display.set_window(
|
||||
position,
|
||||
Position {
|
||||
x: position.x + (width * scale_factor) - 1,
|
||||
y: position.y + (height * scale_factor) - 1,
|
||||
},
|
||||
);
|
||||
|
||||
let qoi_iter = QoiIterator::from(&mut iter, width * height);
|
||||
let scale_iter = ScaleIterator {
|
||||
qoi: qoi_iter,
|
||||
last_row: [Rgb565::yellow(); 120],
|
||||
scale_factor: scale_factor as usize,
|
||||
width: width as usize,
|
||||
counter: 0,
|
||||
index: 0,
|
||||
};
|
||||
|
||||
let mut counter = 0u32;
|
||||
for pixel in scale_iter {
|
||||
let [c1, c2] = pixel.as_color().bytes;
|
||||
display.write(Writeable::Data(&[c1, c2]));
|
||||
counter += 1;
|
||||
}
|
||||
ufmt::uwriteln!(serial, "Counter: {}", counter).unwrap();
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(QoiErr::InvalidMagicNumber)
|
||||
}
|
||||
}
|
||||
|
||||
struct ScaleIterator<'a> {
|
||||
qoi: QoiIterator<'a>,
|
||||
last_row: [Rgb565; 120],
|
||||
width: usize,
|
||||
scale_factor: usize,
|
||||
counter: usize,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ScaleIterator<'a> {
|
||||
type Item = Rgb565;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.scale_factor == 1 {
|
||||
return self.qoi.next();
|
||||
}
|
||||
|
||||
if self.index >= self.width * self.scale_factor {
|
||||
self.counter = (self.counter + 1) % self.scale_factor;
|
||||
self.index = 0;
|
||||
}
|
||||
let index_div = self.index / self.scale_factor;
|
||||
|
||||
if self.counter % self.scale_factor == 0 {
|
||||
if (self.index % self.scale_factor) == 0 {
|
||||
if let Some(pixel) = self.qoi.next() {
|
||||
self.last_row[index_div] = pixel;
|
||||
self.index += 1;
|
||||
Some(pixel)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let pixel = self.last_row[index_div];
|
||||
self.index += 1;
|
||||
Some(pixel)
|
||||
}
|
||||
} else {
|
||||
let pixel = self.last_row[index_div];
|
||||
self.index += 1;
|
||||
Some(pixel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct QoiIterator<'a> {
|
||||
inner: &'a mut dyn Iterator<Item = u8>,
|
||||
prev_pixels: [Rgb565; 64],
|
||||
last_pixel: Rgb565,
|
||||
repeat: u8,
|
||||
expected_colors: u16,
|
||||
parsed_colors: u16,
|
||||
}
|
||||
|
||||
impl<'a> QoiIterator<'a> {
|
||||
fn from(bytes: &'a mut dyn Iterator<Item = u8>, expected: u16) -> Self {
|
||||
QoiIterator {
|
||||
inner: bytes,
|
||||
prev_pixels: [Rgb565(0, 255, 0); 64],
|
||||
last_pixel: Rgb565(0, 0, 0),
|
||||
repeat: 0,
|
||||
expected_colors: expected,
|
||||
parsed_colors: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for QoiIterator<'a> {
|
||||
type Item = Rgb565;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.parsed_colors >= self.expected_colors {
|
||||
return None;
|
||||
}
|
||||
self.parsed_colors += 1;
|
||||
|
||||
if self.repeat > 0 {
|
||||
self.repeat -= 1;
|
||||
return Some(self.last_pixel);
|
||||
}
|
||||
if let Some(byte) = self.inner.next() {
|
||||
let color = if byte == 0xff {
|
||||
let red = self.inner.next().unwrap();
|
||||
let green = self.inner.next().unwrap();
|
||||
let blue = self.inner.next().unwrap();
|
||||
let _alpha = self.inner.next().unwrap();
|
||||
Rgb565(red, green, blue)
|
||||
} else if byte == 0b11111110 {
|
||||
let red = self.inner.next()?;
|
||||
let green = self.inner.next()?;
|
||||
let blue = self.inner.next()?;
|
||||
Rgb565(red, green, blue)
|
||||
} else {
|
||||
let tag = (0b11000000 & byte) >> 6;
|
||||
let data = 0b00111111 & byte;
|
||||
|
||||
if tag == 0 {
|
||||
self.prev_pixels[data as usize]
|
||||
} else if tag == 1 {
|
||||
let dr = ((0b110000 & data) >> 4) as i8 - 2;
|
||||
let dg = ((0b001100 & data) >> 2) as i8 - 2;
|
||||
let db = (0b000011 & data) as i8 - 2;
|
||||
Rgb565(
|
||||
(self.last_pixel.0 as i8).wrapping_add(dr) as u8,
|
||||
(self.last_pixel.1 as i8).wrapping_add(dg) as u8,
|
||||
(self.last_pixel.2 as i8).wrapping_add(db) as u8,
|
||||
)
|
||||
} else if tag == 2 {
|
||||
let second = self.inner.next()?;
|
||||
let dg = data as i8 - 32;
|
||||
let dr_dg = ((0b11110000 & second) >> 4) as i8 - 8;
|
||||
let db_dg = (0b00001111 & second) as i8 - 8;
|
||||
let dr = dr_dg + dg;
|
||||
let db = db_dg + dg;
|
||||
|
||||
Rgb565(
|
||||
(self.last_pixel.0 as i8 + dr) as u8,
|
||||
(self.last_pixel.1 as i8 + dg) as u8,
|
||||
(self.last_pixel.2 as i8 + db) as u8,
|
||||
)
|
||||
} else if tag == 3 {
|
||||
// QOI_OP_RUN
|
||||
self.repeat = data;
|
||||
self.last_pixel
|
||||
} else {
|
||||
Rgb565::green()
|
||||
}
|
||||
};
|
||||
self.last_pixel = color;
|
||||
let hash =
|
||||
((color.0 as u32 * 3 + color.1 as u32 * 5 + color.2 as u32 * 7 + 255 as u32 * 11)
|
||||
% 64) as usize;
|
||||
|
||||
self.prev_pixels[hash] = color;
|
||||
Some(color)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user