filament-counter-v2/code/main.c

1724 lines
32 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 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);
}