From 625c10081e1fc125a5cf9410f057722f7b97fbd8 Mon Sep 17 00:00:00 2001 From: nedko Date: Tue, 25 Oct 2022 15:59:15 +0300 Subject: [PATCH] Added initial code from old counter with changes to event system --- TODO | 18 + code/Makefile | 8 + code/i2c_master.c | 131 ++++++ code/i2c_master.h | 15 + code/main.c | 1084 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1256 insertions(+) create mode 100644 TODO create mode 100644 code/Makefile create mode 100644 code/i2c_master.c create mode 100644 code/i2c_master.h create mode 100644 code/main.c diff --git a/TODO b/TODO new file mode 100644 index 0000000..d5a20fe --- /dev/null +++ b/TODO @@ -0,0 +1,18 @@ +--- main.c --- +fix everything from main() onward with exception of do_sleep() + +--- Menu --- +Attach - Attempt to attach spool +Detach - Attempt to detach spool +Adjust - Adjust Spool Value +Battery - Print Battery Info - Voltage, mAh + +--- UI --- +123456mm AB + _ +XXXXXXXX | | +XXXXXXXX | | +XXXXXXXX |_| + +Battery Should be 2px segments per 5% diff --git a/code/Makefile b/code/Makefile new file mode 100644 index 0000000..aeb936e --- /dev/null +++ b/code/Makefile @@ -0,0 +1,8 @@ +all: + make build + make program +build: + avr-gcc main.c i2c_master.c -o counter -mmcu=attiny84 -Wall -Wextra + +program: + avrdude -p attiny84 -c usbtiny -U flash:w:counter diff --git a/code/i2c_master.c b/code/i2c_master.c new file mode 100644 index 0000000..3744f6f --- /dev/null +++ b/code/i2c_master.c @@ -0,0 +1,131 @@ +#include "i2c_master.h" +#include + +uint8_t i2c_clock(uint8_t type); + +void i2c_init(void) +{ + // Define SCL and SDA as Output + DDRA |= (1 << 4) | (1 << 6); + + // Preload dataregister with "released level" data + USIDR = 0xFF; + + // Use SCL and SDA pins + // Select clock sources + USICR = (1 << USIWM1) | (1 << USICS1) | (1 << USICLK); + + // Clear flags and reset counter + USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | (1 << USIDC); +} + +uint8_t i2c_start(uint8_t address) +{ + // FOR REPEATED START + // Release SCL + PORTA |= (1 << 4); + // Verify that SCL becomes high + while (!( PINA & (1 << 4) )); + + // GENERATE START CONDITION + // Force SDA LOW + PORTA &= ~(1 << 6); + + // Pull SCL LOW + PORTA &= ~(1 << 4); + // Release SDA + PORTA |= (1 << 6); + + return i2c_write(address); +} + +uint8_t i2c_write(uint8_t data) +{ + // Pull SCL LOW + PORTA &= ~(1 << 4); + // Setup data + USIDR = data; + + // Send 8 bits on bus + i2c_clock(8); + + // Enable SDA as input + DDRA &= ~(1 << 6); + + // Receive 1 bit on bus & Check for NACK + if (i2c_clock(1) & (1 << 0)) + { + return 1; + } + return 0; +} + +uint8_t i2c_read(uint8_t nack) +{ + // Enable SDA as input. + DDRA &= ~(1 << 6); + // Read 8 bits + uint8_t result = i2c_clock(8); + + if (nack) + { + // Load NACK + USIDR = 0xFF; + } + else + { + // Load ACK + USIDR = 0x00; + } + + i2c_clock(1); + return result; +} + +void i2c_stop(void) +{ + // Pull SDA low + PORTA &= ~(1 << 6); + // Release SCL + PORTA |= (1 << 4); + // Wait for SCL to go high + while (!( PINA & (1 << 4) )); + + // Release SDA + PORTA |= (1 << 6); +} + +uint8_t i2c_clock(uint8_t count) +{ + uint8_t reg_temp = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | (1 << USIDC); + // Counter counts number of edges + // Overflow signals end of transmission + reg_temp |= 16 - (count * 2); + + USISR = reg_temp; + + // Set clock source & toggle clock prepare + reg_temp = (1 << USIWM1) | (1 << USICS1) | (1 << USICLK) | (1 << USITC); + do + { + // Generate positve SCL edge. + USICR = reg_temp; + + // Wait for SCL to go high + while (!( PINA & (1 << 4))); + + // Generate negative SCL edge + USICR = reg_temp; + } + // Wait for counter overflow (all edges are completed) + while (!( USISR & (1 << USIOIF))); + + // Read data + reg_temp = USIDR; + // Load dataregister with "released level" data + USIDR = 0xFF; + + // Enable SDA as output + DDRA |= (1 << 6); + return reg_temp; +} diff --git a/code/i2c_master.h b/code/i2c_master.h new file mode 100644 index 0000000..42bba16 --- /dev/null +++ b/code/i2c_master.h @@ -0,0 +1,15 @@ +#include + +#ifndef I2C_MASTER_H +#define I2C_MASTER_H + +#define I2C_READ 0x01 +#define I2C_WRITE 0x00 + +void i2c_init(void); +uint8_t i2c_start(uint8_t address); +uint8_t i2c_write(uint8_t data); +uint8_t i2c_read(uint8_t nack); +void i2c_stop(void); + +#endif // I2C_MASTER_H diff --git a/code/main.c b/code/main.c new file mode 100644 index 0000000..fdec727 --- /dev/null +++ b/code/main.c @@ -0,0 +1,1084 @@ +#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 64 + +// 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) + +// Event queue macros +#define MAX_EVENT_COUNT 64 +#define PTR_INC(x) ((x) = events + ((((x) - events) + 1) % MAX_EVENT_COUNT)) + +enum event_e +{ + EVENT_NONE, + EVENT_ROT_CW, + EVENT_ROT_CCW, + EVENT_SEL_UP, + EVENT_SEL_DOWN, + EVENT_BTN_UP, + EVENT_BTN_DOWN +}; + +enum btn_state_e +{ + BTN_NONE = 0, + BTN_PRESS, + BTN_UP5, + BTN_UP14, + BTN_DOWN5, + BTN_DOWN14 +}; + +enum state_e +{ + STATE_COUNTING, + STATE_SETTING +}; + +// These are set on hardware init +uint8_t old_btn_pin_state; +uint8_t old_rot_pin_state; + +uint8_t btn_state_stable = BTN_NONE; +uint8_t btn_state_current = BTN_NONE; + +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 btn_table[36] = +{ + // Old BTN_NONE + EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_SEL_UP, EVENT_NONE, EVENT_SEL_DOWN, + // Old BTN_PRESS + EVENT_BTN_UP, EVENT_NONE, EVENT_BTN_UP, EVENT_SEL_UP, EVENT_BTN_UP, EVENT_SEL_DOWN, + // Old BTN_UP5 + EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, 0xFF, EVENT_NONE, EVENT_SEL_DOWN, + // Old BTN_UP14 + EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_NONE, EVENT_NONE, EVENT_SEL_DOWN, + // Old BTN_DOWN5 + EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_SEL_UP, EVENT_NONE, 0xFF, + // Old BTN_DOWN14 + EVENT_NONE, EVENT_BTN_DOWN, EVENT_NONE, EVENT_SEL_UP, EVENT_NONE, EVENT_NONE +}; + +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 + // A + // B + 0x1F, 0x20, 0x40, 0x20, 0x1F, // V + // h + 0x7C, 0x04, 0x78, 0x04, 0x78, // m + 0x00, 0x60, 0x60, 0x00, 0x00, // . + // < + // > +}; + +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 += 10; + sei(); +} + +// Changes on button +ISR(PCINT0_vect) +{ + // Check button inputs + uint8_t btn_pin_state = PINA & 0x2F; + uint8_t btn_event = EVENT_NONE; + uint8_t btn_state; + uint8_t btn_idx; + + cli(); + + // Check if button has changed + if ((btn_pin_state ^ old_btn_pin_state) && (event_count != MAX_EVENT_COUNT)) + { + // Pins to state + switch (btn_pin_state) + { + // None + case 0x2F: + btn_state = BTN_NONE; + break; + + // Press + case 0x0F: + btn_state = BTN_PRESS; + break; + + // Up 5 + case 0x2D: + btn_state = BTN_UP5; + break; + + // Up 14 + case 0x2C: + btn_state = BTN_UP14; + break; + + // Down 5 + case 0x27: + btn_state = BTN_DOWN5; + break; + + // Down 14 + case 0x23: + btn_state = BTN_DOWN14; + break; + + // This should never happen -> Button is broken + default: + btn_state = btn_state_current; + break; + } + + btn_idx = btn_state_current * 6 + btn_state; + btn_event = pgm_read_byte(&(btn_table[btn_idx])); + if (0xFF == btn_event) + { + switch (btn_state) + { + case BTN_UP14: + if (BTN_UP14 != btn_state_stable) + { + btn_event = EVENT_SEL_UP; + } + else + { + btn_event = EVENT_NONE; + } + break; + + case BTN_DOWN14: + if (BTN_DOWN14 != btn_state_stable) + { + btn_event = EVENT_SEL_DOWN; + } + else + { + btn_event = EVENT_NONE; + } + break; + + default: + btn_event = EVENT_NONE; + break; + } + } + } + + old_btn_pin_state = btn_pin_state; + btn_state_current = btn_state; + if ((btn_state != BTN_UP5) && (btn_state != BTN_DOWN5)) + { + btn_state_stable = btn_state; + } + + sei(); +} + +// Changes on rotary encoder +ISR(PCINT1_vect) +{ + uint8_t rot_pin_state = PINB & 0x03; + uint8_t rot_event = EVENT_NONE; + uint8_t rot_idx; + + cli(); + if ((rot_pin_state ^ old_rot_pin_state) && (event_count != MAX_EVENT_COUNT)) + { + rot_idx = (old_rot_pin_state << 2) | rot_pin_state; + 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_rot_pin_state = rot_pin_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() +{ + // No Prescaler - 1 MHz + // Set Mode to CTC with ICR1 + TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 < CS10) + + // 1 000 000 / 10 000 = 100 (Hz) + // Interrupt every 10ms + ICR1 = 10000; + + // Enable Interrupt on overflow + TIMSK = (1 << TOIE1); + + // Interrupt on Up/Down/Click pins + PCMSK0 = (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2) | (1 << PCINT3) | (1 << PCINT5); + + // Interrupt on rotary encoder pins + PCMSK1 = (1 << PCINT8) | (1 << PCINT9); + + // Enable Interrupt on pin-changes + GIMSK = (1 << PCIE0) | (1 << PCIE1); + + // Sleep mode - power down + MCUCR = (1 << SM1); + + // Power reduction - Disable timer 0 and ADC + PRR = (1 << PRTIM0) | (1 << PRADC); + + old_btn_pin_state = 0x2F; + old_rot_pin_state = (PINB & 0x03); +} + +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 | 0x10, // Alternating COM Pin Configuration (COM63 = 0, COM31 = 1, ...) + 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])); + } +} + +// Symbol idx in table, +// Start x pixel - 0 -- 117 +// Y row - 0 -- 3 +// Char Size - 10x16 +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 + PRR |= (1 << PRUSI); + + // Enable sleep + MCUCR |= (1 << SE); + + // Sleep + sleep_cpu(); + + // Reset ms counter + cli(); + ms = 2; + sei(); + + // Disable sleep + MCUCR &= ~(1 << SE); + + // Enable clock to USI + PRR &= ~(1 << PRUSI); +} + +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; +}