Compare commits

...

20 Commits

Author SHA1 Message Date
cac25d6006 linear dimming 2025-10-07 19:10:43 +01:00
97079c7b10 dimming 2025-10-07 18:38:53 +01:00
d66914f74f remove debugging 2025-10-04 15:45:54 +01:00
42c18a6d5a brightness scaling optimization 2025-10-04 15:01:50 +01:00
e98a24d32d timer debugging 2025-10-04 14:14:10 +01:00
556ac72d10 refactoring 2025-08-05 20:13:11 +01:00
f816a09554 control segment brightness; test pattern 2025-07-26 15:14:08 +01:00
6517cfd72b timer refactoring 2025-07-26 13:41:50 +01:00
9bec55bcc7 pointing to upstream crate URL 2025-07-18 21:41:30 +01:00
9b7df6b680 license update and README 2025-07-18 21:14:37 +01:00
d6a4b0fe22 got to idle state waiting for key 2025-07-18 20:31:23 +01:00
cba331b939 refactoring of timing constants 2025-07-18 20:31:23 +01:00
56f2738ea2 use timer to update display and read keyboard 2025-07-18 20:31:23 +01:00
1f961964f0 use B compare match register to trigger interrupt 2025-07-18 20:31:23 +01:00
7b524aaae2 trigger LED on timer 2025-07-18 20:31:23 +01:00
8c27acbf50 initial setup to use interrupts for I/O 2025-07-18 20:31:23 +01:00
ef97c334b5 zero latency debounce 2025-07-18 20:31:23 +01:00
d69c3eb56d binary segment representation 2025-07-18 20:31:23 +01:00
46cb2886f2 show stack depth 2025-07-18 20:31:23 +01:00
82fed85229 refactor show 2025-07-18 20:31:23 +01:00
9 changed files with 527 additions and 266 deletions

30
Cargo.lock generated
View File

@@ -27,16 +27,17 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "avr-calc" name = "avr-calc"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arduino-hal", "arduino-hal",
"calc-math", "avr-device",
"core-decimal-calc",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"nb 1.1.0", "nb 1.1.0",
"ufmt", "ufmt",
@@ -88,19 +89,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[package]] [[package]]
name = "calc-math" name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "core-decimal-calc"
version = "0.1.0" version = "0.1.0"
source = "git+https://gitea.hexadust.net/hxd/core-decimal-calc.git#2a0b7a34f032ce58699c3944444ad19a11cfe2e8"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"ufmt", "ufmt",
] ]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "critical-section" name = "critical-section"
version = "1.2.0" version = "1.2.0"
@@ -171,18 +173,18 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.79" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]

View File

