Skip to content
Snippets Groups Projects
main.rs 8.1 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 shell;
Stephen D's avatar
Stephen D committed

#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [USART2, USART6])]
mod app {
    use hal::{
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},
        pac::{SPI1, TIM5, USART1},
Stephen D's avatar
Stephen D committed
        prelude::*,
        rcc::{Clocks, Rcc},
        serial::{self, Serial},
        spi::{Mode, Phase, Polarity, Spi},
        timer::Delay,
    };
    use half::f16;
    use ham_cats::{
        packet::Packet,
Stephen D's avatar
Stephen D committed
        whisker::{Gps, Route},
Stephen D's avatar
Stephen D committed
    };
    use rf4463::{config::RADIO_CONFIG_CATS, Rf4463};
    use stm32f4xx_hal as hal;
    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},
        shell::Shell,
    };
Stephen D's avatar
Stephen D committed

    const MAX_PACKET_LEN: usize = 8191;
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

    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>,
    >;

    #[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>,
        gps_serial: Serial<USART1>,
Stephen D's avatar
Stephen D committed
        flash: LockedFlash,
Stephen D's avatar
Stephen D committed
        buf: &'static mut [u8; MAX_PACKET_LEN],
        config: Config,
Stephen D's avatar
Stephen D committed
    }

    #[shared]
    struct Shared {
        radio: Radio,
        gps: GpsModule,
Stephen D's avatar
Stephen D committed

        usb_dev: UsbDevice<'static, UsbBusType>,
        shell: Shell,
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) {
        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);

        // 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);

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();

        // Setup flash
        let mut flash = LockedFlash::new(dp.FLASH);

        let config = Config::load_from_flash(&mut flash).unwrap_or(Config::default());
        let shell = Shell::new(config.clone(), usb_serial);
Stephen D's avatar
Stephen D committed
        red.set_low();
        green.set_low();

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

        if config.enable_transmit {
            transmit_position::spawn().unwrap();
        }
Stephen D's avatar
Stephen D committed

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

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

    #[task(local = [buf, config], shared = [radio, gps])]
Stephen D's avatar
Stephen D committed
    fn transmit_position(mut ctx: transmit_position::Context) {
        let config = ctx.local.config;

        let pos = match config.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 mut cats: Packet<MAX_PACKET_LEN> = Packet::default();
Stephen D's avatar
Stephen D committed
        cats.add_identification(ham_cats::whisker::Identification {
            icon: config.icon,
            callsign: config.callsign,
            ssid: config.ssid,
Stephen D's avatar
Stephen D committed
        })
        .unwrap();

        if config.comment.len() > 0 {
            cats.add_comment(&config.comment).unwrap();
        }

        cats.add_route(Route::new(config.max_hops)).unwrap();
Stephen D's avatar
Stephen D committed

        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(config.transmit_period_seconds.secs()).unwrap();
Stephen D's avatar
Stephen D committed
    }

    #[task(binds=USART1, 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))
        }
    }

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

        (&mut usb_dev, &mut shell).lock(|usb_dev, shell| {
            while usb_dev.poll(&mut [shell.ushell.get_serial_mut()]) {
                shell.poll(cx.local.flash);
            }
        });
    }

Stephen D's avatar
Stephen D committed
    #[idle]
    fn idle(_: idle::Context) -> ! {
        loop {
            cortex_m::asm::wfi();
        }
    }
}