1823 lines
33 KiB
C
1823 lines
33 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 100
|
|
#define BATTERY_CAPACITY_MAH 2200
|
|
|
|
#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 BUFF_SIZE 16
|
|
|
|
#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 10000
|
|
|
|
// 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.75
|
|
//#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_SLEEP,
|
|
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
|
|
};
|
|
|
|
enum charmap
|
|
{
|
|
CHAR_0 = 0,
|
|
CHAR_1,
|
|
CHAR_2,
|
|
CHAR_3,
|
|
CHAR_4,
|
|
CHAR_5,
|
|
CHAR_6,
|
|
CHAR_7,
|
|
CHAR_8,
|
|
CHAR_9,
|
|
CHAR_A,
|
|
CHAR_B,
|
|
CHAR_C,
|
|
CHAR_D,
|
|
CHAR_V,
|
|
CHAR_a,
|
|
CHAR_c,
|
|
CHAR_d,
|
|
CHAR_e,
|
|
CHAR_h,
|
|
CHAR_j,
|
|
CHAR_m,
|
|
CHAR_r,
|
|
CHAR_s,
|
|
CHAR_t,
|
|
CHAR_u,
|
|
CHAR_y,
|
|
CHAR_minus,
|
|
CHAR_dot,
|
|
CHAR_gt,
|
|
CHAR_deg,
|
|
CHAR_MAX
|
|
};
|
|
|
|
// Bottom -> Top (In Byte); Left -> Right (In Row)
|
|
const uint8_t PROGMEM symbols[CHAR_MAX * 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, 0x20, // s
|
|
0x04, 0x3F, 0x44, 0x40, 0x20, // t
|
|
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
|
|
0x0C, 0x50, 0x50, 0x50, 0x3C, // y
|
|
0x10, 0x10, 0x10, 0x10, 0x10, // minus
|
|
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
|
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
|
0x0E, 0x11, 0x11, 0x0E, 0x00 // deg
|
|
};
|
|
|
|
const char PROGMEM menu_strings[(MENU_COUNT + 1) * MENU_STRLEN] =
|
|
{
|
|
0xFF, 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
|
|
};
|
|
|
|
// 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 = DISPLAY_DELAY;
|
|
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 print area helper
|
|
void display_set_area(uint8_t start_x, uint8_t end_x, uint8_t start_y, uint8_t end_y);
|
|
|
|
// 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 percentage
|
|
void print_battery_percent();
|
|
|
|
// 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);
|
|
|
|
// 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_sleep();
|
|
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_CAPT_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 = btn_state_current;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actually put the event in the queue
|
|
if (btn_event != EVENT_NONE)
|
|
{
|
|
*event_write = btn_event;
|
|
++event_count;
|
|
PTR_INC(event_write);
|
|
}
|
|
|
|
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();
|
|
|
|
display_enable(1);
|
|
print_menu(1);
|
|
state = STATE_MENU;
|
|
|
|
sei();
|
|
while(1)
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_SLEEP:
|
|
process_sleep();
|
|
break;
|
|
|
|
case STATE_MENU:
|
|
process_menu();
|
|
break;
|
|
|
|
case STATE_ADJUST:
|
|
process_adjust();
|
|
break;
|
|
|
|
case STATE_BATTERY:
|
|
process_battery();
|
|
break;
|
|
}
|
|
|
|
if (STATE_SLEEP != state)
|
|
{
|
|
print_mm();
|
|
print_direction();
|
|
print_battery_percent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void simple_init()
|
|
{
|
|
// Pull-ups on button
|
|
PORTA = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5);
|
|
|
|
// Pull-ups on rotary encoder
|
|
PORTB = (1 << 0) | (1 << 1);
|
|
|
|
// 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 capture
|
|
TIMSK1 = (1 << ICIE1);
|
|
|
|
// 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_set_area(uint8_t start_x, uint8_t end_x, uint8_t start_y, uint8_t end_y)
|
|
{
|
|
uint8_t cmd_list[6] =
|
|
{
|
|
0x21, // Set Column Address
|
|
0x00, // Start Address
|
|
0x00, // End Address
|
|
0x22, // Set Page Address
|
|
0x00, // Start Page
|
|
0x00 // End Page
|
|
};
|
|
uint8_t i;
|
|
|
|
if (start_x >= OLED_X_SIZE)
|
|
{
|
|
start_x = OLED_X_SIZE - 1;
|
|
}
|
|
|
|
if (end_x >= OLED_X_SIZE)
|
|
{
|
|
end_x = OLED_X_SIZE - 1;
|
|
}
|
|
|
|
if (end_x < start_x)
|
|
{
|
|
end_x = start_x;
|
|
}
|
|
|
|
if (start_y >= (OLED_Y_SIZE / 8))
|
|
{
|
|
start_y = (OLED_Y_SIZE / 8) - 1;
|
|
}
|
|
|
|
if (end_y >= (OLED_Y_SIZE / 8))
|
|
{
|
|
end_y = (OLED_Y_SIZE / 8) - 1;
|
|
}
|
|
|
|
if (end_y < start_y)
|
|
{
|
|
end_y = start_y;
|
|
}
|
|
|
|
cmd_list[1] = start_x;
|
|
cmd_list[2] = end_x;
|
|
cmd_list[4] = start_y;
|
|
cmd_list[5] = end_y;
|
|
|
|
for (i = 0; i < sizeof(cmd_list); ++i)
|
|
{
|
|
display_send_cmd(cmd_list[i]);
|
|
}
|
|
}
|
|
|
|
void display_init()
|
|
{
|
|
uint16_t i;
|
|
uint8_t buf[BUFF_SIZE];
|
|
|
|
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));
|
|
}
|
|
|
|
i2c_stop();
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
display_set_area(x, x + CHAR_SIZE * 2 - 1, y * 2, y * 2 + 1);
|
|
display_send_data(unfolded_symbol, sizeof(unfolded_symbol));
|
|
i2c_stop();
|
|
}
|
|
|
|
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/clear 'mm'
|
|
if (old_highlight != count_highlight)
|
|
{
|
|
if (0xFF == count_highlight)
|
|
{
|
|
symbol = 0xFF;
|
|
}
|
|
else
|
|
{
|
|
symbol = CHAR_m;
|
|
}
|
|
|
|
print_symbol(symbol, x, y, 0);
|
|
x += (CHAR_SIZE + 1) * 2;
|
|
print_symbol(symbol, 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);
|
|
}
|
|
|
|
if (0xFF != dir_highlight)
|
|
{
|
|
// 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()
|
|
{
|
|
uint8_t x;
|
|
uint8_t y;
|
|
|
|
for (y = 1; y < (OLED_Y_SIZE / 8) / 2; ++y)
|
|
{
|
|
for (x = 0; x < 8; ++x)
|
|
{
|
|
print_symbol(0xFF, x * (CHAR_SIZE + 1) * 2, y, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_menu(uint8_t force_draw)
|
|
{
|
|
static uint8_t old_option = 0xFF;
|
|
static uint8_t old_spool = 0;
|
|
uint8_t i;
|
|
|
|
if (force_draw || (old_option != menu_option) || (old_option == 0 && (old_spool != spool_attached)))
|
|
{
|
|
for (i = 0; i < MENU_COUNT; ++i)
|
|
{
|
|
if (force_draw || (i == old_option) || (i == menu_option))
|
|
{
|
|
print_option(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
old_option = menu_option;
|
|
old_spool = spool_attached;
|
|
}
|
|
|
|
void print_option(uint8_t option)
|
|
{
|
|
uint8_t select = 0xFF;
|
|
uint8_t x = (CHAR_SIZE + 1) * 2;
|
|
uint8_t y = 1 + option;
|
|
uint8_t i;
|
|
|
|
// Whether to invert or not
|
|
if (option == menu_option)
|
|
{
|
|
select = CHAR_gt;
|
|
}
|
|
|
|
// Whether to display "Attach" or "Detach"
|
|
if ((0 == option) && spool_attached)
|
|
{
|
|
option += MENU_COUNT;
|
|
}
|
|
|
|
print_symbol(select, 0, y ,0);
|
|
|
|
for (i = 1; 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, 0);
|
|
x += (CHAR_SIZE + 1) * 2;
|
|
}
|
|
}
|
|
|
|
void print_battery_percent()
|
|
{
|
|
static uint8_t old_symbols[3] = {0xFF, 0xFF, 0xFF};
|
|
int32_t percent = BATTERY_CAPACITY_MAH;
|
|
uint8_t i;
|
|
uint8_t symbol;
|
|
uint8_t is_first = 1;
|
|
|
|
// Calculate percent
|
|
// battery_mAh is always negative
|
|
percent += battery_mAh;
|
|
percent *= 100;
|
|
percent /= BATTERY_CAPACITY_MAH;
|
|
|
|
if (percent < 0)
|
|
{
|
|
percent = 0;
|
|
}
|
|
|
|
// Print percent top to bot
|
|
for (i = 3; i > 0; --i)
|
|
{
|
|
symbol = extract_digit(percent, i);
|
|
if ((is_first) && (0 == symbol) && (i != 1))
|
|
{
|
|
symbol = 0xFF;
|
|
}
|
|
else
|
|
{
|
|
is_first = 0;
|
|
}
|
|
|
|
// 3,2,1 -> 0,1,2 / 1,2,3
|
|
if (symbol != old_symbols[3 - i])
|
|
{
|
|
print_symbol(symbol, OLED_X_SIZE - (CHAR_SIZE + 1) * 2, 3 - i + 1, 0);
|
|
old_symbols[3 - i] = symbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_battery_stat(uint8_t force_draw)
|
|
{
|
|
uint8_t symbols[MENU_STRLEN] = {0xFF, 0, 0, 0, 0, CHAR_m, CHAR_A, CHAR_h};
|
|
static int16_t old_mAh = 0xFFFF;
|
|
static int16_t old_mV = 0xFFFF;
|
|
static int16_t old_temp = 0xFFFF;
|
|
|
|
if (force_draw || (old_mAh != battery_mAh))
|
|
{
|
|
|
|
print_battery_single_stat(1, BATTERY_CAPACITY_MAH + battery_mAh, symbols, 5);
|
|
old_mAh = battery_mAh;
|
|
}
|
|
|
|
symbols[6] = CHAR_V;
|
|
symbols[7] = 0xFF;
|
|
if (force_draw || (old_mV != battery_mV))
|
|
{
|
|
print_battery_single_stat(2, battery_mV, symbols, 5);
|
|
old_mV = battery_mV;
|
|
}
|
|
|
|
symbols[5] = CHAR_deg;
|
|
symbols[6] = CHAR_C;
|
|
if (force_draw || (old_temp != battery_temp))
|
|
{
|
|
print_battery_single_stat(3, 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_minus;
|
|
stat = -stat;
|
|
}
|
|
else
|
|
{
|
|
symbols[0] = 0xFF;
|
|
}
|
|
|
|
// 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 = 0xFF;
|
|
uint32_t value = 0x00FFFFFF;
|
|
|
|
// 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 != 0x00FFFFFF) && (dir != 0xFF))
|
|
if (value != 0x00FFFFFF)
|
|
{
|
|
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];
|
|
uint32_t write_complete_when;
|
|
// This prevents infinite loop when waiting for write to complete
|
|
volatile uint32_t *ms_ptr = &ms;
|
|
|
|
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;
|
|
}
|
|
|
|
// Wait for write to complete
|
|
write_complete_when = (*ms_ptr) + 20;
|
|
while ((*ms_ptr) < write_complete_when);
|
|
|
|
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;
|
|
|
|
// Battery percent is calculated via negative mAh
|
|
if (val > 0)
|
|
{
|
|
reset_battery();
|
|
val = 0;
|
|
}
|
|
|
|
(*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 -= 4096;
|
|
}
|
|
|
|
// 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 -= 4096;
|
|
}
|
|
|
|
// 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);
|
|
read_battery(&battery_mAh, &battery_mV, &battery_temp);
|
|
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 / EEPROM_IDX_SIZE);
|
|
write_eeprom_val(eeprom_idx, (uint32_t) count_value_fine, rot_dir_is_A);
|
|
}
|
|
count_value = (uint32_t) count_value_fine;
|
|
}
|
|
}
|
|
}
|
|
|
|
void process_sleep()
|
|
{
|
|
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:
|
|
display_enable(1);
|
|
read_battery(&battery_mAh, &battery_mV, &battery_temp);
|
|
sleep_when_ms = ms + DISPLAY_DELAY;
|
|
print_menu(1);
|
|
state = STATE_MENU;
|
|
break;
|
|
|
|
case EVENT_BTN_UP:
|
|
long_press_when_ms = 0xFFFFFFFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (STATE_SLEEP == state)
|
|
{
|
|
display_enable(0);
|
|
do_sleep();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (state != STATE_MENU)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Print Menu in auto mode
|
|
print_menu(0);
|
|
|
|
if (ms >= sleep_when_ms)
|
|
{
|
|
print_clear_menu_area();
|
|
state = STATE_SLEEP;
|
|
sleep_when_ms = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
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, count_value, rot_dir_is_A);
|
|
|
|
// Go back to idle
|
|
print_menu(1);
|
|
state = STATE_MENU;
|
|
long_press_when_ms = 0xFFFFFFFF;
|
|
sleep_when_ms = ms + DISPLAY_DELAY;
|
|
spool_counting = 1;
|
|
|
|
count_highlight = 0;
|
|
dir_highlight = 0;
|
|
}
|
|
}
|
|
|
|
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_percent();
|
|
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();
|
|
print_menu(1);
|
|
state = STATE_MENU;
|
|
long_press_when_ms = 0xFFFFFFFF;
|
|
}
|
|
|
|
if (ms >= sleep_when_ms)
|
|
{
|
|
print_clear_menu_area();
|
|
state = STATE_SLEEP;
|
|
sleep_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 / EEPROM_IDX_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;
|
|
read_battery(&battery_mAh, &battery_mV, &battery_temp);
|
|
print_battery_stat(1);
|
|
}
|