fartrx-firmware/src/ui.rs

262 lines
7.6 KiB
Rust

use stm32f4xx_hal::{
gpio::{self, Alternate, Input, Output, PushPull},
i2c::I2c,
pac::{I2C1, SPI1, SYST, TIM2},
prelude::*,
qei, rcc,
spi::{self, Spi},
};
use st7735_lcd::{Orientation, ST7735};
use embedded_graphics::{
mono_font::MonoTextStyle,
pixelcolor::Rgb565,
prelude::*,
primitives::rectangle::Rectangle,
primitives::{Line, PrimitiveStyle},
text::Text,
};
use profont::PROFONT_14_POINT;
use microfft::complex::cfft_128;
use num::Complex;
use num_traits::{float::Float, Pow};
use core::fmt::Write;
use crate::si5153;
type EncoderA = gpio::Pin<'A', 0, Input>;
type EncoderB = gpio::Pin<'A', 1, Input>;
type EncoderButton = gpio::Pin<'A', 5, Input>;
type DisplaySPI = Spi<SPI1>;
type DisplayRST = gpio::Pin<'A', 11, Output>;
type DisplayDC = gpio::Pin<'A', 12, Output>;
type DisplayLed = gpio::Pin<'A', 10, Output<PushPull>>;
type DisplayCS = gpio::Pin<'A', 15, Output<PushPull>>;
type DisplaySCK = gpio::Pin<'B', 3, Alternate<5>>;
type DisplayMOSI = gpio::Pin<'B', 5, Alternate<5>>;
pub struct UI {
disp_led: DisplayLed,
disp_cs: DisplayCS,
disp: ST7735<DisplaySPI, DisplayDC, DisplayRST>,
row_pos: u16,
row_buffer: [Complex<f32>; 128],
row_buffer_count: usize,
encoder: qei::Qei<TIM2>,
encoder_button: EncoderButton,
last_encoder_button: bool,
last_encoder_pos: u32,
carrier_freq: u32,
cursor_pos: u32,
initial_render: bool,
}
impl UI {
pub fn setup(
clocks: &rcc::Clocks,
enc_a: EncoderA,
enc_b: EncoderB,
tim2: TIM2,
encoder_button: EncoderButton,
disp_rst: DisplayRST,
disp_dc: DisplayDC,
mut disp_led: DisplayLed,
mut disp_cs: DisplayCS,
disp_sck: DisplaySCK,
disp_mosi: DisplayMOSI,
spi1: SPI1,
syst: SYST,
) -> UI {
let encoder = qei::Qei::new(tim2, (enc_a, enc_b));
defmt::info!("[UI] Encoder Setup done");
disp_led.set_high();
disp_cs.set_low();
let spi1 = Spi::new(
spi1,
(disp_sck, spi::NoMiso::new(), disp_mosi),
spi::Mode {
polarity: spi::Polarity::IdleLow,
phase: spi::Phase::CaptureOnFirstTransition,
},
16.MHz(),
&clocks,
);
let mut disp = ST7735::new(spi1, disp_dc, disp_rst, true, false, 160, 128);
let mut delay = syst.delay(&clocks);
disp.init(&mut delay).unwrap();
disp.set_orientation(&Orientation::Landscape).unwrap();
disp.clear(Rgb565::BLACK).unwrap();
defmt::info!("[UI] Display setup done");
UI {
disp_led,
disp_cs,
disp,
row_pos: 0,
row_buffer: [Complex::<f32>::new(0.0, 0.0); 128],
row_buffer_count: 0,
encoder,
encoder_button: encoder_button,
last_encoder_pos: 0,
last_encoder_button: false,
carrier_freq: 7_100_000,
cursor_pos: 3,
initial_render: true,
}
}
pub fn update_display(
&mut self,
mut row: [Complex<f32>; 128],
pll: &mut si5153::Si5153<I2c<I2C1>>,
i2c: &mut I2c<I2C1>,
) {
let buffers_per_row = 4;
let row = cfft_128(&mut row);
for idx in 0..row.len() {
self.row_buffer[idx] += row[idx] / buffers_per_row as f32;
}
self.row_buffer_count += 1;
if self.row_buffer_count > buffers_per_row {
self.row_buffer_count = 0;
let gradient = colorous::TURBO;
for idx in 0..128 {
let intens: f32 = self.row_buffer[idx].re.pow(2) + self.row_buffer[idx].im.pow(2);
let log_intens = intens.log10() / 2.0 * 10.0;
let norm_intens = (log_intens + 18.0) / 18.0;
let color = gradient.eval_continuous(norm_intens as f64);
let x = if idx < 64 { 64 + idx } else { idx - 64 };
Pixel(
Point::new(32 + x as i32, 28 + self.row_pos as i32),
Rgb565::new(color.r >> 3, color.g >> 2, color.b >> 3),
)
.draw(&mut self.disp)
.unwrap();
self.row_buffer[idx] = Complex::new(0.0, 0.0);
}
self.row_pos = (self.row_pos + 1) % 100;
Line::new(
Point::new(32, 28 + self.row_pos as i32),
Point::new(159, 28 + self.row_pos as i32),
)
.into_styled(PrimitiveStyle::with_stroke(Rgb565::BLACK, 1))
.draw(&mut self.disp)
.unwrap();
defmt::info!("Position is {}", self.row_pos);
}
if self.initial_render {
Rectangle::new(Point::new(0, 0), Size::new(30, 24))
.into_styled(PrimitiveStyle::with_fill(Rgb565::GREEN))
.draw(&mut self.disp)
.unwrap();
let text_style = MonoTextStyle::new(&PROFONT_14_POINT, Rgb565::BLACK);
Text::new("RX", Point::new(6, 16), text_style)
.draw(&mut self.disp)
.unwrap();
}
let encoder_pos = self.encoder.count();
let diff = encoder_pos.wrapping_sub(self.last_encoder_pos) as i32 / 4;
if diff != 0 || self.initial_render {
if diff != 0 {
let increment = 10.pow(self.cursor_pos);
self.carrier_freq = (self.carrier_freq as i32 + diff * increment) as u32;
pll.set_pll_freq(i2c, si5153::PLL::A, self.carrier_freq * 100);
pll.set_ms_freq(i2c, si5153::Multisynth::MS0, self.carrier_freq);
pll.set_ms_freq(i2c, si5153::Multisynth::MS1, self.carrier_freq);
}
Rectangle::new(Point::new(32, 0), Size::new(160, 18))
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(&mut self.disp)
.unwrap();
let freq_str = format_frequency(self.carrier_freq);
let text_style = MonoTextStyle::new(&PROFONT_14_POINT, Rgb565::WHITE);
Text::new(&freq_str, Point::new(32, 16), text_style)
.draw(&mut self.disp)
.unwrap();
self.last_encoder_pos = encoder_pos;
defmt::info!("Carrier freq is {}", self.carrier_freq);
}
let encoder_button = self.encoder_button.is_high();
if encoder_button && !self.last_encoder_button {
self.cursor_pos += 1;
if self.cursor_pos >= 6 {
self.cursor_pos = 0;
}
Rectangle::new(Point::new(32, 18), Size::new(160, 2))
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(&mut self.disp)
.unwrap();
let str_pos = if self.cursor_pos >= 3 {
self.cursor_pos + 1
} else {
self.cursor_pos
};
Rectangle::new(
Point::new(32 + 90 - 10 * str_pos as i32, 18),
Size::new(8, 2),
)
.into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE))
.draw(&mut self.disp)
.unwrap();
}
self.last_encoder_button = encoder_button;
self.initial_render = false;
}
}
fn format_frequency(freq: u32) -> arrayvec::ArrayString<10> {
let hz = freq % 1000;
let rest = freq / 1000;
let khz = rest % 1000;
let rest = rest / 1000;
let mhz = rest % 1000;
let mut freq_str = arrayvec::ArrayString::new();
write!(freq_str, "{:02}.{:03}.{:03}", mhz, khz, hz).unwrap();
freq_str
}