You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
atmega-geiger/src/main.c

274 lines
6.1 KiB

#include "twi.h"
#include "ssd1306.h"
#include "sevenseg.h"
#include "adc.h"
#include "iopins.h"
#include <avr/interrupt.h>
#include <util/atomic.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
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);
}