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

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

Stephen D's avatar
Stephen D committed
mod config;
Stephen D's avatar
Stephen D committed
mod gps;
Stephen D's avatar
Stephen D committed
mod radio;
Stephen D's avatar
Stephen D committed
mod shell;
Stephen D's avatar
Stephen D committed
mod status;
Stephen D's avatar
Stephen D committed
mod voltage;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
pub const MAX_PACKET_LEN: usize = 8191;
pub const SYS_TICK_FREQ: u32 = 1000;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [USART6])]
Stephen D's avatar
Stephen D committed
mod app {
Stephen D's avatar
Stephen D committed
    use cortex_m::singleton;
Stephen D's avatar
Stephen D committed
    use hal::{
Stephen D's avatar
Stephen D committed
        adc::{config::AdcConfig, Adc},
Stephen D's avatar
Stephen D committed
        flash::LockedFlash,
Stephen D's avatar
Stephen D committed
        gpio,
Stephen D's avatar
Stephen D committed
        otg_fs::{UsbBus, UsbBusType, USB},
Stephen D's avatar
Stephen D committed
        pac::USART2,
Stephen D's avatar
Stephen D committed
        prelude::*,
        rcc::{Clocks, Rcc},
        serial::{self, Serial},
        spi::{Mode, Phase, Polarity, Spi},
    };
    use half::f16;
    use ham_cats::{
Stephen D's avatar
Stephen D committed
        buffer::Buffer,
Stephen D's avatar
Stephen D committed
        packet::Packet,
Stephen D's avatar
Stephen D committed
        whisker::{Gps, Route},
Stephen D's avatar
Stephen D committed
    };
Stephen D's avatar
Stephen D committed
    use stm32f4xx_hal as hal;
Stephen D's avatar
Stephen D committed
    use systick_monotonic::{ExtU64, Systick};
Stephen D's avatar
Stephen D committed
    use usb_device::prelude::*;
Stephen D's avatar
Stephen D committed

