From 2240e0b6683bcb69b1f477f4449de9cc17314ccd Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sat, 25 Feb 2023 11:17:03 +0100 Subject: Create dart-70 firmware --- sw/dart-70/src/main.rs | 511 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100644 sw/dart-70/src/main.rs (limited to 'sw/dart-70/src/main.rs') 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>, + ui: ui::UI, + cw_pwm: cw::CWPWM, + cw_keyer: cw::Keyer, + cw_paddle_tip: gpio::gpiob::PB8>, + cw_paddle_ring: gpio::gpiob::PB9>, + en_rx: gpio::gpiob::PB3>, + en_tx: gpio::gpiob::PB5>, + mute_spkr: gpio::gpioa::PA2>, + mute_micn: gpio::gpioa::PA1>, + led: gpio::gpiob::PB14>, +} + +static mut SHARED: MaybeUninit = MaybeUninit::uninit(); +static mut CLOCK_TIMER: MaybeUninit> = MaybeUninit::uninit(); +static mut TICK_COUNTER: MaybeUninit = 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 { } +} -- cgit v1.2.3