#include <TinyWireM.h>
#include <Tiny4kOLED.h>
#include <avr/sleep.h>
#define adc_disable() (ADCSRA &= -(1<<ADEN))
#define adc_enable() (ADCSRA |= (!<<ADEN))
#define LED 1
#define PIN_UP 4
#define PIN_DOWN 5
#define PIN_OK 3
bool BT_UP = false;
bool BT_DOWN = false;
bool BT_OK = false;
bool BT_OK_changed = false;
long debounceTime = 0L;
long lastWakeUp = 0L;
long debounceDelay = 100L;
bool switchingSettingLine = true;
bool performFrameSwitch = true;
const long repeatInputInterval = 200L;
long timeChangedInput = 0;
// The screen can show 4 setting lines at once.
uint8_t settingsPage = 0;
bool changedSettingsPage = false;
uint8_t redrawsSettingsPage = 2;
void NoOutput(uint8_t m, uint8_t value);
void OutputNum(uint8_t, uint8_t);
void OutputYN(uint8_t, uint8_t);
void NoAction(uint8_t);
#define CURRENT_SETTING_LINE 0
void OutputCurrentSettingLine(uint8_t, uint8_t);
void CurrentSettingLineChanged(uint8_t);
#define CONTRAST_VALUE (CURRENT_SETTING_LINE+1)
const char CONTRAST_NAME[] PROGMEM = "Contrast";
void ContrastChanged(uint8_t value) {
oled.setContrast(value);
}
#define INVERSE_VALUE (CONTRAST_VALUE+1)
const char INVERSE_NAME[] PROGMEM = "Inverse";
void InverseChanged(uint8_t value) {
oled.setInverse(value);
}
#define VCOMH_DESELECT_VALUE (INVERSE_VALUE+1)
const char VCOMH_DESELECT_NAME[] PROGMEM = "Voltage";
void setVoltage(uint8_t, uint8_t);
#define FREQUENCY_VALUE (VCOMH_DESELECT_VALUE+1)
#define DIVIDE_RATIO_VALUE (FREQUENCY_VALUE+1)
const char FREQUENCY_NAME[] PROGMEM = "Frequency";
const char DIVIDE_RATIO_NAME[] PROGMEM = "Divide Ratio";
void DisplayClockChanged(uint8_t value);
#define DISPLAY_FLIP_VALUE (DIVIDE_RATIO_VALUE+1)
const char DISPLAY_FLIP_NAME[] PROGMEM = "Display flip";
void DisplayFlipChanged(uint8_t value) {
oled.setRotation(value);
}
#define DOUBLE_BUFFER_VALUE (DISPLAY_FLIP_VALUE+1)
const char DOUBLE_BUFFER_NAME[] PROGMEM = "Double Buffer";
void DoubleBufferChanged(uint8_t value) {
if (value) {
oled.switchRenderFrame();
} else {
if (oled.currentDisplayFrame() > 0) oled.switchDisplayFrame();
if (oled.currentRenderFrame() > 0) oled.switchRenderFrame();
}
}
#define MEMORY_MODE_VALUE (DOUBLE_BUFFER_VALUE+1)
const char MEMORY_MODE_NAME[] PROGMEM = "Memory Mode";
#define SCROLL_DIRECTION_VALUE (MEMORY_MODE_VALUE+1)
#define SCROLL_INTERVAL_VALUE (SCROLL_DIRECTION_VALUE+1)
#define SCROLL_OFFSET_VALUE (SCROLL_INTERVAL_VALUE+1)
const char SCROLL_DIRECTION_NAME[] PROGMEM = "Direction";
const char INTERVAL_NAME[] PROGMEM = "Interval";
const char SCROLL_OFFSET_NAME[] PROGMEM = "Offset";
void OutputScrollDirection(uint8_t, uint8_t);
void OutputScrollInterval(uint8_t, uint8_t);
const uint8_t scrollIntervals[8] = {7, 4, 5, 6, 0, 1, 2, 3};
const uint8_t scrollIntervalFrames[8] = {2, 3, 4, 5, 6, 32, 64, 128};
#define DEMO_SIZE_VALUE (SCROLL_OFFSET_VALUE+1)
const char DEMO_SIZE_NAME[] PROGMEM = "Demo Size";
#define CURRENT_VOLTAGE_VALUE (DEMO_SIZE_VALUE+1)
const char CURRENT_VOLTAGE_NAME[] PROGMEM = "Vcc";
void OutputCurrentVoltage(uint8_t, uint8_t);
const uint8_t settingLines = CURRENT_VOLTAGE_VALUE;
uint8_t setting_changed = settingLines + 1;
typedef void (*ActionFunc) (uint8_t);
typedef void (*OutputFunc) (uint8_t, uint8_t);
struct {
PGM_P name;
const uint8_t min;
const uint8_t max;
const ActionFunc actionFn;
const OutputFunc outputFn;
} const s[settingLines + 1] PROGMEM = {
// name min max action output
{ NULL, 1, settingLines,
CurrentSettingLineChanged, OutputCurrentSettingLine
},
{CONTRAST_NAME, 0, 255, ContrastChanged, OutputNum},
{INVERSE_NAME, 0, 1, InverseChanged, OutputYN},
{VCOMH_DESELECT_NAME, 0, 255, NoAction, setVoltage},
{FREQUENCY_NAME, 0, 15, DisplayClockChanged, OutputNum},
{DIVIDE_RATIO_NAME, 1, 16, DisplayClockChanged, OutputNum},
{DISPLAY_FLIP_NAME, 0, 1, DisplayFlipChanged, OutputYN},
{DOUBLE_BUFFER_NAME, 0, 1, DoubleBufferChanged, OutputYN},
{MEMORY_MODE_NAME, 0, 2, NoAction, OutputNum},
{SCROLL_DIRECTION_NAME, 0, 2, NoAction, OutputScrollDirection},
{INTERVAL_NAME, 0, 7, NoAction, OutputScrollInterval},
{SCROLL_OFFSET_NAME, 0, 63, NoAction, OutputNum},
{DEMO_SIZE_NAME, 0, 255, NoAction, OutputNum},
{CURRENT_VOLTAGE_NAME, 0, 255, NoAction, OutputCurrentVoltage}
};
uint8_t v[settingLines + 1] = {
1, // CURRENT_SETTING_LINE
0x7F, // CONTRAST_VALUE
0, // INVERSE_VALUE
20, // VCOMH_DESELECT_VALUE
8, // FREQUENCY_VALUE
1, // DIVIDE_RATIO_VALUE
1, // DISPLAY_FLIP_VALUE
1, // DOUBLE_BUFFER_VALUE
2, // MEMORY_MODE_VALUE
0, // SCROLL_DIRECTION_VALUE
0, // SCROLL_INTERVAL_VALUE
1, // SCROLL_OFFSET_VALUE
255, // DEMO_SIZE_VALUE
0 // CURRENT_VOLTAGE_VALUE (Unused)
};
uint8_t r[settingLines + 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
void DisplayClockChanged(__attribute__((unused)) uint8_t value) {
oled.setDisplayClock(v[DIVIDE_RATIO_VALUE], v[FREQUENCY_VALUE]);
}
void sleepWaitingForInput();
void PixelsChanged(uint8_t value) {
if (value == 0) {
oled.off();
sleepWaitingForInput(1 << PIN_UP);
oled.on();
} else if (value == 1) {
oled.setEntireDisplayOn(false);
} else {
oled.setEntireDisplayOn(true);
}
}
ISR(PCINT0_vect) {}
void setup() {
setBT_UPs();
setupOLED();
welcome();
setupADC();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
void setBT_UPs() {
pinMode(PIN_UP, INPUT_PULLUP);
pinMode(PIN_DOWN, INPUT_PULLUP);
pinMode(PIN_OK, INPUT_PULLUP);
}
void welcome() {
oled.setFont(FONT8X16P);
oled.setCursor(1, 15 );
oled.print("______________");
oled.setCursor(30, 29 );
oled.print("WELCOME");
oled.setCursor(1, 31);
oled.print("______________");
delay(1000);
oled.clear();
oled.setFont(FONT6X8P);
}
void setupOLED() {
oled.begin();
//oled.setRotation(0); //0 or 1
//oled.setInternalIref(true);
//oled.setFont(FONT6X8);
oled.clear();
oled.on();
oled.switchRenderFrame();
oled.clear();
}
void loop() {
if (!performFrameSwitch && !BT_OK && !BT_UP && !BT_DOWN)
sleepWaitingForInput((1 << PIN_UP) | (1 << PIN_DOWN) | (1 << PIN_OK));
readInputs();
processInputs();
processChanges();
updateDisplay();
}
void sleepWaitingForInput(uint8_t pins) {
// Don't sleep till after the debounce period - as otherwise a bounce will wake it up
long currentTime = millis();
if (currentTime < debounceTime && currentTime >= lastWakeUp) // time might wrap
{
return;
}
uint8_t temp = TIMSK;
TIMSK = 0; // Disable timer interrupt(s)
GIMSK = 1 << PCIE; // Enable pin-change interrupt
PCMSK = pins;
ADCSRA &= ~(1 << ADEN); // Disable ADC to save power
sleep_enable();
sleep_cpu();
GIMSK = 0; // Turn off interrupt
TIMSK = temp; // Re-enable timer interrupt(s)
ADCSRA |= 1 << ADEN; // Re-enable ADC
lastWakeUp = millis();
debounceTime = lastWakeUp;
}
void readInputs() {
bool oldRockerUp = BT_UP;
bool oldRockerDown = BT_DOWN;
bool oldRockerIn = BT_OK;
BT_UP = digitalRead(PIN_UP) == LOW;
BT_DOWN = digitalRead(PIN_DOWN) == LOW;
BT_OK = digitalRead(PIN_OK) == LOW;
BT_OK_changed = false;
if ((oldRockerUp != BT_UP) || (oldRockerDown != BT_DOWN) || (oldRockerIn != BT_OK))
{
long currentTime = millis();
if (currentTime < debounceTime && currentTime >= lastWakeUp) // time might wrap
{
return;
}
debounceTime = currentTime + debounceDelay;
}
if (oldRockerIn != BT_OK)
{
BT_OK_changed = true;
}
}
void processInputs() {
if (switchingSettingLine) {
processInputsSwitchingSettingLine();
} else {
processInputsForSetting(v[CURRENT_SETTING_LINE]);
}
if (BT_OK_changed && BT_OK) {
switchingSettingLine = !switchingSettingLine;
setting_changed = CURRENT_SETTING_LINE;
}
}
void processInputsSwitchingSettingLine() {
processInputsForSetting(CURRENT_SETTING_LINE);
if (setting_changed == CURRENT_SETTING_LINE) {
uint8_t oldSettingsPage = settingsPage;
settingsPage = (v[CURRENT_SETTING_LINE] - 1) >> 2;
changedSettingsPage = oldSettingsPage != settingsPage;
}
}
void processInputsForSetting(uint8_t m) {
long ms = millis();
if ((ms > timeChangedInput) && ((ms - timeChangedInput) < repeatInputInterval)) return;
uint8_t value = v[m];
uint8_t oldValue = value;
bool inc = BT_UP;
bool dec = BT_DOWN;
if (m == CURRENT_SETTING_LINE) {
inc = BT_DOWN;
dec = BT_UP;
}
if (inc && (value < pgm_read_byte(&(s[m].max)))) value++;
if (dec && (value > pgm_read_byte(&(s[m].min)))) value--;
if (oldValue != value) {
setting_changed = m;
v[m] = value;
}
}
bool CheckValueChanged(uint8_t m) {
if (setting_changed == m) {
setting_changed = settingLines + 1;
r[m] = v[DOUBLE_BUFFER_VALUE] + 1;
timeChangedInput = millis();
return true;
}
return false;
}
void processChanges() {
for (uint8_t m = 0; m <= settingLines; m++)
if (CheckValueChanged(m)) ((ActionFunc)pgm_read_ptr(&(s[m].actionFn)))(v[m]);
}
void NoAction(__attribute__((unused)) uint8_t value) {}
void CurrentSettingLineChanged(__attribute__((unused)) uint8_t value) {
if (changedSettingsPage) {
redrawsSettingsPage = v[DOUBLE_BUFFER_VALUE] + 1;
}
}
bool CheckRedraw(uint8_t m) {
if (r[m] > 0) {
r[m]--;
performFrameSwitch = true;
return true;
}
return false;
}
void updateDisplay() {
performFrameSwitch = false;
if (redrawsSettingsPage > 0) {
OutputSettingsPage();
redrawsSettingsPage--;
performFrameSwitch = true;
}
for (uint8_t m = 0; m <= settingLines; m++)
if (CheckRedraw(m)) ((OutputFunc)pgm_read_ptr(&(s[m].outputFn)))(m, v[m]);
if (performFrameSwitch && v[DOUBLE_BUFFER_VALUE]) oled.switchFrame();
}
void OutputSettingsPage() {
oled.clear();
r[CURRENT_SETTING_LINE] = redrawsSettingsPage;
uint8_t m = settingsPage * 4 + 1;
for (uint8_t l = 0; (l < 4) && (m <= settingLines); l++, m++) {
oled.setCursor(0, l);
for (uint8_t k = 0; k < strlen_P((PGM_P)pgm_read_word(&(s[m].name))); k++)
{
char myChar = pgm_read_byte((PGM_P)pgm_read_word(&(s[m].name)) + k);
oled.print(myChar);
}
oled.print(':');
r[m] = redrawsSettingsPage;
}
}
void PositionCursor(uint8_t m) {
oled.setCursor((strlen_P((PGM_P)pgm_read_word(&(s[m].name))) + 2) * 6, (m - 1) & 0x03);
}
void OutputNumOnly(uint8_t value) {
oled.print(value);
if (value < 100) oled.print(' ');
if (value < 10) oled.print(' ');
}
void NoOutput(__attribute__((unused)) uint8_t m, __attribute__((unused)) uint8_t value) {}
void OutputCurrentSettingLine(__attribute__((unused)) uint8_t m, uint8_t value) {
uint8_t currentLine = (value - 1) & 0x03;
for (uint8_t l = 0; l < 4; l++) {
oled.setCursor(120, l);
if (l == currentLine) {
if (switchingSettingLine)
oled.print('-');
else
oled.print('<');
} else {
oled.print(' ');
}
}
}
void OutputNum(uint8_t m, uint8_t value) {
PositionCursor(m);
OutputNumOnly(value);
}
void OutputYN(uint8_t m, uint8_t value) {
PositionCursor(m);
if (value)
oled.print('Y');
else
oled.print('N');
}
void OutputScrollInterval(uint8_t m, uint8_t value) {
PositionCursor(m);
OutputNumOnly(scrollIntervalFrames[value]);
}
void OutputScrollDirection(uint8_t m, uint8_t value) {
PositionCursor(m);
if (value == 0) oled.print('L');
else if (value == 1) oled.print('-');
else oled.print('R');
}
void setVoltage(uint8_t m, uint8_t value) {
PositionCursor(m);
oled.print(value);
oled.print(F("V"));
analogWrite(LED, value);
}
void OutputCurrentVoltage(uint8_t m, __attribute__((unused)) uint8_t value) {
PositionCursor(m);
unsigned int result = readADC();
result = 1126400L / result; // Calculate Vcc (in mV); 1.1*1024*1000
oled.print(result);
oled.print(F(" mV"));
}
void setupADC() {
ADMUX = 0b1100 << MUX0; // Vcc Ref, Measure bandgap voltage (1.1)
ADCSRA = 1 << ADEN | 4 << ADPS0; // Enable, 62.5kHz ADC clock (16x prescalar)
}
unsigned int readADC() {
unsigned char low, high;
ADCSRA = ADCSRA | 1 << ADSC; // Start
do {} while (ADCSRA & 1 << ADSC); // Wait while conversion in progress
low = ADCL;
high = ADCH;
return (high << 8 | low);
}