From 0f40a4c3191c5f420d7dd3ca4203f63b6f1dd813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Tue, 20 Dec 2022 11:52:14 +0100 Subject: [PATCH] Initial commit --- .cargo/config.toml | 5 + .github/Dockerfile.ci | 22 +++ .github/workflows/docker-image.yml | 23 +++ .gitignore | 1 + Cargo.lock | 199 ++++++++++++++++++++ Cargo.toml | 36 ++++ README.md | 28 +++ avr-specs/avr-atmega1280.json | 27 +++ avr-specs/avr-atmega168.json | 27 +++ avr-specs/avr-atmega2560.json | 27 +++ avr-specs/avr-atmega328p.json | 27 +++ avr-specs/avr-atmega32u4.json | 27 +++ avr-specs/avr-atmega48p.json | 27 +++ avr-specs/avr-atmega8.json | 31 ++++ avr-specs/avr-attiny85.json | 27 +++ avr-specs/avr-attiny88.json | 27 +++ main.rs | 0 rust-toolchain.toml | 2 + src/animation.rs | 172 ++++++++++++++++++ src/button.rs | 77 ++++++++ src/entrypoint.rs | 281 +++++++++++++++++++++++++++++ src/filled_seven_segment.rs | 111 ++++++++++++ src/filled_sipo.rs | 49 +++++ src/led_matrix.rs | 102 +++++++++++ src/rng.rs | 44 +++++ src/seven_segment.rs | 100 ++++++++++ src/sipo.rs | 57 ++++++ 27 files changed, 1556 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .github/Dockerfile.ci create mode 100644 .github/workflows/docker-image.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 avr-specs/avr-atmega1280.json create mode 100644 avr-specs/avr-atmega168.json create mode 100644 avr-specs/avr-atmega2560.json create mode 100644 avr-specs/avr-atmega328p.json create mode 100644 avr-specs/avr-atmega32u4.json create mode 100644 avr-specs/avr-atmega48p.json create mode 100644 avr-specs/avr-atmega8.json create mode 100644 avr-specs/avr-attiny85.json create mode 100644 avr-specs/avr-attiny88.json create mode 100644 main.rs create mode 100644 rust-toolchain.toml create mode 100644 src/animation.rs create mode 100644 src/button.rs create mode 100644 src/entrypoint.rs create mode 100644 src/filled_seven_segment.rs create mode 100644 src/filled_sipo.rs create mode 100644 src/led_matrix.rs create mode 100644 src/rng.rs create mode 100644 src/seven_segment.rs create mode 100644 src/sipo.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..57e111d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "avr-specs/avr-atmega8.json" + +[unstable] +build-std = ["core"] diff --git a/.github/Dockerfile.ci b/.github/Dockerfile.ci new file mode 100644 index 0000000..fdaa45f --- /dev/null +++ b/.github/Dockerfile.ci @@ -0,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"] diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..dce698f --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c2a8cf8 --- /dev/null +++ b/Cargo.lock @@ -0,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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8029e06 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "guess-the-number" +version = "0.1.0" +authors = ["František Boháček "] +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" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4bd3d9 --- /dev/null +++ b/README.md @@ -0,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-/release/template-bin.elf`. + diff --git a/avr-specs/avr-atmega1280.json b/avr-specs/avr-atmega1280.json new file mode 100644 index 0000000..9f1256c --- /dev/null +++ b/avr-specs/avr-atmega1280.json @@ -0,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" +} diff --git a/avr-specs/avr-atmega168.json b/avr-specs/avr-atmega168.json new file mode 100644 index 0000000..fe8e4cd --- /dev/null +++ b/avr-specs/avr-atmega168.json @@ -0,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" +} diff --git a/avr-specs/avr-atmega2560.json b/avr-specs/avr-atmega2560.json new file mode 100644 index 0000000..609d2c3 --- /dev/null +++ b/avr-specs/avr-atmega2560.json @@ -0,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" +} diff --git a/avr-specs/avr-atmega328p.json b/avr-specs/avr-atmega328p.json new file mode 100644 index 0000000..e236b08 --- /dev/null +++ b/avr-specs/avr-atmega328p.json @@ -0,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" +} diff --git a/avr-specs/avr-atmega32u4.json b/avr-specs/avr-atmega32u4.json new file mode 100644 index 0000000..5aa1d88 --- /dev/null +++ b/avr-specs/avr-atmega32u4.json @@ -0,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" +} diff --git a/avr-specs/avr-atmega48p.json b/avr-specs/avr-atmega48p.json new file mode 100644 index 0000000..51fa78c --- /dev/null +++ b/avr-specs/avr-atmega48p.json @@ -0,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" +} diff --git a/avr-specs/avr-atmega8.json b/avr-specs/avr-atmega8.json new file mode 100644 index 0000000..10d5dde --- /dev/null +++ b/avr-specs/avr-atmega8.json @@ -0,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" +} diff --git a/avr-specs/avr-attiny85.json b/avr-specs/avr-attiny85.json new file mode 100644 index 0000000..505c12f --- /dev/null +++ b/avr-specs/avr-attiny85.json @@ -0,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" +} diff --git a/avr-specs/avr-attiny88.json b/avr-specs/avr-attiny88.json new file mode 100644 index 0000000..721d8dd --- /dev/null +++ b/avr-specs/avr-attiny88.json @@ -0,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" +} diff --git a/main.rs b/main.rs new file mode 100644 index 0000000..e69de29 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..51b6b50 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2022-05-10" diff --git a/src/animation.rs b/src/animation.rs new file mode 100644 index 0000000..d73695f --- /dev/null +++ b/src/animation.rs @@ -0,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 + } +} diff --git a/src/button.rs b/src/button.rs new file mode 100644 index 0000000..cd85636 --- /dev/null +++ b/src/button.rs @@ -0,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, + active_high: bool, + last_active: bool, + active: bool, + integrator: u8 +} + +impl Button { + pub fn create(input: Pin, 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; + } +} diff --git a/src/entrypoint.rs b/src/entrypoint.rs new file mode 100644 index 0000000..881e714 --- /dev/null +++ b/src/entrypoint.rs @@ -0,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, + current_number: Option, + 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 + } +} diff --git a/src/filled_seven_segment.rs b/src/filled_seven_segment.rs new file mode 100644 index 0000000..d168f83 --- /dev/null +++ b/src/filled_seven_segment.rs @@ -0,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; 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) { + 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]; + } +} diff --git a/src/filled_sipo.rs b/src/filled_sipo.rs new file mode 100644 index 0000000..3b26260 --- /dev/null +++ b/src/filled_sipo.rs @@ -0,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(); + } +} diff --git a/src/led_matrix.rs b/src/led_matrix.rs new file mode 100644 index 0000000..cb4cb05 --- /dev/null +++ b/src/led_matrix.rs @@ -0,0 +1,102 @@ +use atmega_hal::port::{Pin, mode}; + +pub struct LEDMatrix { + width: u8, + height: u8, + data: u8, + anodes: [Option>; 8], + cathodes: [Option>; 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) { + self.anodes[self.anodes_count] = Some(anode); + self.anodes_count += 1; + } + + pub fn add_cathode(&mut self, cathode: Pin) { + 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; + } +} diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000..58b3681 --- /dev/null +++ b/src/rng.rs @@ -0,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 + } +} diff --git a/src/seven_segment.rs b/src/seven_segment.rs new file mode 100644 index 0000000..5aa1d14 --- /dev/null +++ b/src/seven_segment.rs @@ -0,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; + } +} diff --git a/src/sipo.rs b/src/sipo.rs new file mode 100644 index 0000000..4f04413 --- /dev/null +++ b/src/sipo.rs @@ -0,0 +1,57 @@ +use atmega_hal::port::{Pin, mode}; + +pub struct Sipo { + srclk : Pin, + srclr : Pin, + ser : Pin, + rclk : Pin, +} + +impl Sipo { + pub fn create(srclk: Pin, srclr: Pin, ser: Pin, rclk: Pin) -> 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(); + } +} -- 2.48.1