add systick and adc interrupt

master
Ondřej Hruška 1 year ago
parent 2cc0a155e8
commit 8d1380bad6
  1. 2
      lib/libssd1306/src/nano_engine/README.md
  2. 288
      src/main.c

@ -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
<a name="simple-nanoengine-demo"></a>

@ -4,24 +4,142 @@
#include "adc.h"
#include "iopins.h"
#include <avr/interrupt.h>
#include <util/atomic.h>
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);
}

Loading…
Cancel
Save