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
ATmega328p-microcontroller) using a simple button and a 240x240 LCD display with
the ST7789-driver over
This project is a relatively simple slideshow running on the Adafruit Feather
328P (an ATmega328p-microcontroller) using a simple button and a 240x240 LCD
display with the ST7789-driver over
[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
the most significant being [`atmega-hal`](https://github.com/Rahix/avr-hal), a
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)
}
pub fn black() -> Rgb565 {
Rgb565(0, 0, 0)
}
pub fn white() -> Rgb565 {
Rgb565(255, 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
}
@ -74,17 +66,6 @@ pub struct Vec2 {
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)]
pub struct Color {
pub bytes: [u8; 2],

View File

@ -1,375 +1,9 @@
use core::{
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] {
fn letter(character: char) -> [u8; 8] {
match character {
'a' => addr_of!(A),
'b' => addr_of!(B),
'c' => addr_of!(C),
'd' => addr_of!(D),
'e' => addr_of!(E),
'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)
}
}
'a' => [
0b01111110, 0b01000010, 0b10000001, 0b11111111, 0b10000001, 0b10000001, 0b10000001,
0b10000001,
],
_ => [0; 8],
}
}

View File

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

View File

@ -11,21 +11,16 @@
use core::ptr::addr_of;
use atmega_hal::{
Adc, Usart, Wdt,
adc::AdcSettings,
delay,
Usart,
spi::{self, Settings},
usart::{Baudrate, UsartOps},
wdt::WdtOps,
usart::Baudrate,
};
use embedded_hal::delay::DelayNs;
use panic_halt as _;
use crate::{
display::{Display, Rgb565, Vec2},
font::{draw_number, draw_text},
graphics::{Image, LARGE_CAT_UNSAFE, draw_image},
peripherals::{Button, Knob},
display::{Display, Vec2},
graphics::{Image, LARGE_CAT_UNSAFE, PRESS_BTN_UNSAFE, draw_image},
peripherals::Button,
};
mod display;
@ -80,108 +75,35 @@ fn main() -> ! {
display.init();
let mut adc = Adc::new(dp.ADC, AdcSettings::default());
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 len = images.len();
let mut delay = atmega_hal::delay::Delay::<CoreClock>::new();
let mut position = Vec2 { x: 10, y: 10 };
let original_position = position.clone();
draw_text(
match draw_image(
&mut serial,
&mut Image::from(addr_of!(PRESS_BTN_UNSAFE)).iter(),
&mut display,
"dice:\n",
Rgb565::white(),
Rgb565::black(),
&mut position,
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;
Vec2 { x: 0, y: 0 },
) {
Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(),
Err(e) => ufmt::uwriteln!(serial, "Error: {:?}", e).unwrap(),
}
loop {
clock += 1;
if button.poll() {
animation = 50;
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(
match draw_image(
&mut serial,
&mut Image::from(addr_of!(LARGE_CAT_UNSAFE)).iter(),
&mut images[idx].iter(),
&mut display,
Vec2 { x: 0, y: 0 },
)
.unwrap();
cat_received = true;
) {
Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(),
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>,
{
pub fn poll(&mut self) -> f32 {
self.raw() as f32 / 1024f32
}
pub fn raw(&mut self) -> u16 {
self.pin.analog_read(&mut self.adc)
let read = self.pin.analog_read(&mut self.adc);
read as f32 / 1024f32
}
}