Skip to content
Snippets Groups Projects
Select Git revision
  • 030f1e0f8d7d42b02c8a3ae9283c487b9b10c97c
  • master default protected
  • pi-builds
  • sensors
4 results

main.rs

Blame
  • main.rs NaN GiB
    #![no_main]
    #![no_std]
    
    // use panic_halt as _;
    use panic_semihosting as _;
    
    mod config;
    mod geo;
    mod gps;
    mod radio;
    mod shell;
    mod status;
    
    pub const MAX_PACKET_LEN: usize = 8191;
    
    #[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [USART2, USART6])]
    mod app {
        use cortex_m::singleton;
        use hal::{
            flash::LockedFlash,
            gpio,
            otg_fs::{UsbBus, UsbBusType, USB},
            pac::USART1,
            prelude::*,
            rcc::{Clocks, Rcc},
            serial::{self, Serial},
            spi::{Mode, Phase, Polarity, Spi},
        };
        use half::f16;
        use ham_cats::{
            packet::Packet,
            whisker::{Gps, Route},
        };
        use stm32f4xx_hal as hal;
        use systick_monotonic::{ExtU64, Systick};
        use usb_device::prelude::*;
    
        use crate::{
            config::{Config, GpsSetting},
            gps::{GpsModule, GpsPos},
            radio::RadioManager,
            shell::Shell,
            status::Status,
            MAX_PACKET_LEN,
        };
    
        const SYS_CLK: u32 = 84_000_000;
        const H_CLK: u32 = 21_000_000;
    
        const LED_BLINK_RATE: u64 = 250;
    
        const MODE: Mode = Mode {
            polarity: Polarity::IdleLow,
            phase: Phase::CaptureOnFirstTransition,
        };
    
        #[monotonic(binds = SysTick, default = true)]
        type MyMono = Systick<10_000>;
    
        #[local]
        struct Local {
            green: gpio::Pin<'B', 15, gpio::Output>,
            usb_detect: gpio::Pin<'A', 5, gpio::Input>,
            gps_serial: Serial<USART1>,
            flash: LockedFlash,
            buf: &'static mut [u8; MAX_PACKET_LEN],
        }
    
        #[shared]
        struct Shared {
            red: gpio::Pin<'B', 14, gpio::Output>,
            radio: Option<RadioManager<'static>>,
            gps: GpsModule,
    
            usb_dev: UsbDevice<'static, UsbBusType>,
            shell: Shell,
            config: Config,
            status: Status,
        }
    
        fn setup_clocks(rcc: Rcc) -> Clocks {
            rcc.cfgr
                .use_hse(25.MHz())
                .hclk(H_CLK.Hz())
                .sysclk(SYS_CLK.Hz())
                .pclk1(24.MHz())
                .pclk2(24.MHz())
                .require_pll48clk()
                .freeze()
        }
    
        #[init]
        fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) {
            let dp = ctx.device;
            let gpioa = dp.GPIOA.split();
            let gpiob = dp.GPIOB.split();
    
            let clocks = setup_clocks(dp.RCC.constrain());
    
            // Setup flash
            let mut flash = LockedFlash::new(dp.FLASH);
    
            let config = Config::load_from_flash(&mut flash).unwrap_or_default();
    
            // setup LEDs
            let mut red = gpiob.pb14.into_push_pull_output();
            let mut green = gpiob.pb15.into_push_pull_output();
    
            red.set_low();
            green.set_low();
    
            let usb_detect = gpioa.pa5.into_pull_down_input();
    
            // setup radio
            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 spi = Spi::new(dp.SPI1, (sclk, miso, mosi), MODE, 10.MHz(), &clocks);
    
            let delay = dp.TIM5.delay_us(&clocks);
            let buf = singleton!(: [u8; MAX_PACKET_LEN] = [0; MAX_PACKET_LEN]).unwrap();
            let radio = RadioManager::new(spi, sdn, cs, delay, buf, config.enable_digipeating);
    
            // 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);
    
            // 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);
    
            let buf = cortex_m::singleton!(: [u8; MAX_PACKET_LEN] = [0; MAX_PACKET_LEN]).unwrap();
    
            let status = if usb_detect.is_high() {
                Status::Programming(false)
            } else if radio.is_none() {
                Status::Error(false)
            } else if !config.enable_transmit {
                green.set_high();
                Status::TxDisabled(false)
            } else {
                green.set_high();
    
                Status::Normal
            };
    
            if config.enable_transmit && radio.is_some() {
                transmit_position::spawn().unwrap();
                radio_tick::spawn().unwrap();
            }
    
            led_handler::spawn().unwrap();
    
            (
                Shared {
                    red,
                    radio,
                    gps: GpsModule::new(),
    
                    usb_dev,
                    shell,
                    config,
                    status,
                },
                Local {
                    green,
                    usb_detect,
                    gps_serial,
                    flash,
                    buf,
                },
                init::Monotonics(Systick::new(ctx.core.SYST, H_CLK)),
            )
        }
    
        #[task(priority = 2, local = [], shared = [red, radio, config, status])]
        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()
            });
    
            if ctx.shared.status.lock(|s| matches!(s, Status::Normal)) {
                radio_tick::spawn_after(20u64.millis()).unwrap();
            }
        }
    
        #[task(priority = 2, local = [buf], shared = [red, radio, gps, config, status])]
        fn transmit_position(mut ctx: transmit_position::Context) {
            let mut config = ctx.shared.config;
            let (black_hole, transmit_period_seconds) =
                config.lock(|c| (c.black_hole, c.transmit_period_seconds));
    
            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()),
            };
    
            let should_transmit = match pos {
                Some(pos) => !black_hole.in_black_hole(pos.latitude, pos.longitude),
                None => true,
            };
    
            if should_transmit {
                let mut cats: Packet<MAX_PACKET_LEN> = Packet::default();
    
                config.lock(|config| {
                    cats.add_identification(ham_cats::whisker::Identification {
                        icon: config.icon,
                        callsign: config.callsign,
                        ssid: config.ssid,
                    })
                    .unwrap();
    
                    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();
                }
    
                let x = cats.fully_encode().unwrap();
    
                let buf = ctx.local.buf;
                buf[..x.len()].copy_from_slice(&x);
    
                let tx_buf = &buf[..x.len()];
    
                ctx.shared.radio.lock(|radio| {
                    radio
                        .as_mut()
                        .unwrap()
                        .tx(&mut ctx.shared.red, tx_buf)
                        .unwrap();
                });
            }
    
            if ctx.shared.status.lock(|s| matches!(s, Status::Normal)) {
                transmit_position::spawn_after(transmit_period_seconds.secs()).unwrap();
            }
        }
    
        #[task(priority = 9, local = [green, usb_detect], shared = [red, status])]
        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());
            }
    
            status.lock(|s| match s {
                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;
                }
            });
        }
    
        #[task(binds=USART1, priority = 3, 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))
            }
        }
    
        #[task(binds=OTG_FS, priority = 2, local=[flash], shared=[usb_dev, shell])]
        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);
                }
            });
        }
    
        #[idle]
        fn idle(_: idle::Context) -> ! {
            loop {
                cortex_m::asm::wfi();
            }
        }
    }