Compare commits

..

No commits in common. "8643aa6fcc6411cda4965d938c7ca9b4d0935387" and "e6a712bb96282c62923af8033f609e47a3aa85a5" have entirely different histories.

7 changed files with 40 additions and 511 deletions

View File

@ -1,16 +1,10 @@
# Dice game # Slideshow
This project is a simple dice-game running on the Adafruit Feather 328P (an This project is a relatively simple slideshow running on the Adafruit Feather
ATmega328p-microcontroller) using a simple button and a 240x240 LCD display with 328P (an ATmega328p-microcontroller) using a simple button and a 240x240 LCD
the ST7789-driver over display with the ST7789-driver over
[SPI](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface). [SPI](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface).
In the game you roll a number between 1 and 10, and if you get a 10, you are
awarded a picture of a high-quality cat stored in program-memory!
This project is a direct continuation of my earlier
[slideshow](https://git.teascade.net/teascade/slideshow)
This project is written on Rust and uses a very minimal amount of libraries with This project is written on Rust and uses a very minimal amount of libraries with
the most significant being [`atmega-hal`](https://github.com/Rahix/avr-hal), a the most significant being [`atmega-hal`](https://github.com/Rahix/avr-hal), a
hardware abstraction layer for ATmega-microcontrollers. Images are stored in the hardware abstraction layer for ATmega-microcontrollers. Images are stored in the

Binary file not shown.

View File

@ -43,14 +43,6 @@ impl Rgb565 {
Rgb565(0, 255, 255) Rgb565(0, 255, 255)
} }
pub fn black() -> Rgb565 {
Rgb565(0, 0, 0)
}
pub fn white() -> Rgb565 {
Rgb565(255, 255, 255)
}
pub fn qoi_hash(&self) -> usize { 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 ((self.0 as u32 * 3 + self.1 as u32 * 5 + self.2 as u32 * 7 + 255 * 11) % 64) as usize
} }
@ -74,17 +66,6 @@ pub struct Vec2 {
pub y: u16, pub y: u16,
} }
impl Mul<u16> for Vec2 {
type Output = Vec2;
fn mul(self, rhs: u16) -> Self::Output {
Vec2 {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
pub struct Color { pub struct Color {
pub bytes: [u8; 2], pub bytes: [u8; 2],

View File

@ -1,375 +1,9 @@
use core::{ fn letter(character: char) -> [u8; 8] {
arch::asm,
ptr::{addr_of, null},
};
use atmega_hal::port::PinOps;
use embedded_hal::delay::DelayNs;
use crate::{
display::{Display, Rgb565, Vec2},
graphics::draw_stream,
};
#[unsafe(link_section = ".progmem.data")]
pub static A: [u8; 8] = [
0b01111110, 0b01000010, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b10000001, 0b10000001,
];
#[unsafe(link_section = ".progmem.data")]
pub static B: [u8; 8] = [
0b11111100, 0b10000010, 0b10000010, 0b10001100, 0b10000010, 0b10000001, 0b10000001, 0b11111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static C: [u8; 8] = [
0b11111110, 0b10000001, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000001, 0b11111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static D: [u8; 8] = [
0b11111100, 0b10000010, 0b10000010, 0b10000001, 0b10000001, 0b10000010, 0b10000010, 0b11111100,
];
#[unsafe(link_section = ".progmem.data")]
pub static E: [u8; 8] = [
0b11111111, 0b10000000, 0b10000000, 0b11111000, 0b10000000, 0b10000000, 0b10000000, 0b11111111,
];
#[unsafe(link_section = ".progmem.data")]
pub static F: [u8; 8] = [
0b11111111, 0b10000000, 0b10000000, 0b11111000, 0b10000000, 0b10000000, 0b10000000, 0b10000000,
];
#[unsafe(link_section = ".progmem.data")]
pub static G: [u8; 8] = [
0b11111110, 0b10000001, 0b10000000, 0b10000000, 0b10011111, 0b10010001, 0b01000010, 0b00111100,
];
#[unsafe(link_section = ".progmem.data")]
pub static H: [u8; 8] = [
0b10000001, 0b10000001, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b10000001, 0b10000001,
];
#[unsafe(link_section = ".progmem.data")]
pub static I: [u8; 8] = [
0b00111100, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00111100,
];
#[unsafe(link_section = ".progmem.data")]
pub static J: [u8; 8] = [
0b00001110, 0b00000010, 0b00000010, 0b00000010, 0b00000010, 0b10000010, 0b10000010, 0b01111100,
];
#[unsafe(link_section = ".progmem.data")]
pub static K: [u8; 8] = [
0b01000010, 0b01000100, 0b01001000, 0b01110000, 0b01001000, 0b01000100, 0b01000010, 0b01000001,
];
#[unsafe(link_section = ".progmem.data")]
pub static L: [u8; 8] = [
0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b11111111,
];
#[unsafe(link_section = ".progmem.data")]
pub static M: [u8; 8] = [
0b00100100, 0b11011011, 0b10011001, 0b10011001, 0b10011001, 0b10000001, 0b10000001, 0b10000001,
];
#[unsafe(link_section = ".progmem.data")]
pub static N: [u8; 8] = [
0b11000001, 0b10100001, 0b10010001, 0b10001001, 0b10001001, 0b10000101, 0b10000111, 0b10000011,
];
#[unsafe(link_section = ".progmem.data")]
pub static O: [u8; 8] = [
0b11111111, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b11111111,
];
#[unsafe(link_section = ".progmem.data")]
pub static P: [u8; 8] = [
0b11111111, 0b10000001, 0b10000001, 0b11111111, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
];
#[unsafe(link_section = ".progmem.data")]
pub static Q: [u8; 8] = [
0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b10001001, 0b10000101, 0b10000011, 0b01111111,
];
#[unsafe(link_section = ".progmem.data")]
pub static R: [u8; 8] = [
0b11111111, 0b10000001, 0b10000001, 0b11111111, 0b11111100, 0b10000010, 0b10000001, 0b10000001,
];
#[unsafe(link_section = ".progmem.data")]
pub static S: [u8; 8] = [
0b01111111, 0b10000000, 0b10000000, 0b01111110, 0b00000001, 0b00000001, 0b00000001, 0b11111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static T: [u8; 8] = [
0b11111111, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000,
];
#[unsafe(link_section = ".progmem.data")]
pub static U: [u8; 8] = [
0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b01111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static V: [u8; 8] = [
0b10000001, 0b10000001, 0b01000010, 0b01000010, 0b00100100, 0b00100100, 0b00100100, 0b00011000,
];
#[unsafe(link_section = ".progmem.data")]
pub static W: [u8; 8] = [
0b10000001, 0b10000001, 0b10000001, 0b10000001, 0b10011001, 0b01011010, 0b00100100, 0b00100100,
];
#[unsafe(link_section = ".progmem.data")]
pub static X: [u8; 8] = [
0b10000001, 0b01000010, 0b00100100, 0b00011000, 0b00011000, 0b00100100, 0b01000010, 0b10000001,
];
#[unsafe(link_section = ".progmem.data")]
pub static Y: [u8; 8] = [
0b10000001, 0b01000010, 0b00100100, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000,
];
#[unsafe(link_section = ".progmem.data")]
pub static Z: [u8; 8] = [
0b11111111, 0b00000111, 0b00001100, 0b00011000, 0b00110000, 0b01100000, 0b11000000, 0b11111111,
];
#[unsafe(link_section = ".progmem.data")]
pub static EXCLAMATION: [u8; 8] = [
0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000, 0b01000000,
];
#[unsafe(link_section = ".progmem.data")]
pub static COMMA: [u8; 8] = [
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b01000000, 0b10000000,
];
#[unsafe(link_section = ".progmem.data")]
pub static COLON: [u8; 8] = [
0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b01100000, 0b01100000,
];
#[unsafe(link_section = ".progmem.data")]
pub static EMPTY: [u8; 8] = [
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_1: [u8; 8] = [
0b00011000, 0b00111000, 0b01111000, 0b00011000, 0b00011000, 0b00011000, 0b00011000, 0b00011000,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_2: [u8; 8] = [
0b11111110, 0b11110110, 0b00001100, 0b00011000, 0b00110000, 0b01100000, 0b11111111, 0b11111111,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_3: [u8; 8] = [
0b01111110, 0b10000001, 0b00011110, 0b00011110, 0b00000001, 0b10000001, 0b11111111, 0b01111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_4: [u8; 8] = [
0b11000110, 0b11000110, 0b11000110, 0b11111111, 0b11111110, 0b00000110, 0b00000110, 0b00000110,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_5: [u8; 8] = [
0b11111111, 0b11111111, 0b11000000, 0b11111100, 0b00000110, 0b11000011, 0b11111111, 0b01111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_6: [u8; 8] = [
0b01111110, 0b11000011, 0b11000000, 0b11111110, 0b11000011, 0b11000011, 0b11000011, 0b01111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_7: [u8; 8] = [
0b11111111, 0b11111111, 0b00000110, 0b00001100, 0b00011000, 0b00110000, 0b01100000, 0b11000000,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_8: [u8; 8] = [
0b01111110, 0b11111111, 0b11000011, 0b01111110, 0b11000011, 0b11000011, 0b11111111, 0b01111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_9: [u8; 8] = [
0b01111110, 0b11000011, 0b11000011, 0b01111111, 0b00000011, 0b11000011, 0b11000011, 0b01111110,
];
#[unsafe(link_section = ".progmem.data")]
pub static NUM_0: [u8; 8] = [
0b01111110, 0b11000111, 0b11001011, 0b11001011, 0b11010011, 0b11010011, 0b11100011, 0b01111110,
];
fn letter(character: char) -> *const [u8; 8] {
match character { match character {
'a' => addr_of!(A), 'a' => [
'b' => addr_of!(B), 0b01111110, 0b01000010, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b10000001,
'c' => addr_of!(C), 0b10000001,
'd' => addr_of!(D), ],
'e' => addr_of!(E), _ => [0; 8],
'f' => addr_of!(F),
'g' => addr_of!(G),
'h' => addr_of!(H),
'i' => addr_of!(I),
'j' => addr_of!(J),
'k' => addr_of!(K),
'l' => addr_of!(L),
'm' => addr_of!(M),
'n' => addr_of!(N),
'o' => addr_of!(O),
'p' => addr_of!(P),
'q' => addr_of!(Q),
'r' => addr_of!(R),
's' => addr_of!(S),
't' => addr_of!(T),
'u' => addr_of!(U),
'v' => addr_of!(V),
'w' => addr_of!(W),
'x' => addr_of!(X),
'y' => addr_of!(Y),
'z' => addr_of!(Z),
'0' => addr_of!(NUM_0),
'1' => addr_of!(NUM_1),
'2' => addr_of!(NUM_2),
'3' => addr_of!(NUM_3),
'4' => addr_of!(NUM_4),
'5' => addr_of!(NUM_5),
'6' => addr_of!(NUM_6),
'7' => addr_of!(NUM_7),
'8' => addr_of!(NUM_8),
'9' => addr_of!(NUM_9),
'!' => addr_of!(EXCLAMATION),
',' => addr_of!(COMMA),
':' => addr_of!(COLON),
_ => addr_of!(EMPTY),
}
}
pub fn draw_text<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
display: &mut Display<T, DCPin, RSTPin>,
text: &str,
fg: Rgb565,
bg: Rgb565,
position: &mut Vec2,
scale: u16,
) {
let kerning = 9 * scale;
let original_x = position.x;
for line in text.split('\n') {
for word in line.split(' ') {
let word_length = word.len() as u16 * kerning;
if position.x + word_length > 240 {
position.x = original_x;
position.y += kerning;
}
for c in word.chars() {
draw_character(display, c, fg, bg, original_x, position, scale);
}
draw_character(display, ' ', fg, bg, original_x, position, scale);
}
position.y += kerning;
position.x = original_x;
}
}
pub fn draw_character<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
display: &mut Display<T, DCPin, RSTPin>,
character: char,
fg: Rgb565,
bg: Rgb565,
original_x: u16,
position: &mut Vec2,
scale: u16,
) {
let kerning = 9 * scale;
if position.x + kerning > 240 {
position.x = original_x;
position.y += kerning;
}
if position.y + kerning > 240 {
position.y = 0;
}
Letter::from(character, fg, bg)
.iter()
.draw(display, *position, scale);
position.x += kerning;
}
pub fn draw_number<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
display: &mut Display<T, DCPin, RSTPin>,
number: u32,
fg: Rgb565,
bg: Rgb565,
position: &mut Vec2,
scale: u16,
) {
if number >= 10 {
draw_number(display, number / 10, fg, bg, position, scale);
}
let character = match number % 10 {
0 => '0',
1 => '1',
2 => '2',
3 => '3',
4 => '4',
5 => '5',
6 => '6',
7 => '7',
8 => '8',
9 => '9',
_ => 'X',
};
draw_character(display, character, fg, bg, position.x, position, scale);
}
pub struct Letter {
base: [u8; 8],
pub fg: Rgb565,
pub bg: Rgb565,
}
impl Letter {
pub fn from(character: char, fg: Rgb565, bg: Rgb565) -> Letter {
let mut out = [0u8; 8];
let out_ptr = out.as_mut_ptr();
let ptr_addr = letter(character);
unsafe {
asm!(
// Load value of Z to temporary register $1 and post-increment Z
"lpm {1}, Z+",
// Store value from register $1 to X and post-increment X
"st X+, {1}",
// Subtract loop counter at register $0
"subi {0}, 1",
// If equality failed, jump back 8 bytes (or 4 instructions)
"brne -8",
inout(reg) 8u8 => _,
out(reg) _,
inout("Z") ptr_addr => _,
inout("X") out_ptr => _
)
}
Letter { base: out, fg, bg }
}
pub fn iter<'a>(&'a self) -> LetterIter<'a> {
LetterIter {
letter: &self,
idx: 0,
}
}
}
pub struct LetterIter<'a> {
letter: &'a Letter,
idx: usize,
}
impl<'a> LetterIter<'a> {
pub fn draw<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
mut self,
display: &mut Display<T, DCPin, RSTPin>,
position: Vec2,
scale: u16,
) {
draw_stream(&mut self, display, position, Vec2 { x: 8, y: 8 }, scale);
}
}
impl<'a> Iterator for LetterIter<'a> {
type Item = Rgb565;
fn next(&mut self) -> Option<Self::Item> {
let byte_idx = self.idx / 8;
let bit_idx = 7 - (self.idx % 8);
if byte_idx >= 8 {
None
} else {
self.idx += 1;
let flag = (self.letter.base[byte_idx] & (1 << bit_idx)) >> bit_idx;
if flag == 1 {
Some(self.letter.fg)
} else {
Some(self.letter.bg)
}
}
} }
} }

View File

@ -108,8 +108,7 @@ pub fn draw_image<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
y: height, y: height,
}, },
scale_factor, scale_factor,
); )
Ok(())
} else { } else {
Err(QoiErr::InvalidMagicNumber) Err(QoiErr::InvalidMagicNumber)
} }
@ -121,7 +120,7 @@ pub fn draw_stream<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
position: Vec2, position: Vec2,
scale: Vec2, scale: Vec2,
scale_factor: u16, scale_factor: u16,
) { ) -> Result<(), QoiErr> {
let scale_iter = ScaleIterator { let scale_iter = ScaleIterator {
iter: stream, iter: stream,
last_row: [Rgb565::yellow(); 120], last_row: [Rgb565::yellow(); 120],
@ -143,6 +142,8 @@ pub fn draw_stream<T: DelayNs, DCPin: PinOps, RSTPin: PinOps>(
let [c1, c2] = pixel.as_color().bytes; let [c1, c2] = pixel.as_color().bytes;
display.write(Writeable::Data(&[c1, c2])); display.write(Writeable::Data(&[c1, c2]));
} }
Ok(())
} }
struct ScaleIterator<'a> { struct ScaleIterator<'a> {
iter: &'a mut dyn Iterator<Item = Rgb565>, iter: &'a mut dyn Iterator<Item = Rgb565>,

View File

@ -11,21 +11,16 @@
use core::ptr::addr_of; use core::ptr::addr_of;
use atmega_hal::{ use atmega_hal::{
Adc, Usart, Wdt, Usart,
adc::AdcSettings,
delay,
spi::{self, Settings}, spi::{self, Settings},
usart::{Baudrate, UsartOps}, usart::Baudrate,
wdt::WdtOps,
}; };
use embedded_hal::delay::DelayNs;
use panic_halt as _; use panic_halt as _;
use crate::{ use crate::{
display::{Display, Rgb565, Vec2}, display::{Display, Vec2},
font::{draw_number, draw_text}, graphics::{Image, LARGE_CAT_UNSAFE, PRESS_BTN_UNSAFE, draw_image},
graphics::{Image, LARGE_CAT_UNSAFE, draw_image}, peripherals::Button,
peripherals::{Button, Knob},
}; };
mod display; mod display;
@ -80,108 +75,35 @@ fn main() -> ! {
display.init(); display.init();
let mut adc = Adc::new(dp.ADC, AdcSettings::default());
let mut button = Button::from(pins.pd5.into_pull_up_input()); let mut button = Button::from(pins.pd5.into_pull_up_input());
let mut knob = Knob {
pin: pins.pc1.into_analog_input(&mut adc),
adc,
};
let mut idx = 0;
let images = [Image::from(addr_of!(LARGE_CAT_UNSAFE))]; let images = [Image::from(addr_of!(LARGE_CAT_UNSAFE))];
let len = images.len();
let mut delay = atmega_hal::delay::Delay::<CoreClock>::new(); match draw_image(
&mut serial,
let mut position = Vec2 { x: 10, y: 10 }; &mut Image::from(addr_of!(PRESS_BTN_UNSAFE)).iter(),
let original_position = position.clone();
draw_text(
&mut display, &mut display,
"dice:\n", Vec2 { x: 0, y: 0 },
Rgb565::white(), ) {
Rgb565::black(), Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(),
&mut position, Err(e) => ufmt::uwriteln!(serial, "Error: {:?}", e).unwrap(),
3, }
);
let number_pos = position.clone();
draw_number(
&mut display,
0,
Rgb565::white(),
Rgb565::black(),
&mut number_pos.clone(),
3,
);
let mut clock = 0u32;
let mut animation = 0;
let mut animation_reached = true;
let mut cat_received = false;
let max_number = 10;
loop { loop {
clock += 1;
if button.poll() { if button.poll() {
animation = 50; match draw_image(
animation_reached = false;
if cat_received {
display.draw_rect(
Vec2 { x: 0, y: 0 },
Vec2 { x: 240, y: 240 },
Rgb565::black().as_color(),
);
draw_text(
&mut display,
"dice:\n",
Rgb565::white(),
Rgb565::black(),
&mut original_position.clone(),
3,
);
cat_received = false;
}
}
if animation > 0 {
let modulo = match animation {
50..100 => 5,
20..50 => 10,
0..20 => 20,
_ => 10,
};
animation -= 1;
if animation % modulo == 0 {
let random = (((clock * 543_128) ^ 7_643_125) & 0b11111111) as f32 / 255f32;
let dice = (random * max_number as f32) as u32;
draw_number(
&mut display,
dice,
Rgb565::white(),
Rgb565::black(),
&mut number_pos.clone(),
3,
);
}
} else if !animation_reached {
animation_reached = true;
let random = (((clock * 543_128) ^ 7_643_125) & 0b11111111) as f32 / 255f32;
let dice = (random * max_number as f32 + 1f32) as u32;
draw_number(
&mut display,
dice,
Rgb565::white(),
Rgb565::black(),
&mut number_pos.clone(),
3,
);
if dice == max_number {
delay.delay_ms(1000);
draw_image(
&mut serial, &mut serial,
&mut Image::from(addr_of!(LARGE_CAT_UNSAFE)).iter(), &mut images[idx].iter(),
&mut display, &mut display,
Vec2 { x: 0, y: 0 }, Vec2 { x: 0, y: 0 },
) ) {
.unwrap(); Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(),
cat_received = true; Err(e) => ufmt::uwriteln!(serial, "Error: {:?}", e).unwrap(),
} }
idx = (idx + 1) % len;
} }
} }
} }

View File

@ -51,10 +51,7 @@ where
Pin<mode::Analog, T>: AdcChannel<Atmega, ADC>, Pin<mode::Analog, T>: AdcChannel<Atmega, ADC>,
{ {
pub fn poll(&mut self) -> f32 { pub fn poll(&mut self) -> f32 {
self.raw() as f32 / 1024f32 let read = self.pin.analog_read(&mut self.adc);
} read as f32 / 1024f32
pub fn raw(&mut self) -> u16 {
self.pin.analog_read(&mut self.adc)
} }
} }