// // 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); static void __sleep_enter_stop(void); uint32_t statled_ontime = 0; int main(void) { HAL_Init(); HAL_Delay(1000); // startup delay before infinisleep sysclk_init(); gpio_init(); rtc_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; // DEBUG EMZ FIXME // state = SYSTEM_IDLE; uint32_t gps_polltimer = 0; uint64_t fix_acq_starttime = 0; uint64_t nextwspr_time = 0; uint8_t nextwspr_time_valid = 0; uint64_t last_wspr_tx_time = 0; uint64_t idle_blink_last = 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); // __DBGMCU_CLK_ENABLE() ; // (RCC->APB2ENR |= (RCC_APB2ENR_DBGMCUEN)) // HAL_EnableDBGStopMode(); // SET_BIT(DBGMCU->CR, DBGMCU_CR_DBG_STOP); //////////////////////////////////////////// ///// TODO: MUST synchro RTC to start of 1PPS second to make sure we actually start wspr on time with 1s granularity ///////////////////////////////////////// while (1) { // Every 10 minutes, wake up and try to wspr if(state == SYSTEM_IDLE && (rtc_timestamp_seconds() - last_wspr_tx_time >= 60 * 10)) { state = SYSTEM_GPSACQ; } // Update fix status every 2 seconds, only if the GPS is powered on if(rtc_timestamp_seconds() - gps_polltimer >= 3) { gps_polltimer = rtc_timestamp_seconds(); if(gps_ison()) { HAL_GPIO_WritePin(LED_BLUE, 1); HAL_Delay(50); HAL_GPIO_WritePin(LED_BLUE, 0); gps_update_data(); // If odd minute if(gps_getdata()->minute % 2) { // Wait until even minute plus one second, coming soon nextwspr_time = rtc_timestamp_seconds() + (60 - (gps_getdata()->second * 1)); nextwspr_time_valid = 1; } // If even minute else { // Wait until odd minute, one minute and some change away nextwspr_time = rtc_timestamp_seconds() + 60 + (60 - (gps_getdata()->second * 1)); nextwspr_time_valid = 1; } } } switch(state) { // Idling: sleep and wait for RTC timeslot trigger case SYSTEM_IDLE: { // Don't blink with the usual method blink_rate = BLINK_DISABLE; // If we haven't blinked for a while, blink now if(rtc_timestamp() - idle_blink_last > 10 * 1000) { HAL_GPIO_WritePin(LED_BLUE, 1); HAL_Delay(20); HAL_GPIO_WritePin(LED_BLUE, 0); idle_blink_last = rtc_timestamp(); } // Go into stop mode for ~1s or so __sleep_enter_stop(); // TODO: Eventually use GPS time to calibrate the RTC maybe/trim RTC clock } break; // Attempt to acquire GPS fix case SYSTEM_GPSACQ: { //blink_rate = BLINK_FAST; blink_rate = BLINK_DISABLE; if(!gps_ison()) { fix_acq_starttime = rtc_timestamp_seconds(); 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(rtc_timestamp_seconds() - fix_acq_starttime >= 60 * 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 = rtc_timestamp_seconds(); // repeat acq/tx cycle after big time delay state = SYSTEM_IDLE; } else { // We're waiting for a GPS fix, might as well sleep and let the GPS get a fix. // Enter stop mode for 1 second. Blink in the GPS code above. __sleep_enter_stop(); } } break; // Wait for wspr timeslot and start transmitting case SYSTEM_WSPRTX: { //blink_rate = BLINK_MED; blink_rate = BLINK_DISABLE; // If we're after the minute but not more than 2s after the minute, start tx if(rtc_timestamp_seconds() >= nextwspr_time) { if(rtc_timestamp_seconds() < nextwspr_time + 2) { 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 = rtc_timestamp_seconds(); state = SYSTEM_IDLE; adc_stop(); } else { // Window was missed, go back to idle, and try again after time delay last_wspr_tx_time = rtc_timestamp_seconds(); state = SYSTEM_IDLE; adc_stop(); } nextwspr_time_valid = 0; // invalidate wspr time } else { HAL_GPIO_WritePin(LED_BLUE, 1); HAL_Delay(50); HAL_GPIO_WritePin(LED_BLUE, 0); HAL_Delay(50); HAL_GPIO_WritePin(LED_BLUE, 1); HAL_Delay(50); HAL_GPIO_WritePin(LED_BLUE, 0); // Enter stop mode for 1 second __sleep_enter_stop(); } } break; } #ifndef LED_DISABLE if((blink_rate != BLINK_DISABLE) && (HAL_GetTick() - led_timer > blink_rate)) { __ledpulse(); led_timer = HAL_GetTick(); } if((blink_rate != BLINK_DISABLE) && (statled_ontime && HAL_GetTick() - statled_ontime > 10)) { HAL_GPIO_WritePin(LED_BLUE, 0); statled_ontime = 0; } #endif } } static void __sleep_enter_stop(void) { // Save ms unix timestamp before we enter sleep mode HAL_SuspendTick(); uint64_t start = rtc_timestamp(); __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // Calculate how long we were asleep uint32_t timedelta = rtc_timestamp() - start; // Increment systick by this value to keep all timing happy HAL_IncTickBy(timedelta); HAL_ResumeTick(); // We have woken up! Clear wakeup flag __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); } 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; }