aboutsummaryrefslogtreecommitdiffstats
path: root/sw/dart-70/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'sw/dart-70/src/main.rs')
-rw-r--r--sw/dart-70/src/main.rs511
1 files changed, 511 insertions, 0 deletions
diff --git a/sw/dart-70/src/main.rs b/sw/dart-70/src/main.rs
new file mode 100644
index 0000000..425f1c6
--- /dev/null
+++ b/sw/dart-70/src/main.rs
@@ -0,0 +1,511 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2023 Matthias P. Braendli
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#![no_main]
+#![no_std]
+
+use core::mem::MaybeUninit;
+use cortex_m_rt::ExceptionFrame;
+use cortex_m_semihosting::hprintln;
+use panic_semihosting as _;
+
+use stm32f1xx_hal::{
+ prelude::*,
+ adc,
+ pac,
+ pac::interrupt,
+ i2c,
+ gpio,
+ gpio::PinState,
+ timer::{CounterHz, Timer, Event},
+ qei::QeiOptions,
+};
+
+use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780};
+
+pub mod ui;
+pub mod cw;
+pub mod state;
+pub mod si_clock;
+pub mod log10f;
+
+use state::*;
+
+const TICKS_PER_SECOND: u32 = 100;
+
+struct SharedWithISR {
+ state: State,
+ last_sequence_state_change: u32,
+ cw_ptt_timestamp: u32,
+ cw_key_n: gpio::gpioa::PA15<gpio::Output<gpio::OpenDrain>>,
+ ui: ui::UI,
+ cw_pwm: cw::CWPWM,
+ cw_keyer: cw::Keyer,
+ cw_paddle_tip: gpio::gpiob::PB8<gpio::Input<gpio::PullUp>>,
+ cw_paddle_ring: gpio::gpiob::PB9<gpio::Input<gpio::PullUp>>,
+ en_rx: gpio::gpiob::PB3<gpio::Output<gpio::PushPull>>,
+ en_tx: gpio::gpiob::PB5<gpio::Output<gpio::PushPull>>,
+ mute_spkr: gpio::gpioa::PA2<gpio::Output<gpio::PushPull>>,
+ mute_micn: gpio::gpioa::PA1<gpio::Output<gpio::PushPull>>,
+ led: gpio::gpiob::PB14<gpio::Output<gpio::OpenDrain>>,
+}
+
+static mut SHARED: MaybeUninit<SharedWithISR> = MaybeUninit::uninit();
+static mut CLOCK_TIMER: MaybeUninit<CounterHz<pac::TIM2>> = MaybeUninit::uninit();
+static mut TICK_COUNTER: MaybeUninit<u32> = MaybeUninit::uninit();
+
+fn ticks_now() -> u32 {
+ cortex_m::interrupt::free(|_cs| unsafe { *TICK_COUNTER.as_ptr() })
+}
+
+fn get_state_copy() -> State {
+ cortex_m::interrupt::free(|_cs| unsafe {
+ (*SHARED.as_ptr()).state.clone()
+ })
+}
+
+#[cortex_m_rt::entry]
+fn main() -> ! {
+ let cp = cortex_m::Peripherals::take().unwrap();
+ let dp = pac::Peripherals::take().unwrap();
+
+ let mut flash = dp.FLASH.constrain();
+ let rcc = dp.RCC.constrain();
+ let mut afio = dp.AFIO.constrain();
+ let clocks = rcc.cfgr
+ .use_hse(16.MHz())
+ .sysclk(32.MHz())
+ .pclk1(24.MHz())
+ .adcclk(2.MHz())
+ .freeze(&mut flash.acr);
+ let mut delay = cp.SYST.delay(&clocks);
+
+ delay.delay_ms(200u16);
+
+ let mut gpioa = dp.GPIOA.split();
+ let mut gpiob = dp.GPIOB.split();
+ let mut gpioc = dp.GPIOC.split();
+
+ // Buttons as analog inputs (multi-level)
+ let mic_ptt = gpioa.pa3.into_floating_input(&mut gpioa.crl);
+ let vox_ptt_n = gpioa.pa0.into_pull_up_input(&mut gpioa.crl);
+ let btn0 = gpiob.pb1.into_floating_input(&mut gpiob.crl); // BTN0 Button A
+ let btn1 = gpiob.pb0.into_floating_input(&mut gpiob.crl); // BTN1 Button B
+ let btn2 = gpiob.pb12.into_floating_input(&mut gpiob.crh); // BTN2 Button C
+ let btn3 = gpiob.pb13.into_floating_input(&mut gpiob.crh); // BTN3 Button D
+ let pc15 = gpioc.pc15.into_floating_input(&mut gpioc.crh); // ENC BTN
+ let ui = ui::UI::new(mic_ptt, vox_ptt_n, btn0, btn1, btn2, btn3, pc15);
+
+ let cw_pwm = {
+ let pa8 = gpioa.pa8.into_alternate_push_pull(&mut gpioa.crh); // CW PWM output using TIM1 Ch1
+ let pwm = dp.TIM1.pwm_hz(pa8, &mut afio.mapr, cw::SIDETONE_FREQ.Hz(), &clocks);
+ let channel = pwm.split();
+ cw::CWPWM::new(channel)
+ };
+
+ let cw_paddle_tip = gpiob.pb8.into_pull_up_input(&mut gpiob.crh); // CW paddle tip
+ let cw_paddle_ring = gpiob.pb9.into_pull_up_input(&mut gpiob.crh); // CW paddle ring
+
+ let mut s_meter = gpioa.pa5.into_analog(&mut gpioa.crl);
+ let mut adc2 = adc::Adc::adc2(dp.ADC2, clocks);
+ let mut last_s_meter_update_time = 0;
+
+ // Configure PB14 as output. (LED)
+ let mut led = gpiob.pb14.into_open_drain_output(&mut gpiob.crh);
+ led.set_low();
+
+ let (pa15, pb3, _pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4);
+ let cw_key_n = pa15.into_open_drain_output_with_state(&mut gpioa.crh, PinState::High);
+
+ let en_rx = pb3.into_push_pull_output_with_state(&mut gpiob.crl, PinState::Low);
+ let en_tx = gpiob.pb5.into_push_pull_output_with_state(&mut gpiob.crl, PinState::Low);
+
+ let mute_spkr = gpioa.pa2.into_push_pull_output_with_state(&mut gpioa.crl, PinState::Low);
+ let mute_micn = gpioa.pa1.into_push_pull_output_with_state(&mut gpioa.crl, PinState::Low);
+
+ let c1 = gpioa.pa6;
+ let c2 = gpioa.pa7;
+
+ let qei = Timer::new(dp.TIM3, &clocks)
+ .qei((c1, c2), &mut afio.mapr, QeiOptions::default());
+
+ // Configure I2C2 for display
+ let scl2 = gpiob.pb10.into_alternate_open_drain(&mut gpiob.crh);
+ let sda2 = gpiob.pb11.into_alternate_open_drain(&mut gpiob.crh);
+
+ let i2c2 = i2c::BlockingI2c::i2c2(
+ dp.I2C2,
+ (scl2, sda2),
+ i2c::Mode::Fast {
+ frequency: 400_000.Hz(),
+ duty_cycle: i2c::DutyCycle::Ratio2to1,
+ },
+ clocks,
+ /* start_timeout_us */ 1000,
+ /* start_retries */ 10,
+ /* addr_timeout_us */ 1000,
+ /* data_timeout_us */ 1000,
+ );
+
+ let i2c2_busmanager = shared_bus::BusManagerSimple::new(i2c2);
+
+ const I2C_ADDRESS: u8 = 0b010_0000; // MCP23008, depending on solder bridges
+ let mut lcd = match HD44780::new_i2c_mcp23008(i2c2_busmanager.acquire_i2c(), I2C_ADDRESS, &mut delay) {
+ Ok(lcd) => lcd,
+ Err(_) => panic!("HD44780 init fail"),
+ };
+
+ lcd.reset(&mut delay).unwrap();
+ lcd.clear(&mut delay).unwrap();
+ lcd.set_display_mode(
+ DisplayMode {
+ display: Display::On,
+ cursor_visibility: Cursor::Invisible,
+ cursor_blink: CursorBlink::Off,
+ },
+ &mut delay).unwrap();
+ lcd.set_cursor_pos(0, &mut delay).unwrap();
+ lcd.write_str(" HB9EGM ", &mut delay).unwrap();
+ lcd.set_cursor_pos(40, &mut delay).unwrap();
+ lcd.write_str(" DART-70 2023 ", &mut delay).unwrap();
+ delay.delay_ms(1_500u16);
+
+ // Configure I2C1 to be used for Si5351
+ let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
+ let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);
+
+ let i2c = i2c::BlockingI2c::i2c1(
+ dp.I2C1,
+ (scl, sda),
+ &mut afio.mapr,
+ i2c::Mode::Standard {
+ frequency: 100_000.Hz(),
+ },
+ clocks,
+ /* start_timeout_us */ 1000,
+ /* start_retries */ 10,
+ /* addr_timeout_us */ 1000,
+ /* data_timeout_us */ 1000,
+ );
+ let i2c_busmanager = shared_bus::BusManagerSimple::new(i2c);
+
+
+ let mut siclock = {
+ let shared = unsafe { &mut *SHARED.as_mut_ptr() };
+ *shared = SharedWithISR {
+ state : State::new(),
+ last_sequence_state_change : 0,
+ cw_ptt_timestamp : 0,
+ cw_key_n,
+ ui,
+ cw_pwm,
+ cw_keyer : cw::Keyer::new(12, TICKS_PER_SECOND),
+ cw_paddle_tip, cw_paddle_ring, en_rx, en_tx, mute_spkr, mute_micn, led
+ };
+
+ si_clock::SiClock::new(i2c_busmanager.acquire_i2c(), shared.state.bfo(), shared.state.vfo())
+ };
+
+ let mut last_s_meter_value = 0;
+ ui::update_disp(&mut lcd, &get_state_copy(), &mut delay, last_s_meter_value, false);
+
+ let mut last_encoder_count = qei.count();
+
+ {
+ let ticks = unsafe { &mut *TICK_COUNTER.as_mut_ptr() };
+ *ticks = 0;
+ }
+
+ {
+ let timer = unsafe { &mut *CLOCK_TIMER.as_mut_ptr() };
+ *timer = Timer::new(dp.TIM2, &clocks).counter_hz();
+ timer.start(TICKS_PER_SECOND.Hz()).unwrap();
+ timer.listen(Event::Update);
+ }
+
+ unsafe { pac::NVIC::unmask(pac::Interrupt::TIM2); }
+
+ let mut last_disp_update_counter = 1;
+ let mut previous_vfo = 0;
+ let mut previous_bfo = 0;
+
+ loop {
+ let mut update_disp_required = false;
+ let mut bfo_tune_fail = false;
+
+ let state = get_state_copy();
+
+ let encoder_count : u16 = qei.count();
+ if encoder_count != last_encoder_count {
+ let delta = encoder_count.wrapping_sub(last_encoder_count);
+ let delta = if delta > 0x7FFF { delta as i32 - 0x10000 } else { delta as i32 };
+
+ let require_bfo_update = cortex_m::interrupt::free(|_cs| {
+ let shared = unsafe { &mut *SHARED.as_mut_ptr() };
+ let r = shared.ui.update_encoder(&mut shared.state, delta);
+ if let Mode::CW(CWMode::Iambic) = shared.state.mode {
+ shared.cw_keyer.set_speed(shared.state.cw_wpm, TICKS_PER_SECOND)
+ }
+ r
+ });
+
+ if require_bfo_update {
+ bfo_tune_fail = !siclock.set_bfo(state.bfo()).is_ok();
+ }
+ siclock.set_vfo(state.vfo());
+ update_disp_required = true;
+ }
+
+ let bfo = state.bfo();
+ if previous_bfo != bfo {
+ bfo_tune_fail = !siclock.set_bfo(bfo).is_ok();
+ }
+ previous_bfo = bfo;
+
+ let vfo = state.vfo();
+ if previous_vfo != vfo {
+ siclock.set_vfo(vfo);
+ }
+ previous_vfo = vfo;
+
+ let s_meter_adc_value: u16 = adc2.read(&mut s_meter).unwrap();
+ let s_meter_value = s_meter_from_adc(s_meter_adc_value);
+ let t_now = ticks_now();
+ if last_s_meter_update_time + 10 < t_now {
+ update_disp_required |= s_meter_value != last_s_meter_value;
+ last_s_meter_value = s_meter_value;
+ last_s_meter_update_time = t_now;
+ }
+
+ if last_disp_update_counter != state.update_disp_counter {
+ update_disp_required = true;
+ last_disp_update_counter = state.update_disp_counter;
+ }
+
+ if update_disp_required {
+ let state = get_state_copy();
+ ui::update_disp(&mut lcd, &state, &mut delay, s_meter_value, bfo_tune_fail);
+ }
+
+ last_encoder_count = encoder_count;
+
+ cortex_m::asm::wfi();
+ }
+}
+
+fn s_meter_from_adc(adc : u16) -> u8 {
+ // Avoid 0 because of log10
+ let adc = f32::from(if adc == 0 { 1 } else { adc });
+ // ADC is 12-bit, convert to dB full-scale
+ let adc_db = 10f32 * log10f::log10f(adc / 4092f32);
+
+ /* Hand-calibrated lookup table */
+ if adc_db <= -35f32 {
+ 1
+ }
+ else if adc_db <= -20f32 {
+ 4
+ }
+ else if adc_db <= -12f32 {
+ 5
+ }
+ else if adc_db <= -8f32 {
+ 6
+ }
+ else if adc_db <= -7f32 {
+ 7
+ }
+ else {
+ 9
+ }
+}
+
+#[interrupt]
+fn TIM2() {
+ let timer = unsafe { &mut *CLOCK_TIMER.as_mut_ptr() };
+ timer.clear_interrupt(Event::Update);
+
+ let ticks = unsafe { &mut *TICK_COUNTER.as_mut_ptr() };
+ *ticks += 1;
+
+ let mut shared = unsafe { &mut *SHARED.as_mut_ptr() };
+ let button_result = shared.ui.handle_buttons(&mut shared.state);
+
+ if button_result.display_update {
+ shared.state.update_disp_counter += 1;
+ }
+
+ let cw_paddle_tip_low = shared.cw_paddle_tip.is_low();
+ let cw_paddle_ring_low = shared.cw_paddle_ring.is_low();
+
+ if cw_paddle_tip_low || cw_paddle_ring_low {
+ shared.state.send_tone = false;
+ }
+
+ let cw_ptt_delay : u32 = TICKS_PER_SECOND * 800 / 1000;
+ let cw_ptt = shared.state.send_tone ||
+ match shared.state.mode {
+ Mode::CW(_) => {
+ if cw_paddle_tip_low || cw_paddle_ring_low {
+ shared.cw_ptt_timestamp = *ticks;
+ true
+ }
+ else {
+ shared.cw_ptt_timestamp + cw_ptt_delay > *ticks
+ }
+ },
+ _ => false,
+ };
+
+ let cw_beep = shared.state.send_tone ||
+ match shared.state.mode {
+ Mode::CW(CWMode::StraightKey) => cw_paddle_tip_low,
+ Mode::CW(CWMode::Iambic) => shared.cw_keyer.tick(*ticks, cw_paddle_tip_low, cw_paddle_ring_low),
+ _ => false,
+ };
+
+ let next_state = match shared.state.sequence_state {
+ SequenceState::Rx => {
+ shared.mute_spkr.set_low();
+ shared.mute_micn.set_low();
+ shared.en_rx.set_high();
+ shared.en_tx.set_low();
+ if button_result.ptt || cw_ptt {
+ SequenceState::MutingSpkr
+ }
+ else {
+ SequenceState::Rx
+ }
+ },
+ SequenceState::MutingSpkr => {
+ shared.mute_spkr.set_high();
+ shared.mute_micn.set_low();
+ shared.en_rx.set_high();
+ shared.en_tx.set_low();
+ if button_result.ptt {
+ SequenceState::SwitchingSSB
+ }
+ else if cw_ptt {
+ SequenceState::SwitchingCW
+ }
+ else {
+ SequenceState::Rx
+ }
+ },
+ SequenceState::SwitchingSSB => {
+ shared.mute_spkr.set_high();
+ shared.en_rx.set_low();
+ shared.en_tx.set_low();
+ shared.mute_micn.set_high();
+
+ if button_result.ptt {
+ SequenceState::TxSSB
+ }
+ else {
+ SequenceState::Rx
+ }
+ },
+ SequenceState::SwitchingCW => {
+ shared.mute_spkr.set_high();
+ shared.en_rx.set_low();
+ shared.en_tx.set_low();
+ shared.mute_micn.set_low();
+ if cw_ptt {
+ SequenceState::TxCW
+ }
+ else {
+ SequenceState::Rx
+ }
+ },
+ SequenceState::TxSSB => {
+ shared.en_rx.set_low();
+ shared.en_tx.set_high();
+
+ if button_result.ptt {
+ SequenceState::TxSSB
+ }
+ else {
+ SequenceState::SwitchingSSB
+ }
+ },
+ SequenceState::TxCW => {
+ shared.en_rx.set_low();
+ shared.en_tx.set_high();
+
+ if cw_ptt {
+ SequenceState::TxCW
+ }
+ else {
+ SequenceState::SwitchingCW
+ }
+ },
+ };
+
+ match shared.state.sequence_state {
+ SequenceState::TxCW => {
+ if cw_beep {
+ shared.led.set_low();
+ shared.cw_pwm.on();
+ shared.cw_key_n.set_low();
+ }
+ else {
+ shared.led.set_high();
+ shared.cw_pwm.off();
+ shared.cw_key_n.set_high();
+ }
+ },
+ _ => {
+ shared.led.set_high();
+ shared.cw_pwm.off();
+ shared.cw_key_n.set_high();
+ },
+ }
+
+ const SWITCHING_DELAY : u32 = TICKS_PER_SECOND * 80 / 1000;
+ if shared.state.sequence_state != next_state &&
+ shared.last_sequence_state_change + SWITCHING_DELAY <= *ticks {
+ shared.state.sequence_state = next_state;
+ shared.last_sequence_state_change = *ticks;
+ }
+}
+
+#[allow(non_snake_case)]
+#[cortex_m_rt::exception]
+unsafe fn HardFault(ef: &ExceptionFrame) -> ! {
+ let periph = unsafe { cortex_m::Peripherals::steal() };
+ let hfsr = periph.SCB.hfsr.read();
+ let cfsr = periph.SCB.cfsr.read();
+
+ hprintln!("Hardfault {:x} {:x} at {:x}\n", hfsr, cfsr, ef.pc());
+ cortex_m::asm::bkpt();
+ loop { }
+}
+
+#[allow(non_snake_case)]
+#[cortex_m_rt::exception]
+unsafe fn DefaultHandler(irqn: i16) {
+ hprintln!("Unhandled exception (IRQn = {})", irqn);
+ cortex_m::asm::bkpt();
+ loop { }
+}