    use crate::{
        config::{Config, GpsSetting},
        gps::{GpsModule, GpsPos},
Stephen D's avatar
Stephen D committed
        radio::RadioManager,
        shell::Shell,
Stephen D's avatar
Stephen D committed
        status::Status,
Stephen D's avatar
Stephen D committed
        voltage::VoltMeter,
        MAX_PACKET_LEN, SYS_TICK_FREQ,
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
    const SYS_CLK: u32 = 84_000_000;
    const H_CLK: u32 = 21_000_000;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
    const LED_BLINK_RATE: u64 = 250;

Stephen D's avatar
Stephen D committed
    const MODE: Mode = Mode {
        polarity: Polarity::IdleLow,
        phase: Phase::CaptureOnFirstTransition,
    };

    #[monotonic(binds = SysTick, default = true)]
    type MyMono = Systick<{ SYS_TICK_FREQ }>;
Stephen D's avatar
Stephen D committed

    #[local]
    struct Local {
Stephen D's avatar
Stephen D committed
        green: gpio::Pin<'B', 15, gpio::Output>,
        usb_detect: gpio::Pin<'A', 5, gpio::Input>,
Stephen D's avatar
Stephen D committed
        gps_serial: Serial<USART2>,
Stephen D's avatar
Stephen D committed
        flash: LockedFlash,
Stephen D's avatar
Stephen D committed
        buf: &'static mut [u8; MAX_PACKET_LEN],
        gps_enable: gpio::Pin<'C', 8, gpio::Output<gpio::OpenDrain>>,
Stephen D's avatar
Stephen D committed
    }

    #[shared]
    struct Shared {
Stephen D's avatar
Stephen D committed
        red: gpio::Pin<'B', 14, gpio::Output>,
        radio: Option<RadioManager<'static>>,
Stephen D's avatar
Stephen D committed
        gps: GpsModule,
Stephen D's avatar
Stephen D committed
        volt_meter: VoltMeter,
Stephen D's avatar
Stephen D committed

        usb_dev: UsbDevice<'static, UsbBusType>,
        shell: Shell,
Stephen D's avatar
Stephen D committed
        config: Config,
        status: Status,
Stephen D's avatar
Stephen D committed
    }

    fn setup_clocks(rcc: Rcc) -> Clocks {
        rcc.cfgr
            .use_hse(25.MHz())
            .hclk(H_CLK.Hz())
Stephen D's avatar
Stephen D committed
            .sysclk(SYS_CLK.Hz())
            .pclk1(24.MHz())
            .pclk2(24.MHz())
Stephen D's avatar
Stephen D committed
            .require_pll48clk()
Stephen D's avatar
Stephen D committed
            .freeze()
    }

    #[init]
    fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) {
Stephen D's avatar
Stephen D committed
        let dp = ctx.device;
Stephen D's avatar
Stephen D committed
        let gpioa = dp.GPIOA.split();
        let gpiob = dp.GPIOB.split();
Stephen D's avatar
Stephen D committed
        let gpioc = dp.GPIOC.split();
Stephen D's avatar
Stephen D committed

        let clocks = setup_clocks(dp.RCC.constrain());

Stephen D's avatar
Stephen D committed
        // Setup flash
        let mut flash = LockedFlash::new(dp.FLASH);

        let config = Config::load_from_flash(&mut flash).unwrap_or_default();

Stephen D's avatar
Stephen D committed
        // setup LEDs
        let mut red = gpiob.pb14.into_push_pull_output();
        let mut green = gpiob.pb15.into_push_pull_output();

Stephen D's avatar
Stephen D committed
        red.set_low();
        green.set_low();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        // setup volt meter
        let volt_pin = gpioc.pc0.into_analog();
        let volt_adc = Adc::adc1(dp.ADC1, true, AdcConfig::default());
        let volt_meter = VoltMeter::new(volt_pin, volt_adc);

        // Detect if we're connected to USB or not
        let usb_detect = gpioa.pa5.into_pull_down_input();

        // need to give time for the pull-down to take effect
        let mut delay = dp.TIM5.delay_us(&clocks);
        delay.delay_us(100u8);

        let usb_connected = usb_detect.is_high();

Stephen D's avatar
Stephen D committed
        // setup radio
        let radio = if usb_connected {
            None
        } else {
            let mosi = gpiob.pb5.into_alternate();
            let miso = gpiob.pb4.into_alternate();
            let sclk = gpiob.pb3.into_alternate();
Stephen D's avatar
Stephen D committed

            let sdn = gpiob.pb12.into_push_pull_output();
            let cs = gpioa.pa8.into_push_pull_output();
Stephen D's avatar
Stephen D committed

            let spi = Spi::new(dp.SPI1, (sclk, miso, mosi), MODE, 10.MHz(), &clocks);
Stephen D's avatar
Stephen D committed

            let buf = singleton!(: [u8; MAX_PACKET_LEN] = [0; MAX_PACKET_LEN]).unwrap();
            let radio = RadioManager::new(spi, sdn, cs, delay, buf, &config);
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        // Setup GPS power
        let mut gps_enable = gpioc.pc8.into_open_drain_output();

Stephen D's avatar
Stephen D committed
        // Setup GPS serial
Stephen D's avatar
Stephen D committed
        let pins = (gpioa.pa2, gpioa.pa3);
Stephen D's avatar
Stephen D committed
        let mut gps_serial = Serial::new(
Stephen D's avatar
Stephen D committed
            dp.USART2,
Stephen D's avatar
Stephen D committed
            pins,
            serial::Config::default()
                .baudrate(9600.bps())
                .wordlength_8(),
            &clocks,
        )
        .unwrap()
        .with_u8_data();
        gps_serial.listen(serial::Event::Rxne);

Stephen D's avatar
Stephen D committed
        // Setup USB
        static mut EP_MEMORY: [u32; 1024] = [0; 1024];
        static mut USB_BUS: Option<usb_device::bus::UsbBusAllocator<UsbBusType>> = None;

        let usb = USB {
            usb_global: dp.OTG_FS_GLOBAL,
            usb_device: dp.OTG_FS_DEVICE,
            usb_pwrclk: dp.OTG_FS_PWRCLK,
            pin_dm: gpioa.pa11.into(),
            pin_dp: gpioa.pa12.into(),
            hclk: clocks.hclk(),
        };
        unsafe {
            USB_BUS.replace(UsbBus::new(usb, &mut EP_MEMORY));
        }

        let usb_serial = usbd_serial::SerialPort::new(unsafe { USB_BUS.as_ref().unwrap() });
        let usb_dev = UsbDeviceBuilder::new(
            unsafe { USB_BUS.as_ref().unwrap() },
            UsbVidPid(0x16c0, 0x27dd),
        )
        .manufacturer("cats.radio")
        .product("CATS mobile transceiver")
        .serial_number("4242")
        .device_class(usbd_serial::USB_CLASS_CDC)
        .build();

        let shell = Shell::new(config.clone(), usb_serial);
Stephen D's avatar
Stephen D committed
        let buf = cortex_m::singleton!(: [u8; MAX_PACKET_LEN] = [0; MAX_PACKET_LEN]).unwrap();

        let status = if usb_connected {
            Status::Programming(false)
        } else if radio.is_none() {
Stephen D's avatar
Stephen D committed
            Status::Error(false)
        } else if !config.enable_transmit {
            green.set_high();
Stephen D's avatar
Stephen D committed
            Status::TxDisabled(false)
        } else {
            green.set_high();
Stephen D's avatar
Stephen D committed
            gps_enable.set_low(); // enable GPS
Stephen D's avatar
Stephen D committed

            Status::Normal
        };

        if config.enable_transmit && radio.is_some() {
            transmit_position::spawn().unwrap();
Stephen D's avatar
Stephen D committed
            radio_tick::spawn().unwrap();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        led_handler::spawn().unwrap();

Stephen D's avatar
Stephen D committed
        (
            Shared {
Stephen D's avatar
Stephen D committed
                red,
Stephen D's avatar
Stephen D committed
                radio,
                gps: GpsModule::new(),
Stephen D's avatar
Stephen D committed
                volt_meter,
Stephen D's avatar
Stephen D committed

                usb_dev,
                shell,
Stephen D's avatar
Stephen D committed
                config,
Stephen D's avatar
Stephen D committed
            },
            Local {
                green,
                usb_detect,
Stephen D's avatar
Stephen D committed
                gps_serial,
Stephen D's avatar
Stephen D committed
                flash,
Stephen D's avatar
Stephen D committed
                buf,
                gps_enable,
Stephen D's avatar
Stephen D committed
            },
            init::Monotonics(Systick::new(ctx.core.SYST, H_CLK)),
Stephen D's avatar
Stephen D committed
        )
    }

Stephen D's avatar
Stephen D committed
    #[task(priority = 2, local = [], shared = [red, radio, config, status])]
Stephen D's avatar
Stephen D committed
    fn radio_tick(mut ctx: radio_tick::Context) {
        (ctx.shared.radio, ctx.shared.config).lock(|r, c| {
            r.as_mut()
                .unwrap()
                .tick(&mut ctx.shared.red, Some((&c.callsign, c.ssid)))
                .unwrap()
        });
Stephen D's avatar
Stephen D committed

        if ctx.shared.status.lock(|s| matches!(s, Status::Normal)) {
            radio_tick::spawn_after(20u64.millis()).unwrap();
        }
Stephen D's avatar
Stephen D committed
    }

    #[task(priority = 2, local = [buf], shared = [red, radio, gps, config, status])]
Stephen D's avatar
Stephen D committed
    fn transmit_position(mut ctx: transmit_position::Context) {
Stephen D's avatar
Stephen D committed
        let mut config = ctx.shared.config;
        let (black_hole, transmit_period_seconds) =
            config.lock(|c| (c.black_hole, c.transmit_period_seconds));
Stephen D's avatar
Stephen D committed
        let pos = match config.lock(|c| c.gps) {
            GpsSetting::Disabled => None,
            GpsSetting::Fixed(lat, lon) => Some(GpsPos {
                latitude: lat,
                longitude: lon,
                altitude_meters: 0.0,
                speed_ms: 0.0,
                true_course: 0.0,
            }),
            GpsSetting::Receiver => ctx.shared.gps.lock(|gps| gps.pos()),
        };
Stephen D's avatar
Stephen D committed

        let should_transmit = match pos {
            Some(pos) => !black_hole.in_black_hole(pos.latitude, pos.longitude),
            None => true,
        };
Stephen D's avatar
Stephen D committed
            let mut buf = [0; MAX_PACKET_LEN];
            let mut cats = Packet::new(&mut buf);
            config.lock(|config| {
                cats.add_identification(ham_cats::whisker::Identification {
                    icon: config.icon,
                    callsign: config.callsign,
                    ssid: config.ssid,
                })
                .unwrap();
Stephen D's avatar
Stephen D committed

                if !config.comment.is_empty() {
                    cats.add_comment(&config.comment).unwrap();
                }

                cats.add_route(Route::new(config.max_hops)).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();
            }
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
            let mut x = Buffer::new_empty(buf);
            cats.fully_encode(&mut x).unwrap();
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
            (ctx.shared.radio, config).lock(|r, c| {
                r.as_mut()
Stephen D's avatar
Stephen D committed
                    .tx(&mut ctx.shared.red, &x, Some((&c.callsign, c.ssid)))
Stephen D's avatar
Stephen D committed

        if ctx.shared.status.lock(|s| matches!(s, Status::Normal)) {
            transmit_position::spawn_after(transmit_period_seconds.secs()).unwrap();
        }
Stephen D's avatar
Stephen D committed
    }

    #[task(priority = 2, local = [green, usb_detect, gps_enable], shared = [red, status])]
Stephen D's avatar
Stephen D committed
    fn led_handler(mut ctx: led_handler::Context) {
        led_handler::spawn_after(LED_BLINK_RATE.millis()).unwrap();

        let mut status = ctx.shared.status;
        let cur_status = status.lock(|x| *x);

        if ctx.local.usb_detect.is_high() && !matches!(cur_status, Status::Programming(_)) {
            status.lock(|s| *s = Status::Programming(false));

            ctx.shared.red.lock(|red| red.set_low());
            ctx.local.gps_enable.set_high();
        }

        status.lock(|s| match s {
Stephen D's avatar
Stephen D committed
            Status::Normal => {}
            Status::Programming(b) => {
                ctx.local.green.set_state((*b).into());
                *b = !*b;
            }
            Status::TxDisabled(b) => {
                ctx.shared.red.lock(|red| red.set_state((*b).into()));
                *b = !*b;
            }
            Status::Error(b) => {
                ctx.shared.red.lock(|red| red.set_state((*b).into()));
                ctx.local.green.set_state((!*b).into());
                *b = !*b;
            }
Stephen D's avatar
Stephen D committed
    }

Stephen D's avatar
Stephen D committed
    #[task(binds=USART2, priority = 3, shared=[gps], local=[gps_serial])]
Stephen D's avatar
Stephen D committed
    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))
        }
    }

Stephen D's avatar
Stephen D committed
    #[task(binds=OTG_FS, priority = 2, local=[flash], shared=[usb_dev, shell, volt_meter])]
Stephen D's avatar
Stephen D committed
    fn usb_fs(cx: usb_fs::Context) {
        let usb_fs::SharedResources {
            mut usb_dev,
            mut shell,
Stephen D's avatar
Stephen D committed
            mut volt_meter,
Stephen D's avatar
Stephen D committed
        } = cx.shared;

        (&mut usb_dev, &mut shell).lock(|usb_dev, shell| {
            while usb_dev.poll(&mut [shell.ushell.get_serial_mut()]) {
Stephen D's avatar
Stephen D committed
                shell.poll(cx.local.flash, &mut volt_meter);
Stephen D's avatar
Stephen D committed
    #[idle]
    fn idle(_: idle::Context) -> ! {
        loop {
            cortex_m::asm::wfi();
        }
    }
}