diff --git a/src/global_state.h b/src/global_state.h new file mode 100644 index 0000000..cb3efa1 --- /dev/null +++ b/src/global_state.h @@ -0,0 +1,15 @@ +/** + * Declarations of global variables + */ + +#ifndef CPROJ_GLOBAL_VARIABLES_H +#define CPROJ_GLOBAL_VARIABLES_H + +#include +#include + +extern volatile bool req_update_display; +extern volatile bool is_weak_battery; +extern volatile uint16_t timestamp_100ms; + +#endif //CPROJ_GLOBAL_VARIABLES_H diff --git a/src/high_voltage.c b/src/high_voltage.c new file mode 100644 index 0000000..d512dc9 --- /dev/null +++ b/src/high_voltage.c @@ -0,0 +1,140 @@ +/** + * High voltage source control logic + */ + +#include "high_voltage.h" +#include "iopins.h" +#include "global_state.h" +#include +#include +#include + +static volatile bool is_pwm_on = false; + +// 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 + + +/// when capacitor was last fully charged +static volatile uint16_t ts_cap_charged = 0; + +static void pwm_on() +{ + TCCR0A |= _BV(COM0B1); + is_pwm_on = true; +} + +static void pwm_off() +{ + TCCR0A &= ~_BV(COM0B1); + is_pwm_on = false; +} + + +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_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_adc() +{ + 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 init_high_voltage() +{ + init_pwm_out(); + init_adc(); +} + +void hv_disable() +{ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // if ADC is disabled, the IRQ never fires and it won't be turned on + cbi(ADCSRA, ADEN); + // Turn it off + pwm_off(); + } +} diff --git a/src/high_voltage.h b/src/high_voltage.h new file mode 100644 index 0000000..f9fe286 --- /dev/null +++ b/src/high_voltage.h @@ -0,0 +1,14 @@ +/** + * High voltage + */ + +#ifndef CPROJ_HIGH_VOLTAGE_H +#define CPROJ_HIGH_VOLTAGE_H + +/// Init HV source. Requires interrupts enabled! +void init_high_voltage(); + +/// Disable the HV source +void hv_disable(); + +#endif //CPROJ_HIGH_VOLTAGE_H diff --git a/src/main.c b/src/main.c index 5f97768..7c627de 100644 --- a/src/main.c +++ b/src/main.c @@ -1,229 +1,39 @@ -#include "twi.h" -#include "ssd1306.h" #include "sevenseg.h" -#include "adc.h" -#include "iopins.h" #include #include +#include +#include +#include "time_base.h" +#include "high_voltage.h" +#include "radiation.h" +#include "user_interface.h" -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 +/* globals */ +volatile bool req_update_display = false; +volatile bool is_weak_battery = false; -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 shutdown_due_to_weak_battery(); -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) +void __attribute__((noreturn)) main() { - // 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(); - } -} + init_user_interface(); - -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(); + init_timebase(); + init_radiation(); // --- let's go --- sei(); - vsen_adc_init(); + // this needs interrupts enabled + init_high_voltage(); for (;;) { _delay_ms(100); // check if redraw is needed bool update_display = false; - ATOMIC_BLOCK(ATOMIC_FORCEON) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { update_display = req_update_display; req_update_display = false; } @@ -238,37 +48,18 @@ void __attribute__((noreturn)) main() { } } -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(); + hv_disable(); + show_empty_battery_icon(); // try to preserve power; but the display will still drain the battery. // we should probably shut it off too. - for(;;) { + 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); -} diff --git a/src/radiation.c b/src/radiation.c new file mode 100644 index 0000000..356ac1d --- /dev/null +++ b/src/radiation.c @@ -0,0 +1,52 @@ +/** + * Radiation counting + */ + +#include "radiation.h" +#include "time_base.h" +#include "global_state.h" +#include "iopins.h" + +#include +#include +#include +#include + +#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; + +// geiger tube pin change interrupt +ISR(INT0_vect) +{ + if (measurements[meas_bin] < MEAS_MAX_COUNT) { + measurements[meas_bin]++; + } + req_update_display = true; +} + +void second_callback_irq() +{ + meas_bin++; + if (meas_bin >= MEAS_BIN_COUNT) { + meas_bin = 0; + } + measurements[meas_bin] = 0; + req_update_display = true; +} + +void init_radiation() { + // clear the measurement buffer + memset((void*) measurements, 0, sizeof(measurements)); + + as_input(D2); + + // using INT0 - arduino pin D2 + EICRA = _BV(ISC01) | _BV(ISC00); // rising edge + EIMSK = _BV(INT0); +} diff --git a/src/radiation.h b/src/radiation.h new file mode 100644 index 0000000..3425993 --- /dev/null +++ b/src/radiation.h @@ -0,0 +1,10 @@ +/** + * Radiation counting + */ + +#ifndef CPROJ_RADIATION_H +#define CPROJ_RADIATION_H + +void init_radiation(); + +#endif //CPROJ_RADIATION_H diff --git a/src/sevenseg.h b/src/sevenseg.h index a611931..39043f0 100644 --- a/src/sevenseg.h +++ b/src/sevenseg.h @@ -6,6 +6,7 @@ #define CPROJ_SEVENSEG_H #include +#include struct SevenSeg { uint8_t x0; diff --git a/src/time_base.c b/src/time_base.c new file mode 100644 index 0000000..1c35387 --- /dev/null +++ b/src/time_base.c @@ -0,0 +1,33 @@ +/** + * Time base + */ + +#include "time_base.h" + +#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; + +// 100ms counter +ISR(TIMER1_COMPA_vect) +{ + timestamp_100ms++; + + subsec_counter++; + if (subsec_counter == 10) { + subsec_counter = 0; + second_callback_irq(); + } +} + +void init_timebase() +{ + // CTC & presc=256 + TCCR1B = (1 << WGM12) | (1 << CS12); + TIMSK1 = (1 << OCIE1A); // Enable CTC interrupt + OCR1A = 6250; // 100ms +} diff --git a/src/time_base.h b/src/time_base.h new file mode 100644 index 0000000..e500d7c --- /dev/null +++ b/src/time_base.h @@ -0,0 +1,12 @@ +/** + * TODO file description + */ + +#ifndef CPROJ_TIME_BASE_H +#define CPROJ_TIME_BASE_H + + +void second_callback_irq(); +void init_timebase(); + +#endif //CPROJ_TIME_BASE_H diff --git a/src/user_interface.c b/src/user_interface.c new file mode 100644 index 0000000..4a1f114 --- /dev/null +++ b/src/user_interface.c @@ -0,0 +1,48 @@ +/** + * Dispay + */ + +#include "user_interface.h" +#include "twi.h" +#include "ssd1306.h" +#include "sevenseg.h" +#include "global_state.h" + +static struct SevenSeg sseg = { + .x0 = 0, + .y0 = 0, + .charwidth = 17, + .thick = 3, + //.charwidth = 16, + //.thick = 1, + .spacing = 4, +}; + +void init_user_interface() +{ + TWI_Init(); + ssd1306_128x32_i2c_init(); + ssd1306_clearScreen(); + + // can't show anything useful at least until the first tick arrives, but more than 1 tick is needed + show_loading_icon(); +} + + +void show_current_radiation() +{ + // TODO + sseg_number(&sseg, timestamp_100ms, 5, 0); +} + +void show_empty_battery_icon() +{ + // TODO + sseg_number(&sseg, 9999, 5, 0); +} + +void show_loading_icon() +{ + // TODO + sseg_number(&sseg, 0, 5, 0); +} diff --git a/src/user_interface.h b/src/user_interface.h new file mode 100644 index 0000000..91d939a --- /dev/null +++ b/src/user_interface.h @@ -0,0 +1,16 @@ +/** + * UI + */ + +#ifndef CPROJ_USER_INTERFACE_H +#define CPROJ_USER_INTERFACE_H + +void init_user_interface(); + +void show_current_radiation(); + +void show_empty_battery_icon(); + +void show_loading_icon(); + +#endif //CPROJ_USER_INTERFACE_H