Compare commits

...

37 commits

Author SHA1 Message Date
9b7bfb0562 fw-rust: Improve readability
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 01:40:18 +02:00
791a0a0245 fw-rust: Use DefaultClock type alias everywhere
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 01:40:18 +02:00
b51511dbab fw-rust: Improve setup screen
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Remove redundant match arms when handling inputs and code when drawing
the setup screen.
2022-04-03 01:40:18 +02:00
c36581e703 fw-rust: Make Si5351 useable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
In order to make the Si5351 configurable add some needed assets,
create the ability to print the frequency with selected digits and add
a channel screen.
2022-04-03 01:40:15 +02:00
7f14974146 fw-rust: Remove unnecessary Draw trait
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 01:23:06 +02:00
9994b1fc40 fw-rust: Improve debouncing when going back
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
When pressing the button until the timer1 creates an interrupt in
order to create a "back" event, there were some debouncing problems.
2022-04-02 18:24:31 +02:00
4a03c7045f knob: Improve the design of the knob [CI SKIP] 2022-04-02 00:42:40 +02:00
52bf0e6eec fw-rust: Prepare to implement Si5351 commands
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Show settings of Si5351 channels on home screen. Initialize I2c and
Si5351.
2022-04-01 01:55:07 +02:00
134db298f6 fw-rust: Move PWM handling into screen module
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Move the handling of the PWM signal which generates the dimmable
backlight signal into the screen module.
2022-03-30 23:01:45 +02:00
fdd1f4636d fw-rust: Refactor everything
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Remove avr-eeprom dependency for now and introduce events.
2022-03-30 22:59:24 +02:00
6cd8df515d readme: Improve wording [CI SKIP] 2022-03-29 17:29:13 +02:00
800d3ff8c3 fw-rust: Create "all" build task [CI SKIP]
The "all" task configures a new mcu. It burns the fuses, configures
the eeprom and flashs the firmware.
2022-03-28 17:11:10 +02:00
34d1623abb knob: Add the encoder knob model [CI SKIP] 2022-03-24 21:52:39 +01:00
63cc1c9d0f fw-rust: Remove clear() function for LCD
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Use `fill_area()` instead of `clear()` to clear the the display.
2022-03-22 17:36:09 +01:00
08a371062d fw-rust: Refactor init function of the LCD
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Use an array for the init sequence commands and iterate over it.
2022-03-21 12:46:18 +01:00
7d8f5f6870 fw-rust: Create a print u8 function for setup
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Create `print_u8()` helper function to print `u8` primitives to
lcd. Refactor contrast and backlight variables to also keep them in a
global `AtomicU8`.
2022-03-21 12:46:18 +01:00
c2920ea334 fw-rust: Break everything down in multiple files
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
To reduce length of `main.rs` and therefor improve readability create
source files for:

- Assets (`assets.rs`): Contains all graphical assets such as the
  splash screen assets, symbols and the symbol table.
- LCD (`lcd.rs`): Contains all lower level things regarding the LCD
  such as the `Lcd` struct and its implementations.
- Screen (`screen/mod.rs`):
    - Splash (`screen/splash.rs`)
    - Home (`screen/home.rs`)
    - Setup (`screen/setup.rs`)

In the future it would probably make sense to move the LCD module into
the screen module.
2022-03-18 11:19:36 +01:00
91e120b258 fw-rust: Add setup screen and handle button input
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
To create a setup screen more symbols are created and added to the
symbol table. Helper functions are added to draw homogenous areas onto
the display. Add two new fields `Click` and `Back` to the `Input`
enum. Adapt functions already handling input to those new kinds of
input. Add the setup screen construct. Add global atomics to handle
the button and the timer for handling the back input when the button
is pressed until the timer one overflows. Button press and the timer
are handled via interrupts.
2022-03-16 22:25:18 +01:00
0003717408 fw-rust: Refactor home screen
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add new function for easier instantiation and refactor the input
function.
2022-03-15 19:03:36 +01:00
2ec8d1aeb9 fw-rust: Handle screens differently, use encoder
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Handle the screens differently and implement rotary encoder support.
2022-03-15 00:17:54 +01:00
4a33986e8d fw-rust: Improve URL asset
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-03-14 22:54:07 +01:00
1688bb868e fw-rust: Add symbols and screens
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add several symbols and a symbol table in an ascii order. Add the
`print()` and `print_inverted()` functions to be able to draw strings,
"black on white" as well as "white on black" onto the display. Add a
`HomeScreen` which also saves the state of which selection is
active. To save the screen state and also make it accessible globally
use a `RefCell` contained in a `Mutex`.

