| 1 | //! Timer/counter 0 and 1: mode, control bits and progress to overflow. |
| 2 | |
| 3 | use bench51_core::session::Session; |
| 4 | use bench51_core::sfr::{TCON, TH0, TH1, TL0, TL1, TMOD}; |
| 5 | use eframe::egui::{self, Align, Layout, Ui}; |
| 6 | |
| 7 | use super::{sfr_changed, sfr_chip}; |
| 8 | use crate::theme::{WARNING, palette}; |
| 9 | use crate::widgets; |
| 10 | |
| 11 | /// Count so far and the count at which the timer overflows. |
| 12 | pub fn progress(mode: u8, th: u8, tl: u8) -> (u32, u32) { |
| 13 | match mode { |
| 14 | 0 => (((th as u32) << 5) | (tl as u32 & 0x1F), 1 << 13), |
| 15 | 1 => (((th as u32) << 8) | tl as u32, 1 << 16), |
| 16 | 2 => { |
| 17 | let span = 256 - th as u32; |
| 18 | ((tl as u32).saturating_sub(th as u32).min(span), span.max(1)) |
| 19 | } |
| 20 | _ => (tl as u32, 256), |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | fn timer(ui: &mut Ui, s: &mut Session, n: u8) { |
| 25 | let p = palette(ui.ctx()); |
| 26 | let (th, tl) = if n == 0 { (TH0, TL0) } else { (TH1, TL1) }; |
| 27 | let shift = 4 * n; |
| 28 | let ctl = (s.sim.sfr(TMOD) >> shift) & 0x0F; |
| 29 | let mut mode = ctl & 3; |
| 30 | let (tr, tf) = if n == 0 { (0x10, 0x20) } else { (0x40, 0x80) }; |
| 31 | let running = s.sim.sfr(TCON) & tr != 0; |
| 32 | |
| 33 | ui.horizontal(|ui| { |
| 34 | ui.label(p.label(format!("Timer {n}")).color(p.text_primary)); |
| 35 | ui.with_layout(Layout::right_to_left(Align::Center), |ui| { |
| 36 | let desc = match mode { |
| 37 | 0 => "13-bit", |
| 38 | 1 => "16-bit", |
| 39 | 2 => "8-bit auto-reload", |
| 40 | _ => "split 8-bit", |
| 41 | }; |
| 42 | ui.label(p.label(desc).color(p.text_disabled)); |
| 43 | }); |
| 44 | }); |
| 45 | ui.add_space(4.0); |
| 46 | ui.horizontal(|ui| { |
| 47 | let changed = sfr_changed(s, th) || sfr_changed(s, tl); |
| 48 | ui.label( |
| 49 | p.data(format!("{:02X} {:02X}", s.sim.sfr(th), s.sim.sfr(tl))) |
| 50 | .size(18.0) |
| 51 | .color(if changed { WARNING } else { p.text_primary }), |
| 52 | ); |
| 53 | ui.label(p.label(format!("TH{n} TL{n}")).color(p.text_disabled)); |
| 54 | }); |
| 55 | let (count, max) = progress(mode, s.sim.sfr(th), s.sim.sfr(tl)); |
| 56 | let segments = 24; |
| 57 | let filled = (count as u64 * segments as u64 / max as u64) as usize; |
| 58 | let color = if running { |
| 59 | p.text_display |
| 60 | } else { |
| 61 | p.text_disabled |
| 62 | }; |
| 63 | let w = ui.available_width().min(360.0); |
| 64 | widgets::segment_bar(ui, w, 8.0, segments, filled, color, false); |
| 65 | ui.add_space(6.0); |
| 66 | ui.horizontal_wrapped(|ui| { |
| 67 | ui.spacing_mut().item_spacing.x = 4.0; |
| 68 | if widgets::segmented(ui, &mut mode, &[(0, "M0"), (1, "M1"), (2, "M2"), (3, "M3")]) { |
| 69 | let tmod = s.sim.sfr(TMOD) & !(3 << shift) | (mode << shift); |
| 70 | s.sim.set_sfr(TMOD, tmod); |
| 71 | } |
| 72 | ui.add_space(6.0); |
| 73 | sfr_chip(ui, s, "GATE", TMOD, 0x08 << shift); |
| 74 | sfr_chip(ui, s, "C/T", TMOD, 0x04 << shift); |
| 75 | sfr_chip(ui, s, &format!("TR{n}"), TCON, tr); |
| 76 | sfr_chip(ui, s, &format!("TF{n}"), TCON, tf); |
| 77 | }); |
| 78 | } |
| 79 | |
| 80 | pub fn show(ui: &mut Ui, s: &mut Session) { |
| 81 | egui::ScrollArea::vertical() |
| 82 | .auto_shrink([false; 2]) |
| 83 | .show(ui, |ui| { |
| 84 | timer(ui, s, 0); |
| 85 | ui.add_space(24.0); |
| 86 | timer(ui, s, 1); |
| 87 | }); |
| 88 | } |
| 89 | |
| 90 | #[cfg(test)] |
| 91 | mod tests { |
| 92 | use super::progress; |
| 93 | |
| 94 | #[test] |
| 95 | fn overflow_progress_per_mode() { |
| 96 | assert_eq!(progress(1, 0x80, 0x00), (0x8000, 0x10000)); |
| 97 | assert_eq!(progress(0, 0x01, 0xFF), (0x3F, 0x2000)); |
| 98 | assert_eq!(progress(2, 0xFD, 0xFE), (1, 3)); |
| 99 | assert_eq!(progress(2, 0xFF, 0xFF), (0, 1)); |
| 100 | assert_eq!(progress(3, 0x12, 0x40), (0x40, 256)); |
| 101 | } |
| 102 | } |