nixie.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. use stm32l4xx_hal::{
  2. prelude::{
  3. _embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write,
  4. _embedded_hal_blocking_i2c_WriteRead,
  5. },
  6. };
  7. use crate::pca9685;
  8. pub const DS3231_ADDR: u8 = 0x68;
  9. pub const TUSB322_ADDR: u8 = 0x47;
  10. pub const PCA9685_ADDR_1: u8 = 0x41;
  11. pub const PCA9685_ADDR_2: u8 = 0x42;
  12. pub const PCA9685_ADDR_3: u8 = 0x43;
  13. pub const PCA9685_ALL_CALL: u8 = 0x70; // Default enabled
  14. pub const PCA9685_SUB_CALL_1: u8 = 0x71; // Default disabled
  15. pub const PCA9685_SUB_CALL_2: u8 = 0x72; // Default disabled
  16. pub const PCA9685_SUB_CALL_3: u8 = 0x73; // Default disabled
  17. pub const DISPLAY_REFRESH_FPS: u32 = 500;
  18. pub const DIGIT_FADE_DURATION_MS: u32 = 1000;
  19. pub const CYCLE_FADE_DURATION_MS: u32 = 200;
  20. pub const CYCLE_ITERATIONS: usize = 20;
  21. pub const CYCLE_REFRESH_INTERVAL: u32 = 60;
  22. pub const CYCLE_REFRESH_VARIANCE: u32 = 30;
  23. const DOT_MIN_BRIGHTNESS: u32 = 256;
  24. const DOT_MAX_BRIGHTNESS: u32 = 640;
  25. const DOT_FADE_DURATION_US: u32 = 1_000_000;
  26. const DIGIT_MAX_BRIGHTNESS: u32 = 4096;
  27. const DIGIT_MIN_BRIGHTNESS: u32 = 0;
  28. const NUM_TUBES: usize = 4;
  29. const NUM_DIGITS: usize = 10;
  30. const MAP_DOT_ADDR: u8 = PCA9685_ADDR_2;
  31. const MAP_DOT_PIN: u8 = 15;
  32. const MAP_ADDR: usize = 0;
  33. const MAP_PIN: usize = 1;
  34. struct DigitToPin {
  35. address: u8,
  36. pin: usize,
  37. }
  38. struct PwmDriver {
  39. digit: [DigitToPin; 10],
  40. }
  41. struct PwmOutputMap {
  42. driver: [PwmDriver; 4],
  43. dot_address: u8,
  44. dot_pin: usize,
  45. }
  46. static TUBE_MAPPING: PwmOutputMap = {
  47. PwmOutputMap {
  48. driver: [
  49. PwmDriver {
  50. digit: [
  51. DigitToPin { address: PCA9685_ADDR_1, pin: 8, }, // Tube 0 Digit 0
  52. DigitToPin { address: PCA9685_ADDR_1, pin: 9, }, // Tube 0 Digit 1
  53. DigitToPin { address: PCA9685_ADDR_1, pin: 10, }, // Tube 0 Digit 2
  54. DigitToPin { address: PCA9685_ADDR_1, pin: 12, }, // Tube 0 Digit 3
  55. DigitToPin { address: PCA9685_ADDR_1, pin: 15, }, // Tube 0 Digit 4
  56. DigitToPin { address: PCA9685_ADDR_1, pin: 14, }, // Tube 0 Digit 5
  57. DigitToPin { address: PCA9685_ADDR_1, pin: 11, }, // Tube 0 Digit 6
  58. DigitToPin { address: PCA9685_ADDR_1, pin: 0, }, // Tube 0 Digit 7
  59. DigitToPin { address: PCA9685_ADDR_1, pin: 1, }, // Tube 0 Digit 8
  60. DigitToPin { address: PCA9685_ADDR_1, pin: 13, }, // Tube 0 Digit 9
  61. ],
  62. },
  63. PwmDriver {
  64. digit: [
  65. DigitToPin { address: PCA9685_ADDR_1, pin: 5, }, // Tube 1 Digit 0
  66. DigitToPin { address: PCA9685_ADDR_1, pin: 6, }, // Tube 1 Digit 1
  67. DigitToPin { address: PCA9685_ADDR_1, pin: 7, }, // Tube 1 Digit 2
  68. DigitToPin { address: PCA9685_ADDR_1, pin: 2, }, // Tube 1 Digit 3
  69. DigitToPin { address: PCA9685_ADDR_2, pin: 4, }, // Tube 1 Digit 4
  70. DigitToPin { address: PCA9685_ADDR_2, pin: 1, }, // Tube 1 Digit 5
  71. DigitToPin { address: PCA9685_ADDR_1, pin: 4, }, // Tube 1 Digit 6
  72. DigitToPin { address: PCA9685_ADDR_2, pin: 2, }, // Tube 1 Digit 7
  73. DigitToPin { address: PCA9685_ADDR_2, pin: 3, }, // Tube 1 Digit 8
  74. DigitToPin { address: PCA9685_ADDR_1, pin: 3, }, // Tube 1 Digit 9
  75. ],
  76. },
  77. PwmDriver {
  78. digit: [
  79. DigitToPin { address: PCA9685_ADDR_3, pin: 8, }, // Tube 2 Digit 0
  80. DigitToPin { address: PCA9685_ADDR_3, pin: 9, }, // Tube 2 Digit 1
  81. DigitToPin { address: PCA9685_ADDR_3, pin: 10, }, // Tube 2 Digit 2
  82. DigitToPin { address: PCA9685_ADDR_3, pin: 12, }, // Tube 2 Digit 3
  83. DigitToPin { address: PCA9685_ADDR_2, pin: 12, }, // Tube 2 Digit 4
  84. DigitToPin { address: PCA9685_ADDR_2, pin: 13, }, // Tube 2 Digit 5
  85. DigitToPin { address: PCA9685_ADDR_3, pin: 11, }, // Tube 2 Digit 6
  86. DigitToPin { address: PCA9685_ADDR_2, pin: 14, }, // Tube 2 Digit 7
  87. DigitToPin { address: PCA9685_ADDR_2, pin: 11, }, // Tube 2 Digit 8
  88. DigitToPin { address: PCA9685_ADDR_3, pin: 13, }, // Tube 2 Digit 9
  89. ],
  90. },
  91. PwmDriver {
  92. digit: [
  93. DigitToPin { address: PCA9685_ADDR_3, pin: 5, }, // Tube 3 Digit 0
  94. DigitToPin { address: PCA9685_ADDR_3, pin: 6, }, // Tube 3 Digit 1
  95. DigitToPin { address: PCA9685_ADDR_3, pin: 7, }, // Tube 3 Digit 2
  96. DigitToPin { address: PCA9685_ADDR_3, pin: 2, }, // Tube 3 Digit 3
  97. DigitToPin { address: PCA9685_ADDR_3, pin: 14, }, // Tube 3 Digit 4
  98. DigitToPin { address: PCA9685_ADDR_3, pin: 15, }, // Tube 3 Digit 5
  99. DigitToPin { address: PCA9685_ADDR_3, pin: 4, }, // Tube 3 Digit 6
  100. DigitToPin { address: PCA9685_ADDR_3, pin: 1, }, // Tube 3 Digit 7
  101. DigitToPin { address: PCA9685_ADDR_3, pin: 0, }, // Tube 3 Digit 8
  102. DigitToPin { address: PCA9685_ADDR_3, pin: 3, }, // Tube 3 Digit 9
  103. ],
  104. },
  105. ],
  106. dot_address: PCA9685_ADDR_2,
  107. dot_pin: 15,
  108. }
  109. };
  110. #[derive(Debug, PartialEq)]
  111. enum State {
  112. Idle,
  113. Incrementing,
  114. Decrementing,
  115. }
  116. struct Digit {
  117. state: State,
  118. value: u32,
  119. pwm_start: u32,
  120. pwm_end: u32,
  121. fade_duration: Option<u32>,
  122. updated: bool,
  123. }
  124. impl Digit {
  125. const fn default() -> Self {
  126. Self {
  127. state: State::Idle,
  128. value: 0,
  129. pwm_start: 0,
  130. pwm_end: 0,
  131. fade_duration: None,
  132. updated: false,
  133. }
  134. }
  135. }
  136. struct CycleSettings {
  137. last_digit: Option<u32>,
  138. next_digit: u32,
  139. iteration: usize,
  140. last_fade_duration: u32,
  141. }
  142. struct Tube {
  143. digits: [Digit; NUM_DIGITS],
  144. last_digit: Option<u32>,
  145. cycle: Option<CycleSettings>,
  146. }
  147. impl Tube {
  148. const fn default() -> Self {
  149. const DIGIT_INIT: Digit = Digit::default();
  150. Self {
  151. digits: [DIGIT_INIT; 10],
  152. last_digit: None,
  153. cycle: None,
  154. }
  155. }
  156. fn fade_in_out_digit(&mut self, digit: Option<u32>, fade_duration: u32, cycle_cmd: bool) {
  157. // If the tube is in the middle of a cycle sequence and a call comes
  158. // in to update the tube digit (for time), override the last value of
  159. // the cycle sequence with the new digit.
  160. if let Some(ref mut cycle) = self.cycle {
  161. if !cycle_cmd {
  162. cycle.last_digit = digit;
  163. cycle.last_fade_duration = fade_duration;
  164. }
  165. }
  166. // Dont update if actively cycling tube unless cycle_cmd is set
  167. if (self.cycle.is_none() && !cycle_cmd) || cycle_cmd {
  168. // Fade out all digits
  169. for digit in 0..NUM_DIGITS {
  170. self.digits[digit].state = State::Decrementing;
  171. self.digits[digit].fade_duration = Some(fade_duration);
  172. }
  173. // Fade in the specified digit
  174. if let Some(digit) = digit {
  175. self.digits[digit as usize].state = State::Incrementing;
  176. self.digits[digit as usize].fade_duration = Some(fade_duration);
  177. }
  178. self.last_digit = digit;
  179. }
  180. }
  181. }
  182. pub struct Clock {
  183. tubes: [Tube; NUM_TUBES],
  184. dot: Digit,
  185. minute: Option<u32>,
  186. hour: Option<u32>,
  187. }
  188. impl Clock {
  189. pub const fn default() -> Self {
  190. const TUBE_INIT: Tube = Tube::default();
  191. Self {
  192. tubes: [TUBE_INIT; NUM_TUBES],
  193. dot: Digit::default(),
  194. minute: None,
  195. hour: None,
  196. }
  197. }
  198. // Sets a new time to be displayed
  199. pub fn rtc_tick(&mut self, second: u32, minute: u32, hour: u32) {
  200. // Update digit for each tube if value has changed
  201. match self.hour {
  202. Some(prev_hour) if prev_hour / 10 == hour / 10 => {
  203. if hour / 10 == 0 {
  204. self.tubes[0].fade_in_out_digit(None, DIGIT_FADE_DURATION_MS, false);
  205. }
  206. }
  207. _ => {
  208. self.tubes[0].fade_in_out_digit(Some(hour / 10), DIGIT_FADE_DURATION_MS, false);
  209. }
  210. }
  211. match self.hour {
  212. Some(prev_hour) if prev_hour % 10 == hour % 10 => {}
  213. _ => {
  214. self.tubes[1].fade_in_out_digit(Some(hour % 10), DIGIT_FADE_DURATION_MS, false);
  215. }
  216. }
  217. match self.minute {
  218. Some(prev_minute) if prev_minute / 10 == minute / 10 => {}
  219. _ => {
  220. self.tubes[2].fade_in_out_digit(Some(minute / 10), DIGIT_FADE_DURATION_MS, false);
  221. }
  222. }
  223. match self.minute {
  224. Some(prev_minute) if prev_minute % 10 == minute % 10 => {}
  225. _ => {
  226. self.tubes[3].fade_in_out_digit(Some(minute % 10), DIGIT_FADE_DURATION_MS, false);
  227. }
  228. }
  229. #[cfg(test)]
  230. println!(
  231. "RTC tick: {}{}:{}{}",
  232. hour / 10,
  233. hour % 10,
  234. minute / 10,
  235. minute % 10
  236. );
  237. // Set fade direction for dot
  238. self.dot.state = match second % 2 {
  239. 0 => State::Incrementing,
  240. 1 => State::Decrementing,
  241. _ => State::Idle,
  242. };
  243. self.dot.fade_duration = Some(DIGIT_FADE_DURATION_MS);
  244. #[cfg(test)]
  245. println!("RTC tick: dot state is {:?}", self.dot.state);
  246. // Store the last set value for the next update
  247. self.hour = Some(hour);
  248. self.minute = Some(minute);
  249. }
  250. // Updates the display with values due to fade in/out
  251. // Returns true if values have changed
  252. pub fn fps_tick(&mut self) -> bool {
  253. let mut pending_refresh: bool = false;
  254. let mut update_fn = |digit: &mut Digit, min: u32, max: u32, steps: u32| {
  255. match digit.state {
  256. State::Incrementing => {
  257. if digit.value >= max {
  258. digit.value = max;
  259. digit.state = State::Idle;
  260. } else {
  261. digit.value = digit.value.saturating_add(steps).clamp(min, max);
  262. digit.updated = true;
  263. pending_refresh = true;
  264. }
  265. }
  266. State::Decrementing => {
  267. if digit.value <= min {
  268. digit.value = min;
  269. digit.state = State::Idle;
  270. } else {
  271. digit.value = digit.value.saturating_sub(steps).clamp(min, max);
  272. digit.updated = true;
  273. pending_refresh = true;
  274. }
  275. }
  276. State::Idle => {
  277. digit.fade_duration = None;
  278. }
  279. };
  280. };
  281. #[cfg(not(test))]
  282. self.tubes.iter_mut().for_each(|tube| {
  283. tube.digits.iter_mut().for_each(|digit| {
  284. if let Some(fade_duration) = digit.fade_duration {
  285. let ticks = fade_duration * 1000 / (1000 / DISPLAY_REFRESH_FPS * 1000);
  286. let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
  287. update_fn(digit, DIGIT_MIN_BRIGHTNESS, DIGIT_MAX_BRIGHTNESS, steps);
  288. }
  289. });
  290. });
  291. #[cfg(test)]
  292. for (t, tube) in self.tubes.iter_mut().enumerate() {
  293. for (d, digit) in tube.digits.iter_mut().enumerate() {
  294. if let Some(fade_duration) = digit.fade_duration {
  295. let ticks = fade_duration * 1000 / (1000 / DISPLAY_REFRESH_FPS * 1000);
  296. let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
  297. update_fn(digit, DIGIT_MIN_BRIGHTNESS, DIGIT_MAX_BRIGHTNESS, steps);
  298. }
  299. if digit.updated {
  300. println!(
  301. "Refresh tick: updated tube {} digit {} to value {}",
  302. t, d, digit.value
  303. );
  304. }
  305. }
  306. }
  307. // Update dot values
  308. if let Some(fade_duration) = self.dot.fade_duration {
  309. let ticks = fade_duration * 1000 / (1000 / DISPLAY_REFRESH_FPS * 1000);
  310. let steps = ((DOT_MAX_BRIGHTNESS - DOT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
  311. update_fn(&mut self.dot, DOT_MIN_BRIGHTNESS, DOT_MAX_BRIGHTNESS, steps);
  312. }
  313. #[cfg(test)]
  314. if self.dot.updated {
  315. println!("Refresh tick: updated dot to value {}", self.dot.value);
  316. }
  317. // Compute actual PWM values if display values have changed
  318. if pending_refresh {
  319. self.distribute_pwm();
  320. }
  321. pending_refresh
  322. }
  323. // Updates the digit displayed during a cycle sequence
  324. // Returns true if the cycle sequence has completed
  325. pub fn cycle_tick(&mut self) -> bool {
  326. let mut cycle_ended = true;
  327. self.tubes.iter_mut().for_each(|tube| {
  328. if let Some(cycle) = tube.cycle.as_mut() {
  329. #[cfg(test)]
  330. println!("Cycle tick: iteration {}", cycle.iteration);
  331. if cycle.iteration > 0 {
  332. let next_digit = cycle.next_digit;
  333. cycle.next_digit = if cycle.next_digit == 9 { 0 } else { cycle.next_digit + 1 };
  334. cycle.iteration = cycle.iteration - 1;
  335. tube.fade_in_out_digit(Some(next_digit), CYCLE_FADE_DURATION_MS, true);
  336. cycle_ended = false;
  337. } else {
  338. let last_digit = cycle.last_digit;
  339. let last_fade = cycle.last_fade_duration;
  340. tube.cycle = None;
  341. tube.fade_in_out_digit(last_digit, last_fade, false);
  342. }
  343. }
  344. });
  345. cycle_ended
  346. }
  347. // Start cycling sequence for the given tube to prevent long term damage from cathode poisoning
  348. pub fn cycle_start(&mut self, tube: usize) {
  349. self.tubes[tube].cycle = Some(CycleSettings {
  350. last_digit: self.tubes[tube].last_digit,
  351. next_digit: 0,
  352. iteration: CYCLE_ITERATIONS,
  353. last_fade_duration: DIGIT_FADE_DURATION_MS,
  354. });
  355. }
  356. // Writes updated PWM values to each PCA9685
  357. pub fn write_i2c<T>(&mut self, i2c: &mut T)
  358. where
  359. T: _embedded_hal_blocking_i2c_WriteRead
  360. + _embedded_hal_blocking_i2c_Read
  361. + _embedded_hal_blocking_i2c_Write,
  362. {
  363. for (t, tube) in self.tubes.iter_mut().enumerate() {
  364. for (d, digit) in tube.digits.iter_mut().enumerate() {
  365. if digit.updated {
  366. pca9685::set_digit(
  367. i2c,
  368. TUBE_MAPPING.driver[t].digit[d].address,
  369. TUBE_MAPPING.driver[t].digit[d].pin,
  370. digit.pwm_start,
  371. digit.pwm_end,
  372. );
  373. digit.updated = false;
  374. }
  375. }
  376. }
  377. if self.dot.updated {
  378. pca9685::set_digit(
  379. i2c,
  380. TUBE_MAPPING.dot_address,
  381. TUBE_MAPPING.dot_pin,
  382. self.dot.pwm_start,
  383. self.dot.pwm_end,
  384. );
  385. self.dot.updated = false;
  386. }
  387. }
  388. // In the event that there are multiple PWM outputs at less than 100% duty cycle,
  389. // stagger the start time of each PWM to reduce the switch on surge current. If the
  390. // duty cycle is greater than 100%, distribute the PWM outputs as much as possible
  391. // to keep the current consumption at a minimum.
  392. fn distribute_pwm(&mut self) {
  393. let mut last_pwm: u32 = 0;
  394. let mut incrementing: bool = true;
  395. // Closure to avoid duplicate code
  396. let mut update_digit = |digit: &mut Digit| {
  397. if digit.value == DIGIT_MIN_BRIGHTNESS {
  398. digit.pwm_start = 0;
  399. digit.pwm_end = 0;
  400. } else if digit.value == DIGIT_MAX_BRIGHTNESS {
  401. digit.pwm_start = 0;
  402. digit.pwm_end = DIGIT_MAX_BRIGHTNESS;
  403. } else {
  404. if incrementing {
  405. if last_pwm + digit.value > DIGIT_MAX_BRIGHTNESS {
  406. digit.pwm_start = DIGIT_MAX_BRIGHTNESS - digit.value;
  407. digit.pwm_end = DIGIT_MAX_BRIGHTNESS;
  408. last_pwm = digit.pwm_start;
  409. incrementing = false;
  410. } else {
  411. digit.pwm_start = last_pwm;
  412. digit.pwm_end = digit.pwm_start + digit.value;
  413. last_pwm = digit.pwm_end;
  414. }
  415. } else {
  416. if last_pwm - DIGIT_MIN_BRIGHTNESS < digit.value {
  417. digit.pwm_start = DIGIT_MIN_BRIGHTNESS;
  418. digit.pwm_end = digit.pwm_start + digit.value;
  419. last_pwm = digit.pwm_end;
  420. incrementing = true;
  421. } else {
  422. digit.pwm_end = last_pwm;
  423. digit.pwm_start = digit.pwm_end - digit.value;
  424. last_pwm = digit.pwm_start;
  425. }
  426. }
  427. digit.updated = true;
  428. }
  429. };
  430. #[cfg(not(test))]
  431. self.tubes.iter_mut().for_each(|tube| {
  432. tube.digits.iter_mut().for_each(|digit| {
  433. update_digit(digit);
  434. });
  435. });
  436. #[cfg(test)]
  437. for (t, tube) in self.tubes.iter_mut().enumerate() {
  438. for (d, digit) in tube.digits.iter_mut().enumerate() {
  439. update_digit(digit);
  440. if digit.updated {
  441. println!(
  442. "Distribute PWM: tube {} digit {} start {} end {}",
  443. t, d, digit.pwm_start, digit.pwm_end
  444. );
  445. }
  446. }
  447. }
  448. // The dot is somewhat sensitive to changes to PWM signal, so for the sake
  449. // of consistency, keep the signal on time at the end of the PWM period.
  450. // Otherwise the distribution algorithm will sometimes place the on time at
  451. // the end of the PWM period in one cycle and at the start of the period for
  452. // the next, which results in visual flickers.
  453. // update_digit(&mut self.dot);
  454. self.dot.pwm_start = DIGIT_MAX_BRIGHTNESS - self.dot.value;
  455. self.dot.pwm_end = DIGIT_MAX_BRIGHTNESS;
  456. #[cfg(test)]
  457. println!(
  458. "Distribute PWM: dot start {} end {}",
  459. self.dot.pwm_start, self.dot.pwm_end
  460. );
  461. }
  462. }
  463. #[cfg(test)]
  464. mod test {
  465. use super::*;
  466. use std::println;
  467. #[test]
  468. fn pwm_calc_test() {
  469. let mut clock: Clock = Clock::default();
  470. // Initialize clock to arbitrary time
  471. clock.rtc_tick(10, 8, 12);
  472. // Iterate and print output values for each display refresh tick
  473. for tick in 0..1000 {
  474. println!("\nRefresh tick: {}", tick);
  475. if !clock.fps_tick() {
  476. println!("Refresh halted");
  477. break;
  478. }
  479. // Reset the updated field for each digit
  480. clock.tubes.iter_mut().for_each(|tube| {
  481. tube.digits.iter_mut().for_each(|digit| {
  482. digit.updated = false;
  483. });
  484. });
  485. clock.dot.updated = false;
  486. }
  487. }
  488. #[test]
  489. fn cycle_test() {
  490. let mut clock: Clock = Clock::default();
  491. // Initialize clock to arbitrary time
  492. clock.rtc_tick(00, 27, 8);
  493. // Simulate a cycle refresh sequence on tube 0
  494. clock.tubes[0].cycle = Some(CycleSettings {
  495. last_digit: clock.tubes[0].last_digit,
  496. next_digit: 0,
  497. iteration: CYCLE_ITERATIONS,
  498. last_fade_duration: DIGIT_FADE_DURATION_MS,
  499. });
  500. // Iterate and print debug values for each cycle
  501. for cycle in 0..1000 {
  502. println!("\nCycle tick: {}", cycle);
  503. if clock.cycle_tick() {
  504. println!("Cycle halted");
  505. break;
  506. }
  507. // Iterate and print output values for each display refresh tick
  508. for tick in 0..1000 {
  509. println!("\nRefresh tick: {}", tick);
  510. if !clock.fps_tick() {
  511. println!("Refresh halted");
  512. break;
  513. }
  514. // Reset the updated field for each digit
  515. clock.tubes.iter_mut().for_each(|tube| {
  516. tube.digits.iter_mut().for_each(|digit| {
  517. digit.updated = false;
  518. });
  519. });
  520. clock.dot.updated = false;
  521. }
  522. }
  523. }
  524. }