Also update the build dependencies.
2022-03-14 22:54:07 +01:00
de8f789e63 fw-rust: Create Lcd struct to handle the screen
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
To improve screen handling create the `Lcd` struct and implement some
first helper functions.
2022-03-12 19:20:28 +01:00
518f113cc0 fw-rust: Use avr-eeprom create to read from EEPROM
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Use the `avr-eeprom` crate when reading from EEPROM.
2022-03-12 17:20:53 +01:00
9425093391 fw-rust: Draw the splash screen via function
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Make the splash screen assets `const` and draw the splash screen with
the new `draw_splash()` function.
2022-03-11 02:35:57 +01:00
36be6d27bc fw-rust: Create a function to clear the screen
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Create a utility function to clear the complete screen.
2022-03-09 19:42:10 +01:00
8b9d21a012 fw-rust: Start to use the avr-eeprom crate
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Start to use the avr-eeprom crate to handle values residing inside the
EEPROM.
2022-03-08 19:05:40 +01:00
b34b2810db fw-rust: Handle data residing in the EEPROM
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-03-08 14:42:04 +01:00
4bc3e84bdd fw-rust, readme: Add CI config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-03-07 18:35:54 +01:00
8315560daa fw-rust: Use chip-generic interrupt utilities
Instead of manually enabling and disabling interrupts use the
chip-generic interrupt utilities.
2022-03-07 18:30:33 +01:00
a688d4c5ed fw-rust: Show splash screen
Put assets for the splash screen into the data segment and print it.
2022-03-07 18:30:33 +01:00
c42b4595c7 fw-rust: Clear the screen
After initializing the LCD display, clear the screen.
2022-03-06 19:20:49 +01:00
817776d735 fw-rust: Initialize the LCD display
Send commands via SPI to the LCD display to initialize and configure
it.
2022-03-06 19:20:49 +01:00
643e5f1af4 fw-rust: Create a SPI interface for the display
In order to, later on, send data to the display create a SPI
interface.
2022-03-06 19:20:49 +01:00
aa59bc302d fw-rust: Create a PWM signal for the backlight
To have a dimmable backlight, generate a variable duty-cycle PWM
signal which is consumed by the backlight driver circuit.
2022-03-06 19:20:49 +01:00
d2772291bf fw-rust: Add a makefile for cargo-make
It has to be decided what kind of tooling should be used, for now
cargo-make is used. This will probably be changed to use the tools
provided by [probe.rs](https://probe.rs).
2022-03-06 19:20:49 +01:00
aa75712418 fw-rust: Minimal hello world 2022-03-06 19:20:49 +01:00
20 changed files with 2057 additions and 9 deletions

28
.woodpecker.yml Normal file
View file

@ -0,0 +1,28 @@
pipeline:
fmt:
group: default
image: rust_avr
commands:
- cd firmware/rust/
- cargo fmt --all -- --check
clippy:
group: default
image: rust_avr
commands:
- cd firmware/rust/
- cargo clippy --all-features
doc:
group: default
image: rust_avr
commands:
- cd firmware/rust/
- cargo doc --all-features
build:
group: default
image: rust_avr
commands:
- cd firmware/rust/
- cargo build --all-features

View file

@ -1,12 +1,11 @@
# Clock Generator # Clock Generator [![status-badge](https://ci.onders.org/api/badges/finga/clock_generator/status.svg)](https://ci.onders.org/finga/clock_generator)
A simple board operating another Si5351 clock generator board. A simple board to control an Si5351 clock generator board with the
TWI. Though, this is not limited to the Si5351 board as it is designed
to be universal usable.
## Motherboard ## Board
The board is populated with an ATmega328(p), a display and its
backlight driver, a switchable rotary encoder and powered by a mini
USB port. All unneeded MCU pins are accessible via pin headers.
The board is populated by an ATmega328(p), powered by a mini USB port,
a display and its backlight driver as well as a switchable rotary
encoder. The board is designed to be used not only for this project,
but also for similar projects where peripherals such as a display and
a rotary encoder are needed. All free mcu pins are accessible via pin
headers.

View file

@ -0,0 +1,6 @@
[build]
target = "avr-specs/avr-atmega328p.json"
[unstable]
build-std = ["core"]
build-std-features = ["compiler-builtins-mangled-names"]

1
firmware/rust/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

233
firmware/rust/Cargo.lock generated Normal file
View file

@ -0,0 +1,233 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "atmega-hal"
version = "0.1.0"
source = "git+https://github.com/rahix/avr-hal?branch=main#e897783816437a677aa577ddfdaa34e9a1e86d96"
dependencies = [
"avr-device",
"avr-hal-generic",
]
[[package]]
name = "avr-device"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb440a38bef22cef233b3eb46ef13e588eb8dcc70a1bdce3f00ebc6bf6b48dd"
dependencies = [
"avr-device-macros",
"bare-metal",
"cfg-if",
"vcell",
]
[[package]]
name = "avr-device-macros"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c62997db86a92e73b68ab491d4379e1ddb12e94a9eb542180bc6b60f0e09f8b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "avr-hal-generic"
version = "0.1.0"
source = "git+https://github.com/rahix/avr-hal?branch=main#e897783816437a677aa577ddfdaa34e9a1e86d96"
dependencies = [
"avr-device",
"cfg-if",
"embedded-hal",
"nb 0.1.3",
"paste",
"ufmt",
"void",
]
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clock-generator"
version = "0.1.0-dev"
dependencies = [
"atmega-hal",
"avr-device",
"embedded-hal",
"nb 1.0.0",
"panic-halt",
"si5351",
]
[[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 = "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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[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.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "si5351"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06c1f1f280165963ce5fdff953a5303ac186b2b723aa1ed33916db70ceabfb8"
dependencies = [
"bitflags",
"embedded-hal",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "ufmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7ecea7ef79d3f8f878eee614afdf5256475c63ad76139d4da6125617c784a0"
dependencies = [
"proc-macro-hack",
"ufmt-macros",
"ufmt-write",
]
[[package]]
name = "ufmt-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed813e34a2bfa9dc58ee2ed8c8314d25e6d70c911486d64b8085cb695cfac069"
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-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[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"

35
firmware/rust/Cargo.toml Normal file
View file

@ -0,0 +1,35 @@
[package]
name = "clock-generator"
version = "0.1.0-dev"
authors = ["finga <finga@onders.org>"]
edition = "2018"
license = "GPL-3.0-or-later"
[[bin]]
name = "clock-generator"
test = false
bench = false
[dependencies]
panic-halt = "0.2"
nb = "1.0"
embedded-hal = "0.2"
avr-device = { version = "0.3", features = ["atmega328p"] }
si5351 = "0.2"
[dependencies.atmega-hal]
git = "https://github.com/rahix/avr-hal"
branch = "main"
features = ["atmega328p", "rt"]
[profile.dev]
panic = "abort"
lto = true
opt-level = "s"
[profile.release]
panic = "abort"
codegen-units = 1
debug = true
lto = true
opt-level = "s"

View file

@ -0,0 +1,45 @@
[env]
MCU = "atmega328p"
PROGRAMMER = "usbasp"
EXTENDED_FUSE = "0xFF"
HIGH_FUSE = "0xD6"
LOW_FUSE = "0xE2"
[tasks.size]
description = "Print usage of memory segments"
dependencies = ["build"]
command = "avr-size"
args = ["--format=avr", "--mcu=${MCU}", "target/avr-atmega328p/debug/clock-generator.elf"]
[tasks.copy_flash]
description = "Extract the flash"
dependencies = ["build"]
command = "avr-objcopy"
args = ["-O", "ihex", "-j", ".text", "-j", ".data", "target/avr-atmega328p/debug/clock-generator.elf", "target/avr-atmega328p/debug/clock-generator.hex"]
[tasks.flash]
description = "Flash the firmware"
dependencies = ["copy_flash", "size"]
command = "avrdude"
args = ["-p", "${MCU}", "-c", "${PROGRAMMER}", "-U", "flash:w:target/avr-atmega328p/debug/clock-generator.hex:a"]
[tasks.copy_eeprom]
description = "Extract the EEPROM"
dependencies = ["build"]
command = "avr-objcopy"
args = ["--change-section-lma", ".eeprom=0", "-O", "ihex", "-j", ".eeprom", "target/avr-atmega328p/debug/clock-generator.elf", "target/avr-atmega328p/debug/clock-generator.eep"]
[tasks.eeprom]
description = "Flash the eeprom"
dependencies = ["copy_eeprom", "size"]
command = "avrdude"
args = ["-p", "${MCU}", "-c", "${PROGRAMMER}", "-U", "eeprom:w:target/avr-atmega328p/debug/clock-generator.eep:a"]
[tasks.fuses]
description = "Burn the fuses"
command = "avrdude"
args = ["-p", "${MCU}", "-c", "${PROGRAMMER}", "-U", "efuse:w:${EXTENDED_FUSE}:m", "-U", "hfuse:w:${HIGH_FUSE}:m", "-U", "lfuse:w:${LOW_FUSE}:m"]
[tasks.all]
description = "Execute all tasks"
run_task = { name = ["fuses", "eeprom", "flash"] }

View file

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

View file

@ -0,0 +1,4 @@
[toolchain]
channel = "nightly-2021-01-07"
components = [ "rust-src" ]
profile = "minimal"

294
firmware/rust/src/assets.rs Normal file
View file

@ -0,0 +1,294 @@
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
pub const SACRED_CHAO: [[u8; 40]; 5] = [
[
0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF0, 0xF8, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFC, 0xFC,
0xF8, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
],
[
0x80, 0xF0, 0xFC, 0xFE, 0xFF, 0xFF, 0x7F, 0x3F, 0x1F, 0x3F, 0x3F, 0x7F, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0x0F, 0x0F, 0x07, 0x07, 0x07, 0x03, 0xC3,
0xE3, 0x73, 0x37, 0x17, 0x07, 0x0F, 0x1E, 0x3C, 0xF0, 0x80,
],
[
0xFF, 0xFF, 0xFF, 0xE7, 0xC3, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xFC, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC, 0x30, 0x00, 0x00, 0xFF,
],
[
0x01, 0x0F, 0x3F, 0x4F, 0x9F, 0x3F, 0x3E, 0x3C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3E, 0x3E, 0x3E,
0x1F, 0x1F, 0x0F, 0x07, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x07, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x07, 0x07, 0x03, 0x81, 0x40, 0x30, 0x0E, 0x01,
],
[
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20, 0x40, 0x40, 0x40,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x40, 0x20, 0x20,
0x10, 0x08, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
pub const ONDERS_ORG: [[u8; 48]; 2] = [
[
0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x60, 0xF8, 0x00, 0x00,
0xE0, 0xA0, 0xE0, 0x00, 0x00, 0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0xA0, 0xA0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x60, 0xE0, 0x00, 0x00,
0xE0, 0x60, 0xE0,
],
[
0x03, 0x02, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x03, 0x00, 0x00,
0x03, 0x02, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x03, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x0B, 0x0A, 0x0F,
],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
pub const ON: [u8; 11] = [
0x00, 0x7E, 0x66, 0x7E, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00,
];
pub const OFF: [u8; 11] = [
0x7E, 0x66, 0x7E, 0x00, 0x7E, 0x16, 0x16, 0x00, 0x7E, 0x16, 0x16,
];
pub const PLL_A: [u8; 6] = [0x00, 0x7C, 0x12, 0x12, 0x7C, 0x00];
pub const PLL_B: [u8; 6] = [0x00, 0x7E, 0x4A, 0x4A, 0x74, 0x00];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_DOT: [&[u8]; 2] = [&[0x00, 0x00], &[0x30, 0x30]];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_0: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x0C, 0xFC, 0xF8],
&[0x1F, 0x3F, 0x30, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_1: [&[u8]; 2] = [
&[0x30, 0x30, 0xFC, 0xFC, 0x00],
&[0x30, 0x30, 0x3F, 0x3F, 0x30],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_2: [&[u8]; 2] = [
&[0x18, 0x1C, 0x8C, 0xFC, 0xF8],
&[0x38, 0x3E, 0x3F, 0x33, 0x30],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_3: [&[u8]; 2] = [
&[0x18, 0x9C, 0x8C, 0xFC, 0x78],
&[0x18, 0x39, 0x31, 0x3F, 0x1E],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_4: [&[u8]; 2] = [
&[0x80, 0xE0, 0x78, 0xFC, 0xFC],
&[0x07, 0x07, 0x06, 0x3F, 0x3F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_5: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x8C, 0x8C, 0x0C],
&[0x1C, 0x3D, 0x31, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_6: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x8C, 0xBC, 0x38],
&[0x1F, 0x3F, 0x31, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_7: [&[u8]; 2] = [
&[0x0C, 0x0C, 0xEC, 0xFC, 0x1C],
&[0x00, 0x3E, 0x3F, 0x01, 0x00],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_8: [&[u8]; 2] = [
&[0x78, 0xFC, 0x8C, 0xFC, 0x78],
&[0x1E, 0x3F, 0x31, 0x3F, 0x1E],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_9: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x8C, 0xFC, 0xF8],
&[0x1C, 0x3D, 0x31, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_COLON: [&[u8]; 2] = [&[0x30, 0x30], &[0x0C, 0x0C]];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_A: [&[u8]; 2] = [
&[0xC0, 0xF0, 0x3C, 0x3C, 0xF0, 0xC0],
&[0x3F, 0x3F, 0x06, 0x06, 0x3F, 0x3F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_B: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0x78],
&[0x3F, 0x3F, 0x31, 0x31, 0x3F, 0x1E],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_C: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x0C, 0x1C, 0x18],
&[0x1F, 0x3F, 0x30, 0x38, 0x18],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_E: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x8C, 0x8C, 0x0C, 0x0C],
&[0x3F, 0x3F, 0x31, 0x31, 0x30, 0x30],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_F: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x8C, 0x8C, 0x0C, 0x0C],
&[0x3F, 0x3F, 0x01, 0x01, 0x00, 0x00],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_G: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x0C, 0x0C, 0x3C, 0x38],
&[0x1F, 0x3F, 0x30, 0x33, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_H: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x80, 0x80, 0xFC, 0xFC],
&[0x3F, 0x3F, 0x01, 0x01, 0x3F, 0x3F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_I: [&[u8]; 2] = [&[0x0C, 0xFC, 0xFC, 0x0C], &[0x30, 0x3F, 0x3F, 0x30]];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_K: [&[u8]; 2] = [
&[0xFC, 0xFC, 0xC0, 0xF0, 0x7C, 0x1C],
&[0x3F, 0x3F, 0x03, 0x0F, 0x3E, 0x38],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_L: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x00, 0x00, 0x00],
&[0x3F, 0x3F, 0x30, 0x30, 0x30],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_N: [&[u8]; 2] = [
&[0xFC, 0xFC, 0xF0, 0xC0, 0x00, 0xFC, 0xFC],
&[0x3F, 0x3F, 0x00, 0x03, 0x0F, 0x3F, 0x3F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_O: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x0C, 0x0C, 0xFC, 0xF8],
&[0x1F, 0x3F, 0x30, 0x30, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_P: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0xF8],
&[0x3F, 0x3F, 0x01, 0x01, 0x01, 0x00],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_Q: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x0C, 0x0C, 0xFC, 0xF8],
&[0x1F, 0x3F, 0x30, 0x38, 0x7F, 0x6F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_R: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0xF8],
&[0x3F, 0x3F, 0x01, 0x03, 0x3F, 0x3E],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_S: [&[u8]; 2] = [
&[0xF8, 0xFC, 0x8C, 0x8C, 0x9C, 0x18],
&[0x18, 0x39, 0x31, 0x31, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_T: [&[u8]; 2] = [
&[0x0C, 0x0C, 0xFC, 0xFC, 0x0C, 0x0C],
&[0x00, 0x00, 0x3F, 0x3F, 0x00, 0x00],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_U: [&[u8]; 2] = [
&[0xFC, 0xFC, 0x00, 0x00, 0xFC, 0xFC],
&[0x1F, 0x3F, 0x30, 0x30, 0x3F, 0x1F],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_UNDERSCORE: [&[u8]; 2] = [
&[0x00, 0x00, 0x00, 0x00, 0x00],
&[0x30, 0x30, 0x30, 0x30, 0x30],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
const SYM_INVALID: [&[u8]; 2] = [
&[0x80, 0xE0, 0x98, 0xCC, 0x4C, 0x18, 0xE0, 0x80],
&[0x01, 0x07, 0x1F, 0x24, 0x25, 0x1F, 0x07, 0x01],
];
// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized
pub const SYMBOL_TABLE: [&[&[u8]; 2]; 50] = [
&SYM_DOT, // '.'
&SYM_INVALID, // '/'
&SYM_0, // '0'
&SYM_1, // '1'
&SYM_2, // '2'
&SYM_3, // '3'
&SYM_4, // '4'
&SYM_5, // '5'
&SYM_6, // '6'
&SYM_7, // '7'
&SYM_8, // '8'
&SYM_9, // '9'
&SYM_COLON, // ':'
&SYM_INVALID, // ';'
&SYM_INVALID, // '<'
&SYM_INVALID, // '='
&SYM_INVALID, // '>'
&SYM_INVALID, // '?'
&SYM_INVALID, // '@'
&SYM_A, // 'A'
&SYM_B, // 'B'
&SYM_C, // 'C'
&SYM_INVALID, // 'D'
&SYM_E, // 'E'
&SYM_F, // 'F'
&SYM_G, // 'G'
&SYM_H, // 'H'
&SYM_I, // 'I'
&SYM_INVALID, // 'J'
&SYM_K, // 'K'
&SYM_L, // 'L'
&SYM_INVALID, // 'M'
&SYM_N, // 'N'
&SYM_O, // 'O'
&SYM_P, // 'P'
&SYM_Q, // 'Q'
&SYM_R, // 'R'
&SYM_S, // 'S'
&SYM_T, // 'T'
&SYM_U, // 'U'
&SYM_INVALID, // 'V'
&SYM_INVALID, // 'W'
&SYM_INVALID, // 'X'
&SYM_INVALID, // 'Y'
&SYM_INVALID, // 'Z'
&SYM_INVALID, // '['
&SYM_INVALID, // '\'
&SYM_INVALID, // ']'
&SYM_INVALID, // '^'
&SYM_UNDERSCORE, // '_'
];

View file

@ -0,0 +1,48 @@
use atmega_hal::Peripherals;
use avr_device::interrupt;
use core::convert::Infallible;
use nb::Error::WouldBlock;
pub fn read_byte(p_addr: *const u8) -> nb::Result<u8, Infallible> {
let dp = unsafe { Peripherals::steal() };
let eeprom = dp.EEPROM;
// Wait for completion of previous access
if eeprom.eecr.read().eepe().bit_is_set() {
return Err(WouldBlock);
}
interrupt::free(|_cs| {
// Write address into EEPROM address register
eeprom.eear.write(|w| unsafe { w.bits(p_addr as u16) });
// Start to read from EEPROM by setting EERE
eeprom.eecr.write(|w| w.eere().set_bit());
});
// Return data from EEPROM data register
Ok(eeprom.eedr.read().bits())
}
pub fn write_byte(p_addr: *const u8, data: u8) -> nb::Result<(), Infallible> {
let dp = unsafe { Peripherals::steal() };
let eeprom = dp.EEPROM;
// Wait for completion of previous access
if eeprom.eecr.read().eepe().bit_is_set() {
return Err(WouldBlock);
}
interrupt::free(|_cs| {
// Write address into EEPROM address register
eeprom.eear.write(|w| unsafe { w.bits(p_addr as u16) });
// Write data into EEPROM data register
eeprom.eedr.write(|w| unsafe { w.bits(data) });
// Enable writing to the EEPROM by setting EEMPE
eeprom.eecr.write(|w| w.eempe().set_bit());
// Start to write to EEPROM by setting EEPE
eeprom.eecr.write(|w| w.eepe().set_bit());
});
// Return data from EEPROM data register
Ok(())
}

359
firmware/rust/src/lcd.rs Normal file
View file

@ -0,0 +1,359 @@
use atmega_hal::{
delay::Delay,
port::{mode::Output, Pin, PB0, PB1},
Spi,
};
use core::convert::TryInto;
use embedded_hal::{blocking::delay::DelayMs, spi::FullDuplex};
use nb::block;
use crate::{assets::SYMBOL_TABLE, eeprom, DefaultClock, CONTRAST};
// TODO: Make `cd` and `rst` pins generic pins
pub struct Lcd {
pub spi: Spi,
pub cd: Pin<Output, PB0>,
rst: Pin<Output, PB1>,
}
impl Lcd {
pub fn new(spi: Spi, cd: Pin<Output, PB0>, rst: Pin<Output, PB1>) -> Self {
Self { spi, cd, rst }
}
pub fn init(&mut self) {
let mut delay = Delay::<DefaultClock>::new();
// TODO: Test if delay is really needed
delay.delay_ms(1_u8);
self.rst.set_high();
// TODO: Try to reduce delay to a minimum
delay.delay_ms(1_u8);
let init_sequence = [
0x40, // (6) Set Scroll Line: Display start line 0
0xA1, // (13) Set SEG direction: SEG reverse
0xC0, // (14) Set COM direction: Normal COM0 - COM63
0xA6, // (11) Set Inverse Display: Display inverse off
0xA2, // (17) Set LCD Bias Ratio: Set Bias 1/9 (Duty 1/65)
0x2F, // (5) Set Power Control: Booster, Regulator and Follower on
0x27, // (8) Set VLCD Resistor Ratio: Set Contrast
0xEE, // (18) Reset Cursor Update Mode
0x81, // (9) Set Electronic Volume: Set Contrast
nb::block!(eeprom::read_byte(&CONTRAST)).unwrap(), // (9) Set Electronic Volume: Set Contrast
0xAF, // (12) Set Display Enable: Display on
];
for i in init_sequence.iter() {
block!(self.spi.send(*i)).unwrap();
}
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
}
pub fn set_contrast(&mut self, contrast: u8) {
assert!(contrast <= 63);
block!(self.spi.send(0x81)).unwrap(); // (9) Set Electronic Volume: Set Contrast
block!(self.spi.send(contrast)).unwrap(); // (9) Set Electronic Volume: Set Contrast
}
pub fn move_cursor(&mut self, segment: u8, page: u8) {
assert!(segment < 102);
assert!(page < 8);
block!(self.spi.send(0x0F & segment)).unwrap();
block!(self.spi.send(0x10 + (segment >> 4))).unwrap();
block!(self.spi.send(0xB0 + page)).unwrap();
}
fn fill(&mut self, segment: u8, page: u8, width: u8, data: u8) {
assert!(segment + width <= 102);
let mut delay = Delay::<DefaultClock>::new();
self.move_cursor(segment, page);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
for _ in 0..width {
block!(self.spi.send(data)).unwrap();
}
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
pub fn fill_area(&mut self, segment: u8, page: u8, width: u8, height: u8, data: u8) {
assert!(page + height <= 8);
for i in 0..height {
self.fill(segment, page + i, width, data);
}
}
pub fn print(&mut self, segment: u8, page: u8, string: &str) {
let mut delay = Delay::<DefaultClock>::new();
for i in 0..2 {
self.move_cursor(segment, page + i);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
block!(self.spi.send(0x00)).unwrap();
block!(self.spi.send(0x00)).unwrap();
for c in string.chars() {
for segment in SYMBOL_TABLE[c as usize - 46][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}
pub fn print_inverted(&mut self, segment: u8, page: u8, string: &str) {
let mut delay = Delay::<DefaultClock>::new();
for i in 0..2 {
self.move_cursor(segment, page + i);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
block!(self.spi.send(0xFF)).unwrap();
block!(self.spi.send(0xFF)).unwrap();
for c in string.chars() {
for segment in SYMBOL_TABLE[c as usize - 46][i as usize] {
block!(self.spi.send(!*segment)).unwrap();
}
block!(self.spi.send(0xFF)).unwrap();
}
block!(self.spi.send(0xFF)).unwrap();
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}
pub fn print_u8(&mut self, segment: u8, page: u8, digits: u8, data: u8) {
assert!(digits <= 3);
let mut delay = Delay::<DefaultClock>::new();
let mut array = [0_usize; 3];
for (i, item) in array.iter_mut().enumerate() {
*item = (((data / 10_u8.pow(i.try_into().unwrap())) % 10) + 2).into();
}
array.reverse();
for i in 0..2 {
self.move_cursor(segment, page + i);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
block!(self.spi.send(0x00)).unwrap();
block!(self.spi.send(0x00)).unwrap();
for j in 3 - digits..3 {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}
pub fn print_u8_inverted(&mut self, segment: u8, page: u8, digits: u8, data: u8) {
assert!(digits <= 3);
let mut delay = Delay::<DefaultClock>::new();
let mut array = [0usize; 3];
for (i, item) in array.iter_mut().enumerate() {
*item = (((data / 10_u8.pow(i.try_into().unwrap())) % 10) + 2).into();
}
array.reverse();
for i in 0..2 {
self.move_cursor(segment, page + i);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
block!(self.spi.send(0xFF)).unwrap();
block!(self.spi.send(0xFF)).unwrap();
for j in 3 - digits..3 {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(!*segment)).unwrap();
}
block!(self.spi.send(0xFF)).unwrap();
}
block!(self.spi.send(0xFF)).unwrap();
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}
pub fn print_freq(&mut self, segment: u8, page: u8, data: u32) {
let mut delay = Delay::<DefaultClock>::new();
let mut array = [0usize; 9];
for (i, item) in array.iter_mut().enumerate() {
*item = (((data / 10_u32.pow(i.try_into().unwrap())) % 10) + 2)
.try_into()
.unwrap();
}
array.reverse();
for i in 0..2 {
self.move_cursor(segment, page + i);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
block!(self.spi.send(0x00)).unwrap();
block!(self.spi.send(0x00)).unwrap();
for j in 0..3 {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
}
for segment in SYMBOL_TABLE[0][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
for j in 3..6 {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
}
for segment in SYMBOL_TABLE[0][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
for j in 6..9 {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}
pub fn print_freq_digit(&mut self, segment: u8, page: u8, data: u32, digit: u8) {
let mut delay = Delay::<DefaultClock>::new();
let mut array = [0usize; 9];
for (i, item) in array.iter_mut().enumerate() {
*item = (((data / 10_u32.pow(i.try_into().unwrap())) % 10) + 2)
.try_into()
.unwrap();
}
array.reverse();
let digit = 8 - digit;
for i in 0..2 {
self.move_cursor(segment, page + i);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
block!(self.spi.send(0x00)).unwrap();
block!(self.spi.send(0x00)).unwrap();
for j in 0..3 {
if j == digit {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(!*segment)).unwrap();
}
} else {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
}
block!(self.spi.send(0x00)).unwrap();
}
for segment in SYMBOL_TABLE[0][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
for j in 3..6 {
if j == digit {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(!*segment)).unwrap();
}
} else {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
}
block!(self.spi.send(0x00)).unwrap();
}
for segment in SYMBOL_TABLE[0][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
for j in 6..9 {
if j == digit {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(!*segment)).unwrap();
}
} else {
for segment in SYMBOL_TABLE[array[j as usize]][i as usize] {
block!(self.spi.send(*segment)).unwrap();
}
}
block!(self.spi.send(0x00)).unwrap();
}
block!(self.spi.send(0x00)).unwrap();
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}
pub fn print_icon(&mut self, segment: u8, page: u8, symbol: &[u8]) {
let mut delay = Delay::<DefaultClock>::new();
self.move_cursor(segment, page);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_high();
for c in symbol {
block!(self.spi.send(*c)).unwrap();
}
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
self.cd.set_low();
}
}

207
firmware/rust/src/main.rs Normal file
View file

@ -0,0 +1,207 @@
#![feature(abi_avr_interrupt)]
#![no_std]
#![no_main]
use atmega_hal::{
clock::MHz8,
delay::Delay,
pac::{EXINT, TC1},
pins,
port::{self, mode::PullUp, Pin, PB6, PB7, PC0},
spi::{DataOrder, SerialClockRate, Settings, Spi},
Peripherals,
};
use avr_device::interrupt;
use core::sync::atomic::{AtomicBool, Ordering};
use embedded_hal::{
blocking::delay::DelayMs,
spi::{Mode, Phase, Polarity},
};
use panic_halt as _;
mod assets;
mod eeprom;
mod lcd;
mod rotary;
mod screen;
use rotary::{Direction, Rotary};
use screen::{Screen, Screens};
pub type DefaultClock = MHz8;
pub type I2c = atmega_hal::i2c::I2c<DefaultClock>;
#[used]
#[link_section = ".eeprom"]
static BACKLIGHT: u8 = 0;
#[used]
#[link_section = ".eeprom"]
static CONTRAST: u8 = 8;
pub enum Input {
Next,
Previous,
Select,
Back,
}
struct ClockGenerator {
screen: Screen,
tc1: TC1,
exint: EXINT,
encoder: Rotary<Pin<port::mode::Input<PullUp>, PB6>, Pin<port::mode::Input<PullUp>, PB7>>,
button: Pin<port::mode::Input<PullUp>, PC0>,
delay: Delay<DefaultClock>,
}
impl ClockGenerator {
fn new() -> Self {
let dp = Peripherals::take().unwrap();
let pins = pins!(dp);
// Init SPI
let (spi, _) = Spi::new(
dp.SPI,
pins.pb5.into_output(),
pins.pb3.into_output(),
pins.pb4.into_pull_up_input(),
pins.pb2.into_output(),
Settings {
data_order: DataOrder::MostSignificantFirst,
clock: SerialClockRate::OscfOver2,
mode: Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnFirstTransition,
},
},
);
Self {
screen: Screen::new(
dp.TC0,
spi,
pins.pd5.into_output(),
pins.pb0.into_output(),
pins.pb1.into_output(),
I2c::new(
dp.TWI,
pins.pc4.into_pull_up_input(),
pins.pc5.into_pull_up_input(),
50000,
),
),
tc1: dp.TC1,
exint: dp.EXINT,
delay: Delay::<DefaultClock>::new(),
encoder: Rotary::new(pins.pb6.into_pull_up_input(), pins.pb7.into_pull_up_input()),
button: pins.pc0.into_pull_up_input(),
}
}
fn init(&mut self) {
// Init Timer/Counter1
self.tc1.ocr1a.write(|w| unsafe { w.bits(65535) });
// Enable interrupts for encoder, button and timer
self.exint.pcicr.write(|w| w.pcie().bits(0b0000_0011));
self.exint.pcmsk0.write(|w| w.pcint().bits(0b1100_0000));
self.exint.pcmsk1.write(|w| w.pcint().bits(0b0000_0001));
self.tc1.timsk1.write(|w| w.ocie1a().set_bit());
// Init screen
self.screen.init();
// Show splash screen for a moment
self.delay.delay_ms(2000_u16);
// Set home screen
self.screen.change(Screens::Home(screen::Home::new()));
// Enable interrupts globally
unsafe { interrupt::enable() };
}
fn update(&mut self) {
if UPDATE_ENCODER.load(Ordering::SeqCst) {
interrupt::free(|_cs| {
self.delay.delay_ms(3_u8);
match self.encoder.update() {
Direction::Clockwise => {
self.screen.input(&Input::Next);
}
Direction::CounterClockwise => {
self.screen.input(&Input::Previous);
}
Direction::None => {}
}
UPDATE_ENCODER.store(false, Ordering::SeqCst);
})
}
if UPDATE_TIMER.load(Ordering::SeqCst) {
self.tc1.tccr1b.write(|w| w.cs1().no_clock());
self.screen.input(&Input::Back);
UPDATE_TIMER.store(false, Ordering::SeqCst);
}
if UPDATE_BUTTON.load(Ordering::SeqCst) {
self.delay.delay_ms(2_u8);
if self.button.is_low() {
// Press
if self.tc1.tccr1b.read().cs1().is_no_clock() {
self.tc1.tcnt1.write(|w| unsafe { w.bits(0) });
self.tc1.tccr1b.write(|w| w.cs1().prescale_64());
}
} else {
// Release
if self.tc1.tccr1b.read().cs1().is_prescale_64() {
self.tc1.tccr1b.write(|w| w.cs1().no_clock());
self.screen.input(&Input::Select);
self.delay.delay_ms(3_u8);
}
}
UPDATE_BUTTON.store(false, Ordering::SeqCst);
}
}
}
static UPDATE_ENCODER: AtomicBool = AtomicBool::new(false);
static UPDATE_BUTTON: AtomicBool = AtomicBool::new(false);
static UPDATE_TIMER: AtomicBool = AtomicBool::new(false);
#[interrupt(atmega328p)]
#[allow(non_snake_case)]
fn PCINT0() {
interrupt::free(|_cs| {
UPDATE_ENCODER.store(true, Ordering::SeqCst);
})
}
#[interrupt(atmega328p)]
#[allow(non_snake_case)]
fn PCINT1() {
interrupt::free(|_cs| {
UPDATE_BUTTON.store(true, Ordering::SeqCst);
})
}
#[interrupt(atmega328p)]
#[allow(non_snake_case)]
fn TIMER1_COMPA() {
interrupt::free(|_cs| {
UPDATE_TIMER.store(true, Ordering::SeqCst);
})
}
#[atmega_hal::entry]
fn main() -> ! {
let mut cg = ClockGenerator::new();
cg.init();
loop {
cg.update();
}
}

View file

@ -0,0 +1,51 @@
use embedded_hal::digital::v2::InputPin;
pub struct Rotary<A, B> {
pin_a: A,
pin_b: B,
state: u8,
}
pub enum Direction {
Clockwise,
CounterClockwise,
None,
}
impl<A, B> Rotary<A, B>
where
A: InputPin,
B: InputPin,
{
pub fn new(pin_a: A, pin_b: B) -> Self {
Self {
pin_a,
pin_b,
state: 0,
}
}
pub fn update(&mut self) -> Direction {
let a = self.pin_a.is_low();
let b = self.pin_b.is_low();
let new_state = match (a, b) {
(Ok(true), Ok(true)) => 0b00,
(Ok(true), Ok(false)) => 0b01,
(Ok(false), Ok(true)) => 0b10,
(Ok(false), Ok(false)) => 0b11,
_ => return Direction::None,
};
let direction = match (self.state, new_state) {
(0b10, 0b11) => Direction::Clockwise,
(0b01, 0b00) => Direction::Clockwise,
(0b01, 0b11) => Direction::CounterClockwise,
(0b10, 0b00) => Direction::CounterClockwise,
_ => Direction::None,
};
self.state = new_state;
direction
}
}

View file

@ -0,0 +1,191 @@
use super::{ClockChannel, Event, Home, Screens};
use crate::{lcd::Lcd, Input};
use si5351::{ClockOutput, PLL};
enum Selection {
Frequency,
FrequencyDigit(u8),
Digit(u8),
Enabled,
Pll,
Back,
}
pub struct Channel {
active: Selection,
channel: ClockChannel,
}
impl Channel {
pub fn new(channel: ClockChannel) -> Self {
Self {
active: Selection::Frequency,
channel,
}
}
pub fn input(&mut self, input: &Input) -> Event {
self.active = match self.active {
Selection::Frequency => match input {
Input::Next => Selection::Enabled,
Input::Previous => Selection::Back,
Input::Select => Selection::FrequencyDigit(8),
Input::Back => return Event::Screen(Screens::Home(Home::new())),
},
Selection::FrequencyDigit(digit) => match input {
Input::Next => {
if digit == u8::MIN {
Selection::FrequencyDigit(8)
} else {
Selection::FrequencyDigit(digit - 1)
}
}
Input::Previous => {
if digit >= 8 {
Selection::FrequencyDigit(u8::MIN)
} else {
Selection::FrequencyDigit(digit + 1)
}
}
Input::Select => Selection::Digit(digit),
Input::Back => Selection::Frequency,
},
Selection::Digit(digit) => match input {
Input::Next => {
let new_freq = self.channel.freq + 10_u32.pow(digit.into());
if new_freq < 162_000_000 {
self.channel.freq = new_freq;
} else {
self.channel.freq = 162_000_000
}
return Event::Channel(self.channel);
}
Input::Previous => {
let difference = 10_u32.pow(digit.into());
if self.channel.freq > difference {
let new_freq = self.channel.freq - difference;
if new_freq > 15_000 {
self.channel.freq = new_freq;
} else {
self.channel.freq = 15_000;
}
} else {
self.channel.freq = 15_000;
}
return Event::Channel(self.channel);
}
Input::Select => Selection::Frequency,
Input::Back => Selection::FrequencyDigit(digit),
},
Selection::Enabled => match input {
Input::Next => Selection::Pll,
Input::Previous => Selection::Frequency,
Input::Select => {
self.channel.enabled = !self.channel.enabled;
return Event::Channel(self.channel);
}
Input::Back => return Event::Screen(Screens::Home(Home::new())),
},
Selection::Pll => match input {
Input::Next => Selection::Back,
Input::Previous => Selection::Enabled,
Input::Select => {
self.channel.pll = match self.channel.pll {
PLL::A => PLL::B,
PLL::B => PLL::A,
};
return Event::Channel(self.channel);
}
Input::Back => return Event::Screen(Screens::Home(Home::new())),
},
Selection::Back => match input {
Input::Next => Selection::Frequency,
Input::Previous => Selection::Pll,
_ => return Event::Screen(Screens::Home(Home::new())),
},
};
Event::None
}
fn draw_enabled(&self, lcd: &mut Lcd, inverted: bool) {
if inverted {
match self.channel.enabled {
false => lcd.print_inverted(13, 4, "OFF"),
true => lcd.print_inverted(16, 4, "ON"),
}
} else {
match self.channel.enabled {
false => lcd.print(13, 4, "OFF"),
true => lcd.print(16, 4, "ON"),
}
}
}
pub fn draw(&self, lcd: &mut Lcd) {
lcd.fill_area(0, 0, 19, 2, 0xFF);
match self.channel.output {
ClockOutput::Clk0 => lcd.print_inverted(19, 0, "CHANNEL_1"),
ClockOutput::Clk1 => lcd.print_inverted(19, 0, "CHANNEL_2"),
ClockOutput::Clk2 => lcd.print_inverted(19, 0, "CHANNEL_3"),
_ => unimplemented!(),
}
lcd.fill_area(83, 0, 19, 2, 0xFF);
match &self.active {
Selection::Frequency => {
lcd.print_inverted(0, 2, "FREQ:");
lcd.print_freq(39, 2, self.channel.freq);
self.draw_enabled(lcd, false);
match self.channel.pll {
PLL::A => lcd.print(59, 4, "PLL_A"),
PLL::B => lcd.print(59, 4, "PLL_B"),
}
lcd.print(36, 6, "BACK");
}
Selection::FrequencyDigit(digit) | Selection::Digit(digit) => {
lcd.print(0, 2, "FREQ:");
lcd.print_freq_digit(39, 2, self.channel.freq, *digit);
self.draw_enabled(lcd, false);
match self.channel.pll {
PLL::A => lcd.print(59, 4, "PLL_A"),
PLL::B => lcd.print(59, 4, "PLL_B"),
}
lcd.print(36, 6, "BACK");
}
Selection::Enabled => {
lcd.print(0, 2, "FREQ:");
lcd.print_freq(39, 2, self.channel.freq);
self.draw_enabled(lcd, true);
match self.channel.pll {
PLL::A => lcd.print(59, 4, "PLL_A"),
PLL::B => lcd.print(59, 4, "PLL_B"),
}
lcd.print(36, 6, "BACK");
}
Selection::Pll => {
lcd.print(0, 2, "FREQ:");
lcd.print_freq(39, 2, self.channel.freq);
self.draw_enabled(lcd, false);
match self.channel.pll {
PLL::A => lcd.print_inverted(59, 4, "PLL_A"),
PLL::B => lcd.print_inverted(59, 4, "PLL_B"),
}
lcd.print(36, 6, "BACK");
}
Selection::Back => {
lcd.print(0, 2, "FREQ:");
lcd.print_freq(39, 2, self.channel.freq);
match self.channel.enabled {
false => lcd.print(13, 4, "OFF"),
true => lcd.print(16, 4, "ON"),
}
match self.channel.pll {
PLL::A => lcd.print(59, 4, "PLL_A"),
PLL::B => lcd.print(59, 4, "PLL_B"),
}
lcd.print_inverted(36, 6, "BACK");
}
}
}
}

View file

@ -0,0 +1,93 @@
use super::{Channel, Event, Screens, Setup};
use crate::{lcd::Lcd, screen::ClockChannel, Input};
enum Selection {
Ch1,
Ch2,
Ch3,
Setup,
}
pub struct Home {
active: Selection,
}
impl Home {
pub fn new() -> Self {
Self {
active: Selection::Ch1,
}
}
pub fn input(&mut self, input: &Input, channels: [ClockChannel; 3]) -> Event {
self.active = match self.active {
Selection::Ch1 => match input {
Input::Next => Selection::Ch2,
Input::Previous => Selection::Setup,
Input::Select => return Event::Screen(Screens::Channel(Channel::new(channels[0]))),
Input::Back => Selection::Ch1,
},
Selection::Ch2 => match input {
Input::Next => Selection::Ch3,
Input::Previous => Selection::Ch1,
Input::Select => return Event::Screen(Screens::Channel(Channel::new(channels[1]))),
Input::Back => Selection::Ch2,
},
Selection::Ch3 => match input {
Input::Next => Selection::Setup,
Input::Previous => Selection::Ch2,
Input::Select => return Event::Screen(Screens::Channel(Channel::new(channels[2]))),
Input::Back => Selection::Ch3,
},
Selection::Setup => match input {
Input::Next => Selection::Ch1,
Input::Previous => Selection::Ch3,
Input::Select => return Event::Screen(Screens::Setup(Setup::new())),
Input::Back => Selection::Setup,
},
};
Event::None
}
pub fn draw(&self, lcd: &mut Lcd, channels: [ClockChannel; 3]) {
match &self.active {
Selection::Ch1 => {
lcd.print_inverted(0, 0, "CH1");
channels[0].print(lcd, 0);
lcd.print(0, 2, "CH2");
channels[1].print(lcd, 2);
lcd.print(0, 4, "CH3");
channels[2].print(lcd, 4);
lcd.print(33, 6, "SETUP");
}
Selection::Ch2 => {
lcd.print(0, 0, "CH1");
channels[0].print(lcd, 0);
lcd.print_inverted(0, 2, "CH2");
channels[1].print(lcd, 2);
lcd.print(0, 4, "CH3");
channels[2].print(lcd, 4);
lcd.print(33, 6, "SETUP");
}
Selection::Ch3 => {
lcd.print(0, 0, "CH1");
channels[0].print(lcd, 0);
lcd.print(0, 2, "CH2");
channels[1].print(lcd, 2);
lcd.print_inverted(0, 4, "CH3");
channels[2].print(lcd, 4);
lcd.print(33, 6, "SETUP");
}
Selection::Setup => {
lcd.print(0, 0, "CH1");
channels[0].print(lcd, 0);
lcd.print(0, 2, "CH2");
channels[1].print(lcd, 2);
lcd.print(0, 4, "CH3");
channels[2].print(lcd, 4);
lcd.print_inverted(33, 6, "SETUP");
}
}
}
}

View file

@ -0,0 +1,206 @@
use atmega_hal::{
pac::TC0,
port::{mode::Output, Pin, PB0, PB1, PD5},
Spi,
};
use si5351::{ClockOutput, Si5351, Si5351Device, PLL};
mod channel;
mod home;
mod setup;
mod splash;
use crate::{
assets::{OFF, ON, PLL_A, PLL_B},
eeprom,
lcd::Lcd,
I2c, Input, BACKLIGHT,
};
pub use channel::Channel;
pub use home::Home;
pub use setup::Setup;
pub use splash::Splash;
// TODO: Only update changes instead of whole screen
pub enum Event {
Screen(Screens),
Backlight(u8),
Contrast(u8),
Channel(ClockChannel),
None,
}
#[derive(Clone, Copy)]
pub struct ClockChannel {
output: ClockOutput,
freq: u32,
enabled: bool,
pll: PLL,
}
impl ClockChannel {
fn print(&self, lcd: &mut Lcd, page: u8) {
lcd.print_freq(25, page, self.freq);
lcd.print_icon(91, page, if self.enabled { &ON } else { &OFF });
lcd.print_icon(
94,
page + 1,
match self.pll {
PLL::A => &PLL_A,
PLL::B => &PLL_B,
},
);
}
fn update(&self, si5351: &mut Si5351Device<I2c>) {
si5351
.set_frequency(self.pll, self.output, self.freq)
.unwrap();
si5351.set_clock_enabled(self.output, self.enabled);
si5351.flush_output_enabled().unwrap();
}
}
pub enum Screens {
Splash(Splash),
Home(Home),
Setup(Setup),
Channel(Channel),
}
impl Screens {
pub fn input(&mut self, input: &Input, channels: [ClockChannel; 3]) -> Event {
match self {
Screens::Splash(_) => Event::None,
Screens::Home(home) => home.input(input, channels),
Screens::Setup(setup) => setup.input(input),
Screens::Channel(channel) => channel.input(input),
}
}
}
pub struct Screen {
lcd: Lcd,
tc0: TC0,
pwm: Pin<Output, PD5>,
screen: Screens,
si5351: Si5351Device<I2c>,
channels: [ClockChannel; 3],
}
impl Screen {
pub fn new(
tc0: TC0,
spi: Spi,
pwm: Pin<Output, PD5>,
cd: Pin<Output, PB0>,
rst: Pin<Output, PB1>,
i2c: I2c,
) -> Self {
Self {
lcd: Lcd::new(spi, cd, rst),
tc0,
pwm,
screen: Screens::Splash(Splash),
si5351: Si5351Device::new_adafruit_module(i2c),
channels: [
ClockChannel {
output: ClockOutput::Clk0,
freq: 1_000_000,
enabled: false,
pll: PLL::A,
},
ClockChannel {
output: ClockOutput::Clk1,
freq: 1_000_000,
enabled: false,
pll: PLL::A,
},
ClockChannel {
output: ClockOutput::Clk2,
freq: 1_000_000,
enabled: false,
pll: PLL::A,
},
],
}
}
pub fn init(&mut self) {
// Init display backlight
self.tc0.ocr0a.write(|w| unsafe { w.bits(255) });
self.tc0.tccr0a.write(|w| {
w.wgm0().pwm_fast();
w.com0b().match_clear()
});
self.set_backlight(nb::block!(eeprom::read_byte(&BACKLIGHT)).unwrap());
// Init lcd display
self.lcd.init();
self.draw();
// Init Si5351
self.si5351.init_adafruit_module().unwrap();
}
fn set_backlight(&mut self, backlight: u8) {
match backlight {
0 => {
self.tc0.tccr0b.write(|w| w.cs0().no_clock());
self.pwm.set_low();
}
1..=5 => {
self.tc0.tccr0b.write(|w| {
w.wgm02().set_bit();
w.cs0().prescale_256()
});
self.tc0.ocr0b.write(|w| unsafe { w.bits(backlight - 1) });
}
_ => {
self.tc0.tccr0b.write(|w| {
w.wgm02().set_bit();
w.cs0().prescale_64()
});
self.tc0.ocr0b.write(|w| unsafe { w.bits(backlight - 6) });
}
}
}
pub fn draw(&mut self) {
self.lcd.fill_area(0, 0, 102, 8, 0x00);
match &self.screen {
Screens::Splash(splash) => splash.draw(&mut self.lcd),
Screens::Home(home) => home.draw(&mut self.lcd, self.channels),
Screens::Setup(setup) => setup.draw(&mut self.lcd),
Screens::Channel(channel) => channel.draw(&mut self.lcd),
}
}
pub fn input(&mut self, input: &Input) {
match self.screen.input(input, self.channels) {
Event::Screen(screen) => self.screen = screen,
Event::Backlight(backlight) => self.set_backlight(backlight),
Event::Contrast(contrast) => self.lcd.set_contrast(contrast),
Event::Channel(channel) => {
match channel.output {
ClockOutput::Clk0 => self.channels[0] = channel,
ClockOutput::Clk1 => self.channels[1] = channel,
ClockOutput::Clk2 => self.channels[2] = channel,
_ => unimplemented!(),
}
channel.update(&mut self.si5351);
}
Event::None => {}
}
self.draw();
}
pub fn change(&mut self, screen: Screens) {
self.screen = screen;
self.draw();
}
}

View file

@ -0,0 +1,152 @@
use super::{Event, Home, Screens};
use crate::{eeprom, lcd::Lcd, Input, BACKLIGHT, CONTRAST};
enum Selection {
Backlight,
BacklightEdit,
Contrast,
ContrastEdit,
Back,
}
pub struct Setup {
active: Selection,
backlight: u8,
contrast: u8,
}
impl Setup {
pub fn new() -> Self {
Self {
active: Selection::Backlight,
backlight: nb::block!(eeprom::read_byte(&BACKLIGHT)).unwrap(),
contrast: nb::block!(eeprom::read_byte(&CONTRAST)).unwrap(),
}
}
pub fn input(&mut self, input: &Input) -> Event {
self.active = match self.active {
Selection::Backlight => match input {
Input::Next => Selection::Contrast,
Input::Previous => Selection::Back,
Input::Select => Selection::BacklightEdit,
Input::Back => return Event::Screen(Screens::Home(Home::new())),
},
Selection::BacklightEdit => match input {
Input::Next => {
self.backlight = if self.backlight == u8::MAX {
u8::MIN
} else {
self.backlight + 1
};
return Event::Backlight(self.backlight);
}
Input::Previous => {
self.backlight = if self.backlight == u8::MIN {
u8::MAX
} else {
self.backlight - 1
};
return Event::Backlight(self.backlight);
}
Input::Select => {
nb::block!(eeprom::write_byte(&BACKLIGHT, self.backlight)).unwrap();
Selection::Backlight
}
Input::Back => {
self.active = Selection::Backlight;
self.backlight = nb::block!(eeprom::read_byte(&BACKLIGHT)).unwrap();
return Event::Backlight(self.backlight);
}
},
Selection::Contrast => match input {
Input::Next => Selection::Back,
Input::Previous => Selection::Backlight,
Input::Select => Selection::ContrastEdit,
Input::Back => return Event::Screen(Screens::Home(Home::new())),
},
Selection::ContrastEdit => match input {
Input::Next => {
self.contrast = if self.contrast >= 63 {
u8::MIN
} else {
self.contrast + 1
};
return Event::Contrast(self.contrast);
}
Input::Previous => {
self.contrast = if self.contrast == u8::MIN {
63
} else {
self.contrast - 1
};
return Event::Contrast(self.contrast);
}
Input::Select => {
nb::block!(eeprom::write_byte(&CONTRAST, self.contrast)).unwrap();
Selection::Contrast
}
Input::Back => {
self.active = Selection::Contrast;
self.contrast = nb::block!(eeprom::read_byte(&CONTRAST)).unwrap();
return Event::Contrast(self.contrast);
}
},
Selection::Back => match input {
Input::Next => Selection::Backlight,
Input::Previous => Selection::Contrast,
_ => return Event::Screen(Screens::Home(Home::new())),
},
};
Event::None
}
pub fn draw(&self, lcd: &mut Lcd) {
lcd.fill_area(0, 0, 33, 2, 0xFF);
lcd.print_inverted(33, 0, "SETUP");
lcd.fill_area(69, 0, 33, 2, 0xFF);
match &self.active {
Selection::Contrast => {
lcd.print(0, 2, "BACKLIGHT:");
lcd.print_u8(81, 2, 3, self.backlight);
lcd.print_inverted(0, 4, "CONTRAST:");
lcd.print_u8(87, 4, 2, self.contrast);
lcd.print(36, 6, "BACK");
}
Selection::ContrastEdit => {
lcd.print(0, 2, "BACKLIGHT:");
lcd.print_u8(81, 2, 3, self.backlight);
lcd.print(0, 4, "CONTRAST:");
lcd.print_u8_inverted(87, 4, 2, self.contrast);
lcd.print(36, 6, "BACK");
}
Selection::Backlight => {
lcd.print_inverted(0, 2, "BACKLIGHT:");
lcd.print_u8(81, 2, 3, self.backlight);
lcd.print(0, 4, "CONTRAST:");
lcd.print_u8(87, 4, 2, self.contrast);
lcd.print(36, 6, "BACK");
}
Selection::BacklightEdit => {
lcd.print(0, 2, "BACKLIGHT:");
lcd.print_u8_inverted(81, 2, 3, self.backlight);
lcd.print(0, 4, "CONTRAST:");
lcd.print_u8(87, 4, 2, self.contrast);
lcd.print(36, 6, "BACK");
}
Selection::Back => {
lcd.print(0, 2, "BACKLIGHT:");
lcd.print_u8(81, 2, 3, self.backlight);
lcd.print(0, 4, "CONTRAST:");
lcd.print_u8(87, 4, 2, self.contrast);
lcd.print_inverted(36, 6, "BACK");
}
}
}
}

View file

@ -0,0 +1,48 @@
use crate::{
assets::{ONDERS_ORG, SACRED_CHAO},
lcd::Lcd,
DefaultClock,
};
use atmega_hal::delay::Delay;
use embedded_hal::{blocking::delay::DelayMs, spi::FullDuplex};
use nb::block;
pub struct Splash;
impl Splash {
pub fn draw(&self, lcd: &mut Lcd) {
let mut delay = Delay::<DefaultClock>::new();
for (i, page) in SACRED_CHAO.iter().enumerate() {
lcd.move_cursor(31, 1 + i as u8);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
lcd.cd.set_high();
for segment in page {
block!(lcd.spi.send(*segment)).unwrap();
}
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
lcd.cd.set_low();
}
for (i, page) in ONDERS_ORG.iter().enumerate() {
lcd.move_cursor(27, 6 + i as u8);
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
lcd.cd.set_high();
for segment in page {
block!(lcd.spi.send(*segment)).unwrap();
}
// TODO: This delay fixes issues, try find a better solution
delay.delay_ms(1_u8);
lcd.cd.set_low();
}
}
}

21
knob/knob.scad Normal file
View file

@ -0,0 +1,21 @@
$fn = 200;
module shaft() {
difference() {
cylinder(h = 5, d = 6);
translate([2, -2.5, 0])
cube(5);
}
translate([0, 0, -5])
cylinder(h = 5, d = 8);
}
difference() {
cylinder(h = 14, d1 = 18, d2 = 16);
translate([0, 0, 5])
shaft();
}
translate([0, 0, 14])
cylinder(h = 2, d1 = 16, d2 = 12);