/* Fiat Barchetta SmartTools
Oleg Nikitin [email protected]
MIT License
Required hardware
- AtMega328
- ds3231 RTC
- LCD 16x2 with i2C
- HC-06 Bluetooth (serial input disabled till future versions)
v0.2 from 21.03.2025
- fix on/off flow
- add filter for running values
v0.1.1 from 20.03.2025
- fix voltage settings
- simplify power off settings
v0.1b from 19.03.2025
- display clock
- display greating message from code
- display power wire voltage and themp from ds3231 chip
- auto poweroff delay
*/
#include <Arduino.h>
#include <microDS3231.h> // https://github.com/GyverLibs/microDS3231.git
#include <LiquidCrystal.h> // https://github.com/arduino-libraries/LiquidCrystal
#include <GyverPower.h> // https://github.com/GyverLibs/GyverPower.git
#include <GyverFilters.h> // https://github.com/GyverLibs/GyverFilters.git
#define DEBUG_ENABLE
#define DEBUG(x)
#define SECOND 1000
#define SERIAL_DEBUG_OUTPUT_DELAY SECOND * 2 // 5 seconds
// Pins setup
#define DIGITAL_INTERRUPT_PIN 2
#define INTERRUPT_PIN digitalPinToInterrupt(DIGITAL_INTERRUPT_PIN)
#define LIGHT_SENSOR_PIN A6
#define VOLTAGE_SENSOR_PIN A7
#define BACKLIGHT_PIN 11
// Main params
#define SERIAL_PORT 9600
#define POWER_DOWN_PERIOD SLEEP_FOREVER
#define SETUP_RTC_DATETIME rtc.setTime(BUILD_SEC, BUILD_MIN, BUILD_HOUR, BUILD_DAY, BUILD_MONTH, BUILD_YEAR)
#define MESSAGE "Darenka milaya"
#define DT_FORMAT "%02d:%02d %02d.%02d.%02d"
#define BASE_DELAY SECOND
#define POWEROFF_DELAY (uint32_t) SECOND * 60 * 5 // 5 minutes
// display settings
#define DISPLAY_CLOCK_ROW 0
#define DISPLAY_CLOCK_COL 0
#define DISPLAY_STATUS_ROW 1
#define DISPLAY_MESSAGE_COL 0
#define DISPLAY_VOLTAGE_COL 0
#define DISPLAY_SPACE_COL 5
#define DISPLAY_THEMP_COL 12
// Sensors setup
#define KEY_ON_BATTERY_VOLTAGE 9 // To ensure real battery voltage
#define ENGINE_ON_BATTERY_VOLTAGE 12
#define MAX_PIN_VOLTAGE 5
#define LIMIT_PIN_VALUE 1024
#define VDR1 5100
#define VDR2 2000
// Light sensor setup
#define LIGTH_SENSOR_LIMIT_VALUE 150
#define LIGTH_SENSOR_SHIFT_VALUE 25
// Debug setup
#ifdef DEBUG_ENABLE
#define DEBUG(x) Serial.println(x)
#define BASE_DELAY 300
#define POWER_DOWN_PERIOD SLEEP_2048MS
#define POWEROFF_DELAY SECOND * 5 // 30 seconds
#endif
// Macros
#define EVERY_MS(x) \
static uint32_t tmr; \
bool flag = millis() - tmr >= (x); \
if (flag) tmr += (x); \
if (flag)
#define FOREVER_LOOP for (;;)
// RTC setup
// SDA A4
// SDL A5
#define RTC_NAME #DS3231;
MicroDS3231 rtc;
// LCD setup
// SDA A4
// SDL A5
#define LCD_ROWS 2
#define LCD_COLS 16
// include the library code:
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 5, en = 6, d4 = 7, d5 = 8, d6 = 9, d7 = 10;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// refresh time variable
uint32_t keyRemovedAt;
// sensor variables
int lightValue, voltageValue, themperatureValue;
float currentSensorVoltage, currentBatteryVoltage;
// Date and Time setup
DateTime dateTimeValue;
// System triggers
bool isKeyOn,
isKeyWasOn,
isLCDEnabled,
isRTCEnabled,
isEngineOn,
isBacklightOn,
isBacklightRequired;
// LCD symbols
static byte heart[8] = {
0x00,
0x0A,
0x1F,
0x1F,
0x0E,
0x04,
0x00,
0x00
};
#define FILTER_SPEED 0.01
GKalman voltageFilter(50, FILTER_SPEED);
void setBacklight();
void displayClock();
void displayStatus();
void keyOn();
void updateStatus();
void updateDisplay();
void printStatus();
void powerOn();
void powerOff();
void initLCD();
void initRTC();
// main code block
void setup() {
#ifdef DEBUG_ENABLE
Serial.begin(SERIAL_PORT);
#endif
lcd.begin(LCD_COLS, LCD_ROWS);
power.setSleepMode(POWERDOWN_SLEEP);
attachInterrupt(INTERRUPT_PIN, keyOn, RISING);
}
void loop() {
updateStatus();
updateDisplay();
}
void powerOn() {
delay(BASE_DELAY);
initRTC();
initLCD();
}
void powerOff() {
DEBUG("Power down for " + String(POWER_DOWN_PERIOD));
lcd.noDisplay();
isLCDEnabled = isRTCEnabled = false;
delay(BASE_DELAY);
power.sleep(POWER_DOWN_PERIOD);
}
void initLCD() {
if (!isLCDEnabled) {
lcd.display();
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.createChar(0, heart);
analogWrite(BACKLIGHT_PIN, 0);
isLCDEnabled = true;
}
}
void initRTC() {
if (!isRTCEnabled && !rtc.begin()) {
DEBUG("DS3231 not found");
#ifndef DEBUG_ENABLE
FOREVER_LOOP;
#endif
} else {
if (rtc.lostPower()) {
DEBUG("DS3231 lost power. Reset!");
if (__COUNTER__ == 0) {
SETUP_RTC_DATETIME;
}
}
isRTCEnabled = true;
}
}
void keyOn() {
power.wakeUp();
}
void readPins() {
voltageValue = analogRead(VOLTAGE_SENSOR_PIN);
dateTimeValue = rtc.getTime();
themperatureValue = rtc.getTemperature();
lightValue = analogRead(LIGHT_SENSOR_PIN);
voltageValue = voltageFilter.filtered(voltageValue);
}
void backlightSwith() {
bool isSensorUnderMin = (lightValue < (LIGTH_SENSOR_LIMIT_VALUE - LIGTH_SENSOR_SHIFT_VALUE));
bool isSensorOverMax = (lightValue > (LIGTH_SENSOR_LIMIT_VALUE + LIGTH_SENSOR_SHIFT_VALUE));
if (isSensorUnderMin || isSensorOverMax) {
isBacklightRequired = (lightValue < LIGTH_SENSOR_LIMIT_VALUE) && isKeyOn;
}
}
void updateStatus() {
isKeyWasOn = isKeyOn;
isKeyOn = digitalRead(DIGITAL_INTERRUPT_PIN);
if (isKeyOn) {
powerOn();
readPins();
currentSensorVoltage = (voltageValue * (float)MAX_PIN_VOLTAGE) / (float)LIMIT_PIN_VALUE;
currentBatteryVoltage = (currentSensorVoltage * ((float)VDR1 + (float)VDR2)) / (float)VDR2;
isEngineOn = currentBatteryVoltage > ENGINE_ON_BATTERY_VOLTAGE;
backlightSwith();
}
#ifdef DEBUG_ENABLE
EVERY_MS(SERIAL_DEBUG_OUTPUT_DELAY) {
printStatus();
}
#endif
if (!isKeyOn) {
EVERY_MS(POWEROFF_DELAY) {
powerOff();
}
}
}
void updateDisplay() {
if (isLCDEnabled) {
setBacklight();
displayClock();
displayStatus();
}
}
void setBacklight() {
if (isBacklightRequired && !isBacklightOn) {
analogWrite(BACKLIGHT_PIN, 50);;
isBacklightOn = true;
}
if (!isBacklightRequired && isBacklightOn) {
analogWrite(BACKLIGHT_PIN, 0);;
isBacklightOn = false;
}
}
void displayClock() {
char currentDateTime[16];
sprintf(
currentDateTime,
DT_FORMAT,
dateTimeValue.hour,
dateTimeValue.minute,
dateTimeValue.date,
dateTimeValue.month,
dateTimeValue.year);
EVERY_MS(BASE_DELAY) {
lcd.setCursor(DISPLAY_CLOCK_COL, DISPLAY_CLOCK_ROW);
lcd.print(currentDateTime);
}
}
void displayMessage() {
EVERY_MS(BASE_DELAY * 5) {
lcd.setCursor(DISPLAY_MESSAGE_COL, DISPLAY_STATUS_ROW);
lcd.print(char(0));
lcd.print(MESSAGE);
lcd.print(char(0));
}
}
void displaySensors() {
EVERY_MS(BASE_DELAY * 5) {
lcd.setCursor(DISPLAY_VOLTAGE_COL, DISPLAY_STATUS_ROW);
String voltage = String(currentBatteryVoltage, 1) + "V ";
lcd.print(voltage);
lcd.setCursor(DISPLAY_SPACE_COL, DISPLAY_STATUS_ROW);
lcd.print(" ");
lcd.setCursor(DISPLAY_THEMP_COL, DISPLAY_STATUS_ROW);
lcd.print(String(themperatureValue) + "\xDF" + "C");
}
}
void displayStatus() {
if (isKeyOn) {
if (!isEngineOn) {
displayMessage();
} else {
displaySensors();
}
} else {
displayMessage();
}
}
void printStatus() {
DEBUG("******** | " + (isRTCEnabled ? (rtc.getTimeString() + " | " + rtc.getDateString()) : "RTC DISABLED") + " | ********");
DEBUG("LCD: " + String(isLCDEnabled ? "ON" : "OFF"));
DEBUG("RTC: " + String(isRTCEnabled ? "ON" : "OFF"));
DEBUG("RTC temparature: " + String(themperatureValue) + "\xDF" + "C");
DEBUG("Light sensor: " + String(lightValue) + " // LCD backlight " + String(isBacklightRequired ? "required" : "not required") + " // Status: " + String(isBacklightOn ? "ON" : "OFF"));
DEBUG("Sensor voltage: " + String(currentSensorVoltage, 2) + "V" + " // " + "Source battery voltage: " + String(currentBatteryVoltage, 1) + "V");
DEBUG("Key status: " + String(isKeyOn ? "Key inserted" : "Key not inserted"));
DEBUG("Engine status: " + String(isEngineOn ? "Engine is running" : "Engine stopped"));
}