aboutsummaryrefslogtreecommitdiffstats
path: root/sw
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2020-07-17 15:39:45 +0200
committerMatthias P. Braendli <matthias.braendli@mpb.li>2020-07-17 15:39:45 +0200
commit9be7009e6eed7e4d0c54c07a919b46642f4b3a6f (patch)
tree84f2f6744062ed99d3392016dfad80c3fb2af376 /sw
parentcd3d884aabc50fa577d0914bbd2cc8299dd7ae11 (diff)
downloadpicardy-9be7009e6eed7e4d0c54c07a919b46642f4b3a6f.tar.gz
picardy-9be7009e6eed7e4d0c54c07a919b46642f4b3a6f.tar.bz2
picardy-9be7009e6eed7e4d0c54c07a919b46642f4b3a6f.zip
SW: handle buttons
Diffstat (limited to 'sw')
-rw-r--r--sw/demo1/src/lib.rs29
-rw-r--r--sw/demo1/src/main.rs169
-rw-r--r--sw/demo1/src/si_clock.rs130
-rw-r--r--sw/demo1/src/ui.rs151
-rw-r--r--sw/pio.txt36
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