#include #include #include #include #include #include #include "i2c_master.h" #define EEPROM_SIZE 256 #define EEPROM_IDX_SIZE 4 #define BATTERY_REG_MODE 0x00 #define BATTERY_REG_CTRL 0x01 #define BATTERY_READ_ADDR 0x02 #define BATTERY_READ_ADDR_END 0x0B #define BATTERY_BUFF_SIZE (BATTERY_READ_ADDR_END - BATTERY_READ_ADDR + 1) #define BATTERY_CHARGE 0 #define BATTERY_VOLT 6 #define BATTERY_TEMP 8 #define BATTERY_MILLIOHM 30 #define BATTERY_CHARGE_MULT 67 #define BATTERY_CHARGE_DIV (10 * BATTERY_MILLIOHM) #define BATTERY_VOLT_MULT 244 #define BATTERY_VOLT_DIV 100 #define BATTERY_TEMP_MULT 125 #define BATTERY_TEMP_DIV 100 #define CHAR_SIZE 5 #define OLED_ADDRESS (0x3C << 1) // #define OLED_ADDRESS (0x3D << 1) #define EEPROM_ADDRESS (0x50 << 1) #define BATTERY_ADDRESS (0x70 << 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 // Event queue macros #define MAX_EVENT_COUNT 64 #define PTR_INC(x) ((x) = events + ((((x) - events) + 1) % MAX_EVENT_COUNT)) #define MENU_COUNT 3 #define MENU_STRLEN 8 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 main_state_e { STATE_IDLE, STATE_MENU, STATE_ADJUST, STATE_BATTERY }; // 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 }; #define CHAR_0 0 #define CHAR_A 10 #define CHAR_B 11 #define CHAR_C 12 #define CHAR_D 13 #define CHAR_V 14 #define CHAR_a 15 #define CHAR_c 16 #define CHAR_d 17 #define CHAR_e 18 #define CHAR_h 19 #define CHAR_j 20 #define CHAR_m 21 #define CHAR_r 22 #define CHAR_s 23 #define CHAR_t 24 #define CHAR_u 25 #define CHAR_y 26 #define CHAR_dot 27 #define CHAR_lt 28 #define CHAR_gt 29 #define CHAR_deg 30 // Bottom -> Top (In Byte); Left -> Right (In Row) const uint8_t PROGMEM symbols[31 * CHAR_SIZE] = { 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 0x7E, 0x11, 0x11, 0x11, 0x7E, // A 0x7F, 0x49, 0x49, 0x49, 0x36, // B 0x3E, 0x41, 0x41, 0x41, 0x22, // C 0x7F, 0x41, 0x41, 0x41, 0x3E, // D 0x1F, 0x20, 0x40, 0x20, 0x1F, // V 0x20, 0x54, 0x54, 0x54, 0x78, // a 0x38, 0x44, 0x44, 0x44, 0x20, // c 0x38, 0x44, 0x44, 0x48, 0x7F, // d 0x38, 0x54, 0x54, 0x54, 0x18, // e 0x7F, 0x08, 0x04, 0x04, 0x78, // h 0x20, 0x40, 0x44, 0x3D, 0x00, // j 0x7C, 0x04, 0x78, 0x04, 0x78, // m 0x7C, 0x08, 0x04, 0x04, 0x08, // r 0x48, 0x54, 0x54, 0x54, 0x40, // s 0x04, 0x3F, 0x44, 0x40, 0x20, // t 0x3C, 0x40, 0x40, 0x20, 0x7C, // u 0x0C, 0x50, 0x50, 0x50, 0x3C, // y 0x00, 0x60, 0x60, 0x00, 0x00, // . 0x00, 0x08, 0x14, 0x22, 0x41, // < 0x41, 0x22, 0x14, 0x08, 0x00, // > 0x0E, 0x11, 0x11, 0x0E, 0x00 // deg }; const char PROGMEM menu_strings[(MENU_COUNT + 1) * MENU_STRLEN] = { CHAR_A, CHAR_t, CHAR_t, CHAR_a, CHAR_c, CHAR_h, 0xFF, 0xFF, CHAR_A, CHAR_d, CHAR_j, CHAR_u, CHAR_s, CHAR_t, 0xFF, 0xFF, CHAR_B, CHAR_a, CHAR_t, CHAR_t, CHAR_e, CHAR_r, CHAR_y, 0xFF, CHAR_D, CHAR_e, CHAR_t, CHAR_a, CHAR_c, CHAR_h, 0xFF, 0xFF }; // 1 / (4 * ROT_PULSE_COUNT) * (2 * pi * ROT_WHEEL_RAD) const float rot_coeff = 1.57f * ROT_WHEEL_RAD / ROT_PULSE_COUNT; float count_value_fine = 0; uint32_t count_value = 0; int8_t rot_value = 0; uint8_t rot_dir_is_A = 1; int16_t eeprom_idx = 0; uint8_t spool_attached = 0; uint8_t spool_counting = 0; uint32_t sleep_when_ms = 0; uint32_t long_press_when_ms = 0xFFFFFFFF; uint8_t count_highlight = 0xFF; uint8_t dir_highlight = 0xFF; uint8_t needs_update = 0; uint8_t state; uint8_t menu_option = 0; int16_t battery_mAh = 0; int16_t battery_mV = 0; int16_t battery_temp = 0; void (*menu[MENU_COUNT])(); // Init direct hardware void simple_init(); // OLED send cmd helper void display_send_cmd(uint8_t cmd); // OLED send data helper void display_send_data(const uint8_t *data, uint16_t buflen); // OLED init void display_init(); // OLED On/Off void display_enable(uint8_t en); // Symbol printing helper void get_symbol16(uint8_t index, uint8_t *out); // Print symbol on screen // 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); // Print the length left with mm and highlight the required digit // uses count_value for the value // count_highlight - for highlit digit // 0 - no highlight // 1 -- 6 (rigth to left digit) // 0xFF - clear all text void print_mm(); // Print the direction of the filament based on // rot_dir_is_A - direction // dir_highlight - 0 - no, 1 - yes, 0xFF - clear void print_direction(); // Clears the menu/data area void print_clear_menu_area(); // Print the menu void print_menu(uint8_t force_draw); // Prints the option in the correct place void print_option(uint8_t option); // Prints the battery icon depending on battery percentage void print_battery_icon(); // Prints battery statistics in menu area void print_battery_stat(uint8_t force_draw); // Prints single battery stat on row using symbols void print_battery_single_stat(uint8_t y, int16_t bat_stat, uint8_t *symbols, uint8_t decimal_idx); // Prints battery temp void print_battery_temp(); // Find the current eeprom data idx of the attached spool int16_t find_eeprom_idx(); // Read value and direction of current spool int8_t read_eeprom_val(int8_t idx, uint32_t *value, uint8_t *direction); // Write value and direction of current spool int8_t write_eeprom_val(int8_t idx, uint32_t value, uint8_t direction); // Reset battery counter stats int8_t reset_battery(); // Read battery stats // temp is in celsius int8_t read_battery(int16_t *mAh, int16_t *mV, int16_t *temp); // I2C write helper int8_t i2c_write_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len); // I2C read helper int8_t i2c_read_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len); // Do the big sleep void do_sleep(); // Extracts digit from value // 1 - ones, 2 - tens, 3 - hundreds ... uint8_t extract_digit(uint32_t value, uint8_t digit_num); // Returns an event from the queue or EVENT_NONE // EVENT_NONE could be from the queue uint8_t consume_event(); // Adjusts count value based on event void spool_count(uint8_t event); void process_idle(); void process_menu(); void process_adjust(); void process_battery(); // Attaches or detaches spool (Menu 0) void attach_detach_spool(); // Sets the state to adjust spool (Menu 1) void set_adjust_spool(); // Sets the state to battery info (Menu 3) void set_battery_state(); // Updates the ms counter ISR(TIM1_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() { // Init Direct Hardware simple_init(); i2c_init(); display_init(); reset_battery(); read_battery(&battery_mAh, &battery_mV, &battery_temp); // Attempt to attach spool on initial startup attach_detach_spool(); state = STATE_IDLE; sei(); while(1) { switch (state) { case STATE_IDLE: process_idle(); break; case STATE_MENU: process_menu(); break; case STATE_ADJUST: process_adjust(); break; case STATE_BATTERY: process_battery(); break; } print_mm(); print_direction(); print_battery_icon(); if (sleep_when_ms > ms) { sleep_when_ms = 0xFFFFFFFF; do_sleep(); read_battery(&battery_mAh, &battery_mV, &battery_temp); } } } 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 TIMSK1 = (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 (only button) GIMSK = (1 << PCIE0); // 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); menu[0] = attach_detach_spool; menu[1] = set_adjust_spool; menu[2] = set_battery_state; } 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() { static uint8_t old_symbols[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; static uint8_t old_highlight = 0xFF; 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(count_value, i); if (count_highlight == i) { invert = 1; } else { invert = 0; } // Leading zero removal // There's no highlight, this is the first digit to write, it's 0 // and it's not the last digit // or everything is removed if ((is_first && (0 == count_highlight) && (0 == symbol) && (1 != i)) || (0xFF == count_highlight)) { symbol = 0xFF; } else { is_first = 0; } // Update only necessary digits // On highlight change or on symbol change if ((old_highlight != count_highlight) || (symbol != old_symbols[6 - i])) { print_symbol(symbol, x, y, invert); old_symbols[6 - i] = symbol; } x += (CHAR_SIZE + 1) * 2; } // Print 'mm' (only after clear) if ((0xFF == old_highlight) && (0xFF != count_highlight)) { print_symbol(CHAR_m, x, y, 0); x += (CHAR_SIZE + 1) * 2; print_symbol(CHAR_m, x, y, 0); } // Clear 'mm' if (0xFF == count_highlight) { print_symbol(0xFF, x, y, 0); x += (CHAR_SIZE + 1) * 2; print_symbol(0xFF, x, y, 0); } old_highlight = count_highlight; } void print_direction() { uint8_t symbol; static uint8_t old_is_A = 0; static uint8_t old_highlight = 0xFF; if (rot_dir_is_A) { symbol = CHAR_A; } else { symbol = CHAR_B; } // Clear it all if ((0xFF == dir_highlight) && (old_highlight != dir_highlight)) { print_symbol(0xFF, OLED_X_SIZE - (CHAR_SIZE + 1) * 2 * 2, 0, 0); print_symbol(0xFF, OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 0, 0); } else { // Print '>' only when display is comming from clear if (0xFF == old_highlight) { print_symbol(CHAR_gt, OLED_X_SIZE - (CHAR_SIZE + 1) * 2 * 2, 0, 0); } // Print symbol on change or highlight change if ((old_is_A != rot_dir_is_A) || (old_highlight != dir_highlight)) { print_symbol(symbol, OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 0, dir_highlight); } } old_is_A = rot_dir_is_A; old_highlight = dir_highlight; } void print_clear_menu_area() { uint16_t i; uint16_t count; uint8_t bytes; uint8_t data[16]; uint8_t cmd_list[6] = { 0x21, // Set Column Address 0x00, // Start Address 0x00, // End Address 0x22, // Set Page Address 0x02, // Start Page 2 - Skip Top Row Where Spool Data Is 0x00 // End Page }; // End X - Do Not Clear Battery Symbol cmd_list[2] = OLED_X_SIZE - (CHAR_SIZE + 1) * 2 * 2 - 1; // End Y - Depends On OLED Size cmd_list[5] = (OLED_Y_SIZE / 8) - 1; for (i = 0; i < sizeof(cmd_list); ++i) { display_send_cmd(cmd_list[i]); } for (i = 0; i < sizeof(data); ++i) { data[i] = 0x00; } count = (cmd_list[2] + 1) * (cmd_list[5] - cmd_list[4] + 1); i = 0; while (i < count) { // If remaining bytes is smaller than buffer - send only required number bytes = (count - i) < sizeof(data) ? count - i : sizeof(data); display_send_data(data, bytes); i += bytes; } } void print_menu(uint8_t force_draw) { static uint8_t old_option = 0xFF; uint8_t i; if (force_draw || (old_option != menu_option)) { for (i = 0; i < MENU_COUNT; ++i) { if (force_draw || (i == old_option) || (i == menu_option)) { print_option(i); } } } } void print_option(uint8_t option) { uint8_t invert = 0; uint8_t x = 0; uint8_t y = 1 + option; uint8_t i; // Whether to invert or not if (option == menu_option) { invert = 1; } // Whether to display "Attach" or "Detach" if ((0 == option) && spool_attached) { option += MENU_COUNT; } for (i = 0; i < MENU_STRLEN; ++i) { // Get the symbol index from the menu_strings print_symbol(pgm_read_byte(&(menu_strings[option * MENU_STRLEN + i])), x, y, invert); x += (CHAR_SIZE + 1) * 2; } } void print_battery_icon() { } void print_battery_stat(uint8_t force_draw) { uint8_t symbols[MENU_STRLEN] = {0xFF, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF}; int16_t old_mAh = 0xFFFF; int16_t old_mV = 0xFFFF; int16_t old_temp = 0xFFFF; if ((old_mAh != battery_mAh) || force_draw) { symbols[5] = CHAR_m; symbols[6] = CHAR_A; symbols[7] = CHAR_h; print_battery_single_stat(1, battery_mAh, symbols, 0xFF); old_mAh = battery_mAh; } if ((old_mV != battery_mV) || force_draw) { symbols[5] = CHAR_m; symbols[6] = CHAR_V; symbols[7] = 0xFF; print_battery_single_stat(1, battery_mV, symbols, 0xFF); old_mV = battery_mV; } if ((old_temp != battery_temp) || force_draw) { symbols[5] = CHAR_deg; symbols[6] = CHAR_C; symbols[7] = 0xFF; print_battery_single_stat(1, battery_temp, symbols, 3); old_temp = battery_temp; } } void print_battery_single_stat(uint8_t y, int16_t bat_stat, uint8_t *symbols, uint8_t decimal_idx) { uint8_t x = 0; uint8_t i; uint16_t stat = bat_stat; // Show +/- if (bat_stat < 0) { symbols[0] = CHAR_lt; stat = -stat; } else { symbols[0] = CHAR_gt; } // Extract 4 digits for (i = 0; i < 4; ++i) { if ((4 - i) == decimal_idx) { symbols[decimal_idx] = CHAR_dot; continue; } symbols[4 - i] = stat % 10; stat /= 10; } // Remove leading zeroes for (i = 1; i < 5; ++i) { if (0 == symbols[i] && (decimal_idx != (i + 1))) { symbols[i] = 0xFF; } else { break; } } // Do the actual print for (i = 0; i < MENU_STRLEN; ++i) { print_symbol(symbols[i], x, y, 0); x += (CHAR_SIZE + 1) * 2; } } int16_t find_eeprom_idx() { uint8_t err; int16_t idx = 0; uint8_t found = 0; uint8_t dir; uint32_t value = 0xFFFFFFFF; // FF is the erased byte value for (idx = 0; idx < EEPROM_SIZE / EEPROM_IDX_SIZE; ++idx) { err = read_eeprom_val(idx, &value, &dir); if (0 != err) { return -1; } if (value != 0xFFFFFFFF) { found = 1; break; } } // Not found = new EEPROM if (!found) { return 0; } return idx; } int8_t read_eeprom_val(int8_t idx, uint32_t *value, uint8_t *direction) { int8_t err; uint8_t buf[4]; if (idx < 0) { return -1; } idx %= (EEPROM_SIZE / EEPROM_IDX_SIZE); err = i2c_read_buff(EEPROM_ADDRESS, idx * EEPROM_IDX_SIZE, buf, 4); if (4 != err) { return -1; } (*direction) = buf[0]; buf[0] = 0; (*value) = 0; for (uint8_t i = 0; i < 4; ++i) { (*value) <<= 8; (*value) |= buf[i]; } return 0; } int8_t write_eeprom_val(int8_t idx, uint32_t value, uint8_t direction) { int8_t err; uint8_t buf[4]; if (idx < 0) { return -1; } idx %= (EEPROM_SIZE / EEPROM_IDX_SIZE); for (uint8_t i = 0; i < 4; ++i) { buf[i] = ((value >> ((3 - i) * 8)) & 0xFF); } buf[0] = direction; err = i2c_write_buff(EEPROM_ADDRESS, idx * EEPROM_IDX_SIZE, buf, 4); if (4 != err) { return -1; } return 0; } int8_t reset_battery() { int8_t err; uint8_t val; // Reset All Battery Counters val = 0x02; err = i2c_write_buff(BATTERY_ADDRESS, BATTERY_REG_CTRL, &val, 1); if (1 != err) { return -1; } // Set Running Mode val = 0x10; err = i2c_write_buff(BATTERY_ADDRESS, BATTERY_REG_MODE, &val, 1); if (1 != err) { return -1; } return 0; } int8_t read_battery(int16_t *mAh, int16_t *mV, int16_t *temp) { // Reading from 0x02 to 0x0B inclusive uint8_t buf[BATTERY_BUFF_SIZE]; int8_t err; int32_t val; err = i2c_read_buff(BATTERY_ADDRESS, BATTERY_READ_ADDR, buf, BATTERY_BUFF_SIZE); if (err != BATTERY_BUFF_SIZE) { return -1; } // Read mAh bits val = buf[BATTERY_CHARGE + 1]; val <<= 8; val |= buf[BATTERY_CHARGE]; // If bit 15 is set if (val & 0x00008000) { val -= 65536; } // Transform into mAh val *= BATTERY_CHARGE_MULT; val /= BATTERY_CHARGE_DIV; (*mAh) = val; // Read mV bits val = buf[BATTERY_VOLT + 1]; val <<= 8; val |= buf[BATTERY_VOLT]; // If bit 11 is set if (val & 0x00000800) { val -= 2048; } // Transform into mV val *= BATTERY_VOLT_MULT; val /= BATTERY_VOLT_DIV; (*mV) = val; // Read temp bits val = buf[BATTERY_TEMP + 1]; val <<= 8; val |= buf[BATTERY_TEMP]; // If bit 11 is set if (val & 0x00000800) { val -= 2048; } // Transform into temp val *= BATTERY_TEMP_MULT; val /= BATTERY_TEMP_DIV; (*temp) = val; return 0; } int8_t i2c_write_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len) { uint8_t err; uint8_t i; err = i2c_start(i2c_addr | I2C_WRITE); if (0 != err) { i2c_stop(); return -1; } err = i2c_write(data_addr); if (0 != err) { i2c_stop(); return -1; } for (i = 0; i < len; ++i) { err = i2c_write(buf[i]); if (0 != err) { i2c_stop(); return i; } } i2c_stop(); return i; } int8_t i2c_read_buff(uint8_t i2c_addr, uint8_t data_addr, uint8_t *buf, uint8_t len) { uint8_t err; uint8_t i; err = i2c_start(i2c_addr | I2C_WRITE); if (0 != err) { i2c_stop(); return -1; } err = i2c_write(data_addr); if (0 != err) { i2c_stop(); return -1; } err = i2c_start(i2c_addr | I2C_READ); if (0 != err) { i2c_stop(); return -1; } for (i = 0; i < len; ++i) { buf[i] = i2c_read(i == len - 1); } i2c_stop(); return i; } void do_sleep() { // Disable clock to USI PRR |= (1 << PRUSI); // Enable sleep MCUCR |= (1 << SE); // Sleep sleep_cpu(); // Reset ms counter cli(); ms = 0; sei(); // Disable sleep MCUCR &= ~(1 << SE); // Enable clock to USI PRR &= ~(1 << PRUSI); } uint8_t extract_digit(uint32_t value, uint8_t digit_num) { while (1 != digit_num) { value /= 10; --digit_num; } return value % 10; } uint8_t consume_event() { uint8_t curr_event; if (event_count > 0) { cli(); curr_event = *event_read; PTR_INC(event_read); --event_count; sei(); } else { curr_event = EVENT_NONE; } return curr_event; } void spool_count(uint8_t event) { if ((!spool_attached) || (!spool_counting)) { return; } // A / CW = + // B / CCW = + // B / CW = - // A / CCW = - if ((rot_dir_is_A && (event == EVENT_ROT_CW)) || (!rot_dir_is_A && (event == EVENT_ROT_CCW))) { ++rot_value; } else { --rot_value; } 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) { // Update EEPROM with 0 // Clear old cell & write to next one write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF); ++eeprom_idx; eeprom_idx %= EEPROM_SIZE / EEPROM_IDX_SIZE; write_eeprom_val(eeprom_idx, 0, rot_dir_is_A); count_value_fine = 0; count_value = 0; spool_counting = 0; // Turn Display ON to show value display_enable(1); sleep_when_ms = ms + DISPLAY_DELAY; } else { // Update EEPROM every 100 mm if ((uint32_t) count_value_fine / 100 != count_value / 100) { // Clear old cell & write to next one write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF); ++eeprom_idx; eeprom_idx %= EEPROM_SIZE; write_eeprom_val(eeprom_idx, (uint32_t) count_value_fine, rot_dir_is_A); } count_value = (uint32_t) count_value_fine; } } } void process_idle() { uint8_t curr_event; while (event_count > 0) { curr_event = consume_event(); switch (curr_event) { case EVENT_ROT_CW: // Fall-through case EVENT_ROT_CCW: spool_count(curr_event); break; case EVENT_BTN_DOWN: long_press_when_ms = ms + LONG_PRESS; sleep_when_ms = ms + DISPLAY_DELAY; break; case EVENT_BTN_UP: long_press_when_ms = 0xFFFFFFFF; // Click - update battery info read_battery(&battery_mAh, &battery_mV, &battery_temp); break; } } // Hold button to enter menu if (ms >= long_press_when_ms) { state = STATE_MENU; menu_option = 0; long_press_when_ms = 0xFFFFFFFF; print_menu(1); } } void process_menu() { uint8_t curr_event; while (event_count > 0) { curr_event = consume_event(); switch (curr_event) { case EVENT_ROT_CW: // Fall-through case EVENT_ROT_CCW: spool_count(curr_event); break; case EVENT_BTN_DOWN: long_press_when_ms = ms + LONG_PRESS; sleep_when_ms = ms + DISPLAY_DELAY; break; case EVENT_BTN_UP: if (0xFFFFFFFF != long_press_when_ms) { // Button Clicked - Do whatever is selected menu[menu_option](); } long_press_when_ms = 0xFFFFFFFF; break; case EVENT_SEL_UP: sleep_when_ms = ms + DISPLAY_DELAY; menu_option += MENU_COUNT - 1; menu_option %= MENU_COUNT; break; case EVENT_SEL_DOWN: sleep_when_ms = ms + DISPLAY_DELAY; ++menu_option; menu_option %= MENU_COUNT; break; } } // Hold button to exit menu if (ms >= long_press_when_ms) { print_clear_menu_area(); state = STATE_IDLE; long_press_when_ms = 0xFFFFFFFF; } else { // Print Menu in auto mode print_menu(0); } } void process_adjust() { uint8_t curr_event; uint32_t digit_val = 1; for (uint8_t i = 1; i < count_highlight; ++i) { digit_val *= 10; } // NO SLEEP sleep_when_ms = 0xFFFFFFFF; while (event_count > 0) { curr_event = consume_event(); switch (curr_event) { case EVENT_BTN_DOWN: long_press_when_ms = ms + LONG_PRESS; break; case EVENT_BTN_UP: if (0xFFFFFFFF != long_press_when_ms) { // Button Clicked - Go to next digit or direction if (count_highlight) { --count_highlight; if (0 == count_highlight) { dir_highlight = 1; } } else { // Wrap around dir_highlight = 0; count_highlight = 6; } } long_press_when_ms = 0xFFFFFFFF; break; case EVENT_SEL_UP: // Digit ++ if (0 != count_highlight) { if (9 == extract_digit(count_value, count_highlight)) { count_value -= digit_val * 10; } count_value += digit_val; } else { rot_dir_is_A = !rot_dir_is_A; } break; case EVENT_SEL_DOWN: // Digit -- if (0 != count_highlight) { if (0 == extract_digit(count_value, count_highlight)) { count_value += digit_val * 10; } count_value -= digit_val; } else { rot_dir_is_A = !rot_dir_is_A; } break; } } // Hold button to stop if (ms >= long_press_when_ms) { count_value_fine = count_value; // Write to EEPROM // Clear old cell & write to next one write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF); ++eeprom_idx; eeprom_idx %= EEPROM_SIZE / EEPROM_IDX_SIZE; write_eeprom_val(eeprom_idx, (uint32_t) count_value, rot_dir_is_A); // Go back to idle state = STATE_IDLE; long_press_when_ms = 0xFFFFFFFF; sleep_when_ms = ms + DISPLAY_DELAY; spool_counting = 1; } } void process_battery() { uint8_t curr_event; while (event_count > 0) { curr_event = consume_event(); switch (curr_event) { case EVENT_ROT_CW: // Fall-through case EVENT_ROT_CCW: spool_count(curr_event); break; case EVENT_BTN_DOWN: sleep_when_ms = ms + DISPLAY_DELAY; long_press_when_ms = ms + LONG_PRESS; break; case EVENT_BTN_UP: if (0xFFFFFFFF != long_press_when_ms) { // Button Clicked - Update Battery Info read_battery(&battery_mAh, &battery_mV, &battery_temp); print_battery_icon(); print_battery_stat(0); } long_press_when_ms = 0xFFFFFFFF; break; } } // Hold button to return to idle if (ms >= long_press_when_ms) { print_clear_menu_area(); state = STATE_IDLE; long_press_when_ms = 0xFFFFFFFF; } } void attach_detach_spool() { if (spool_attached) { // Detach // Clear old cell & write to next one write_eeprom_val(eeprom_idx, 0xFFFFFFFF, 0xFF); ++eeprom_idx; eeprom_idx %= EEPROM_SIZE; write_eeprom_val(eeprom_idx, (uint32_t) count_value_fine, rot_dir_is_A); spool_counting = 0; spool_attached = 0; count_highlight = 0xFF; dir_highlight = 0xFF; // Disable rotary encoder interrupts GIMSK &= (~(1 << PCIE1)); } else { // Attach // Detect EEPROM eeprom_idx = find_eeprom_idx(); if (-1 == eeprom_idx) { return; } // Read Value if (0 != read_eeprom_val(eeprom_idx, &count_value, &rot_dir_is_A)) { return; } // New EEPROM if (0x00FFFFFF == count_value) { spool_counting = 0; count_value = 0; count_value_fine = 0; rot_dir_is_A = 1; } else { spool_counting = 1; count_value_fine = count_value; } spool_attached = 1; count_highlight = 0; dir_highlight = 0; // Enable rotary encoder interrupts GIMSK |= (1 << PCIE1); } } void set_adjust_spool() { if (!spool_attached) { return; } print_clear_menu_area(); count_highlight = 6; spool_counting = 0; state = STATE_ADJUST; } void set_battery_state() { print_clear_menu_area(); state = STATE_BATTERY; print_battery_stat(1); }