diff --git a/lib/libssd1306/src/nano_engine/README.md b/lib/libssd1306/src/nano_engine/README.md index 2d0d0c1..c682523 100644 --- a/lib/libssd1306/src/nano_engine/README.md +++ b/lib/libssd1306/src/nano_engine/README.md @@ -30,7 +30,7 @@ There are 2 issues with tiny controllers: * they support low frequencies The first problem is solved in NanoEngine by using double-buffer to redraw only part of display content at once. By default NanoEngine uses 8x8 small buffer (64 bytes) and 24 bytes to store information on areas, which need to be refreshed. -The second problem is solved almost the same way: refresh only those part of display content, which were changed since last frame update. For example, ssd1331 oled display work on SPI at 8MHz frequency, that means in ideal conditions the screen content can be refreshed 162 times per second (`8000000/(96*64*8)`). But with the data, you need to send also commands to the display and to do some other stuff. And real tests with Atmega328p show that `ssd1306_clearScreen()` can run only at 58 FPS, coping data from buffer to OLED memory runs slower. +The meas_bin problem is solved almost the same way: refresh only those part of display content, which were changed since last frame update. For example, ssd1331 oled display work on SPI at 8MHz frequency, that means in ideal conditions the screen content can be refreshed 162 times per meas_bin (`8000000/(96*64*8)`). But with the data, you need to send also commands to the display and to do some other stuff. And real tests with Atmega328p show that `ssd1306_clearScreen()` can run only at 58 FPS, coping data from buffer to OLED memory runs slower. There is no such issue for Arduboy, since it uses monochrome OLED ssd1306 with only 1KiB of RAM buffer, and theoretical fps can be up to 976. For color display and small controllers the main solution is to refresh only part of display content. Arkanoid8 can give easily 60 fps with NanoEngine8 diff --git a/src/main.c b/src/main.c index b3f181c..5f97768 100644 --- a/src/main.c +++ b/src/main.c @@ -4,24 +4,142 @@ #include "adc.h" #include "iopins.h" #include +#include -const uint16_t adc_target16_acceptable = 900; -const uint16_t adc_target16 = 972; -//const uint8_t adc_target8 = 243; +static void show_loading_icon(); +static void show_empty_battery_icon(); +static void shutdown_due_to_weak_battery(); +static void show_current_radiation(); -static bool g_pwm_on = false; +static void second_callback_irq(); -static volatile uint16_t tick_counter = 0; -static volatile bool tick_counter_changed = false; +// tube starting voltage is 350V +#define TURN_ON_VOLTAGE 355.0 +#define TURN_OFF_VOLTAGE 380.0 +#define MINIMAL_VOLTAGE 350.0 -// Tick +// sensing via resistive divider +#define VREF 1.1 + +// in kiloohms +#define R_UPPER 10000.0 +#define R_LOWER 27.0 + +#define ADC_MAXVAL 1023 + +#define RDIV_FACTOR (R_LOWER / (R_UPPER + R_LOWER)) + +/// ADC input voltage that's too low - PWM must start +#define VSEN_TURN_ON (TURN_ON_VOLTAGE * RDIV_FACTOR) +/// ADC input voltage that's too high - PWM must stop +#define VSEN_TURN_OFF (TURN_OFF_VOLTAGE * RDIV_FACTOR) +/// ADC input voltage that's lowest possible for the geiger tube to function +#define VSEN_MINIMAL (MINIMAL_VOLTAGE * RDIV_FACTOR) + +// the above, but converted to ADC word +#define ADCVAL_TURN_ON ((uint16_t) (ADC_MAXVAL * (VSEN_TURN_ON / VREF))) +#define ADCVAL_TURN_OFF ((uint16_t) (ADC_MAXVAL * (VSEN_TURN_OFF / VREF))) +#define ADCVAL_MINIMAL ((uint16_t) (ADC_MAXVAL * (VSEN_MINIMAL / VREF))) + +/// Channel used to measure VSEN +#define VSEN_ADC_CHANNEL 0 + +static struct SevenSeg sseg = { + .x0 = 0, + .y0 = 0, + .charwidth = 17, + .thick = 3, + //.charwidth = 16, + //.thick = 1, + .spacing = 4, +}; + +static volatile bool is_pwm_on = false; +static volatile bool req_update_display = false; +static volatile bool is_weak_battery = false; + +#define MEAS_BIN_COUNT 120 +typedef uint8_t meas_bin_t; +#define MEAS_MAX_COUNT 255 + +/// measurements in the last X seconds +static volatile meas_bin_t measurements[MEAS_BIN_COUNT]; +/// Currently active measurement bin, increments once per second +static volatile uint8_t meas_bin = 0; + +/// timestamp counted in units of 100ms +static volatile uint16_t timestamp_100ms = 0; +/// sub-second counter, counts 0-9 then overflows +static volatile uint16_t subsec_counter = 0; + +// when capacitor was last fully charged +static volatile uint16_t ts_cap_charged = 0; + +static inline void pwm_on() { + TCCR0A |= _BV(COM0B1); + is_pwm_on = true; +} + +static void inline pwm_off() { + TCCR0A &= ~_BV(COM0B1); + is_pwm_on = false; +} + +// geiger tube pin change interrupt ISR(INT0_vect) { - tick_counter++; - tick_counter_changed = true; + if (measurements[meas_bin] < MEAS_MAX_COUNT) { + measurements[meas_bin]++; + } + req_update_display = true; +} + +// 1 second counter +ISR(TIMER1_COMPA_vect) +{ + timestamp_100ms++; + + subsec_counter++; + if (subsec_counter == 10) { + subsec_counter = 0; + second_callback_irq(); + } +} + +static void second_callback_irq() +{ + meas_bin++; + if (meas_bin >= MEAS_BIN_COUNT) { + meas_bin = 0; + } + measurements[meas_bin] = 0; + req_update_display = true; +} + +ISR(ADC_vect) +{ + // Service the boost converter + uint16_t analog = ADCW; + + if (is_pwm_on) { + if (analog >= ADCVAL_TURN_OFF) { + pwm_off(); + ts_cap_charged = timestamp_100ms; + } else { + // 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; + } + } + } + else if (analog < ADCVAL_TURN_ON) { + pwm_on(); + } } -static void init_isr() { + +static void init_pcisr() { as_input(D2); // using INT0 - arduino pin D2 @@ -45,86 +163,112 @@ static void init_pwm_out() { TCCR0B = _BV(CS01) | _BV(CS00) | _BV(WGM02); } -static void pwm_on() { - TCCR0A |= _BV(COM0B1); - g_pwm_on = true; +static void init_second_base() { + // CTC & presc=256 + TCCR1B = (1 << WGM12) | (1 << CS12); + TIMSK1 = (1 << OCIE1A); // Enable CTC interrupt + OCR1A = 6250; // 100ms } -static void pwm_off() { - TCCR0A &= ~_BV(COM0B1); - g_pwm_on = false; +static void vsen_adc_init() { + ADCSRA = + // 128 prescaler -> 125 kHz + _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) + // auto trigger + | _BV(ADATE) + // interrupt enable + | _BV(ADIE); + + ADCSRB = 0; // free-running + DIDR0 = ADC0D; // disable the digital input buffer on the pin + + ADMUX = + // internal ref 1.1V + _BV(REFS0) | _BV(REFS1) + // select channel + | VSEN_ADC_CHANNEL; + + sbi(ADCSRA, ADEN); // Enable ADC + sbi(ADCSRA, ADSC); // Start } -void main() { +void __attribute__((noreturn)) main() { + // clear the measurement buffer + memset((void*) measurements, 0, sizeof(measurements)); + TWI_Init(); - adc_init(); + + + init_second_base(); init_pwm_out(); ssd1306_128x32_i2c_init(); ssd1306_clearScreen(); - struct SevenSeg sseg = { - .x0 = 0, - .y0 = 0, - .charwidth = 17, - .thick = 3, - //.charwidth = 16, - //.thick = 1, - .spacing = 4, - }; - - init_isr(); - sei(); + // TODO use ADC interrupt - // TODO show loading icon - sseg_number(&sseg, 0, 5, 0); + init_pcisr(); - // request ADC meas - adc_async_start_measure_word(0); + // TODO show loading icon (can't show anything useful at least until the first tick arrives) + show_loading_icon(); -// uint16_t cnt = 0; - uint16_t analog; - uint16_t count_boost_fail = 0; - for (;;) { - if (adc_async_ready()) { - analog = adc_async_get_result_word(); - adc_async_start_measure_word(0); - - bool good_voltage = analog >= adc_target16; - - if (g_pwm_on) { - if (good_voltage) { - pwm_off(); - count_boost_fail = 0; - } else { - count_boost_fail++; - } - } - else if (!good_voltage) { - pwm_on(); - } + // --- let's go --- + sei(); - // If fail to reach target voltage in reasonable time, - // show weak battery icon and stop trying. - if (count_boost_fail > 50000 && analog < adc_target16_acceptable) { - // TODO weak battery icon - sseg_number(&sseg, 9999, 5, 0); - pwm_off(); + vsen_adc_init(); - for(;;) {} - } + for (;;) { + _delay_ms(100); - // TODO synchronization? - if (tick_counter_changed) { - tick_counter_changed = false; - sseg_number(&sseg, tick_counter, 5, 0); - } + // check if redraw is needed + bool update_display = false; + ATOMIC_BLOCK(ATOMIC_FORCEON) { + update_display = req_update_display; + req_update_display = false; + } -// if (++cnt > 10000) { -// sseg_number(&sseg, analog, 5, 0); -// cnt = 0; -// } + if (update_display) { + show_current_radiation(); } + + if (is_weak_battery) { + shutdown_due_to_weak_battery(); + } + } +} + +void show_current_radiation() +{ + // TODO + sseg_number(&sseg, timestamp_100ms, 5, 0); +} + +void __attribute__((noreturn)) shutdown_due_to_weak_battery() +{ + pwm_off(); + + show_empty_battery_icon(); + + cli(); + + // try to preserve power; but the display will still drain the battery. + // we should probably shut it off too. + + for(;;) { + sleep_enable(); + sleep_cpu(); } } + +void show_empty_battery_icon() +{ + // TODO + sseg_number(&sseg, 9999, 5, 0); +} + +void show_loading_icon() +{ + // TODO + sseg_number(&sseg, 0, 5, 0); +}