Compare commits
10 Commits
d6a4b0fe22
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
cac25d6006
|
|||
|
97079c7b10
|
|||
|
d66914f74f
|
|||
|
42c18a6d5a
|
|||
|
e98a24d32d
|
|||
|
556ac72d10
|
|||
|
f816a09554
|
|||
|
6517cfd72b
|
|||
|
9bec55bcc7
|
|||
|
9b7df6b680
|
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
21
LICENSE-MIT
21
LICENSE-MIT
@@ -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.
|
|
||||||
15
README.md
15
README.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
120
src/main.rs
120
src/main.rs
@@ -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);
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/timer.rs
72
src/timer.rs
@@ -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;
|
||||||
// 16_000_000 / 64 * 1000 / 1_000_000 => 250
|
|
||||||
let ocra = DefaultClock::FREQ / 64 * segment_rate_us / 1_000_000;
|
|
||||||
let ocrb = DefaultClock::FREQ / 64 * segment_on_us / 1_000_000;
|
|
||||||
assert!(ocra > ocrb);
|
|
||||||
|
|
||||||
// Use CTC mode: reset counter when matches compare value
|
const fn us_to_ticks(us: u32) -> u32 {
|
||||||
tc0.tccr0a.write(|w| w.wgm0().ctc());
|
TIMER_FREQ * us / 1_000_000
|
||||||
// Set the compare value for TOP (reset)
|
}
|
||||||
tc0.ocr0a
|
|
||||||
.write(|w| w.bits(ocra.try_into().expect("timer init seg_freq out of rage")));
|
// Timer 0 (8bit)
|
||||||
// Set the compare value for B match
|
pub struct SegmentTimer {
|
||||||
tc0.ocr0b
|
timer: TC0,
|
||||||
.write(|w| w.bits(ocrb.try_into().expect("timer init on_div out of rage")));
|
}
|
||||||
// Slow down the timer (CLK / prescale)
|
|
||||||
tc0.tccr0b.write(|w| w.cs0().prescale_64());
|
impl SegmentTimer {
|
||||||
// Raise interrupt on TOP (reset)
|
// Sets up timer to rise interrupts:
|
||||||
// Raise interrupt on B match
|
// 1. TIMER0_COMPA - segment_switch_us - time in μs to switch to next segment
|
||||||
tc0.timsk0
|
// 2. TIMER0_COMPB - set by set_segment_on_time to keep segment LEDs on
|
||||||
.write(|w| w.ocie0a().set_bit().ocie0b().set_bit());
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user