diff --git a/Makefile b/Makefile index 8e29944..fd8255a 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ CFLAGS = -std=gnu99 -mmcu=$(MCU) -DF_CPU=$(F_CPU)UL CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums CFLAGS += -Wall -Wno-main -Wno-strict-prototypes -Wno-comment CFLAGS += -g2 -Wextra -Wfatal-errors -Wno-unused-but-set-variable -CFLAGS += -ffunction-sections -fdata-sections -Os -Wno-array-bounds +CFLAGS += -ffunction-sections -fdata-sections -O2 -Wno-array-bounds LFLAGS = -Wl,--gc-sections -Wl,--relax #-Llib/libssd1306/bld/ -l:libssd1306.a diff --git a/src/global_state.h b/src/global_state.h index cb3efa1..d88e57d 100644 --- a/src/global_state.h +++ b/src/global_state.h @@ -9,7 +9,12 @@ #include extern volatile bool req_update_display; -extern volatile bool is_weak_battery; +extern volatile bool disp_show_tick_mark; +extern volatile bool status_weak_battery; + +/// timestamp counted in units of 100ms extern volatile uint16_t timestamp_100ms; +/// sub-second counter, counts 0-9 then overflows +extern volatile uint16_t subsec_counter; #endif //CPROJ_GLOBAL_VARIABLES_H diff --git a/src/graphic_loading.c b/src/graphic_loading.c index cf80812..232a159 100644 --- a/src/graphic_loading.c +++ b/src/graphic_loading.c @@ -16,50 +16,41 @@ void show_loading_screen(uint8_t progress_percent, bool clear) // bar in a box -#define hei 20 +#define hei 16 #define wid DISPLAY_W -#define thic 2 -#define inpad 3 #define ofsx ((DISPLAY_W - wid)/2) -#define ofsy ((DISPLAY_H - hei)/2) +#define ofsy 8 - const uint8_t rects[4][4] = { - // top - { - 0, 0, - wid - 1, thic - 1 - }, - // left - { - 0, thic, - thic - 1, hei - thic - 1 - }, - // right - { - wid - thic, thic, - wid - 1, hei - thic - 1 - }, - // bot - { - 0, hei - thic, - wid - 1, hei - 1 - }, - }; + ssd1306_setColor(0xFFFF); + + lcdint_t xx = (uint8_t)(((uint16_t)wid * (uint16_t)progress_percent)/(uint16_t)100); + + ssd1306_fillRect( + ofsx, + ofsy, + ofsx + xx, + ofsy + hei + ); + + ssd1306_fillRect( + ofsx + xx + 1, + ofsy, + ofsx + wid - 2, + ofsy + ); + + ssd1306_fillRect( + ofsx + xx + 1, + ofsy + hei, + ofsx + wid - 2, + ofsy + hei + ); - for (int i = 0; i < 4; i++) { - ssd1306_fillRect( - ofsx + rects[i][0], - ofsy + rects[i][1], - ofsx + rects[i][2], - ofsy + rects[i][3] - ); - } -// ssd1306_fillRect( - ofsx + thic + inpad, - ofsy + thic + inpad, - ofsx + (uint8_t)(((uint16_t)wid * (uint16_t)progress_percent)/(uint16_t)100) - thic - inpad - 1, - ofsy + hei - thic - inpad - 1 + ofsx + wid - 1, + ofsy, + ofsx + wid - 1, + ofsy + hei ); } diff --git a/src/high_voltage.c b/src/high_voltage.c index 5da53ef..0325ab4 100644 --- a/src/high_voltage.c +++ b/src/high_voltage.c @@ -72,7 +72,7 @@ ISR(ADC_vect) // If fail to reach target voltage in reasonable time, // show weak battery icon and stop trying. if ((timestamp_100ms - ts_cap_charged) > 10 && analog < ADCVAL_MINIMAL) { - is_weak_battery = true; + status_weak_battery = true; } } } else if (analog < ADCVAL_TURN_ON) { diff --git a/src/main.c b/src/main.c index 82501c1..f2ae85e 100644 --- a/src/main.c +++ b/src/main.c @@ -7,10 +7,12 @@ #include "high_voltage.h" #include "radiation.h" #include "user_interface.h" +#include "ssd1306.h" /* globals */ volatile bool req_update_display = false; -volatile bool is_weak_battery = false; +volatile bool status_weak_battery = false; +volatile uint8_t disp_show_tick_mark = 0; static void shutdown_due_to_weak_battery(); @@ -31,20 +33,33 @@ void __attribute__((noreturn)) main() init_high_voltage(); for (;;) { - _delay_ms(100); + // one tick makes the marker lit for 10 ms (more in practice, plus display ghosting latency) + for (uint8_t i = 0; i < 10; i++) { + _delay_ms(10); + if (disp_show_tick_mark == 1) { + ssd1306_setColor(0xFFFF); + ssd1306_fillRect(124, 0, 127, 3); + disp_show_tick_mark = 2; + } else if (disp_show_tick_mark == 2) { + disp_show_tick_mark = 0; + ssd1306_setColor(0x0000); + ssd1306_fillRect(124, 0, 127, 3); + ssd1306_setColor(0xFFFF); + } + } // check if redraw is needed - bool update_display = false; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - update_display = req_update_display; - req_update_display = false; - } +// bool update_display = true; +// ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { +// update_display = req_update_display; +// req_update_display = false; +// } - if (update_display) { - show_current_radiation(); - } +// if (update_display) { + show_current_radiation(); +// } - if (is_weak_battery) { + if (status_weak_battery) { shutdown_due_to_weak_battery(); } } diff --git a/src/radiation.c b/src/radiation.c index bcc6318..092a976 100644 --- a/src/radiation.c +++ b/src/radiation.c @@ -14,15 +14,15 @@ #include #define MEAS_BIN_COUNT 120 -typedef uint8_t ticks_t; +typedef uint16_t ticks_t; typedef uint8_t binnum_t; -#define TICKS_MAX 255 +#define TICKS_MAX 0xFFFF #define NEEDED_TICKS_FOR_VALUE_DISPLAY 5 -#define TICKS_NEEDED_FOR_AUTORANGE 15 +#define TICKS_NEEDED_FOR_AUTORANGE 10 -uint16_t get_tick_count_in_bins(binnum_t bincount); +uint32_t get_tick_count_in_bins(binnum_t bincount); /// measurements in the last X seconds static volatile ticks_t measurements[MEAS_BIN_COUNT]; @@ -40,6 +40,7 @@ ISR(INT0_vect) measurements[meas_bin]++; } req_update_display = true; + disp_show_tick_mark = 1; } void second_callback_irq() @@ -82,9 +83,10 @@ void second_callback_irq() } } -void init_radiation() { +void init_radiation() +{ // clear the measurement buffer - memset((void*) measurements, 0, sizeof(measurements)); + memset((void *) measurements, 0, sizeof(measurements)); as_input(D2); @@ -93,34 +95,30 @@ void init_radiation() { EIMSK = _BV(INT0); } -uint8_t rad_get_progress() { - ticks_t ticks = get_tick_count_in_bins(num_live_bins); +uint8_t rad_get_progress() +{ + uint16_t ticks = get_tick_count_in_bins(num_live_bins); if (ticks >= NEEDED_TICKS_FOR_VALUE_DISPLAY) { return 100; } - return (((uint16_t)ticks * 100) / (uint16_t)NEEDED_TICKS_FOR_VALUE_DISPLAY); + return (((uint16_t) ticks * 100) / (uint16_t) NEEDED_TICKS_FOR_VALUE_DISPLAY); } -uint16_t get_tick_count_in_bins(binnum_t bincount) { +uint32_t get_tick_count_in_bins(binnum_t bincount) +{ if (bincount == 0) { return 0; } - uint16_t all_ticks = 0; + uint32_t all_ticks = 0; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - // going backwards binnum_t bin = meas_bin; for (;;) { - ticks_t in_bin = measurements[bin]; - if ((0xFFFF - all_ticks) < in_bin) { - all_ticks = 0xFFFF; - } else { - all_ticks += in_bin; - } + all_ticks += (uint32_t) measurements[bin]; if (bin == meas_oldest_bin) { break; @@ -144,37 +142,56 @@ uint16_t get_tick_count_in_bins(binnum_t bincount) { return all_ticks; } -uint16_t rad_get_cpm() { - // bins take 1 second +void rad_get_reading(struct rad_results *out) +{ + if (!out) { return; } - float count = get_tick_count_in_bins(num_observed_bins); - float cpm = (count / (float)num_observed_bins) * 60.0f; - return (uint16_t) roundf(cpm * 10.0f); -} + // bins take 1 second + const float count = (float) get_tick_count_in_bins(num_observed_bins); + const float cpm = (count / (float) num_observed_bins) * 60.0f; -uint16_t rad_get_usvh(uint8_t *out_decimals) { - float count = get_tick_count_in_bins(num_observed_bins); - float cpm = (count / (float)num_observed_bins) * 60.0f; + out->cpm_x10 = (uint32_t) roundf(cpm * 10.0f); // https://sites.google.com/site/diygeigercounter/technical/gm-tubes-supported - float usvh = (cpm / 153.8f); - - if (usvh < 99.0f) { - *out_decimals = 3; - return (uint16_t) roundf(usvh * 1000.0f); + const float usvh = (cpm / 153.8f); + + if (usvh < 2.0f) { + out->danger_level = RAD_LEVEL_0_SAFE; + } else if (usvh < 10.0f) { + out->danger_level = RAD_LEVEL_1_ELEVATED; + } else if (usvh < 20.0f) { + out->danger_level = RAD_LEVEL_2_DANGER; + } else if (usvh < 1000.0f) { + out->danger_level = RAD_LEVEL_3_HIGH_DANGER; + } else { + out->danger_level = RAD_LEVEL_4_SEVERE; } - if (usvh < 999.0f) { - *out_decimals = 2; - return (uint16_t) roundf(usvh * 100.0f); - } +// // for testing only +// if (usvh < 1.0f) { +// out->danger_level = RAD_LEVEL_0_SAFE; +// } else if (usvh < 3.0f) { +// out->danger_level = RAD_LEVEL_1_ELEVATED; +// } else if (usvh < 6.0f) { +// out->danger_level = RAD_LEVEL_2_DANGER; +// } else if (usvh < 9.0f) { +// out->danger_level = RAD_LEVEL_3_HIGH_DANGER; +// } else { +// out->danger_level = RAD_LEVEL_4_SEVERE; +// } - if (usvh < 9999.0f) { - *out_decimals = 1; - return (uint16_t) roundf(usvh * 10.0f); + if (usvh < 99.0f) { + out->usvh_decimals = 3; + out->usvh_num = (uint32_t) roundf(usvh * 1000.0f); + } else if (usvh < 999.0f) { + out->usvh_decimals = 2; + out->usvh_num = (uint32_t) roundf(usvh * 100.0f); + } else if (usvh < 9999.0f) { + out->usvh_decimals = 1; + out->usvh_num = (uint32_t) roundf(usvh * 10.0f); + } else { + out->usvh_decimals = 0; + out->usvh_num = (uint32_t) roundf(usvh); } - - *out_decimals = 0; - return (uint16_t) roundf(usvh); } diff --git a/src/radiation.h b/src/radiation.h index 9675211..1c301cd 100644 --- a/src/radiation.h +++ b/src/radiation.h @@ -8,14 +8,41 @@ #include #include +/// Init the radiation measuring subsystem void init_radiation(); -/// get progress 0-100, 100 = normal radiation resumed +/// Get progress 0-100, 100 = normal radiation reading resumed. +/// We need some number of ticks after boot so the averaging results +/// are somewhat accurate. uint8_t rad_get_progress(); +enum rad_danger { + // 0-2 uSv + RAD_LEVEL_0_SAFE = 0, + // 2-15 uSv + RAD_LEVEL_1_ELEVATED, + // 10-20 uSv + RAD_LEVEL_2_DANGER, + // 20-1000 uSv + RAD_LEVEL_3_HIGH_DANGER, + // 1000+ uSv ("evacuate immediately") + RAD_LEVEL_4_SEVERE, -uint16_t rad_get_cpm(); -/// Get current radiation dose rate u uSv/h, x10 (one decimal place) -uint16_t rad_get_usvh(uint8_t *out_decimals); + RAD_LEVEL_MAX, +}; + +struct rad_results { + /// CPM (usvh before conversion), x10 (one decimal place) + uint32_t cpm_x10; + /// Current radiation dose rate uSv/h, adjusting scaling to fit in 5 digits + uint32_t usvh_num; + /// The position of the decimal point (places from the right) + uint8_t usvh_decimals; + /// Danger level enum + enum rad_danger danger_level; +}; + +void rad_get_reading(struct rad_results *out); #endif //CPROJ_RADIATION_H + diff --git a/src/sevenseg.c b/src/sevenseg.c index 87fdf53..de5279b 100644 --- a/src/sevenseg.c +++ b/src/sevenseg.c @@ -112,7 +112,7 @@ void sseg_period(struct SevenSeg *disp) { disp->x += disp->thick + disp->spacing; } -void sseg_number(struct SevenSeg *disp, uint16_t num, uint8_t places, uint8_t decimals) { +void sseg_number(struct SevenSeg *disp, uint32_t num, uint8_t places, uint8_t decimals) { uint8_t digits[5] = {}; uint8_t pos = 4; while (num > 0) { diff --git a/src/sevenseg.h b/src/sevenseg.h index 39043f0..c5cbb69 100644 --- a/src/sevenseg.h +++ b/src/sevenseg.h @@ -25,7 +25,7 @@ void sseg_blank(struct SevenSeg *disp); void sseg_period(struct SevenSeg *disp); -void sseg_number(struct SevenSeg *disp, uint16_t num, uint8_t places, uint8_t decimals); +void sseg_number(struct SevenSeg *disp, uint32_t num, uint8_t places, uint8_t decimals); #endif //CPROJ_SEVENSEG_H diff --git a/src/time_base.c b/src/time_base.c index 7a4fb73..d457428 100644 --- a/src/time_base.c +++ b/src/time_base.c @@ -9,17 +9,14 @@ #include #include -/// timestamp counted in units of 100ms volatile uint16_t timestamp_100ms = 0; -/// sub-second counter, counts 0-9 then overflows -static volatile uint16_t subsec_counter = 0; +volatile uint16_t subsec_counter = 0; // 100ms counter ISR(TIMER1_COMPA_vect) { timestamp_100ms++; - req_update_display = true; subsec_counter++; diff --git a/src/user_interface.c b/src/user_interface.c index 483a520..bdc672c 100644 --- a/src/user_interface.c +++ b/src/user_interface.c @@ -16,8 +16,6 @@ static struct SevenSeg sseg = { .y0 = 0, .charwidth = 17, .thick = 3, - //.charwidth = 16, - //.thick = 1, .spacing = 4, }; @@ -33,14 +31,27 @@ void init_user_interface() ssd1306_clearScreen(); } -void turn_off_display() { +void turn_off_display() +{ ssd1306_displayOff(); } -static volatile uint16_t last_value = 0; -static volatile uint16_t last_value_d = 0; void show_current_radiation() { + static struct rad_results last_results = {}; + static struct rad_results results; + static uint8_t pending_dark_frames = 0; + static bool is_invert = false; + static bool was_dark = false; + + // 12 step counter is better suited for display flashing effect + static uint8_t tick_counter_12 = 0; + + tick_counter_12 += 1; + if (tick_counter_12 == 12) { + tick_counter_12 = 0; + } + // this is called from main, so we can sleep if (!ended_loading) { @@ -55,22 +66,81 @@ void show_current_radiation() clear_screen(); // start showing values } else { - if (progress < 10) progress = 10; + if (progress < 10) { progress = 10; } show_loading_screen(progress, false); return; } } -// uint16_t cpm = rad_get_cpm(); -// if (cpm != last_value) { -// last_value = cpm; -// sseg_number(&sseg, cpm, 5, 1); -// } - uint8_t decimals = 0; - uint16_t usvh = rad_get_usvh(&decimals); - if (usvh != last_value || decimals != last_value_d) { - last_value = usvh; - last_value_d = decimals; - sseg_number(&sseg, usvh, 5, decimals); + if (pending_dark_frames > 0) { + pending_dark_frames -= 1; + return; + } + + rad_get_reading(&results); + + // Severe inverts display to make it super obvious that shit's bad + if (results.danger_level >= RAD_LEVEL_4_SEVERE) { + if (!is_invert) { + ssd1306_invertMode(); + is_invert = true; + } + } else { + if (is_invert) { + is_invert = false; + ssd1306_normalMode(); + } + } + + // blinking, higher radiation = faster + if (results.danger_level == RAD_LEVEL_1_ELEVATED) { + if (tick_counter_12 == 0) { + pending_dark_frames = 1; + } + } + + if (results.danger_level == RAD_LEVEL_2_DANGER) { + if (tick_counter_12 == 0 || tick_counter_12 == 6) { + pending_dark_frames = 1; + } + } + + if (results.danger_level >= RAD_LEVEL_3_HIGH_DANGER) { + if (tick_counter_12 == 0 || tick_counter_12 == 4 || tick_counter_12 == 8) { + pending_dark_frames = 1; + } + } + + if (pending_dark_frames) { + clear_screen(); + was_dark = true; + return; + } + + if (was_dark + || results.cpm_x10 != last_results.cpm_x10 + || results.usvh_decimals != last_results.usvh_decimals + || results.danger_level != last_results.danger_level) + { + was_dark = false; + + if (results.usvh_decimals != last_results.usvh_decimals || results.danger_level != last_results.danger_level) { + clear_screen(); + } + + memcpy(&last_results, &results, sizeof(struct rad_results)); + + // This is an optimized function that draws black as well as white lines as rectangles, + // so we don't need to redraw the whole screen + sseg_number(&sseg, results.usvh_num, 5, results.usvh_decimals); + + ssd1306_setColor(0xFFFF); + for (uint8_t lv = 1; lv <= results.danger_level; lv++) { + uint8_t xx = 129 - lv * 3; + + ssd1306_fillRect( + xx, 20, xx, 30 + ); + } } }