#include <LiquidCrystal_I2C.h>
#include <Encoder.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <EEPROM.h>
// Pin definitions
#define LED_RED 1
#define LED_GREEN 0
#define LED_WHITE_1 7
#define LED_WHITE_2 2
#define LED_ANCHOR 6
#define BUZZER_PIN 13
// Rotary encoder pins
#define ENCODER_PIN_A 5
#define ENCODER_PIN_B 4
#define ENCODER_BUTTON 3
// LCD setup (assuming address 0x27, 16 columns, 2 rows)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Encoder setup
Encoder myEncoder(ENCODER_PIN_A, ENCODER_PIN_B);
// Mode definitions
enum Mode {
ALL_OFF,
MOTORING,
SAILING,
ANCHOR
};
// Global variables
Mode currentMode = ALL_OFF;
bool isLocked = true;
long lastEncoderPosition = 0;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; // 50ms debounce time
unsigned long modeStartTime = 0;
unsigned long lastActivityTime = 0;
const unsigned long SLEEP_TIMEOUT = 300000; // 5 minutes in milliseconds
// EEPROM address for storing the mode
const int EEPROM_MODE_ADDRESS = 0;
// Custom character definitions
byte anchorChar[8] = {
0b00100,
0b01110,
0b01110,
0b01110,
0b01110,
0b11111,
0b00100,
0b00100
};
byte propellerChar[8] = {
0b00100,
0b10101,
0b01110,
0b11111,
0b01110,
0b10101,
0b00100,
0b00000
};
byte sailChar[8] = {
0b00010,
0b00110,
0b01110,
0b11110,
0b11111,
0b01110,
0b00100,
0b00100
};
void setup() {
// Initialize LED pins
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_WHITE_1, OUTPUT);
pinMode(LED_WHITE_2, OUTPUT);
pinMode(LED_ANCHOR, OUTPUT);
// Initialize buzzer pin
pinMode(BUZZER_PIN, OUTPUT);
// Initialize encoder button pin
pinMode(ENCODER_BUTTON, INPUT_PULLUP);
// Initialize LCD
lcd.init();
lcd.backlight();
// Create custom characters
lcd.createChar(0, anchorChar);
lcd.createChar(1, propellerChar);
lcd.createChar(2, sailChar);
// Read last mode from EEPROM
int savedMode = EEPROM.read(EEPROM_MODE_ADDRESS);
if (savedMode >= ALL_OFF && savedMode <= ANCHOR) {
currentMode = static_cast<Mode>(savedMode);
}
// Initialize mode start time
modeStartTime = millis();
// Display initial message
updateDisplayFull();
// Setup sleep mode
setupSleepMode();
}
void loop() {
// Check for inactivity and enter sleep mode if necessary
if (isLocked && (millis() - lastActivityTime > SLEEP_TIMEOUT)) {
enterSleepMode();
}
// Check encoder button
checkEncoderButton();
// Check encoder rotation if unlocked
if (!isLocked) {
checkEncoderRotation();
}
// Update LEDs based on current mode
updateLEDs();
// Periodically update only the time on the display
static unsigned long lastTimeUpdate = 0;
if (millis() - lastTimeUpdate > 1000) { // Update every second
updateDisplayTime();
lastTimeUpdate = millis();
}
}
void checkEncoderButton() {
int buttonState = digitalRead(ENCODER_BUTTON);
if (buttonState == LOW) {
// Button is pressed
if (millis() - lastDebounceTime > debounceDelay) {
isLocked = !isLocked;
playTone(isLocked ? 2000 : 1000, 100); // Different tones for lock/unlock
updateDisplayFull(); // Full update when lock status changes
lastDebounceTime = millis();
lastActivityTime = millis();
}
}
}
void checkEncoderRotation() {
long newPosition = myEncoder.read() / 4; // Divide by 4 to get more defined steps
if (newPosition != lastEncoderPosition) {
if (newPosition > lastEncoderPosition) {
currentMode = static_cast<Mode>((currentMode + 1) % 4);
} else {
currentMode = static_cast<Mode>((currentMode - 1 + 4) % 4);
}
lastEncoderPosition = newPosition;
playTone(1500, 50); // Short beep for mode change
modeStartTime = millis(); // Reset mode start time
updateDisplayFull(); // Full update when mode changes
lastActivityTime = millis();
// Save current mode to EEPROM
EEPROM.update(EEPROM_MODE_ADDRESS, currentMode);
}
}
void updateLEDs() {
digitalWrite(LED_RED, (currentMode == MOTORING || currentMode == SAILING) ? HIGH : LOW);
digitalWrite(LED_GREEN, (currentMode == MOTORING || currentMode == SAILING) ? HIGH : LOW);
digitalWrite(LED_WHITE_1, (currentMode == MOTORING || currentMode == SAILING) ? HIGH : LOW);
digitalWrite(LED_WHITE_2, (currentMode == SAILING) ? HIGH : LOW);
digitalWrite(LED_ANCHOR, (currentMode == ANCHOR) ? HIGH : LOW);
}
void updateDisplayFull() {
lcd.clear(); // Clear the entire display
lcd.setCursor(0, 0);
switch (currentMode) {
case ALL_OFF:
lcd.print("Iddle!");
break;
case MOTORING:
lcd.print("Motoring");
lcd.setCursor(15, 0);
lcd.write(1); // Propeller symbol
break;
case SAILING:
lcd.print("Sailing");
lcd.setCursor(15, 0);
lcd.write(2); // Sail symbol
break;
case ANCHOR:
lcd.print("At anchor");
lcd.setCursor(15, 0);
lcd.write(0); // Anchor symbol
break;
}
lcd.setCursor(0, 1);
lcd.print(isLocked ? "Locked " : "Unlocked ");
updateDisplayTime(); // Update time after changing mode
}
void updateDisplayTime() {
// Update only the time portion of the display
unsigned long modeTime = (millis() - modeStartTime) / 1000; // Time in seconds
lcd.setCursor(9, 1);
lcd.print(formatTime(modeTime));
}
String formatTime(unsigned long seconds) {
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
char buffer[6]; // HH:MM + null terminator
sprintf(buffer, "%02d:%02d", hours, minutes);
return String(buffer);
}
void playTone(unsigned int frequency, unsigned long duration) {
tone(BUZZER_PIN, frequency, duration);
}
void enterSleepMode() {
// Turn off LEDs and LCD backlight
updateLEDs();
lcd.noBacklight();
// Disable ADC
ADCSRA = 0;
// Configure sleep mode
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
// Disable brown-out detection during sleep (BOD)
sleep_bod_disable();
// Enter sleep mode
sleep_mode();
// Program resumes here after waking up
sleep_disable();
// Re-enable ADC
ADCSRA = 0b10000000;
// Turn on LCD backlight
lcd.backlight();
// Reset activity time
lastActivityTime = millis();
// Update display
updateDisplayFull();
}
// Interrupt Service Routine for waking up from sleep
ISR(PCINT2_vect) {
// This function will run when the interrupt is triggered, but we don't need to do anything here.
}
void setupSleepMode() {
// Enable pin change interrupt on the encoder button pin
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT19); // Assuming the encoder button is on digital pin 3 (PCINT19)
}