From ef309b58ffb165ae76b965bc96328fd8b9a13ec5 Mon Sep 17 00:00:00 2001 From: Peter Lewis Date: Tue, 30 Jun 2026 15:24:01 +0100 Subject: [PATCH 1/3] Add astro pack: sun / moon / Maidenhead / lat-lon display modes Five GPS-derived astronomy read-outs, each opt-in via a MODE_* config key and shown on the 10-char date row while the live clock keeps running on the time row (SATVIEW-style, COUNT_NORMAL): MODE_SUN sunrise / sunset / solar noon (local), auto-paged MODE_SUN_AZEL sun azimuth & elevation, now MODE_MOON moon phase index (0-7) + illuminated % MODE_GRID Maidenhead grid locator MODE_LATLON latitude / longitude, auto-paged The astronomy maths is a self-contained Core/Src/astro.c (+ astro.h): low- precision NOAA/Meeus sun alt-az and event times, moon phase, equation of time, and Maidenhead. It has no firmware dependencies, so it unit-tests natively (test/test_astro.c, 45 reference vectors) and was validated to ~1e-5 before touching the firmware. The L4 has only a single-precision FPU, so the double maths would be slow soft- float. It is therefore computed in the main loop (astro_update(), gated on the active mode exactly as measure_vbat() is for MODE_VBAT) and swapped into a cache under a brief __disable_irq() mask; the sendDate() cases that run inside the SysTick ISR only format the cached scalars -- no trig in the interrupt. Modes are disabled by default; with no GPS fix they use fake_latitude/longitude if set, else show "----". config.txt documents the keys and the moon-phase index legend. Co-Authored-By: Claude Opus 4.8 --- mk4-time/Core/Inc/astro.h | 48 +++++++++++++ mk4-time/Core/Inc/main.h | 9 +++ mk4-time/Core/Src/astro.c | 140 +++++++++++++++++++++++++++++++++++++ mk4-time/Core/Src/main.c | 121 ++++++++++++++++++++++++++++++++ mk4-time/test/test_astro.c | 104 +++++++++++++++++++++++++++ qspi/config.txt | 28 ++++++++ 6 files changed, 450 insertions(+) create mode 100644 mk4-time/Core/Inc/astro.h create mode 100644 mk4-time/Core/Src/astro.c create mode 100644 mk4-time/test/test_astro.c diff --git a/mk4-time/Core/Inc/astro.h b/mk4-time/Core/Inc/astro.h new file mode 100644 index 0000000..c296f6c --- /dev/null +++ b/mk4-time/Core/Inc/astro.h @@ -0,0 +1,48 @@ +/* + * astro.h — minimal solar/lunar/grid astronomy for the Precision Clock Mk IV. + * + * Self-contained C99 + ; NO firmware dependencies, so it compiles and + * unit-tests natively (see test_astro.c). All angles in degrees at the API + * boundary; UTC instants are passed as a double of Unix seconds. + * + * Algorithms are low-precision (NOAA/Meeus-class) approximations — accurate to a + * fraction of a degree / a minute or two, which is all a 7-segment readout shows, + * and cheap enough to evaluate once per mode entry on the STM32's soft-double. + */ +#ifndef ASTRO_H +#define ASTRO_H + +/* (a) Sun apparent alt/az for an observer at (lat, lon) decimal degrees, N+/E+, + * at the given UTC instant. Writes azimuth in [0,360) measured from North + * clockwise, and elevation in [-90,90] (negative = below the horizon). + * No refraction or parallax correction. */ +void sun_az_el(double lat, double lon, double unix_s, double *az, double *el); + +/* (b) Sun event times for the UTC calendar day containing unix_s. + * Every output is a decimal UTC hour and MAY be < 0 or > 24 (the event falls + * on the previous/next day) — callers add the local offset and wrap, they do + * NOT clamp. solar_noon is always written. Returns 0 normally; returns + * nonzero on polar day/night (sun never crosses -0.833 deg), in which case + * sunrise/sunset are left untouched and only solar_noon is meaningful. + * Any of the optional twilight pointers may be NULL. */ +int sun_times(double lat, double lon, double unix_s, + double *sunrise, double *sunset, double *solar_noon, + double *civil_dusk, double *nautical_dusk, double *golden_dusk); + +/* (c) Moon. phase is the synodic fraction [0,1): 0=new, .25=first quarter, + * .5=full, .75=last quarter. */ +double moon_phase(double unix_s); +double moon_illuminated_fraction(double phase); /* (1 - cos(2*pi*phase)) / 2 */ +int moon_phase_index(double phase); /* 0..7, see ASTRO_MOON_NAMES */ + +/* (d) Equation of time in minutes (+ = apparent sun ahead of mean/clock sun). */ +double equation_of_time(double unix_s); + +/* (e) 6-character Maidenhead locator for (lat, lon). out must hold >= 7 bytes. + * Writes "----\0" if either coordinate is non-finite. */ +void maidenhead(double lat, double lon, char out[7]); + +/* Phase-index -> short name. Index from moon_phase_index(). */ +extern const char *const ASTRO_MOON_NAMES[8]; + +#endif /* ASTRO_H */ diff --git a/mk4-time/Core/Inc/main.h b/mk4-time/Core/Inc/main.h index 539aba0..5cfa8ec 100644 --- a/mk4-time/Core/Inc/main.h +++ b/mk4-time/Core/Inc/main.h @@ -162,6 +162,15 @@ enum { MODE_DDMMYYYY, #endif + // Astro pack — GPS-derived astronomy read-outs. SATVIEW-style: the payload + // shows on the 10-char date row while the live clock keeps running on the + // time row. Enabled individually via the MODE_* config keys, like any mode. + MODE_SUN, // sunrise / sunset / solar noon (local), auto-paged + MODE_SUN_AZEL, // sun azimuth & elevation, now + MODE_MOON, // moon phase index + illuminated % + MODE_GRID, // Maidenhead grid locator + MODE_LATLON, // latitude / longitude, auto-paged + NUM_DISPLAY_MODES }; diff --git a/mk4-time/Core/Src/astro.c b/mk4-time/Core/Src/astro.c new file mode 100644 index 0000000..e34f680 --- /dev/null +++ b/mk4-time/Core/Src/astro.c @@ -0,0 +1,140 @@ +/* + * astro.c — see astro.h. + * Pure C99 + ; every intermediate is double on purpose (single-precision + * loses the sub-arc-minute accuracy these approximations are otherwise good for). + */ +#include "astro.h" +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define J2000_UNIX 946728000.0 /* 2000-01-01 12:00:00 UTC = JD 2451545.0 */ +#define DEG (M_PI / 180.0) +#define RAD (180.0 / M_PI) + +const char *const ASTRO_MOON_NAMES[8] = { + "New", "Waxing Crescent", "First Quarter", "Waxing Gibbous", + "Full", "Waning Gibbous", "Last Quarter", "Waning Crescent", +}; + +static double days_since_j2000(double unix_s) { return (unix_s - J2000_UNIX) / 86400.0; } + +/* The shared low-precision solar block: from days-since-J2000 produce mean + * longitude L, anomaly g, ecliptic longitude lambda, obliquity eps, and the + * apparent right ascension alpha (deg) and declination delta (rad). */ +static void sun_ecliptic(double n, double *L, double *g, double *lambda, + double *eps, double *alpha, double *delta) { + double Lv = fmod(280.460 + 0.9856474 * n, 360.0); + double gv = fmod(357.528 + 0.9856003 * n, 360.0); + double lam = Lv + 1.915 * sin(gv * DEG) + 0.020 * sin(2.0 * gv * DEG); + double ep = (23.439 - 0.0000004 * n) * DEG; + double al = atan2(cos(ep) * sin(lam * DEG), cos(lam * DEG)) * RAD; + double de = asin(sin(ep) * sin(lam * DEG)); + if (L) *L = Lv; + if (g) *g = gv; + if (lambda) *lambda = lam; + if (eps) *eps = ep; + if (alpha) *alpha = al; + if (delta) *delta = de; +} + +void sun_az_el(double lat, double lon, double unix_s, double *az, double *el) { + double n = days_since_j2000(unix_s); + double alpha, delta; + sun_ecliptic(n, NULL, NULL, NULL, NULL, &alpha, &delta); + + double gmst = fmod(18.697374558 + 24.06570982441908 * n, 24.0); /* GMST in hours, used as-is */ + double lst = gmst * 15.0 + lon; /* degrees */ + double ha = (lst - alpha) * DEG; /* radians */ + + double e = asin(sin(lat * DEG) * sin(delta) + cos(lat * DEG) * cos(delta) * cos(ha)) * RAD; + double a = atan2(-sin(ha), tan(delta) * cos(lat * DEG) - sin(lat * DEG) * cos(ha)) * RAD; + if (a < 0.0) a += 360.0; + if (el) *el = e; + if (az) *az = a; +} + +double equation_of_time(double unix_s) { + double n = days_since_j2000(unix_s); + double L, alpha; + sun_ecliptic(n, &L, NULL, NULL, NULL, &alpha, NULL); + double diff = fmod(L - alpha, 360.0); + if (diff > 180.0) diff -= 360.0; + else if (diff <= -180.0) diff += 360.0; + return 4.0 * diff; /* minutes */ +} + +int sun_times(double lat, double lon, double unix_s, + double *sunrise, double *sunset, double *solar_noon, + double *civil_dusk, double *nautical_dusk, double *golden_dusk) { + /* Reference instant = 12:00:00 UTC of the calendar day of unix_s. */ + double noon_unix = trunc(unix_s / 86400.0) * 86400.0 + 43200.0; + double n = days_since_j2000(noon_unix); + double delta; + sun_ecliptic(n, NULL, NULL, NULL, NULL, NULL, &delta); + double eot = equation_of_time(noon_unix); /* minutes */ + + double noon = 12.0 - eot / 60.0 - lon / 15.0; + if (solar_noon) *solar_noon = noon; + + /* Hour angle (deg) at which the sun centre sits at altitude h (deg). */ + double cosO = (sin(-0.833 * DEG) - sin(lat * DEG) * sin(delta)) / (cos(lat * DEG) * cos(delta)); + if (cosO < -1.0 || cosO > 1.0) return 1; /* polar day / night: no rise or set */ + double omega = acos(cosO) * RAD; + if (sunrise) *sunrise = noon - omega / 15.0; + if (sunset) *sunset = noon + omega / 15.0; + + if (civil_dusk) { + double c = (sin(-6.0 * DEG) - sin(lat * DEG) * sin(delta)) / (cos(lat * DEG) * cos(delta)); + double o = (c < -1.0 || c > 1.0) ? omega : acos(c) * RAD; + *civil_dusk = noon + o / 15.0; + } + if (nautical_dusk) { + double c = (sin(-12.0 * DEG) - sin(lat * DEG) * sin(delta)) / (cos(lat * DEG) * cos(delta)); + double o = (c < -1.0 || c > 1.0) ? omega : acos(c) * RAD; + *nautical_dusk = noon + o / 15.0; + } + if (golden_dusk) { + double c = (sin(6.0 * DEG) - sin(lat * DEG) * sin(delta)) / (cos(lat * DEG) * cos(delta)); + double o = (c < -1.0 || c > 1.0) ? omega : acos(c) * RAD; + *golden_dusk = noon + o / 15.0; + } + return 0; +} + +double moon_phase(double unix_s) { + double n = days_since_j2000(unix_s); + double phase = fmod((n - 5.26) / 29.53059, 1.0); + if (phase < 0.0) phase += 1.0; + return phase; +} + +double moon_illuminated_fraction(double phase) { return (1.0 - cos(2.0 * M_PI * phase)) / 2.0; } + +int moon_phase_index(double phase) { return ((int)(phase * 8.0 + 0.5)) & 7; } + +void maidenhead(double lat, double lon, char out[7]) { + if (!isfinite(lat) || !isfinite(lon)) { strcpy(out, "----"); return; } + lat = fmin(nextafter(90.0, -INFINITY), fmax(-90.0, lat)); + lon = fmin(nextafter(180.0, -INFINITY), fmax(-180.0, lon)); + double LON = lon + 180.0; /* [0,360) */ + double LAT = lat + 90.0; /* [0,180) */ + + int f0 = (int)(LON / 20.0); if (f0 < 0) f0 = 0; if (f0 > 17) f0 = 17; + int f1 = (int)(LAT / 10.0); if (f1 < 0) f1 = 0; if (f1 > 17) f1 = 17; + int s2 = (int)(fmod(LON, 20.0) / 2.0); if (s2 < 0) s2 = 0; if (s2 > 9) s2 = 9; + int s3 = (int)(fmod(LAT, 10.0)); if (s3 < 0) s3 = 0; if (s3 > 9) s3 = 9; + int u4 = (int)(fmod(LON, 2.0) * 12.0); if (u4 < 0) u4 = 0; if (u4 > 23) u4 = 23; + int u5 = (int)(fmod(LAT, 1.0) * 24.0); if (u5 < 0) u5 = 0; if (u5 > 23) u5 = 23; + + out[0] = (char)('A' + f0); + out[1] = (char)('A' + f1); + out[2] = (char)('0' + s2); + out[3] = (char)('0' + s3); + out[4] = (char)('a' + u4); + out[5] = (char)('a' + u5); + out[6] = '\0'; +} diff --git a/mk4-time/Core/Src/main.c b/mk4-time/Core/Src/main.c index 2a35026..e45954f 100644 --- a/mk4-time/Core/Src/main.c +++ b/mk4-time/Core/Src/main.c @@ -32,6 +32,7 @@ #include "qspi_drv.h" #include "zonedetect.h" #include "chainloader.h" +#include "astro.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -148,6 +149,20 @@ uint8_t decisec=0, centisec=0, millisec=0; float longitude=-9999, latitude=-9999; _Bool data_valid=0, had_pps=0, rtc_good=0, new_position=1; + +// Astro pack — sun/moon/grid read-outs, computed once a second in the main loop +// (astro_update) and formatted by the sendDate cases, the same compute-in-loop / +// format-in-ISR split MODE_VBAT uses for vbat. +struct astro_cache_s { + uint32_t epoch; // currentTime this was computed for; 0 = never computed + _Bool have_pos; // a usable lat/lon was available + _Bool sun_up_today; // false = polar day/night (no rise/set this date) + int16_t rise_min, set_min, noon_min; // local minutes-of-day [0,1440) + int16_t az, el; // sun azimuth 0..359 / elevation, whole degrees + uint8_t moon_idx, moon_pct; // phase index 0..7 / illuminated % + char grid[8]; // Maidenhead locator, or "----" + float lat_show, lon_show; // the snapshot lat/lon, for MODE_LATLON +} astro = {0}; #define rtc_last_write RTC->BKP30R #define rtc_last_calibration RTC->BKP31R uint32_t last_pps_time = 0; @@ -216,6 +231,57 @@ void memcpyword(volatile uint32_t *dest, volatile uint32_t *src, size_t n){ } // 12 bytes at 115200 8E1 is 1.14ms, 32 bytes would be 3.06ms +// --- Astro pack helpers ---------------------------------------------------- +// A usable position is held in latitude/longitude from either a GPS fix or the +// configured fake_latitude/fake_longitude; both sit at the -9999 sentinel until +// a position is known, so a simple range check is the "have we got a fix" test. +static _Bool astro_pos_ok(float lat, float lon){ + return lat >= -90.0f && lat <= 90.0f && lon >= -180.0f && lon <= 180.0f; +} +// Decimal UTC hour (sun_times may return <0 or >24) -> local minutes-of-day [0,1440). +static int astro_local_minutes(double utc_h){ + double h = fmod(utc_h + currentOffset / 3600.0, 24.0); + if (h < 0) h += 24.0; + int m = (int)(h * 60.0 + 0.5); + if (m >= 1440) m -= 1440; + return m; +} +// Recompute the astro cache (called from the main loop, never the ISR). The +// double soft-float maths runs here, then the small result struct is swapped in +// under a brief IRQ mask so sendDate() always reads a consistent snapshot. +static void astro_update(void){ + if (astro.epoch == (uint32_t)currentTime) return; // at most once a second + struct astro_cache_s c = {0}; + c.epoch = (uint32_t)currentTime; + double ph = moon_phase((double)currentTime); // moon needs no fix + c.moon_idx = moon_phase_index(ph); + c.moon_pct = (uint8_t)(moon_illuminated_fraction(ph) * 100.0 + 0.5); + float lat = latitude, lon = longitude; // one consistent snapshot of the fix + c.have_pos = astro_pos_ok(lat, lon); + if (c.have_pos) { + c.lat_show = lat; + c.lon_show = lon; + double az, el, rise = 0, set = 0, noon = 0; + sun_az_el(lat, lon, (double)currentTime, &az, &el); + int ia = (int)(az + 0.5); if (ia >= 360) ia -= 360; + c.az = (int16_t)ia; + c.el = (int16_t)(el < 0 ? el - 0.5 : el + 0.5); + c.sun_up_today = (sun_times(lat, lon, (double)currentTime, + &rise, &set, &noon, 0, 0, 0) == 0); + c.noon_min = (int16_t)astro_local_minutes(noon); // noon is valid even at the poles + if (c.sun_up_today) { + c.rise_min = (int16_t)astro_local_minutes(rise); + c.set_min = (int16_t)astro_local_minutes(set); + } + maidenhead(lat, lon, c.grid); + } else { + strcpy(c.grid, "----"); + } + __disable_irq(); + astro = c; + __enable_irq(); +} + void sendDate( _Bool now ){ if (waitingForLatch) { if (countMode==COUNT_HIDDEN) { @@ -472,6 +538,47 @@ void sendDate( _Bool now ){ case MODE_FIRMWARE_CRC_D: uart2_tx_buffer[0]=CMD_SHOW_CRC; break; + + // --- Astro pack: format the main-loop-computed cache onto the date row only, + // leaving the time row as the running clock (SATVIEW-style). -------------- + case MODE_SUN: { + if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "RISE ----"); break; } + int page = (currentTime / 2) % 3; // rise -> set -> solar noon, 2 s each + // labels padded to 4 chars in the literal ("SET "/"SOL ") so the time digits + // line up under RISE without relying on the nano printf honouring "%-4s" + const char *lbl = page == 0 ? "RISE" : page == 1 ? "SET " : "SOL "; + int m = page == 0 ? astro.rise_min : page == 1 ? astro.set_min : astro.noon_min; + if (!astro.sun_up_today && page != 2) { // sun never rises/sets today + i = sprintf((char*)&uart2_tx_buffer[1], "%s ----", lbl); + } else { + i = sprintf((char*)&uart2_tx_buffer[1], "%s %02d.%02d", lbl, m / 60, m % 60); + } + break; + } + case MODE_SUN_AZEL: + if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "AZ -- EL--"); } + else if (astro.el < 0) i = sprintf((char*)&uart2_tx_buffer[1], "AZ%03dEL-%02d", astro.az, -astro.el); + else i = sprintf((char*)&uart2_tx_buffer[1], "AZ%03dEL%02d", astro.az, astro.el); + break; + case MODE_MOON: // UTC only; no fix needed + if (!astro.epoch) i = sprintf((char*)&uart2_tx_buffer[1], "MOON -"); + else i = sprintf((char*)&uart2_tx_buffer[1], "MOON %d %3d", astro.moon_idx, astro.moon_pct); + break; + case MODE_GRID: + i = sprintf((char*)&uart2_tx_buffer[1], "%s", astro.epoch ? astro.grid : "----"); + break; + case MODE_LATLON: + if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "LAT ----"); } + else if ((currentTime / 2) % 2 == 0) { // page latitude / longitude, 2 s each + long h = (long)(astro.lat_show * 100.0 + (astro.lat_show < 0 ? -0.5 : 0.5)); // hundredths, rounded + if (h < 0) i = sprintf((char*)&uart2_tx_buffer[1], "LAT-%ld.%02ld", -h / 100, -h % 100); + else i = sprintf((char*)&uart2_tx_buffer[1], "LAT %ld.%02ld", h / 100, h % 100); + } else { + long h = (long)(astro.lon_show * 100.0 + (astro.lon_show < 0 ? -0.5 : 0.5)); + if (h < 0) i = sprintf((char*)&uart2_tx_buffer[1], "LON-%ld.%02ld", -h / 100, -h % 100); + else i = sprintf((char*)&uart2_tx_buffer[1], "LON %ld.%02ld", h / 100, h % 100); + } + break; } if (now) { uart2_tx_buffer[++i]= CMD_RELOAD_TEXT; @@ -999,6 +1106,16 @@ void parseConfigString(char *key, char *value) { } else if (strcasecmp(key, "MODE_FIRMWARE_CRC") == 0) { set_mode_enabled(MODE_FIRMWARE_CRC_D, value); set_mode_enabled(MODE_FIRMWARE_CRC_T, value); + } else if (strcasecmp(key, "MODE_SUN") == 0) { + set_mode_enabled(MODE_SUN, value); + } else if (strcasecmp(key, "MODE_SUN_AZEL") == 0) { + set_mode_enabled(MODE_SUN_AZEL, value); + } else if (strcasecmp(key, "MODE_MOON") == 0) { + set_mode_enabled(MODE_MOON, value); + } else if (strcasecmp(key, "MODE_GRID") == 0) { + set_mode_enabled(MODE_GRID, value); + } else if (strcasecmp(key, "MODE_LATLON") == 0) { + set_mode_enabled(MODE_LATLON, value); } else if (strcasecmp(key, "Tolerance_time_1ms") == 0) { config.tolerance_1ms = atoi(value); } else if (strcasecmp(key, "Tolerance_time_10ms") == 0) { @@ -2127,6 +2244,10 @@ int main(void) if (displayMode == MODE_VBAT) measure_vbat(); + if (displayMode == MODE_SUN || displayMode == MODE_SUN_AZEL || displayMode == MODE_MOON + || displayMode == MODE_GRID || displayMode == MODE_LATLON) + astro_update(); + /* USER CODE END WHILE */ diff --git a/mk4-time/test/test_astro.c b/mk4-time/test/test_astro.c new file mode 100644 index 0000000..4df94fc --- /dev/null +++ b/mk4-time/test/test_astro.c @@ -0,0 +1,104 @@ +/* Native unit test for ../Core/Src/astro.c against the reference vectors used to + * develop the astro pack. Not part of the firmware build (test/ is not a project + * source path); build & run on a host: + * + * cc -std=c99 -O2 -I ../Core/Inc ../Core/Src/astro.c test_astro.c -lm -o test_astro && ./test_astro + */ +#include "astro.h" +#include +#include +#include + +static int fails = 0, total = 0; + +static void chk(const char *name, double got, double exp, double tol) { + total++; + double d = fabs(got - exp); + if (d > tol || !isfinite(got)) { + printf(" FAIL %-28s got %.6f exp %.6f (|d|=%.6f > %.6f)\n", name, got, exp, d, tol); + fails++; + } else { + printf(" ok %-28s %.6f (|d|=%.2e)\n", name, got, d); + } +} + +static void chks(const char *name, const char *got, const char *exp) { + total++; + if (strcmp(got, exp) != 0) { printf(" FAIL %-28s got \"%s\" exp \"%s\"\n", name, got, exp); fails++; } + else printf(" ok %-28s \"%s\"\n", name, got); +} + +struct loc { const char *tag; double lat, lon, t; }; + +int main(void) { + struct loc V1 = {"Greenwich", 51.4779, -0.0015, 1718971200}; /* 2024-06-21 12:00 */ + struct loc V2 = {"Sydney", -33.8568, 151.2153, 1704067200}; /* 2024-01-01 00:00 */ + struct loc V3 = {"Quito", -0.1807, -78.4678, 1411819200}; /* 2014-09-27 12:00 */ + struct loc V4 = {"Fairbanks", 64.8378, -147.7164, 1671460200}; /* 2022-12-19 14:30 */ + struct loc *V[4] = {&V1, &V2, &V3, &V4}; + + double az, el; + printf("sun_az_el:\n"); + double exp_el[4] = {61.9537, 61.9872, 13.7817, -29.3334}; + double exp_az[4] = {179.0572, 75.1095, 91.7169, 82.8687}; + for (int i = 0; i < 4; i++) { + char nm[40]; + sun_az_el(V[i]->lat, V[i]->lon, V[i]->t, &az, &el); + sprintf(nm, "el %s", V[i]->tag); chk(nm, el, exp_el[i], 0.001); + sprintf(nm, "az %s", V[i]->tag); chk(nm, az, exp_az[i], 0.001); + } + + printf("equation_of_time (min):\n"); + double exp_eot[4] = {-1.92784, -3.09298, 8.99946, 2.88245}; + for (int i = 0; i < 4; i++) { + char nm[40]; sprintf(nm, "eot %s", V[i]->tag); + chk(nm, equation_of_time(V[i]->t), exp_eot[i], 0.0005); + } + + printf("moon_phase / illuminated:\n"); + double exp_ph[4] = {0.49108, 0.64968, 0.10744, 0.86985}; + double exp_il[4] = {0.99921, 0.79471, 0.10966, 0.15807}; + int exp_idx[4] = {4, 5, 1, 7}; + for (int i = 0; i < 4; i++) { + char nm[40]; double p = moon_phase(V[i]->t); + sprintf(nm, "phase %s", V[i]->tag); chk(nm, p, exp_ph[i], 0.0002); + sprintf(nm, "illum %s", V[i]->tag); chk(nm, moon_illuminated_fraction(p), exp_il[i], 0.0005); + sprintf(nm, "idx %s", V[i]->tag); chk(nm, moon_phase_index(p), exp_idx[i], 0.0); + } + + printf("sun_times (UTC h):\n"); + double rise, set, noon, civ, nau, gold; + /* V1 Greenwich */ + sun_times(V1.lat, V1.lon, V1.t, &rise, &set, &noon, &civ, &nau, &gold); + chk("noon V1", noon, 12.032231, 0.0003); + chk("rise V1", rise, 3.715903, 0.0003); + chk("set V1", set, 20.348558, 0.0003); + chk("civil V1", civ, 21.143501, 0.0003); + chk("naut V1", nau, 22.383822, 0.0003); + /* V3 Quito */ + sun_times(V3.lat, V3.lon, V3.t, &rise, &set, &noon, NULL, NULL, NULL); + chk("noon V3", noon, 17.081196, 0.0003); + chk("rise V3", rise, 11.025277, 0.0003); + chk("set V3", set, 23.137114, 0.0003); + /* V4 Fairbanks (events spill past midnight) */ + sun_times(V4.lat, V4.lon, V4.t, &rise, &set, &noon, &civ, &nau, &gold); + chk("noon V4", noon, 21.798861, 0.0005); + chk("rise V4", rise, 19.944940, 0.0005); + chk("set V4", set, 23.652783, 0.0005); + chk("civil V4", civ, 25.076604, 0.0005); + chk("naut V4", nau, 26.273118, 0.0005); + + printf("maidenhead:\n"); + char g[7]; + maidenhead(V1.lat, V1.lon, g); chks("grid V1", g, "IO91xl"); + maidenhead(V2.lat, V2.lon, g); chks("grid V2", g, "QF56od"); + maidenhead(V3.lat, V3.lon, g); chks("grid V3", g, "FI09st"); + maidenhead(V4.lat, V4.lon, g); chks("grid V4", g, "BP64du"); + maidenhead(51.508, -0.128, g); chks("Trafalgar Sq", g, "IO91wm"); + maidenhead(41.714, -72.728, g); chks("ARRL HQ", g, "FN31pr"); + maidenhead(-41.283, 174.745, g); chks("Wellington", g, "RE78ir"); + maidenhead(1.0 / 0.0, 0.0, g); chks("non-finite", g, "----"); + + printf("\n%d/%d passed, %d failed\n", total - fails, total, fails); + return fails ? 1 : 0; +} diff --git a/qspi/config.txt b/qspi/config.txt index 3606e81..f68d10f 100644 --- a/qspi/config.txt +++ b/qspi/config.txt @@ -100,3 +100,31 @@ mode_satview = 0 # show coin cell voltage mode_vbat = 0 + + +## astro modes (GPS-derived) +# These show on the date row while the live clock keeps running on the time row. +# They use the current position; with no GPS fix they fall back to fake_latitude/ +# fake_longitude below (handy indoors), or show "----" if neither is set. + +# Sunrise / sunset / solar noon (local time). Pages every 2 s: RISE -> SET -> SOL. +MODE_SUN = disabled + +# Sun azimuth & elevation right now, e.g. "AZ142EL38" (degrees; elevation may be negative). +MODE_SUN_AZEL = disabled + +# Moon: phase index 0-7 then illuminated %, e.g. "MOON 4 99". +# Index: 0 new, 1 waxing crescent, 2 first quarter, 3 waxing gibbous, +# 4 full, 5 waning gibbous, 6 last quarter, 7 waning crescent. +MODE_MOON = disabled + +# Maidenhead grid locator, e.g. "IO91xl". +MODE_GRID = disabled + +# Latitude / longitude in decimal degrees. Pages every 2 s: LAT -> LON. +MODE_LATLON = disabled + +# Fixed position for the astro modes when there is no GPS fix (decimal degrees, N+/E+). +# Note: setting these also pins the clock's position (GPS position updates are ignored). +#fake_latitude = 51.48 +#fake_longitude = -0.01 From 8405e1325cee0e96468d18f5351aca91b8ae3550 Mon Sep 17 00:00:00 2001 From: Peter Lewis Date: Sun, 5 Jul 2026 09:02:17 +0100 Subject: [PATCH 2/3] astro: give LAT/LON a sign slot so negatives keep the label gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "LAT 51.48" vs "LAT-51.48" let the minus swallow the separator. Add a sign slot after the label space — "LAT 51.48" / "LAT -51.48" — so the digits start in the same column either way, matching the RISE/SET layout. A 3-digit longitude can't fit both the separator and the slot in 10 characters, so the separator alone is dropped there ("LON-179.99"). Co-Authored-By: Claude Opus 4.8 --- mk4-time/Core/Src/main.c | 6215 +++++++++++++++++++------------------- 1 file changed, 3109 insertions(+), 3106 deletions(-) diff --git a/mk4-time/Core/Src/main.c b/mk4-time/Core/Src/main.c index e45954f..7734603 100644 --- a/mk4-time/Core/Src/main.c +++ b/mk4-time/Core/Src/main.c @@ -1,3106 +1,3109 @@ -/* USER CODE BEGIN Header */ -/** - ****************************************************************************** - * @file : main.c - * @brief : Main program body - ****************************************************************************** - * @attention - * - *

