// // WSPRHAB: Minimal high-altitude balloon tracker with WSPR telemetry // #include "stm32f0xx_hal.h" #include "adc.h" #include "system.h" #include "i2c.h" #include "uart.h" #include "gpio.h" #include "wspr.h" #include "rtc.h" #include "gps.h" #include "config.h" // We have access to the 1PPS pin of the gps... could have trim routine for internal oscillator based on this when we have a fix // Probable wake up 1 minute early -- 0.45min possible +/- on wakeup time with 15min sync intervals // TODO: Add JT9 message with more grid locator digits + altitude + vbatt + temp // MSG13charmax: // X: gridloc // Y: altitude // Z: temperature // KD8TDF XXYYZZ // could use alt callsign thing enum _state { SYSTEM_IDLE = 0, // awaiting RTC interrupt for wakeup TODO wake up before scheduled time to get fix? SYSTEM_GPSACQ, // RTC interrupted SYSTEM_WSPRTX, // Wait for timeslot and actually transmit the message }; static void __calc_gridloc(char *dst, double lat, double lon); static void ledpulse(void); uint32_t statled_ontime = 0; int main(void) { HAL_Init(); HAL_Delay(1000); // startup delay before infinisleep sysclk_init(); rtc_init(); gpio_init(); adc_init(); wspr_init(); uint32_t led_timer = HAL_GetTick(); led_blink(4); uint16_t blink_rate = BLINK_FAST; uint8_t state = SYSTEM_GPSACQ; uint32_t gps_polltimer = 0; uint32_t fix_acq_starttime = 0; uint32_t nextwspr_time = 0; uint8_t nextwspr_time_valid = 0; uint32_t last_wspr_tx_time = 0; uint8_t packet_type = 0; // Transmit pilot tone to test TX on bootup HAL_Delay(1000); wspr_pilot_tone(); adc_stop(); HAL_Delay(1000); while (1) { // TODO: Disable GPIO port clocking when not needed! // Every 10 minutes, wake up and try to wspr // if(state == SYSTEM_IDLE && (HAL_GetTick() - last_wspr_tx_time > 60000 * 10)) // { // state = SYSTEM_GPSACQ; // } // Update fix status every 2 seconds, only if the GPS is powered on if(HAL_GetTick() - gps_polltimer > 2000) { if(gps_ison()) { gps_update_data(); // If odd minute if(gps_getdata()->minute % 2) { // Wait until even minute plus one second, coming soon nextwspr_time = HAL_GetTick() + (60000 - (gps_getdata()->second * 1000)); nextwspr_time_valid = 1; } // If even minute else { // Wait until odd minute, one minute and some change away nextwspr_time = HAL_GetTick() + 60000 + (60000 - (gps_getdata()->second * 1000)); nextwspr_time_valid = 1; } } gps_polltimer = HAL_GetTick(); } switch(state) { // Idling: sleep and wait for RTC timeslot trigger case SYSTEM_IDLE: { blink_rate = BLINK_SLOW; // Actually sleep for real: disable systick and sleep until RTC interrupt // HAL_SuspendTick(); // Enter sleep mode: wait for interrupt HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); // We have woken up! // This is hopefully the only timer that needs to stay alive in idle mode // last_wspr_tx_time += 0; // move this timer forward based on sleep length // HAL_ResumeTick(); // TODO: Eventually use GPS time to calibrate the RTC maybe } break; // Attempt to acquire GPS fix case SYSTEM_GPSACQ: { blink_rate = BLINK_FAST; HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); if(!gps_ison()) { fix_acq_starttime = HAL_GetTick(); gps_poweron(); // power on and initialize GPS module } // TODO: Move GPS processing into here from above! // If 3d fix with a decent enough precision if( ((gps_getdata()->fixtype == 2) || (gps_getdata()->fixtype == 3)) && gps_getdata()->pdop < 10 && nextwspr_time_valid == 1) { // Disable GPS module gps_poweroff(); // TODO: Set RTC from GPS time // TODO: Set RTC for countdown to next transmission timeslot! // TODO: Set wspr countdown timer for this transmission! fix_acq_starttime = 0; state = SYSTEM_WSPRTX; adc_start(); } // If no decent fix in 3 minutes else if(HAL_GetTick() - fix_acq_starttime > 60000 * 3) { // Flash error code and go to idle, try again next time led_blink(4); gps_poweroff(); fix_acq_starttime = 0; last_wspr_tx_time = HAL_GetTick(); // repeat acq/tx cycle after big time delay state = SYSTEM_IDLE; } } break; // Wait for wspr timeslot and start transmitting case SYSTEM_WSPRTX: { blink_rate = BLINK_MED; // Wait for wspr countdown timer to expire and go to tx // if(timeout_expired) // { // If we're after the minute but not more than 2s after the minute, start tx if(HAL_GetTick() >= nextwspr_time) { if(HAL_GetTick() < nextwspr_time + 2000) { volatile double latitude_flt = (double)gps_getdata()->latitude / 10000000.0; volatile double longitude_flt = (double)gps_getdata()->longitude / 10000000.0; volatile uint8_t grid_locator[7]; __calc_gridloc(grid_locator, latitude_flt, longitude_flt); // TODO: Switch between alternate and standard packet wspr_transmit(grid_locator, packet_type); packet_type = !packet_type; // alternate packet type last_wspr_tx_time = HAL_GetTick(); state = SYSTEM_IDLE; adc_stop(); } else { // Window was missed, go back to idle, and try again after time delay last_wspr_tx_time = HAL_GetTick(); state = SYSTEM_IDLE; adc_stop(); } nextwspr_time_valid = 0; // invalidate wspr time } else { HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); HAL_PWR_EnterSLEEPMode(0, PWR_SLEEPENTRY_WFI); } // Schedule next wakeup (maybe 2mins prior to timeslot if no osc trim) // Next wakeup should enter SYSTEM_GPSACQ state... } break; } #ifndef LED_DISABLE if(HAL_GetTick() - led_timer > blink_rate) { ledpulse(); led_timer = HAL_GetTick(); } if(statled_ontime && HAL_GetTick() - statled_ontime > 10) { HAL_GPIO_WritePin(LED_BLUE, 0); statled_ontime = 0; } #endif } } static void ledpulse(void) { HAL_GPIO_WritePin(LED_BLUE, 1); statled_ontime = HAL_GetTick(); } static void __calc_gridloc(char *dst, double lat, double lon) { int o1, o2, o3; int a1, a2, a3; double remainder; // longitude remainder = lon + 180.0; o1 = (int)(remainder / 20.0); remainder = remainder - (double)o1 * 20.0; o2 = (int)(remainder / 2.0); remainder = remainder - 2.0 * (double)o2; o3 = (int)(12.0 * remainder); // latitude remainder = lat + 90.0; a1 = (int)(remainder / 10.0); remainder = remainder - (double)a1 * 10.0; a2 = (int)(remainder); remainder = remainder - (double)a2; a3 = (int)(24.0 * remainder); dst[0] = (char)o1 + 'A'; dst[1] = (char)a1 + 'A'; dst[2] = (char)o2 + '0'; dst[3] = (char)a2 + '0'; dst[4] = (char)o3 + 'A'; dst[5] = (char)a3 + 'A'; dst[6] = (char)0; }