Skip to content
Snippets Groups Projects
main.rs 6.62 KiB
Newer Older
Stephen D's avatar
Stephen D committed
#![no_main]
#![no_std]

// use panic_halt as _;
use panic_semihosting as _;

mod gps;

#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [USART2, USART6])]
mod app {
    use embedded_graphics::{pixelcolor::BinaryColor, prelude::*};
    use hal::{
        gpio,
        i2c::I2c,
        pac::{I2C1, SPI1, TIM2, TIM5, USART1},
        prelude::*,
        rcc::{Clocks, Rcc},
        serial::{self, Serial},
        spi::{Mode, Phase, Polarity, Spi},
        timer::Delay,
    };
    use half::f16;
    use ham_cats::{
        packet::Packet,
        whisker::{Arbitrary, Gps, Route},
    };
    use rf4463::{config::RADIO_CONFIG_CATS, Rf4463};
    use ssd1306::{
        mode::{BufferedGraphicsMode, DisplayConfig},
        prelude::*,
        size::DisplaySize128x64,
        I2CDisplayInterface, Ssd1306,
    };
    use stm32f4xx_hal as hal;
    use systick_monotonic::{ExtU64, Systick};

    use crate::gps::GpsModule;

    const MAX_PACKET_LEN: usize = 8191;
    const SYS_CLK: u32 = 100_000_000;

    const MODE: Mode = Mode {
        polarity: Polarity::IdleLow,
        phase: Phase::CaptureOnFirstTransition,
    };

    type Radio = Rf4463<
        Spi<SPI1>,
        gpio::Pin<'B', 12, gpio::Output>,
        gpio::Pin<'A', 8, gpio::Output>,
        Delay<TIM5, 1000000>,
    >;

    type Lcd = Ssd1306<
        I2CInterface<I2c<I2C1>>,
        DisplaySize128x64,
        BufferedGraphicsMode<DisplaySize128x64>,
    >;

    #[monotonic(binds = SysTick, default = true)]
    type MyMono = Systick<100>;

    #[local]
    struct Local {
        radio_irq: gpio::Pin<'B', 13, gpio::Input>,
        red: gpio::Pin<'B', 15, gpio::Output>,
        green: gpio::Pin<'B', 14, gpio::Output>,
        lcd: Lcd,
        gps_serial: Serial<USART1>,
        buf: &'static mut [u8; MAX_PACKET_LEN],
    }

    #[shared]
    struct Shared {
        radio: Radio,
        gps: GpsModule,
    }

    fn setup_clocks(rcc: Rcc) -> Clocks {
        rcc.cfgr
            .use_hse(25.MHz())
            .hclk(24u32.MHz())
            .sysclk(SYS_CLK.Hz())
            .pclk1(24.MHz())
            .pclk2(24.MHz())
            .freeze()
    }

    #[init]
    fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) {
        let mut dp = ctx.device;
        let gpioa = dp.GPIOA.split();
        let gpiob = dp.GPIOB.split();

        let clocks = setup_clocks(dp.RCC.constrain());
        let mut sys_cfg = dp.SYSCFG.constrain();

        // setup 4463 spi
        let mosi = gpiob.pb5.into_alternate();
        let miso = gpiob.pb4.into_alternate();
        let sclk = gpiob.pb3.into_alternate();

        let sdn = gpiob.pb12.into_push_pull_output();
        let cs = gpioa.pa8.into_push_pull_output();
        let mut radio_irq = gpiob.pb13.into_pull_up_input();

        let spi = Spi::new(dp.SPI1, (sclk, miso, mosi), MODE, 10.MHz(), &clocks);

        let delay = dp.TIM5.delay_us(&clocks);
        let mut radio = Rf4463::new(spi, sdn, cs, delay, &mut RADIO_CONFIG_CATS.clone()).unwrap();
        // sets us up for the default CATS frequency, 430.500 MHz
        radio.set_channel(20);

        radio_irq.make_interrupt_source(&mut sys_cfg);
        radio_irq.enable_interrupt(&mut dp.EXTI);
        radio_irq.trigger_on_edge(&mut dp.EXTI, gpio::Edge::Falling);

        // set up display
        let scl = gpiob.pb6.into_alternate().set_open_drain();
        let sda = gpiob.pb7.into_alternate().set_open_drain();

        let i2c = I2c::new(dp.I2C1, (scl, sda), 400.kHz(), &clocks);

        let interface = I2CDisplayInterface::new(i2c);
        let mut lcd = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate180)
            .into_buffered_graphics_mode();
        lcd.init().unwrap();
        lcd.clear(BinaryColor::Off).unwrap();

        lcd.flush().unwrap();

        // setup LEDs
        let mut green = gpiob.pb14.into_push_pull_output();
        let mut red = gpiob.pb15.into_push_pull_output();

        // Setup GPS serial
        let pins = (gpioa.pa9, gpioa.pa10);
        let mut gps_serial = Serial::new(
            dp.USART1,
            pins,
            serial::Config::default()
                .baudrate(9600.bps())
                .wordlength_8(),
            &clocks,
        )
        .unwrap()
        .with_u8_data();
        gps_serial.listen(serial::Event::Rxne);

        red.set_low();
        green.set_low();

        let buf = cortex_m::singleton!(: [u8; MAX_PACKET_LEN] = [0; MAX_PACKET_LEN]).unwrap();

        transmit_position::spawn().unwrap();

        (
            Shared {
                radio,
                gps: GpsModule::new(),
            },
            Local {
                radio_irq,
                red,
                green,
                lcd,
                gps_serial,
                buf,
            },
            init::Monotonics(Systick::new(ctx.core.SYST, SYS_CLK)),
        )
    }

    #[task(local = [buf], shared = [radio, gps])]
    fn transmit_position(mut ctx: transmit_position::Context) {
        let pos = ctx.shared.gps.lock(|gps| gps.pos());

        let mut cats: Packet<MAX_PACKET_LEN> = Packet::default();
        cats.add_identification(ham_cats::whisker::Identification {
            icon: 0,
            callsign: "VE9QLE".try_into().unwrap(),
            ssid: 12,
        })
        .unwrap();
        cats.add_comment("Hello world! CATS over RF").unwrap();
        for _ in 0..15 {
            cats.add_arbitrary(Arbitrary::new(&[0; 255]).unwrap())
                .unwrap();
        }
        cats.add_route(Route::new(3)).unwrap();

        if let Some(pos) = pos {
            cats.add_gps(Gps::new(
                pos.latitude,
                pos.longitude,
                f16::from_f32(pos.altitude_meters),
                0,
                pos.true_course as f64,
                f16::from_f32(pos.speed_ms),
            ))
            .unwrap();
        }

        ctx.shared.radio.lock(|radio| {
            let x = cats.fully_encode().unwrap();

            let buf = ctx.local.buf;
            buf[..x.len()].copy_from_slice(&x);

            let tx_buf = &buf[..x.len()];

            radio.start_tx(&tx_buf).unwrap();
            while !radio.is_idle() {
                radio.interrupt(None, Some(&tx_buf)).unwrap();
            }
        });

        transmit_position::spawn_after(1000u64.millis()).unwrap();
    }

    #[task(binds=USART1, priority = 2, shared=[gps], local=[gps_serial])]
    fn gps_uart(mut ctx: gps_uart::Context) {
        let gps_serial = ctx.local.gps_serial;

        while let Ok(b) = gps_serial.read() {
            ctx.shared.gps.lock(|gps| gps.consume(b))
        }
    }

    #[idle]
    fn idle(_: idle::Context) -> ! {
        loop {
            cortex_m::asm::wfi();
        }
    }
}