fw-rust: Make Si5351 useable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

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.
This commit is contained in:
finga 2022-04-02 18:38:46 +02:00
parent 7f14974146
commit c36581e703
5 changed files with 430 additions and 119 deletions

View file

@ -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],
@ -247,7 +265,7 @@ pub const SYMBOL_TABLE: [&[&[u8]; 2]; 50] = [
&SYM_C, // 'C'
&SYM_INVALID, // 'D'
&SYM_E, // 'E'
&SYM_INVALID, // 'F'
&SYM_F, // 'F'
&SYM_G, // 'G'
&SYM_H, // 'H'
&SYM_I, // 'I'
@ -258,7 +276,7 @@ pub const SYMBOL_TABLE: [&[&[u8]; 2]; 50] = [
&SYM_N, // 'N'
&SYM_O, // 'O'
&SYM_P, // 'P'
&SYM_INVALID, // 'Q'
&SYM_Q, // 'Q'
&SYM_R, // 'R'
&SYM_S, // 'S'
&SYM_T, // 'T'
@ -272,5 +290,5 @@ pub const SYMBOL_TABLE: [&[&[u8]; 2]; 50] = [
&SYM_INVALID, // '\'
&SYM_INVALID, // ']'
&SYM_INVALID, // '^'
&SYM_INVALID, // '_'
&SYM_UNDERSCORE, // '_'
];

View file

@ -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::<MHz8>::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::<MHz8>::new();
self.move_cursor(segment, page);

View file

@ -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");
}
}
}
}

View file

@ -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");
}
}

View file

@ -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<I2c>) {
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<Output, PD5>,
screen: Screens,
si5351: Si5351Device<I2c>,
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 => {}
}