nixie.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  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::{ds3231, 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. const MAP_DOT_ADDR: u8 = PCA9685_ADDR_2;
  18. const MAP_DOT_PIN: u8 = 15;
  19. const MAP_ADDR: usize = 0;
  20. const MAP_PIN: usize = 1;
  21. const DIGIT_FADE_DURATION_US: u32 = 1_000_000;
  22. const REFRESH_RATE_US: u32 = 1000;
  23. const DIGIT_RNG_FADE_DURATION_US: u32 = 200_000;
  24. const DIGIT_RNG_FADE_ITERATIONS: usize = 20;
  25. const DIGIT_RNG_REFRESH_INTERVAL: usize = 60;
  26. const DIGIT_RNG_REFRESH_VARIANCE: usize = 30;
  27. const DOT_MIN_BRIGHTNESS: u32 = 256;
  28. const DOT_MAX_BRIGHTNESS: u32 = 640;
  29. const DOT_FADE_DURATION_US: u32 = 1_000_000;
  30. const DIGIT_MAX_BRIGHTNESS: u32 = 4096;
  31. const DIGIT_MIN_BRIGHTNESS: u32 = 0;
  32. const NUM_TUBES: usize = 4;
  33. const NUM_DIGITS: usize = 10;
  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 {
  52. address: PCA9685_ADDR_1,
  53. pin: 8,
  54. }, // Tube 0 Digit 0
  55. DigitToPin {
  56. address: PCA9685_ADDR_1,
  57. pin: 9,
  58. }, // Tube 0 Digit 1
  59. DigitToPin {
  60. address: PCA9685_ADDR_1,
  61. pin: 10,
  62. }, // Tube 0 Digit 2
  63. DigitToPin {
  64. address: PCA9685_ADDR_1,
  65. pin: 12,
  66. }, // Tube 0 Digit 3
  67. DigitToPin {
  68. address: PCA9685_ADDR_1,
  69. pin: 15,
  70. }, // Tube 0 Digit 4
  71. DigitToPin {
  72. address: PCA9685_ADDR_1,
  73. pin: 14,
  74. }, // Tube 0 Digit 5
  75. DigitToPin {
  76. address: PCA9685_ADDR_1,
  77. pin: 11,
  78. }, // Tube 0 Digit 6
  79. DigitToPin {
  80. address: PCA9685_ADDR_1,
  81. pin: 0,
  82. }, // Tube 0 Digit 7
  83. DigitToPin {
  84. address: PCA9685_ADDR_1,
  85. pin: 1,
  86. }, // Tube 0 Digit 8
  87. DigitToPin {
  88. address: PCA9685_ADDR_1,
  89. pin: 13,
  90. }, // Tube 0 Digit 9
  91. ],
  92. },
  93. PwmDriver {
  94. digit: [
  95. DigitToPin {
  96. address: PCA9685_ADDR_1,
  97. pin: 5,
  98. }, // Tube 1 Digit 0
  99. DigitToPin {
  100. address: PCA9685_ADDR_1,
  101. pin: 6,
  102. }, // Tube 1 Digit 1
  103. DigitToPin {
  104. address: PCA9685_ADDR_1,
  105. pin: 7,
  106. }, // Tube 1 Digit 2
  107. DigitToPin {
  108. address: PCA9685_ADDR_1,
  109. pin: 2,
  110. }, // Tube 1 Digit 3
  111. DigitToPin {
  112. address: PCA9685_ADDR_2,
  113. pin: 4,
  114. }, // Tube 1 Digit 4
  115. DigitToPin {
  116. address: PCA9685_ADDR_2,
  117. pin: 1,
  118. }, // Tube 1 Digit 5
  119. DigitToPin {
  120. address: PCA9685_ADDR_1,
  121. pin: 4,
  122. }, // Tube 1 Digit 6
  123. DigitToPin {
  124. address: PCA9685_ADDR_2,
  125. pin: 2,
  126. }, // Tube 1 Digit 7
  127. DigitToPin {
  128. address: PCA9685_ADDR_2,
  129. pin: 3,
  130. }, // Tube 1 Digit 8
  131. DigitToPin {
  132. address: PCA9685_ADDR_1,
  133. pin: 3,
  134. }, // Tube 1 Digit 9
  135. ],
  136. },
  137. PwmDriver {
  138. digit: [
  139. DigitToPin {
  140. address: PCA9685_ADDR_3,
  141. pin: 8,
  142. }, // Tube 2 Digit 0
  143. DigitToPin {
  144. address: PCA9685_ADDR_3,
  145. pin: 9,
  146. }, // Tube 2 Digit 1
  147. DigitToPin {
  148. address: PCA9685_ADDR_3,
  149. pin: 10,
  150. }, // Tube 2 Digit 2
  151. DigitToPin {
  152. address: PCA9685_ADDR_3,
  153. pin: 12,
  154. }, // Tube 2 Digit 3
  155. DigitToPin {
  156. address: PCA9685_ADDR_2,
  157. pin: 12,
  158. }, // Tube 2 Digit 4
  159. DigitToPin {
  160. address: PCA9685_ADDR_2,
  161. pin: 13,
  162. }, // Tube 2 Digit 5
  163. DigitToPin {
  164. address: PCA9685_ADDR_3,
  165. pin: 11,
  166. }, // Tube 2 Digit 6
  167. DigitToPin {
  168. address: PCA9685_ADDR_2,
  169. pin: 14,
  170. }, // Tube 2 Digit 7
  171. DigitToPin {
  172. address: PCA9685_ADDR_2,
  173. pin: 11,
  174. }, // Tube 2 Digit 8
  175. DigitToPin {
  176. address: PCA9685_ADDR_3,
  177. pin: 13,
  178. }, // Tube 2 Digit 9
  179. ],
  180. },
  181. PwmDriver {
  182. digit: [
  183. DigitToPin {
  184. address: PCA9685_ADDR_3,
  185. pin: 5,
  186. }, // Tube 3 Digit 0
  187. DigitToPin {
  188. address: PCA9685_ADDR_3,
  189. pin: 6,
  190. }, // Tube 3 Digit 1
  191. DigitToPin {
  192. address: PCA9685_ADDR_3,
  193. pin: 7,
  194. }, // Tube 3 Digit 2
  195. DigitToPin {
  196. address: PCA9685_ADDR_3,
  197. pin: 2,
  198. }, // Tube 3 Digit 3
  199. DigitToPin {
  200. address: PCA9685_ADDR_3,
  201. pin: 14,
  202. }, // Tube 3 Digit 4
  203. DigitToPin {
  204. address: PCA9685_ADDR_3,
  205. pin: 15,
  206. }, // Tube 3 Digit 5
  207. DigitToPin {
  208. address: PCA9685_ADDR_3,
  209. pin: 4,
  210. }, // Tube 3 Digit 6
  211. DigitToPin {
  212. address: PCA9685_ADDR_3,
  213. pin: 1,
  214. }, // Tube 3 Digit 7
  215. DigitToPin {
  216. address: PCA9685_ADDR_3,
  217. pin: 0,
  218. }, // Tube 3 Digit 8
  219. DigitToPin {
  220. address: PCA9685_ADDR_3,
  221. pin: 3,
  222. }, // Tube 3 Digit 9
  223. ],
  224. },
  225. ],
  226. dot_address: PCA9685_ADDR_2,
  227. dot_pin: 15,
  228. }
  229. };
  230. #[derive(PartialEq)]
  231. enum DigitState {
  232. Idle,
  233. Incrementing,
  234. Decrementing,
  235. }
  236. struct Digit {
  237. current_state: DigitState,
  238. value: u32,
  239. pwm_start: u32,
  240. pwm_end: u32,
  241. updated: bool,
  242. }
  243. impl Digit {
  244. const fn default() -> Self {
  245. Self {
  246. current_state: DigitState::Idle,
  247. value: 0,
  248. pwm_start: 0,
  249. pwm_end: 0,
  250. updated: false,
  251. }
  252. }
  253. }
  254. struct Tube {
  255. digits: [Digit; NUM_DIGITS],
  256. last_active_digit: Option<usize>,
  257. refresh_last_digit: Option<usize>,
  258. refresh_active: bool,
  259. }
  260. impl Tube {
  261. const fn default() -> Self {
  262. const DIGIT_INIT: Digit = Digit::default();
  263. Self {
  264. digits: [DIGIT_INIT; 10],
  265. last_active_digit: None,
  266. refresh_last_digit: None,
  267. refresh_active: false,
  268. }
  269. }
  270. }
  271. struct Clock {
  272. tubes: [Tube; NUM_TUBES],
  273. dot: Digit,
  274. fade_duration: u32,
  275. }
  276. impl Clock {
  277. const fn default() -> Self {
  278. const TUBE_INIT: Tube = Tube::default();
  279. Self {
  280. tubes: [TUBE_INIT; NUM_TUBES],
  281. dot: Digit::default(),
  282. fade_duration: 0,
  283. }
  284. }
  285. }
  286. static mut CLOCK: Clock = Clock::default();
  287. pub fn fade_in_out_digit(tube: usize, digit: Option<usize>, fade_duration: u32, refresh_cmd: bool) {
  288. // assert!(tube < NUM_TUBES);
  289. // assert!(Some(digit) < NUM_DIGITS);
  290. unsafe {
  291. // If the tube is in the middle of a refresh sequence and a call comes
  292. // in to update the tube digit (for time), override the last value of
  293. // the refresh sequence with the new digit.
  294. if CLOCK.tubes[tube].refresh_active && !refresh_cmd {
  295. CLOCK.tubes[tube].refresh_last_digit = digit;
  296. }
  297. // Dont update if actively refreshing tube unless RngUpdate is set
  298. if (!CLOCK.tubes[tube].refresh_active && !refresh_cmd) || refresh_cmd {
  299. // Fade out all digits
  300. for digit in 0..NUM_DIGITS {
  301. CLOCK.tubes[tube].digits[digit].current_state = DigitState::Decrementing;
  302. }
  303. // Fade in the specified digit
  304. if let Some(digit) = digit {
  305. CLOCK.tubes[tube].digits[digit].current_state = DigitState::Incrementing;
  306. }
  307. CLOCK.tubes[tube].last_active_digit = digit;
  308. CLOCK.fade_duration = fade_duration;
  309. }
  310. }
  311. // TODO! trigger refresh timer
  312. }
  313. pub fn refresh_frame() {
  314. let mut pending_refresh: bool = false;
  315. unsafe {
  316. let ticks = CLOCK.fade_duration / REFRESH_RATE_US;
  317. let steps = ((DIGIT_MAX_BRIGHTNESS - DIGIT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
  318. CLOCK.tubes.iter_mut().for_each(|tube| {
  319. tube.digits.iter_mut().for_each(|digit| {
  320. match digit.current_state {
  321. DigitState::Incrementing => {
  322. digit.value = digit.value.saturating_add(steps);
  323. if digit.value >= DIGIT_MAX_BRIGHTNESS {
  324. digit.value = DIGIT_MAX_BRIGHTNESS;
  325. digit.current_state = DigitState::Idle;
  326. }
  327. digit.updated = true;
  328. pending_refresh = true;
  329. }
  330. DigitState::Decrementing => {
  331. digit.value = digit.value.saturating_sub(steps);
  332. if digit.value <= DIGIT_MIN_BRIGHTNESS {
  333. digit.value = DIGIT_MIN_BRIGHTNESS;
  334. digit.current_state = DigitState::Idle;
  335. }
  336. digit.updated = true;
  337. pending_refresh = true;
  338. }
  339. DigitState::Idle => (),
  340. };
  341. });
  342. });
  343. // Handle dot
  344. let steps = ((DOT_MAX_BRIGHTNESS - DOT_MIN_BRIGHTNESS) + ticks - 1) / ticks;
  345. match CLOCK.dot.current_state {
  346. DigitState::Incrementing => {
  347. CLOCK.dot.value = CLOCK.dot.value.saturating_add(steps);
  348. if CLOCK.dot.value >= DIGIT_MAX_BRIGHTNESS {
  349. CLOCK.dot.value = DIGIT_MAX_BRIGHTNESS;
  350. CLOCK.dot.current_state = DigitState::Idle;
  351. }
  352. CLOCK.dot.updated = true;
  353. pending_refresh = true;
  354. }
  355. DigitState::Decrementing => {
  356. CLOCK.dot.value = CLOCK.dot.value.saturating_sub(steps);
  357. if CLOCK.dot.value >= DIGIT_MIN_BRIGHTNESS {
  358. CLOCK.dot.value = DIGIT_MIN_BRIGHTNESS;
  359. CLOCK.dot.current_state = DigitState::Idle;
  360. }
  361. CLOCK.dot.updated = true;
  362. pending_refresh = true;
  363. }
  364. DigitState::Idle => (),
  365. }
  366. }
  367. if pending_refresh {
  368. // TODO! trigger refresh timer
  369. }
  370. }
  371. pub fn rtc_tick<T>(i2c: &mut T)
  372. where
  373. T: _embedded_hal_blocking_i2c_WriteRead
  374. + _embedded_hal_blocking_i2c_Read
  375. + _embedded_hal_blocking_i2c_Write,
  376. {
  377. static mut STARTUP: bool = true;
  378. static mut PREV_MINUTE: u32 = 0;
  379. static mut PREV_HOUR: u32 = 0;
  380. let (second, minute, hour) = ds3231::get_time(DS3231_ADDR, i2c);
  381. let (weekday, day, month, _, _) = ds3231::get_date(DS3231_ADDR, i2c);
  382. let hour = if ds3231::in_dst(weekday, day, month, hour) {
  383. (hour + 1) % 12
  384. } else {
  385. hour % 12
  386. };
  387. let hour = if hour == 0 { 12 } else { hour };
  388. unsafe {
  389. if STARTUP || PREV_HOUR / 10 != hour / 10 {
  390. fade_in_out_digit(
  391. 0,
  392. if hour / 10 != 0 {
  393. Some((hour / 10) as usize)
  394. } else {
  395. None
  396. },
  397. DIGIT_FADE_DURATION_US,
  398. false,
  399. )
  400. }
  401. if STARTUP || PREV_HOUR % 10 != hour % 10 {
  402. fade_in_out_digit(1, Some((hour % 10) as usize), DIGIT_FADE_DURATION_US, false);
  403. }
  404. if STARTUP || PREV_MINUTE / 10 != minute / 10 {
  405. fade_in_out_digit(
  406. 2,
  407. Some((minute / 10) as usize),
  408. DIGIT_FADE_DURATION_US,
  409. false,
  410. );
  411. }
  412. if STARTUP || PREV_MINUTE % 10 != minute % 10 {
  413. fade_in_out_digit(
  414. 3,
  415. Some((minute % 10) as usize),
  416. DIGIT_FADE_DURATION_US,
  417. false,
  418. );
  419. }
  420. CLOCK.dot.current_state = match second % 2 {
  421. 0 => DigitState::Incrementing,
  422. 1 => DigitState::Decrementing,
  423. _ => DigitState::Idle,
  424. };
  425. PREV_MINUTE = minute;
  426. PREV_HOUR = hour;
  427. STARTUP = false;
  428. }
  429. }
  430. pub fn refresh_tick<T>(i2c: &mut T)
  431. where
  432. T: _embedded_hal_blocking_i2c_WriteRead
  433. + _embedded_hal_blocking_i2c_Read
  434. + _embedded_hal_blocking_i2c_Write,
  435. {
  436. compute_pwm_offset();
  437. unsafe {
  438. for (t, tube) in CLOCK.tubes.iter().enumerate() {
  439. for (d, digit) in tube.digits.iter().filter(|d| d.updated).enumerate() {
  440. pca9685::set_digit(
  441. TUBE_MAPPING.driver[t].digit[d].address,
  442. i2c,
  443. TUBE_MAPPING.driver[t].digit[d].pin,
  444. digit.pwm_start,
  445. digit.pwm_end,
  446. );
  447. }
  448. }
  449. if CLOCK.dot.updated {
  450. pca9685::set_digit(
  451. TUBE_MAPPING.dot_address,
  452. i2c,
  453. TUBE_MAPPING.dot_pin,
  454. CLOCK.dot.pwm_start,
  455. CLOCK.dot.pwm_end,
  456. );
  457. }
  458. }
  459. }
  460. pub fn rng_tick<T>(i2c: &mut T)
  461. where
  462. T: _embedded_hal_blocking_i2c_WriteRead
  463. + _embedded_hal_blocking_i2c_Read
  464. + _embedded_hal_blocking_i2c_Write,
  465. {
  466. }
  467. // In the event that there are multiple PWM outputs at less than 100% duty cycle,
  468. // stagger the start time of each PWM to reduce the switch on surge current.
  469. fn compute_pwm_offset() {
  470. let mut active_digits: u32 = 0;
  471. let mut total_on_time: u32 = 0;
  472. let mut last_pwm_end: u32 = 0;
  473. // Determine the number of active outputs as well as the total on-time across all outputs.
  474. // Ignore outputs that are off (min) or fully on (max) as they have no surge impact.
  475. unsafe {
  476. CLOCK.tubes.iter().for_each(|tube| {
  477. tube.digits.iter().for_each(|digit| {
  478. if digit.value != DIGIT_MAX_BRIGHTNESS && digit.value != DIGIT_MIN_BRIGHTNESS {
  479. active_digits = active_digits + 1;
  480. total_on_time = total_on_time + digit.value;
  481. }
  482. });
  483. });
  484. if CLOCK.dot.value != DIGIT_MAX_BRIGHTNESS && CLOCK.dot.value != DIGIT_MIN_BRIGHTNESS {
  485. active_digits = active_digits + 1;
  486. total_on_time = total_on_time + CLOCK.dot.value;
  487. }
  488. // If the total on-time across all outputs is less than one PWM period, stagger each
  489. // output such that the rise of one pulse begins at the end of the previous pulse.
  490. if total_on_time <= DIGIT_MAX_BRIGHTNESS {
  491. CLOCK.tubes.iter_mut().for_each(|tube| {
  492. tube.digits.iter_mut().for_each(|digit| {
  493. if digit.value == DIGIT_MIN_BRIGHTNESS {
  494. digit.pwm_start = 0;
  495. digit.pwm_end = 0;
  496. } else if digit.value == DIGIT_MAX_BRIGHTNESS {
  497. digit.pwm_start = 0;
  498. digit.pwm_end = DIGIT_MAX_BRIGHTNESS;
  499. } else {
  500. digit.pwm_start = last_pwm_end;
  501. digit.pwm_end = last_pwm_end + digit.value;
  502. last_pwm_end = digit.pwm_end;
  503. digit.updated = true;
  504. }
  505. });
  506. });
  507. if CLOCK.dot.value == DIGIT_MIN_BRIGHTNESS {
  508. CLOCK.dot.pwm_start = 0;
  509. CLOCK.dot.pwm_end = 0;
  510. } else if CLOCK.dot.value == DIGIT_MAX_BRIGHTNESS {
  511. CLOCK.dot.pwm_start = 0;
  512. CLOCK.dot.pwm_end = DIGIT_MAX_BRIGHTNESS;
  513. } else {
  514. CLOCK.dot.pwm_start = last_pwm_end;
  515. CLOCK.dot.pwm_end = last_pwm_end + CLOCK.dot.value;
  516. CLOCK.dot.updated = true;
  517. }
  518. } else {
  519. // Compute the amount of overlap between all outputs
  520. // int overlap = (totalOnTime - PCA9685_Max_Brightness) / (validOutputs - 1);
  521. let overlap = (total_on_time - DIGIT_MAX_BRIGHTNESS) / (active_digits - 1);
  522. // Compute the staggered output period for each output
  523. CLOCK.tubes.iter_mut().for_each(|tube| {
  524. tube.digits.iter_mut().for_each(|digit| {
  525. if digit.value == DIGIT_MIN_BRIGHTNESS {
  526. digit.pwm_start = 0;
  527. digit.pwm_end = 0;
  528. } else if digit.value == DIGIT_MAX_BRIGHTNESS {
  529. digit.pwm_start = 0;
  530. digit.pwm_end = DIGIT_MAX_BRIGHTNESS;
  531. } else {
  532. digit.pwm_start = last_pwm_end.saturating_sub(overlap);
  533. digit.pwm_end = digit.pwm_start + digit.value;
  534. last_pwm_end = digit.pwm_end;
  535. digit.updated = true;
  536. }
  537. });
  538. });
  539. if CLOCK.dot.value == DIGIT_MIN_BRIGHTNESS {
  540. CLOCK.dot.pwm_start = 0;
  541. CLOCK.dot.pwm_end = 0;
  542. } else if CLOCK.dot.value == DIGIT_MAX_BRIGHTNESS {
  543. CLOCK.dot.pwm_start = 0;
  544. CLOCK.dot.pwm_end = DIGIT_MAX_BRIGHTNESS;
  545. } else {
  546. CLOCK.dot.pwm_start = last_pwm_end.saturating_sub(overlap);
  547. CLOCK.dot.pwm_end = CLOCK.dot.pwm_start + CLOCK.dot.value;
  548. CLOCK.dot.updated = true;
  549. }
  550. }
  551. }
  552. }