#include <LiquidCrystal_I2C.h>
#define LED_R 9
#define LED_G 10
#define LED_B 11
#define BUTTON_UP 6
#define BUTTON_DOWN 5
#define BUTTON_LEFT 4
#define BUTTON_RIGHT 7
#define BUTTON_ENTER 8
#define BUTTON_HIGH 1
#define LCD_ROWS_COUNT 2
#define LCD_CELL_COUNT 16
#define POS_X_LINE_INFO 1
#define POS_Y_LINE_INFO 0
#define BLINK_INTERVAL 500
#define MASK_CHAR 35
#define START_MODE_EDIT false
#define BUTTON_DELAY() delay(150)
#define CLICK(_BUTTON_) (digitalRead(_BUTTON_) == BUTTON_HIGH)
#define GETLVAL_COLOR_BY_STATE_LED(_COLOR_, _STATE_LCD_) (((uint8_t*)(&_COLOR_))[_STATE_LCD_ - 1])
// ======================================== DEBUG Mode
#define DEBUG_SERIAL 0
#if DEBUG_SERIAL > 0
#define __DEBUG true
#endif
#if __DEBUG
#define DUBUG_ECHO(_TEXT_) Serial.println(_TEXT_)
String nameEnumsStateLCD[] = {
"Default",
"EditRed",
"EditGreen",
"EditBlue"
};
#else
#define DUBUG_ECHO(_TEXT_)
#endif
// ========================================
enum StateLCD: uint8_t{
Default = 0,
EditRed = 1,
EditGreen = 2,
EditBlue = 3
};
struct Color{
uint8_t R;
uint8_t G;
uint8_t B;
};
// ======================================== Константные значения
const char* hexTable = "0123456789ABCDEF";
const char* ledComponentNames = "RGB";
const char MaskLine[] = {MASK_CHAR, MASK_CHAR, 0};
// ======================================== Переменные управления цветом светодиода
Color currentColor;
Color editColor;
uint8_t editingValue = 0;
// ======================================== Переменные состояния экрана
LiquidCrystal_I2C lcd(0x27, LCD_CELL_COUNT, LCD_ROWS_COUNT);
uint8_t writedValue = 0;
StateLCD lcdState = StateLCD::Default;
uint64_t lcdTimer = 0;
bool timerState = false;
// ======================================== Инциализация компонент
inline void initLEDS();
inline void initButtons();
inline void initLCD();
// ======================================== Работо с отображением данных
// Попытка обновления вывода
inline void loopLCD();
// Начинает печатать шаблон 'X:'
void startWritePart(StateLCD ledComponent);
// Печатает HEX значение 8 bits
inline void writeHex(uint8_t value);
// Изменяет состояние экрана
void switchStateLCD(StateLCD state);
// Принудительная перерисовка экрана
void forcedUpdateLCD();
// Конфигурирует редактируемые данные в переменной editColor
inline Color* configurateEditingData();
// ======================================== Прочее
// Обработка нажатий
inline void buttonHandler();
// Выводит RGB компоненты на светодиод
inline void applayStateLEDS(Color* newColor);
void setup()
{
initLEDS();
initButtons();
initLCD();
#if __DEBUG
Serial.begin(DEBUG_SERIAL);
#endif
}
void loop()
{
buttonHandler();
loopLCD();
}
inline void buttonHandler(){
/*
* Специально не производиться проверка
* нижней верхней границы для удобства
*/
if(CLICK(BUTTON_UP)){
if(editingValue != 255) editingValue++;
DUBUG_ECHO("BUTTON_UP: " + String(editingValue));
BUTTON_DELAY();
}else if(CLICK(BUTTON_DOWN)){
if(editingValue != 0) editingValue--;
DUBUG_ECHO("BUTTON_UP: " + String(editingValue));
BUTTON_DELAY();
}else if(CLICK(BUTTON_LEFT)){
switchStateLCD(
(StateLCD)((lcdState - 1) & StateLCD::EditBlue)
);
DUBUG_ECHO("BUTTON_LEFT: " + nameEnumsStateLCD[lcdState]);
BUTTON_DELAY();
}else if(CLICK(BUTTON_RIGHT)){
switchStateLCD(
(StateLCD)((lcdState + 1) & StateLCD::EditBlue)
);
DUBUG_ECHO("BUTTON_RIGHT: " + nameEnumsStateLCD[lcdState]);
BUTTON_DELAY();
}else if(CLICK(BUTTON_ENTER)){
applayStateLEDS(configurateEditingData());
switchStateLCD(StateLCD::Default);
DUBUG_ECHO("BUTTON_ENTER: " + nameEnumsStateLCD[lcdState]);
}
}
void startWritePart(StateLCD ledComponent){
ledComponent = (StateLCD)(ledComponent - 1);
lcd.setCursor(POS_X_LINE_INFO + 5 * ledComponent,
POS_Y_LINE_INFO);
lcd.print(ledComponentNames[ledComponent]);
lcd.print(':');
}
inline void writeHex(uint8_t value){
lcd.print(hexTable[value >> 4]);
lcd.print(hexTable[value & 15]);
}
void forcedUpdateLCD(){
for(uint8_t i = 1; i < 4; i++){
startWritePart((StateLCD)i);
writeHex(((uint8_t*)(¤tColor))[i - 1]);
}
timerState = START_MODE_EDIT;
}
inline void initLCD(){
lcd.begin(LCD_CELL_COUNT, LCD_ROWS_COUNT);
forcedUpdateLCD();
}
inline void loopLCD(){
if(lcdState){
uint64_t currentMillis = millis();
bool isUpdate = false;
if(editingValue != writedValue){
isUpdate = true;
timerState = !START_MODE_EDIT;
}
if(isUpdate || currentMillis - lcdTimer > BLINK_INTERVAL){
lcdTimer = currentMillis;
startWritePart(lcdState);
if(timerState){
writeHex(editingValue);
writedValue = editingValue;
}else{
lcd.print(MaskLine);
}
timerState = !timerState;
}
}
}
inline Color* configurateEditingData(){
if(lcdState == StateLCD::Default) return ¤tColor;
GETLVAL_COLOR_BY_STATE_LED(editColor, lcdState) = editingValue;
return &editColor;
}
void switchStateLCD(StateLCD state){
if(state == lcdState) return;
if(lcdState != StateLCD::Default){
startWritePart(lcdState);
writeHex(editingValue);
}
if(state == StateLCD::Default){
forcedUpdateLCD();
editColor = currentColor;
}else{
configurateEditingData();
editingValue = GETLVAL_COLOR_BY_STATE_LED(editColor, state);
}
timerState = START_MODE_EDIT;
lcdState = state;
}
inline void applayStateLEDS(Color* newColor){
analogWrite(LED_R, newColor->R << 2); // входные данные от 0 - 1023
analogWrite(LED_G, newColor->G << 2); // битовый сдвиг ускоренная версия деления
analogWrite(LED_B, newColor->B << 2); // или умножения числа на 2
currentColor = *newColor;
}
inline void initLEDS(){
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
}
inline void initButtons(){
pinMode(BUTTON_UP, INPUT);
pinMode(BUTTON_DOWN, INPUT);
pinMode(BUTTON_LEFT, INPUT);
pinMode(BUTTON_RIGHT, INPUT);
pinMode(BUTTON_ENTER, INPUT);
}