Compare commits

..

No commits in common. "1d575d06b1c2a686fbce8f28418e69efa01028cc" and "6196d41c930a072fe327d515e7d7062ff63c124a" have entirely different histories.

14 changed files with 88 additions and 987 deletions

View File

@ -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
View File

@ -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"

View File

@ -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"]

View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +0,0 @@
[toolchain]
channel = "nightly-2025-04-27"
components = [
"rustc",
"cargo",
"clippy",
"rustfmt",
"rust-src",
"rust-analyzer",
]
profile = "minimal"

View File

@ -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));
}
}
}

View File

@ -1,9 +0,0 @@
fn letter(character: char) -> [u8; 8] {
match character {
'a' => [
0b01111110, 0b01000010, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b10000001,
0b10000001,
],
_ => [0; 8],
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}
}