/* * gps_task.c * * Created on: Jan 20, 2018 * Author: chris.l */ #include "esp_gps_ntp.h" #include "driver/uart.h" #include "freertos/timers.h" #include "sys/time.h" #include "minmea.h" static const char *TAG = "GPS"; #define RX_BUF_SIZE 255 #define ESP_INTR_FLAG_DEFAULT 0 // simple versions - we don't worry about side effects #define MAX(a, b) ((a) < (b) ? (b) : (a)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define VALIDITY_TIMER_MS 1010 #define PPS_VALID_COUNT 10 // must have at least this many "good" PPS interrupts to be valid #define PPS_PIN (GPIO_NUM_19) #define TXD_PIN (GPIO_NUM_17) #define RXD_PIN (GPIO_NUM_16) #define LED_PIN (GPIO_NUM_15) #define GPS_UART (UART_NUM_2) static TimerHandle_t timer; static volatile uint32_t timeouts; static volatile time_t seconds; static volatile micros_t last_micros; static volatile micros_t min_micros; static volatile micros_t max_micros; static volatile bool gps_valid; static volatile bool valid; static volatile uint32_t valid_in; static volatile time_t valid_since; static volatile uint32_t valid_count; static volatile time_t initial_valid; static uint32_t dispersion; static volatile char reason[128]; typedef struct gps_status { bool valid; int sat_count; int fix_quality; } GPSStatus; static GPSStatus gps_status; #ifdef LED_PIN #define DEBUG_LED_ON() gpio_set_level(LED_PIN, 1) #define DEBUG_LED_OFF() gpio_set_level(LED_PIN, 0) #define DEBUG_LED_INIT() init_debug_led() static void init_debug_led() { // // set up the LED pin // gpio_config_t io_conf; //interrupt disabled io_conf.intr_type = GPIO_PIN_INTR_DISABLE; //set as input mode io_conf.mode = GPIO_MODE_OUTPUT; //bit mask of the pins that you want to set,e.g.GPIO18/19 io_conf.pin_bit_mask = (1ULL<timeouts = timeouts; status->seconds = seconds; status->min_micros = min_micros; status->max_micros = max_micros; status->gps_valid = gps_valid; status->valid = valid; status->valid_in = valid_in; status->valid_since = valid_since; status->valid_count = valid_count; status->initial_valid = initial_valid; status->sat_count = gps_status.sat_count; status->fix_quality = gps_status.fix_quality; } static void IRAM_ATTR pps_isr_handler(void* arg) { DEBUG_LED_ON(); micros_t cur_micros = micros(); // restart the validity timer xTimerReset(timer, 0); // // trigger a display update each second // triggerDisplay(); // // don't trust PPS if GPS is not valid. // if (!gps_valid) { DEBUG_LED_OFF(); return; } // // if we are still counting down then keep waiting // if (valid_in) { --valid_in; if (valid_in == 0) { // clear stats and mark us valid min_micros = 0; max_micros = 0; valid = true; valid_since = seconds; ++valid_count; reason[0] = '\0'; if (initial_valid == 0) { initial_valid = seconds; } } } seconds++; // // the first time around we just initialize the last value // if (last_micros == 0) { last_micros = cur_micros; DEBUG_LED_OFF(); return; } uint32_t micros_count = cur_micros - last_micros; last_micros = cur_micros; if (min_micros == 0 || micros_count < min_micros) { min_micros = micros_count; } if (micros_count > max_micros) { max_micros = micros_count; } DEBUG_LED_OFF(); } // // read a single NMEA record // static char *readNMEA() { static uint8_t line[RX_BUF_SIZE+1]; int size; uint8_t *p = line; *p = '\0'; while(1) { if (p >= line+RX_BUF_SIZE) { ESP_LOGI(TAG, "Read buffer overflow! size %d bytes: '%s'", p-line, line); } size = uart_read_bytes(GPS_UART, (unsigned char *)p, 1, portMAX_DELAY); if (size == 1) { if (*p == '$') { // // '$' starts a record so reset pointer to start // p = line; *p = '$'; } else if (*p == '\r' || line[0] != '$') { // // skip newlin and any chars if buffer does not start with '$' continue; } else if (*p == '\n') { // // got a record! // *p = 0; break; } p++; } } return (char*)line; } static void IRAM_ATTR invalidate(const char* fmt, ...) { va_list argp; va_start(argp, fmt); if (reason[0] == '\0') { vsnprintf((char*)reason, sizeof(reason)-1, fmt, argp); } va_end(argp); if (valid) { valid_since = seconds; } valid = false; gps_valid = false; last_micros = 0; valid_in = 0; } static void init_pps() { // // set up the PPS interrupt // gpio_config_t io_conf; //interrupt of falling edge io_conf.intr_type = GPIO_PIN_INTR_POSEDGE; //set as input mode io_conf.mode = GPIO_MODE_INPUT; //bit mask of the pins that you want to set,e.g.GPIO18/19 io_conf.pin_bit_mask = (1ULL< ts.tv_sec) { timewarps += 1; ESP_LOGD(TAG, "$RMC: ignoring timewarp back! delayed serial? %lu -> %lu", seconds, ts.tv_sec); if (timewarps > 1) { invalidate("time warped backwards too many (%d) times!", timewarps); timewarps = 0; } } else if (seconds != ts.tv_sec) { ESP_LOGW(TAG, "$RMC: adjusting seconds %lu -> %lu", seconds, ts.tv_sec); seconds = ts.tv_sec; } else { timewarps = 0; } // // if gps was not valid, it is now // if (!gps_valid) { gps_valid = true; valid_in = PPS_VALID_COUNT; ESP_LOGI(TAG, "gps valid!"); } } else { if (valid) { invalidate("GPS $RMC"); } } } else { ESP_LOGE(TAG, "$RMC: failed to parse line: %s", line); } } break; case MINMEA_SENTENCE_GGA: { struct minmea_sentence_gga frame; if (minmea_parse_gga(&frame, line)) { ESP_LOGD(TAG, "$GGA: seconds: %lu timeouts: %u fix: %d sats: %d", seconds, timeouts, frame.fix_quality, frame.satellites_tracked); gps_status.sat_count = frame.satellites_tracked; gps_status.fix_quality = frame.fix_quality; } else { ESP_LOGE(TAG, "$GGA: failed to parse line: %s", line); } } break; case MINMEA_SENTENCE_GSV: case MINMEA_SENTENCE_GSA: case MINMEA_SENTENCE_GLL: case MINMEA_SENTENCE_GST: case MINMEA_SENTENCE_VTG: case MINMEA_SENTENCE_ZDA: ESP_LOGV(TAG, "IGNORING: %s", line); break; case MINMEA_INVALID: ESP_LOGE(TAG, "INVALID: '%s'", line); break; case MINMEA_UNKNOWN: ESP_LOGW(TAG, "UNKNOWN: '%s'", line); break; } // // Recompute dispersion periodically // static time_t last_seconds; if (seconds != last_seconds) { double disp = us2s(MAX(abs(MICROS_PER_SEC-max_micros), abs(MICROS_PER_SEC-min_micros))); dispersion = (uint32_t)(disp * 65536.0); #if 0 ESP_LOGI(TAG, "min: %llu max: %llu jitter: %llu sats: %d fix: %d valid_count: %u valid_in: %u valid: %s", min_micros, max_micros, max_micros - min_micros, gps_status.sat_count, gps_status.fix_quality, valid_count, valid_in, valid ? "true" : "false"); #endif } last_seconds = seconds; } } void start_gps() { esp_log_level_set(TAG, ESP_LOG_INFO); xTaskCreatePinnedToCore(gps_task, "gps_task", 1024*2, NULL, GPS_TASK_PRI, NULL, 1); }