Compare commits

...

10 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
7 changed files with 187 additions and 113 deletions

29
Cargo.lock generated
View File

@@ -27,9 +27,9 @@ 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"
@@ -37,7 +37,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"arduino-hal", "arduino-hal",
"avr-device", "avr-device",
"calc-math", "core-decimal-calc",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"nb 1.1.0", "nb 1.1.0",
"ufmt", "ufmt",
@@ -89,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"
@@ -172,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,7 @@ 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" avr-device = "0.7.0"
[dependencies.arduino-hal] [dependencies.arduino-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,11 +82,43 @@ impl SegmentPins {
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct Segment(u8); pub struct Brightness(u8);
impl Brightness {
pub const fn new(b: u8) -> Brightness {
Brightness(b)
}
pub const fn full() -> Brightness {
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(0) Segment(0, Brightness::full())
}
pub fn brightness(&mut self, b: Brightness) -> &mut Self {
self.1 = b;
self
} }
pub fn off(&mut self) -> &mut Self { pub fn off(&mut self) -> &mut Self {
@@ -134,7 +166,7 @@ impl Segment {
self self
} }
pub fn apply(&self, seg: &mut SegmentPins) { pub fn apply(&self, seg: &mut SegmentPins) -> Brightness {
seg.set_off(); seg.set_off();
if self.0 & 0b1000_0000 != 0 { if self.0 & 0b1000_0000 != 0 {
seg.set_a(); seg.set_a();
@@ -160,6 +192,7 @@ impl Segment {
if self.0 & 0b0000_0001 != 0 { if self.0 & 0b0000_0001 != 0 {
seg.set_dp(); seg.set_dp();
} }
self.1
} }
pub fn num(&mut self, no: u8) -> &mut Self { pub fn num(&mut self, no: u8) -> &mut Self {

View File

@@ -12,23 +12,21 @@ use avr_device::{
asm::sleep, asm::sleep,
interrupt::{CriticalSection, Mutex}, interrupt::{CriticalSection, Mutex},
}; };
use calc_math::{ use core::cell::{Cell, RefCell};
use core_decimal_calc::{
calc::{StackCalc, StackCalcError}, calc::{StackCalc, StackCalcError},
Decimal, Decimal,
}; };
use core::cell::RefCell;
use display::{DispalyState, SegmentPins, Show}; use display::{DispalyState, SegmentPins, Show};
use keyboard::{Debounce, KeyPress, KeyReadout, Keyboard}; use keyboard::{Debounce, KeyPress, KeyReadout, Keyboard};
use arduino_hal::{ use arduino_hal::{
adc::channel::{ADC6, ADC7}, adc::channel::{ADC6, ADC7},
hal::port::PB5,
port::{mode::Output, Pin},
Adc, Adc,
}; };
use ufmt::derive::uDebug; use ufmt::derive::uDebug;
use crate::io::IOPins; 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;
@@ -49,7 +47,14 @@ pub const DISPLAY_SEGMENT_EXP_MINUS: usize = 6;
// Timing // Timing
// Note: it takes ~224 μs to read keyboard after segment off // 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_RATE_US: u32 = 1000; // Time in μs between segment updates
pub const IO_SEGMENT_ON_US: u32 = 200; // How long in μs to hold segment LEDs on 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)
// Dimming
pub const DISPLAY_FPS: u16 = (1_000_000 / (IO_SEGMENT_RATE_US * DISPLAY_SEGMENTS as u32)) as u16;
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 // Calculator setup
pub const STACK_DEPTH: usize = 7; pub const STACK_DEPTH: usize = 7;
@@ -62,47 +67,46 @@ type Calc = StackCalc<f32, STACK_DEPTH, 5, u8>;
// * Set another timer to run for 1ms. // * Set another timer to run for 1ms.
// * On another timer interrupt expire disable display (LEDs off) and handle keyboard input. // * On another timer interrupt expire disable display (LEDs off) and handle keyboard input.
static LED: Mutex<RefCell<Option<Pin<Output, PB5>>>> = Mutex::new(RefCell::new(None)); // Values shared between main and interrupt handlers
static IO_LOOP: Mutex<RefCell<Option<IOLoop>>> = Mutex::new(RefCell::new(None)); type Global<T> = Mutex<Cell<T>>;
static KEY_PRESS: Mutex<RefCell<Option<KeyPress>>> = Mutex::new(RefCell::new(None)); type RefGlobal<T> = Mutex<RefCell<Option<T>>>;
static ADC: Mutex<RefCell<Option<Adc>>> = Mutex::new(RefCell::new(None));
fn try_access<'cs, 'v: 'cs, T, O>( 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>>>, v: &'v Mutex<RefCell<Option<T>>>,
cs: CriticalSection<'cs>, cs: CriticalSection<'cs>,
f: impl for<'t> FnOnce(&'t mut T) -> O, f: impl for<'t> FnOnce(&'t mut T) -> O,
) -> Option<O> { ) -> O {
if let Some(mut v) = v.borrow(cs).try_borrow_mut().ok() { let mut v = v.borrow(cs).borrow_mut();
if let Some(v) = v.as_mut() { f(v.as_mut().unwrap())
return Some(f(v));
}
}
None
} }
#[avr_device::interrupt(atmega328p)] #[avr_device::interrupt(atmega328p)]
unsafe fn TIMER0_COMPA() { unsafe fn TIMER0_COMPA() {
avr_device::interrupt::free(|cs| { avr_device::interrupt::free(|cs| {
try_access(&LED, cs, |led| led.set_high()).expect("LED not available (COMPA)"); access_global(&IO_LOOP, cs, |io_loop| {
try_access(&IO_LOOP, cs, |io_loop| { access_global(&SEGMENT_TIMER, cs, |st| {
io_loop.display_on(); let brightness = io_loop.display_on();
}) st.segment_on_time(brightness.scale_brightness());
});
});
}); });
} }
#[avr_device::interrupt(atmega328p)] #[avr_device::interrupt(atmega328p)]
unsafe fn TIMER0_COMPB() { unsafe fn TIMER0_COMPB() {
avr_device::interrupt::free(|cs| { avr_device::interrupt::free(|cs| {
try_access(&LED, cs, |led| led.set_low()).expect("LED not available (COMPB)"); access_global(&IO_LOOP, cs, |io_loop| {
try_access(&IO_LOOP, cs, |io_loop| {
io_loop.display_off(); io_loop.display_off();
try_access(&ADC, cs, |adc| { access_global(&ADC, cs, |adc| {
io_loop.read_key(adc); io_loop.read_key(adc);
}); });
if let Some(key) = io_loop.advance() { if let Some(key) = io_loop.advance() {
if let Some(mut key_press) = KEY_PRESS.borrow(cs).try_borrow_mut().ok() { KEY_PRESS.borrow(cs).replace(Some(key));
key_press.replace(key);
}
} }
}); });
}); });
@@ -110,24 +114,30 @@ unsafe fn TIMER0_COMPB() {
pub struct IOLoop { pub struct IOLoop {
index: usize, index: usize,
frame: u16,
io_pins: IOPins, io_pins: IOPins,
segment_pins: SegmentPins, segment_pins: SegmentPins,
dispaly: DispalyState, dispaly: DispalyState,
keyboard: Keyboard, keyboard: Keyboard,
readount: Option<KeyReadout>, readount: Option<KeyReadout>,
debounce: Debounce, debounce: Debounce,
sleep_timer: u16,
dimming: u8,
} }
impl IOLoop { impl IOLoop {
pub fn new(io_pins: IOPins, segment_pins: SegmentPins, keyboard: Keyboard) -> IOLoop { pub fn new(io_pins: IOPins, segment_pins: SegmentPins, keyboard: Keyboard) -> IOLoop {
IOLoop { IOLoop {
index: 0, index: 0,
frame: 0,
io_pins, io_pins,
segment_pins, segment_pins,
dispaly: Default::default(), dispaly: Default::default(),
keyboard, keyboard,
readount: None, readount: None,
debounce: Default::default(), debounce: Default::default(),
sleep_timer: 0,
dimming: u8::MAX,
} }
} }
@@ -146,21 +156,47 @@ impl IOLoop {
pub fn advance(&mut self) -> Option<KeyPress> { pub fn advance(&mut self) -> Option<KeyPress> {
self.index += 1; self.index += 1;
if self.index == self.io_pins.len() || self.index == self.dispaly.len() { 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 // Start from first segment
self.index = 0; self.index = 0;
// Full keyboard scan complete, debounce and return result // Full keyboard scan complete, debounce
self.debounce.input(self.readount.take()) 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 { } else {
None None
} }
} }
pub fn display_on(&mut self) { 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(); self.select_off();
let segment = self.dispaly[self.index]; let segment = self.dispaly[self.index];
segment.apply(&mut self.segment_pins); let brighness = segment.apply(&mut self.segment_pins);
self.select_on(); self.select_on();
brighness.dimm(self.dimming)
} }
pub fn display_off(&mut self) { pub fn display_off(&mut self) {
@@ -175,6 +211,10 @@ impl IOLoop {
} }
self.select_off(); self.select_off();
} }
pub fn frame(&self) -> (u16, usize) {
(self.frame, self.index)
}
} }
#[derive(uDebug)] #[derive(uDebug)]
@@ -512,8 +552,8 @@ fn main() -> ! {
); );
let keyboard = Keyboard::new(ADC7, ADC6); let keyboard = Keyboard::new(ADC7, ADC6);
let io_loop = IOLoop::new(io_pins, segment_pins, keyboard); let io_loop = IOLoop::new(io_pins, segment_pins, keyboard);
let led = pins.d13.into_output();
let adc = Adc::new(dp.ADC, Default::default()); 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();
@@ -527,15 +567,10 @@ fn main() -> ! {
}; };
avr_device::interrupt::free(|cs| { avr_device::interrupt::free(|cs| {
LED.borrow(cs).replace(Some(led));
IO_LOOP.borrow(cs).replace(Some(io_loop)); IO_LOOP.borrow(cs).replace(Some(io_loop));
ADC.borrow(cs).replace(Some(adc)); ADC.borrow(cs).replace(Some(adc));
SEGMENT_TIMER.borrow(cs).replace(Some(segment_timer));
}); });
timer::segment_timer_init(
dp.TC0, // Timer0 (8bit)
IO_SEGMENT_RATE_US,
IO_SEGMENT_ON_US,
);
unsafe { unsafe {
avr_device::interrupt::enable(); avr_device::interrupt::enable();
} }
@@ -543,7 +578,7 @@ fn main() -> ! {
loop { loop {
let mut key = None; let mut key = None;
avr_device::interrupt::free(|cs| { avr_device::interrupt::free(|cs| {
key = KEY_PRESS.borrow(cs).borrow_mut().take(); key = KEY_PRESS.borrow(cs).take();
}); });
if let TransientState::Done = state.transient { if let TransientState::Done = state.transient {
@@ -602,15 +637,16 @@ fn main() -> ! {
.take(calc.len()) .take(calc.len())
{ {
seg.dp(); seg.dp();
seg.brightness(Brightness::full());
} }
} }
TransientState::Err { .. } => display.error(), TransientState::Err { .. } => display.error(),
} }
avr_device::interrupt::free(|cs| { avr_device::interrupt::free(|cs| {
try_access(&IO_LOOP, cs, |io_loop| { access_global(&IO_LOOP, cs, |io_loop| {
io_loop.update_display(&display); io_loop.update_display(&display);
}); })
}); });
} }
} }

View File

@@ -1,26 +1,54 @@
use arduino_hal::{clock::Clock, pac::TC0, DefaultClock}; use arduino_hal::{clock::Clock, pac::TC0, DefaultClock};
// Sets up timer to rise two interrupts: // Prescaler set for the timer (see tccr0b)
// 1. TIMER0_COMPA - every segment_rate_us μs const TIMER_PRESCALE: u32 = 64;
// 2. TIMER0_COMPB - segment_on_us μs after TIMER0_COMPA // Timer clock tick rate per second
pub fn segment_timer_init(tc0: TC0, segment_rate_us: u32, segment_on_us: u32) { 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 // 16_000_000 / 64 * 1000 / 1_000_000 => 250
let ocra = DefaultClock::FREQ / 64 * segment_rate_us / 1_000_000; let ocra = us_to_ticks(segment_switch_us);
let ocrb = DefaultClock::FREQ / 64 * segment_on_us / 1_000_000;
assert!(ocra > ocrb);
// Use CTC mode: reset counter when matches compare value // Use CTC mode: reset counter when matches compare value
tc0.tccr0a.write(|w| w.wgm0().ctc()); tc0.tccr0a.write(|w| w.wgm0().ctc());
// Set the compare value for TOP (reset) // Set the compare value for TOP (reset)
tc0.ocr0a tc0.ocr0a.write(|w| {
.write(|w| w.bits(ocra.try_into().expect("timer init seg_freq out of rage"))); w.bits(
// Set the compare value for B match ocra.try_into()
tc0.ocr0b .expect("timer init segment_switch_us out of rage"),
.write(|w| w.bits(ocrb.try_into().expect("timer init on_div out of rage"))); )
});
// Slow down the timer (CLK / prescale) // Slow down the timer (CLK / prescale)
tc0.tccr0b.write(|w| w.cs0().prescale_64()); tc0.tccr0b.write(|w| w.cs0().prescale_64());
// Raise interrupt on TOP (reset) // Raise interrupt on TOP (reset)
// Raise interrupt on B match // Raise interrupt on B match
tc0.timsk0 tc0.timsk0
.write(|w| w.ocie0a().set_bit().ocie0b().set_bit()); .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));
}
} }