A => .cargo/config.toml +5 -0
@@ 1,5 @@
+[build]
+target = "avr-specs/avr-atmega8.json"
+
+[unstable]
+build-std = ["core"]
A => .github/Dockerfile.ci +22 -0
@@ 1,22 @@
+FROM ubuntu:18.04
+
+RUN useradd -m avr-rust
+
+# Install dependencies
+RUN apt-get update -y && apt-get install -y wget gcc binutils gcc-avr avr-libc
+
+RUN mkdir -p /code && chown avr-rust:avr-rust /code
+
+USER avr-rust
+
+# Install Rustup along with nightly
+RUN wget -q https://sh.rustup.rs -O /tmp/rustup.sh && sh /tmp/rustup.sh -y --profile minimal --default-toolchain nightly -c rust-src --quiet
+ENV PATH=/home/avr-rust/.cargo/bin:$PATH
+
+COPY --chown=avr-rust:avr-rust . /code
+
+WORKDIR /code
+
+ENV AVR_CPU_FREQUENCY_HZ=16000000
+
+ENTRYPOINT ["cargo"]
A => .github/workflows/docker-image.yml +23 -0
@@ 1,23 @@
+name: Test suite
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+ schedule:
+ - cron: "0 2 * * 1-5"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Prepare the Rust build environment
+ run:
+ docker build . --file .github/Dockerfile.ci --tag rust-avr-ci:$GITHUB_RUN_NUMBER
+
+ - name: Compile the crate
+ run:
+ docker run rust-avr-ci:$GITHUB_RUN_NUMBER build --release
A => .gitignore +1 -0
A => Cargo.lock +199 -0
@@ 1,199 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "atmega-hal"
+version = "0.1.0"
+dependencies = [
+ "avr-device",
+ "avr-hal-generic",
+]
+
+[[package]]
+name = "avr-device"
+version = "0.4.0"
+dependencies = [
+ "avr-device-macros",
+ "bare-metal",
+ "cfg-if",
+ "vcell",
+]
+
+[[package]]
+name = "avr-device-macros"
+version = "0.4.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "avr-hal-generic"
+version = "0.1.0"
+dependencies = [
+ "avr-device",
+ "cfg-if",
+ "embedded-hal",
+ "embedded-storage",
+ "nb 0.1.3",
+ "paste",
+ "rustversion",
+ "ufmt",
+ "void",
+]
+
+[[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 = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[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-storage"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
+
+[[package]]
+name = "guess-the-number"
+version = "0.1.0"
+dependencies = [
+ "atmega-hal",
+ "avr-hal-generic",
+ "nb 0.1.3",
+ "panic-halt",
+ "ufmt",
+]
+
+[[package]]
+name = "nb"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
+dependencies = [
+ "nb 1.0.0",
+]
+
+[[package]]
+name = "nb"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
+
+[[package]]
+name = "panic-halt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812"
+
+[[package]]
+name = "paste"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
+
+[[package]]
+name = "syn"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "ufmt"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31d3c0c63312dfc9d8e5c71114d617018a19f6058674003c0da29ee8d8036cdd"
+dependencies = [
+ "proc-macro-hack",
+ "ufmt-macros",
+ "ufmt-write",
+]
+
+[[package]]
+name = "ufmt-macros"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ab6c92f30c996394a8bd525aef9f03ce01d0d7ac82d81902968057e37dd7d9"
+dependencies = [
+ "proc-macro-hack",
+ "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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[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"
A => Cargo.toml +36 -0
@@ 1,36 @@
+[package]
+name = "guess-the-number"
+version = "0.1.0"
+authors = ["František Boháček <fandabohacek@gmail.com>"]
+edition = "2021"
+
+[[bin]]
+path = "src/entrypoint.rs"
+name = "guess-the-number"
+test = false
+bench = false
+
+[dependencies]
+panic-halt = "0.2.0"
+ufmt = "0.1.0"
+nb = "0.1.2"
+
+[dependencies.atmega-hal]
+path = "../avr-hal/mcu/atmega-hal"
+features = ["rt","atmega8"]
+
+[dependencies.avr-hal-generic]
+path = "../avr-hal/avr-hal-generic"
+
+[profile.dev]
+panic = "abort"
+lto = true
+opt-level = "s"
+
+[profile.release]
+panic = "abort"
+strip = true
+codegen-units = 1
+debug = false
+lto = true
+opt-level = "s"
A => README.md +28 -0
@@ 1,28 @@
+# Rust AVR executable template
+
+A template for Rust based AVR executables. Use with cargo-generate:
+
+```
+ cargo generate --git https://github.com/astepkowski/rust-avr-template.git
+```
+then select AVR family (ATmega, ATtiny) and chip.
+
+**NOTE**: This software template repository is offered in the public domain. It is free to use, adapt, modify, distribute, with no restrictions and no crediting required.
+
+## Build instructions
+
+Install Rust nightly.
+
+```
+rustup toolchain install nightly-2022-06-13
+rustup component add rust-src --toolchain nightly-2022-06-13
+```
+
+Then run:
+
+```
+cargo build --release
+```
+
+The final ELF executable file will then be available at `target/avr-<mcu_name>/release/template-bin.elf`.
+
A => avr-specs/avr-atmega1280.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "atmega1280",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=atmega1280",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-atmega168.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "atmega168",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=atmega168",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-atmega2560.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "atmega2560",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=atmega2560",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-atmega328p.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "atmega328p",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=atmega328p",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-atmega32u4.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "atmega32u4",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=atmega32u4",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-atmega48p.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "atmega48p",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=atmega48p",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-atmega8.json +31 -0
@@ 1,31 @@
+{
+ "arch": "avr",
+ "cpu": "atmega8",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "env": "",
+ "executables": true,
+ "linker": "avr-gcc",
+ "linker-flavor": "gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "os": "unknown",
+ "position-independent-executables": false,
+ "exe-suffix": ".elf",
+ "eh-frame-header": false,
+ "pre-link-args": {
+ "gcc": [
+ "-Os",
+ "-mmcu=atmega8"
+ ]
+ },
+ "late-link-args": {
+ "gcc": [
+ "-lc",
+ "-lgcc"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-endian": "little",
+ "target-pointer-width": "16",
+ "vendor": "unknown"
+}
A => avr-specs/avr-attiny85.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "attiny85",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=attiny85",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => avr-specs/avr-attiny88.json +27 -0
@@ 1,27 @@
+{
+ "arch": "avr",
+ "atomic-cas": false,
+ "cpu": "attiny88",
+ "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
+ "eh-frame-header": false,
+ "exe-suffix": ".elf",
+ "executables": true,
+ "late-link-args": {
+ "gcc": [
+ "-lgcc"
+ ]
+ },
+ "linker": "avr-gcc",
+ "linker-is-gnu": true,
+ "llvm-target": "avr-unknown-unknown",
+ "max-atomic-width": 8,
+ "no-default-libraries": false,
+ "pre-link-args": {
+ "gcc": [
+ "-mmcu=attiny88",
+ "-Wl,--as-needed"
+ ]
+ },
+ "target-c-int-width": "16",
+ "target-pointer-width": "16"
+}
A => main.rs +0 -0
A => rust-toolchain.toml +2 -0
@@ 1,2 @@
+[toolchain]
+channel = "nightly-2022-05-10"
A => src/animation.rs +172 -0
@@ 1,172 @@
+use super::filled_seven_segment;
+use super::led_matrix;
+
+#[derive(PartialEq, Eq)]
+pub enum AnimationState {
+ Running,
+ End
+}
+
+pub trait Animation {
+ fn step(&mut self, seven_segment: &mut filled_seven_segment::FilledSevenSegment, led_matrix: &mut led_matrix::LEDMatrix) -> AnimationState;
+ fn cleanup(&mut self, seven_segment: &mut filled_seven_segment::FilledSevenSegment, led_matrix: &mut led_matrix::LEDMatrix);
+ fn running(&self) -> bool;
+}
+
+pub struct HelloAnimation {
+ pub inner_step: u16,
+ pub outer_step: u16,
+ pub hidden: bool
+}
+
+pub struct WinAnimation {
+ pub number: u16,
+ pub led_step: u8,
+ pub led_quarter: u8,
+ pub led_inner: u16,
+ pub hidden: bool
+}
+
+const WIN_ANIMATION_MAX_LED_OUTER_STEP: u8 = 5; // max led_step
+const WIN_ANIMATION_MAX_LED_STEP: u8 = 4; // add led_step
+const WIN_ANIMATION_MAX_LED_INNER_STEP: u16 = 2500; // 10000; // add led_quarter
+
+const HELO_ANIMATION_MAX_INNER_STEP: u16 = 5000; // 20000;
+const HELO_ANIMATION_MAX_OUTER_STEP: u16 = 5;
+
+impl WinAnimation {
+ pub fn create(number: u16) -> WinAnimation {
+ WinAnimation {
+ number,
+ led_inner: 0,
+ led_quarter: 0,
+ led_step: 0,
+ hidden: true
+ }
+ }
+
+ pub fn reset(&mut self, number: u16) {
+ self.number = number;
+ self.led_step = 0;
+ self.led_quarter = 0;
+ self.led_inner = 0;
+ self.hidden = true;
+ }
+}
+
+impl Animation for WinAnimation {
+ fn step(&mut self, seven_segment: &mut filled_seven_segment::FilledSevenSegment, led_matrix: &mut led_matrix::LEDMatrix) -> AnimationState {
+ if self.led_inner == 0 && self.led_quarter == 0 && self.led_step == 0 {
+ seven_segment.set_number(self.number);
+ led_matrix.clear();
+ }
+
+ if self.led_inner > WIN_ANIMATION_MAX_LED_INNER_STEP {
+ self.led_inner = 0;
+ self.led_quarter += 1;
+ }
+
+ if self.led_quarter >= WIN_ANIMATION_MAX_LED_STEP {
+ self.led_quarter = 0;
+ self.led_step += 1;
+ }
+
+ if self.led_step > WIN_ANIMATION_MAX_LED_OUTER_STEP {
+ return AnimationState::End;
+ }
+
+ led_matrix.clear();
+
+ if self.led_step < 2 {
+ led_matrix.set(self.led_quarter, self.led_step, true);
+ } else if self.led_step == 2 {
+ led_matrix.set(self.led_quarter, 0, true);
+ led_matrix.set(self.led_quarter, 1, true);
+ } else if self.led_step == 3 {
+ led_matrix.set(3 - self.led_quarter, 0, true);
+ led_matrix.set(3 - self.led_quarter, 1, true);
+ } else {
+ for i in 0..=self.led_quarter {
+ led_matrix.set(i, 0, true);
+ led_matrix.set(i, 1, true);
+ }
+ }
+
+ if (self.led_quarter == 2 || self.led_quarter == 0) && self.led_inner == 0 {
+ if self.hidden {
+ seven_segment.show_all_digits();
+ } else {
+ seven_segment.hide_all_digits();
+ }
+
+ self.hidden = !self.hidden;
+ }
+
+ self.led_inner += 1;
+ AnimationState::Running
+ }
+
+ fn cleanup(&mut self, seven_segment: &mut filled_seven_segment::FilledSevenSegment, led_matrix: &mut led_matrix::LEDMatrix) {
+ led_matrix.set_data(0xFF);
+ seven_segment.show_all_digits();
+ }
+
+ fn running(&self) -> bool {
+ self.led_step < WIN_ANIMATION_MAX_LED_OUTER_STEP
+ }
+}
+
+impl HelloAnimation {
+ pub fn create() -> HelloAnimation {
+ HelloAnimation {
+ inner_step: 0,
+ outer_step: 0,
+ hidden: false
+ }
+ }
+}
+
+impl Animation for HelloAnimation {
+
+ fn step(&mut self, seven_segment: &mut filled_seven_segment::FilledSevenSegment, led_matrix: &mut led_matrix::LEDMatrix) -> AnimationState {
+ // Helo text
+ if self.inner_step == 0 && self.outer_step == 0 {
+ seven_segment.set_digit(3, Some(72)); // H
+ seven_segment.set_digit(2, Some(69)); // E
+ seven_segment.set_digit(1, Some(76)); // L
+ seven_segment.set_digit(0, Some(79)); // O
+ led_matrix.set_data(0xFF);
+ }
+
+ if self.inner_step >= HELO_ANIMATION_MAX_INNER_STEP {
+ self.inner_step = 0;
+ self.outer_step += 1;
+
+ let matrix_data = led_matrix.data();
+ led_matrix.set_data(!matrix_data);
+
+ if self.hidden {
+ seven_segment.show_all_digits();
+ } else {
+ seven_segment.hide_all_digits();
+ }
+ self.hidden = !self.hidden;
+ }
+
+ if self.outer_step == HELO_ANIMATION_MAX_OUTER_STEP {
+ return AnimationState::End
+ }
+
+ self.inner_step += 1;
+ AnimationState::Running
+ }
+
+ fn cleanup(&mut self, seven_segment: &mut filled_seven_segment::FilledSevenSegment, led_matrix: &mut led_matrix::LEDMatrix) {
+ led_matrix.clear();
+ seven_segment.show_all_digits();
+ }
+
+ fn running(&self) -> bool {
+ self.outer_step < HELO_ANIMATION_MAX_OUTER_STEP
+ }
+}
A => src/button.rs +77 -0
@@ 1,77 @@
+use atmega_hal::port::{Pin, mode};
+
+#[derive(PartialEq, Eq)]
+pub enum ButtonState {
+ Inactive, // button is not pressed and the state is same from last time
+ Active, // button is pressed and the state is same from last time
+ Pressed, // The button was just pressed
+ Released // The button was just released
+}
+
+const DEBOUNCECYCLES: u8 = 50;
+
+pub struct Button {
+ input: Pin<mode::Input>,
+ active_high: bool,
+ last_active: bool,
+ active: bool,
+ integrator: u8
+}
+
+impl Button {
+ pub fn create(input: Pin<mode::Input>, active_high: bool) -> Button {
+ Button {
+ input,
+ active_high,
+ last_active: false,
+ active: false,
+ integrator: 0,
+ }
+ }
+
+ pub fn step(&mut self) {
+ let mut btn_active = self.input.is_low();
+ if self.active_high {
+ btn_active = !btn_active;
+ }
+
+ if !btn_active {
+ if self.integrator > 0 {
+ self.integrator -= 1;
+ }
+ } else if self.integrator < DEBOUNCECYCLES {
+ self.integrator += 1;
+ }
+
+ self.active = self.pressed();
+ }
+
+ fn pressed(&mut self) -> bool{
+ if self.integrator == 0 {
+ self.last_active = self.active;
+ self.active = false;
+ } else if self.integrator >= DEBOUNCECYCLES {
+ self.integrator = DEBOUNCECYCLES;
+ self.last_active = self.active;
+ self.active = true;
+ }
+
+ self.active
+ }
+
+ pub fn state(&self) -> ButtonState {
+ if self.active {
+ if self.last_active {
+ return ButtonState::Active;
+ }
+
+ return ButtonState::Pressed;
+ }
+
+ if !self.last_active {
+ return ButtonState::Inactive;
+ }
+
+ return ButtonState::Released;
+ }
+}
A => src/entrypoint.rs +281 -0
@@ 1,281 @@
+#![no_std]
+#![no_main]
+
+mod sipo;
+mod filled_sipo;
+mod seven_segment;
+mod filled_seven_segment;
+mod led_matrix;
+mod animation;
+mod button;
+mod rng;
+
+use panic_halt as _;
+
+const DIGITS: usize = 4;
+const LED_MATRIX_CORRECT_ROW: u8 = 0;
+const LED_MATRIX_INCORRECT_POSITION_ROW: u8 = 1;
+
+static mut HELLO_ANIMATION: animation::HelloAnimation = animation::HelloAnimation { inner_step: 0, outer_step: 0, hidden: false };
+static mut WIN_ANIMATION: animation::WinAnimation = animation::WinAnimation { number: 0, led_step: 0, led_quarter: 0, led_inner: 0, hidden: true } ;
+
+#[atmega_hal::entry]
+fn main() -> ! {
+ // PERIPHERALS
+ let dp = atmega_hal::Peripherals::take().unwrap();
+ let pins = atmega_hal::pins!(dp);
+
+ let srclr = pins.pc2.into_output().downgrade();
+ let srclk = pins.pc3.into_output().downgrade();
+ let rclk = pins.pc4.into_output().downgrade();
+ let ser = pins.pc5.into_output().downgrade();
+
+ let shift_register = sipo::Sipo::create(srclk, srclr, ser, rclk);
+
+ let shift_register = filled_sipo::FilledSipo::create(shift_register);
+ let seven_segment = seven_segment::SevenSegment::create(4, true, false);
+ let seven_segment = filled_seven_segment::FilledSevenSegment::create(seven_segment, shift_register);
+
+ let mut matrix = led_matrix::LEDMatrix::create(4, 2);
+ matrix.add_anode(pins.pd3.into_output().downgrade());
+ matrix.add_anode(pins.pd2.into_output().downgrade());
+ matrix.add_anode(pins.pd1.into_output().downgrade());
+ matrix.add_anode(pins.pd0.into_output().downgrade());
+
+ matrix.add_cathode(pins.pd5.into_output().downgrade());
+ matrix.add_cathode(pins.pd6.into_output().downgrade());
+
+ let in_1 = pins.pc1.into_pull_up_input().downgrade().forget_imode();
+ let in_2 = pins.pb2.into_pull_up_input().downgrade().forget_imode();
+ let in_3 = pins.pb1.into_pull_up_input().downgrade().forget_imode();
+ let in_4 = pins.pd7.into_pull_up_input().downgrade().forget_imode();
+ let in_confirm = pins.pb0.into_pull_up_input().downgrade().forget_imode();
+
+ let btn_1 = button::Button::create(in_1, false);
+ let btn_2 = button::Button::create(in_2, false);
+ let btn_3 = button::Button::create(in_3, false);
+ let btn_4 = button::Button::create(in_4, false);
+ let btn_confirm = button::Button::create(in_confirm, false);
+ // PERIPHERALS END
+
+ // RNG
+ let rng = rng::Rng::init(123, 111, 45);
+
+ // GAME
+ let mut game = Game {
+ seven_segment,
+ led_matrix: matrix,
+ state: GameState::Start,
+ guessing_number: None,
+ current_number: None,
+ animation: None,
+ rng,
+ buttons: [btn_1, btn_2, btn_3, btn_4],
+ confirm: btn_confirm
+ };
+
+ unsafe {
+ HELLO_ANIMATION = animation::HelloAnimation::create();
+ WIN_ANIMATION = animation::WinAnimation::create(0);
+ game.animation = Some(&mut HELLO_ANIMATION);
+ }
+
+ let mut step: u64 = 0;
+ loop {
+ // Show seven segment, matrix data
+ step += 1;
+ game.seven_segment.step();
+ if step > 50 {
+ game.led_matrix.step();
+ step = 0;
+ }
+
+ for button in game.buttons.iter_mut() {
+ button.step();
+ }
+ game.confirm.step();
+
+ // Animation logic
+ if let Some(animation) = &mut game.animation {
+ if animation.running() {
+ let state = animation.step(&mut game.seven_segment, &mut game.led_matrix);
+
+ if state == animation::AnimationState::End {
+ animation.cleanup(&mut game.seven_segment, &mut game.led_matrix);
+ game.animation = None;
+ }
+ } else {
+ animation.cleanup(&mut game.seven_segment, &mut game.led_matrix);
+ game.animation = None;
+ }
+ }
+
+ game.step();
+ }
+}
+
+pub struct Game {
+ seven_segment: filled_seven_segment::FilledSevenSegment,
+ led_matrix: led_matrix::LEDMatrix,
+ state: GameState,
+ guessing_number: Option<u16>,
+ current_number: Option<u16>,
+ animation: Option<&'static mut dyn animation::Animation>,
+ rng: rng::Rng,
+ buttons: [button::Button; 4],
+ confirm: button::Button
+}
+
+pub enum GameState {
+ Start,
+ Play,
+ Won
+}
+
+impl Game {
+ pub fn step(&mut self) {
+ match self.state {
+ GameState::Start | GameState::Won => {
+ if self.any_button_pressed() {
+ self.start_new_game();
+ }
+ },
+ GameState::Play => {
+ if self.confirm.state() == button::ButtonState::Pressed {
+ if self.current_number == self.guessing_number {
+ self.end_current_game();
+ return;
+ }
+
+ self.update_led_matrix();
+ }
+
+ let mut btns_pressed: [bool; DIGITS] = [false; DIGITS];
+ for (i, button) in self.buttons.iter().enumerate() {
+ let state = button.state();
+ btns_pressed[i] = state == button::ButtonState::Pressed;
+ }
+
+ for (i, pressed) in btns_pressed.iter().enumerate() {
+ if *pressed {
+ self.increase_digit(DIGITS - 1 - i);
+ }
+ }
+ }
+ }
+ }
+
+ fn get_digit(number: u16, digit_index: usize) -> u8 {
+ let mut digit = number;
+ for _ in 0..digit_index {
+ digit /= 10;
+ }
+
+ (digit % 10).try_into().unwrap()
+ }
+
+ fn update_led_matrix(&mut self) {
+ self.led_matrix.clear();
+ let current_number = self.current_number.unwrap();
+ let guessing_number = self.guessing_number.unwrap();
+
+ let mut current_digits: [u8; DIGITS] = [0, 0, 0, 0];
+ let mut guessing_digits: [u8; DIGITS] = [0, 0, 0, 0];
+
+
+ for i in 0..DIGITS {
+ current_digits[i] = Self::get_digit(current_number, i);
+ guessing_digits[i] = Self::get_digit(guessing_number, i);
+ }
+
+ for j in 0..2 {
+ self.led_matrix.set(
+ 0,
+ 1,
+ false
+ );
+ }
+
+ for i in 0..DIGITS {
+ if current_digits[i] == guessing_digits[i] {
+ self.led_matrix.set(
+ i.try_into().unwrap(),
+ 0,
+ true
+ );
+ }
+ else {
+ for j in 0..DIGITS {
+ if current_digits[j] != guessing_digits[j] && current_digits[i] == guessing_digits[j] {
+ /*self.led_matrix.set(
+ i.try_into().unwrap(),
+ 1,
+ true
+ );*/
+ }
+ }
+
+ }
+ }
+ }
+
+ fn increase_digit(&mut self, digit_index: usize) {
+ let current_number = self.current_number.unwrap();
+ let mut order = 1;
+ for _ in 0..digit_index {
+ order *= 10;
+ }
+
+ let current_digit = Self::get_digit(current_number, digit_index);
+ let new_digit: u16 = ((current_digit + 1) % 10).into();
+
+ let trimmed_number = current_number % order;
+ let mut new_number = current_number - (current_number % (order*10));
+ new_number += new_digit * order + trimmed_number;
+
+ self.current_number = Some(new_number);
+ self.seven_segment.set_number(new_number);
+ }
+
+ fn end_current_game(&mut self) {
+ // TODO: win animation
+ //self.animation = None;
+ unsafe {
+ WIN_ANIMATION.reset(self.guessing_number.unwrap());
+ self.animation = Some(&mut WIN_ANIMATION);
+ }
+ self.cleanup_current_game();
+ self.state = GameState::Won;
+ }
+
+ fn cleanup_current_game(&mut self) {
+ self.guessing_number = None;
+ self.current_number = None;
+ }
+
+ fn start_new_game(&mut self) {
+ if let Some(animation) = &mut self.animation {
+ animation.cleanup(&mut self.seven_segment, &mut self.led_matrix);
+ self.animation = None;
+ }
+
+ self.guessing_number = Some(self.rng.take_u16() % 10000);
+ self.current_number = Some(self.guessing_number.unwrap());
+ self.seven_segment.set_number(self.current_number.unwrap());
+ self.led_matrix.clear();
+
+ self.state = GameState::Play;
+ }
+
+ fn any_button_pressed(&mut self) -> bool {
+ for btn in self.buttons.iter() {
+ let state = btn.state();
+
+ if state == button::ButtonState::Pressed {
+ return true;
+ }
+ }
+
+ self.confirm.state() == button::ButtonState::Pressed
+ }
+}
A => src/filled_seven_segment.rs +111 -0
@@ 1,111 @@
+use super::seven_segment;
+use super::filled_sipo;
+
+pub struct FilledSevenSegment {
+ seven_segment: seven_segment::SevenSegment,
+ sipo: filled_sipo::FilledSipo,
+ digits: [Option<u8>; 4],
+ hide: u8,
+ update_step: usize
+}
+
+impl FilledSevenSegment {
+ pub fn create(seven_segment: seven_segment::SevenSegment, sipo: filled_sipo::FilledSipo) -> FilledSevenSegment {
+ FilledSevenSegment {
+ seven_segment,
+ sipo,
+ digits: [None, None, None, None],
+ hide: 0,
+ update_step: 0
+ }
+ }
+
+ fn get_digit(digit: u16, digit_index: usize) -> u8 {
+ let mut digit = digit;
+ for _ in 0..digit_index {
+ digit /= 10;
+ }
+
+ return (digit % 10).try_into().unwrap();
+ }
+
+ pub fn hide_digit(&mut self, digit_index: usize) {
+ self.hide |= 1 << digit_index;
+ }
+
+ pub fn show_digit(&mut self, digit_index: usize) {
+ self.hide &= !(1 << digit_index);
+ }
+
+ pub fn hide_all_digits(&mut self) {
+ self.hide = 0xFF;
+ }
+
+ pub fn show_all_digits(&mut self) {
+ self.hide = 0;
+ }
+
+ pub fn set_digit(&mut self, digit_index: usize, digit: Option<u8>) {
+ if digit_index < 4 {
+ self.digits[digit_index] = digit;
+ }
+ }
+
+ pub fn set_number(&mut self, number: u16) {
+ for i in 0..4_usize {
+ let digit = Self::get_digit(number, i);
+ self.digits[i] = Some(digit);
+ }
+ }
+
+ pub fn show_number_block(&mut self) {
+ while !self.step() {}
+ }
+
+ fn fill_digit(&mut self, digit_index: usize) {
+ if digit_index > 3 {
+ return
+ }
+
+ if (self.hide & (1 << digit_index)) != 0 {
+ self.sipo.clear();
+ return
+ }
+
+ if let Some(digit) = self.digits[digit_index] {
+ self.seven_segment.fill_digit(&mut self.sipo, digit, digit_index);
+ } else {
+ self.sipo.clear();
+ }
+ }
+
+ pub fn step(&mut self) -> bool {
+ if self.update_step == 0 {
+ self.fill_digit(0);
+ self.update_step = 1;
+ }
+
+ if self.sipo.step() {
+ self.update_step += 1;
+
+ if self.update_step <= self.seven_segment.digits().into() {
+ self.fill_digit(self.update_step - 1);
+ }
+ }
+
+ if self.update_step > self.seven_segment.digits().into() {
+ self.update_step = 0;
+ return true;
+ }
+
+ return false;
+ }
+
+ pub fn reset(&mut self) {
+ self.update_step = 0;
+ }
+
+ pub fn clear(&mut self) {
+ self.digits = [None, None, None, None];
+ }
+}
A => src/filled_sipo.rs +49 -0
@@ 1,49 @@
+use super::sipo;
+
+pub struct FilledSipo {
+ shift_register: sipo::Sipo,
+ data: u16,
+ update_step: u8
+}
+
+impl FilledSipo {
+ pub fn create(shift_register: sipo::Sipo) -> FilledSipo {
+ FilledSipo {
+ shift_register,
+ data: 0,
+ update_step: 0
+ }
+ }
+
+ pub fn set_data(&mut self, data: u16) {
+ self.data = data;
+ self.reset();
+ }
+
+ pub fn push_block(&mut self) {
+ while !self.step() {
+ }
+ }
+
+ pub fn step(&mut self) -> bool {
+ self.shift_register.shift_value((self.data >> (15 - self.update_step)) & 1 == 1);
+
+ if self.update_step >= 15 {
+ self.update_step = 0;
+ self.shift_register.show();
+ return true;
+ }
+
+ self.update_step += 1;
+ return false;
+ }
+
+ pub fn reset(&mut self) {
+ self.update_step = 0;
+ }
+
+ pub fn clear(&mut self) {
+ self.set_data(0);
+ self.reset();
+ }
+}
A => src/led_matrix.rs +102 -0
@@ 1,102 @@
+use atmega_hal::port::{Pin, mode};
+
+pub struct LEDMatrix {
+ width: u8,
+ height: u8,
+ data: u8,
+ anodes: [Option<Pin<mode::Output>>; 8],
+ cathodes: [Option<Pin<mode::Output>>; 8],
+ anodes_count: usize,
+ cathodes_count: usize,
+ update_step: usize
+}
+
+impl LEDMatrix {
+ pub fn create(width: u8, height: u8) -> LEDMatrix {
+ LEDMatrix {
+ width,
+ height,
+ data: 0,
+ anodes: [None, None, None, None, None, None, None, None],
+ cathodes: [None, None, None, None, None, None, None, None],
+ anodes_count: 0,
+ cathodes_count: 0,
+ update_step: 0
+ }
+ }
+
+ fn get_position(width: u8, x: u8, y: u8) -> u8 {
+ return width*y + x;
+ }
+
+ pub fn data(&self) -> u8 {
+ self.data
+ }
+
+ pub fn set_data(&mut self, data: u8) {
+ self.data = data;
+ }
+
+ pub fn set(&mut self, x: u8, y: u8, value: bool) {
+ if x >= self.width || y >= self.height {
+ return
+ }
+
+ let mask = 1 << Self::get_position(self.width, x, y);
+ if value {
+ self.data |= mask;
+ } else {
+ self.data &= !mask;
+ }
+ }
+
+ pub fn add_anode(&mut self, anode: Pin<mode::Output>) {
+ self.anodes[self.anodes_count] = Some(anode);
+ self.anodes_count += 1;
+ }
+
+ pub fn add_cathode(&mut self, cathode: Pin<mode::Output>) {
+ self.cathodes[self.cathodes_count] = Some(cathode);
+ self.cathodes_count += 1;
+ }
+
+ pub fn step(&mut self) -> bool {
+ let update_unsigned: u8 = self.update_step.try_into().unwrap();
+ let first_position: u8 = update_unsigned * self.width;
+
+ for x in 0..self.cathodes_count {
+ let cathode = &mut self.cathodes[x];
+ if let Some(cathode) = cathode {
+ cathode.set_high();
+ }
+ }
+
+ for x in 0..self.anodes_count {
+ let anode = &mut self.anodes[x];
+ if let Some(anode) = anode {
+ let x_unsigned: u8 = x.try_into().unwrap();
+ if self.data & (1 << (first_position + x_unsigned)) != 0 {
+ anode.set_high();
+ } else {
+ anode.set_low();
+ }
+ }
+ }
+
+ if let Some(cathode) = &mut self.cathodes[self.update_step] {
+ cathode.set_low();
+ }
+
+ self.update_step += 1;
+ if self.update_step >= self.height.into() {
+ self.update_step = 0;
+ return true;
+ }
+
+ return false;
+ }
+
+ pub fn clear(&mut self) {
+ self.data = 0;
+ }
+}
A => src/rng.rs +44 -0
@@ 1,44 @@
+pub struct Rng {
+ a: u8,
+ b: u8,
+ c: u8,
+ x: u8
+}
+
+impl Rng {
+ pub fn init(s1: u8, s2: u8, s3: u8) -> Rng {
+ let mut rng = Rng {
+ a: 0,
+ b: 0,
+ c: 0,
+ x: 0
+ };
+
+ rng.a ^= s1;
+ rng.b ^= s2;
+ rng.c ^= s3;
+ rng.randomize();
+
+ rng
+ }
+
+ fn randomize(&mut self) -> u8 {
+ self.x += 1;
+ self.a = self.a^self.c^self.x;
+ self.b = self.b+self.a;
+ self.c = self.c + (self.b >> 1)^self.a;
+
+ self.c
+ }
+
+ pub fn take_u8(&mut self) -> u8 {
+ self.randomize()
+ }
+
+ pub fn take_u16(&mut self) -> u16 {
+ let first: u16 = self.randomize().into();
+ let second: u16 = self.randomize().into();
+
+ first << 8 | second
+ }
+}
A => src/seven_segment.rs +100 -0
@@ 1,100 @@
+use super::filled_sipo;
+
+pub struct SevenSegment {
+ digits: u8,
+ dp: bool,
+ common_cathode: bool
+}
+
+impl SevenSegment {
+ pub fn create(digits: u8, dp: bool, common_cathode: bool) -> SevenSegment {
+ SevenSegment {
+ digits,
+ dp,
+ common_cathode
+ }
+ }
+
+ fn get_digit_segments(digit: u8) -> u8 {
+ match digit {
+ // HGFEDCBA
+ 0 => 0b00111111, // 0
+ 1 => 0b00000110, // 1
+ 2 => 0b01011011, // 2
+ 3 => 0b01001111, // 3
+ 4 => 0b01100110, // 4
+ 5 => 0b01101101, // 5
+ 6 => 0b01111101, // 6
+ 7 => 0b00000111, // 7
+ 8 => 0b01111111, // 8
+ 9 => 0b01101111, // 9
+ 65 => 0b01110111, // A
+ 98 => 0b01111100, // b
+ 67 => 0b00111001, // C
+ 99 => 0b01011000, // c
+ 100 => 0b01011110, // d
+ 69 => 0b01111001, // E
+ 70 => 0b01110001, // F
+ 71 => 0b00111101, // G
+ 103 => 0b01101111, // g
+ 72 => 0b011110110, // H
+ 104 => 0b01110100, // h
+ 73 => 0b00000110, // I
+ 76 => 0b00111000, // L
+ 108 => 0b00110000, // l
+ 110 => 0b01010100, // n
+ 78 => 0b00110111, // N
+ 79 => 0b00111111, // O
+ 111 => 0b01011100, // o
+ 80 => 0b01110011, // P
+ 81 => 0b01100111, // Q
+ 114 => 0b01010000, // r
+ 83 => 0b01101101, // S
+ 116 => 0b01111000, // t
+ 85 => 0b00111110, // U
+ _ => 0b00000000, // nothing
+ }
+ }
+
+ pub fn digits(&self) -> u8 {
+ return self.digits;
+ }
+
+ pub fn dp(&self) -> bool {
+ return self.dp;
+ }
+
+ pub fn common_cathode(&self) -> bool {
+ return self.common_cathode;
+ }
+
+ fn get_digit_selector(digit_count: u8, digit_index: usize) -> u8 {
+ let digit_count: usize = digit_count.into();
+ return 1 << (digit_count - 1 - digit_index);
+ }
+
+ pub fn fill_digit(&self, sipo: &mut filled_sipo::FilledSipo, digit: u8, digit_index: usize) -> bool {
+ if digit_index >= self.digits.into() {
+ return false;
+ }
+
+ let mut segments = SevenSegment::get_digit_segments(digit);
+ let mut digit_selector = SevenSegment::get_digit_selector(self.digits, digit_index);
+
+ if self.common_cathode {
+ digit_selector = !digit_selector;
+ } else {
+ segments = !segments;
+ }
+
+ let segments: u16 = segments.into();
+ let digit_selector: u16 = digit_selector.into();
+
+ if self.dp {
+ sipo.set_data(digit_selector << 8 | segments);
+ } else {
+ sipo.set_data(digit_selector << 7 | (segments & 0x7F));
+ }
+ return true;
+ }
+}
A => src/sipo.rs +57 -0
@@ 1,57 @@
+use atmega_hal::port::{Pin, mode};
+
+pub struct Sipo {
+ srclk : Pin<mode::Output>,
+ srclr : Pin<mode::Output>,
+ ser : Pin<mode::Output>,
+ rclk : Pin<mode::Output>,
+}
+
+impl Sipo {
+ pub fn create(srclk: Pin<mode::Output>, srclr: Pin<mode::Output>, ser: Pin<mode::Output>, rclk: Pin<mode::Output>) -> Sipo {
+ let mut sipo = Sipo {
+ srclk,
+ srclr,
+ ser,
+ rclk
+ };
+
+ sipo.setup();
+ return sipo;
+ }
+
+ pub fn setup(&mut self) {
+ self.clear();
+ self.show();
+ }
+
+ pub fn set(&mut self, value: bool) {
+ if value {
+ self.ser.set_high();
+ } else {
+ self.ser.set_low();
+ }
+ }
+
+ pub fn shift(&mut self) {
+ self.srclk.set_low();
+ self.srclk.set_high();
+ self.srclk.set_low();
+ }
+
+ pub fn shift_value(&mut self, value: bool) {
+ self.set(value);
+ self.shift();
+ }
+
+ pub fn show(&mut self) {
+ self.rclk.set_low();
+ self.rclk.set_high();
+ self.rclk.set_low();
+ }
+
+ pub fn clear(&mut self) {
+ self.srclr.set_low();
+ self.srclr.set_high();
+ }
+}