© Copyright (c) 2020 STMicroelectronics. - * All rights reserved.

- * - * This software component is licensed by ST under BSD 3-Clause license, - * the "License"; You may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * opensource.org/licenses/BSD-3-Clause - * - ****************************************************************************** - */ -/* USER CODE END Header */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" -#include "fatfs.h" -#include "usb_device.h" - -/* Private includes ----------------------------------------------------------*/ -/* USER CODE BEGIN Includes */ -#include -#include -#include -#include -#include "qspi_drv.h" -#include "zonedetect.h" -#include "chainloader.h" -#include "astro.h" -/* USER CODE END Includes */ - -/* Private typedef -----------------------------------------------------------*/ -/* USER CODE BEGIN PTD */ - -/* USER CODE END PTD */ - -/* Private define ------------------------------------------------------------*/ -/* USER CODE BEGIN PD */ -/* USER CODE END PD */ - -/* Private macro -------------------------------------------------------------*/ -/* USER CODE BEGIN PM */ - -/* USER CODE END PM */ - -/* Private variables ---------------------------------------------------------*/ -ADC_HandleTypeDef hadc1; -ADC_HandleTypeDef hadc3; - -CRC_HandleTypeDef hcrc; - -DAC_HandleTypeDef hdac1; -DMA_HandleTypeDef hdma_dac_ch1; - -QSPI_HandleTypeDef hqspi; - -RTC_HandleTypeDef hrtc; - -TIM_HandleTypeDef htim1; -TIM_HandleTypeDef htim2; -TIM_HandleTypeDef htim5; -TIM_HandleTypeDef htim6; -TIM_HandleTypeDef htim7; -DMA_HandleTypeDef hdma_tim1_up; -DMA_HandleTypeDef hdma_tim5_ch1; -DMA_HandleTypeDef hdma_tim5_ch2; -DMA_HandleTypeDef hdma_tim7_up; - -UART_HandleTypeDef huart1; -UART_HandleTypeDef huart2; -DMA_HandleTypeDef hdma_usart1_rx; -DMA_HandleTypeDef hdma_usart2_tx; - -/* USER CODE BEGIN PV */ - -/* USER CODE END PV */ - -/* Private function prototypes -----------------------------------------------*/ -void SystemClock_Config(void); -static void MX_GPIO_Init(void); -static void MX_DMA_Init(void); -static void MX_QUADSPI_Init(void); -static void MX_TIM1_Init(void); -static void MX_USART2_UART_Init(void); -static void MX_USART1_UART_Init(void); -static void MX_TIM2_Init(void); -static void MX_ADC1_Init(void); -static void MX_DAC1_Init(void); -static void MX_TIM6_Init(void); -static void MX_RTC_Init(void); -static void MX_TIM7_Init(void); -static void MX_CRC_Init(void); -static void MX_LPTIM1_Init(void); -static void MX_TIM5_Init(void); -static void MX_ADC3_Init(void); -/* USER CODE BEGIN PFP */ -void tmToBcd(struct tm *in, bcdStamp_t *out ); -uint8_t loadRulesSingle(char * str); -void nextMode(_Bool); -/* USER CODE END PFP */ - -/* Private user code ---------------------------------------------------------*/ -/* USER CODE BEGIN 0 */ -const uint8_t cLut[]= { cSegDecode0, cSegDecode1, cSegDecode2, cSegDecode3, cSegDecode4, cSegDecode5, cSegDecode6, cSegDecode7, cSegDecode8, cSegDecode9 }; -const uint16_t bLut[]={ bSegDecode0, bSegDecode1, bSegDecode2, bSegDecode3, bSegDecode4, bSegDecode5, bSegDecode6, bSegDecode7, bSegDecode8, bSegDecode9 }; - -const char* wday_str[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; - -buffer_c_t buffer_c[80] = {0}; - -uint16_t buffer_b[80] = {0}; - -uint8_t uart2_tx_buffer[32]; - -volatile uint16_t buffer_adc[ADC_BUFFER_SIZE] = {0}; -uint16_t buffer_dac[DAC_BUFFER_SIZE] = {[0 ... DAC_BUFFER_SIZE-1] = 4095}; -float dac_target=4095; -float vbat = 0.0; - -uint16_t buffer_colons_L[200] = {0}; -uint16_t buffer_colons_R[200] = {0}; - -uint8_t nmea[NMEA_BUF_SIZE]; -uint8_t satview[SV_COUNT]; -uint8_t satview_stale = 0; - -time_t currentTime; -bcdStamp_t nextBcd; -int tm_yday; -int8_t tm_wday; -int iso_year; -int8_t iso_wday; -uint8_t iso_week; -uint32_t countdown_days; -int32_t currentOffset=0; - -struct { - uint8_t c; - uint16_t b[5]; -} next7seg; - -uint8_t decisec=0, centisec=0, millisec=0; - -float longitude=-9999, latitude=-9999; -_Bool data_valid=0, had_pps=0, rtc_good=0, new_position=1; - -// Astro pack — sun/moon/grid read-outs, computed once a second in the main loop -// (astro_update) and formatted by the sendDate cases, the same compute-in-loop / -// format-in-ISR split MODE_VBAT uses for vbat. -struct astro_cache_s { - uint32_t epoch; // currentTime this was computed for; 0 = never computed - _Bool have_pos; // a usable lat/lon was available - _Bool sun_up_today; // false = polar day/night (no rise/set this date) - int16_t rise_min, set_min, noon_min; // local minutes-of-day [0,1440) - int16_t az, el; // sun azimuth 0..359 / elevation, whole degrees - uint8_t moon_idx, moon_pct; // phase index 0..7 / illuminated % - char grid[8]; // Maidenhead locator, or "----" - float lat_show, lon_show; // the snapshot lat/lon, for MODE_LATLON -} astro = {0}; -#define rtc_last_write RTC->BKP30R -#define rtc_last_calibration RTC->BKP31R -uint32_t last_pps_time = 0; -uint32_t time_till_first_fix = 0; - -struct { - uint32_t t; - int32_t offset; -} rules[162]; -#define MAX_RULES (sizeof rules / sizeof rules[0]) - -char loadedRulesString[32]; -char preloadRulesString[32]; -char textDisplay[32]; -_Bool delayedLoadRules = 0; -_Bool delayedReadConfigFile = 0; -_Bool delayedCheckOnEject = 0; -uint32_t delayedDisplayFreq = 0; - -_Bool waitingForLatch = 0; -_Bool resendDate = 0; - -uint32_t LPTIM1_high; - -uint8_t displayMode = 0, countMode = 0, colonMode = 0; -uint8_t requestMode = 255; -uint8_t nmea_cdc_level=0; -int debug_rtc_val = 0; - -#define CHECK_CONFIG_MTIME - -struct { -#ifdef CHECK_CONFIG_MTIME - unsigned short fdate; - unsigned short ftime; -#endif - uint32_t tolerance_1ms; - uint32_t tolerance_10ms; - uint32_t tolerance_100ms; - float fake_long; - float fake_lat; - time_t countdown_to; - float brightness_override; - volatile _Bool zone_override; - _Bool modes_enabled[NUM_DISPLAY_MODES]; - -} config = {0}; - -struct { - float in; - float out; -} brightnessCurve[] = { - {0, 4095-0}, - {1425, 4095-737}, - {2566, 4095-1601}, - {3396, 4095-2725}, - {4095, 4095-4095}, -}; - -// memcpy() appears to move data by bytes, which doesn't work with the word-accessed backup registers -// here we explicitly move data a word at a time -void memcpyword(volatile uint32_t *dest, volatile uint32_t *src, size_t n){ - while (n--){ - dest[n] = src[n]; - } -} - -// 12 bytes at 115200 8E1 is 1.14ms, 32 bytes would be 3.06ms -// --- Astro pack helpers ---------------------------------------------------- -// A usable position is held in latitude/longitude from either a GPS fix or the -// configured fake_latitude/fake_longitude; both sit at the -9999 sentinel until -// a position is known, so a simple range check is the "have we got a fix" test. -static _Bool astro_pos_ok(float lat, float lon){ - return lat >= -90.0f && lat <= 90.0f && lon >= -180.0f && lon <= 180.0f; -} -// Decimal UTC hour (sun_times may return <0 or >24) -> local minutes-of-day [0,1440). -static int astro_local_minutes(double utc_h){ - double h = fmod(utc_h + currentOffset / 3600.0, 24.0); - if (h < 0) h += 24.0; - int m = (int)(h * 60.0 + 0.5); - if (m >= 1440) m -= 1440; - return m; -} -// Recompute the astro cache (called from the main loop, never the ISR). The -// double soft-float maths runs here, then the small result struct is swapped in -// under a brief IRQ mask so sendDate() always reads a consistent snapshot. -static void astro_update(void){ - if (astro.epoch == (uint32_t)currentTime) return; // at most once a second - struct astro_cache_s c = {0}; - c.epoch = (uint32_t)currentTime; - double ph = moon_phase((double)currentTime); // moon needs no fix - c.moon_idx = moon_phase_index(ph); - c.moon_pct = (uint8_t)(moon_illuminated_fraction(ph) * 100.0 + 0.5); - float lat = latitude, lon = longitude; // one consistent snapshot of the fix - c.have_pos = astro_pos_ok(lat, lon); - if (c.have_pos) { - c.lat_show = lat; - c.lon_show = lon; - double az, el, rise = 0, set = 0, noon = 0; - sun_az_el(lat, lon, (double)currentTime, &az, &el); - int ia = (int)(az + 0.5); if (ia >= 360) ia -= 360; - c.az = (int16_t)ia; - c.el = (int16_t)(el < 0 ? el - 0.5 : el + 0.5); - c.sun_up_today = (sun_times(lat, lon, (double)currentTime, - &rise, &set, &noon, 0, 0, 0) == 0); - c.noon_min = (int16_t)astro_local_minutes(noon); // noon is valid even at the poles - if (c.sun_up_today) { - c.rise_min = (int16_t)astro_local_minutes(rise); - c.set_min = (int16_t)astro_local_minutes(set); - } - maidenhead(lat, lon, c.grid); - } else { - strcpy(c.grid, "----"); - } - __disable_irq(); - astro = c; - __enable_irq(); -} - -void sendDate( _Bool now ){ - if (waitingForLatch) { - if (countMode==COUNT_HIDDEN) { - // if we've entered count_hidden while waiting for latch, it will never happen - sendLatch() - waitingForLatch=0; - } else { - resendDate=1; - return; - } - } - - uint8_t i = 10; - HAL_UART_AbortTransmit(&huart2); - uart2_tx_buffer[0] = CMD_LOAD_TEXT; - - switch (displayMode) { - default: - case MODE_ISO8601_STD: - uart2_tx_buffer[1] ='2'; - uart2_tx_buffer[2] ='0'; - uart2_tx_buffer[3] ='0'+nextBcd.tenYears; - uart2_tx_buffer[4] ='0'+nextBcd.years; - uart2_tx_buffer[5] ='-'; - uart2_tx_buffer[6] ='0'+nextBcd.tenMonths; - uart2_tx_buffer[7] ='0'+nextBcd.months; - uart2_tx_buffer[8] ='-'; - uart2_tx_buffer[9] ='0'+nextBcd.tenDays; - uart2_tx_buffer[10]='0'+nextBcd.days; - break; -#ifdef NONCOMPLIANT_DATE_MODES - case MODE_DDMMYYYY: - uart2_tx_buffer[1] ='0'+nextBcd.tenDays; - uart2_tx_buffer[2] ='0'+nextBcd.days; - uart2_tx_buffer[3] ='-'; - uart2_tx_buffer[4] ='0'+nextBcd.tenMonths; - uart2_tx_buffer[5] ='0'+nextBcd.months; - uart2_tx_buffer[6] ='-'; - uart2_tx_buffer[7] ='2'; - uart2_tx_buffer[8] ='0'; - uart2_tx_buffer[9] ='0'+nextBcd.tenYears; - uart2_tx_buffer[10]='0'+nextBcd.years; - break; -#endif - case MODE_ISO_ORDINAL: - uart2_tx_buffer[1] ='2' ;//-2+nextBcd.seconds; - uart2_tx_buffer[2] ='0'; - uart2_tx_buffer[3] ='0'+nextBcd.tenYears; - uart2_tx_buffer[4] ='0'+nextBcd.years; - uart2_tx_buffer[5] ='-'; - i = 5 + sprintf((char*)&uart2_tx_buffer[6], "%d", tm_yday+1); - break; - case MODE_ISO_WEEK: - i = sprintf((char*)&uart2_tx_buffer[1], "%d-W%d-%d", iso_year, iso_week, iso_wday+1); - break; - case MODE_UNIX: - i = sprintf((char*)&uart2_tx_buffer[1], "%010ld", (uint32_t)currentTime); - break; - case MODE_JULIAN_DATE: - i = sprintf((char*)&uart2_tx_buffer[1], "%10f", (double)currentTime/86400.0 + 2440587.5 ); - break; - case MODE_MODIFIED_JD: - i = sprintf((char*)&uart2_tx_buffer[1], "%10f", (double)currentTime/86400.0 + 40587); - break; - case MODE_SHOW_OFFSET: - // This probably isn't the best place to do it, but the data is static anyway - - if (currentOffset<0){ - buffer_b[0]=bCat0 | 0b0000000000; - buffer_b[1]=bCat1 | 0b0100000000; - } else { - buffer_b[0]=bCat0 | 0b0100011000; - buffer_b[1]=bCat1 | 0b0111000000; - } - int minutes = ((abs(currentOffset)/60) %60); - int hours = (abs(currentOffset)/3600); - - buffer_b[2]=bCat2 | bLut[ hours/10 ]; - buffer_b[3]=bCat3 | bLut[ hours%10 ]; - buffer_b[4]=bCat4 | bLut[ minutes/10 ]; - - buffer_c[0].low= cLut[ minutes%10 ]; - buffer_c[0].high=0b11001110; - buffer_c[1].low=0; - buffer_c[2].low=0; - buffer_c[3].low=0; - - uart2_tx_buffer[1] ='u'; - uart2_tx_buffer[2] ='t'; - uart2_tx_buffer[3] ='c'; - uart2_tx_buffer[4] =' '; - uart2_tx_buffer[5] ='o'; - uart2_tx_buffer[6] ='f'; - uart2_tx_buffer[7] ='f'; - uart2_tx_buffer[8] ='s'; - uart2_tx_buffer[9] ='e'; - uart2_tx_buffer[10]='t'; - break; - case MODE_SHOW_TZ_NAME: - if (loadedRulesString[0]) { - char * zo = loadedRulesString; - while (*zo && *zo != '/') zo++; - if (currentTime%4 <2) { - zo++; - i = snprintf((char*)&uart2_tx_buffer[1], 11,"%s", zo); - } else { - i = zo-loadedRulesString; - if (i>10) i=10; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-truncation" - snprintf((char*)&uart2_tx_buffer[1], i+1,"%s", loadedRulesString); -#pragma GCC diagnostic pop - } - } else { - uart2_tx_buffer[1]='-'; - i=1; - } - break; - case MODE_WEEKDAY: - i = sprintf((char*)&uart2_tx_buffer[1], "%s", wday_str[tm_wday]); - break; - case MODE_WEEKDA_DD: - sprintf((char*)&uart2_tx_buffer[1], "%-7.7s ", wday_str[tm_wday]); - uart2_tx_buffer[9] ='0'+nextBcd.tenDays; - uart2_tx_buffer[10]='0'+nextBcd.days; - break; - case MODE_WDY_MM_DD: - sprintf((char*)&uart2_tx_buffer[1], "%.4s ", wday_str[tm_wday]); - uart2_tx_buffer[6] ='0'+nextBcd.tenMonths; - uart2_tx_buffer[7] ='0'+nextBcd.months; - uart2_tx_buffer[8] ='-'; - uart2_tx_buffer[9] ='0'+nextBcd.tenDays; - uart2_tx_buffer[10]='0'+nextBcd.days; - break; - case MODE_SATVIEW: - if (satview[SV_GPS_L1]==255 && satview[SV_GPS_UNKNOWN]==255) { - i = sprintf((char*)&uart2_tx_buffer[1], "GPS -"); - } else { - uint8_t GPS_sv = 0, GLONASS_sv = 0, GALILEO_sv = 0, BEIDOU_sv = 0; - if (satview[SV_GPS_L1]!=255) GPS_sv += satview[SV_GPS_L1]; - if (satview[SV_GPS_UNKNOWN]!=255) GPS_sv += satview[SV_GPS_UNKNOWN]; - if (satview[SV_GLONASS_L1]!=255) GLONASS_sv += satview[SV_GLONASS_L1]; - if (satview[SV_GLONASS_UNKNOWN]!=255) GLONASS_sv += satview[SV_GLONASS_UNKNOWN]; - if (satview[SV_GALILEO_E1]!=255) GALILEO_sv += satview[SV_GALILEO_E1]; - if (satview[SV_GALILEO_UNKNOWN]!=255) GALILEO_sv += satview[SV_GALILEO_UNKNOWN]; - if (satview[SV_BEIDOU_B1]!=255) BEIDOU_sv += satview[SV_BEIDOU_B1]; - if (satview[SV_BEIDOU_UNKNOWN]!=255) BEIDOU_sv += satview[SV_BEIDOU_UNKNOWN]; - - if (GLONASS_sv>0 && GLONASS_sv>=GALILEO_sv && GLONASS_sv>=BEIDOU_sv) { - i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d L%d", GPS_sv, GLONASS_sv); - } else if (GALILEO_sv>0 && GALILEO_sv>=GLONASS_sv && GALILEO_sv>=BEIDOU_sv){ - i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d A%d", GPS_sv, GALILEO_sv); - } else if (BEIDOU_sv>0 && BEIDOU_sv>=GLONASS_sv && BEIDOU_sv>=GALILEO_sv){ - i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d b%d", GPS_sv, BEIDOU_sv); - } else { - i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d -", GPS_sv); - } - } - break; - case MODE_STANDBY: - return; - case MODE_COUNTDOWN: - i = sprintf((char*)&uart2_tx_buffer[1], "t-%7ldd", countdown_days); - break; - case MODE_DEBUG_BRIGHTNESS: - i = sprintf((char*)&uart2_tx_buffer[1], "%04d %04d", (int)ADC1->DR, 4095-(int)dac_target); - break; - case MODE_DEBUG_RTC: - i = sprintf((char*)&uart2_tx_buffer[1], "rtc %d", debug_rtc_val); - break; - case MODE_TEXT: - if (textDisplay[0]) { - i = snprintf((char*)&uart2_tx_buffer[1], 30,"%s", textDisplay); - } else { - uart2_tx_buffer[1]='-'; - i=1; - } - break; - case MODE_VBAT: - if (vbat == 0.0) { - i = sprintf((char*)&uart2_tx_buffer[1], "bat -"); - } else { - i = sprintf((char*)&uart2_tx_buffer[1], "bat %.4f", vbat); - } - break; - case MODE_TTFF: - // Our assumption is that uwTick is zero at power on - if (!had_pps) time_till_first_fix = (int)(uwTick/1000); - i = sprintf((char*)&uart2_tx_buffer[1], "ttff %3d.%02d", (int)(time_till_first_fix/60), (int)(time_till_first_fix%60)); - break; - case MODE_DISPLAYTEST: - int nn = currentTime%10; - - TIM2->CCR1 = 0; - TIM2->CCR2 = 0; - buffer_c[0].high &= ~cSegDP; - buffer_c[1].high &= ~cSegDP; - buffer_c[2].high &= ~cSegDP; - buffer_c[3].high &= ~cSegDP; - - if ((currentTime%20)<10) { - uart2_tx_buffer[1] = - uart2_tx_buffer[2] = - uart2_tx_buffer[3] = - uart2_tx_buffer[4] = - uart2_tx_buffer[5] = - uart2_tx_buffer[6] = - uart2_tx_buffer[7] = - uart2_tx_buffer[8] = - uart2_tx_buffer[9] = - uart2_tx_buffer[10]= '0'+ nn; - - buffer_b[0]=bCat0 | bLut[ nn ]; - buffer_b[1]=bCat1 | bLut[ nn ]; - buffer_b[2]=bCat2 | bLut[ nn ]; - buffer_b[3]=bCat3 | bLut[ nn ]; - buffer_b[4]=bCat4 | bLut[ nn ]; - - buffer_c[0].low= cLut[ nn ]; - buffer_c[1].low=cLut[ nn ]; - buffer_c[2].low=cLut[ nn ]; - buffer_c[3].low=cLut[ nn ]; - - if ((currentTime%2) ==0) { - TIM2->CCR2 = 300; - } else { - TIM2->CCR1 = 300; - } - } else { - - buffer_b[0]=bCat0 | (nn==0?bLut[8]:0); - buffer_b[1]=bCat1 | (nn==1?bLut[8]:0); - buffer_b[2]=bCat2 | (nn==2?bLut[8]:0); - buffer_b[3]=bCat3 | (nn==3?bLut[8]:0); - buffer_b[4]=bCat4 | (nn==4?bLut[8]:0); - buffer_c[0].low=(nn==5?cLut[8]:0); - buffer_c[1].low=(nn==6?cLut[8]:0); - buffer_c[2].low=(nn==7?cLut[8]:0); - buffer_c[3].low=(nn==8?cLut[8]:0); - - if (nn>=5) buffer_c[nn-5].high |= cSegDP; - - i = sprintf((char*)&uart2_tx_buffer[1], "%*s8.", nn, ""); - } - - break; - case MODE_FIRMWARE_CRC_T: - { - extern uint32_t _app_crc[]; - uint32_t fwt = byteswap32(_app_crc[0]); - i = sprintf((char*)&uart2_tx_buffer[1], "t %08lx", fwt); - } - break; - case MODE_FIRMWARE_CRC_D: - uart2_tx_buffer[0]=CMD_SHOW_CRC; - break; - - // --- Astro pack: format the main-loop-computed cache onto the date row only, - // leaving the time row as the running clock (SATVIEW-style). -------------- - case MODE_SUN: { - if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "RISE ----"); break; } - int page = (currentTime / 2) % 3; // rise -> set -> solar noon, 2 s each - // labels padded to 4 chars in the literal ("SET "/"SOL ") so the time digits - // line up under RISE without relying on the nano printf honouring "%-4s" - const char *lbl = page == 0 ? "RISE" : page == 1 ? "SET " : "SOL "; - int m = page == 0 ? astro.rise_min : page == 1 ? astro.set_min : astro.noon_min; - if (!astro.sun_up_today && page != 2) { // sun never rises/sets today - i = sprintf((char*)&uart2_tx_buffer[1], "%s ----", lbl); - } else { - i = sprintf((char*)&uart2_tx_buffer[1], "%s %02d.%02d", lbl, m / 60, m % 60); - } - break; - } - case MODE_SUN_AZEL: - if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "AZ -- EL--"); } - else if (astro.el < 0) i = sprintf((char*)&uart2_tx_buffer[1], "AZ%03dEL-%02d", astro.az, -astro.el); - else i = sprintf((char*)&uart2_tx_buffer[1], "AZ%03dEL%02d", astro.az, astro.el); - break; - case MODE_MOON: // UTC only; no fix needed - if (!astro.epoch) i = sprintf((char*)&uart2_tx_buffer[1], "MOON -"); - else i = sprintf((char*)&uart2_tx_buffer[1], "MOON %d %3d", astro.moon_idx, astro.moon_pct); - break; - case MODE_GRID: - i = sprintf((char*)&uart2_tx_buffer[1], "%s", astro.epoch ? astro.grid : "----"); - break; - case MODE_LATLON: - if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "LAT ----"); } - else if ((currentTime / 2) % 2 == 0) { // page latitude / longitude, 2 s each - long h = (long)(astro.lat_show * 100.0 + (astro.lat_show < 0 ? -0.5 : 0.5)); // hundredths, rounded - if (h < 0) i = sprintf((char*)&uart2_tx_buffer[1], "LAT-%ld.%02ld", -h / 100, -h % 100); - else i = sprintf((char*)&uart2_tx_buffer[1], "LAT %ld.%02ld", h / 100, h % 100); - } else { - long h = (long)(astro.lon_show * 100.0 + (astro.lon_show < 0 ? -0.5 : 0.5)); - if (h < 0) i = sprintf((char*)&uart2_tx_buffer[1], "LON-%ld.%02ld", -h / 100, -h % 100); - else i = sprintf((char*)&uart2_tx_buffer[1], "LON %ld.%02ld", h / 100, h % 100); - } - break; - } - if (now) { - uart2_tx_buffer[++i]= CMD_RELOAD_TEXT; - } else { - uart2_tx_buffer[++i]= '\n'; - waitingForLatch=1; - } - HAL_UART_Transmit_DMA(&huart2, uart2_tx_buffer, i+1); - -} - -void setNextTimestamp(time_t nextTime){ - - int32_t offset = 0; - for (uint8_t i=0; i< MAX_RULES; i++) { - if (rules[i].t <= nextTime) offset=rules[i].offset; - else break; - } - // in case of the remote chance that we're interrupted while calculating, - // don't assign to currentOffset until the end of the loop - currentOffset = offset; - nextTime += offset; - - struct tm * nextTm = gmtime( &nextTime ); - tmToBcd( nextTm, &nextBcd ); - tm_yday = nextTm->tm_yday; - tm_wday = nextTm->tm_wday; - - if (displayMode == MODE_ISO_WEEK){ - iso_wday = (nextTm->tm_wday + 6) % 7; - nextTm->tm_mday -= iso_wday -3; - mktime(nextTm); - iso_year = nextTm->tm_year + 1900; - iso_week = nextTm->tm_yday/7 + 1; - } - - next7seg.c = cLut[nextBcd.seconds]; - - next7seg.b[0] = bCat0 | cLut[nextBcd.tenHours]<<2; - next7seg.b[1] = bCat1 | cLut[nextBcd.hours]<<2; - next7seg.b[2] = bCat2 | cLut[nextBcd.tenMinutes]<<2; - next7seg.b[3] = bCat3 | cLut[nextBcd.minutes]<<2; - next7seg.b[4] = bCat4 | cLut[nextBcd.tenSeconds]<<2; - -} - -void setNextCountdown(time_t nextTime){ - - int64_t remaining; - if (config.countdown_to < nextTime) { - remaining = 0; - SetPPS( &PPS_NoUpdate ); // don't show 999 at the next pulse - - } else remaining = config.countdown_to - nextTime; - - uint64_t seconds = remaining % 60; - uint64_t minutes = remaining / 60; - uint64_t hours = minutes / 60; - minutes %= 60; - countdown_days = hours / 24; - hours %= 24; - - next7seg.b[0] = bCat0 | cLut[hours / 10]<<2; - next7seg.b[1] = bCat1 | cLut[hours % 10]<<2; - next7seg.b[2] = bCat2 | cLut[minutes / 10]<<2; - next7seg.b[3] = bCat3 | cLut[minutes % 10]<<2; - next7seg.b[4] = bCat4 | cLut[seconds / 10]<<2; - next7seg.c = cLut[seconds % 10]; -} - -// Store UTC on RTC -// need to also write zone into backup registers -// Only called at the start of a second, don't attempt to write subseconds. -void write_rtc(void){ - - RTC_DateTypeDef sdatestructure; - RTC_TimeTypeDef stimestructure; - bcdStamp_t cBcd; - struct tm * cTm = gmtime( ¤tTime ); - - tmToBcd( cTm, &cBcd ); - - sdatestructure.Year = (cBcd.tenYears<<4) | cBcd.years; - sdatestructure.Month = (cBcd.tenMonths<<4) | cBcd.months; - sdatestructure.Date = (cBcd.tenDays<<4) | cBcd.days; - sdatestructure.WeekDay = RTC_WEEKDAY_MONDAY; - - HAL_RTC_SetDate(&hrtc,&sdatestructure,RTC_FORMAT_BCD); - - stimestructure.Hours = (cBcd.tenHours<<4) | cBcd.hours; - stimestructure.Minutes = (cBcd.tenMinutes<<4) | cBcd.minutes; - stimestructure.Seconds = (cBcd.tenSeconds<<4) | cBcd.seconds; - stimestructure.SubSeconds = 0x00; - stimestructure.TimeFormat = RTC_HOURFORMAT12_AM; - stimestructure.DayLightSaving = RTC_DAYLIGHTSAVING_NONE ; - stimestructure.StoreOperation = RTC_STOREOPERATION_RESET; - - HAL_RTC_SetTime(&hrtc,&stimestructure,RTC_FORMAT_BCD); - - // Write zone info to backup registers - // There are 32 words of memory, 128 bytes - // First 8 words are the zone string including separator and null byte (always less than 32 bytes) - // Next 22 words is a chunk of the ruleset in use, i.e. 11 years - // Last two words are time of write, and time of last calibration - - uint8_t i; - for (i=0; i< MAX_RULES; i++) { - if (rules[i].t > currentTime) break; - } - if (i==0) return; //something has gone wrong, data invalid - i--; //include currently active rule - - char numRulesToStore = (i+11>=MAX_RULES-1)? (MAX_RULES-i)*2 : 22; - - memcpyword( (uint32_t*)&(RTC->BKP0R), (uint32_t*)loadedRulesString, 8 ); - memcpyword( (uint32_t*)&(RTC->BKP8R), (uint32_t*)&rules[i], numRulesToStore ); - - rtc_last_write = (uint32_t)currentTime; -} - -time_t bcdToTm(bcdStamp_t *in, struct tm *out ) { - out->tm_isdst = 0; - out->tm_sec = in->seconds + in->tenSeconds*10; - out->tm_min = in->minutes + in->tenMinutes*10; - out->tm_hour = in->hours + in->tenHours*10; - out->tm_mday = in->days + in->tenDays*10; - out->tm_mon = in->months + in->tenMonths*10 -1; - out->tm_year = in->years + in->tenYears*10 + 100; //Years since 1900 - - return mktime(out); -} -void tmToBcd(struct tm *in, bcdStamp_t *out ) { - out->tenYears = (in->tm_year-100) / 10; - out->years = (in->tm_year-100) % 10; - out->tenMonths = (in->tm_mon+1) / 10; - out->months = (in->tm_mon+1) % 10; - out->tenDays = in->tm_mday / 10; - out->days = in->tm_mday % 10; - out->tenHours = in->tm_hour / 10; - out->hours = in->tm_hour % 10; - out->tenMinutes = in->tm_min / 10; - out->minutes = in->tm_min % 10; - out->tenSeconds = in->tm_sec / 10; - out->seconds = in->tm_sec % 10; -} - -void decodeRMC(void){ - - // do checksum - uint8_t *c = &nmea[1], *end = &nmea[sizeof(nmea)]; - uint8_t sum=0; - - bcdStamp_t rmcBcd; - struct tm rmcTm; - - while (*c !='*') { - sum ^= *c; - if (*c==',') *c=0; - c++; - if(c==end) return; //checksum not found - } - - sprintf((char*)nmea, "%02X", sum); - if (nmea[0] != c[1] || nmea[1]!=c[2]) return; //checksum error - -#define nextField() while (*c && c!=end) c++; c++; - - c=&nmea[7]; // Time - - if (*c==0) return; // time not present - - rmcBcd.tenHours = *c++ -'0'; - rmcBcd.hours = *c++ -'0'; - rmcBcd.tenMinutes = *c++ -'0'; - rmcBcd.minutes = *c++ -'0'; - rmcBcd.tenSeconds = *c++ -'0'; - rmcBcd.seconds = *c++ -'0'; - - if (*c++ =='.') { // subseconds not always present - //if (*c!='0') printf("subseconds non-zero: %s\n", c); - } - nextField() // Navigation receiver warning - data_valid = (*c=='A'?1:0); - - float tempLatitude=-9999, tempLongitude=-9999; - - nextField() // Latitude deg - if (*c){ - tempLatitude = (float)(*c++ -'0')*10.0; - tempLatitude += (float)(*c++ -'0'); - tempLatitude += (float)atof((char*)c) / 60.0; - } - nextField() // Latitude N/S - if (*c =='S') tempLatitude =-tempLatitude; - - nextField() // Longitude deg - if (*c){ - tempLongitude = (float)(*c++ -'0')*100.0; - tempLongitude += (float)(*c++ -'0')*10.0; - tempLongitude += (float)(*c++ -'0'); - tempLongitude += (float)atof((char*)c) / 60.0; - } - nextField() // Longitude E/W - if (*c == 'W') tempLongitude =-tempLongitude; - - if (!config.fake_long && !config.fake_lat) { - longitude = tempLongitude; - latitude = tempLatitude; - new_position=1; - } - - nextField() // Speed over ground, Knots - nextField() // Course Made Good, True - nextField() // Date - - if (*c==0) return; // date not present - - rmcBcd.tenDays = *c++ -'0'; - rmcBcd.days = *c++ -'0'; - rmcBcd.tenMonths = *c++ -'0'; - rmcBcd.months = *c++ -'0'; - rmcBcd.tenYears = *c++ -'0'; - rmcBcd.years = *c++ -'0'; - - - // Immediately after power-up, the GPS module does not know the GPS time/UTC leapsecond offset, and makes a guess - // Even if it gets a fix and starts outputting PPS, the time can be off by a few seconds (usually 2 or 3 fast) - // Only make use of this invalid data if there is nothing else to go on - if ( data_valid || (!had_pps && !rtc_good) ) { - currentTime = bcdToTm( &rmcBcd, &rmcTm ); - - if (decisec >= 9) { - currentTime++; - // check we're not <2ms away from rollover - if (centisec==9 && millisec>7) return; - - // Under normal conditions, we should only be parsing nmea at around .300 to .400 - // USART1 preemption priority is currently 1, so we could be interrupted by systick here - setNextTimestamp( currentTime ); - sendDate(0); - } - } - -} - -void decodeGSV(uint8_t rec){ - unsigned int sv = (nmea[11]-'0')*10 + (nmea[12]-'0'); - uint8_t constellation = nmea[2]; - uint8_t signal_id; - - // signal ID is not always present in GSV (on M8Q) - - unsigned int num_fields = 0, r=0; - while (++rODR, bright); - HAL_DMA_Start(&hdma_tim7_up, (uint32_t)buffer_c, (uint32_t)&GPIOC->ODR, bright); -} - -void displayOff(void){ - - uart2_tx_buffer[0]=' '; //in case already waiting for latch - uart2_tx_buffer[1]= CMD_LOAD_TEXT; - uart2_tx_buffer[2]= CMD_RELOAD_TEXT; - HAL_UART_AbortTransmit(&huart2); - HAL_UART_Transmit_DMA(&huart2, uart2_tx_buffer, 3); - - HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1); - HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2); - - HAL_DMA_Abort(&hdma_tim1_up); - HAL_DMA_Abort(&hdma_tim7_up); - GPIOB->ODR=0; - GPIOC->ODR=0; -} -void displayOn(void){ - HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); - HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); - setDisplayPWM(5); -} - -void setDisplayFreq(uint32_t freq){ - if (waitingForLatch) { - delayedDisplayFreq = freq; - return; - } - - if (freq<1000 || freq>100000) {delayedDisplayFreq=0; return;} - - uint8_t tx_buf[4]; - tx_buf[0]= CMD_SET_FREQUENCY; - tx_buf[1]= (freq>>14) & 0x7F; - tx_buf[2]= (freq>>7) & 0x7F; - tx_buf[3]= (freq) & 0x7F; - if (HAL_UART_Transmit(&huart2, tx_buf, 4, 2) == HAL_OK) { - delayedDisplayFreq = 0; - } - - uint32_t arr = round(16000000.0 / (float)freq) -1.0; - - TIM1->ARR = arr; - TIM7->ARR = arr; -} - -#define colonAnimationStart() \ - TIM5->CNT=0; \ - HAL_DMA_Start(&hdma_tim5_ch1, (uint32_t)buffer_colons_L, (uint32_t)&TIM2->CCR1, 200); \ - HAL_DMA_Start(&hdma_tim5_ch2, (uint32_t)buffer_colons_R, (uint32_t)&TIM2->CCR2, 200); - -#define colonAnimationStop() \ - HAL_DMA_Abort(&hdma_tim5_ch1); \ - HAL_DMA_Abort(&hdma_tim5_ch2); - -#define colonAnimationSync() \ - colonAnimationStop() \ - colonAnimationStart() - -void loadColonAnimation(void){ - - - switch (colonMode) { - case COLON_MODE_SLOWFADE: - for (int k=0;k<100;k++) { - buffer_colons_R[k] = - buffer_colons_L[k] = k*2; - buffer_colons_R[k+100] = - buffer_colons_L[k+100] = 198-k*2; - } - break; - case COLON_MODE_HEARTBEAT: - for (int k=0;k<50;k++) { - buffer_colons_L[k] = k*4; - } - for (int k=0;k<100;k++) { - buffer_colons_L[k+50] = 200 - k*2; - } - for (int k=0;k<50;k++) { - buffer_colons_L[k+150] = 0; - } - for (int k=0;k<200;k++) { - buffer_colons_R[k] = buffer_colons_L[(k+175)%200]; - } - - break; - case COLON_MODE_1PPS_SAWTOOTH: - for (int k=0;k<100;k++) { - buffer_colons_R[k] = - buffer_colons_L[k] = 196-(k*k)/50; - buffer_colons_R[k+100] = - buffer_colons_L[k+100] = 196-(k*k)/50; - } - break; - case COLON_MODE_ALT_SAWTOOTH: - for (int k=0;k<100;k++) { - buffer_colons_R[k] = 0; - buffer_colons_L[k+100] = 0; - buffer_colons_L[k] = 196-(k*k)/50; - buffer_colons_R[k+100] = 196-(k*k)/50; - } - break; - case COLON_MODE_TOGGLE: - for (int k=0;k<100;k++) { - buffer_colons_R[k] = 200; - buffer_colons_L[k] = 200; - buffer_colons_R[k+100] = 0; - buffer_colons_L[k+100] = 0; - } - break; - case COLON_MODE_SOLID: - for (int k=0;k<200;k++) { - buffer_colons_R[k] = 200; - buffer_colons_L[k] = 200; - } - break; - } - -} - -_Bool truthy(char const* str){ - if (strcasecmp(str, "on")==0) return 1; - if (strcasecmp(str, "enabled")==0) return 1; - if (strcasecmp(str, "1")==0) return 1; - return 0; -} - -_Bool falsey(char const* str){ - if (strcasecmp(str, "off")==0) return 1; - if (strcasecmp(str, "disabled")==0) return 1; - if (strcasecmp(str, "0")==0) return 1; - if (strcasecmp(str, "none")==0) return 1; - return 0; -} - -// Accept a float between 0.0 and 1.0, or an int from 0 to 4096 -float parseBrightness(char *v, _Bool invert){ - if (!v[0]) return -1; - float b = strtof(v, NULL); - if (!isfinite(b) || b<0.0) return -1; - if (b<=1.0 && v[1]=='.') - return invert? (1.0-b) * 4095 : b*4095; - if (b<=4095) - return invert? 4095-b : b; - return -1; -} - -#define set_mode_enabled(mode, value) \ - if ((config.modes_enabled[mode] = truthy(value))) requestMode=mode; - -void parseConfigString(char *key, char *value) { - - if (strcasecmp(key, "text") == 0) { - - strcpy(textDisplay, value); - - } else if (strcasecmp(key, "MATRIX_FREQUENCY") == 0) { - - setDisplayFreq(atoi(value)); - - } else if (strcasecmp(key, "zone_override") == 0) { - - if (!value[0] || delayedLoadRules) return; - - strcpy(preloadRulesString, value); - delayedLoadRules=1; - ZDAbort(); - - } else if (strcasecmp(key, "brightness") == 0) { - - config.brightness_override = parseBrightness(value, 1); - - } else if (strcasecmp(key, "countdown_to") == 0) { - - // support fractional seconds?? - struct tm t = {0}; - if( sscanf(value, "%d-%d-%dT%d:%d:%dZ", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec) >=3) { - - if (t.tm_year > 9999) return; // arbitrary cutoff, ~3e6 days - t.tm_year -= 1900; - t.tm_mon -= 1; - - config.countdown_to = mktime(&t) -1; - - } - } else if (strcasecmp(key, "MODE_ISO8601_STD") == 0) { - set_mode_enabled(MODE_ISO8601_STD, value); - } else if (strcasecmp(key, "MODE_ISO_ORDINAL") == 0) { - set_mode_enabled(MODE_ISO_ORDINAL, value); - } else if (strcasecmp(key, "MODE_ISO_WEEK") == 0) { - set_mode_enabled(MODE_ISO_WEEK, value); - } else if (strcasecmp(key, "MODE_UNIX") == 0) { - set_mode_enabled(MODE_UNIX, value); - } else if (strcasecmp(key, "MODE_JULIAN_DATE") == 0) { - set_mode_enabled(MODE_JULIAN_DATE, value); - } else if (strcasecmp(key, "MODE_MODIFIED_JD") == 0) { - set_mode_enabled(MODE_MODIFIED_JD, value); - } else if (strcasecmp(key, "MODE_SHOW_OFFSET") == 0) { - set_mode_enabled(MODE_SHOW_OFFSET, value); - } else if (strcasecmp(key, "MODE_SHOW_TZ_NAME") == 0) { - set_mode_enabled(MODE_SHOW_TZ_NAME, value); - } else if (strcasecmp(key, "MODE_WEEKDAY") == 0) { - set_mode_enabled(MODE_WEEKDAY, value); - } else if (strcasecmp(key, "MODE_WEEKDA_DD") == 0) { - set_mode_enabled(MODE_WEEKDA_DD, value); - } else if (strcasecmp(key, "MODE_WDY_MM_DD") == 0) { - set_mode_enabled(MODE_WDY_MM_DD, value); - } else if (strcasecmp(key, "MODE_STANDBY") == 0) { - set_mode_enabled(MODE_STANDBY, value); - } else if (strcasecmp(key, "MODE_COUNTDOWN") == 0) { - set_mode_enabled(MODE_COUNTDOWN, value); - } else if (strcasecmp(key, "MODE_SATVIEW") == 0) { - set_mode_enabled(MODE_SATVIEW, value); - } else if (strcasecmp(key, "MODE_DEBUG_BRIGHTNESS") == 0) { - set_mode_enabled(MODE_DEBUG_BRIGHTNESS, value); - } else if (strcasecmp(key, "MODE_DEBUG_RTC") == 0) { - set_mode_enabled(MODE_DEBUG_RTC, value); - } else if (strcasecmp(key, "MODE_TEXT") == 0) { - set_mode_enabled(MODE_TEXT, value); - } else if (strcasecmp(key, "MODE_VBAT") == 0) { - set_mode_enabled(MODE_VBAT, value); - } else if (strcasecmp(key, "MODE_DISPLAYTEST") == 0) { - set_mode_enabled(MODE_DISPLAYTEST, value); - } else if (strcasecmp(key, "MODE_TTFF") == 0) { - set_mode_enabled(MODE_TTFF, value); -#ifdef NONCOMPLIANT_DATE_MODES - } else if (strcasecmp(key, "MODE_DDMMYYYY") == 0) { - set_mode_enabled(MODE_DDMMYYYY, value); -#endif - } else if (strcasecmp(key, "MODE_FIRMWARE_CRC") == 0) { - set_mode_enabled(MODE_FIRMWARE_CRC_D, value); - set_mode_enabled(MODE_FIRMWARE_CRC_T, value); - } else if (strcasecmp(key, "MODE_SUN") == 0) { - set_mode_enabled(MODE_SUN, value); - } else if (strcasecmp(key, "MODE_SUN_AZEL") == 0) { - set_mode_enabled(MODE_SUN_AZEL, value); - } else if (strcasecmp(key, "MODE_MOON") == 0) { - set_mode_enabled(MODE_MOON, value); - } else if (strcasecmp(key, "MODE_GRID") == 0) { - set_mode_enabled(MODE_GRID, value); - } else if (strcasecmp(key, "MODE_LATLON") == 0) { - set_mode_enabled(MODE_LATLON, value); - } else if (strcasecmp(key, "Tolerance_time_1ms") == 0) { - config.tolerance_1ms = atoi(value); - } else if (strcasecmp(key, "Tolerance_time_10ms") == 0) { - config.tolerance_10ms = atoi(value); - } else if (strcasecmp(key, "Tolerance_time_100ms") == 0) { - config.tolerance_100ms = atoi(value); - } else if (strcasecmp(key, "fake_longitude") == 0) { - config.fake_long = atof(value); - } else if (strcasecmp(key, "fake_latitude") == 0) { - config.fake_lat = atof(value); - } else if (strcasecmp(key, "colon_mode") == 0) { - - if (strcasecmp(value, "solid") == 0) { - colonMode = COLON_MODE_SOLID; - } else if (strcasecmp(value, "heartbeat") == 0) { - colonMode = COLON_MODE_HEARTBEAT; - } else if (strcasecmp(value, "sawtooth") == 0) { - colonMode = COLON_MODE_1PPS_SAWTOOTH; - } else if (strcasecmp(value, "alt_sawtooth") == 0) { - colonMode = COLON_MODE_ALT_SAWTOOTH; - } else if (strcasecmp(value, "toggle") == 0) { - colonMode = COLON_MODE_TOGGLE; - } else colonMode = COLON_MODE_SLOWFADE; - - } else if (strcasecmp(key, "nmea") == 0) { - - if (falsey(value)) { - nmea_cdc_level = NMEA_NONE; - } else if (strcasecmp(value, "rmc") == 0) { - nmea_cdc_level = NMEA_RMC; - } else nmea_cdc_level = NMEA_ALL; - - } else if (key[0]=='B' && key[1]=='S' && key[3]==0) { //BS1, BS2, etc - if (!key[2] || key[2]<'1' || key[2]>'0'+sizeof(brightnessCurve)/sizeof(brightnessCurve[0])) return; - - char *c = &value[0]; - while (*c++) if(*c==',') break; - if (*c==0) return; - *c=0; c++; - - float in = parseBrightness(value,0); - float out = parseBrightness(c,1); - if (in<0 || out<0) return; - - brightnessCurve[key[2]-'1'].in = in; - brightnessCurve[key[2]-'1'].out = out; - - } - -} - -void postConfigCleanup(void){ - loadColonAnimation(); - - // check at least one mode is enabled - uint8_t j = 0; - for (uint8_t i=0; i=2)) { - parseConfigString(key, value); - postConfigCleanup(); - } - k=0; - v=0; - state=0; - return; - } - - switch (state) { - case 0: // read key - if (k) { - if (c=='=') {state =2; break;} - if (c==' ' || c=='\t') {state =1; break;} - } - key[k++] = c; - if (k==31) k--; - break; - case 1: // whitespace - if (c=='=') state=2; - else if (c!=' ' && c!='\t') {state=0; k=0; key[k++]=c;} - break; - case 2: //second whitespace - if (c!=' ' && c!='\t' && c!='=') {state=3; value[v++]=c;} - break; - case 3: - value[v++]=c; - if (v==31) v--; - } -} - -void readConfigFile(void){ - -#ifdef CHECK_CONFIG_MTIME - FILINFO fno; - if (f_stat(CONFIG_FILENAME, &fno) == FR_OK) { - // if unchanged, exit early before touching any config - // if the file doesn't exist, fall through and fail on the f_open - if (fno.fdate==config.fdate && fno.ftime==config.ftime) return; - config.fdate=fno.fdate; - config.ftime=fno.ftime; - } -#endif - - config.tolerance_1ms = 1000; - config.tolerance_10ms = 10000; - config.tolerance_100ms = 100000; - config.zone_override = 0; - config.brightness_override = -1.0; - colonMode = 0; - - FIL file; - - if (f_open(&file, CONFIG_FILENAME, FA_READ) != FR_OK) { - postConfigCleanup(); - return; - } - - char key[32], value[32], s[1]; - unsigned int rc; - uint16_t col=0; - - - while (1) { - f_read(&file, s, 1, &rc); - if (rc!=1) break; //EOF - - if (s[0]=='\r' || s[0]=='\n') { col=0; continue; } //EOL - - if (col==0 && (s[0]=='#' || s[0]==';')) { // comments - while (rc && s[0]!='\n') f_read(&file, s, 1, &rc); - continue; - } - - if (s[0]!='=') { - if (col CAL_PERIOD) { - - LPTIM1_high=0; - LL_LPTIM_StartCounter(LPTIM1, LL_LPTIM_OPERATING_MODE_CONTINUOUS); - calibStart = currentTime; - - } else if ((uint32_t)currentTime - calibStart == CAL_PERIOD) { - volatile uint16_t x = LPTIM1->CNT; - volatile uint16_t y = LPTIM1->CNT; - if (x!=y) goto skipRtcCal; - - int32_t error = ((LPTIM1_high<<16) + x) - 32768*CAL_PERIOD + LPTIM_START_DELAY; - float e = (float)error * 32.0 / CAL_PERIOD; - - debug_rtc_val = error;//0x100 + round(e); - - if (e>255.0 || e< -255.0) goto skipRtcCal; - - __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); - RTC->CALR = 0x100 + (int)round(e); - __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); - rtc_last_calibration = (uint32_t)currentTime; - -skipRtcCal: - // Prepare the counter for the next calibration - // LPTIM1->CNT is read only, the only way to zero it is to disable and re-enable the timer. - // There is a further delay associated with this, better to put it here than right at the moment we want to start the timer. - LPTIM1->CR &= ~LPTIM_CR_ENABLE; - LPTIM1->CR |= LPTIM_CR_ENABLE; - LL_LPTIM_SetAutoReload(LPTIM1, 0xFFFF); - LL_LPTIM_ClearFLAG_ARRM(LPTIM1); // just in case there's one pending - } -} - -void EXTI9_5_IRQHandler(void){__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7);} - -// PPS rising edge -void PPS(void) -{ - SysTick->VAL = SysTick->LOAD; - - buffer_c[3].low=cLut[0]; - buffer_c[2].low=cLut[0]; - buffer_c[1].low=cLut[0]; - loadNextTimestamp(); - millisec=0; - centisec=0; - decisec=0; - - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7); - - // clear systick flag if set? - - // During first power up PPS can be emitted before the GPS leapsecond offset is known - // In this case, it is safest to pretend PPS hasn't happened - if (!data_valid) return; - - calibrateRTC(); - - if ((currentTime & 1) ==0) {colonAnimationSync()} - - had_pps = 1; - last_pps_time = (uint32_t)currentTime; -} - -void PPS_NoUpdate(void) -{ - SysTick->VAL = SysTick->LOAD; - triggerPendSV(); - - millisec=0; - centisec=0; - decisec=0; - - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7); - - if (!data_valid) return; - - calibrateRTC(); - - had_pps = 1; - last_pps_time = (uint32_t)currentTime; -} - -void PPS_Countdown(void) -{ - SysTick->VAL = SysTick->LOAD; - - buffer_c[3].low=cLut[9]; - buffer_c[2].low=cLut[9]; - buffer_c[1].low=cLut[9]; - loadNextTimestamp(); - millisec=0; - centisec=0; - decisec=0; - - __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7); - - if (!data_valid) return; - calibrateRTC(); - if ((currentTime & 1) ==0) {colonAnimationSync()} - - had_pps = 1; - last_pps_time = (uint32_t)currentTime; -} - -void PPS_Init(void){ - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - /*Configure GPIO pin : PC7 */ - GPIO_InitStruct.Pin = GPIO_PIN_7; - GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - /* EXTI interrupt init*/ - HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); - - SetPPS( &PPS ); -} - -#define timetick() \ - millisec++; \ - if (millisec>=10) { \ - millisec=0; \ - centisec++; \ - if (centisec>=10) { \ - centisec=0; \ - decisec++; \ - if (decisec>=10) { \ - decisec=0; \ - loadNextTimestamp(); \ - } \ - } \ - } - -void SysTick_CountUp_P3(void) -{ - timetick() - - buffer_c[3].low=cLut[millisec]; - buffer_c[2].low=cLut[centisec]; - buffer_c[1].low=cLut[decisec]; - - - - HAL_IncTick(); - - // At the 0.900 mark, we calculate what the display should read at the next pulse - if (decisec==9 && centisec==0 && millisec==0){ - // Calculating the next display from the unix timestamp takes about 32uS with -O2, -O3 or -Os - // takes about 70uS on -O0 so I think it's fine to do this within systick - // If needed, we should move this to a lower priority software-triggered interrupt - currentTime++; - setNextTimestamp( currentTime ); - sendDate(0); - } -} - -void SysTick_CountUp_P2(void) { - timetick() - - buffer_c[2].low=cLut[centisec]; - buffer_c[1].low=cLut[decisec]; - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextTimestamp( currentTime ); - sendDate(0); - } -} -void SysTick_CountUp_P1(void) { - - timetick() - - buffer_c[1].low=cLut[decisec]; - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextTimestamp( currentTime ); - sendDate(0); - } -} - -void SysTick_CountUp_P0(void) { - - timetick() - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextTimestamp( currentTime ); - sendDate(0); - } -} - -void SysTick_CountUp_NoUpdate(void) { - millisec++; - if (millisec>=10) { - millisec=0; - centisec++; - if (centisec>=10) { - centisec=0; - decisec++; - if (decisec>=10) { - decisec=0; - // write_rtc still needs to happen - triggerPendSV(); - } - } - } - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextTimestamp( currentTime ); - //sendDate(0); - } -} - - -void SysTick_CountDown_P3(void) -{ - timetick() - - buffer_c[3].low=cLut[9-millisec]; - buffer_c[2].low=cLut[9-centisec]; - buffer_c[1].low=cLut[9-decisec]; - - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextCountdown( currentTime ); - sendDate(0); - } -} - -void SysTick_CountDown_P2(void) -{ - timetick() - - //buffer_c[3].low=cLut[9-millisec]; - buffer_c[2].low=cLut[9-centisec]; - buffer_c[1].low=cLut[9-decisec]; - - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextCountdown( currentTime ); - sendDate(0); - } -} - -void SysTick_CountDown_P1(void) -{ - timetick() - - //buffer_c[3].low=cLut[9-millisec]; - //buffer_c[2].low=cLut[9-centisec]; - buffer_c[1].low=cLut[9-decisec]; - - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextCountdown( currentTime ); - sendDate(0); - } -} - -// A no precision countdown is going to be really ambiguous, as it will hit zero a second before the target -// Then again it will only be used in situations where the tolerance is worse than a second -void SysTick_CountDown_P0(void) -{ - timetick() - - //buffer_c[3].low=cLut[9-millisec]; - //buffer_c[2].low=cLut[9-centisec]; - //buffer_c[1].low=cLut[9-decisec]; - - HAL_IncTick(); - - if (decisec==9 && centisec==0 && millisec==0){ - currentTime++; - setNextCountdown( currentTime ); - sendDate(0); - } -} - -void SysTick_Dummy(void){ - HAL_IncTick(); -} - -// We cannot use hardware vbus monitoring since the pin is occupied by USART1 TX -// We can't use EXTI on PA8 as it's in the same group as PPS -void monitor_vbus(void){ - static _Bool vbus_state = 1; // power-on state is initialised, even if not connected - - _Bool vbus = (GPIOA->IDR & GPIO_PIN_8); - - if (vbus_state && !vbus) { // disconnected - - MX_USB_Stop(); - - } else if (vbus && !vbus_state) { // connected - - MX_USB_DEVICE_Init(); - - } - vbus_state = vbus; -} - -void measure_vbat(void){ - ADC123_COMMON->CCR |= ADC_CCR_VBATEN; - HAL_Delay(5); - HAL_ADC_Start(&hadc3); - HAL_ADC_PollForConversion(&hadc3, 10); - uint16_t adc = HAL_ADC_GetValue(&hadc3); - ADC123_COMMON->CCR &= ~ADC_CCR_VBATEN; - vbat = (float)adc *0.0024102564102564104;//3*3.29/4095.0; -} - -uint8_t f_getzcmp(FIL* fp, char * str){ - unsigned int rc; - char * a = str; - char b[1] = {1}; - uint8_t ret = 0; - - while (b[0]!=0) { - f_read(fp, &b, 1, &rc); - if (b[0] != *a++) ret=-1; - } - return ret; -} -uint8_t findField( FIL* fp, char* str, uint8_t count, uint8_t padding ) { - char buf[4]; - unsigned int rc; - for (uint8_t i=0; i= currentTime) { - SetPPS( &PPS_Countdown ); - - if (currentTime - last_pps_time < config.tolerance_1ms){ - buffer_c[0].high= 0b11001110 | cSegDP; - SetSysTick( &SysTick_CountDown_P3 ); - } else if (currentTime - last_pps_time < config.tolerance_10ms){ - buffer_c[3].low = 0b01000000; - buffer_c[0].high= 0b11001110 | cSegDP; - SetSysTick( &SysTick_CountDown_P2 ); - } else if (currentTime - rtc_last_calibration < config.tolerance_100ms){ - buffer_c[3].low = 0b01000000; - buffer_c[2].low = 0b01000000; - buffer_c[0].high= 0b11001110 | cSegDP; - SetSysTick( &SysTick_CountDown_P1 ); - } else { - buffer_c[3].low = 0b01000000; - buffer_c[2].low = 0b01000000; - buffer_c[1].low = 0b01000000; - buffer_c[0].high= 0b11001110; - SetSysTick( &SysTick_CountDown_P0 ); - } - - } else { - countMode = COUNT_HIDDEN; - SetSysTick( &SysTick_CountUp_NoUpdate ); - SetPPS( &PPS_NoUpdate ); - buffer_c[0].high= 0b11001110 | cSegDP; - buffer_c[0].low=cSegDecode0; - buffer_c[1].low=cSegDecode0; - buffer_c[2].low=cSegDecode0; - buffer_c[3].low=cSegDecode0; - - next7seg.b[0] = bCat0 | cLut[0]<<2; - next7seg.b[1] = bCat1 | cLut[0]<<2; - next7seg.b[2] = bCat2 | cLut[0]<<2; - next7seg.b[3] = bCat3 | cLut[0]<<2; - next7seg.b[4] = bCat4 | cLut[0]<<2; - next7seg.c = cLut[0]; - } - - } -} - -#define justExited(x) ((oldMode==x) && (displayMode != x)) -void nextMode(_Bool reverse){ - - uint8_t oldMode = displayMode; - - if (requestMode!=255){ - if (!config.modes_enabled[requestMode]) { - requestMode=255; - return; - } - displayMode=requestMode; - requestMode=255; - } else if (reverse) { - do { - if (--displayMode >= NUM_DISPLAY_MODES) displayMode=NUM_DISPLAY_MODES-1; - } while (!config.modes_enabled[displayMode]); - } else { - do { - if (++displayMode >=NUM_DISPLAY_MODES) displayMode=0; - } while (!config.modes_enabled[displayMode]); - } - - if (justExited(MODE_VBAT)) vbat = 0.0; - if (justExited(MODE_STANDBY)) displayOn(); - if (justExited(MODE_DISPLAYTEST)) { - buffer_c[1].high &= ~cSegDP; - buffer_c[2].high &= ~cSegDP; - buffer_c[3].high &= ~cSegDP; - } - if ( displayMode == MODE_ISO_WEEK || justExited(MODE_COUNTDOWN)) { - // If we exit countdown mode at .9 seconds - // it will show the wrong time for .1 seconds - setNextTimestamp(currentTime); - } - - if (displayMode == MODE_SHOW_OFFSET || displayMode == MODE_DISPLAYTEST) { - countMode = COUNT_HIDDEN; - SetSysTick( &SysTick_CountUp_NoUpdate ); - SetPPS( &PPS_NoUpdate ); - colonAnimationStop() - TIM2->CCR1 = 0; // specific to show_offset - TIM2->CCR2 = 300; - } else if (displayMode == MODE_COUNTDOWN) { - - if (config.countdown_to >= currentTime) { - countMode = COUNT_DOWN; - setNextCountdown(currentTime); - } else { - countMode = COUNT_HIDDEN; - countdown_days = 0; - } - setPrecision(); - TIM2->CCR1 = 0; - TIM2->CCR2 = 0; - latchSegments(); - - } - else { - if (countMode != COUNT_NORMAL) { - countMode = COUNT_NORMAL; - setPrecision(); - SetPPS( &PPS ); - TIM2->CCR1 = 0; - TIM2->CCR2 = 0; - latchSegments(); - } - } - sendDate(1); -} -void button1pressed(void){ - nextMode(0); -} -void button2pressed(void){ - nextMode(1); -} -void buttonsBothHeld(void){ - HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1); - HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2); - - HAL_DMA_Abort(&hdma_tim1_up); - HAL_DMA_Abort(&hdma_tim7_up); - GPIOB->ODR=0; - GPIOC->ODR=0; - - NVIC_SystemReset(); -} - -void generateDACbuffer(uint16_t * buf) { - - static float dac_last=4095; - - - if (displayMode == MODE_STANDBY) { - dac_target = dac_target*0.7 + 1.2*4095.0*0.3; - if (dac_target>4094.0) { - dac_target=4095.0; - displayOff(); - } - } else if (config.brightness_override >=0.0) { - dac_target = config.brightness_override; - } else { - float adc = (float)ADC1->DR; - - uint8_t i; - for (i=1; i< sizeof(brightnessCurve)/sizeof(brightnessCurve[0]) -1; i++){ - if (brightnessCurve[i].in > adc) break; - } - float factor = (adc - brightnessCurve[i-1].in) / (brightnessCurve[i].in - brightnessCurve[i-1].in); - - float out = brightnessCurve[i-1].out*(1.0-factor) + brightnessCurve[i].out*factor; - - if (out>4095.0 || !isfinite(out)) out=4095.0; - else if (out<0.0) out=0.0; - - dac_target = dac_target*0.5 + out*0.5; - } - - - HAL_ADC_Start(&hadc1); - - - - float step = (dac_target-dac_last)/(DAC_BUFFER_SIZE*0.5); - for (size_t i=0; iVTOR = (uint32_t)&__VECTORS_RAM; - - SetSysTick( &SysTick_Dummy ); - - - /* USER CODE END 1 */ - - /* MCU Configuration--------------------------------------------------------*/ - - /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ - HAL_Init(); - - /* USER CODE BEGIN Init */ - - /* USER CODE END Init */ - - /* Configure the system clock */ - SystemClock_Config(); - - /* USER CODE BEGIN SysInit */ - - buffer_c[0].high=0b11001110; - buffer_c[1].high=0b11001101; - buffer_c[2].high=0b11001011; - buffer_c[3].high=0b11000111; - buffer_c[4].high=0b11001111; - - /* USER CODE END SysInit */ - - /* Initialize all configured peripherals */ - MX_GPIO_Init(); - MX_DMA_Init(); - MX_QUADSPI_Init(); - MX_TIM1_Init(); - MX_USART2_UART_Init(); - MX_FATFS_Init(); - //MX_USB_DEVICE_Init(); - MX_USART1_UART_Init(); - MX_TIM2_Init(); - MX_ADC1_Init(); - MX_DAC1_Init(); - MX_TIM6_Init(); - MX_TIM7_Init(); - MX_CRC_Init(); - MX_LPTIM1_Init(); - MX_TIM5_Init(); - /* USER CODE BEGIN 2 */ - - - // Configure display matrix - if (HAL_DMA_Start(&hdma_tim7_up, (uint32_t)buffer_c, (uint32_t)&GPIOC->ODR, 5) != HAL_OK) - Error_Handler(); - - if (HAL_DMA_Start(&hdma_tim1_up, (uint32_t)buffer_b, (uint32_t)&GPIOB->ODR, 5) != HAL_OK) - Error_Handler(); - - __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE); - __HAL_TIM_ENABLE(&htim1); - - __HAL_TIM_ENABLE_DMA(&htim7, TIM_DMA_UPDATE); - __HAL_TIM_ENABLE(&htim7); - - - doDateUpdate(); - MX_USB_DEVICE_Init(); - - // Enable UART2 interrupt for button presses - USART2->CR1 |= USART_CR1_RXNEIE; - - - // Configure UART1 for NMEA strings from GPS module - USART1->CR1 |= USART_CR1_CMIE ; - - USART1->CR1 &= ~(USART_CR1_UE); - USART1->CR2 |= '\n'<<24; - USART1->CR1 |= USART_CR1_UE; - - - MX_ADC3_Init(); - - // Configure ADC and DAC DMA for display brightness - HAL_ADC_Start(&hadc1); - HAL_TIM_Base_Start(&htim6); - - if (HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*)buffer_dac, DAC_BUFFER_SIZE, DAC_ALIGN_12B_R) !=HAL_OK) - Error_Handler(); - - // Configure Colon Separators - TIM2->CCR1 = 0; - TIM2->CCR2 = 0; - - //loadColonAnimation(); - - __HAL_TIM_ENABLE_DMA(&htim5, TIM_DMA_CC1 | TIM_DMA_CC2); - __HAL_TIM_ENABLE(&htim5); - - //colonAnimationStart() - - - //Enable DP for subseconds - buffer_c[0].high=0b11001110 | cSegDP; - - - - buffer_c[0].low=cSegDecode0; - buffer_c[1].low=cSegDecode0; - buffer_c[2].low=cSegDecode0; - buffer_c[3].low=cSegDecode0; - - next7seg.c = buffer_c[0].low; - - next7seg.b[0] = buffer_b[0] = bCat0 | bSegDecode0; - next7seg.b[1] = buffer_b[1] = bCat1 | bSegDecode0; - next7seg.b[2] = buffer_b[2] = bCat2 | bSegDecode0; - next7seg.b[3] = buffer_b[3] = bCat3 | bSegDecode0; - next7seg.b[4] = buffer_b[4] = bCat4 | bSegDecode0; - - //setDisplayPWM(5); - displayOn(); - - readConfigFile(); - checkDelayedLoadRules(); - - measure_vbat(); - - if (RTC->ISR & RTC_ISR_INITS) //RTC contains non-zero data - { - RTC_DateTypeDef sdate; - RTC_TimeTypeDef stime; - - if (!config.zone_override){ - char zone[32]; - memcpyword( (uint32_t*)zone, (uint32_t*)&(RTC->BKP0R), 8 ); - zone[31]=0; - - if (loadRulesSingle(zone) != RULES_OK){ // takes ~8ms - memcpyword( (uint32_t*)loadedRulesString, (uint32_t*)&(RTC->BKP0R), 8 ); - loadedRulesString[31]=0;//paranoia - memcpyword( (uint32_t*)rules, (uint32_t*)&(RTC->BKP8R), 22 ); - } - } - - - hrtc.Instance = RTC; - HAL_RTC_GetTime(&hrtc, &stime, RTC_FORMAT_BIN); - HAL_RTC_GetDate(&hrtc, &sdate, RTC_FORMAT_BIN); - - struct tm out; - - out.tm_isdst = 0; - - out.tm_sec = stime.Seconds; - out.tm_min = stime.Minutes; - out.tm_hour = stime.Hours; - out.tm_mday = sdate.Date; - out.tm_mon = sdate.Month -1; - out.tm_year = sdate.Year + 100; //Years since 1900 - - currentTime = mktime(&out); - - float fraction = (float)(32767 - stime.SubSeconds) / 32768.0; - - // SysTick->VAL = SysTick->LOAD; ? - millisec = (uint32_t)(fraction*1000) % 10; - centisec = (uint32_t)(fraction*100) % 10; - decisec = (uint32_t)(fraction*10) % 10; - - if (decisec>=9) currentTime++; - - setNextTimestamp( currentTime ); - sendDate(1); - latchSegments(); - - // As the coin cell goes flat, the RTC stops ticking long before the backup registers die. - // Powering on with a flat battery means the clock thinks no time has passed, and assumes it has good precision. - // Explicitly stop this by checking the battery voltage. - if (vbat > 2.70) { - rtc_good=1; - } else { - // trash the calibration time to ensure lowest precision display - if (currentTime - rtc_last_calibration < config.tolerance_100ms) - rtc_last_calibration -= config.tolerance_100ms +1; - } - - } else { // backup domain reset - - currentTime=946684800; // 2000-01-01T00:00:00 - - // The init process blanks the subsecond registers - MX_RTC_Init(); - } - - vbat = 0.0; // don't allow measurement to go stale - - setPrecision(); - PPS_Init(); - HAL_UART_Receive_DMA(&huart1, nmea, sizeof(nmea)); - -//#define MEASURE_LOOKUP_TIME - - /* USER CODE END 2 */ - - /* Infinite loop */ - /* USER CODE BEGIN WHILE */ - while (1) - { - if (new_position && !qspi_write_time && !config.zone_override - && (data_valid || (config.fake_long && config.fake_lat)) - && latitude>=-90.0 && latitude<=90.0 && longitude>=-180.0 && longitude<=180.0) { - - new_position=0; - FIL mapfile; - if (f_open(&mapfile, MAP_FILENAME, FA_READ) == FR_OK) { -#ifdef MEASURE_LOOKUP_TIME - uint32_t start=uwTick; -#endif - ZoneDetect *const zdb = ZDOpenDatabase(&mapfile); - - if (!zdb) { - // mapfile error - } else { - char* zone = ZDHelperSimpleLookupString(zdb, latitude, longitude); -#ifdef MEASURE_LOOKUP_TIME - uint32_t ztime=uwTick-start; -#endif - if (zone && !delayedLoadRules) { -#ifdef MEASURE_LOOKUP_TIME - start=uwTick; -#endif - loadRulesSingle(zone); -#ifdef MEASURE_LOOKUP_TIME - sprintf(textDisplay,"d%ld L%ld",ztime, uwTick-start); -#endif - } - free(zone); - ZDCloseDatabase(zdb); - //f_close(&mapfile); - } - } - // else no_map = 1 - } - - if (delayedCheckOnEject) firmwareCheckOnEject(); - - if (delayedReadConfigFile) { - FATFS_remount(); - readConfigFile(); - delayedReadConfigFile=0; - } - - checkDelayedLoadRules(); - - if (delayedDisplayFreq) setDisplayFreq(delayedDisplayFreq); - - monitor_vbus(); - - if (displayMode == MODE_VBAT) - measure_vbat(); - - if (displayMode == MODE_SUN || displayMode == MODE_SUN_AZEL || displayMode == MODE_MOON - || displayMode == MODE_GRID || displayMode == MODE_LATLON) - astro_update(); - - - /* USER CODE END WHILE */ - - /* USER CODE BEGIN 3 */ - } - /* USER CODE END 3 */ -} - -/** - * @brief System Clock Configuration - * @retval None - */ -void SystemClock_Config(void) -{ - RCC_OscInitTypeDef RCC_OscInitStruct = {0}; - RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; - RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; - - /** Configure LSE Drive Capability - */ - HAL_PWR_EnableBkUpAccess(); - __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW); - /** Initializes the CPU, AHB and APB busses clocks - */ - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE - |RCC_OSCILLATORTYPE_MSI; - RCC_OscInitStruct.HSEState = RCC_HSE_ON; - RCC_OscInitStruct.LSEState = RCC_LSE_ON; - RCC_OscInitStruct.MSIState = RCC_MSI_ON; - RCC_OscInitStruct.MSICalibrationValue = 0; - RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_11; - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; - RCC_OscInitStruct.PLL.PLLM = 2; - RCC_OscInitStruct.PLL.PLLN = 64; - RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; - RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; - RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV4; - if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) - { - Error_Handler(); - } - /** Initializes the CPU, AHB and APB busses clocks - */ - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK - |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; - - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) - { - Error_Handler(); - } - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_USART1 - |RCC_PERIPHCLK_USART2|RCC_PERIPHCLK_LPTIM1 - |RCC_PERIPHCLK_USB|RCC_PERIPHCLK_ADC; - PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; - PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1; - PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE; - PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK; - PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; - PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_MSI; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) - { - Error_Handler(); - } - /** Configure the main internal regulator output voltage - */ - if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) - { - Error_Handler(); - } - /** Enable MSI Auto calibration - */ - HAL_RCCEx_EnableMSIPLLMode(); -} - -/** - * @brief ADC1 Initialization Function - * @param None - * @retval None - */ -static void MX_ADC1_Init(void) -{ - - /* USER CODE BEGIN ADC1_Init 0 */ - - /* USER CODE END ADC1_Init 0 */ - - ADC_MultiModeTypeDef multimode = {0}; - ADC_ChannelConfTypeDef sConfig = {0}; - - /* USER CODE BEGIN ADC1_Init 1 */ - - /* USER CODE END ADC1_Init 1 */ - /** Common config - */ - hadc1.Instance = ADC1; - hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; - hadc1.Init.Resolution = ADC_RESOLUTION_12B; - hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; - hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; - hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; - hadc1.Init.LowPowerAutoWait = DISABLE; - hadc1.Init.ContinuousConvMode = DISABLE; - hadc1.Init.NbrOfConversion = 1; - hadc1.Init.DiscontinuousConvMode = DISABLE; - hadc1.Init.NbrOfDiscConversion = 1; - hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; - hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; - hadc1.Init.DMAContinuousRequests = DISABLE; - hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; - hadc1.Init.OversamplingMode = DISABLE; - if (HAL_ADC_Init(&hadc1) != HAL_OK) - { - Error_Handler(); - } - /** Configure the ADC multi-mode - */ - multimode.Mode = ADC_MODE_INDEPENDENT; - if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) - { - Error_Handler(); - } - /** Configure Regular Channel - */ - sConfig.Channel = ADC_CHANNEL_10; - sConfig.Rank = ADC_REGULAR_RANK_1; - sConfig.SamplingTime = ADC_SAMPLETIME_92CYCLES_5; - sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.OffsetNumber = ADC_OFFSET_NONE; - sConfig.Offset = 0; - if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN ADC1_Init 2 */ - - /* USER CODE END ADC1_Init 2 */ - -} - -/** - * @brief ADC3 Initialization Function - * @param None - * @retval None - */ -static void MX_ADC3_Init(void) -{ - - /* USER CODE BEGIN ADC3_Init 0 */ - - /* USER CODE END ADC3_Init 0 */ - - ADC_ChannelConfTypeDef sConfig = {0}; - - /* USER CODE BEGIN ADC3_Init 1 */ - - - /* USER CODE END ADC3_Init 1 */ - /** Common config - */ - hadc3.Instance = ADC3; - hadc3.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2; - hadc3.Init.Resolution = ADC_RESOLUTION_12B; - hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT; - hadc3.Init.ScanConvMode = ADC_SCAN_DISABLE; - hadc3.Init.EOCSelection = ADC_EOC_SINGLE_CONV; - hadc3.Init.LowPowerAutoWait = DISABLE; - hadc3.Init.ContinuousConvMode = DISABLE; - hadc3.Init.NbrOfConversion = 1; - hadc3.Init.DiscontinuousConvMode = DISABLE; - hadc3.Init.NbrOfDiscConversion = 1; - hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START; - hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; - hadc3.Init.DMAContinuousRequests = DISABLE; - hadc3.Init.Overrun = ADC_OVR_DATA_PRESERVED; - hadc3.Init.OversamplingMode = ENABLE; - hadc3.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_16; - hadc3.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_4; - hadc3.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER; - hadc3.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_RESUMED_MODE; - - if (HAL_ADC_Init(&hadc3) != HAL_OK) - { - Error_Handler(); - } - - HAL_ADCEx_Calibration_Start(&hadc3, ADC_SINGLE_ENDED); - - /** Configure Regular Channel - */ - sConfig.Channel = ADC_CHANNEL_VBAT; - sConfig.Rank = ADC_REGULAR_RANK_1; - sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5; - sConfig.SingleDiff = ADC_SINGLE_ENDED; - sConfig.OffsetNumber = ADC_OFFSET_NONE; - sConfig.Offset = 0; - if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN ADC3_Init 2 */ - ADC123_COMMON->CCR &= ~ADC_CCR_VBATEN; - /* USER CODE END ADC3_Init 2 */ - -} - -/** - * @brief CRC Initialization Function - * @param None - * @retval None - */ -static void MX_CRC_Init(void) -{ - - /* USER CODE BEGIN CRC_Init 0 */ - - /* USER CODE END CRC_Init 0 */ - - /* USER CODE BEGIN CRC_Init 1 */ - - /* USER CODE END CRC_Init 1 */ - hcrc.Instance = CRC; - hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE; - hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE; - hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_BYTE; - hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_ENABLE; - hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_WORDS; - if (HAL_CRC_Init(&hcrc) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN CRC_Init 2 */ - - /* USER CODE END CRC_Init 2 */ - -} - -/** - * @brief DAC1 Initialization Function - * @param None - * @retval None - */ -static void MX_DAC1_Init(void) -{ - - /* USER CODE BEGIN DAC1_Init 0 */ - - /* USER CODE END DAC1_Init 0 */ - - DAC_ChannelConfTypeDef sConfig = {0}; - - /* USER CODE BEGIN DAC1_Init 1 */ - - /* USER CODE END DAC1_Init 1 */ - /** DAC Initialization - */ - hdac1.Instance = DAC1; - if (HAL_DAC_Init(&hdac1) != HAL_OK) - { - Error_Handler(); - } - /** DAC channel OUT1 config - */ - sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE; - sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO; - sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; - sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE; - sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY; - if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN DAC1_Init 2 */ - HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 4095); - /* USER CODE END DAC1_Init 2 */ - -} - -/** - * @brief LPTIM1 Initialization Function - * @param None - * @retval None - */ -static void MX_LPTIM1_Init(void) -{ - - /* USER CODE BEGIN LPTIM1_Init 0 */ - - /* USER CODE END LPTIM1_Init 0 */ - - /* Peripheral clock enable */ - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1); - - /* LPTIM1 interrupt Init */ - NVIC_SetPriority(LPTIM1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),1, 0)); - NVIC_EnableIRQ(LPTIM1_IRQn); - - /* USER CODE BEGIN LPTIM1_Init 1 */ - - /* USER CODE END LPTIM1_Init 1 */ - LL_LPTIM_SetClockSource(LPTIM1, LL_LPTIM_CLK_SOURCE_INTERNAL); - LL_LPTIM_SetPrescaler(LPTIM1, LL_LPTIM_PRESCALER_DIV1); - LL_LPTIM_SetPolarity(LPTIM1, LL_LPTIM_OUTPUT_POLARITY_REGULAR); - LL_LPTIM_SetUpdateMode(LPTIM1, LL_LPTIM_UPDATE_MODE_IMMEDIATE); - LL_LPTIM_SetCounterMode(LPTIM1, LL_LPTIM_COUNTER_MODE_INTERNAL); - LL_LPTIM_TrigSw(LPTIM1); - LL_LPTIM_SetInput1Src(LPTIM1, LL_LPTIM_INPUT1_SRC_GPIO); - LL_LPTIM_SetInput2Src(LPTIM1, LL_LPTIM_INPUT2_SRC_GPIO); - /* USER CODE BEGIN LPTIM1_Init 2 */ - - LL_LPTIM_Enable(LPTIM1); - LL_LPTIM_SetAutoReload(LPTIM1, 0xFFFF); - LL_LPTIM_EnableIT_ARRM(LPTIM1); - - /* USER CODE END LPTIM1_Init 2 */ - -} - -/** - * @brief QUADSPI Initialization Function - * @param None - * @retval None - */ -static void MX_QUADSPI_Init(void) -{ - - /* USER CODE BEGIN QUADSPI_Init 0 */ - - /* USER CODE END QUADSPI_Init 0 */ - - /* USER CODE BEGIN QUADSPI_Init 1 */ - - /* USER CODE END QUADSPI_Init 1 */ - /* QUADSPI parameter configuration*/ - hqspi.Instance = QUADSPI; - hqspi.Init.ClockPrescaler = 0; - hqspi.Init.FifoThreshold = 4; - hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; - hqspi.Init.FlashSize = 23; - hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE; - hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; - if (HAL_QSPI_Init(&hqspi) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN QUADSPI_Init 2 */ - - /* USER CODE END QUADSPI_Init 2 */ - -} - -/** - * @brief RTC Initialization Function - * @param None - * @retval None - */ -static void MX_RTC_Init(void) -{ - - /* USER CODE BEGIN RTC_Init 0 */ - - /* USER CODE END RTC_Init 0 */ - - /* USER CODE BEGIN RTC_Init 1 */ - - /* USER CODE END RTC_Init 1 */ - /** Initialize RTC Only - */ - hrtc.Instance = RTC; - hrtc.Init.HourFormat = RTC_HOURFORMAT_24; - hrtc.Init.AsynchPrediv = 0; - hrtc.Init.SynchPrediv = 32759; - hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; - hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE; - hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; - hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; - if (HAL_RTC_Init(&hrtc) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN RTC_Init 2 */ - - // RM page 1236 - __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); - RTC->CALR = 0x100; // CALM to midpoint - __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); - - /* USER CODE END RTC_Init 2 */ - -} - -/** - * @brief TIM1 Initialization Function - * @param None - * @retval None - */ -static void MX_TIM1_Init(void) -{ - - /* USER CODE BEGIN TIM1_Init 0 */ - - /* USER CODE END TIM1_Init 0 */ - - TIM_ClockConfigTypeDef sClockSourceConfig = {0}; - TIM_MasterConfigTypeDef sMasterConfig = {0}; - - /* USER CODE BEGIN TIM1_Init 1 */ - - /* USER CODE END TIM1_Init 1 */ - htim1.Instance = TIM1; - htim1.Init.Prescaler = 0; - htim1.Init.CounterMode = TIM_COUNTERMODE_UP; - htim1.Init.Period = 256; - htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - htim1.Init.RepetitionCounter = 0; - htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - if (HAL_TIM_Base_Init(&htim1) != HAL_OK) - { - Error_Handler(); - } - sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; - if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) - { - Error_Handler(); - } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; - sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN TIM1_Init 2 */ - - /* USER CODE END TIM1_Init 2 */ - -} - -/** - * @brief TIM2 Initialization Function - * @param None - * @retval None - */ -static void MX_TIM2_Init(void) -{ - - /* USER CODE BEGIN TIM2_Init 0 */ - - /* USER CODE END TIM2_Init 0 */ - - TIM_MasterConfigTypeDef sMasterConfig = {0}; - TIM_OC_InitTypeDef sConfigOC = {0}; - - /* USER CODE BEGIN TIM2_Init 1 */ - - /* USER CODE END TIM2_Init 1 */ - htim2.Instance = TIM2; - htim2.Init.Prescaler = 8; - htim2.Init.CounterMode = TIM_COUNTERMODE_UP; - htim2.Init.Period = 10000; - htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; - if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) - { - Error_Handler(); - } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) - { - Error_Handler(); - } - sConfigOC.OCMode = TIM_OCMODE_PWM2; - sConfigOC.Pulse = 0; - sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; - sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; - if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) - { - Error_Handler(); - } - if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN TIM2_Init 2 */ - - /* USER CODE END TIM2_Init 2 */ - HAL_TIM_MspPostInit(&htim2); - -} - -/** - * @brief TIM5 Initialization Function - * @param None - * @retval None - */ -static void MX_TIM5_Init(void) -{ - - /* USER CODE BEGIN TIM5_Init 0 */ - - /* USER CODE END TIM5_Init 0 */ - - TIM_ClockConfigTypeDef sClockSourceConfig = {0}; - TIM_MasterConfigTypeDef sMasterConfig = {0}; - TIM_OC_InitTypeDef sConfigOC = {0}; - - /* USER CODE BEGIN TIM5_Init 1 */ - - /* USER CODE END TIM5_Init 1 */ - htim5.Instance = TIM5; - htim5.Init.Prescaler = 7999; - htim5.Init.CounterMode = TIM_COUNTERMODE_UP; - htim5.Init.Period = 99; - htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - if (HAL_TIM_Base_Init(&htim5) != HAL_OK) - { - Error_Handler(); - } - sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; - if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK) - { - Error_Handler(); - } - if (HAL_TIM_OC_Init(&htim5) != HAL_OK) - { - Error_Handler(); - } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK) - { - Error_Handler(); - } - sConfigOC.OCMode = TIM_OCMODE_TIMING; - sConfigOC.Pulse = 0; - sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; - sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; - if (HAL_TIM_OC_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) - { - Error_Handler(); - } - if (HAL_TIM_OC_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN TIM5_Init 2 */ - - /* USER CODE END TIM5_Init 2 */ - -} - -/** - * @brief TIM6 Initialization Function - * @param None - * @retval None - */ -static void MX_TIM6_Init(void) -{ - - /* USER CODE BEGIN TIM6_Init 0 */ - - /* USER CODE END TIM6_Init 0 */ - - TIM_MasterConfigTypeDef sMasterConfig = {0}; - - /* USER CODE BEGIN TIM6_Init 1 */ - - /* USER CODE END TIM6_Init 1 */ - htim6.Instance = TIM6; - htim6.Init.Prescaler = 8000; - htim6.Init.CounterMode = TIM_COUNTERMODE_UP; - htim6.Init.Period = 100; - htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - if (HAL_TIM_Base_Init(&htim6) != HAL_OK) - { - Error_Handler(); - } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN TIM6_Init 2 */ - - /* USER CODE END TIM6_Init 2 */ - -} - -/** - * @brief TIM7 Initialization Function - * @param None - * @retval None - */ -static void MX_TIM7_Init(void) -{ - - /* USER CODE BEGIN TIM7_Init 0 */ - - /* USER CODE END TIM7_Init 0 */ - - TIM_MasterConfigTypeDef sMasterConfig = {0}; - - /* USER CODE BEGIN TIM7_Init 1 */ - - /* USER CODE END TIM7_Init 1 */ - htim7.Instance = TIM7; - htim7.Init.Prescaler = 0; - htim7.Init.CounterMode = TIM_COUNTERMODE_UP; - htim7.Init.Period = 256; - htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - if (HAL_TIM_Base_Init(&htim7) != HAL_OK) - { - Error_Handler(); - } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN TIM7_Init 2 */ - - /* USER CODE END TIM7_Init 2 */ - -} - -/** - * @brief USART1 Initialization Function - * @param None - * @retval None - */ -static void MX_USART1_UART_Init(void) -{ - - /* USER CODE BEGIN USART1_Init 0 */ - - /* USER CODE END USART1_Init 0 */ - - /* USER CODE BEGIN USART1_Init 1 */ - - /* USER CODE END USART1_Init 1 */ - huart1.Instance = USART1; - huart1.Init.BaudRate = 9600; - huart1.Init.WordLength = UART_WORDLENGTH_8B; - huart1.Init.StopBits = UART_STOPBITS_1; - huart1.Init.Parity = UART_PARITY_NONE; - huart1.Init.Mode = UART_MODE_TX_RX; - huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; - huart1.Init.OverSampling = UART_OVERSAMPLING_16; - huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; - huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; - if (HAL_UART_Init(&huart1) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN USART1_Init 2 */ - - /* USER CODE END USART1_Init 2 */ - -} - -/** - * @brief USART2 Initialization Function - * @param None - * @retval None - */ -static void MX_USART2_UART_Init(void) -{ - - /* USER CODE BEGIN USART2_Init 0 */ - - /* USER CODE END USART2_Init 0 */ - - /* USER CODE BEGIN USART2_Init 1 */ - - /* USER CODE END USART2_Init 1 */ - huart2.Instance = USART2; - huart2.Init.BaudRate = 115200; - huart2.Init.WordLength = UART_WORDLENGTH_9B; - huart2.Init.StopBits = UART_STOPBITS_1; - huart2.Init.Parity = UART_PARITY_EVEN; - huart2.Init.Mode = UART_MODE_TX_RX; - huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; - huart2.Init.OverSampling = UART_OVERSAMPLING_16; - huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; - huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_RXOVERRUNDISABLE_INIT; - huart2.AdvancedInit.OverrunDisable = UART_ADVFEATURE_OVERRUN_DISABLE; - if (HAL_UART_Init(&huart2) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN USART2_Init 2 */ - - /* USER CODE END USART2_Init 2 */ - -} - -/** - * Enable DMA controller clock - */ -static void MX_DMA_Init(void) -{ - - /* DMA controller clock enable */ - __HAL_RCC_DMA1_CLK_ENABLE(); - __HAL_RCC_DMA2_CLK_ENABLE(); - - /* DMA interrupt init */ - /* DMA1_Channel3_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 1, 0); - HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn); - /* DMA1_Channel4_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn); - /* DMA1_Channel5_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); - /* DMA1_Channel6_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn); - /* DMA1_Channel7_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn); - /* DMA2_Channel4_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA2_Channel4_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA2_Channel4_IRQn); - /* DMA2_Channel5_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA2_Channel5_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA2_Channel5_IRQn); - -} - -/** - * @brief GPIO Initialization Function - * @param None - * @retval None - */ -static void MX_GPIO_Init(void) -{ - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - /* GPIO Ports Clock Enable */ - __HAL_RCC_GPIOC_CLK_ENABLE(); - __HAL_RCC_GPIOH_CLK_ENABLE(); - __HAL_RCC_GPIOA_CLK_ENABLE(); - __HAL_RCC_GPIOB_CLK_ENABLE(); - - /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 - |GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 - |GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11 - |GPIO_PIN_12, GPIO_PIN_RESET); - - /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14 - |GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 - |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET); - - /*Configure GPIO pins : PC13 PC0 PC1 PC2 - PC3 PC4 PC5 PC6 - PC8 PC9 PC10 PC11 - PC12 */ - GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 - |GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 - |GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11 - |GPIO_PIN_12; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - - /*Configure GPIO pins : PB2 PB12 PB13 PB14 - PB15 PB3 PB4 PB5 - PB6 PB7 PB8 PB9 */ - GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14 - |GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 - |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); - - /*Configure GPIO pin : PA8 */ - GPIO_InitStruct.Pin = GPIO_PIN_8; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_PULLUP; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - -} - -/* USER CODE BEGIN 4 */ - -/* USER CODE END 4 */ - -/** - * @brief This function is executed in case of error occurrence. - * @retval None - */ -void Error_Handler(void) -{ - /* USER CODE BEGIN Error_Handler_Debug */ - /* User can add his own implementation to report the HAL error return state */ - - __disable_irq(); - - buffer_c[0].high=0b11011110; - buffer_c[1].high=0b11011101; - buffer_c[2].high=0b11011011; - buffer_c[3].high=0b11010111; - buffer_c[4].high=0b11001111; - buffer_c[0].low=0b01010000; - buffer_c[1].low=0b01010000; - buffer_c[2].low=0b01011100; - buffer_c[3].low=0b01010000; - buffer_c[4].low=0; - - buffer_b[0] = bCat0; - buffer_b[1] = bCat1; - buffer_b[2] = bCat2; - buffer_b[3] = bCat3; - buffer_b[4] = bCat4 | 0b0111100100; - - //setDisplayPWM(5); - - - - - while(1); - /* USER CODE END Error_Handler_Debug */ -} - -#ifdef USE_FULL_ASSERT -/** - * @brief Reports the name of the source file and the source line number - * where the assert_param error has occurred. - * @param file: pointer to the source file name - * @param line: assert_param error line source number - * @retval None - */ -void assert_failed(uint8_t *file, uint32_t line) -{ - /* USER CODE BEGIN 6 */ - /* User can add his own implementation to report the file name and line number, - tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ - /* USER CODE END 6 */ -} -#endif /* USE_FULL_ASSERT */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * @file : main.c + * @brief : Main program body + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2020 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ +/* USER CODE END Header */ + +/* Includes ------------------------------------------------------------------*/ +#include "main.h" +#include "fatfs.h" +#include "usb_device.h" + +/* Private includes ----------------------------------------------------------*/ +/* USER CODE BEGIN Includes */ +#include +#include +#include +#include +#include "qspi_drv.h" +#include "zonedetect.h" +#include "chainloader.h" +#include "astro.h" +/* USER CODE END Includes */ + +/* Private typedef -----------------------------------------------------------*/ +/* USER CODE BEGIN PTD */ + +/* USER CODE END PTD */ + +/* Private define ------------------------------------------------------------*/ +/* USER CODE BEGIN PD */ +/* USER CODE END PD */ + +/* Private macro -------------------------------------------------------------*/ +/* USER CODE BEGIN PM */ + +/* USER CODE END PM */ + +/* Private variables ---------------------------------------------------------*/ +ADC_HandleTypeDef hadc1; +ADC_HandleTypeDef hadc3; + +CRC_HandleTypeDef hcrc; + +DAC_HandleTypeDef hdac1; +DMA_HandleTypeDef hdma_dac_ch1; + +QSPI_HandleTypeDef hqspi; + +RTC_HandleTypeDef hrtc; + +TIM_HandleTypeDef htim1; +TIM_HandleTypeDef htim2; +TIM_HandleTypeDef htim5; +TIM_HandleTypeDef htim6; +TIM_HandleTypeDef htim7; +DMA_HandleTypeDef hdma_tim1_up; +DMA_HandleTypeDef hdma_tim5_ch1; +DMA_HandleTypeDef hdma_tim5_ch2; +DMA_HandleTypeDef hdma_tim7_up; + +UART_HandleTypeDef huart1; +UART_HandleTypeDef huart2; +DMA_HandleTypeDef hdma_usart1_rx; +DMA_HandleTypeDef hdma_usart2_tx; + +/* USER CODE BEGIN PV */ + +/* USER CODE END PV */ + +/* Private function prototypes -----------------------------------------------*/ +void SystemClock_Config(void); +static void MX_GPIO_Init(void); +static void MX_DMA_Init(void); +static void MX_QUADSPI_Init(void); +static void MX_TIM1_Init(void); +static void MX_USART2_UART_Init(void); +static void MX_USART1_UART_Init(void); +static void MX_TIM2_Init(void); +static void MX_ADC1_Init(void); +static void MX_DAC1_Init(void); +static void MX_TIM6_Init(void); +static void MX_RTC_Init(void); +static void MX_TIM7_Init(void); +static void MX_CRC_Init(void); +static void MX_LPTIM1_Init(void); +static void MX_TIM5_Init(void); +static void MX_ADC3_Init(void); +/* USER CODE BEGIN PFP */ +void tmToBcd(struct tm *in, bcdStamp_t *out ); +uint8_t loadRulesSingle(char * str); +void nextMode(_Bool); +/* USER CODE END PFP */ + +/* Private user code ---------------------------------------------------------*/ +/* USER CODE BEGIN 0 */ +const uint8_t cLut[]= { cSegDecode0, cSegDecode1, cSegDecode2, cSegDecode3, cSegDecode4, cSegDecode5, cSegDecode6, cSegDecode7, cSegDecode8, cSegDecode9 }; +const uint16_t bLut[]={ bSegDecode0, bSegDecode1, bSegDecode2, bSegDecode3, bSegDecode4, bSegDecode5, bSegDecode6, bSegDecode7, bSegDecode8, bSegDecode9 }; + +const char* wday_str[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; + +buffer_c_t buffer_c[80] = {0}; + +uint16_t buffer_b[80] = {0}; + +uint8_t uart2_tx_buffer[32]; + +volatile uint16_t buffer_adc[ADC_BUFFER_SIZE] = {0}; +uint16_t buffer_dac[DAC_BUFFER_SIZE] = {[0 ... DAC_BUFFER_SIZE-1] = 4095}; +float dac_target=4095; +float vbat = 0.0; + +uint16_t buffer_colons_L[200] = {0}; +uint16_t buffer_colons_R[200] = {0}; + +uint8_t nmea[NMEA_BUF_SIZE]; +uint8_t satview[SV_COUNT]; +uint8_t satview_stale = 0; + +time_t currentTime; +bcdStamp_t nextBcd; +int tm_yday; +int8_t tm_wday; +int iso_year; +int8_t iso_wday; +uint8_t iso_week; +uint32_t countdown_days; +int32_t currentOffset=0; + +struct { + uint8_t c; + uint16_t b[5]; +} next7seg; + +uint8_t decisec=0, centisec=0, millisec=0; + +float longitude=-9999, latitude=-9999; +_Bool data_valid=0, had_pps=0, rtc_good=0, new_position=1; + +// Astro pack — sun/moon/grid read-outs, computed once a second in the main loop +// (astro_update) and formatted by the sendDate cases, the same compute-in-loop / +// format-in-ISR split MODE_VBAT uses for vbat. +struct astro_cache_s { + uint32_t epoch; // currentTime this was computed for; 0 = never computed + _Bool have_pos; // a usable lat/lon was available + _Bool sun_up_today; // false = polar day/night (no rise/set this date) + int16_t rise_min, set_min, noon_min; // local minutes-of-day [0,1440) + int16_t az, el; // sun azimuth 0..359 / elevation, whole degrees + uint8_t moon_idx, moon_pct; // phase index 0..7 / illuminated % + char grid[8]; // Maidenhead locator, or "----" + float lat_show, lon_show; // the snapshot lat/lon, for MODE_LATLON +} astro = {0}; +#define rtc_last_write RTC->BKP30R +#define rtc_last_calibration RTC->BKP31R +uint32_t last_pps_time = 0; +uint32_t time_till_first_fix = 0; + +struct { + uint32_t t; + int32_t offset; +} rules[162]; +#define MAX_RULES (sizeof rules / sizeof rules[0]) + +char loadedRulesString[32]; +char preloadRulesString[32]; +char textDisplay[32]; +_Bool delayedLoadRules = 0; +_Bool delayedReadConfigFile = 0; +_Bool delayedCheckOnEject = 0; +uint32_t delayedDisplayFreq = 0; + +_Bool waitingForLatch = 0; +_Bool resendDate = 0; + +uint32_t LPTIM1_high; + +uint8_t displayMode = 0, countMode = 0, colonMode = 0; +uint8_t requestMode = 255; +uint8_t nmea_cdc_level=0; +int debug_rtc_val = 0; + +#define CHECK_CONFIG_MTIME + +struct { +#ifdef CHECK_CONFIG_MTIME + unsigned short fdate; + unsigned short ftime; +#endif + uint32_t tolerance_1ms; + uint32_t tolerance_10ms; + uint32_t tolerance_100ms; + float fake_long; + float fake_lat; + time_t countdown_to; + float brightness_override; + volatile _Bool zone_override; + _Bool modes_enabled[NUM_DISPLAY_MODES]; + +} config = {0}; + +struct { + float in; + float out; +} brightnessCurve[] = { + {0, 4095-0}, + {1425, 4095-737}, + {2566, 4095-1601}, + {3396, 4095-2725}, + {4095, 4095-4095}, +}; + +// memcpy() appears to move data by bytes, which doesn't work with the word-accessed backup registers +// here we explicitly move data a word at a time +void memcpyword(volatile uint32_t *dest, volatile uint32_t *src, size_t n){ + while (n--){ + dest[n] = src[n]; + } +} + +// 12 bytes at 115200 8E1 is 1.14ms, 32 bytes would be 3.06ms +// --- Astro pack helpers ---------------------------------------------------- +// A usable position is held in latitude/longitude from either a GPS fix or the +// configured fake_latitude/fake_longitude; both sit at the -9999 sentinel until +// a position is known, so a simple range check is the "have we got a fix" test. +static _Bool astro_pos_ok(float lat, float lon){ + return lat >= -90.0f && lat <= 90.0f && lon >= -180.0f && lon <= 180.0f; +} +// Decimal UTC hour (sun_times may return <0 or >24) -> local minutes-of-day [0,1440). +static int astro_local_minutes(double utc_h){ + double h = fmod(utc_h + currentOffset / 3600.0, 24.0); + if (h < 0) h += 24.0; + int m = (int)(h * 60.0 + 0.5); + if (m >= 1440) m -= 1440; + return m; +} +// Recompute the astro cache (called from the main loop, never the ISR). The +// double soft-float maths runs here, then the small result struct is swapped in +// under a brief IRQ mask so sendDate() always reads a consistent snapshot. +static void astro_update(void){ + if (astro.epoch == (uint32_t)currentTime) return; // at most once a second + struct astro_cache_s c = {0}; + c.epoch = (uint32_t)currentTime; + double ph = moon_phase((double)currentTime); // moon needs no fix + c.moon_idx = moon_phase_index(ph); + c.moon_pct = (uint8_t)(moon_illuminated_fraction(ph) * 100.0 + 0.5); + float lat = latitude, lon = longitude; // one consistent snapshot of the fix + c.have_pos = astro_pos_ok(lat, lon); + if (c.have_pos) { + c.lat_show = lat; + c.lon_show = lon; + double az, el, rise = 0, set = 0, noon = 0; + sun_az_el(lat, lon, (double)currentTime, &az, &el); + int ia = (int)(az + 0.5); if (ia >= 360) ia -= 360; + c.az = (int16_t)ia; + c.el = (int16_t)(el < 0 ? el - 0.5 : el + 0.5); + c.sun_up_today = (sun_times(lat, lon, (double)currentTime, + &rise, &set, &noon, 0, 0, 0) == 0); + c.noon_min = (int16_t)astro_local_minutes(noon); // noon is valid even at the poles + if (c.sun_up_today) { + c.rise_min = (int16_t)astro_local_minutes(rise); + c.set_min = (int16_t)astro_local_minutes(set); + } + maidenhead(lat, lon, c.grid); + } else { + strcpy(c.grid, "----"); + } + __disable_irq(); + astro = c; + __enable_irq(); +} + +void sendDate( _Bool now ){ + if (waitingForLatch) { + if (countMode==COUNT_HIDDEN) { + // if we've entered count_hidden while waiting for latch, it will never happen + sendLatch() + waitingForLatch=0; + } else { + resendDate=1; + return; + } + } + + uint8_t i = 10; + HAL_UART_AbortTransmit(&huart2); + uart2_tx_buffer[0] = CMD_LOAD_TEXT; + + switch (displayMode) { + default: + case MODE_ISO8601_STD: + uart2_tx_buffer[1] ='2'; + uart2_tx_buffer[2] ='0'; + uart2_tx_buffer[3] ='0'+nextBcd.tenYears; + uart2_tx_buffer[4] ='0'+nextBcd.years; + uart2_tx_buffer[5] ='-'; + uart2_tx_buffer[6] ='0'+nextBcd.tenMonths; + uart2_tx_buffer[7] ='0'+nextBcd.months; + uart2_tx_buffer[8] ='-'; + uart2_tx_buffer[9] ='0'+nextBcd.tenDays; + uart2_tx_buffer[10]='0'+nextBcd.days; + break; +#ifdef NONCOMPLIANT_DATE_MODES + case MODE_DDMMYYYY: + uart2_tx_buffer[1] ='0'+nextBcd.tenDays; + uart2_tx_buffer[2] ='0'+nextBcd.days; + uart2_tx_buffer[3] ='-'; + uart2_tx_buffer[4] ='0'+nextBcd.tenMonths; + uart2_tx_buffer[5] ='0'+nextBcd.months; + uart2_tx_buffer[6] ='-'; + uart2_tx_buffer[7] ='2'; + uart2_tx_buffer[8] ='0'; + uart2_tx_buffer[9] ='0'+nextBcd.tenYears; + uart2_tx_buffer[10]='0'+nextBcd.years; + break; +#endif + case MODE_ISO_ORDINAL: + uart2_tx_buffer[1] ='2' ;//-2+nextBcd.seconds; + uart2_tx_buffer[2] ='0'; + uart2_tx_buffer[3] ='0'+nextBcd.tenYears; + uart2_tx_buffer[4] ='0'+nextBcd.years; + uart2_tx_buffer[5] ='-'; + i = 5 + sprintf((char*)&uart2_tx_buffer[6], "%d", tm_yday+1); + break; + case MODE_ISO_WEEK: + i = sprintf((char*)&uart2_tx_buffer[1], "%d-W%d-%d", iso_year, iso_week, iso_wday+1); + break; + case MODE_UNIX: + i = sprintf((char*)&uart2_tx_buffer[1], "%010ld", (uint32_t)currentTime); + break; + case MODE_JULIAN_DATE: + i = sprintf((char*)&uart2_tx_buffer[1], "%10f", (double)currentTime/86400.0 + 2440587.5 ); + break; + case MODE_MODIFIED_JD: + i = sprintf((char*)&uart2_tx_buffer[1], "%10f", (double)currentTime/86400.0 + 40587); + break; + case MODE_SHOW_OFFSET: + // This probably isn't the best place to do it, but the data is static anyway + + if (currentOffset<0){ + buffer_b[0]=bCat0 | 0b0000000000; + buffer_b[1]=bCat1 | 0b0100000000; + } else { + buffer_b[0]=bCat0 | 0b0100011000; + buffer_b[1]=bCat1 | 0b0111000000; + } + int minutes = ((abs(currentOffset)/60) %60); + int hours = (abs(currentOffset)/3600); + + buffer_b[2]=bCat2 | bLut[ hours/10 ]; + buffer_b[3]=bCat3 | bLut[ hours%10 ]; + buffer_b[4]=bCat4 | bLut[ minutes/10 ]; + + buffer_c[0].low= cLut[ minutes%10 ]; + buffer_c[0].high=0b11001110; + buffer_c[1].low=0; + buffer_c[2].low=0; + buffer_c[3].low=0; + + uart2_tx_buffer[1] ='u'; + uart2_tx_buffer[2] ='t'; + uart2_tx_buffer[3] ='c'; + uart2_tx_buffer[4] =' '; + uart2_tx_buffer[5] ='o'; + uart2_tx_buffer[6] ='f'; + uart2_tx_buffer[7] ='f'; + uart2_tx_buffer[8] ='s'; + uart2_tx_buffer[9] ='e'; + uart2_tx_buffer[10]='t'; + break; + case MODE_SHOW_TZ_NAME: + if (loadedRulesString[0]) { + char * zo = loadedRulesString; + while (*zo && *zo != '/') zo++; + if (currentTime%4 <2) { + zo++; + i = snprintf((char*)&uart2_tx_buffer[1], 11,"%s", zo); + } else { + i = zo-loadedRulesString; + if (i>10) i=10; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" + snprintf((char*)&uart2_tx_buffer[1], i+1,"%s", loadedRulesString); +#pragma GCC diagnostic pop + } + } else { + uart2_tx_buffer[1]='-'; + i=1; + } + break; + case MODE_WEEKDAY: + i = sprintf((char*)&uart2_tx_buffer[1], "%s", wday_str[tm_wday]); + break; + case MODE_WEEKDA_DD: + sprintf((char*)&uart2_tx_buffer[1], "%-7.7s ", wday_str[tm_wday]); + uart2_tx_buffer[9] ='0'+nextBcd.tenDays; + uart2_tx_buffer[10]='0'+nextBcd.days; + break; + case MODE_WDY_MM_DD: + sprintf((char*)&uart2_tx_buffer[1], "%.4s ", wday_str[tm_wday]); + uart2_tx_buffer[6] ='0'+nextBcd.tenMonths; + uart2_tx_buffer[7] ='0'+nextBcd.months; + uart2_tx_buffer[8] ='-'; + uart2_tx_buffer[9] ='0'+nextBcd.tenDays; + uart2_tx_buffer[10]='0'+nextBcd.days; + break; + case MODE_SATVIEW: + if (satview[SV_GPS_L1]==255 && satview[SV_GPS_UNKNOWN]==255) { + i = sprintf((char*)&uart2_tx_buffer[1], "GPS -"); + } else { + uint8_t GPS_sv = 0, GLONASS_sv = 0, GALILEO_sv = 0, BEIDOU_sv = 0; + if (satview[SV_GPS_L1]!=255) GPS_sv += satview[SV_GPS_L1]; + if (satview[SV_GPS_UNKNOWN]!=255) GPS_sv += satview[SV_GPS_UNKNOWN]; + if (satview[SV_GLONASS_L1]!=255) GLONASS_sv += satview[SV_GLONASS_L1]; + if (satview[SV_GLONASS_UNKNOWN]!=255) GLONASS_sv += satview[SV_GLONASS_UNKNOWN]; + if (satview[SV_GALILEO_E1]!=255) GALILEO_sv += satview[SV_GALILEO_E1]; + if (satview[SV_GALILEO_UNKNOWN]!=255) GALILEO_sv += satview[SV_GALILEO_UNKNOWN]; + if (satview[SV_BEIDOU_B1]!=255) BEIDOU_sv += satview[SV_BEIDOU_B1]; + if (satview[SV_BEIDOU_UNKNOWN]!=255) BEIDOU_sv += satview[SV_BEIDOU_UNKNOWN]; + + if (GLONASS_sv>0 && GLONASS_sv>=GALILEO_sv && GLONASS_sv>=BEIDOU_sv) { + i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d L%d", GPS_sv, GLONASS_sv); + } else if (GALILEO_sv>0 && GALILEO_sv>=GLONASS_sv && GALILEO_sv>=BEIDOU_sv){ + i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d A%d", GPS_sv, GALILEO_sv); + } else if (BEIDOU_sv>0 && BEIDOU_sv>=GLONASS_sv && BEIDOU_sv>=GALILEO_sv){ + i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d b%d", GPS_sv, BEIDOU_sv); + } else { + i = sprintf((char*)&uart2_tx_buffer[1], "GPS %d -", GPS_sv); + } + } + break; + case MODE_STANDBY: + return; + case MODE_COUNTDOWN: + i = sprintf((char*)&uart2_tx_buffer[1], "t-%7ldd", countdown_days); + break; + case MODE_DEBUG_BRIGHTNESS: + i = sprintf((char*)&uart2_tx_buffer[1], "%04d %04d", (int)ADC1->DR, 4095-(int)dac_target); + break; + case MODE_DEBUG_RTC: + i = sprintf((char*)&uart2_tx_buffer[1], "rtc %d", debug_rtc_val); + break; + case MODE_TEXT: + if (textDisplay[0]) { + i = snprintf((char*)&uart2_tx_buffer[1], 30,"%s", textDisplay); + } else { + uart2_tx_buffer[1]='-'; + i=1; + } + break; + case MODE_VBAT: + if (vbat == 0.0) { + i = sprintf((char*)&uart2_tx_buffer[1], "bat -"); + } else { + i = sprintf((char*)&uart2_tx_buffer[1], "bat %.4f", vbat); + } + break; + case MODE_TTFF: + // Our assumption is that uwTick is zero at power on + if (!had_pps) time_till_first_fix = (int)(uwTick/1000); + i = sprintf((char*)&uart2_tx_buffer[1], "ttff %3d.%02d", (int)(time_till_first_fix/60), (int)(time_till_first_fix%60)); + break; + case MODE_DISPLAYTEST: + int nn = currentTime%10; + + TIM2->CCR1 = 0; + TIM2->CCR2 = 0; + buffer_c[0].high &= ~cSegDP; + buffer_c[1].high &= ~cSegDP; + buffer_c[2].high &= ~cSegDP; + buffer_c[3].high &= ~cSegDP; + + if ((currentTime%20)<10) { + uart2_tx_buffer[1] = + uart2_tx_buffer[2] = + uart2_tx_buffer[3] = + uart2_tx_buffer[4] = + uart2_tx_buffer[5] = + uart2_tx_buffer[6] = + uart2_tx_buffer[7] = + uart2_tx_buffer[8] = + uart2_tx_buffer[9] = + uart2_tx_buffer[10]= '0'+ nn; + + buffer_b[0]=bCat0 | bLut[ nn ]; + buffer_b[1]=bCat1 | bLut[ nn ]; + buffer_b[2]=bCat2 | bLut[ nn ]; + buffer_b[3]=bCat3 | bLut[ nn ]; + buffer_b[4]=bCat4 | bLut[ nn ]; + + buffer_c[0].low= cLut[ nn ]; + buffer_c[1].low=cLut[ nn ]; + buffer_c[2].low=cLut[ nn ]; + buffer_c[3].low=cLut[ nn ]; + + if ((currentTime%2) ==0) { + TIM2->CCR2 = 300; + } else { + TIM2->CCR1 = 300; + } + } else { + + buffer_b[0]=bCat0 | (nn==0?bLut[8]:0); + buffer_b[1]=bCat1 | (nn==1?bLut[8]:0); + buffer_b[2]=bCat2 | (nn==2?bLut[8]:0); + buffer_b[3]=bCat3 | (nn==3?bLut[8]:0); + buffer_b[4]=bCat4 | (nn==4?bLut[8]:0); + buffer_c[0].low=(nn==5?cLut[8]:0); + buffer_c[1].low=(nn==6?cLut[8]:0); + buffer_c[2].low=(nn==7?cLut[8]:0); + buffer_c[3].low=(nn==8?cLut[8]:0); + + if (nn>=5) buffer_c[nn-5].high |= cSegDP; + + i = sprintf((char*)&uart2_tx_buffer[1], "%*s8.", nn, ""); + } + + break; + case MODE_FIRMWARE_CRC_T: + { + extern uint32_t _app_crc[]; + uint32_t fwt = byteswap32(_app_crc[0]); + i = sprintf((char*)&uart2_tx_buffer[1], "t %08lx", fwt); + } + break; + case MODE_FIRMWARE_CRC_D: + uart2_tx_buffer[0]=CMD_SHOW_CRC; + break; + + // --- Astro pack: format the main-loop-computed cache onto the date row only, + // leaving the time row as the running clock (SATVIEW-style). -------------- + case MODE_SUN: { + if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "RISE ----"); break; } + int page = (currentTime / 2) % 3; // rise -> set -> solar noon, 2 s each + // labels padded to 4 chars in the literal ("SET "/"SOL ") so the time digits + // line up under RISE without relying on the nano printf honouring "%-4s" + const char *lbl = page == 0 ? "RISE" : page == 1 ? "SET " : "SOL "; + int m = page == 0 ? astro.rise_min : page == 1 ? astro.set_min : astro.noon_min; + if (!astro.sun_up_today && page != 2) { // sun never rises/sets today + i = sprintf((char*)&uart2_tx_buffer[1], "%s ----", lbl); + } else { + i = sprintf((char*)&uart2_tx_buffer[1], "%s %02d.%02d", lbl, m / 60, m % 60); + } + break; + } + case MODE_SUN_AZEL: + if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "AZ -- EL--"); } + else if (astro.el < 0) i = sprintf((char*)&uart2_tx_buffer[1], "AZ%03dEL-%02d", astro.az, -astro.el); + else i = sprintf((char*)&uart2_tx_buffer[1], "AZ%03dEL%02d", astro.az, astro.el); + break; + case MODE_MOON: // UTC only; no fix needed + if (!astro.epoch) i = sprintf((char*)&uart2_tx_buffer[1], "MOON -"); + else i = sprintf((char*)&uart2_tx_buffer[1], "MOON %d %3d", astro.moon_idx, astro.moon_pct); + break; + case MODE_GRID: + i = sprintf((char*)&uart2_tx_buffer[1], "%s", astro.epoch ? astro.grid : "----"); + break; + case MODE_LATLON: + // RISE/SET-style layout: label, separator space, a sign slot (space when positive), then + // the digits — numbers align whether signed or not, and short values keep clear space at + // the row's end. A 3-digit longitude can't fit both the separator and the sign slot in + // 10 chars, so the separator is dropped just for that case ("LON 179.99" / "LON-179.99"). + if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "LAT ----"); } + else { + _Bool lat = (currentTime / 2) % 2 == 0; // page latitude / longitude, 2 s each + double v = lat ? astro.lat_show : astro.lon_show; + long h = (long)(v * 100.0 + (v < 0 ? -0.5 : 0.5)); // hundredths, rounded + long a2 = h < 0 ? -h : h; + i = sprintf((char*)&uart2_tx_buffer[1], (a2 >= 10000) ? "%s%c%ld.%02ld" : "%s %c%ld.%02ld", + lat ? "LAT" : "LON", h < 0 ? '-' : ' ', a2 / 100, a2 % 100); + } + break; + } + if (now) { + uart2_tx_buffer[++i]= CMD_RELOAD_TEXT; + } else { + uart2_tx_buffer[++i]= '\n'; + waitingForLatch=1; + } + HAL_UART_Transmit_DMA(&huart2, uart2_tx_buffer, i+1); + +} + +void setNextTimestamp(time_t nextTime){ + + int32_t offset = 0; + for (uint8_t i=0; i< MAX_RULES; i++) { + if (rules[i].t <= nextTime) offset=rules[i].offset; + else break; + } + // in case of the remote chance that we're interrupted while calculating, + // don't assign to currentOffset until the end of the loop + currentOffset = offset; + nextTime += offset; + + struct tm * nextTm = gmtime( &nextTime ); + tmToBcd( nextTm, &nextBcd ); + tm_yday = nextTm->tm_yday; + tm_wday = nextTm->tm_wday; + + if (displayMode == MODE_ISO_WEEK){ + iso_wday = (nextTm->tm_wday + 6) % 7; + nextTm->tm_mday -= iso_wday -3; + mktime(nextTm); + iso_year = nextTm->tm_year + 1900; + iso_week = nextTm->tm_yday/7 + 1; + } + + next7seg.c = cLut[nextBcd.seconds]; + + next7seg.b[0] = bCat0 | cLut[nextBcd.tenHours]<<2; + next7seg.b[1] = bCat1 | cLut[nextBcd.hours]<<2; + next7seg.b[2] = bCat2 | cLut[nextBcd.tenMinutes]<<2; + next7seg.b[3] = bCat3 | cLut[nextBcd.minutes]<<2; + next7seg.b[4] = bCat4 | cLut[nextBcd.tenSeconds]<<2; + +} + +void setNextCountdown(time_t nextTime){ + + int64_t remaining; + if (config.countdown_to < nextTime) { + remaining = 0; + SetPPS( &PPS_NoUpdate ); // don't show 999 at the next pulse + + } else remaining = config.countdown_to - nextTime; + + uint64_t seconds = remaining % 60; + uint64_t minutes = remaining / 60; + uint64_t hours = minutes / 60; + minutes %= 60; + countdown_days = hours / 24; + hours %= 24; + + next7seg.b[0] = bCat0 | cLut[hours / 10]<<2; + next7seg.b[1] = bCat1 | cLut[hours % 10]<<2; + next7seg.b[2] = bCat2 | cLut[minutes / 10]<<2; + next7seg.b[3] = bCat3 | cLut[minutes % 10]<<2; + next7seg.b[4] = bCat4 | cLut[seconds / 10]<<2; + next7seg.c = cLut[seconds % 10]; +} + +// Store UTC on RTC +// need to also write zone into backup registers +// Only called at the start of a second, don't attempt to write subseconds. +void write_rtc(void){ + + RTC_DateTypeDef sdatestructure; + RTC_TimeTypeDef stimestructure; + bcdStamp_t cBcd; + struct tm * cTm = gmtime( ¤tTime ); + + tmToBcd( cTm, &cBcd ); + + sdatestructure.Year = (cBcd.tenYears<<4) | cBcd.years; + sdatestructure.Month = (cBcd.tenMonths<<4) | cBcd.months; + sdatestructure.Date = (cBcd.tenDays<<4) | cBcd.days; + sdatestructure.WeekDay = RTC_WEEKDAY_MONDAY; + + HAL_RTC_SetDate(&hrtc,&sdatestructure,RTC_FORMAT_BCD); + + stimestructure.Hours = (cBcd.tenHours<<4) | cBcd.hours; + stimestructure.Minutes = (cBcd.tenMinutes<<4) | cBcd.minutes; + stimestructure.Seconds = (cBcd.tenSeconds<<4) | cBcd.seconds; + stimestructure.SubSeconds = 0x00; + stimestructure.TimeFormat = RTC_HOURFORMAT12_AM; + stimestructure.DayLightSaving = RTC_DAYLIGHTSAVING_NONE ; + stimestructure.StoreOperation = RTC_STOREOPERATION_RESET; + + HAL_RTC_SetTime(&hrtc,&stimestructure,RTC_FORMAT_BCD); + + // Write zone info to backup registers + // There are 32 words of memory, 128 bytes + // First 8 words are the zone string including separator and null byte (always less than 32 bytes) + // Next 22 words is a chunk of the ruleset in use, i.e. 11 years + // Last two words are time of write, and time of last calibration + + uint8_t i; + for (i=0; i< MAX_RULES; i++) { + if (rules[i].t > currentTime) break; + } + if (i==0) return; //something has gone wrong, data invalid + i--; //include currently active rule + + char numRulesToStore = (i+11>=MAX_RULES-1)? (MAX_RULES-i)*2 : 22; + + memcpyword( (uint32_t*)&(RTC->BKP0R), (uint32_t*)loadedRulesString, 8 ); + memcpyword( (uint32_t*)&(RTC->BKP8R), (uint32_t*)&rules[i], numRulesToStore ); + + rtc_last_write = (uint32_t)currentTime; +} + +time_t bcdToTm(bcdStamp_t *in, struct tm *out ) { + out->tm_isdst = 0; + out->tm_sec = in->seconds + in->tenSeconds*10; + out->tm_min = in->minutes + in->tenMinutes*10; + out->tm_hour = in->hours + in->tenHours*10; + out->tm_mday = in->days + in->tenDays*10; + out->tm_mon = in->months + in->tenMonths*10 -1; + out->tm_year = in->years + in->tenYears*10 + 100; //Years since 1900 + + return mktime(out); +} +void tmToBcd(struct tm *in, bcdStamp_t *out ) { + out->tenYears = (in->tm_year-100) / 10; + out->years = (in->tm_year-100) % 10; + out->tenMonths = (in->tm_mon+1) / 10; + out->months = (in->tm_mon+1) % 10; + out->tenDays = in->tm_mday / 10; + out->days = in->tm_mday % 10; + out->tenHours = in->tm_hour / 10; + out->hours = in->tm_hour % 10; + out->tenMinutes = in->tm_min / 10; + out->minutes = in->tm_min % 10; + out->tenSeconds = in->tm_sec / 10; + out->seconds = in->tm_sec % 10; +} + +void decodeRMC(void){ + + // do checksum + uint8_t *c = &nmea[1], *end = &nmea[sizeof(nmea)]; + uint8_t sum=0; + + bcdStamp_t rmcBcd; + struct tm rmcTm; + + while (*c !='*') { + sum ^= *c; + if (*c==',') *c=0; + c++; + if(c==end) return; //checksum not found + } + + sprintf((char*)nmea, "%02X", sum); + if (nmea[0] != c[1] || nmea[1]!=c[2]) return; //checksum error + +#define nextField() while (*c && c!=end) c++; c++; + + c=&nmea[7]; // Time + + if (*c==0) return; // time not present + + rmcBcd.tenHours = *c++ -'0'; + rmcBcd.hours = *c++ -'0'; + rmcBcd.tenMinutes = *c++ -'0'; + rmcBcd.minutes = *c++ -'0'; + rmcBcd.tenSeconds = *c++ -'0'; + rmcBcd.seconds = *c++ -'0'; + + if (*c++ =='.') { // subseconds not always present + //if (*c!='0') printf("subseconds non-zero: %s\n", c); + } + nextField() // Navigation receiver warning + data_valid = (*c=='A'?1:0); + + float tempLatitude=-9999, tempLongitude=-9999; + + nextField() // Latitude deg + if (*c){ + tempLatitude = (float)(*c++ -'0')*10.0; + tempLatitude += (float)(*c++ -'0'); + tempLatitude += (float)atof((char*)c) / 60.0; + } + nextField() // Latitude N/S + if (*c =='S') tempLatitude =-tempLatitude; + + nextField() // Longitude deg + if (*c){ + tempLongitude = (float)(*c++ -'0')*100.0; + tempLongitude += (float)(*c++ -'0')*10.0; + tempLongitude += (float)(*c++ -'0'); + tempLongitude += (float)atof((char*)c) / 60.0; + } + nextField() // Longitude E/W + if (*c == 'W') tempLongitude =-tempLongitude; + + if (!config.fake_long && !config.fake_lat) { + longitude = tempLongitude; + latitude = tempLatitude; + new_position=1; + } + + nextField() // Speed over ground, Knots + nextField() // Course Made Good, True + nextField() // Date + + if (*c==0) return; // date not present + + rmcBcd.tenDays = *c++ -'0'; + rmcBcd.days = *c++ -'0'; + rmcBcd.tenMonths = *c++ -'0'; + rmcBcd.months = *c++ -'0'; + rmcBcd.tenYears = *c++ -'0'; + rmcBcd.years = *c++ -'0'; + + + // Immediately after power-up, the GPS module does not know the GPS time/UTC leapsecond offset, and makes a guess + // Even if it gets a fix and starts outputting PPS, the time can be off by a few seconds (usually 2 or 3 fast) + // Only make use of this invalid data if there is nothing else to go on + if ( data_valid || (!had_pps && !rtc_good) ) { + currentTime = bcdToTm( &rmcBcd, &rmcTm ); + + if (decisec >= 9) { + currentTime++; + // check we're not <2ms away from rollover + if (centisec==9 && millisec>7) return; + + // Under normal conditions, we should only be parsing nmea at around .300 to .400 + // USART1 preemption priority is currently 1, so we could be interrupted by systick here + setNextTimestamp( currentTime ); + sendDate(0); + } + } + +} + +void decodeGSV(uint8_t rec){ + unsigned int sv = (nmea[11]-'0')*10 + (nmea[12]-'0'); + uint8_t constellation = nmea[2]; + uint8_t signal_id; + + // signal ID is not always present in GSV (on M8Q) + + unsigned int num_fields = 0, r=0; + while (++rODR, bright); + HAL_DMA_Start(&hdma_tim7_up, (uint32_t)buffer_c, (uint32_t)&GPIOC->ODR, bright); +} + +void displayOff(void){ + + uart2_tx_buffer[0]=' '; //in case already waiting for latch + uart2_tx_buffer[1]= CMD_LOAD_TEXT; + uart2_tx_buffer[2]= CMD_RELOAD_TEXT; + HAL_UART_AbortTransmit(&huart2); + HAL_UART_Transmit_DMA(&huart2, uart2_tx_buffer, 3); + + HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1); + HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2); + + HAL_DMA_Abort(&hdma_tim1_up); + HAL_DMA_Abort(&hdma_tim7_up); + GPIOB->ODR=0; + GPIOC->ODR=0; +} +void displayOn(void){ + HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); + HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); + setDisplayPWM(5); +} + +void setDisplayFreq(uint32_t freq){ + if (waitingForLatch) { + delayedDisplayFreq = freq; + return; + } + + if (freq<1000 || freq>100000) {delayedDisplayFreq=0; return;} + + uint8_t tx_buf[4]; + tx_buf[0]= CMD_SET_FREQUENCY; + tx_buf[1]= (freq>>14) & 0x7F; + tx_buf[2]= (freq>>7) & 0x7F; + tx_buf[3]= (freq) & 0x7F; + if (HAL_UART_Transmit(&huart2, tx_buf, 4, 2) == HAL_OK) { + delayedDisplayFreq = 0; + } + + uint32_t arr = round(16000000.0 / (float)freq) -1.0; + + TIM1->ARR = arr; + TIM7->ARR = arr; +} + +#define colonAnimationStart() \ + TIM5->CNT=0; \ + HAL_DMA_Start(&hdma_tim5_ch1, (uint32_t)buffer_colons_L, (uint32_t)&TIM2->CCR1, 200); \ + HAL_DMA_Start(&hdma_tim5_ch2, (uint32_t)buffer_colons_R, (uint32_t)&TIM2->CCR2, 200); + +#define colonAnimationStop() \ + HAL_DMA_Abort(&hdma_tim5_ch1); \ + HAL_DMA_Abort(&hdma_tim5_ch2); + +#define colonAnimationSync() \ + colonAnimationStop() \ + colonAnimationStart() + +void loadColonAnimation(void){ + + + switch (colonMode) { + case COLON_MODE_SLOWFADE: + for (int k=0;k<100;k++) { + buffer_colons_R[k] = + buffer_colons_L[k] = k*2; + buffer_colons_R[k+100] = + buffer_colons_L[k+100] = 198-k*2; + } + break; + case COLON_MODE_HEARTBEAT: + for (int k=0;k<50;k++) { + buffer_colons_L[k] = k*4; + } + for (int k=0;k<100;k++) { + buffer_colons_L[k+50] = 200 - k*2; + } + for (int k=0;k<50;k++) { + buffer_colons_L[k+150] = 0; + } + for (int k=0;k<200;k++) { + buffer_colons_R[k] = buffer_colons_L[(k+175)%200]; + } + + break; + case COLON_MODE_1PPS_SAWTOOTH: + for (int k=0;k<100;k++) { + buffer_colons_R[k] = + buffer_colons_L[k] = 196-(k*k)/50; + buffer_colons_R[k+100] = + buffer_colons_L[k+100] = 196-(k*k)/50; + } + break; + case COLON_MODE_ALT_SAWTOOTH: + for (int k=0;k<100;k++) { + buffer_colons_R[k] = 0; + buffer_colons_L[k+100] = 0; + buffer_colons_L[k] = 196-(k*k)/50; + buffer_colons_R[k+100] = 196-(k*k)/50; + } + break; + case COLON_MODE_TOGGLE: + for (int k=0;k<100;k++) { + buffer_colons_R[k] = 200; + buffer_colons_L[k] = 200; + buffer_colons_R[k+100] = 0; + buffer_colons_L[k+100] = 0; + } + break; + case COLON_MODE_SOLID: + for (int k=0;k<200;k++) { + buffer_colons_R[k] = 200; + buffer_colons_L[k] = 200; + } + break; + } + +} + +_Bool truthy(char const* str){ + if (strcasecmp(str, "on")==0) return 1; + if (strcasecmp(str, "enabled")==0) return 1; + if (strcasecmp(str, "1")==0) return 1; + return 0; +} + +_Bool falsey(char const* str){ + if (strcasecmp(str, "off")==0) return 1; + if (strcasecmp(str, "disabled")==0) return 1; + if (strcasecmp(str, "0")==0) return 1; + if (strcasecmp(str, "none")==0) return 1; + return 0; +} + +// Accept a float between 0.0 and 1.0, or an int from 0 to 4096 +float parseBrightness(char *v, _Bool invert){ + if (!v[0]) return -1; + float b = strtof(v, NULL); + if (!isfinite(b) || b<0.0) return -1; + if (b<=1.0 && v[1]=='.') + return invert? (1.0-b) * 4095 : b*4095; + if (b<=4095) + return invert? 4095-b : b; + return -1; +} + +#define set_mode_enabled(mode, value) \ + if ((config.modes_enabled[mode] = truthy(value))) requestMode=mode; + +void parseConfigString(char *key, char *value) { + + if (strcasecmp(key, "text") == 0) { + + strcpy(textDisplay, value); + + } else if (strcasecmp(key, "MATRIX_FREQUENCY") == 0) { + + setDisplayFreq(atoi(value)); + + } else if (strcasecmp(key, "zone_override") == 0) { + + if (!value[0] || delayedLoadRules) return; + + strcpy(preloadRulesString, value); + delayedLoadRules=1; + ZDAbort(); + + } else if (strcasecmp(key, "brightness") == 0) { + + config.brightness_override = parseBrightness(value, 1); + + } else if (strcasecmp(key, "countdown_to") == 0) { + + // support fractional seconds?? + struct tm t = {0}; + if( sscanf(value, "%d-%d-%dT%d:%d:%dZ", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec) >=3) { + + if (t.tm_year > 9999) return; // arbitrary cutoff, ~3e6 days + t.tm_year -= 1900; + t.tm_mon -= 1; + + config.countdown_to = mktime(&t) -1; + + } + } else if (strcasecmp(key, "MODE_ISO8601_STD") == 0) { + set_mode_enabled(MODE_ISO8601_STD, value); + } else if (strcasecmp(key, "MODE_ISO_ORDINAL") == 0) { + set_mode_enabled(MODE_ISO_ORDINAL, value); + } else if (strcasecmp(key, "MODE_ISO_WEEK") == 0) { + set_mode_enabled(MODE_ISO_WEEK, value); + } else if (strcasecmp(key, "MODE_UNIX") == 0) { + set_mode_enabled(MODE_UNIX, value); + } else if (strcasecmp(key, "MODE_JULIAN_DATE") == 0) { + set_mode_enabled(MODE_JULIAN_DATE, value); + } else if (strcasecmp(key, "MODE_MODIFIED_JD") == 0) { + set_mode_enabled(MODE_MODIFIED_JD, value); + } else if (strcasecmp(key, "MODE_SHOW_OFFSET") == 0) { + set_mode_enabled(MODE_SHOW_OFFSET, value); + } else if (strcasecmp(key, "MODE_SHOW_TZ_NAME") == 0) { + set_mode_enabled(MODE_SHOW_TZ_NAME, value); + } else if (strcasecmp(key, "MODE_WEEKDAY") == 0) { + set_mode_enabled(MODE_WEEKDAY, value); + } else if (strcasecmp(key, "MODE_WEEKDA_DD") == 0) { + set_mode_enabled(MODE_WEEKDA_DD, value); + } else if (strcasecmp(key, "MODE_WDY_MM_DD") == 0) { + set_mode_enabled(MODE_WDY_MM_DD, value); + } else if (strcasecmp(key, "MODE_STANDBY") == 0) { + set_mode_enabled(MODE_STANDBY, value); + } else if (strcasecmp(key, "MODE_COUNTDOWN") == 0) { + set_mode_enabled(MODE_COUNTDOWN, value); + } else if (strcasecmp(key, "MODE_SATVIEW") == 0) { + set_mode_enabled(MODE_SATVIEW, value); + } else if (strcasecmp(key, "MODE_DEBUG_BRIGHTNESS") == 0) { + set_mode_enabled(MODE_DEBUG_BRIGHTNESS, value); + } else if (strcasecmp(key, "MODE_DEBUG_RTC") == 0) { + set_mode_enabled(MODE_DEBUG_RTC, value); + } else if (strcasecmp(key, "MODE_TEXT") == 0) { + set_mode_enabled(MODE_TEXT, value); + } else if (strcasecmp(key, "MODE_VBAT") == 0) { + set_mode_enabled(MODE_VBAT, value); + } else if (strcasecmp(key, "MODE_DISPLAYTEST") == 0) { + set_mode_enabled(MODE_DISPLAYTEST, value); + } else if (strcasecmp(key, "MODE_TTFF") == 0) { + set_mode_enabled(MODE_TTFF, value); +#ifdef NONCOMPLIANT_DATE_MODES + } else if (strcasecmp(key, "MODE_DDMMYYYY") == 0) { + set_mode_enabled(MODE_DDMMYYYY, value); +#endif + } else if (strcasecmp(key, "MODE_FIRMWARE_CRC") == 0) { + set_mode_enabled(MODE_FIRMWARE_CRC_D, value); + set_mode_enabled(MODE_FIRMWARE_CRC_T, value); + } else if (strcasecmp(key, "MODE_SUN") == 0) { + set_mode_enabled(MODE_SUN, value); + } else if (strcasecmp(key, "MODE_SUN_AZEL") == 0) { + set_mode_enabled(MODE_SUN_AZEL, value); + } else if (strcasecmp(key, "MODE_MOON") == 0) { + set_mode_enabled(MODE_MOON, value); + } else if (strcasecmp(key, "MODE_GRID") == 0) { + set_mode_enabled(MODE_GRID, value); + } else if (strcasecmp(key, "MODE_LATLON") == 0) { + set_mode_enabled(MODE_LATLON, value); + } else if (strcasecmp(key, "Tolerance_time_1ms") == 0) { + config.tolerance_1ms = atoi(value); + } else if (strcasecmp(key, "Tolerance_time_10ms") == 0) { + config.tolerance_10ms = atoi(value); + } else if (strcasecmp(key, "Tolerance_time_100ms") == 0) { + config.tolerance_100ms = atoi(value); + } else if (strcasecmp(key, "fake_longitude") == 0) { + config.fake_long = atof(value); + } else if (strcasecmp(key, "fake_latitude") == 0) { + config.fake_lat = atof(value); + } else if (strcasecmp(key, "colon_mode") == 0) { + + if (strcasecmp(value, "solid") == 0) { + colonMode = COLON_MODE_SOLID; + } else if (strcasecmp(value, "heartbeat") == 0) { + colonMode = COLON_MODE_HEARTBEAT; + } else if (strcasecmp(value, "sawtooth") == 0) { + colonMode = COLON_MODE_1PPS_SAWTOOTH; + } else if (strcasecmp(value, "alt_sawtooth") == 0) { + colonMode = COLON_MODE_ALT_SAWTOOTH; + } else if (strcasecmp(value, "toggle") == 0) { + colonMode = COLON_MODE_TOGGLE; + } else colonMode = COLON_MODE_SLOWFADE; + + } else if (strcasecmp(key, "nmea") == 0) { + + if (falsey(value)) { + nmea_cdc_level = NMEA_NONE; + } else if (strcasecmp(value, "rmc") == 0) { + nmea_cdc_level = NMEA_RMC; + } else nmea_cdc_level = NMEA_ALL; + + } else if (key[0]=='B' && key[1]=='S' && key[3]==0) { //BS1, BS2, etc + if (!key[2] || key[2]<'1' || key[2]>'0'+sizeof(brightnessCurve)/sizeof(brightnessCurve[0])) return; + + char *c = &value[0]; + while (*c++) if(*c==',') break; + if (*c==0) return; + *c=0; c++; + + float in = parseBrightness(value,0); + float out = parseBrightness(c,1); + if (in<0 || out<0) return; + + brightnessCurve[key[2]-'1'].in = in; + brightnessCurve[key[2]-'1'].out = out; + + } + +} + +void postConfigCleanup(void){ + loadColonAnimation(); + + // check at least one mode is enabled + uint8_t j = 0; + for (uint8_t i=0; i=2)) { + parseConfigString(key, value); + postConfigCleanup(); + } + k=0; + v=0; + state=0; + return; + } + + switch (state) { + case 0: // read key + if (k) { + if (c=='=') {state =2; break;} + if (c==' ' || c=='\t') {state =1; break;} + } + key[k++] = c; + if (k==31) k--; + break; + case 1: // whitespace + if (c=='=') state=2; + else if (c!=' ' && c!='\t') {state=0; k=0; key[k++]=c;} + break; + case 2: //second whitespace + if (c!=' ' && c!='\t' && c!='=') {state=3; value[v++]=c;} + break; + case 3: + value[v++]=c; + if (v==31) v--; + } +} + +void readConfigFile(void){ + +#ifdef CHECK_CONFIG_MTIME + FILINFO fno; + if (f_stat(CONFIG_FILENAME, &fno) == FR_OK) { + // if unchanged, exit early before touching any config + // if the file doesn't exist, fall through and fail on the f_open + if (fno.fdate==config.fdate && fno.ftime==config.ftime) return; + config.fdate=fno.fdate; + config.ftime=fno.ftime; + } +#endif + + config.tolerance_1ms = 1000; + config.tolerance_10ms = 10000; + config.tolerance_100ms = 100000; + config.zone_override = 0; + config.brightness_override = -1.0; + colonMode = 0; + + FIL file; + + if (f_open(&file, CONFIG_FILENAME, FA_READ) != FR_OK) { + postConfigCleanup(); + return; + } + + char key[32], value[32], s[1]; + unsigned int rc; + uint16_t col=0; + + + while (1) { + f_read(&file, s, 1, &rc); + if (rc!=1) break; //EOF + + if (s[0]=='\r' || s[0]=='\n') { col=0; continue; } //EOL + + if (col==0 && (s[0]=='#' || s[0]==';')) { // comments + while (rc && s[0]!='\n') f_read(&file, s, 1, &rc); + continue; + } + + if (s[0]!='=') { + if (col CAL_PERIOD) { + + LPTIM1_high=0; + LL_LPTIM_StartCounter(LPTIM1, LL_LPTIM_OPERATING_MODE_CONTINUOUS); + calibStart = currentTime; + + } else if ((uint32_t)currentTime - calibStart == CAL_PERIOD) { + volatile uint16_t x = LPTIM1->CNT; + volatile uint16_t y = LPTIM1->CNT; + if (x!=y) goto skipRtcCal; + + int32_t error = ((LPTIM1_high<<16) + x) - 32768*CAL_PERIOD + LPTIM_START_DELAY; + float e = (float)error * 32.0 / CAL_PERIOD; + + debug_rtc_val = error;//0x100 + round(e); + + if (e>255.0 || e< -255.0) goto skipRtcCal; + + __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); + RTC->CALR = 0x100 + (int)round(e); + __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); + rtc_last_calibration = (uint32_t)currentTime; + +skipRtcCal: + // Prepare the counter for the next calibration + // LPTIM1->CNT is read only, the only way to zero it is to disable and re-enable the timer. + // There is a further delay associated with this, better to put it here than right at the moment we want to start the timer. + LPTIM1->CR &= ~LPTIM_CR_ENABLE; + LPTIM1->CR |= LPTIM_CR_ENABLE; + LL_LPTIM_SetAutoReload(LPTIM1, 0xFFFF); + LL_LPTIM_ClearFLAG_ARRM(LPTIM1); // just in case there's one pending + } +} + +void EXTI9_5_IRQHandler(void){__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7);} + +// PPS rising edge +void PPS(void) +{ + SysTick->VAL = SysTick->LOAD; + + buffer_c[3].low=cLut[0]; + buffer_c[2].low=cLut[0]; + buffer_c[1].low=cLut[0]; + loadNextTimestamp(); + millisec=0; + centisec=0; + decisec=0; + + __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7); + + // clear systick flag if set? + + // During first power up PPS can be emitted before the GPS leapsecond offset is known + // In this case, it is safest to pretend PPS hasn't happened + if (!data_valid) return; + + calibrateRTC(); + + if ((currentTime & 1) ==0) {colonAnimationSync()} + + had_pps = 1; + last_pps_time = (uint32_t)currentTime; +} + +void PPS_NoUpdate(void) +{ + SysTick->VAL = SysTick->LOAD; + triggerPendSV(); + + millisec=0; + centisec=0; + decisec=0; + + __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7); + + if (!data_valid) return; + + calibrateRTC(); + + had_pps = 1; + last_pps_time = (uint32_t)currentTime; +} + +void PPS_Countdown(void) +{ + SysTick->VAL = SysTick->LOAD; + + buffer_c[3].low=cLut[9]; + buffer_c[2].low=cLut[9]; + buffer_c[1].low=cLut[9]; + loadNextTimestamp(); + millisec=0; + centisec=0; + decisec=0; + + __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_7); + + if (!data_valid) return; + calibrateRTC(); + if ((currentTime & 1) ==0) {colonAnimationSync()} + + had_pps = 1; + last_pps_time = (uint32_t)currentTime; +} + +void PPS_Init(void){ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + /*Configure GPIO pin : PC7 */ + GPIO_InitStruct.Pin = GPIO_PIN_7; + GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + /* EXTI interrupt init*/ + HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); + + SetPPS( &PPS ); +} + +#define timetick() \ + millisec++; \ + if (millisec>=10) { \ + millisec=0; \ + centisec++; \ + if (centisec>=10) { \ + centisec=0; \ + decisec++; \ + if (decisec>=10) { \ + decisec=0; \ + loadNextTimestamp(); \ + } \ + } \ + } + +void SysTick_CountUp_P3(void) +{ + timetick() + + buffer_c[3].low=cLut[millisec]; + buffer_c[2].low=cLut[centisec]; + buffer_c[1].low=cLut[decisec]; + + + + HAL_IncTick(); + + // At the 0.900 mark, we calculate what the display should read at the next pulse + if (decisec==9 && centisec==0 && millisec==0){ + // Calculating the next display from the unix timestamp takes about 32uS with -O2, -O3 or -Os + // takes about 70uS on -O0 so I think it's fine to do this within systick + // If needed, we should move this to a lower priority software-triggered interrupt + currentTime++; + setNextTimestamp( currentTime ); + sendDate(0); + } +} + +void SysTick_CountUp_P2(void) { + timetick() + + buffer_c[2].low=cLut[centisec]; + buffer_c[1].low=cLut[decisec]; + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextTimestamp( currentTime ); + sendDate(0); + } +} +void SysTick_CountUp_P1(void) { + + timetick() + + buffer_c[1].low=cLut[decisec]; + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextTimestamp( currentTime ); + sendDate(0); + } +} + +void SysTick_CountUp_P0(void) { + + timetick() + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextTimestamp( currentTime ); + sendDate(0); + } +} + +void SysTick_CountUp_NoUpdate(void) { + millisec++; + if (millisec>=10) { + millisec=0; + centisec++; + if (centisec>=10) { + centisec=0; + decisec++; + if (decisec>=10) { + decisec=0; + // write_rtc still needs to happen + triggerPendSV(); + } + } + } + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextTimestamp( currentTime ); + //sendDate(0); + } +} + + +void SysTick_CountDown_P3(void) +{ + timetick() + + buffer_c[3].low=cLut[9-millisec]; + buffer_c[2].low=cLut[9-centisec]; + buffer_c[1].low=cLut[9-decisec]; + + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextCountdown( currentTime ); + sendDate(0); + } +} + +void SysTick_CountDown_P2(void) +{ + timetick() + + //buffer_c[3].low=cLut[9-millisec]; + buffer_c[2].low=cLut[9-centisec]; + buffer_c[1].low=cLut[9-decisec]; + + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextCountdown( currentTime ); + sendDate(0); + } +} + +void SysTick_CountDown_P1(void) +{ + timetick() + + //buffer_c[3].low=cLut[9-millisec]; + //buffer_c[2].low=cLut[9-centisec]; + buffer_c[1].low=cLut[9-decisec]; + + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextCountdown( currentTime ); + sendDate(0); + } +} + +// A no precision countdown is going to be really ambiguous, as it will hit zero a second before the target +// Then again it will only be used in situations where the tolerance is worse than a second +void SysTick_CountDown_P0(void) +{ + timetick() + + //buffer_c[3].low=cLut[9-millisec]; + //buffer_c[2].low=cLut[9-centisec]; + //buffer_c[1].low=cLut[9-decisec]; + + HAL_IncTick(); + + if (decisec==9 && centisec==0 && millisec==0){ + currentTime++; + setNextCountdown( currentTime ); + sendDate(0); + } +} + +void SysTick_Dummy(void){ + HAL_IncTick(); +} + +// We cannot use hardware vbus monitoring since the pin is occupied by USART1 TX +// We can't use EXTI on PA8 as it's in the same group as PPS +void monitor_vbus(void){ + static _Bool vbus_state = 1; // power-on state is initialised, even if not connected + + _Bool vbus = (GPIOA->IDR & GPIO_PIN_8); + + if (vbus_state && !vbus) { // disconnected + + MX_USB_Stop(); + + } else if (vbus && !vbus_state) { // connected + + MX_USB_DEVICE_Init(); + + } + vbus_state = vbus; +} + +void measure_vbat(void){ + ADC123_COMMON->CCR |= ADC_CCR_VBATEN; + HAL_Delay(5); + HAL_ADC_Start(&hadc3); + HAL_ADC_PollForConversion(&hadc3, 10); + uint16_t adc = HAL_ADC_GetValue(&hadc3); + ADC123_COMMON->CCR &= ~ADC_CCR_VBATEN; + vbat = (float)adc *0.0024102564102564104;//3*3.29/4095.0; +} + +uint8_t f_getzcmp(FIL* fp, char * str){ + unsigned int rc; + char * a = str; + char b[1] = {1}; + uint8_t ret = 0; + + while (b[0]!=0) { + f_read(fp, &b, 1, &rc); + if (b[0] != *a++) ret=-1; + } + return ret; +} +uint8_t findField( FIL* fp, char* str, uint8_t count, uint8_t padding ) { + char buf[4]; + unsigned int rc; + for (uint8_t i=0; i= currentTime) { + SetPPS( &PPS_Countdown ); + + if (currentTime - last_pps_time < config.tolerance_1ms){ + buffer_c[0].high= 0b11001110 | cSegDP; + SetSysTick( &SysTick_CountDown_P3 ); + } else if (currentTime - last_pps_time < config.tolerance_10ms){ + buffer_c[3].low = 0b01000000; + buffer_c[0].high= 0b11001110 | cSegDP; + SetSysTick( &SysTick_CountDown_P2 ); + } else if (currentTime - rtc_last_calibration < config.tolerance_100ms){ + buffer_c[3].low = 0b01000000; + buffer_c[2].low = 0b01000000; + buffer_c[0].high= 0b11001110 | cSegDP; + SetSysTick( &SysTick_CountDown_P1 ); + } else { + buffer_c[3].low = 0b01000000; + buffer_c[2].low = 0b01000000; + buffer_c[1].low = 0b01000000; + buffer_c[0].high= 0b11001110; + SetSysTick( &SysTick_CountDown_P0 ); + } + + } else { + countMode = COUNT_HIDDEN; + SetSysTick( &SysTick_CountUp_NoUpdate ); + SetPPS( &PPS_NoUpdate ); + buffer_c[0].high= 0b11001110 | cSegDP; + buffer_c[0].low=cSegDecode0; + buffer_c[1].low=cSegDecode0; + buffer_c[2].low=cSegDecode0; + buffer_c[3].low=cSegDecode0; + + next7seg.b[0] = bCat0 | cLut[0]<<2; + next7seg.b[1] = bCat1 | cLut[0]<<2; + next7seg.b[2] = bCat2 | cLut[0]<<2; + next7seg.b[3] = bCat3 | cLut[0]<<2; + next7seg.b[4] = bCat4 | cLut[0]<<2; + next7seg.c = cLut[0]; + } + + } +} + +#define justExited(x) ((oldMode==x) && (displayMode != x)) +void nextMode(_Bool reverse){ + + uint8_t oldMode = displayMode; + + if (requestMode!=255){ + if (!config.modes_enabled[requestMode]) { + requestMode=255; + return; + } + displayMode=requestMode; + requestMode=255; + } else if (reverse) { + do { + if (--displayMode >= NUM_DISPLAY_MODES) displayMode=NUM_DISPLAY_MODES-1; + } while (!config.modes_enabled[displayMode]); + } else { + do { + if (++displayMode >=NUM_DISPLAY_MODES) displayMode=0; + } while (!config.modes_enabled[displayMode]); + } + + if (justExited(MODE_VBAT)) vbat = 0.0; + if (justExited(MODE_STANDBY)) displayOn(); + if (justExited(MODE_DISPLAYTEST)) { + buffer_c[1].high &= ~cSegDP; + buffer_c[2].high &= ~cSegDP; + buffer_c[3].high &= ~cSegDP; + } + if ( displayMode == MODE_ISO_WEEK || justExited(MODE_COUNTDOWN)) { + // If we exit countdown mode at .9 seconds + // it will show the wrong time for .1 seconds + setNextTimestamp(currentTime); + } + + if (displayMode == MODE_SHOW_OFFSET || displayMode == MODE_DISPLAYTEST) { + countMode = COUNT_HIDDEN; + SetSysTick( &SysTick_CountUp_NoUpdate ); + SetPPS( &PPS_NoUpdate ); + colonAnimationStop() + TIM2->CCR1 = 0; // specific to show_offset + TIM2->CCR2 = 300; + } else if (displayMode == MODE_COUNTDOWN) { + + if (config.countdown_to >= currentTime) { + countMode = COUNT_DOWN; + setNextCountdown(currentTime); + } else { + countMode = COUNT_HIDDEN; + countdown_days = 0; + } + setPrecision(); + TIM2->CCR1 = 0; + TIM2->CCR2 = 0; + latchSegments(); + + } + else { + if (countMode != COUNT_NORMAL) { + countMode = COUNT_NORMAL; + setPrecision(); + SetPPS( &PPS ); + TIM2->CCR1 = 0; + TIM2->CCR2 = 0; + latchSegments(); + } + } + sendDate(1); +} +void button1pressed(void){ + nextMode(0); +} +void button2pressed(void){ + nextMode(1); +} +void buttonsBothHeld(void){ + HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1); + HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2); + + HAL_DMA_Abort(&hdma_tim1_up); + HAL_DMA_Abort(&hdma_tim7_up); + GPIOB->ODR=0; + GPIOC->ODR=0; + + NVIC_SystemReset(); +} + +void generateDACbuffer(uint16_t * buf) { + + static float dac_last=4095; + + + if (displayMode == MODE_STANDBY) { + dac_target = dac_target*0.7 + 1.2*4095.0*0.3; + if (dac_target>4094.0) { + dac_target=4095.0; + displayOff(); + } + } else if (config.brightness_override >=0.0) { + dac_target = config.brightness_override; + } else { + float adc = (float)ADC1->DR; + + uint8_t i; + for (i=1; i< sizeof(brightnessCurve)/sizeof(brightnessCurve[0]) -1; i++){ + if (brightnessCurve[i].in > adc) break; + } + float factor = (adc - brightnessCurve[i-1].in) / (brightnessCurve[i].in - brightnessCurve[i-1].in); + + float out = brightnessCurve[i-1].out*(1.0-factor) + brightnessCurve[i].out*factor; + + if (out>4095.0 || !isfinite(out)) out=4095.0; + else if (out<0.0) out=0.0; + + dac_target = dac_target*0.5 + out*0.5; + } + + + HAL_ADC_Start(&hadc1); + + + + float step = (dac_target-dac_last)/(DAC_BUFFER_SIZE*0.5); + for (size_t i=0; iVTOR = (uint32_t)&__VECTORS_RAM; + + SetSysTick( &SysTick_Dummy ); + + + /* USER CODE END 1 */ + + /* MCU Configuration--------------------------------------------------------*/ + + /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ + HAL_Init(); + + /* USER CODE BEGIN Init */ + + /* USER CODE END Init */ + + /* Configure the system clock */ + SystemClock_Config(); + + /* USER CODE BEGIN SysInit */ + + buffer_c[0].high=0b11001110; + buffer_c[1].high=0b11001101; + buffer_c[2].high=0b11001011; + buffer_c[3].high=0b11000111; + buffer_c[4].high=0b11001111; + + /* USER CODE END SysInit */ + + /* Initialize all configured peripherals */ + MX_GPIO_Init(); + MX_DMA_Init(); + MX_QUADSPI_Init(); + MX_TIM1_Init(); + MX_USART2_UART_Init(); + MX_FATFS_Init(); + //MX_USB_DEVICE_Init(); + MX_USART1_UART_Init(); + MX_TIM2_Init(); + MX_ADC1_Init(); + MX_DAC1_Init(); + MX_TIM6_Init(); + MX_TIM7_Init(); + MX_CRC_Init(); + MX_LPTIM1_Init(); + MX_TIM5_Init(); + /* USER CODE BEGIN 2 */ + + + // Configure display matrix + if (HAL_DMA_Start(&hdma_tim7_up, (uint32_t)buffer_c, (uint32_t)&GPIOC->ODR, 5) != HAL_OK) + Error_Handler(); + + if (HAL_DMA_Start(&hdma_tim1_up, (uint32_t)buffer_b, (uint32_t)&GPIOB->ODR, 5) != HAL_OK) + Error_Handler(); + + __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE); + __HAL_TIM_ENABLE(&htim1); + + __HAL_TIM_ENABLE_DMA(&htim7, TIM_DMA_UPDATE); + __HAL_TIM_ENABLE(&htim7); + + + doDateUpdate(); + MX_USB_DEVICE_Init(); + + // Enable UART2 interrupt for button presses + USART2->CR1 |= USART_CR1_RXNEIE; + + + // Configure UART1 for NMEA strings from GPS module + USART1->CR1 |= USART_CR1_CMIE ; + + USART1->CR1 &= ~(USART_CR1_UE); + USART1->CR2 |= '\n'<<24; + USART1->CR1 |= USART_CR1_UE; + + + MX_ADC3_Init(); + + // Configure ADC and DAC DMA for display brightness + HAL_ADC_Start(&hadc1); + HAL_TIM_Base_Start(&htim6); + + if (HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*)buffer_dac, DAC_BUFFER_SIZE, DAC_ALIGN_12B_R) !=HAL_OK) + Error_Handler(); + + // Configure Colon Separators + TIM2->CCR1 = 0; + TIM2->CCR2 = 0; + + //loadColonAnimation(); + + __HAL_TIM_ENABLE_DMA(&htim5, TIM_DMA_CC1 | TIM_DMA_CC2); + __HAL_TIM_ENABLE(&htim5); + + //colonAnimationStart() + + + //Enable DP for subseconds + buffer_c[0].high=0b11001110 | cSegDP; + + + + buffer_c[0].low=cSegDecode0; + buffer_c[1].low=cSegDecode0; + buffer_c[2].low=cSegDecode0; + buffer_c[3].low=cSegDecode0; + + next7seg.c = buffer_c[0].low; + + next7seg.b[0] = buffer_b[0] = bCat0 | bSegDecode0; + next7seg.b[1] = buffer_b[1] = bCat1 | bSegDecode0; + next7seg.b[2] = buffer_b[2] = bCat2 | bSegDecode0; + next7seg.b[3] = buffer_b[3] = bCat3 | bSegDecode0; + next7seg.b[4] = buffer_b[4] = bCat4 | bSegDecode0; + + //setDisplayPWM(5); + displayOn(); + + readConfigFile(); + checkDelayedLoadRules(); + + measure_vbat(); + + if (RTC->ISR & RTC_ISR_INITS) //RTC contains non-zero data + { + RTC_DateTypeDef sdate; + RTC_TimeTypeDef stime; + + if (!config.zone_override){ + char zone[32]; + memcpyword( (uint32_t*)zone, (uint32_t*)&(RTC->BKP0R), 8 ); + zone[31]=0; + + if (loadRulesSingle(zone) != RULES_OK){ // takes ~8ms + memcpyword( (uint32_t*)loadedRulesString, (uint32_t*)&(RTC->BKP0R), 8 ); + loadedRulesString[31]=0;//paranoia + memcpyword( (uint32_t*)rules, (uint32_t*)&(RTC->BKP8R), 22 ); + } + } + + + hrtc.Instance = RTC; + HAL_RTC_GetTime(&hrtc, &stime, RTC_FORMAT_BIN); + HAL_RTC_GetDate(&hrtc, &sdate, RTC_FORMAT_BIN); + + struct tm out; + + out.tm_isdst = 0; + + out.tm_sec = stime.Seconds; + out.tm_min = stime.Minutes; + out.tm_hour = stime.Hours; + out.tm_mday = sdate.Date; + out.tm_mon = sdate.Month -1; + out.tm_year = sdate.Year + 100; //Years since 1900 + + currentTime = mktime(&out); + + float fraction = (float)(32767 - stime.SubSeconds) / 32768.0; + + // SysTick->VAL = SysTick->LOAD; ? + millisec = (uint32_t)(fraction*1000) % 10; + centisec = (uint32_t)(fraction*100) % 10; + decisec = (uint32_t)(fraction*10) % 10; + + if (decisec>=9) currentTime++; + + setNextTimestamp( currentTime ); + sendDate(1); + latchSegments(); + + // As the coin cell goes flat, the RTC stops ticking long before the backup registers die. + // Powering on with a flat battery means the clock thinks no time has passed, and assumes it has good precision. + // Explicitly stop this by checking the battery voltage. + if (vbat > 2.70) { + rtc_good=1; + } else { + // trash the calibration time to ensure lowest precision display + if (currentTime - rtc_last_calibration < config.tolerance_100ms) + rtc_last_calibration -= config.tolerance_100ms +1; + } + + } else { // backup domain reset + + currentTime=946684800; // 2000-01-01T00:00:00 + + // The init process blanks the subsecond registers + MX_RTC_Init(); + } + + vbat = 0.0; // don't allow measurement to go stale + + setPrecision(); + PPS_Init(); + HAL_UART_Receive_DMA(&huart1, nmea, sizeof(nmea)); + +//#define MEASURE_LOOKUP_TIME + + /* USER CODE END 2 */ + + /* Infinite loop */ + /* USER CODE BEGIN WHILE */ + while (1) + { + if (new_position && !qspi_write_time && !config.zone_override + && (data_valid || (config.fake_long && config.fake_lat)) + && latitude>=-90.0 && latitude<=90.0 && longitude>=-180.0 && longitude<=180.0) { + + new_position=0; + FIL mapfile; + if (f_open(&mapfile, MAP_FILENAME, FA_READ) == FR_OK) { +#ifdef MEASURE_LOOKUP_TIME + uint32_t start=uwTick; +#endif + ZoneDetect *const zdb = ZDOpenDatabase(&mapfile); + + if (!zdb) { + // mapfile error + } else { + char* zone = ZDHelperSimpleLookupString(zdb, latitude, longitude); +#ifdef MEASURE_LOOKUP_TIME + uint32_t ztime=uwTick-start; +#endif + if (zone && !delayedLoadRules) { +#ifdef MEASURE_LOOKUP_TIME + start=uwTick; +#endif + loadRulesSingle(zone); +#ifdef MEASURE_LOOKUP_TIME + sprintf(textDisplay,"d%ld L%ld",ztime, uwTick-start); +#endif + } + free(zone); + ZDCloseDatabase(zdb); + //f_close(&mapfile); + } + } + // else no_map = 1 + } + + if (delayedCheckOnEject) firmwareCheckOnEject(); + + if (delayedReadConfigFile) { + FATFS_remount(); + readConfigFile(); + delayedReadConfigFile=0; + } + + checkDelayedLoadRules(); + + if (delayedDisplayFreq) setDisplayFreq(delayedDisplayFreq); + + monitor_vbus(); + + if (displayMode == MODE_VBAT) + measure_vbat(); + + if (displayMode == MODE_SUN || displayMode == MODE_SUN_AZEL || displayMode == MODE_MOON + || displayMode == MODE_GRID || displayMode == MODE_LATLON) + astro_update(); + + + /* USER CODE END WHILE */ + + /* USER CODE BEGIN 3 */ + } + /* USER CODE END 3 */ +} + +/** + * @brief System Clock Configuration + * @retval None + */ +void SystemClock_Config(void) +{ + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + + /** Configure LSE Drive Capability + */ + HAL_PWR_EnableBkUpAccess(); + __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW); + /** Initializes the CPU, AHB and APB busses clocks + */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE + |RCC_OSCILLATORTYPE_MSI; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.LSEState = RCC_LSE_ON; + RCC_OscInitStruct.MSIState = RCC_MSI_ON; + RCC_OscInitStruct.MSICalibrationValue = 0; + RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_11; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; + RCC_OscInitStruct.PLL.PLLM = 2; + RCC_OscInitStruct.PLL.PLLN = 64; + RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; + RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; + RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV4; + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) + { + Error_Handler(); + } + /** Initializes the CPU, AHB and APB busses clocks + */ + RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK + |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) + { + Error_Handler(); + } + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_USART1 + |RCC_PERIPHCLK_USART2|RCC_PERIPHCLK_LPTIM1 + |RCC_PERIPHCLK_USB|RCC_PERIPHCLK_ADC; + PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; + PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1; + PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE; + PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_SYSCLK; + PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; + PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_MSI; + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + { + Error_Handler(); + } + /** Configure the main internal regulator output voltage + */ + if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) + { + Error_Handler(); + } + /** Enable MSI Auto calibration + */ + HAL_RCCEx_EnableMSIPLLMode(); +} + +/** + * @brief ADC1 Initialization Function + * @param None + * @retval None + */ +static void MX_ADC1_Init(void) +{ + + /* USER CODE BEGIN ADC1_Init 0 */ + + /* USER CODE END ADC1_Init 0 */ + + ADC_MultiModeTypeDef multimode = {0}; + ADC_ChannelConfTypeDef sConfig = {0}; + + /* USER CODE BEGIN ADC1_Init 1 */ + + /* USER CODE END ADC1_Init 1 */ + /** Common config + */ + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + hadc1.Init.LowPowerAutoWait = DISABLE; + hadc1.Init.ContinuousConvMode = DISABLE; + hadc1.Init.NbrOfConversion = 1; + hadc1.Init.DiscontinuousConvMode = DISABLE; + hadc1.Init.NbrOfDiscConversion = 1; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.DMAContinuousRequests = DISABLE; + hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; + hadc1.Init.OversamplingMode = DISABLE; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + Error_Handler(); + } + /** Configure the ADC multi-mode + */ + multimode.Mode = ADC_MODE_INDEPENDENT; + if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) + { + Error_Handler(); + } + /** Configure Regular Channel + */ + sConfig.Channel = ADC_CHANNEL_10; + sConfig.Rank = ADC_REGULAR_RANK_1; + sConfig.SamplingTime = ADC_SAMPLETIME_92CYCLES_5; + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN ADC1_Init 2 */ + + /* USER CODE END ADC1_Init 2 */ + +} + +/** + * @brief ADC3 Initialization Function + * @param None + * @retval None + */ +static void MX_ADC3_Init(void) +{ + + /* USER CODE BEGIN ADC3_Init 0 */ + + /* USER CODE END ADC3_Init 0 */ + + ADC_ChannelConfTypeDef sConfig = {0}; + + /* USER CODE BEGIN ADC3_Init 1 */ + + + /* USER CODE END ADC3_Init 1 */ + /** Common config + */ + hadc3.Instance = ADC3; + hadc3.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2; + hadc3.Init.Resolution = ADC_RESOLUTION_12B; + hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc3.Init.ScanConvMode = ADC_SCAN_DISABLE; + hadc3.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + hadc3.Init.LowPowerAutoWait = DISABLE; + hadc3.Init.ContinuousConvMode = DISABLE; + hadc3.Init.NbrOfConversion = 1; + hadc3.Init.DiscontinuousConvMode = DISABLE; + hadc3.Init.NbrOfDiscConversion = 1; + hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc3.Init.DMAContinuousRequests = DISABLE; + hadc3.Init.Overrun = ADC_OVR_DATA_PRESERVED; + hadc3.Init.OversamplingMode = ENABLE; + hadc3.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_16; + hadc3.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_4; + hadc3.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER; + hadc3.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_RESUMED_MODE; + + if (HAL_ADC_Init(&hadc3) != HAL_OK) + { + Error_Handler(); + } + + HAL_ADCEx_Calibration_Start(&hadc3, ADC_SINGLE_ENDED); + + /** Configure Regular Channel + */ + sConfig.Channel = ADC_CHANNEL_VBAT; + sConfig.Rank = ADC_REGULAR_RANK_1; + sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5; + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN ADC3_Init 2 */ + ADC123_COMMON->CCR &= ~ADC_CCR_VBATEN; + /* USER CODE END ADC3_Init 2 */ + +} + +/** + * @brief CRC Initialization Function + * @param None + * @retval None + */ +static void MX_CRC_Init(void) +{ + + /* USER CODE BEGIN CRC_Init 0 */ + + /* USER CODE END CRC_Init 0 */ + + /* USER CODE BEGIN CRC_Init 1 */ + + /* USER CODE END CRC_Init 1 */ + hcrc.Instance = CRC; + hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE; + hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE; + hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_BYTE; + hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_ENABLE; + hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_WORDS; + if (HAL_CRC_Init(&hcrc) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN CRC_Init 2 */ + + /* USER CODE END CRC_Init 2 */ + +} + +/** + * @brief DAC1 Initialization Function + * @param None + * @retval None + */ +static void MX_DAC1_Init(void) +{ + + /* USER CODE BEGIN DAC1_Init 0 */ + + /* USER CODE END DAC1_Init 0 */ + + DAC_ChannelConfTypeDef sConfig = {0}; + + /* USER CODE BEGIN DAC1_Init 1 */ + + /* USER CODE END DAC1_Init 1 */ + /** DAC Initialization + */ + hdac1.Instance = DAC1; + if (HAL_DAC_Init(&hdac1) != HAL_OK) + { + Error_Handler(); + } + /** DAC channel OUT1 config + */ + sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE; + sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO; + sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; + sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE; + sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY; + if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN DAC1_Init 2 */ + HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 4095); + /* USER CODE END DAC1_Init 2 */ + +} + +/** + * @brief LPTIM1 Initialization Function + * @param None + * @retval None + */ +static void MX_LPTIM1_Init(void) +{ + + /* USER CODE BEGIN LPTIM1_Init 0 */ + + /* USER CODE END LPTIM1_Init 0 */ + + /* Peripheral clock enable */ + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1); + + /* LPTIM1 interrupt Init */ + NVIC_SetPriority(LPTIM1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),1, 0)); + NVIC_EnableIRQ(LPTIM1_IRQn); + + /* USER CODE BEGIN LPTIM1_Init 1 */ + + /* USER CODE END LPTIM1_Init 1 */ + LL_LPTIM_SetClockSource(LPTIM1, LL_LPTIM_CLK_SOURCE_INTERNAL); + LL_LPTIM_SetPrescaler(LPTIM1, LL_LPTIM_PRESCALER_DIV1); + LL_LPTIM_SetPolarity(LPTIM1, LL_LPTIM_OUTPUT_POLARITY_REGULAR); + LL_LPTIM_SetUpdateMode(LPTIM1, LL_LPTIM_UPDATE_MODE_IMMEDIATE); + LL_LPTIM_SetCounterMode(LPTIM1, LL_LPTIM_COUNTER_MODE_INTERNAL); + LL_LPTIM_TrigSw(LPTIM1); + LL_LPTIM_SetInput1Src(LPTIM1, LL_LPTIM_INPUT1_SRC_GPIO); + LL_LPTIM_SetInput2Src(LPTIM1, LL_LPTIM_INPUT2_SRC_GPIO); + /* USER CODE BEGIN LPTIM1_Init 2 */ + + LL_LPTIM_Enable(LPTIM1); + LL_LPTIM_SetAutoReload(LPTIM1, 0xFFFF); + LL_LPTIM_EnableIT_ARRM(LPTIM1); + + /* USER CODE END LPTIM1_Init 2 */ + +} + +/** + * @brief QUADSPI Initialization Function + * @param None + * @retval None + */ +static void MX_QUADSPI_Init(void) +{ + + /* USER CODE BEGIN QUADSPI_Init 0 */ + + /* USER CODE END QUADSPI_Init 0 */ + + /* USER CODE BEGIN QUADSPI_Init 1 */ + + /* USER CODE END QUADSPI_Init 1 */ + /* QUADSPI parameter configuration*/ + hqspi.Instance = QUADSPI; + hqspi.Init.ClockPrescaler = 0; + hqspi.Init.FifoThreshold = 4; + hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; + hqspi.Init.FlashSize = 23; + hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE; + hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; + if (HAL_QSPI_Init(&hqspi) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN QUADSPI_Init 2 */ + + /* USER CODE END QUADSPI_Init 2 */ + +} + +/** + * @brief RTC Initialization Function + * @param None + * @retval None + */ +static void MX_RTC_Init(void) +{ + + /* USER CODE BEGIN RTC_Init 0 */ + + /* USER CODE END RTC_Init 0 */ + + /* USER CODE BEGIN RTC_Init 1 */ + + /* USER CODE END RTC_Init 1 */ + /** Initialize RTC Only + */ + hrtc.Instance = RTC; + hrtc.Init.HourFormat = RTC_HOURFORMAT_24; + hrtc.Init.AsynchPrediv = 0; + hrtc.Init.SynchPrediv = 32759; + hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; + hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE; + hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; + hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; + if (HAL_RTC_Init(&hrtc) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN RTC_Init 2 */ + + // RM page 1236 + __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); + RTC->CALR = 0x100; // CALM to midpoint + __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); + + /* USER CODE END RTC_Init 2 */ + +} + +/** + * @brief TIM1 Initialization Function + * @param None + * @retval None + */ +static void MX_TIM1_Init(void) +{ + + /* USER CODE BEGIN TIM1_Init 0 */ + + /* USER CODE END TIM1_Init 0 */ + + TIM_ClockConfigTypeDef sClockSourceConfig = {0}; + TIM_MasterConfigTypeDef sMasterConfig = {0}; + + /* USER CODE BEGIN TIM1_Init 1 */ + + /* USER CODE END TIM1_Init 1 */ + htim1.Instance = TIM1; + htim1.Init.Prescaler = 0; + htim1.Init.CounterMode = TIM_COUNTERMODE_UP; + htim1.Init.Period = 256; + htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + htim1.Init.RepetitionCounter = 0; + htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + if (HAL_TIM_Base_Init(&htim1) != HAL_OK) + { + Error_Handler(); + } + sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; + if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; + sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN TIM1_Init 2 */ + + /* USER CODE END TIM1_Init 2 */ + +} + +/** + * @brief TIM2 Initialization Function + * @param None + * @retval None + */ +static void MX_TIM2_Init(void) +{ + + /* USER CODE BEGIN TIM2_Init 0 */ + + /* USER CODE END TIM2_Init 0 */ + + TIM_MasterConfigTypeDef sMasterConfig = {0}; + TIM_OC_InitTypeDef sConfigOC = {0}; + + /* USER CODE BEGIN TIM2_Init 1 */ + + /* USER CODE END TIM2_Init 1 */ + htim2.Instance = TIM2; + htim2.Init.Prescaler = 8; + htim2.Init.CounterMode = TIM_COUNTERMODE_UP; + htim2.Init.Period = 10000; + htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; + if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) + { + Error_Handler(); + } + sConfigOC.OCMode = TIM_OCMODE_PWM2; + sConfigOC.Pulse = 0; + sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; + sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; + if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) + { + Error_Handler(); + } + if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN TIM2_Init 2 */ + + /* USER CODE END TIM2_Init 2 */ + HAL_TIM_MspPostInit(&htim2); + +} + +/** + * @brief TIM5 Initialization Function + * @param None + * @retval None + */ +static void MX_TIM5_Init(void) +{ + + /* USER CODE BEGIN TIM5_Init 0 */ + + /* USER CODE END TIM5_Init 0 */ + + TIM_ClockConfigTypeDef sClockSourceConfig = {0}; + TIM_MasterConfigTypeDef sMasterConfig = {0}; + TIM_OC_InitTypeDef sConfigOC = {0}; + + /* USER CODE BEGIN TIM5_Init 1 */ + + /* USER CODE END TIM5_Init 1 */ + htim5.Instance = TIM5; + htim5.Init.Prescaler = 7999; + htim5.Init.CounterMode = TIM_COUNTERMODE_UP; + htim5.Init.Period = 99; + htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + if (HAL_TIM_Base_Init(&htim5) != HAL_OK) + { + Error_Handler(); + } + sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; + if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK) + { + Error_Handler(); + } + if (HAL_TIM_OC_Init(&htim5) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK) + { + Error_Handler(); + } + sConfigOC.OCMode = TIM_OCMODE_TIMING; + sConfigOC.Pulse = 0; + sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; + sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; + if (HAL_TIM_OC_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) + { + Error_Handler(); + } + if (HAL_TIM_OC_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN TIM5_Init 2 */ + + /* USER CODE END TIM5_Init 2 */ + +} + +/** + * @brief TIM6 Initialization Function + * @param None + * @retval None + */ +static void MX_TIM6_Init(void) +{ + + /* USER CODE BEGIN TIM6_Init 0 */ + + /* USER CODE END TIM6_Init 0 */ + + TIM_MasterConfigTypeDef sMasterConfig = {0}; + + /* USER CODE BEGIN TIM6_Init 1 */ + + /* USER CODE END TIM6_Init 1 */ + htim6.Instance = TIM6; + htim6.Init.Prescaler = 8000; + htim6.Init.CounterMode = TIM_COUNTERMODE_UP; + htim6.Init.Period = 100; + htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + if (HAL_TIM_Base_Init(&htim6) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN TIM6_Init 2 */ + + /* USER CODE END TIM6_Init 2 */ + +} + +/** + * @brief TIM7 Initialization Function + * @param None + * @retval None + */ +static void MX_TIM7_Init(void) +{ + + /* USER CODE BEGIN TIM7_Init 0 */ + + /* USER CODE END TIM7_Init 0 */ + + TIM_MasterConfigTypeDef sMasterConfig = {0}; + + /* USER CODE BEGIN TIM7_Init 1 */ + + /* USER CODE END TIM7_Init 1 */ + htim7.Instance = TIM7; + htim7.Init.Prescaler = 0; + htim7.Init.CounterMode = TIM_COUNTERMODE_UP; + htim7.Init.Period = 256; + htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + if (HAL_TIM_Base_Init(&htim7) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + if (HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN TIM7_Init 2 */ + + /* USER CODE END TIM7_Init 2 */ + +} + +/** + * @brief USART1 Initialization Function + * @param None + * @retval None + */ +static void MX_USART1_UART_Init(void) +{ + + /* USER CODE BEGIN USART1_Init 0 */ + + /* USER CODE END USART1_Init 0 */ + + /* USER CODE BEGIN USART1_Init 1 */ + + /* USER CODE END USART1_Init 1 */ + huart1.Instance = USART1; + huart1.Init.BaudRate = 9600; + huart1.Init.WordLength = UART_WORDLENGTH_8B; + huart1.Init.StopBits = UART_STOPBITS_1; + huart1.Init.Parity = UART_PARITY_NONE; + huart1.Init.Mode = UART_MODE_TX_RX; + huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; + huart1.Init.OverSampling = UART_OVERSAMPLING_16; + huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; + huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; + if (HAL_UART_Init(&huart1) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN USART1_Init 2 */ + + /* USER CODE END USART1_Init 2 */ + +} + +/** + * @brief USART2 Initialization Function + * @param None + * @retval None + */ +static void MX_USART2_UART_Init(void) +{ + + /* USER CODE BEGIN USART2_Init 0 */ + + /* USER CODE END USART2_Init 0 */ + + /* USER CODE BEGIN USART2_Init 1 */ + + /* USER CODE END USART2_Init 1 */ + huart2.Instance = USART2; + huart2.Init.BaudRate = 115200; + huart2.Init.WordLength = UART_WORDLENGTH_9B; + huart2.Init.StopBits = UART_STOPBITS_1; + huart2.Init.Parity = UART_PARITY_EVEN; + huart2.Init.Mode = UART_MODE_TX_RX; + huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; + huart2.Init.OverSampling = UART_OVERSAMPLING_16; + huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; + huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_RXOVERRUNDISABLE_INIT; + huart2.AdvancedInit.OverrunDisable = UART_ADVFEATURE_OVERRUN_DISABLE; + if (HAL_UART_Init(&huart2) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN USART2_Init 2 */ + + /* USER CODE END USART2_Init 2 */ + +} + +/** + * Enable DMA controller clock + */ +static void MX_DMA_Init(void) +{ + + /* DMA controller clock enable */ + __HAL_RCC_DMA1_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); + + /* DMA interrupt init */ + /* DMA1_Channel3_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 1, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn); + /* DMA1_Channel4_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn); + /* DMA1_Channel5_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); + /* DMA1_Channel6_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn); + /* DMA1_Channel7_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn); + /* DMA2_Channel4_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA2_Channel4_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA2_Channel4_IRQn); + /* DMA2_Channel5_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA2_Channel5_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA2_Channel5_IRQn); + +} + +/** + * @brief GPIO Initialization Function + * @param None + * @retval None + */ +static void MX_GPIO_Init(void) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + /* GPIO Ports Clock Enable */ + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOH_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 + |GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 + |GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11 + |GPIO_PIN_12, GPIO_PIN_RESET); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14 + |GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 + |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET); + + /*Configure GPIO pins : PC13 PC0 PC1 PC2 + PC3 PC4 PC5 PC6 + PC8 PC9 PC10 PC11 + PC12 */ + GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2 + |GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6 + |GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11 + |GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + /*Configure GPIO pins : PB2 PB12 PB13 PB14 + PB15 PB3 PB4 PB5 + PB6 PB7 PB8 PB9 */ + GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14 + |GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 + |GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + /*Configure GPIO pin : PA8 */ + GPIO_InitStruct.Pin = GPIO_PIN_8; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_PULLUP; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + +} + +/* USER CODE BEGIN 4 */ + +/* USER CODE END 4 */ + +/** + * @brief This function is executed in case of error occurrence. + * @retval None + */ +void Error_Handler(void) +{ + /* USER CODE BEGIN Error_Handler_Debug */ + /* User can add his own implementation to report the HAL error return state */ + + __disable_irq(); + + buffer_c[0].high=0b11011110; + buffer_c[1].high=0b11011101; + buffer_c[2].high=0b11011011; + buffer_c[3].high=0b11010111; + buffer_c[4].high=0b11001111; + buffer_c[0].low=0b01010000; + buffer_c[1].low=0b01010000; + buffer_c[2].low=0b01011100; + buffer_c[3].low=0b01010000; + buffer_c[4].low=0; + + buffer_b[0] = bCat0; + buffer_b[1] = bCat1; + buffer_b[2] = bCat2; + buffer_b[3] = bCat3; + buffer_b[4] = bCat4 | 0b0111100100; + + //setDisplayPWM(5); + + + + + while(1); + /* USER CODE END Error_Handler_Debug */ +} + +#ifdef USE_FULL_ASSERT +/** + * @brief Reports the name of the source file and the source line number + * where the assert_param error has occurred. + * @param file: pointer to the source file name + * @param line: assert_param error line source number + * @retval None + */ +void assert_failed(uint8_t *file, uint32_t line) +{ + /* USER CODE BEGIN 6 */ + /* User can add his own implementation to report the file name and line number, + tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ + /* USER CODE END 6 */ +} +#endif /* USE_FULL_ASSERT */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ From be9a56fb7498b0db19d7435a643d202d48c34812 Mon Sep 17 00:00:00 2001 From: Peter Lewis Date: Sun, 5 Jul 2026 12:42:20 +0100 Subject: [PATCH 3/3] astro: configurable per-screen page dwell (page_ms, default 5500 ms) The paged modes (SUN, LATLON) previously flipped sub-screen every 2 s hard-coded. Make the dwell a config key (page_ms, ms; unset -> 5500, floored at 250 so a tiny value can't flood the date-board UART), driven off uwTick, and repaint the moment a page flips rather than waiting for the 1 Hz date-row refresh (guarded by decisec!=9 to avoid racing the SysTick sendDate). Co-Authored-By: Claude Opus 4.8 --- mk4-time/Core/Src/main.c | 25 ++++++++++++++++++++++--- qspi/config.txt | 8 ++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/mk4-time/Core/Src/main.c b/mk4-time/Core/Src/main.c index 7734603..48a0b45 100644 --- a/mk4-time/Core/Src/main.c +++ b/mk4-time/Core/Src/main.c @@ -207,6 +207,7 @@ struct { time_t countdown_to; float brightness_override; volatile _Bool zone_override; + uint16_t page_ms; // paged modes (SUN/LATLON): sub-screen dwell, ms _Bool modes_enabled[NUM_DISPLAY_MODES]; } config = {0}; @@ -238,6 +239,10 @@ void memcpyword(volatile uint32_t *dest, volatile uint32_t *src, size_t n){ static _Bool astro_pos_ok(float lat, float lon){ return lat >= -90.0f && lat <= 90.0f && lon >= -180.0f && lon <= 180.0f; } +// Sub-screen dwell (ms) for the paged modes (SUN, LATLON). Unset -> 5500 ms, a +// subjectively-tuned cadence found by feel. Floored at 250 ms so a tiny value can't +// flood the date-board UART. +static uint32_t page_ms(void){ uint32_t m = config.page_ms; return m == 0 ? 5500 : (m < 250 ? 250 : m); } // Decimal UTC hour (sun_times may return <0 or >24) -> local minutes-of-day [0,1440). static int astro_local_minutes(double utc_h){ double h = fmod(utc_h + currentOffset / 3600.0, 24.0); @@ -543,7 +548,7 @@ void sendDate( _Bool now ){ // leaving the time row as the running clock (SATVIEW-style). -------------- case MODE_SUN: { if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "RISE ----"); break; } - int page = (currentTime / 2) % 3; // rise -> set -> solar noon, 2 s each + int page = (uwTick / page_ms()) % 3; // rise -> set -> solar noon, page_ms each // labels padded to 4 chars in the literal ("SET "/"SOL ") so the time digits // line up under RISE without relying on the nano printf honouring "%-4s" const char *lbl = page == 0 ? "RISE" : page == 1 ? "SET " : "SOL "; @@ -574,7 +579,7 @@ void sendDate( _Bool now ){ // 10 chars, so the separator is dropped just for that case ("LON 179.99" / "LON-179.99"). if (!astro.have_pos || !astro.epoch) { i = sprintf((char*)&uart2_tx_buffer[1], "LAT ----"); } else { - _Bool lat = (currentTime / 2) % 2 == 0; // page latitude / longitude, 2 s each + _Bool lat = (uwTick / page_ms()) % 2 == 0; // page latitude / longitude, page_ms each double v = lat ? astro.lat_show : astro.lon_show; long h = (long)(v * 100.0 + (v < 0 ? -0.5 : 0.5)); // hundredths, rounded long a2 = h < 0 ? -h : h; @@ -1119,6 +1124,9 @@ void parseConfigString(char *key, char *value) { set_mode_enabled(MODE_GRID, value); } else if (strcasecmp(key, "MODE_LATLON") == 0) { set_mode_enabled(MODE_LATLON, value); + } else if (strcasecmp(key, "page_ms") == 0) { + int v = atoi(value); + config.page_ms = v < 0 ? 0 : (v > 65535 ? 65535 : v); // fits uint16; 0 -> default } else if (strcasecmp(key, "Tolerance_time_1ms") == 0) { config.tolerance_1ms = atoi(value); } else if (strcasecmp(key, "Tolerance_time_10ms") == 0) { @@ -2248,8 +2256,19 @@ int main(void) measure_vbat(); if (displayMode == MODE_SUN || displayMode == MODE_SUN_AZEL || displayMode == MODE_MOON - || displayMode == MODE_GRID || displayMode == MODE_LATLON) + || displayMode == MODE_GRID || displayMode == MODE_LATLON) { astro_update(); + // honour the ms page dwell: the date row otherwise only repaints at 1 Hz, so repaint + // the moment a paged mode flips sub-screen. Only with a fix (no-fix shows a + // page-independent "----"), and never in the last decisecond -- there the SysTick ISR + // runs its own (non-reentrant, shared-UART) sendDate(0), so we'd race it. Same + // decisec!=9 guard the existing main-loop sendDate(1) calls use. + if ((displayMode == MODE_SUN || displayMode == MODE_LATLON) && astro.have_pos && astro.epoch) { + static uint32_t last_pg = 0; + uint32_t pg = uwTick / page_ms(); + if (pg != last_pg && decisec != 9) { last_pg = pg; sendDate(1); } + } + } /* USER CODE END WHILE */ diff --git a/qspi/config.txt b/qspi/config.txt index f68d10f..b2884fb 100644 --- a/qspi/config.txt +++ b/qspi/config.txt @@ -107,7 +107,7 @@ mode_vbat = 0 # They use the current position; with no GPS fix they fall back to fake_latitude/ # fake_longitude below (handy indoors), or show "----" if neither is set. -# Sunrise / sunset / solar noon (local time). Pages every 2 s: RISE -> SET -> SOL. +# Sunrise / sunset / solar noon (local time). Pages RISE -> SET -> SOL (see page_ms). MODE_SUN = disabled # Sun azimuth & elevation right now, e.g. "AZ142EL38" (degrees; elevation may be negative). @@ -121,9 +121,13 @@ MODE_MOON = disabled # Maidenhead grid locator, e.g. "IO91xl". MODE_GRID = disabled -# Latitude / longitude in decimal degrees. Pages every 2 s: LAT -> LON. +# Latitude / longitude in decimal degrees. Pages LAT -> LON (see page_ms). MODE_LATLON = disabled +# Dwell per sub-screen for the paged modes (MODE_SUN, MODE_LATLON), in milliseconds. +# Default 5500 (a subjectively-tuned cadence, found by feel); floored at 250. +page_ms = 5500 + # Fixed position for the astro modes when there is no GPS fix (decimal degrees, N+/E+). # Note: setting these also pins the clock's position (GPS position updates are ignored). #fake_latitude = 51.48