#include #include #include #include #include #include #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.875 //#define ROT_REVERSE // 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_REVERSE 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 += 8; 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 32 (1MHz / 32 / 256 = 8,192 ms) TCCR1 = (1 << CS12) | (1 << CS11); // 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; }