#include "twi.h" #include "ssd1306.h" #include "sevenseg.h" #include "adc.h" #include "iopins.h" #include #include static void show_loading_icon(); static void show_empty_battery_icon(); static void shutdown_due_to_weak_battery(); static void show_current_radiation(); static void second_callback_irq(); // tube starting voltage is 350V #define TURN_ON_VOLTAGE 355.0 #define TURN_OFF_VOLTAGE 380.0 #define MINIMAL_VOLTAGE 350.0 // 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) { 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_pcisr() { as_input(D2); // using INT0 - arduino pin D2 EICRA = _BV(ISC01) | _BV(ISC00); // rising edge EIMSK = _BV(INT0); } static void init_pwm_out() { // Output is OC0A as_output(D5); // initialize the timer // Fast PWM mode, Output to OC0A // clock is 16MHz, presc /64, counting to 80 -> freq 3125Hz // Duty cycle = appx. 60% OCR0A = 80; OCR0B = 46; TCCR0A = _BV(WGM00) | _BV(WGM01); TCCR0B = _BV(CS01) | _BV(CS00) | _BV(WGM02); } static void init_second_base() { // CTC & presc=256 TCCR1B = (1 << WGM12) | (1 << CS12); TIMSK1 = (1 << OCIE1A); // Enable CTC interrupt OCR1A = 6250; // 100ms } 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 __attribute__((noreturn)) main() { // clear the measurement buffer memset((void*) measurements, 0, sizeof(measurements)); TWI_Init(); init_second_base(); init_pwm_out(); ssd1306_128x32_i2c_init(); ssd1306_clearScreen(); // TODO use ADC interrupt init_pcisr(); // TODO show loading icon (can't show anything useful at least until the first tick arrives) show_loading_icon(); // --- let's go --- sei(); vsen_adc_init(); for (;;) { _delay_ms(100); // check if redraw is needed bool update_display = false; ATOMIC_BLOCK(ATOMIC_FORCEON) { update_display = req_update_display; req_update_display = false; } 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); }