From c36581e703e6547e358bac8679f5371b3a7a96e0 Mon Sep 17 00:00:00 2001 From: finga Date: Sat, 2 Apr 2022 18:38:46 +0200 Subject: [PATCH] fw-rust: Make Si5351 useable In order to make the Si5351 configurable add some needed assets, create the ability to print the frequency with selected digits and add a channel screen. --- firmware/rust/src/assets.rs | 118 +++++++++-------- firmware/rust/src/lcd.rs | 73 +++++++++++ firmware/rust/src/screen/channel.rs | 191 ++++++++++++++++++++++++++++ firmware/rust/src/screen/home.rs | 82 +++--------- firmware/rust/src/screen/mod.rs | 85 ++++++++++++- 5 files changed, 430 insertions(+), 119 deletions(-) create mode 100644 firmware/rust/src/screen/channel.rs diff --git a/firmware/rust/src/assets.rs b/firmware/rust/src/assets.rs index 6be82da..3874534 100644 --- a/firmware/rust/src/assets.rs +++ b/firmware/rust/src/assets.rs @@ -146,6 +146,12 @@ const SYM_E: [&[u8]; 2] = [ &[0x3F, 0x3F, 0x31, 0x31, 0x30, 0x30], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_F: [&[u8]; 2] = [ + &[0xFC, 0xFC, 0x8C, 0x8C, 0x0C, 0x0C], + &[0x3F, 0x3F, 0x01, 0x01, 0x00, 0x00], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_G: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x0C, 0x0C, 0x3C, 0x38], @@ -191,6 +197,12 @@ const SYM_P: [&[u8]; 2] = [ &[0x3F, 0x3F, 0x01, 0x01, 0x01, 0x00], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_Q: [&[u8]; 2] = [ + &[0xF8, 0xFC, 0x0C, 0x0C, 0xFC, 0xF8], + &[0x1F, 0x3F, 0x30, 0x38, 0x7F, 0x6F], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_R: [&[u8]; 2] = [ &[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0xF8], @@ -215,6 +227,12 @@ const SYM_U: [&[u8]; 2] = [ &[0x1F, 0x3F, 0x30, 0x30, 0x3F, 0x1F], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_UNDERSCORE: [&[u8]; 2] = [ + &[0x00, 0x00, 0x00, 0x00, 0x00], + &[0x30, 0x30, 0x30, 0x30, 0x30], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_INVALID: [&[u8]; 2] = [ &[0x80, 0xE0, 0x98, 0xCC, 0x4C, 0x18, 0xE0, 0x80], @@ -223,54 +241,54 @@ const SYM_INVALID: [&[u8]; 2] = [ // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized pub const SYMBOL_TABLE: [&[&[u8]; 2]; 50] = [ - &SYM_DOT, // '.' - &SYM_INVALID, // '/' - &SYM_0, // '0' - &SYM_1, // '1' - &SYM_2, // '2' - &SYM_3, // '3' - &SYM_4, // '4' - &SYM_5, // '5' - &SYM_6, // '6' - &SYM_7, // '7' - &SYM_8, // '8' - &SYM_9, // '9' - &SYM_COLON, // ':' - &SYM_INVALID, // ';' - &SYM_INVALID, // '<' - &SYM_INVALID, // '=' - &SYM_INVALID, // '>' - &SYM_INVALID, // '?' - &SYM_INVALID, // '@' - &SYM_A, // 'A' - &SYM_B, // 'B' - &SYM_C, // 'C' - &SYM_INVALID, // 'D' - &SYM_E, // 'E' - &SYM_INVALID, // 'F' - &SYM_G, // 'G' - &SYM_H, // 'H' - &SYM_I, // 'I' - &SYM_INVALID, // 'J' - &SYM_K, // 'K' - &SYM_L, // 'L' - &SYM_INVALID, // 'M' - &SYM_N, // 'N' - &SYM_O, // 'O' - &SYM_P, // 'P' - &SYM_INVALID, // 'Q' - &SYM_R, // 'R' - &SYM_S, // 'S' - &SYM_T, // 'T' - &SYM_U, // 'U' - &SYM_INVALID, // 'V' - &SYM_INVALID, // 'W' - &SYM_INVALID, // 'X' - &SYM_INVALID, // 'Y' - &SYM_INVALID, // 'Z' - &SYM_INVALID, // '[' - &SYM_INVALID, // '\' - &SYM_INVALID, // ']' - &SYM_INVALID, // '^' - &SYM_INVALID, // '_' + &SYM_DOT, // '.' + &SYM_INVALID, // '/' + &SYM_0, // '0' + &SYM_1, // '1' + &SYM_2, // '2' + &SYM_3, // '3' + &SYM_4, // '4' + &SYM_5, // '5' + &SYM_6, // '6' + &SYM_7, // '7' + &SYM_8, // '8' + &SYM_9, // '9' + &SYM_COLON, // ':' + &SYM_INVALID, // ';' + &SYM_INVALID, // '<' + &SYM_INVALID, // '=' + &SYM_INVALID, // '>' + &SYM_INVALID, // '?' + &SYM_INVALID, // '@' + &SYM_A, // 'A' + &SYM_B, // 'B' + &SYM_C, // 'C' + &SYM_INVALID, // 'D' + &SYM_E, // 'E' + &SYM_F, // 'F' + &SYM_G, // 'G' + &SYM_H, // 'H' + &SYM_I, // 'I' + &SYM_INVALID, // 'J' + &SYM_K, // 'K' + &SYM_L, // 'L' + &SYM_INVALID, // 'M' + &SYM_N, // 'N' + &SYM_O, // 'O' + &SYM_P, // 'P' + &SYM_Q, // 'Q' + &SYM_R, // 'R' + &SYM_S, // 'S' + &SYM_T, // 'T' + &SYM_U, // 'U' + &SYM_INVALID, // 'V' + &SYM_INVALID, // 'W' + &SYM_INVALID, // 'X' + &SYM_INVALID, // 'Y' + &SYM_INVALID, // 'Z' + &SYM_INVALID, // '[' + &SYM_INVALID, // '\' + &SYM_INVALID, // ']' + &SYM_INVALID, // '^' + &SYM_UNDERSCORE, // '_' ]; diff --git a/firmware/rust/src/lcd.rs b/firmware/rust/src/lcd.rs index 02bee27..f360796 100644 --- a/firmware/rust/src/lcd.rs +++ b/firmware/rust/src/lcd.rs @@ -268,6 +268,79 @@ impl Lcd { } } + pub fn print_freq_digit(&mut self, segment: u8, page: u8, data: u32, digit: u8) { + let mut delay = Delay::::new(); + + let mut array = [0usize; 9]; + for (i, item) in array.iter_mut().enumerate() { + *item = (((data / 10_u32.pow(i.try_into().unwrap())) % 10) + 2) + .try_into() + .unwrap(); + } + array.reverse(); + let digit = 8 - digit; + + for i in 0..2 { + self.move_cursor(segment, page + i); + + // TODO: This delay fixes issues, try find a better solution + delay.delay_ms(1_u8); + self.cd.set_high(); + + block!(self.spi.send(0x00)).unwrap(); + block!(self.spi.send(0x00)).unwrap(); + for j in 0..3 { + if j == digit { + for segment in SYMBOL_TABLE[array[j as usize]][i as usize] { + block!(self.spi.send(!*segment)).unwrap(); + } + } else { + for segment in SYMBOL_TABLE[array[j as usize]][i as usize] { + block!(self.spi.send(*segment)).unwrap(); + } + } + block!(self.spi.send(0x00)).unwrap(); + } + for segment in SYMBOL_TABLE[0][i as usize] { + block!(self.spi.send(*segment)).unwrap(); + } + block!(self.spi.send(0x00)).unwrap(); + for j in 3..6 { + if j == digit { + for segment in SYMBOL_TABLE[array[j as usize]][i as usize] { + block!(self.spi.send(!*segment)).unwrap(); + } + } else { + for segment in SYMBOL_TABLE[array[j as usize]][i as usize] { + block!(self.spi.send(*segment)).unwrap(); + } + } + block!(self.spi.send(0x00)).unwrap(); + } + for segment in SYMBOL_TABLE[0][i as usize] { + block!(self.spi.send(*segment)).unwrap(); + } + block!(self.spi.send(0x00)).unwrap(); + for j in 6..9 { + if j == digit { + for segment in SYMBOL_TABLE[array[j as usize]][i as usize] { + block!(self.spi.send(!*segment)).unwrap(); + } + } else { + for segment in SYMBOL_TABLE[array[j as usize]][i as usize] { + block!(self.spi.send(*segment)).unwrap(); + } + } + block!(self.spi.send(0x00)).unwrap(); + } + block!(self.spi.send(0x00)).unwrap(); + + // TODO: This delay fixes issues, try find a better solution + delay.delay_ms(1_u8); + self.cd.set_low(); + } + } + pub fn print_icon(&mut self, segment: u8, page: u8, symbol: &[u8]) { let mut delay = Delay::::new(); self.move_cursor(segment, page); diff --git a/firmware/rust/src/screen/channel.rs b/firmware/rust/src/screen/channel.rs new file mode 100644 index 0000000..0ee5dc3 --- /dev/null +++ b/firmware/rust/src/screen/channel.rs @@ -0,0 +1,191 @@ +use super::{ClockChannel, Event, Home, Screens}; +use crate::{lcd::Lcd, Input}; +use si5351::{ClockOutput, PLL}; + +enum Selection { + Frequency, + FrequencyDigit(u8), + Digit(u8), + Enabled, + Pll, + Back, +} + +pub struct Channel { + active: Selection, + channel: ClockChannel, +} + +impl Channel { + pub fn new(channel: ClockChannel) -> Self { + Self { + active: Selection::Frequency, + channel, + } + } + + pub fn input(&mut self, input: &Input) -> Event { + self.active = match self.active { + Selection::Frequency => match input { + Input::Next => Selection::Enabled, + Input::Previous => Selection::Back, + Input::Select => Selection::FrequencyDigit(8), + Input::Back => return Event::Screen(Screens::Home(Home::new())), + }, + Selection::FrequencyDigit(digit) => match input { + Input::Next => { + if digit == u8::MIN { + Selection::FrequencyDigit(8) + } else { + Selection::FrequencyDigit(digit - 1) + } + } + Input::Previous => { + if digit >= 8 { + Selection::FrequencyDigit(u8::MIN) + } else { + Selection::FrequencyDigit(digit + 1) + } + } + Input::Select => Selection::Digit(digit), + Input::Back => Selection::Frequency, + }, + Selection::Digit(digit) => match input { + Input::Next => { + let new_freq = self.channel.freq + 10_u32.pow(digit.into()); + if new_freq < 162_000_000 { + self.channel.freq = new_freq; + } else { + self.channel.freq = 162_000_000 + } + return Event::Channel(self.channel); + } + Input::Previous => { + let difference = 10_u32.pow(digit.into()); + if self.channel.freq > difference { + let new_freq = self.channel.freq - difference; + if new_freq > 15_000 { + self.channel.freq = new_freq; + } else { + self.channel.freq = 15_000; + } + } else { + self.channel.freq = 15_000; + } + return Event::Channel(self.channel); + } + Input::Select => Selection::Frequency, + Input::Back => Selection::FrequencyDigit(digit), + }, + Selection::Enabled => match input { + Input::Next => Selection::Pll, + Input::Previous => Selection::Frequency, + Input::Select => { + self.channel.enabled = !self.channel.enabled; + return Event::Channel(self.channel); + } + Input::Back => return Event::Screen(Screens::Home(Home::new())), + }, + Selection::Pll => match input { + Input::Next => Selection::Back, + Input::Previous => Selection::Enabled, + Input::Select => { + self.channel.pll = match self.channel.pll { + PLL::A => PLL::B, + PLL::B => PLL::A, + }; + return Event::Channel(self.channel); + } + Input::Back => return Event::Screen(Screens::Home(Home::new())), + }, + Selection::Back => match input { + Input::Next => Selection::Frequency, + Input::Previous => Selection::Pll, + _ => return Event::Screen(Screens::Home(Home::new())), + }, + }; + + Event::None + } + + fn draw_enabled(&self, lcd: &mut Lcd, inverted: bool) { + if inverted { + match self.channel.enabled { + false => lcd.print_inverted(13, 4, "OFF"), + true => lcd.print_inverted(16, 4, "ON"), + } + } else { + match self.channel.enabled { + false => lcd.print(13, 4, "OFF"), + true => lcd.print(16, 4, "ON"), + } + } + } + + pub fn draw(&self, lcd: &mut Lcd) { + lcd.fill_area(0, 0, 19, 2, 0xFF); + match self.channel.output { + ClockOutput::Clk0 => lcd.print_inverted(19, 0, "CHANNEL_1"), + ClockOutput::Clk1 => lcd.print_inverted(19, 0, "CHANNEL_2"), + ClockOutput::Clk2 => lcd.print_inverted(19, 0, "CHANNEL_3"), + _ => unimplemented!(), + } + lcd.fill_area(83, 0, 19, 2, 0xFF); + + match &self.active { + Selection::Frequency => { + lcd.print_inverted(0, 2, "FREQ:"); + lcd.print_freq(39, 2, self.channel.freq); + self.draw_enabled(lcd, false); + match self.channel.pll { + PLL::A => lcd.print(59, 4, "PLL_A"), + PLL::B => lcd.print(59, 4, "PLL_B"), + } + lcd.print(36, 6, "BACK"); + } + Selection::FrequencyDigit(digit) | Selection::Digit(digit) => { + lcd.print(0, 2, "FREQ:"); + lcd.print_freq_digit(39, 2, self.channel.freq, *digit); + self.draw_enabled(lcd, false); + match self.channel.pll { + PLL::A => lcd.print(59, 4, "PLL_A"), + PLL::B => lcd.print(59, 4, "PLL_B"), + } + lcd.print(36, 6, "BACK"); + } + Selection::Enabled => { + lcd.print(0, 2, "FREQ:"); + lcd.print_freq(39, 2, self.channel.freq); + self.draw_enabled(lcd, true); + match self.channel.pll { + PLL::A => lcd.print(59, 4, "PLL_A"), + PLL::B => lcd.print(59, 4, "PLL_B"), + } + lcd.print(36, 6, "BACK"); + } + Selection::Pll => { + lcd.print(0, 2, "FREQ:"); + lcd.print_freq(39, 2, self.channel.freq); + self.draw_enabled(lcd, false); + match self.channel.pll { + PLL::A => lcd.print_inverted(59, 4, "PLL_A"), + PLL::B => lcd.print_inverted(59, 4, "PLL_B"), + } + lcd.print(36, 6, "BACK"); + } + Selection::Back => { + lcd.print(0, 2, "FREQ:"); + lcd.print_freq(39, 2, self.channel.freq); + match self.channel.enabled { + false => lcd.print(13, 4, "OFF"), + true => lcd.print(16, 4, "ON"), + } + match self.channel.pll { + PLL::A => lcd.print(59, 4, "PLL_A"), + PLL::B => lcd.print(59, 4, "PLL_B"), + } + lcd.print_inverted(36, 6, "BACK"); + } + } + } +} diff --git a/firmware/rust/src/screen/home.rs b/firmware/rust/src/screen/home.rs index 03aa376..68c064d 100644 --- a/firmware/rust/src/screen/home.rs +++ b/firmware/rust/src/screen/home.rs @@ -1,10 +1,5 @@ -use super::{Event, Screens, Setup, Splash}; -use crate::{ - assets::{OFF, ON, PLL_A, PLL_B}, - lcd::Lcd, - Input, -}; -use si5351::PLL; +use super::{Channel, Event, Screens, Setup}; +use crate::{lcd::Lcd, screen::ClockChannel, Input}; enum Selection { Ch1, @@ -13,74 +8,35 @@ enum Selection { Setup, } -struct Channel { - freq: u32, - enabled: bool, - pll: PLL, -} - -impl Channel { - fn print(&self, lcd: &mut Lcd, page: u8) { - lcd.print_freq(25, page, self.freq); - lcd.print_icon(91, page, if self.enabled { &ON } else { &OFF }); - lcd.print_icon( - 94, - page + 1, - match self.pll { - PLL::A => &PLL_A, - PLL::B => &PLL_B, - }, - ); - } -} - pub struct Home { active: Selection, - channels: [Channel; 3], } impl Home { pub fn new() -> Self { Self { active: Selection::Ch1, - channels: [ - Channel { - freq: 0, - enabled: false, - pll: PLL::A, - }, - Channel { - freq: 0, - enabled: false, - pll: PLL::A, - }, - Channel { - freq: 0, - enabled: false, - pll: PLL::A, - }, - ], } } - pub fn input(&mut self, input: &Input) -> Event { + pub fn input(&mut self, input: &Input, channels: [ClockChannel; 3]) -> Event { self.active = match self.active { Selection::Ch1 => match input { Input::Next => Selection::Ch2, Input::Previous => Selection::Setup, - Input::Select => return Event::Screen(Screens::Splash(Splash)), + Input::Select => return Event::Screen(Screens::Channel(Channel::new(channels[0]))), Input::Back => Selection::Ch1, }, Selection::Ch2 => match input { Input::Next => Selection::Ch3, Input::Previous => Selection::Ch1, - Input::Select => return Event::Screen(Screens::Splash(Splash)), + Input::Select => return Event::Screen(Screens::Channel(Channel::new(channels[1]))), Input::Back => Selection::Ch2, }, Selection::Ch3 => match input { Input::Next => Selection::Setup, Input::Previous => Selection::Ch2, - Input::Select => return Event::Screen(Screens::Splash(Splash)), + Input::Select => return Event::Screen(Screens::Channel(Channel::new(channels[2]))), Input::Back => Selection::Ch3, }, Selection::Setup => match input { @@ -94,42 +50,42 @@ impl Home { Event::None } - pub fn draw(&self, lcd: &mut Lcd) { + pub fn draw(&self, lcd: &mut Lcd, channels: [ClockChannel; 3]) { match &self.active { Selection::Ch1 => { lcd.print_inverted(0, 0, "CH1"); - self.channels[0].print(lcd, 0); + channels[0].print(lcd, 0); lcd.print(0, 2, "CH2"); - self.channels[1].print(lcd, 2); + channels[1].print(lcd, 2); lcd.print(0, 4, "CH3"); - self.channels[2].print(lcd, 4); + channels[2].print(lcd, 4); lcd.print(33, 6, "SETUP"); } Selection::Ch2 => { lcd.print(0, 0, "CH1"); - self.channels[0].print(lcd, 0); + channels[0].print(lcd, 0); lcd.print_inverted(0, 2, "CH2"); - self.channels[1].print(lcd, 2); + channels[1].print(lcd, 2); lcd.print(0, 4, "CH3"); - self.channels[2].print(lcd, 4); + channels[2].print(lcd, 4); lcd.print(33, 6, "SETUP"); } Selection::Ch3 => { lcd.print(0, 0, "CH1"); - self.channels[0].print(lcd, 0); + channels[0].print(lcd, 0); lcd.print(0, 2, "CH2"); - self.channels[1].print(lcd, 2); + channels[1].print(lcd, 2); lcd.print_inverted(0, 4, "CH3"); - self.channels[2].print(lcd, 4); + channels[2].print(lcd, 4); lcd.print(33, 6, "SETUP"); } Selection::Setup => { lcd.print(0, 0, "CH1"); - self.channels[0].print(lcd, 0); + channels[0].print(lcd, 0); lcd.print(0, 2, "CH2"); - self.channels[1].print(lcd, 2); + channels[1].print(lcd, 2); lcd.print(0, 4, "CH3"); - self.channels[2].print(lcd, 4); + channels[2].print(lcd, 4); lcd.print_inverted(33, 6, "SETUP"); } } diff --git a/firmware/rust/src/screen/mod.rs b/firmware/rust/src/screen/mod.rs index e8ab78c..622f761 100644 --- a/firmware/rust/src/screen/mod.rs +++ b/firmware/rust/src/screen/mod.rs @@ -3,13 +3,20 @@ use atmega_hal::{ port::{mode::Output, Pin, PB0, PB1, PD5}, Spi, }; -use si5351::{Si5351, Si5351Device}; +use si5351::{ClockOutput, Si5351, Si5351Device, PLL}; +mod channel; mod home; mod setup; mod splash; -use crate::{eeprom, lcd::Lcd, I2c, Input, BACKLIGHT}; +use crate::{ + assets::{OFF, ON, PLL_A, PLL_B}, + eeprom, + lcd::Lcd, + I2c, Input, BACKLIGHT, +}; +pub use channel::Channel; pub use home::Home; pub use setup::Setup; pub use splash::Splash; @@ -20,21 +27,55 @@ pub enum Event { Screen(Screens), Backlight(u8), Contrast(u8), + Channel(ClockChannel), None, } +#[derive(Clone, Copy)] +pub struct ClockChannel { + output: ClockOutput, + freq: u32, + enabled: bool, + pll: PLL, +} + +impl ClockChannel { + fn print(&self, lcd: &mut Lcd, page: u8) { + lcd.print_freq(25, page, self.freq); + lcd.print_icon(91, page, if self.enabled { &ON } else { &OFF }); + lcd.print_icon( + 94, + page + 1, + match self.pll { + PLL::A => &PLL_A, + PLL::B => &PLL_B, + }, + ); + } + + fn update(&self, si5351: &mut Si5351Device) { + si5351 + .set_frequency(self.pll, self.output, self.freq) + .unwrap(); + si5351.set_clock_enabled(self.output, self.enabled); + si5351.flush_output_enabled().unwrap(); + } +} + pub enum Screens { Splash(Splash), Home(Home), Setup(Setup), + Channel(Channel), } impl Screens { - pub fn input(&mut self, input: &Input) -> Event { + pub fn input(&mut self, input: &Input, channels: [ClockChannel; 3]) -> Event { match self { Screens::Splash(_) => Event::None, - Screens::Home(home) => home.input(input), + Screens::Home(home) => home.input(input, channels), Screens::Setup(setup) => setup.input(input), + Screens::Channel(channel) => channel.input(input), } } } @@ -45,6 +86,7 @@ pub struct Screen { pwm: Pin, screen: Screens, si5351: Si5351Device, + channels: [ClockChannel; 3], } impl Screen { @@ -62,6 +104,26 @@ impl Screen { pwm, screen: Screens::Splash(Splash), si5351: Si5351Device::new_adafruit_module(i2c), + channels: [ + ClockChannel { + output: ClockOutput::Clk0, + freq: 1_000_000, + enabled: false, + pll: PLL::A, + }, + ClockChannel { + output: ClockOutput::Clk1, + freq: 1_000_000, + enabled: false, + pll: PLL::A, + }, + ClockChannel { + output: ClockOutput::Clk2, + freq: 1_000_000, + enabled: false, + pll: PLL::A, + }, + ], } } @@ -110,16 +172,27 @@ impl Screen { match &self.screen { Screens::Splash(splash) => splash.draw(&mut self.lcd), - Screens::Home(home) => home.draw(&mut self.lcd), + Screens::Home(home) => home.draw(&mut self.lcd, self.channels), Screens::Setup(setup) => setup.draw(&mut self.lcd), + Screens::Channel(channel) => channel.draw(&mut self.lcd), } } pub fn input(&mut self, input: &Input) { - match self.screen.input(input) { + match self.screen.input(input, self.channels) { Event::Screen(screen) => self.screen = screen, Event::Backlight(backlight) => self.set_backlight(backlight), Event::Contrast(contrast) => self.lcd.set_contrast(contrast), + Event::Channel(channel) => { + match channel.output { + ClockOutput::Clk0 => self.channels[0] = channel, + ClockOutput::Clk1 => self.channels[1] = channel, + ClockOutput::Clk2 => self.channels[2] = channel, + _ => unimplemented!(), + } + + channel.update(&mut self.si5351); + } Event::None => {} }