|
|
@@ -20,18 +20,91 @@ time_t utc; /* current time */
|
|
|
struct location_s location;
|
|
|
struct auto_pause_s auto_pause;
|
|
|
volatile unsigned long int uptime_ms; /* milliseconds since MCU startup, for the system log */
|
|
|
-
|
|
|
-/* Capture the reset cause before it is cleared by ioinit()/wdt_disable(),
|
|
|
- * and disable the watchdog immediately: otherwise a watchdog-triggered
|
|
|
- * reset with a short timeout could loop forever before main() re-enables it. */
|
|
|
+static unsigned int io_max_ms; /* longest single SD f_write()/f_sync() duration since the last status line, ms */
|
|
|
+
|
|
|
+/*
|
|
|
+ * Reset cause: this board is always started via Optiboot, not a fresh MCU
|
|
|
+ * reset straight into main(). Optiboot reads MCUSR at its own entry, clears
|
|
|
+ * only WDRF (to avoid looping on its own watchdog-based re-entry), and passes
|
|
|
+ * the *original* MCUSR value to the application in register r2 before jumping
|
|
|
+ * to address 0 - see optiboot.c ("save the reset flags in the designated
|
|
|
+ * register ... by putting code in .init0 ... to save R2 to a global
|
|
|
+ * variable"). By the time main() runs, the real MCUSR register no longer
|
|
|
+ * reflects the actual reset cause, so it must be read out of r2 instead, and
|
|
|
+ * that has to happen in .init0 - the very first code executed - before
|
|
|
+ * anything else can clobber the register.
|
|
|
+ */
|
|
|
volatile unsigned char reset_cause __attribute__((section(".noinit")));
|
|
|
-void capture_reset_cause(void) __attribute__((naked, used, section(".init3")));
|
|
|
-void capture_reset_cause(void) {
|
|
|
- reset_cause = MCUSR;
|
|
|
+void get_mcusr_from_r2(void) __attribute__((naked, used, section(".init0")));
|
|
|
+void get_mcusr_from_r2(void) {
|
|
|
+ __asm__ __volatile__ ("mov %0, r2\n" : "=r" (reset_cause) :);
|
|
|
+}
|
|
|
+
|
|
|
+/* Disable the watchdog as early as possible (in case this image is ever
|
|
|
+ * flashed directly via ISP, bypassing Optiboot, which already turns the
|
|
|
+ * watchdog off itself before jumping to the application): otherwise a
|
|
|
+ * watchdog-triggered reset with a short timeout could loop forever before
|
|
|
+ * main() re-enables it with the timeout it actually wants. */
|
|
|
+void wdt_disable_early(void) __attribute__((naked, used, section(".init3")));
|
|
|
+void wdt_disable_early(void) {
|
|
|
MCUSR = 0;
|
|
|
wdt_disable();
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Stack headroom (AVR121 "StackPaint" pattern): fill all RAM between the end
|
|
|
+ * of .bss/.noinit and the top of RAM with a known byte as early as possible
|
|
|
+ * (.init1, before the stack pointer itself is set up in .init2), then later
|
|
|
+ * count how many bytes of that pattern are still untouched. That count can
|
|
|
+ * only shrink over the session, as the deepest interrupt/call nesting grows,
|
|
|
+ * so it is a running record of the worst-case stack headroom seen so far -
|
|
|
+ * no extra storage needed to remember a "minimum".
|
|
|
+ */
|
|
|
+#define STACK_CANARY 0xc5
|
|
|
+
|
|
|
+extern uint8_t __heap_start;
|
|
|
+extern uint8_t __stack;
|
|
|
+
|
|
|
+void stack_paint(void) __attribute__((naked, used, section(".init1")));
|
|
|
+void stack_paint(void) {
|
|
|
+ __asm__ __volatile__ (
|
|
|
+ " ldi r30,lo8(__heap_start)\n"
|
|
|
+ " ldi r31,hi8(__heap_start)\n"
|
|
|
+ " ldi r24,lo8(0xc5)\n" /* STACK_CANARY */
|
|
|
+ " ldi r25,hi8(__stack)\n"
|
|
|
+ " rjmp .Lstackpaint_cmp\n"
|
|
|
+ ".Lstackpaint_loop:\n"
|
|
|
+ " st Z+,r24\n"
|
|
|
+ ".Lstackpaint_cmp:\n"
|
|
|
+ " cpi r30,lo8(__stack)\n"
|
|
|
+ " cpc r31,r25\n"
|
|
|
+ " brlo .Lstackpaint_loop\n"
|
|
|
+ " breq .Lstackpaint_loop\n"
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+unsigned int get_stack_free_bytes(void) {
|
|
|
+ const uint8_t *p = &__heap_start;
|
|
|
+ unsigned int count = 0;
|
|
|
+ while (p <= &__stack && *p == STACK_CANARY) {
|
|
|
+ p++;
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+void io_mark(unsigned long int start_ms) {
|
|
|
+ unsigned int dt = (unsigned int)(get_uptime_ms() - start_ms);
|
|
|
+ if (dt > io_max_ms)
|
|
|
+ io_max_ms = dt;
|
|
|
+}
|
|
|
+
|
|
|
+unsigned int io_get_and_reset_max(void) {
|
|
|
+ unsigned int v = io_max_ms;
|
|
|
+ io_max_ms = 0;
|
|
|
+ return v;
|
|
|
+}
|
|
|
+
|
|
|
void start_bootloader(void) {
|
|
|
typedef void (*do_reboot_t)(void);
|
|
|
const do_reboot_t do_reboot = (do_reboot_t)((FLASHEND - 1023) >> 1);
|
|
|
@@ -194,7 +267,10 @@ void log_put(int c){
|
|
|
uart1_put(c);
|
|
|
logbuf.buf[logbuf.len++] = c;
|
|
|
if (logbuf.len >= LOG_SIZE && (FLAGS & F_FILEOPEN)) {
|
|
|
- if(!f_write(&system_log, logbuf.buf, logbuf.len, &bw))
|
|
|
+ unsigned long int t0 = get_uptime_ms();
|
|
|
+ unsigned char err = f_write(&system_log, logbuf.buf, logbuf.len, &bw);
|
|
|
+ io_mark(t0);
|
|
|
+ if (!err)
|
|
|
logbuf.len = 0;
|
|
|
}
|
|
|
if (logbuf.len > LOG_SIZE) {
|
|
|
@@ -205,8 +281,7 @@ void log_put(int c){
|
|
|
static
|
|
|
void ioinit (void)
|
|
|
{
|
|
|
- wdt_enable(WDTO_4S);
|
|
|
- MCUSR = 0;
|
|
|
+ wdt_enable(WDTO_4S); /* MCUSR was already cleared in wdt_disable_early() (.init3) */
|
|
|
POWER_ON_DDR |= POWER_ON;
|
|
|
PORTA |= POWER_ON;
|
|
|
|
|
|
@@ -342,6 +417,17 @@ unsigned int get_avg_speed_x100(void) {
|
|
|
return System.speed_accum_x100 / System.speed_sample_count;
|
|
|
}
|
|
|
|
|
|
+/* Average speed the "old" way (distance / moving time), kept only so it can
|
|
|
+ * be logged alongside get_avg_speed_x100() in the session summary - the two
|
|
|
+ * should track each other, and a growing gap is worth noticing. */
|
|
|
+unsigned int get_dist_avg_speed_x100(void) {
|
|
|
+ unsigned int moving = get_logging_time();
|
|
|
+ if (!moving)
|
|
|
+ return 0;
|
|
|
+ /* (distance_cm * 3.6) / moving_s, kept as an all-integer x100 fixed-point value */
|
|
|
+ return (unsigned int)((unsigned long int)System.distance * 36UL / (10UL * moving));
|
|
|
+}
|
|
|
+
|
|
|
time_t get_pause_time(void) {
|
|
|
time_t res = System.pause_time;
|
|
|
if (System.current_pause_start < System.time_start)
|
|
|
@@ -518,7 +604,11 @@ int main (void)
|
|
|
}
|
|
|
|
|
|
if (FLAGS & F_FILEOPEN) {
|
|
|
- f_write(&gps_log, Line, len-1, &bw);
|
|
|
+ {
|
|
|
+ unsigned long int t0 = get_uptime_ms();
|
|
|
+ f_write(&gps_log, Line, len-1, &bw);
|
|
|
+ io_mark(t0);
|
|
|
+ }
|
|
|
if (bw != len-1) {
|
|
|
System.status = STATUS_FILE_WRITE_ERROR;
|
|
|
System.global_error |= ERROR_FILE_WRITE;
|
|
|
@@ -526,11 +616,24 @@ int main (void)
|
|
|
}
|
|
|
if (System.location_valid == LOC_VALID_NEW) { /* a new point */
|
|
|
gpx_process_point(&location, &gpx_file);
|
|
|
- auto_pause_process();
|
|
|
+ auto_pause_process();
|
|
|
}
|
|
|
wdt_reset();
|
|
|
if (FLAGS & F_SYNC) {
|
|
|
- if (f_sync(&gps_log)) {
|
|
|
+ /* Sync all three log files together, on this same periodic
|
|
|
+ * timer (IVT_SYNC), rather than after every write: an SD
|
|
|
+ * f_sync() can stall for 100ms+ and must not happen on
|
|
|
+ * every line, or it would stall the NMEA UART reception. */
|
|
|
+ unsigned long int t0 = get_uptime_ms();
|
|
|
+ unsigned char sync_err = f_sync(&gps_log);
|
|
|
+ io_mark(t0);
|
|
|
+ t0 = get_uptime_ms();
|
|
|
+ sync_err |= f_sync(&gpx_file);
|
|
|
+ io_mark(t0);
|
|
|
+ t0 = get_uptime_ms();
|
|
|
+ sync_err |= f_sync(&system_log);
|
|
|
+ io_mark(t0);
|
|
|
+ if (sync_err) {
|
|
|
System.status = STATUS_FILE_SYNC_ERROR;
|
|
|
System.global_error |= ERROR_FILE_SYNC;
|
|
|
break;
|