filament-counter/code/main.c

979 lines
18 KiB
C

#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <stdint.h>
#include "i2c_master.h"
#define EEPROM_SIZE 512
#define CHAR_SIZE 5
#define OLED_ADDRESS (0x3C << 1)
#define OLED_X_SIZE 128
#define OLED_Y_SIZE 32
// Time(ms) to keep the display on before sleep
#define DISPLAY_DELAY 3000
// Time(ms) to assume long button press
#define LONG_PRESS 500
// Rotary Encoder Parameters
#define ROT_PULSE_COUNT 12
#define ROT_DETENTS 24
#define ROT_WHEEL_RAD 13
//#define ROT_CW_UP
// Input masks
#define BUTTON_V 0x01
#define BUTTON_S 1
#define BUTTON_M (BUTTON_V << BUTTON_S)
#define ENCODER_V 0x03
#define ENCODER_S 3
#define ENCODER_M (ENCODER_V << ENCODER_S)
#define INPUT_MASK (BUTTON_M | ENCODER_M)
// Event queue macros
#define MAX_EVENT_COUNT 64
#define PTR_INC(x) ((x) = events + ((((x) - events) + 1) % MAX_EVENT_COUNT))
enum event_e
{
EVENT_BUTTON_DOWN,
EVENT_BUTTON_UP,
EVENT_ROT_CW,
EVENT_ROT_CCW,
EVENT_NONE
};
enum state_e
{
STATE_COUNTING,
STATE_SETTING
};
uint8_t old_input_state;
uint8_t events[MAX_EVENT_COUNT];
uint8_t event_count = 0;
uint8_t *event_read = events;
uint8_t *event_write = events;
uint32_t ms = 0;
#ifdef ROT_CW_UP
const uint8_t PROGMEM rot_table[16] =
{
EVENT_NONE, EVENT_ROT_CCW, EVENT_ROT_CW, EVENT_NONE,
EVENT_ROT_CW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CCW,
EVENT_ROT_CCW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CW,
EVENT_NONE, EVENT_ROT_CW, EVENT_ROT_CCW, EVENT_NONE
};
#else
const uint8_t PROGMEM rot_table[16] =
{
EVENT_NONE, EVENT_ROT_CW, EVENT_ROT_CCW, EVENT_NONE,
EVENT_ROT_CCW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CW,
EVENT_ROT_CW, EVENT_NONE, EVENT_NONE, EVENT_ROT_CCW,
EVENT_NONE, EVENT_ROT_CCW, EVENT_ROT_CW, EVENT_NONE
};
#endif
const uint8_t PROGMEM unfold_table[16] =
{
0x00,
0x02,
0x08,
0x0A,
0x20,
0x22,
0x28,
0x2A,
0x80,
0x82,
0x88,
0x8A,
0xA0,
0xA2,
0xA8,
0xAA
};
const uint8_t PROGMEM symbols[80] =
{
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
0x42, 0x61, 0x51, 0x49, 0x46, // 2
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
0x27, 0x45, 0x45, 0x45, 0x39, // 5
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
0x03, 0x71, 0x09, 0x05, 0x03, // 7
0x36, 0x49, 0x49, 0x49, 0x36, // 8
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
0x7C, 0x04, 0x78, 0x04, 0x78, // m
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
0x00, 0x60, 0x60, 0x00, 0x00, // .
0x00, 0x00, 0xFF, 0x00, 0x00, // |
0x04, 0x06, 0xFF, 0x06, 0x04, // Up-arrow
0x20, 0x60, 0xFF, 0x60, 0x20 // Down-arrow
};
void simple_init();
void display_send_cmd(uint8_t cmd);
void display_send_data(const uint8_t *data, uint16_t buflen);
void display_init();
void display_enable(uint8_t en);
void get_symbol16(uint8_t index, uint8_t *out);
void print_symbol(uint8_t symbol_idx, uint8_t x, uint8_t y, uint8_t invert);
void print_mm(uint32_t value, uint8_t highlight);
void print_direction(uint8_t is_up, uint8_t invert);
void print_voltage(uint16_t value);
uint16_t adc_measure();
uint16_t get_voltage();
uint16_t find_eeprom_idx();
void read_eeprom_val(uint16_t idx, uint32_t *value, uint8_t *dir);
void write_eeprom_val(uint16_t idx, uint32_t value, uint8_t dir);
void do_sleep();
void update_display(uint32_t value, uint32_t highlight, uint8_t dir, uint8_t dir_highlight);
uint8_t extract_digit(uint32_t value, uint8_t digit_num);
ISR(TIMER1_OVF_vect)
{
cli();
++ms;
sei();
}
ISR(PCINT0_vect)
{
// Check button and encoder inputs
uint8_t input_state = PINB & INPUT_MASK;
uint8_t rot_event = EVENT_NONE;
uint8_t rot_idx;
cli();
// Check if button has changed
if (((input_state ^ old_input_state) & BUTTON_M) &&
(event_count != MAX_EVENT_COUNT))
{
// Button is unpressed
if (input_state & BUTTON_M)
{
*event_write = EVENT_BUTTON_UP;
}
else
{
*event_write = EVENT_BUTTON_DOWN;
}
++event_count;
PTR_INC(event_write);
}
// Check if encoder has changed
if (((input_state ^ old_input_state) & ENCODER_M) &&
(event_count != MAX_EVENT_COUNT))
{
rot_idx = ((old_input_state & ENCODER_M) >> (ENCODER_S - 2)) |
((input_state & ENCODER_M) >> ENCODER_S);
rot_event = pgm_read_byte(&(rot_table[rot_idx]));
if (EVENT_NONE != rot_event)
{
*event_write = rot_event;
++event_count;
PTR_INC(event_write);
}
}
old_input_state = input_state;
sei();
}
int main()
{
// 1 / (4 * ROT_PULSE_COUNT) * (2 * pi * ROT_WHEEL_RAD)
const float rot_coeff = 1.57f * ROT_WHEEL_RAD / ROT_PULSE_COUNT;
uint32_t sleep_when_ms = 0;
uint32_t long_press_when_ms = 0;
uint32_t count_value = 0;
float count_value_fine = 0;
uint32_t eeprom_value = 0;
uint16_t eeprom_idx = 0;
uint8_t move_dir = 0;
int8_t rot_value = 0;
uint8_t dir_highlight = 0;
uint8_t highlight = 0;
uint8_t needs_update = 0;
uint8_t curr_event;
uint8_t state;
// Init Direct Hardware
simple_init();
i2c_init();
display_init();
state = STATE_COUNTING;
eeprom_idx = find_eeprom_idx();
if (EEPROM_SIZE == eeprom_idx)
{
eeprom_idx = 0;
state = STATE_SETTING;
highlight = 6;
needs_update = 1;
}
else
{
read_eeprom_val(eeprom_idx, &eeprom_value, &move_dir);
if (0 == eeprom_value)
{
state = STATE_SETTING;
highlight = 6;
needs_update = 1;
}
else
{
count_value = eeprom_value;
count_value_fine = eeprom_value;
}
}
if (STATE_COUNTING == state)
{
sleep_when_ms = 1;
display_enable(0);
}
sei();
while(1)
{
switch (state)
{
case STATE_COUNTING:
while (event_count > 0)
{
// Consume Event
cli();
curr_event = *event_read;
PTR_INC(event_read);
--event_count;
sei();
// Process Event
switch(curr_event)
{
case EVENT_BUTTON_DOWN:
long_press_when_ms = ms + LONG_PRESS;
sleep_when_ms = ms + DISPLAY_DELAY;
display_enable(1);
needs_update = 1;
break;
case EVENT_BUTTON_UP:
long_press_when_ms = 0;
break;
case EVENT_ROT_CW:
if (0 != move_dir)
{
--rot_value;
}
else
{
++rot_value;
}
break;
case EVENT_ROT_CCW:
if (0 != move_dir)
{
++rot_value;
}
else
{
--rot_value;
}
break;
}
}
if (rot_value / (ROT_PULSE_COUNT * 4 / ROT_DETENTS) != 0)
{
count_value_fine += rot_coeff * rot_value;
rot_value = 0;
if (count_value_fine < 0)
{
// Write the zero to EEPROM
++eeprom_idx;
eeprom_idx %= EEPROM_SIZE;
write_eeprom_val(eeprom_idx, 0, move_dir);
count_value_fine = 0;
display_enable(1);
// Switch State
sleep_when_ms = 0;
long_press_when_ms = 0;
highlight = 6;
needs_update = 1;
state = STATE_SETTING;
}
// Update EEPROM When Meter Value Changes
if ((uint32_t)count_value_fine / 1000 != count_value / 1000)
{
++eeprom_idx;
eeprom_idx %= EEPROM_SIZE;
write_eeprom_val(eeprom_idx, (uint32_t) count_value_fine, move_dir);
}
count_value = (uint32_t) count_value_fine;
if ((0 != sleep_when_ms) && (sleep_when_ms > ms))
{
needs_update = 1;
}
}
if ((0 != long_press_when_ms) && (long_press_when_ms < ms))
{
// Switch State
sleep_when_ms = 0;
long_press_when_ms = 0;
highlight = 6;
needs_update = 1;
state = STATE_SETTING;
}
if ((0 != sleep_when_ms) && (sleep_when_ms < ms))
{
display_enable(0);
do_sleep();
sleep_when_ms = 1;
}
// STATE_COUNTING
break;
case STATE_SETTING:
while (event_count > 0)
{
// Consume Event
cli();
curr_event = *event_read;
PTR_INC(event_read);
--event_count;
sei();
// Process Event
switch(curr_event)
{
case EVENT_BUTTON_DOWN:
long_press_when_ms = ms + LONG_PRESS;
break;
case EVENT_BUTTON_UP:
if (long_press_when_ms > ms)
{
// Short Press
if (0 != dir_highlight)
{
if (0 == move_dir)
{
move_dir = 1;
}
else
{
move_dir = 0;
}
}
if (0 != highlight)
{
uint32_t temp_count = count_value;
uint32_t div = 1;
uint8_t i;
for (i = 1; i < highlight; ++i)
{
div *= 10;
}
temp_count = count_value % (div * 10);
count_value -= temp_count;
temp_count += div;
temp_count %= div * 10;
count_value += temp_count;
count_value_fine = count_value;
}
needs_update = 1;
}
long_press_when_ms = 0;
// EVENT_BUTTON_UP
break;
}
}
if ((0 != long_press_when_ms) && (long_press_when_ms < ms))
{
// Long press
if (0 != highlight)
{
--highlight;
if (0 == highlight)
{
dir_highlight = 1;
}
}
else if (0 != dir_highlight)
{
// Switch state
sleep_when_ms = ms + DISPLAY_DELAY;
dir_highlight = 0;
state = STATE_COUNTING;
// Write starting value to EEPROM
++eeprom_idx;
eeprom_idx %= EEPROM_SIZE;
write_eeprom_val(eeprom_idx, count_value, move_dir);
}
needs_update = 1;
long_press_when_ms = 0;
}
// STATE_SETTING
break;
}
if (needs_update)
{
update_display(count_value, highlight, move_dir, dir_highlight);
needs_update = 0;
}
}
}
void simple_init()
{
#if 0
// Set pull-ups on button and rotary encoder
PORTB = (1 << 1) | (1 << 3) | (1 << 4);
#endif
// Prescaler 4 (1 MHz / 256 / 4 ~ 1000)
TCCR1 = (1 << CS11) | (1 << CS10);
// Enable Interrupt on overflow
TIMSK = (1 << TOIE1);
// Interrupt on button and rotary encoder pins
PCMSK = (1 << PCINT1) | (1 << PCINT3) | (1 << PCINT4);
// Sleep mode - power down
MCUCR = (1 << SM1);
// Enable Interrupt on pin-change
GIMSK = (1 << PCIE);
// Measure Vbg(1.1V) with Vcc reference
ADMUX = (1 << MUX3) | (1 << MUX2);
// ADC prescaler 8
ADCSRA = (1 << ADPS1) | (1 << ADPS0);
// Power reduction - Disable timer 0
PRR = (1 << PRTIM0);
old_input_state = INPUT_MASK;
}
void display_send_cmd(uint8_t cmd)
{
i2c_start(OLED_ADDRESS | I2C_WRITE);
i2c_write(0x00); // Command Indicator
i2c_write(cmd);
i2c_stop();
}
void display_send_data(const uint8_t *data, uint16_t buflen)
{
uint16_t i;
for (i = 0; i < buflen; ++i)
{
if (0 == (i % 0x0F))
{
i2c_start(OLED_ADDRESS | I2C_WRITE);
i2c_write(0x40); // Data Indicator
}
i2c_write(data[i]);
if (0x0F == (i % 0x0F))
{
i2c_stop();
}
}
if (0 != (i % 0x0F))
{
i2c_stop();
}
}
void display_init()
{
uint16_t i;
uint8_t buf[16];
const uint8_t cmd_list[] =
{
0xAE, // Display OFF
0xD5, // Set Clock Divider / Oscillator Frequency
0x80, // Default OSC / No Div
0xA8, // Set Multiplex Ratio
OLED_Y_SIZE - 1,
0xD3, // Set Display Offset
0x00, // 0 Offset
0x40 | 0x00, // Set Start Line To 0
0x8D, // Set Charge Pump
0x14, // Enable Charge Pump When Display Is On
0x20, // Set Memory Mode
0x00, // Horizontal Mode
0xA0 | 0x01, // Set Segment Remap - column 127 is seg 0
0xC8, // Set COM Scan Direction To Decrement
0xDA, // Set COM Pin Configuration
0x02 | 0x00, // Row 0 = COM63, Row 31 = COM32
0x81, // Set Contrast
0xFF, // Contrast Value
0xD9, // Set Precharge Period
0x01 | 0xF0, // Phase 1 - 1 CLK, Phase 2 - 15 CLK
0xDB, // Set VCOM Deselect Level
#if 1
0x30, // 0.83 x Vcc
#else
0x40, // UNKNOWN (0.90 x Vcc Assumed)
#endif
0xA4, // Display RAM Contents
0xA6, // Set Normal Display (1 is white)
0x2E, // Deactivate Scroll
0xAF, // Display ON
// Commands to send to RAM
0x21, // Set Column Address
0x00,
OLED_X_SIZE - 1,
0x22, // Set Page Address
0x00,
(OLED_Y_SIZE / 8) - 1
};
// Send Commands
for (i = 0; i < sizeof(cmd_list); ++i)
{
display_send_cmd(cmd_list[i]);
}
// Fill Buffer With Black
for (i = 0; i < sizeof(buf); ++i)
{
buf[i] = 0;
}
// Black Entire Screen
for (i = 0; i < OLED_X_SIZE * OLED_Y_SIZE / 8 / sizeof(buf); ++i)
{
display_send_data(buf, sizeof(buf));
}
}
void display_enable(uint8_t en)
{
static uint8_t old_state = 1;
if (en != 0)
{
en = 1;
}
if (en != old_state)
{
display_send_cmd(0xAE | (en & 0x01));
}
old_state = en;
}
void get_symbol16(uint8_t index, uint8_t *out)
{
uint8_t i;
uint8_t data_byte;
for (i = 0; i < CHAR_SIZE * 4; ++i)
{
out[i] = 0;
}
if (index > sizeof(symbols) / 5)
{
return;
}
for (i = 0; i < CHAR_SIZE; ++i)
{
data_byte = pgm_read_byte(&(symbols[index * CHAR_SIZE + i]));
out[i * 2 + 1] = pgm_read_byte(&(unfold_table[data_byte & 0x0F]));
out[CHAR_SIZE * 2 + i * 2 + 1] = pgm_read_byte(&(unfold_table[(data_byte >> 4) & 0x0F]));
}
}
void print_symbol(uint8_t symbol_idx, uint8_t x, uint8_t y, uint8_t invert)
{
uint8_t unfolded_symbol[CHAR_SIZE * 4];
uint8_t cmd_list[6] =
{
0x21, // Set Column Address
0x00, // Start Address
0x00, // End Address
0x22, // Set Page Address
0x00, // Start Page 0
0x00 // End Page
};
uint8_t i;
get_symbol16(symbol_idx, unfolded_symbol);
if (invert)
{
for (i = 0; i < sizeof(unfolded_symbol); ++i)
{
unfolded_symbol[i] = ~(unfolded_symbol[i]);
}
}
x = x % (OLED_X_SIZE);
y = y % (OLED_Y_SIZE / (8 * 2));
if (x > (OLED_X_SIZE - CHAR_SIZE * 2))
{
x = OLED_X_SIZE - CHAR_SIZE * 2;
}
cmd_list[1] = x;
cmd_list[2] = x + CHAR_SIZE * 2 - 1;
cmd_list[4] = y * 2;
cmd_list[5] = y * 2 + 1;
for (i = 0; i < sizeof(cmd_list); ++i)
{
display_send_cmd(cmd_list[i]);
}
display_send_data(unfolded_symbol, sizeof(unfolded_symbol));
}
void print_mm(uint32_t value, uint8_t highlight)
{
static uint32_t old_value = 0;
static uint8_t old_highlight = 0;
uint8_t x;
uint8_t y;
uint8_t symbol;
uint8_t invert;
uint8_t is_first;
uint8_t i;
x = 0;
y = 0;
is_first = 1;
// Print 6 digits (only needed ones)
for (i = 6; i > 0; --i)
{
symbol = extract_digit(value, i);
if (highlight == i)
{
invert = 1;
}
else
{
invert = 0;
}
if (is_first && (0 == highlight) && (0 == symbol) && (1 != i))
{
symbol = 0xFF;
}
else
{
is_first = 0;
}
if ((0 == old_value) || (symbol != extract_digit(old_value, i)) || (old_highlight != highlight))
{
print_symbol(symbol, x, y, invert);
}
x += (CHAR_SIZE + 1) * 2;
}
// Print 'mm'
if (0 == old_value)
{
print_symbol(10, x, y, 0);
x += (CHAR_SIZE + 1) * 2;
print_symbol(10, x, y, 0);
}
old_value = value;
old_highlight = highlight;
}
void print_direction(uint8_t is_up, uint8_t invert)
{
uint8_t symbol_index[2];
static uint8_t old_is_up = 0;
static uint8_t old_invert = 1;
if (is_up)
{
symbol_index[0] = 14;
symbol_index[1] = 13;
}
else
{
symbol_index[0] = 13;
symbol_index[1] = 15;
}
if ((old_is_up != is_up) || (old_invert != invert))
{
print_symbol(symbol_index[0], OLED_X_SIZE - CHAR_SIZE * 2, 0, invert);
print_symbol(symbol_index[1], OLED_X_SIZE - CHAR_SIZE * 2, 1, invert);
}
old_is_up = is_up;
old_invert = invert;
}
void print_voltage(uint16_t value)
{
static uint16_t old_value = 0;
uint8_t x;
uint8_t y;
uint8_t symbol;
uint8_t i;
x = 0;
y = 1;
// Print 3 digits
for (i = 3; i > 0; --i)
{
symbol = extract_digit(value, i);
if ((0 == old_value) || (symbol != extract_digit(old_value, i)))
{
print_symbol(symbol, x, y, 0);
}
if (0 == x)
{
x += (CHAR_SIZE + 1) * 2;
}
x += (CHAR_SIZE + 1) * 2;
}
if (0 == old_value)
{
// Print 'V'
print_symbol(11, x, y, 0);
// Print '.'
x = (CHAR_SIZE + 1) * 2;
print_symbol(12, x, y, 0);
}
old_value = value;
}
uint16_t adc_measure()
{
uint16_t result;
// Enable and start ADC
ADCSRA |= (1 << ADEN) | (1 << ADSC);
// Wait for conversion
while (ADCSRA & (1 << ADSC));
// Read ADC value
result = ADC;
// Redo to get a better reading
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC));
result = ADC;
// Disable ADC
ADCSRA &= ~(1 << ADEN);
return result;
}
uint16_t measure_voltage()
{
uint32_t val;
uint16_t result;
val = 110;
val *= 1023;
val /= adc_measure();
result = val;
return result;
}
uint16_t find_eeprom_idx()
{
uint16_t idx = 0;
uint8_t found = 0;
// FF is the erased byte value and is used as prefix
for (idx = 0; idx < EEPROM_SIZE; ++idx)
{
if (0xFF != eeprom_read_byte((uint8_t *) idx))
{
found = 1;
break;
}
}
if (!found)
{
return EEPROM_SIZE;
}
// FF XX ...
if (1 == idx)
{
// Check Last byte for value
// FF XX ... FF ??
if (0xFF != eeprom_read_byte((uint8_t *) (EEPROM_SIZE - 1)))
{
idx = EEPROM_SIZE - 1;
}
}
// XX ...
if (0 == idx)
{
// XX ... FF ?? ??
for (idx = EEPROM_SIZE - 2; idx < EEPROM_SIZE; ++idx)
{
if (0xFF != eeprom_read_byte((uint8_t *) idx))
{
break;
}
}
}
// idx here is the byte that's not the FF prefix so reverse it by 1
idx += EEPROM_SIZE;
--idx;
idx %= EEPROM_SIZE;
return idx;
}
void read_eeprom_val(uint16_t idx, uint32_t *value, uint8_t *dir)
{
uint8_t curr_byte;
uint8_t i;
(*value) = 0;
for (i = 0; i < 4; ++i)
{
curr_byte = eeprom_read_byte((uint8_t *) idx);
(*value) <<= 8;
(*value) |= curr_byte;
++idx;
idx %= EEPROM_SIZE;
}
*dir = ((*value) & 0x00800000) >> 23;
(*value) &= 0x007FFFFF;
}
void write_eeprom_val(uint16_t idx, uint32_t value, uint8_t dir)
{
uint8_t curr_byte;
uint8_t i;
value |= 0xFF000000;
if (0 != dir)
{
value |= 0x00800000;
}
for (i = 0; i < 4; ++i)
{
curr_byte = (value >> (24 - 8 * i)) & 0xFF;
eeprom_update_byte((uint8_t *) idx, curr_byte);
++idx;
idx %= EEPROM_SIZE;
}
}
void do_sleep()
{
// Disable clock to USI, ADC
PRR |= (1 << PRUSI) | (1 << PRADC);
// Enable sleep
MCUCR |= (1 << SE);
// Sleep
sleep_cpu();
// Reset ms counter
cli();
ms = 2;
sei();
// Disable sleep
MCUCR &= ~(1 << SE);
// Enable clock to USI, ADC
PRR &= ~((1 << PRUSI) | (1 << PRADC));
}
void update_display(uint32_t value, uint32_t highlight, uint8_t dir, uint8_t dir_highlight)
{
print_mm(value, highlight);
print_direction(dir, dir_highlight);
print_voltage(measure_voltage());
}
uint8_t extract_digit(uint32_t value, uint8_t digit_num)
{
while (1 != digit_num)
{
value /= 10;
--digit_num;
}
return value % 10;
}