diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2020-07-17 15:39:45 +0200 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2020-07-17 15:39:45 +0200 |
commit | 9be7009e6eed7e4d0c54c07a919b46642f4b3a6f (patch) | |
tree | 84f2f6744062ed99d3392016dfad80c3fb2af376 | |
parent | cd3d884aabc50fa577d0914bbd2cc8299dd7ae11 (diff) | |
download | picardy-9be7009e6eed7e4d0c54c07a919b46642f4b3a6f.tar.gz picardy-9be7009e6eed7e4d0c54c07a919b46642f4b3a6f.tar.bz2 picardy-9be7009e6eed7e4d0c54c07a919b46642f4b3a6f.zip |
SW: handle buttons
-rw-r--r-- | sw/demo1/src/lib.rs | 29 | ||||
-rw-r--r-- | sw/demo1/src/main.rs | 169 | ||||
-rw-r--r-- | sw/demo1/src/si_clock.rs | 130 | ||||
-rw-r--r-- | sw/demo1/src/ui.rs | 151 | ||||
-rw-r--r-- | sw/pio.txt | 36 |
5 files changed, 388 insertions, 127 deletions
diff --git a/sw/demo1/src/lib.rs b/sw/demo1/src/lib.rs new file mode 100644 index 0000000..a645de6 --- /dev/null +++ b/sw/demo1/src/lib.rs @@ -0,0 +1,29 @@ +/* + The MIT License (MIT) + + Copyright (c) 2020 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_std] + +pub mod ui; +pub mod si_clock; + diff --git a/sw/demo1/src/main.rs b/sw/demo1/src/main.rs index 3f98295..4e6e5c6 100644 --- a/sw/demo1/src/main.rs +++ b/sw/demo1/src/main.rs @@ -25,12 +25,8 @@ #![no_main] #![no_std] -const REF_CLOCK : u32 = 25_000_000; - -use core::convert::TryInto; - use cortex_m_rt::ExceptionFrame; -use cortex_m_semihosting::hio; +use cortex_m_semihosting::hprintln; use panic_semihosting as _; use stm32f1xx_hal::{ @@ -43,75 +39,10 @@ use stm32f1xx_hal::{ use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin}; use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780}; -use si5351::{Si5351, Si5351Device}; - -use core::fmt::Write; - -fn gcd(x: u32, y: u32) -> u32 { - let mut x = x; - let mut y = y; - while y != 0 { - let t = y; - y = x % y; - x = t; - } - x -} - -fn clock_settings_for_pll(freq: u32, pll: u32) -> (u16, u32, u32) { - let a = pll / freq; - let b = pll - (a * freq); - let gcd = gcd(b, freq); - - let b = b / gcd; - let c = freq / gcd; - (a.try_into().unwrap(), b, c) -} - -fn clock_settings_with_pll_calculation(freq: u32) -> (u16, u8, u32, u32) { - let mut divider : u32 = 900_000_000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal - - if (divider % 2) == 1 { - divider -= 1 // Ensure an even integer division ratio - } - - let pll_freq = divider * freq; - // mult is an integer that must be in the range 15..90 - let mult = pll_freq / REF_CLOCK; - let l = pll_freq % REF_CLOCK; - - let denom = 1048575; - let num = f64::from(l) * f64::from(denom) / f64::from(REF_CLOCK); - - (divider.try_into().unwrap(), mult.try_into().unwrap(), num as u32, denom) -} - -fn print(step: usize) -> Result<(), core::fmt::Error> { - let mut stdout = match hio::hstdout() { - Ok(fd) => fd, - Err(()) => return Err(core::fmt::Error), - }; - - let language = "Rust"; - let ranking = 1; - - write!(stdout, "{}: {} on embedded is #{}!\n", step, language, ranking)?; - - Ok(()) -} - -fn set_vfo(freq: u32, siclock: &mut dyn Si5351) -{ - let (div, mult, num, denom) = clock_settings_with_pll_calculation(freq); - siclock.setup_pll(si5351::PLL::B, mult, num, denom).unwrap(); - siclock.setup_multisynth_int(si5351::Multisynth::MS0, div, si5351::OutputDivider::Div1).unwrap(); - - siclock.select_clock_pll(si5351::ClockOutput::Clk0, si5351::PLL::B); - siclock.set_clock_enabled(si5351::ClockOutput::Clk0, true); - siclock.flush_clock_control(si5351::ClockOutput::Clk0).unwrap(); -} +use demo1::{si_clock, ui}; +use core::fmt::Write; #[cortex_m_rt::entry] fn main() -> ! { @@ -121,11 +52,23 @@ fn main() -> ! { let mut flash = dp.FLASH.constrain(); let mut rcc = dp.RCC.constrain(); let mut afio = dp.AFIO.constrain(&mut rcc.apb2); - let clocks = rcc.cfgr.freeze(&mut flash.acr); + let clocks = rcc.cfgr + .adcclk(2.mhz()) + .freeze(&mut flash.acr); let mut delay = Delay::new(cp.SYST, clocks); - let gpioa = dp.GPIOA.split(&mut rcc.apb2); + let adc1 = dp.ADC1; + // Buttons as analog inputs (multi-level) let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); + let pb0 = gpiob.pb0.into_analog(&mut gpiob.crl); + let pb1 = gpiob.pb1.into_analog(&mut gpiob.crl); + let pb12 = gpiob.pb12.into_pull_up_input(&mut gpiob.crh); + let pb13 = gpiob.pb13.into_pull_up_input(&mut gpiob.crh); + let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); + let pc15 = gpioc.pc15.into_pull_up_input(&mut gpioc.crh); + let mut ui = ui::UI::new(pb0, pb1, adc1, &mut rcc.apb2, &clocks, pb12, pb13, pc15); + + let gpioa = dp.GPIOA.split(&mut rcc.apb2); // Configure PB14 as output. (LED) let mut led = gpiob.pb14.into_push_pull_output(&mut gpiob.crh); @@ -197,65 +140,47 @@ fn main() -> ! { lcd.set_cursor_pos(0, &mut delay).unwrap(); lcd.write_str("Hello, world!", &mut delay).unwrap(); - let mut siclock = Si5351Device::new(i2c_busmanager.acquire(), false, REF_CLOCK); - siclock.init(si5351::CrystalLoad::_10).unwrap(); - - // See freqplan.py for Si5351 frequency plan - // CLK1 = 116MHz - let pll_a_mult = 32; - siclock.setup_pll_int(si5351::PLL::A, 32).unwrap(); - - { - let clk1 = 116_000_000; - let (a, b, c) = clock_settings_for_pll(clk1, pll_a_mult * REF_CLOCK); - siclock.setup_multisynth(si5351::Multisynth::MS1, a, b, c, si5351::OutputDivider::Div1).unwrap(); - siclock.select_clock_pll(si5351::ClockOutput::Clk1, si5351::PLL::A); - siclock.set_clock_enabled(si5351::ClockOutput::Clk1, true); - siclock.flush_clock_control(si5351::ClockOutput::Clk1).unwrap(); - } - - { - let clk2 = 4_195_210; - let (a, b, c) = clock_settings_for_pll(clk2, pll_a_mult * REF_CLOCK); - siclock.setup_multisynth(si5351::Multisynth::MS2, a, b, c, si5351::OutputDivider::Div1).unwrap(); - siclock.select_clock_pll(si5351::ClockOutput::Clk2, si5351::PLL::A); - siclock.set_clock_enabled(si5351::ClockOutput::Clk2, true); - siclock.flush_clock_control(si5351::ClockOutput::Clk2).unwrap(); - } - - siclock.reset_pll(si5351::PLL::A).unwrap(); - - set_vfo(23_000_000, &mut siclock); - - siclock.reset_pll(si5351::PLL::B).unwrap(); - - siclock.flush_output_enabled().unwrap(); - + let mut siclock = si_clock::SiClock::new(i2c_busmanager.acquire()); lcd.set_cursor_pos(0, &mut delay).unwrap(); lcd.write_str("Clocks set. ", &mut delay).unwrap(); - let mut step = 0; - print(step).unwrap(); + hprintln!("Main loop\n").unwrap(); let mut last_encoder_count = qei.count(); loop { led.toggle().unwrap(); - lcd.set_cursor_pos(40, &mut delay).unwrap(); let mut string = arrayvec::ArrayString::<[_; 16]>::new(); let encoder_count = qei.count(); if encoder_count != last_encoder_count { let freq = 23_000_000 + encoder_count as u32; - set_vfo(freq, &mut siclock); + siclock.set_vfo(freq); - write!(string, "{} ", freq).unwrap(); + write!(string, "{:15}", freq).unwrap(); + lcd.set_cursor_pos(40, &mut delay).unwrap(); + lcd.write_str(&string, &mut delay).unwrap(); + } + + if let Some(b) = ui.read_buttons() { + let button = match b { + ui::ButtonPress::A => "A", + ui::ButtonPress::B => "B", + ui::ButtonPress::C => "C", + ui::ButtonPress::D => "D", + ui::ButtonPress::E => "E", + ui::ButtonPress::F => "F", + ui::ButtonPress::G => "G", + ui::ButtonPress::ENC => "X", + }; + + lcd.set_cursor_pos(0, &mut delay).unwrap(); + write!(string, "{:15}", button).unwrap(); lcd.write_str(&string, &mut delay).unwrap(); } - last_encoder_count = encoder_count; - step += 1; + last_encoder_count = encoder_count; } } @@ -265,24 +190,14 @@ fn HardFault(ef: &ExceptionFrame) -> ! { let hfsr = periph.SCB.hfsr.read(); let cfsr = periph.SCB.cfsr.read(); - let mut stdout = match hio::hstdout() { - Ok(fd) => fd, - Err(()) => panic!("no stdout"), - }; - - let _ = write!(stdout, "Hardfault {:x} {:x} at {:x}\n", hfsr, cfsr, ef.pc); + hprintln!("Hardfault {:x} {:x} at {:x}\n", hfsr, cfsr, ef.pc).unwrap(); cortex_m::asm::bkpt(); loop { } } #[cortex_m_rt::exception] fn DefaultHandler(irqn: i16) { - let mut stdout = match hio::hstdout() { - Ok(fd) => fd, - Err(()) => panic!("no stdout"), - }; - - let _ = write!(stdout, "Unhandled exception (IRQn = {})", irqn); + hprintln!("Unhandled exception (IRQn = {})", irqn).unwrap(); cortex_m::asm::bkpt(); loop { } } diff --git a/sw/demo1/src/si_clock.rs b/sw/demo1/src/si_clock.rs new file mode 100644 index 0000000..0bce24b --- /dev/null +++ b/sw/demo1/src/si_clock.rs @@ -0,0 +1,130 @@ +/* + The MIT License (MIT) + + Copyright (c) 2020 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. +*/ +use core::convert::TryInto; + +use si5351::{Si5351, Si5351Device}; +use embedded_hal::blocking::i2c::{WriteRead, Write}; + +const REF_CLOCK : u32 = 25_000_000; + +fn gcd(x: u32, y: u32) -> u32 { + let mut x = x; + let mut y = y; + while y != 0 { + let t = y; + y = x % y; + x = t; + } + x +} + +fn clock_settings_for_pll(freq: u32, pll: u32) -> (u16, u32, u32) { + let a = pll / freq; + let b = pll - (a * freq); + let gcd = gcd(b, freq); + + let b = b / gcd; + let c = freq / gcd; + (a.try_into().unwrap(), b, c) +} + +fn clock_settings_with_pll_calculation(freq: u32) -> (u16, u8, u32, u32) { + let mut divider : u32 = 900_000_000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal + + if (divider % 2) == 1 { + divider -= 1 // Ensure an even integer division ratio + } + + let pll_freq = divider * freq; + // mult is an integer that must be in the range 15..90 + let mult = pll_freq / REF_CLOCK; + let l = pll_freq % REF_CLOCK; + + let denom = 1048575; + let num = f64::from(l) * f64::from(denom) / f64::from(REF_CLOCK); + + (divider.try_into().unwrap(), mult.try_into().unwrap(), num as u32, denom) +} + +fn set_vfo(siclock: &mut dyn Si5351, freq: u32) +{ + let (div, mult, num, denom) = clock_settings_with_pll_calculation(freq); + + siclock.setup_pll(si5351::PLL::B, mult, num, denom).unwrap(); + siclock.setup_multisynth_int(si5351::Multisynth::MS0, div, si5351::OutputDivider::Div1).unwrap(); + siclock.select_clock_pll(si5351::ClockOutput::Clk0, si5351::PLL::B); + siclock.set_clock_enabled(si5351::ClockOutput::Clk0, true); + siclock.flush_clock_control(si5351::ClockOutput::Clk0).unwrap(); +} + +pub struct SiClock<I2C> { + siclock : Si5351Device<I2C>, +} + +impl<I2C, E> SiClock<I2C> + where + I2C: WriteRead<Error = E> + Write<Error = E>, +{ + pub fn new(i2c: I2C) -> SiClock<I2C> { + let mut siclock = Si5351Device::new(i2c, false, REF_CLOCK); + siclock.init(si5351::CrystalLoad::_10).unwrap(); + + // See freqplan.py for Si5351 frequency plan + // CLK1 = 116MHz + let pll_a_mult = 32; + siclock.setup_pll_int(si5351::PLL::A, 32).unwrap(); + + { + let clk1 = 116_000_000; + let (a, b, c) = clock_settings_for_pll(clk1, pll_a_mult * REF_CLOCK); + siclock.setup_multisynth(si5351::Multisynth::MS1, a, b, c, si5351::OutputDivider::Div1).unwrap(); + siclock.select_clock_pll(si5351::ClockOutput::Clk1, si5351::PLL::A); + siclock.set_clock_enabled(si5351::ClockOutput::Clk1, true); + siclock.flush_clock_control(si5351::ClockOutput::Clk1).unwrap(); + } + + { + let clk2 = 4_195_210; + let (a, b, c) = clock_settings_for_pll(clk2, pll_a_mult * REF_CLOCK); + siclock.setup_multisynth(si5351::Multisynth::MS2, a, b, c, si5351::OutputDivider::Div1).unwrap(); + siclock.select_clock_pll(si5351::ClockOutput::Clk2, si5351::PLL::A); + siclock.set_clock_enabled(si5351::ClockOutput::Clk2, true); + siclock.flush_clock_control(si5351::ClockOutput::Clk2).unwrap(); + } + + siclock.reset_pll(si5351::PLL::A).unwrap(); + + set_vfo(&mut siclock, 23_000_000); + + siclock.reset_pll(si5351::PLL::B).unwrap(); + + siclock.flush_output_enabled().unwrap(); + + SiClock{siclock} + } + + pub fn set_vfo(&mut self, freq: u32) { + set_vfo(&mut self.siclock, freq); + } +} diff --git a/sw/demo1/src/ui.rs b/sw/demo1/src/ui.rs new file mode 100644 index 0000000..76f89b9 --- /dev/null +++ b/sw/demo1/src/ui.rs @@ -0,0 +1,151 @@ +/* + The MIT License (MIT) + + Copyright (c) 2020 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. +*/ + +use stm32f1xx_hal::{ + prelude::*, + adc, + rcc::{APB2, Clocks}, + stm32::ADC1, + gpio::gpiob::*, + gpio::gpioc::*, + gpio::{Analog, Input, PullUp}, +}; + +use embedded_hal::digital::v2::InputPin; + +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum ButtonPress { + A, + B, + C, + D, + E, + F, + G, + ENC, +} + +pub struct UI { + btn0_hist : [u16; 3], + btn1_hist : [u16; 3], + + btn0_ch : PB1<Analog>, + btn1_ch : PB0<Analog>, + + btn2 : PB12<Input<PullUp>>, + btn3 : PB13<Input<PullUp>>, + + btn_enc : PC15<Input<PullUp>>, + + adc : adc::Adc<ADC1>, +} + +impl UI { + pub fn new(pb0: PB0<Analog>, pb1: PB1<Analog>, adc1: ADC1, mut apb2: &mut APB2, clocks: &Clocks, pb12: PB12<Input<PullUp>>, pb13: PB13<Input<PullUp>>, pc15 : PC15<Input<PullUp>>) -> UI { + + let adc1 = adc::Adc::adc1(adc1, &mut apb2, *clocks); + + UI { + btn0_hist : [4095; 3], + btn1_hist : [4095; 3], + btn0_ch : pb1, + btn1_ch : pb0, + btn2 : pb12, + btn3 : pb13, + btn_enc : pc15, + adc : adc1, + } + } + + pub fn read_buttons(&mut self) -> Option<ButtonPress> { + // Debounce BTN0 + let btn0_value: u16 = self.adc.read(&mut self.btn0_ch).unwrap(); + + self.btn0_hist[2] = self.btn0_hist[1]; + self.btn0_hist[1] = self.btn0_hist[0]; + self.btn0_hist[0] = btn0_value; + + let mut btn0 = [None; 3]; + for (i, &v) in self.btn0_hist.iter().enumerate() { + btn0[i] = + if v > 3050 { + None + } + else if v > 1650 { + Some(ButtonPress::B) + } + else if v > 675 { + Some(ButtonPress::C) + } + else { + Some(ButtonPress::D) + }; + } + + if btn0.iter().all(|&v| v != None && v == btn0[0]) { + return btn0[0]; + } + + + // Debounce BTN1 + let btn1_value: u16 = self.adc.read(&mut self.btn1_ch).unwrap(); + + self.btn1_hist[2] = self.btn1_hist[1]; + self.btn1_hist[1] = self.btn1_hist[0]; + self.btn1_hist[0] = btn1_value; + + + let mut btn1 = [None; 3]; + for (i, &v) in self.btn1_hist.iter().enumerate() { + btn1[i] = + if v > 3050 { + None + } + else if v > 675 { + Some(ButtonPress::F) + } + else { + Some(ButtonPress::A) + }; + } + + if btn1.iter().all(|&v| v != None && v == btn1[0]) { + return btn1[0]; + } + + if self.btn2.is_low().unwrap() { + return Some(ButtonPress::E) + } + + if self.btn3.is_low().unwrap() { + return Some(ButtonPress::G) + } + + if self.btn_enc.is_low().unwrap() { + return Some(ButtonPress::ENC) + } + + None + } +} diff --git a/sw/pio.txt b/sw/pio.txt new file mode 100644 index 0000000..e8c85e8 --- /dev/null +++ b/sw/pio.txt @@ -0,0 +1,36 @@ +STM32F103C8Tx medium-density LQFP48 + +Pin mapping: see datasheet Table 5 + +## GPIO inputs + +Analog multi-level button inputs + * BTN0 PB1 + * BTN1 PB0 + +Digital buttons + * BTN2 PB12 + * BTN3 PB13 + +Digital rotary encoder + * ENC_A PA6 (TIM3 CH1) + * ENC_B PA7 (TIM3 CH2) + * ENC_BTN PC15 (limited current) + +CW paddle + * CW_TIP PB8 + * CW_RING PB9 + +Microphone switches + * SW1 PA3 + * SW2 PA4 + +## I2C1 for Si5351A-B-GT + + * SCL PB6 default alternate function + * SDA PB7 default alternate function + +## I2C2 for Display, on address 0100<A2><A1><A0> depending on the solder bridges. + + * SCL PB10 default alternate function + * SDA PB11 default alternate function |