control segment brightness; test pattern

This commit is contained in:
2025-07-26 15:14:08 +01:00
parent 6517cfd72b
commit f816a09554
3 changed files with 81 additions and 66 deletions

View File

@@ -82,11 +82,19 @@ impl SegmentPins {
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct Segment(u8); pub struct Brightness(pub u8);
#[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(0xFF))
}
pub fn brightness(&mut self, b: u8) -> &mut Self {
self.1 = Brightness(b);
self
} }
pub fn off(&mut self) -> &mut Self { pub fn off(&mut self) -> &mut Self {
@@ -134,7 +142,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 +168,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

@@ -28,7 +28,7 @@ use arduino_hal::{
}; };
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 +49,12 @@ 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)
const fn scale_brightness(b: u8) -> u32 {
IO_SEGMENT_ON_MIN_US + (IO_SEGMENT_ON_MAX_US - IO_SEGMENT_ON_MIN_US) * b as u32 / 0xFF
}
// Calculator setup // Calculator setup
pub const STACK_DEPTH: usize = 7; pub const STACK_DEPTH: usize = 7;
@@ -66,6 +71,7 @@ static LED: Mutex<RefCell<Option<Pin<Output, PB5>>>> = Mutex::new(RefCell::new(N
static IO_LOOP: Mutex<RefCell<Option<IOLoop>>> = Mutex::new(RefCell::new(None)); static IO_LOOP: Mutex<RefCell<Option<IOLoop>>> = Mutex::new(RefCell::new(None));
static KEY_PRESS: Mutex<RefCell<Option<KeyPress>>> = Mutex::new(RefCell::new(None)); static KEY_PRESS: Mutex<RefCell<Option<KeyPress>>> = Mutex::new(RefCell::new(None));
static ADC: Mutex<RefCell<Option<Adc>>> = Mutex::new(RefCell::new(None)); static ADC: Mutex<RefCell<Option<Adc>>> = Mutex::new(RefCell::new(None));
static SEGMENT_TIMER: Mutex<RefCell<Option<SegmentTimer>>> = Mutex::new(RefCell::new(None));
fn try_access<'cs, 'v: 'cs, T, O>( fn try_access<'cs, 'v: 'cs, T, O>(
v: &'v Mutex<RefCell<Option<T>>>, v: &'v Mutex<RefCell<Option<T>>>,
@@ -83,17 +89,20 @@ fn try_access<'cs, 'v: 'cs, T, O>(
#[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)"); // try_access(&LED, cs, |led| led.set_high()).expect("LED not available (COMPA)");
try_access(&IO_LOOP, cs, |io_loop| { let brightness = try_access(&IO_LOOP, cs, |io_loop| io_loop.display_on());
io_loop.display_on(); if let Some(brightness) = brightness {
}) try_access(&SEGMENT_TIMER, cs, |st| {
st.segment_on_time(scale_brightness(brightness.0))
});
}
}); });
} }
#[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)"); // try_access(&LED, cs, |led| led.set_low()).expect("LED not available (COMPB)");
try_access(&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| { try_access(&ADC, cs, |adc| {
@@ -156,11 +165,12 @@ impl IOLoop {
} }
} }
pub fn display_on(&mut self) { 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
} }
pub fn display_off(&mut self) { pub fn display_off(&mut self) {
@@ -514,6 +524,7 @@ fn main() -> ! {
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 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();
@@ -530,12 +541,8 @@ fn main() -> ! {
LED.borrow(cs).replace(Some(led)); 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();
} }
@@ -603,6 +610,9 @@ fn main() -> ! {
{ {
seg.dp(); seg.dp();
} }
for (no, seg) in display.slice(0, DISPLAY_SEGMENTS).iter_mut().enumerate() {
seg.brightness((no * 0xFF / DISPLAY_SEGMENTS) as u8);
}
} }
TransientState::Err { .. } => display.error(), TransientState::Err { .. } => display.error(),
} }

View File

@@ -13,55 +13,51 @@ const fn us_to_ticks(us: u32) -> u32 {
TIMER_FREQ * us / 1_000_000 TIMER_FREQ * us / 1_000_000
} }
// Sets up timer to rise two interrupts: // Timer 0 (8bit)
// 1. TIMER0_COMPA - segment_switch_us - time in μs to switch to next segment pub struct SegmentTimer(TC0);
// 2. TIMER0_COMPB - segment_on_us - time in μs to keep segment LEDs on
pub fn segment_timer_init(tc0: TC0, segment_switch_us: u32, segment_on_us: u32) {
// 16_000_000 / 64 * 1000 / 1_000_000 => 250
let ocra = us_to_ticks(segment_switch_us);
let ocrb = us_to_ticks(segment_on_us);
assert!(
ocra > ocrb + BUFFER_TICKS,
"segment_on_us cannot be longer than segment_switch_us - buffer"
);
// Use CTC mode: reset counter when matches compare value impl SegmentTimer {
tc0.tccr0a.write(|w| w.wgm0().ctc()); // Sets up timer to rise interrupts:
// Set the compare value for TOP (reset) // 1. TIMER0_COMPA - segment_switch_us - time in μs to switch to next segment
tc0.ocr0a.write(|w| { // 2. TIMER0_COMPB - set by set_segment_on_time to keep segment LEDs on
w.bits( pub fn init(tc0: TC0, segment_switch_us: u32) -> SegmentTimer {
ocra.try_into() // 16_000_000 / 64 * 1000 / 1_000_000 => 250
.expect("timer init segment_switch_us out of rage"), let ocra = us_to_ticks(segment_switch_us);
)
});
// Set the compare value for B match
tc0.ocr0b.write(|w| {
w.bits(
ocrb.try_into()
.expect("timer init segment_on_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());
}
// Set for how long the segment LEDs should be on in μs // Use CTC mode: reset counter when matches compare value
pub fn set_segment_on_time(tc0: TC0, segment_on_us: u32) { tc0.tccr0a.write(|w| w.wgm0().ctc());
let ocra = tc0.ocr0a.read().bits(); // Set the compare value for TOP (reset)
let ocrb = us_to_ticks(segment_on_us); tc0.ocr0a.write(|w| {
assert!( w.bits(
ocra as u32 > ocrb + BUFFER_TICKS, ocra.try_into()
"segment_on_us cannot be longer than segment_switch_us - buffer" .expect("timer init segment_switch_us out of rage"),
); )
// Set the compare value for B match });
tc0.ocr0b.write(|w| { // Slow down the timer (CLK / prescale)
w.bits( tc0.tccr0b.write(|w| w.cs0().prescale_64());
ocrb.try_into() // Raise interrupt on TOP (reset)
.expect("timer init segment_on_us out of rage"), // Raise interrupt on B match
) tc0.timsk0
}); .write(|w| w.ocie0a().set_bit().ocie0b().set_bit());
SegmentTimer(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 ocra = self.0.ocr0a.read().bits();
let ocrb = us_to_ticks(segment_on_us);
assert!(
ocra as u32 > ocrb + BUFFER_TICKS,
"segment_on_us cannot be longer than segment_switch_us - buffer"
);
// Set the compare value for B match
self.0.ocr0b.write(|w| {
w.bits(
ocrb.try_into()
.expect("timer init segment_on_us out of rage"),
)
});
}
} }