Compare commits
37 commits
af3790be0d
...
9b7bfb0562
Author | SHA1 | Date | |
---|---|---|---|
9b7bfb0562 | |||
791a0a0245 | |||
b51511dbab | |||
c36581e703 | |||
7f14974146 | |||
9994b1fc40 | |||
4a03c7045f | |||
52bf0e6eec | |||
134db298f6 | |||
fdd1f4636d | |||
6cd8df515d | |||
800d3ff8c3 | |||
34d1623abb | |||
63cc1c9d0f | |||
08a371062d | |||
7d8f5f6870 | |||
c2920ea334 | |||
91e120b258 | |||
0003717408 | |||
2ec8d1aeb9 | |||
4a33986e8d | |||
1688bb868e | |||
de8f789e63 | |||
518f113cc0 | |||
9425093391 | |||
36be6d27bc | |||
8b9d21a012 | |||
b34b2810db | |||
4bc3e84bdd | |||
8315560daa | |||
a688d4c5ed | |||
c42b4595c7 | |||
817776d735 | |||
643e5f1af4 | |||
aa59bc302d | |||
d2772291bf | |||
aa75712418 |
20 changed files with 2057 additions and 9 deletions
28
.woodpecker.yml
Normal file
28
.woodpecker.yml
Normal 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
|
17
README.md
17
README.md
|
@ -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.
|
|
||||||
|
|
6
firmware/rust/.cargo/config.toml
Normal file
6
firmware/rust/.cargo/config.toml
Normal 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
1
firmware/rust/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
233
firmware/rust/Cargo.lock
generated
Normal file
233
firmware/rust/Cargo.lock
generated
Normal 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
35
firmware/rust/Cargo.toml
Normal 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"
|
45
firmware/rust/Makefile.toml
Normal file
45
firmware/rust/Makefile.toml
Normal 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"] }
|
27
firmware/rust/avr-specs/avr-atmega328p.json
Normal file
27
firmware/rust/avr-specs/avr-atmega328p.json
Normal 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"
|
||||||
|
}
|
4
firmware/rust/rust-toolchain.toml
Normal file
4
firmware/rust/rust-toolchain.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2021-01-07"
|
||||||
|
components = [ "rust-src" ]
|
||||||
|
profile = "minimal"
|
294
firmware/rust/src/assets.rs
Normal file
294
firmware/rust/src/assets.rs
Normal 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, // '_'
|
||||||
|
];
|
48
firmware/rust/src/eeprom.rs
Normal file
48
firmware/rust/src/eeprom.rs
Normal 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
359
firmware/rust/src/lcd.rs
Normal 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
207
firmware/rust/src/main.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
51
firmware/rust/src/rotary.rs
Normal file
51
firmware/rust/src/rotary.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
191
firmware/rust/src/screen/channel.rs
Normal file
191
firmware/rust/src/screen/channel.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
firmware/rust/src/screen/home.rs
Normal file
93
firmware/rust/src/screen/home.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
firmware/rust/src/screen/mod.rs
Normal file
206
firmware/rust/src/screen/mod.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
152
firmware/rust/src/screen/setup.rs
Normal file
152
firmware/rust/src/screen/setup.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
firmware/rust/src/screen/splash.rs
Normal file
48
firmware/rust/src/screen/splash.rs
Normal 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
21
knob/knob.scad
Normal 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);
|
Loading…
Reference in a new issue