@@ -14,7 +14,8 @@ bench = false
ufmt = "0.2.0" ufmt = "0.2.0"
nb = "1.1.0" nb = "1.1.0"
embedded-hal = "1.0" embedded-hal = "1.0"
calc-math = { path = "../calc-math", features = ["ufmt"] } core-decimal-calc = { git = "https://gitea.hexadust.net/hxd/core-decimal-calc.git", features = ["ufmt"] }
avr-device = "0.7.0"
[dependencies.arduino-hal] [dependencies.arduino-hal]
git = "https://github.com/rahix/avr-hal" git = "https://github.com/rahix/avr-hal"

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,17 +1,14 @@
avr-calc # Sinclair Scientific Calculator Emulator alternative software written in Rust
========
Alternative implementation for [Sinclair Scientific calculator](https://wiki.hexadust.net/books/electronics/page/sinclair-scientific-calculator-emulator-1974). Alternative implementation for [Sinclair Scientific calculator](https://wiki.hexadust.net/books/electronics/page/sinclair-scientific-calculator-emulator-1974) board.
## Build Instructions ## Build Instructions
1. Install prerequisites as described in the [`avr-hal` README] (`avr-gcc`, `avr-libc`, `avrdude`, [`ravedude`]). 1. Install prerequisites as described in the [`avr-hal` README] (`avr-gcc`, `avr-libc`, `avrdude`, [`ravedude`]).
2. Use `nightly-2024-03-22` toolchain.
2. Run `cargo build` to build the firmware. 2. Run `cargo build --release` to build the firmware (debug builds won't work for now).
3. Run `RAVEDUDE_PORT=/dev/ttyUSB0 cargo run --release` to flash the firmware to a connected board. If `ravedude` fails to detect your board, check its documentation at
3. Run `cargo run` to flash the firmware to a connected board. If `ravedude` fails to detect your board, check its documentation at
<https://crates.io/crates/ravedude>. <https://crates.io/crates/ravedude>.
4. `ravedude` will open a console session after flashing where you can interact with the UART console of your board. 4. `ravedude` will open a console session after flashing where you can interact with the UART console of your board.
[`avr-hal` README]: https://github.com/Rahix/avr-hal#readme [`avr-hal` README]: https://github.com/Rahix/avr-hal#readme
@@ -19,7 +16,7 @@ Alternative implementation for [Sinclair Scientific calculator](https://wiki.hex
## License ## License
- ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) - [LICENSE-APACHE](LICENSE-APACHE)
## Floating point support ## Floating point support

View File

@@ -5,7 +5,7 @@ use arduino_hal::{
port::{mode::Output, Pin}, port::{mode::Output, Pin},
}; };
use crate::DISPLAY_SEGMENTS; use crate::{DISPLAY_SEGMENTS, IO_SEGMENT_ON_MAX_US, IO_SEGMENT_ON_MIN_US};
pub struct SegmentPins { pub struct SegmentPins {
kd_seg_a: Pin<Output, PD5>, kd_seg_a: Pin<Output, PD5>,
@@ -82,169 +82,166 @@ impl SegmentPins {
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct Segment { pub struct Brightness(u8);
a: bool,
b: bool, impl Brightness {
c: bool, pub const fn new(b: u8) -> Brightness {
d: bool, Brightness(b)
e: bool, }
f: bool,
g: bool, pub const fn full() -> Brightness {
dp: bool, Brightness(u8::MAX)
}
pub const fn unwrap(self) -> u8 {
self.0
}
pub fn dimm(self, dimming: u8) -> Brightness {
Brightness(self.0.saturating_sub(dimming))
}
// Scales brightness (0-255) to range between IO_SEGMENT_ON_MIN_US and IO_SEGMENT_ON_MAX_US
pub fn scale_brightness(self) -> u32 {
// Using >> to avoid 32bit division which take ~576 cycles
IO_SEGMENT_ON_MIN_US + ((IO_SEGMENT_ON_MAX_US - IO_SEGMENT_ON_MIN_US) * self.0 as u32 >> 8)
}
} }
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Segment(u8, Brightness);
impl Segment { impl Segment {
pub fn new() -> Segment { pub fn new() -> Segment {
Segment { Segment(0, Brightness::full())
a: false,
b: false,
c: false,
d: false,
e: false,
f: false,
g: false,
dp: false,
}
} }
pub fn apply(&self, seg: &mut SegmentPins) { pub fn brightness(&mut self, b: Brightness) -> &mut Self {
seg.set_off(); self.1 = b;
if self.a { self
seg.set_a();
}
if self.b {
seg.set_b();
}
if self.c {
seg.set_c();
}
if self.d {
seg.set_d();
}
if self.e {
seg.set_e();
}
if self.f {
seg.set_f();
}
if self.g {
seg.set_g();
}
if self.dp {
seg.set_dp();
}
} }
pub fn off(&mut self) -> &mut Self { pub fn off(&mut self) -> &mut Self {
self.a = false; self.0 = 0;
self.b = false;
self.c = false;
self.d = false;
self.e = false;
self.f = false;
self.g = false;
self.dp = false;
self self
} }
pub fn a(&mut self) -> &mut Self {
self.0 |= 0b1000_0000;
self
}
pub fn b(&mut self) -> &mut Self {
self.0 |= 0b0100_0000;
self
}
pub fn c(&mut self) -> &mut Self {
self.0 |= 0b0010_0000;
self
}
pub fn d(&mut self) -> &mut Self {
self.0 |= 0b0001_0000;
self
}
pub fn e(&mut self) -> &mut Self {
self.0 |= 0b0000_1000;
self
}
pub fn f(&mut self) -> &mut Self {
self.0 |= 0b0000_0100;
self
}
pub fn g(&mut self) -> &mut Self {
self.0 |= 0b0000_0010;
self
}
pub fn dp(&mut self) -> &mut Self {
self.0 |= 0b0000_0001;
self
}
pub fn apply(&self, seg: &mut SegmentPins) -> Brightness {
seg.set_off();
if self.0 & 0b1000_0000 != 0 {
seg.set_a();
}
if self.0 & 0b0100_0000 != 0 {
seg.set_b();
}
if self.0 & 0b0010_0000 != 0 {
seg.set_c();
}
if self.0 & 0b0001_0000 != 0 {
seg.set_d();
}
if self.0 & 0b0000_1000 != 0 {
seg.set_e();
}
if self.0 & 0b0000_0100 != 0 {
seg.set_f();
}
if self.0 & 0b0000_0010 != 0 {
seg.set_g();
}
if self.0 & 0b0000_0001 != 0 {
seg.set_dp();
}
self.1
}
pub fn num(&mut self, no: u8) -> &mut Self { pub fn num(&mut self, no: u8) -> &mut Self {
match no { match no {
0 => { 0 => {
self.a = true; self.a().b().c().d().e().f();
self.b = true;
self.c = true;
self.d = true;
self.e = true;
self.f = true;
} }
1 => { 1 => {
self.b = true; self.b().c();
self.c = true;
} }
2 => { 2 => {
self.a = true; self.a().b().g().e().d();
self.b = true;
self.g = true;
self.e = true;
self.d = true;
} }
3 => { 3 => {
self.a = true; self.a().b().g().c().d();
self.b = true;
self.g = true;
self.c = true;
self.d = true;
} }
4 => { 4 => {
self.f = true; self.f().g().b().c();
self.g = true;
self.b = true;
self.c = true;
} }
5 => { 5 => {
self.a = true; self.a().f().g().c().d();
self.f = true;
self.g = true;
self.c = true;
self.d = true;
} }
6 => { 6 => {
self.a = true; self.a().f().g().c().d().e();
self.f = true;
self.g = true;
self.c = true;
self.d = true;
self.e = true;
} }
7 => { 7 => {
self.a = true; self.a().b().c();
self.b = true;
self.c = true;
} }
8 => { 8 => {
self.a = true; self.a().b().c().d().e().f().g();
self.b = true;
self.c = true;
self.d = true;
self.e = true;
self.f = true;
self.g = true;
} }
9 => { 9 => {
self.a = true; self.a().b().c().d().f().g();
self.b = true;
self.c = true;
self.d = true;
self.f = true;
self.g = true;
} }
_ => panic!("Num out of range"), _ => panic!("Num out of range"),
} }
self self
} }
pub fn dp(&mut self) -> &mut Self {
self.dp = true;
self
}
pub fn minus(&mut self) -> &mut Self { pub fn minus(&mut self) -> &mut Self {
self.g = true; self.g()
self
} }
pub fn prompt(&mut self) -> &mut Self { pub fn prompt(&mut self) -> &mut Self {
self.d = true; self.d()
self
} }
pub fn e(&mut self) -> &mut Self { pub fn sym_e(&mut self) -> &mut Self {
self.a = true; self.a().d().e().f().g()
self.d = true;
self.e = true;
self.f = true;
self.g = true;
self
} }
} }
@@ -254,7 +251,7 @@ impl Default for Segment {
} }
} }
#[derive(Default)] #[derive(Default, Clone, Copy)]
pub struct DispalyState([Segment; DISPLAY_SEGMENTS]); pub struct DispalyState([Segment; DISPLAY_SEGMENTS]);
impl Index<usize> for DispalyState { impl Index<usize> for DispalyState {
@@ -272,6 +269,10 @@ impl IndexMut<usize> for DispalyState {
} }
impl DispalyState { impl DispalyState {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter_segments(&self) -> impl Iterator<Item = &Segment> { pub fn iter_segments(&self) -> impl Iterator<Item = &Segment> {
self.0.iter() self.0.iter()
} }
@@ -293,12 +294,16 @@ impl DispalyState {
self[1].off(); self[1].off();
self[2].off(); self[2].off();
self[3].off().minus(); self[3].off().minus();
self[4].off().e(); self[4].off().sym_e();
self[5].off().minus(); self[5].off().minus();
self[6].off(); self[6].off();
self[7].off(); self[7].off();
self[8].off(); self[8].off();
} }
pub fn slice(&mut self, start: usize, len: usize) -> &mut [Segment] {
&mut self.0.as_mut_slice()[start..(start + len)]
}
} }
// Show data on segment display // Show data on segment display

52
src/io.rs Normal file
View File

@@ -0,0 +1,52 @@
use core::ops::{Index, IndexMut};
use arduino_hal::{
hal::port::{PB3, PB4, PC1, PC2, PC3, PD2, PD3, PD4, PD7},
port::{mode::Output, Pin},
};
pub struct IOPins([Pin<Output>; 9]);
impl IOPins {
pub fn new(
kd_d1_1c: Pin<Output, PD2>,
kd_d2_5div: Pin<Output, PD3>,
kd_d3_6mul: Pin<Output, PD4>,
kd_d4_7up: Pin<Output, PC3>,
kd_d5_8e: Pin<Output, PC2>,
kd_d6_90: Pin<Output, PD7>,
kd_d7_2down: Pin<Output, PC1>,
kd_d8_3plus: Pin<Output, PB3>,
kd_d9_4min: Pin<Output, PB4>,
) -> IOPins {
IOPins([
kd_d1_1c.downgrade(),
kd_d2_5div.downgrade(),
kd_d3_6mul.downgrade(),
kd_d4_7up.downgrade(),
kd_d5_8e.downgrade(),
kd_d6_90.downgrade(),
kd_d7_2down.downgrade(),
kd_d8_3plus.downgrade(),
kd_d9_4min.downgrade(),
])
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl Index<usize> for IOPins {
type Output = Pin<Output>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl IndexMut<usize> for IOPins {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}

View File

@@ -1,4 +1,7 @@
use arduino_hal::{adc::AdcChannel, hal::Atmega, pac::ADC, Adc}; use arduino_hal::{
adc::channel::{ADC6, ADC7},
Adc,
};
use ufmt::derive::uDebug; use ufmt::derive::uDebug;
use crate::{DEBOUNCE_DEPTH, KEYBOARD_ADC_THRESHOLD}; use crate::{DEBOUNCE_DEPTH, KEYBOARD_ADC_THRESHOLD};
@@ -49,13 +52,13 @@ pub struct KeyReadout {
ko: bool, ko: bool,
} }
pub struct Keyboard<KN, KO> { pub struct Keyboard {
kn: KN, kn: ADC7,
ko: KO, ko: ADC6,
} }
impl<KN: AdcChannel<Atmega, ADC>, KO: AdcChannel<Atmega, ADC>> Keyboard<KN, KO> { impl Keyboard {
pub fn new(kn: KN, ko: KO) -> Keyboard<KN, KO> { pub fn new(kn: ADC7, ko: ADC6) -> Keyboard {
Keyboard { kn, ko } Keyboard { kn, ko }
} }
@@ -78,26 +81,24 @@ impl<KN: AdcChannel<Atmega, ADC>, KO: AdcChannel<Atmega, ADC>> Keyboard<KN, KO>
pub struct Debounce { pub struct Debounce {
record: [Option<KeyReadout>; DEBOUNCE_DEPTH], record: [Option<KeyReadout>; DEBOUNCE_DEPTH],
pos: usize, pos: usize,
last: Option<KeyReadout>,
} }
impl Debounce { impl Debounce {
pub fn input(&mut self, key: Option<KeyReadout>) -> Option<KeyPress> { pub fn input(&mut self, key: Option<KeyReadout>) -> Option<KeyPress> {
// React to fresh key press immediately if it was not pressed for depth of record
let new_key = key.and_then(|key| {
self.record
.iter()
.all(|hist| hist != &Some(key))
.then_some(key)
});
self.record[self.pos] = key; self.record[self.pos] = key;
self.pos += 1; self.pos += 1;
if self.pos >= self.record.len() { if self.pos >= self.record.len() {
self.pos = 0; self.pos = 0;
} }
if self.record.iter().all(|hist| hist == &key) && self.last != key {
self.last = key; new_key.and_then(|key| KeyPress::map(key.display_no, key.kn, key.ko))
key.and_then(|key_readout| {
KeyPress::map(key_readout.display_no, key_readout.kn, key_readout.ko)
})
} else if self.record.iter().all(|hist| hist == &None) {
self.last = None;
None
} else {
None
}
} }
} }

View File

@@ -1,11 +1,19 @@
#![feature(abi_avr_interrupt)]
#![no_std] #![no_std]
#![no_main] #![no_main]
mod display; mod display;
mod io;
mod keyboard; mod keyboard;
mod panic; mod panic;
mod timer;
use calc_math::{ use avr_device::{
asm::sleep,
interrupt::{CriticalSection, Mutex},
};
use core::cell::{Cell, RefCell};
use core_decimal_calc::{
calc::{StackCalc, StackCalcError}, calc::{StackCalc, StackCalcError},
Decimal, Decimal,
}; };
@@ -14,12 +22,12 @@ use keyboard::{Debounce, KeyPress, KeyReadout, Keyboard};
use arduino_hal::{ use arduino_hal::{
adc::channel::{ADC6, ADC7}, adc::channel::{ADC6, ADC7},
hal::port::{PB3, PB4, PC1, PC2, PC3, PD2, PD3, PD4, PD7},
port::{mode::Output, Pin},
Adc, Adc,
}; };
use ufmt::derive::uDebug; use ufmt::derive::uDebug;
use crate::{display::Brightness, io::IOPins, timer::SegmentTimer};
// NOTE: 115200 @ 16MHz is 3.5% off, try 9600 or 1M if it causes issues (https://wormfood.net/avrbaudcalc.php) // NOTE: 115200 @ 16MHz is 3.5% off, try 9600 or 1M if it causes issues (https://wormfood.net/avrbaudcalc.php)
const SERIAL_BAUD: u32 = 115200; const SERIAL_BAUD: u32 = 115200;
// Analog threshold level of key press on ADC read from KN or KO // Analog threshold level of key press on ADC read from KN or KO
@@ -36,58 +44,176 @@ pub const DISPLAY_SEGMENT_SIG_MINUS: usize = 0;
pub const DISPLAY_SEGMENTS_EXP: usize = 2; pub const DISPLAY_SEGMENTS_EXP: usize = 2;
pub const DISPLAY_SEGMENT_EXP_MINUS: usize = 6; pub const DISPLAY_SEGMENT_EXP_MINUS: usize = 6;
type Calc = StackCalc<f32, 3, 5, u8>; // Timing
// Note: it takes ~224 μs to read keyboard after segment off
pub const IO_SEGMENT_RATE_US: u32 = 1000; // Time in μs between segment updates
pub const IO_SEGMENT_ON_MIN_US: u32 = 80; // How long in μs to hold segment LEDs on (dark)
pub const IO_SEGMENT_ON_MAX_US: u32 = 700; // How long in μs to hold segment LEDs on (bright)
struct IOSelect<'p> { // Dimming
display_no: usize, pub const DISPLAY_FPS: u16 = (1_000_000 / (IO_SEGMENT_RATE_US * DISPLAY_SEGMENTS as u32)) as u16;
pin: &'p mut Pin<Output>, pub const DISPLAY_DIMM_FRAMES: u16 = DISPLAY_FPS * 10; // How many frames of inactivity before dimming
pub const DISPLAY_DIMM_SPEED: u8 = 4; // Dimm by amount every frame when sleeping
pub const DISPLAY_UNDIMM_SPEED: u8 = 16; // Brighten by amount every frame when not sleeping
// Calculator setup
pub const STACK_DEPTH: usize = 7;
type Calc = StackCalc<f32, STACK_DEPTH, 5, u8>;
// Interrupt driven I/O
//
// * Run timer at 100Hz (10ms) to step through segments.
// * On each segment interrupt configure segments and enable output (LEDs on).
// * Set another timer to run for 1ms.
// * On another timer interrupt expire disable display (LEDs off) and handle keyboard input.
// Values shared between main and interrupt handlers
type Global<T> = Mutex<Cell<T>>;
type RefGlobal<T> = Mutex<RefCell<Option<T>>>;
static IO_LOOP: RefGlobal<IOLoop> = Mutex::new(RefCell::new(None));
static KEY_PRESS: Global<Option<KeyPress>> = Mutex::new(Cell::new(None));
static ADC: RefGlobal<Adc> = Mutex::new(RefCell::new(None));
static SEGMENT_TIMER: RefGlobal<SegmentTimer> = Mutex::new(RefCell::new(None));
fn access_global<'cs, 'v: 'cs, T, O>(
v: &'v Mutex<RefCell<Option<T>>>,
cs: CriticalSection<'cs>,
f: impl for<'t> FnOnce(&'t mut T) -> O,
) -> O {
let mut v = v.borrow(cs).borrow_mut();
f(v.as_mut().unwrap())
} }
impl IOSelect<'_> { #[avr_device::interrupt(atmega328p)]
fn display_no(&self) -> usize { unsafe fn TIMER0_COMPA() {
self.display_no avr_device::interrupt::free(|cs| {
} access_global(&IO_LOOP, cs, |io_loop| {
access_global(&SEGMENT_TIMER, cs, |st| {
fn set_on(&mut self) { let brightness = io_loop.display_on();
self.pin.set_high() st.segment_on_time(brightness.scale_brightness());
} });
fn set_off(&mut self) { });
self.pin.set_low() });
}
} }
struct IOPins([Pin<Output>; 9]); #[avr_device::interrupt(atmega328p)]
unsafe fn TIMER0_COMPB() {
avr_device::interrupt::free(|cs| {
access_global(&IO_LOOP, cs, |io_loop| {
io_loop.display_off();
access_global(&ADC, cs, |adc| {
io_loop.read_key(adc);
});
if let Some(key) = io_loop.advance() {
KEY_PRESS.borrow(cs).replace(Some(key));
}
});
});
}
impl IOPins { pub struct IOLoop {
fn new( index: usize,
kd_d1_1c: Pin<Output, PD2>, frame: u16,
kd_d2_5div: Pin<Output, PD3>, io_pins: IOPins,
kd_d3_6mul: Pin<Output, PD4>, segment_pins: SegmentPins,
kd_d4_7up: Pin<Output, PC3>, dispaly: DispalyState,
kd_d5_8e: Pin<Output, PC2>, keyboard: Keyboard,
kd_d6_90: Pin<Output, PD7>, readount: Option<KeyReadout>,
kd_d7_2down: Pin<Output, PC1>, debounce: Debounce,
kd_d8_3plus: Pin<Output, PB3>, sleep_timer: u16,
kd_d9_4min: Pin<Output, PB4>, dimming: u8,
) -> IOPins { }
IOPins([
kd_d1_1c.downgrade(), impl IOLoop {
kd_d2_5div.downgrade(), pub fn new(io_pins: IOPins, segment_pins: SegmentPins, keyboard: Keyboard) -> IOLoop {
kd_d3_6mul.downgrade(), IOLoop {
kd_d4_7up.downgrade(), index: 0,
kd_d5_8e.downgrade(), frame: 0,
kd_d6_90.downgrade(), io_pins,
kd_d7_2down.downgrade(), segment_pins,
kd_d8_3plus.downgrade(), dispaly: Default::default(),
kd_d9_4min.downgrade(), keyboard,
]) readount: None,
debounce: Default::default(),
sleep_timer: 0,
dimming: u8::MAX,
}
} }
fn iter_mut(&mut self) -> impl Iterator<Item = IOSelect> { pub fn update_display(&mut self, display: &DispalyState) {
self.0 self.dispaly = *display;
.iter_mut() }
.enumerate()
.map(|(display_no, pin)| IOSelect { display_no, pin }) fn select_on(&mut self) {
self.io_pins[self.index].set_high();
}
fn select_off(&mut self) {
self.io_pins[self.index].set_low();
}
pub fn advance(&mut self) -> Option<KeyPress> {
self.index += 1;
if self.index == self.io_pins.len() || self.index == self.dispaly.len() {
// Frame done
self.frame = self.frame.wrapping_add(1);
// Start from first segment
self.index = 0;
// Full keyboard scan complete, debounce
let key = self.debounce.input(self.readount.take());
// Reset or advance sleep timer
if key.is_some() {
self.sleep_timer = 0;
} else {
self.sleep_timer = self.sleep_timer.saturating_add(1);
}
if self.is_sleep() {
self.dimming = self.dimming.saturating_add(DISPLAY_DIMM_SPEED);
} else {
self.dimming = self.dimming.saturating_sub(DISPLAY_UNDIMM_SPEED);
}
key
} else {
None
}
}
pub fn is_sleep(&self) -> bool {
self.sleep_timer >= DISPLAY_DIMM_FRAMES
}
pub fn dimming(&self) -> u8 {
self.dimming
}
pub fn display_on(&mut self) -> Brightness {
self.select_off();
let segment = self.dispaly[self.index];
let brighness = segment.apply(&mut self.segment_pins);
self.select_on();
brighness.dimm(self.dimming)
}
pub fn display_off(&mut self) {
self.select_off();
self.segment_pins.set_off();
}
pub fn read_key(&mut self, adc: &mut Adc) {
self.select_on();
if let key_readout @ Some(_) = self.keyboard.read(adc, self.index) {
self.readount = key_readout;
}
self.select_off();
}
pub fn frame(&self) -> (u16, usize) {
(self.frame, self.index)
} }
} }
@@ -192,22 +318,47 @@ impl Show for NumberInput {
fn show(&self, display: &mut DispalyState) { fn show(&self, display: &mut DispalyState) {
display.off(); display.off();
display[1].dp(); display[1].dp();
if !self.enter_exponent && self.significant_pos < DISPLAY_SEGMENTS_SIG {
display[self.significant_pos + 1].prompt(); let (minus, sig) = display.slice(0, 1 + DISPLAY_SEGMENTS_SIG).split_at_mut(1);
} else if self.enter_exponent && self.exponent_pos < DISPLAY_SEGMENTS_EXP {
display[DISPLAY_SEGMENTS_SIG + 2 + self.exponent_pos].prompt();
}
if self.minus { if self.minus {
display[DISPLAY_SEGMENT_SIG_MINUS].minus(); minus[0].minus();
} }
for i in 0..self.significant_pos {
display[DISPLAY_SEGMENT_SIG_MINUS + 1 + i].num(self.significant[i]); for (seg, num) in sig
.iter_mut()
.zip(self.significant.iter())
.take(self.significant_pos)
{
seg.num(*num);
} }
if !self.enter_exponent {
if let Some(seg) = sig.get_mut(self.significant_pos) {
seg.prompt();
}
}
let (minus, exp) = display
.slice(1 + DISPLAY_SEGMENTS_SIG, 1 + DISPLAY_SEGMENTS_EXP)
.split_at_mut(1);
if self.minus_exponent { if self.minus_exponent {
display[DISPLAY_SEGMENT_EXP_MINUS].minus(); minus[0].minus();
}
for (seg, num) in exp
.iter_mut()
.zip(self.exponent.iter())
.take(self.exponent_pos)
{
seg.num(*num);
}
if self.enter_exponent {
if let Some(seg) = exp.get_mut(self.exponent_pos) {
seg.prompt();
} }
for i in 0..self.exponent_pos {
display[DISPLAY_SEGMENT_EXP_MINUS + 1 + i].num(self.exponent[i]);
} }
} }
} }
@@ -378,7 +529,7 @@ fn main() -> ! {
ufmt::uwriteln!(&mut serial, "Hello from Arduino!").ok(); ufmt::uwriteln!(&mut serial, "Hello from Arduino!").ok();
let mut io = IOPins::new( let io_pins = IOPins::new(
pins.d2.into_output(), pins.d2.into_output(),
pins.d3.into_output(), pins.d3.into_output(),
pins.d4.into_output(), pins.d4.into_output(),
@@ -389,8 +540,7 @@ fn main() -> ! {
pins.d11.into_output(), pins.d11.into_output(),
pins.d12.into_output(), pins.d12.into_output(),
); );
let segment_pins = SegmentPins::new(
let mut seg = SegmentPins::new(
pins.d5.into_output(), pins.d5.into_output(),
pins.d8.into_output(), pins.d8.into_output(),
pins.d10.into_output(), pins.d10.into_output(),
@@ -400,10 +550,11 @@ fn main() -> ! {
pins.a0.into_output(), pins.a0.into_output(),
pins.a4.into_output(), pins.a4.into_output(),
); );
let mut adc = Adc::new(dp.ADC, Default::default());
let keyboard = Keyboard::new(ADC7, ADC6); let keyboard = Keyboard::new(ADC7, ADC6);
let mut debounce = Debounce::default(); let io_loop = IOLoop::new(io_pins, segment_pins, keyboard);
let adc = Adc::new(dp.ADC, Default::default());
let segment_timer = SegmentTimer::init(dp.TC0, IO_SEGMENT_RATE_US);
let mut number_input = NumberInput::default(); let mut number_input = NumberInput::default();
let mut display = DispalyState::default(); let mut display = DispalyState::default();
@@ -415,24 +566,23 @@ fn main() -> ! {
calculator: CalcluclatorState::EnterSignificant, calculator: CalcluclatorState::EnterSignificant,
}; };
loop { avr_device::interrupt::free(|cs| {
let mut last_key_readout: Option<KeyReadout> = None; IO_LOOP.borrow(cs).replace(Some(io_loop));
for (mut io_select, ss) in io.iter_mut().zip(display.iter_segments()) { ADC.borrow(cs).replace(Some(adc));
ss.apply(&mut seg); SEGMENT_TIMER.borrow(cs).replace(Some(segment_timer));
io_select.set_on(); });
arduino_hal::delay_ms(1); unsafe {
io_select.set_off(); avr_device::interrupt::enable();
seg.set_off();
io_select.set_on();
if let key_readout @ Some(_) = keyboard.read(&mut adc, io_select.display_no()) {
last_key_readout = key_readout;
}
io_select.set_off();
} }
let key = debounce.input(last_key_readout); loop {
let mut key = None;
avr_device::interrupt::free(|cs| {
key = KEY_PRESS.borrow(cs).take();
});
if let TransientState::Done = state.transient { if let TransientState::Done = state.transient {
// No transient state - handle key or wait for key
if let Some(key) = key { if let Some(key) = key {
ufmt::uwriteln!( ufmt::uwriteln!(
&mut serial, &mut serial,
@@ -467,16 +617,36 @@ fn main() -> ! {
for (i, dec) in calc.iter().enumerate() { for (i, dec) in calc.iter().enumerate() {
ufmt::uwriteln!(&mut serial, "[{}] {}", i, dec).ok(); ufmt::uwriteln!(&mut serial, "[{}] {}", i, dec).ok();
} }
} else {
// Wait for key
sleep(); // Enter idle mode; wake up on interrupt
} }
} else { } else {
state.transient.on_frame(key) // In transient state - handle animation
arduino_hal::delay_ms(10);
state.transient.on_frame(key);
} }
match &state.transient { match &state.transient {
TransientState::Done => number_input.show(&mut display), TransientState::Done => {
number_input.show(&mut display);
for seg in display
.slice(0, DISPLAY_SEGMENTS)
.iter_mut()
.rev()
.take(calc.len())
{
seg.dp();
seg.brightness(Brightness::full());
}
}
TransientState::Err { .. } => display.error(), TransientState::Err { .. } => display.error(),
} }
arduino_hal::delay_ms(1); avr_device::interrupt::free(|cs| {
access_global(&IO_LOOP, cs, |io_loop| {
io_loop.update_display(&display);
})
});
} }
} }

54
src/timer.rs Normal file
View File

@@ -0,0 +1,54 @@
use arduino_hal::{clock::Clock, pac::TC0, DefaultClock};
// Prescaler set for the timer (see tccr0b)
const TIMER_PRESCALE: u32 = 64;
// Timer clock tick rate per second
const TIMER_FREQ: u32 = DefaultClock::FREQ / TIMER_PRESCALE;
const fn us_to_ticks(us: u32) -> u32 {
TIMER_FREQ * us / 1_000_000
}
// Timer 0 (8bit)
pub struct SegmentTimer {
timer: TC0,
}
impl SegmentTimer {
// Sets up timer to rise interrupts:
// 1. TIMER0_COMPA - segment_switch_us - time in μs to switch to next segment
// 2. TIMER0_COMPB - set by set_segment_on_time to keep segment LEDs on
pub fn init(tc0: TC0, segment_switch_us: u32) -> SegmentTimer {
// 16_000_000 / 64 * 1000 / 1_000_000 => 250
let ocra = us_to_ticks(segment_switch_us);
// Use CTC mode: reset counter when matches compare value
tc0.tccr0a.write(|w| w.wgm0().ctc());
// Set the compare value for TOP (reset)
tc0.ocr0a.write(|w| {
w.bits(
ocra.try_into()
.expect("timer init segment_switch_us out of rage"),
)
});
// Slow down the timer (CLK / prescale)
tc0.tccr0b.write(|w| w.cs0().prescale_64());
// Raise interrupt on TOP (reset)
// Raise interrupt on B match
tc0.timsk0
.write(|w| w.ocie0a().set_bit().ocie0b().set_bit());
SegmentTimer { timer: tc0 }
}
// Set for how long the segment LEDs should be on in μs
// Controls TIMER0_COMPB interrupt time after TIMER0_COMPA
pub fn segment_on_time(&mut self, segment_on_us: u32) {
let delay: u8 = us_to_ticks(segment_on_us)
.try_into()
.expect("timer init segment_on_us out of rage");
let elapsed = self.timer.tcnt0.read().bits();
// Set the compare value for B match
self.timer.ocr0b.write(|w| w.bits(elapsed + delay));
}
}