Загрузка данных
//I2C
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
enum CrosshairType {
CROSS_SMALL,
CROSS_FULL,
CROSS_DASHED
};
enum BatteryState {
BATTERY_FULL,
BATTERY_MEDIUM,
BATTERY_LOW,
BATTERY_EMPTY,
BATTERY_CHARGING
};
// Константы для индикатора батареи
#define BATTERY_X 100
#define BATTERY_Y 0
#define BATTERY_WIDTH 12
#define BATTERY_HEIGHT 6
#define BATTERY_CAP_WIDTH 2
#define BATTERY_CAP_HEIGHT 2
#define BATTERY_SEGMENT_WIDTH 2
// Параметры дисплея
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- ПИНЫ ---
#define BUTTON_PIN 2 // Пин для сброса таймера, смены перекрестья и ЗАПРОСА ИЗМЕРЕНИЯ (изменено на 5)
#define BATTERY_PIN 14 // Пин АЦП для батареи (A0)
#define OUTPUT_PIN 3 // Пин, на который придет единица по истечении времени
// --- ПАРАМЕТРЫ ---
#define TIMER_DURATION_MS 60000 // Время отчета в миллисекундах (1 минута, исправлено с 1 секунды для логики, или оставьте 1000 для 1 сек)
#define RESPONSE_TIMEOUT_MS 2000 // Таймаут ожидания ответа от дальномера (мс)
// --- ПАРАМЕТРЫ СЕГМЕНТОВ АКБ (АЦП и проценты) ---
// Зависимость заряда от напряжения нелинейна, поэтому используем кусочно-линейную аппроксимацию.
// Коэффициенты задаются как границы АЦП и соответствующие границы процента заряда для каждого сегмента.
// Значения АЦП приведены для INTERNAL (1.1В) с учетом типичного делителя напряжения. Откалибруйте под вашу схему.
// Сегмент 1: 4.2В - 4.0В (насыщение)
#define SEG1_ADC_MIN 800
#define SEG1_ADC_MAX 1100
#define SEG1_PCT_MIN 80
#define SEG1_PCT_MAX 100
// Сегмент 2: 4.0В - 3.7В (основной рабочий разряд)
#define SEG2_ADC_MIN 600
#define SEG2_ADC_MAX 800
#define SEG2_PCT_MIN 40
#define SEG2_PCT_MAX 80
// Сегмент 3: 3.7В - 3.5В (спадающая кривая)
#define SEG3_ADC_MIN 400
#define SEG3_ADC_MAX 600
#define SEG3_PCT_MIN 10
#define SEG3_PCT_MAX 40
// Сегмент 4: < 3.5В (глубокий разряд)
#define SEG4_ADC_MIN 0
#define SEG4_ADC_MAX 400
#define SEG4_PCT_MIN 0
#define SEG4_PCT_MAX 10
// --- ПЕРЕМЕННЫЕ СОСТОЯНИЯ ---
volatile CrosshairType currentCrosshair = CROSS_SMALL;
bool needsCrosshairUpdate = false;
BatteryState currentBattery = BATTERY_FULL;
bool needsBatteryUpdate = false;
uint16_t currentDistance = 0;
bool needsDistanceUpdate = false;
// Переменные для кнопки
int buttonstate = 0;
int lastbuttonstate = 0;
// Переменные таймера
unsigned long timerStartMillis = 0;
bool timerIsRunning = false;
bool timerNeedsReset = false;
bool outputPinIsHigh = false;
// Для сглаживания АЦП
const uint8_t SMOOTHING_WINDOW = 8;
uint16_t analogBuffer[SMOOTHING_WINDOW];
uint8_t bufferIndex = 0;
uint32_t analogSum = 0;
// Переменные для управления UART измерением
volatile bool requestMeasurement = false;
bool isWaitingForResponse = false;
unsigned long responseTimeout = 0;
void setup() {
Serial.begin(9600);
delay(500);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BATTERY_PIN, INPUT);
pinMode(OUTPUT_PIN, OUTPUT);
// Начальное состояние выхода — LOW
digitalWrite(OUTPUT_PIN, LOW);
outputPinIsHigh = false;
lastbuttonstate = digitalRead(BUTTON_PIN);
analogReference(INTERNAL);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, RISING);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
display.display();
for (uint8_t i = 0; i < SMOOTHING_WINDOW; i++) {
analogBuffer[i] = 0;
}
}
// Обработчик прерывания: сбрасывает таймер и инициирует запрос измерения
void buttonISR() {
//currentCrosshair = 1;
//needsCrosshairUpdate = true;
// Сбрасываем таймер при нажатии кнопки
timerNeedsReset = true;
// Запрашиваем измерение дальномера
requestMeasurement = true;
/*
buttonstate =digitalRead(BUTTON_PIN);
if (buttonstate != lastbuttonstate)
{
if(buttonstate == HIGH)
{
lastbuttonstate= buttonstate;
// Сбрасываем таймер при нажатии кнопки
timerNeedsReset = true;
// Запрашиваем измерение дальномера
requestMeasurement = true;
}
delay(50);
}*/
}
void loop() {
readBatteryADC();
// 2.1) Обработка отправки сообщения и ожидания ответа по UART
if (requestMeasurement) {
requestMeasurement = false; // Сбрасываем флаг запроса
// Команда для дальномера: 0x55 0xAA 0x88 0xFF 0xFF 0xFF 0xFF 0x84
uint8_t cmd[] = {0x55, 0xAA, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0x84};
Serial.write(cmd, 8);
// Запускаем режим ожидания ответа
isWaitingForResponse = true;
responseTimeout = millis();
}
// Если ожидаем ответ, вызываем функцию чтения (она выполнит уже написанные функции парсинга)
if (isWaitingForResponse) {
readDistanceMeter();
// Защита от зависания: если ответ не пришел за заданное время, выходим из режима ожидания
if (millis() - responseTimeout > RESPONSE_TIMEOUT_MS) {
isWaitingForResponse = false;
}
}
timerHandler(); // Обработка таймера
if ( needsBatteryUpdate || needsDistanceUpdate) {
drawUI();
needsCrosshairUpdate = false;
needsBatteryUpdate = false;
needsDistanceUpdate = false;
}
}
// --- ФУНКЦИЯ ТАЙМЕРА ---
void timerHandler() {
if (timerNeedsReset) {
timerNeedsReset = false;
timerIsRunning = false;
if (outputPinIsHigh) {
digitalWrite(OUTPUT_PIN, LOW);
outputPinIsHigh = false;
}
}
if (!timerIsRunning) {
timerStartMillis = millis();
timerIsRunning = true;
}
if (timerIsRunning) {
if (millis() - timerStartMillis >= TIMER_DURATION_MS) {
if (!outputPinIsHigh) {
digitalWrite(OUTPUT_PIN, HIGH);
outputPinIsHigh = true;
timerIsRunning = false;
}
}
}
}
// Функция чтения дальномера
void readDistanceMeter() {
if (Serial.available() >= 8) {
uint8_t response[8];
for (int i = 0; i < 8; i++) {
response[i] = Serial.read();
}
if (response[0] == 0x55 && response[1] == 0xAA && response[2] == 0x89) {
uint8_t STA = response[3];
if (STA == 1) {
uint8_t DIS_H = response[5];
uint8_t DIS_L = response[6];
uint16_t newDistance = (DIS_H << 8) | DIS_L;
if (newDistance != currentDistance) {
currentDistance = newDistance;
needsDistanceUpdate = true;
}
}
// Если пришел корректный пакет ответа, завершаем ожидание
isWaitingForResponse = false;
}
}
}
void readBatteryADC() {
analogSum -= analogBuffer[bufferIndex];
uint16_t newValue = analogRead(BATTERY_PIN);
analogBuffer[bufferIndex] = newValue;
analogSum += newValue;
bufferIndex++;
if (bufferIndex >= SMOOTHING_WINDOW) {
bufferIndex = 0;
}
uint16_t avgValue = analogSum / SMOOTHING_WINDOW;
uint8_t batteryPct = 0;
// Кусочно-линейная аппроксимация с использованием заданных в define коэффициентов (границ)
if (avgValue >= SEG1_ADC_MAX) {
batteryPct = 100;
} else if (avgValue >= SEG1_ADC_MIN) {
batteryPct = map(avgValue, SEG1_ADC_MIN, SEG1_ADC_MAX, SEG1_PCT_MIN, SEG1_PCT_MAX);
} else if (avgValue >= SEG2_ADC_MIN) {
batteryPct = map(avgValue, SEG2_ADC_MIN, SEG2_ADC_MAX, SEG2_PCT_MIN, SEG2_PCT_MAX);
} else if (avgValue >= SEG3_ADC_MIN) {
batteryPct = map(avgValue, SEG3_ADC_MIN, SEG3_ADC_MAX, SEG3_PCT_MIN, SEG3_PCT_MAX);
} else if (avgValue >= SEG4_ADC_MIN) {
batteryPct = map(avgValue, SEG4_ADC_MIN, SEG4_ADC_MAX, SEG4_PCT_MIN, SEG4_PCT_MAX);
} else {
batteryPct = 0;
}
// Преобразуем рассчитанный процент обратно в состояние батареи для совместимости с UI
BatteryState newState;
if (batteryPct > 80) {
newState = BATTERY_FULL;
} else if (batteryPct > 40) {
newState = BATTERY_MEDIUM;
} else if (batteryPct > 10) {
newState = BATTERY_LOW;
} else if (batteryPct > 0) {
newState = BATTERY_EMPTY;
} else {
newState = BATTERY_CHARGING; // Оставлено для совместимости, можно доработать при наличии пина зарядки
}
if (newState != currentBattery) {
currentBattery = newState;
needsBatteryUpdate = true;
}
}
// --- Отрисовка ---
void drawUI() {
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.print(F("D: "));
display.print(currentDistance);
display.println(F(" mm"));
drawCrosshair(display, currentCrosshair);
drawBattery(display, currentBattery);
display.display();
}
void drawCrosshair(Adafruit_SSD1306 &display, CrosshairType type) {
int16_t centerX = display.width() / 2;
int16_t centerY = display.height() / 2;
switch (type) {
case CROSS_SMALL:
display.drawLine(centerX - 2, centerY, centerX + 2, centerY, SSD1306_WHITE);
display.drawLine(centerX, centerY - 2, centerX, centerY + 2, SSD1306_WHITE);
break;
/*
case CROSS_FULL:
display.drawLine(centerX - 2, centerY, centerX + 2, centerY, SSD1306_WHITE);
display.drawLine(centerX, centerY - 2, centerX, centerY + 2, SSD1306_WHITE);
break;
case CROSS_DASHED:
display.drawLine(centerX - 2, centerY, centerX + 2, centerY, SSD1306_WHITE);
display.drawLine(centerX, centerY - 2, centerX, centerY + 2, SSD1306_WHITE);
break;
*/
case CROSS_FULL:
display.drawLine(0, centerY, display.width() - 1, centerY, SSD1306_WHITE);
display.drawLine(centerX, 0, centerX, display.height() - 1, SSD1306_WHITE);
break;
case CROSS_DASHED:
display.drawLine(centerX - 2, centerY, centerX + 2, centerY, SSD1306_WHITE);
display.drawLine(centerX, centerY - 2, centerX, centerY + 2, SSD1306_WHITE);
for (int16_t x = centerX + 3; x < display.width(); x += 4) {
display.drawLine(x, centerY, x + 1, centerY, SSD1306_WHITE);
}
for (int16_t x = centerX - 3; x >= 0; x -= 4) {
display.drawLine(x, centerY, x + 1, centerY, SSD1306_WHITE);
}
for (int16_t y = centerY + 3; y < display.height(); y += 4) {
display.drawLine(centerX, y, centerX, y + 1, SSD1306_WHITE);
}
for (int16_t y = centerY - 3; y >= 0; y -= 4) {
display.drawLine(centerX, y, centerX, y + 1, SSD1306_WHITE);
}
break;
}
}
void drawBattery(Adafruit_SSD1306 &display, BatteryState state) {
int16_t capX = BATTERY_X + BATTERY_WIDTH;
int16_t capY = BATTERY_Y + (BATTERY_HEIGHT / 2) - (BATTERY_CAP_HEIGHT / 2);
display.drawRect(BATTERY_X, BATTERY_Y, BATTERY_WIDTH, BATTERY_HEIGHT, SSD1306_WHITE);
display.fillRect(capX, capY, BATTERY_CAP_WIDTH, BATTERY_CAP_HEIGHT, SSD1306_WHITE);
uint8_t segments = 0;
bool isCharging = false;
if (state == BATTERY_CHARGING) {
isCharging = true;
segments = 3;
} else {
switch (state) {
case BATTERY_FULL: segments = 5; break;
case BATTERY_MEDIUM: segments = 3; break;
case BATTERY_LOW: segments = 1; break;
case BATTERY_EMPTY: segments = 0; break;
}
}
if (segments > 0) {
display.fillRect(BATTERY_X + 1, BATTERY_Y + 1, BATTERY_SEGMENT_WIDTH * segments, BATTERY_HEIGHT - 2, SSD1306_WHITE);
}
if (isCharging) {
display.drawLine(BATTERY_X + 2, BATTERY_Y + 4, BATTERY_X + 4, BATTERY_Y + 4, SSD1306_WHITE);
display.drawLine(BATTERY_X + 4, BATTERY_Y + 4, BATTERY_X + 1, BATTERY_Y + 1, SSD1306_WHITE);
display.drawLine(BATTERY_X + 1, BATTERY_Y + 1, BATTERY_X + 3, BATTERY_Y + 3, SSD1306_WHITE);
display.drawLine(BATTERY_X + 3, BATTERY_Y + 3, BATTERY_X + 2, BATTERY_Y + 5, SSD1306_WHITE);
}
}
//SPI
// Макрос ответа. data[5] и data[6] будут перезаписаны значением i
#define response_laser {0x55, 0xAA, 0x89, 1, 0xFF, 0x00, 0x00, 0x84}
// Буфер для приема входящего сообщения
uint8_t incomingPacket[8];
// Счетчик принятых байт
uint8_t bytesRead = 0;
// Флаг, сигнализирующий о необходимости отправить ответ
bool packetReceived = false;
// Массив для отправки (создаем переменную из макроса)
uint8_t data[] = response_laser;
void setup() {
Serial.begin(9600);
delay(200);
}
void loop() {
// 1. Функция считывания сообщения
readSerialPacket();
// 2. Если валидный пакет получен — отправляем ответ
if (packetReceived) {
// Останавливаем бесконечную отправку из исходного примера
// (если нужно, чтобы ответ ушел только один раз на один запрос)
// Пример: отправка пакета с текущим значением i (оставим логику из вашего примера)
// Для демонстрации отправим один раз со значением 1000 (0x03E8)
uint16_t i = 1000;
// data[6] — младший байт (LSB)
data[6] = i & 0xFF;
// data[5] — старший байт (MSB)
data[5] = (i >> 8) & 0xFF;
Serial.write(data, sizeof(data));
// Сбрасываем флаг, чтобы не отправлять ответ циклично в loop()
packetReceived = false;
}
}
/**
* Функция считывания пакета из Serial.
* Пакет считается принятым, если в буфере накопилось 8 байт
* и они соответствуют ожидаемому заголовку и контрольной сумме.
*/
void readSerialPacket() {
// Читаем все доступные в буфере байты
while (Serial.available() > 0) {
// Читаем очередной байт
uint8_t incomingByte = Serial.read();
// Накапливаем байты в буфер
if (bytesRead < 8) {
incomingPacket[bytesRead] = incomingByte;
bytesRead++;
}
// Если накоплено 8 байт — проверяем пакет
if (bytesRead == 8) {
// Проверка заголовка (0x55, 0xAA, 0x88), индекса (1) и контрольной суммы (0x85)
if (incomingPacket[0] == 0x55 &&
incomingPacket[1] == 0xAA &&
incomingPacket[2] == 0x88 &&
incomingPacket[3] == 1 &&
incomingPacket[4] == 0xFF &&
incomingPacket[5] == 0xFF &&
incomingPacket[6] == 0xFF &&
incomingPacket[7] == 0x84) {
// Пакет валиден, устанавливаем флаг для отправки ответа
packetReceived = true;
}
// Сбрасываем счетчик для приема следующего сообщения
// (даже если пакет невалидный, чтобы буфер не завис)
bytesRead = 0;
}
}
}