// Лабораторна 2: Ring buffer + timestamping + overflow policies
// Плата: Arduino Uno (Wokwi)
// Сенсор: потенціометр на A0
#include <Arduino.h>
enum OverflowPolicy {
DROP_NEWEST = 0,
DROP_OLDEST = 1,
SKIP_SAMPLING = 2
};
// ====== Налаштування (можна міняти для варіантів) ======
static const uint16_t BUF_CAP = 128; // місткість буфера
static const uint32_t SAMPLE_PERIOD_US = 4000; // 2000 us = 500 Гц
static const OverflowPolicy POLICY = SKIP_SAMPLING;
static const uint8_t PIN_SENSOR = A0;
static const uint8_t PIN_LOAD_BTN = 2; // кнопка перевантаження (опційно)
// ====== Структура зразка ======
struct Sample {
uint32_t seq;
uint32_t t_us; // час зняття, micros()
uint16_t value; // analogRead 0..1023
};
// ====== Ring buffer ======
struct RingBuffer {
Sample data[BUF_CAP];
uint16_t head = 0; // куди писати
uint16_t tail = 0; // звідки читати
uint16_t count = 0; // скільки елементів
};
RingBuffer rb;
static uint32_t g_seq = 0;
static uint32_t g_dropped = 0;
static uint32_t g_overflows = 0;
// ====== helpers ======
uint16_t rb_occupancy() { return rb.count; }
bool rb_empty() { return rb.count == 0; }
bool rb_full() { return rb.count == BUF_CAP; }
void rb_push_drop_newest(const Sample &s) {
if (rb_full()) {
g_overflows++;
g_dropped++; // відкинули новий
return;
}
rb.data[rb.head] = s;
rb.head = (rb.head + 1) % BUF_CAP;
rb.count++;
}
void rb_push_drop_oldest(const Sample &s) {
if (rb_full()) {
// викидаємо найстаріший (tail) і записуємо новий
g_overflows++;
g_dropped++; // рахуємо втрату як “втратили найстаріший”
rb.tail = (rb.tail + 1) % BUF_CAP;
rb.count--; // звільнили місце
}
rb.data[rb.head] = s;
rb.head = (rb.head + 1) % BUF_CAP;
rb.count++;
}
bool rb_pop(Sample &out) {
if (rb_empty()) return false;
out = rb.data[rb.tail];
rb.tail = (rb.tail + 1) % BUF_CAP;
rb.count--;
return true;
}
// ====== Producer: “ISR-подібна” подія вибірки ======
void produce_sample() {
// Опційний backpressure: якщо повно — пропустити вимір
if (POLICY == SKIP_SAMPLING && rb_full()) {
g_overflows++;
g_dropped++; // пропуск виміру = втрата даних
return;
}
Sample s;
s.seq = g_seq++;
s.t_us = micros();
s.value = analogRead(PIN_SENSOR);
if (POLICY == DROP_NEWEST) rb_push_drop_newest(s);
else if (POLICY == DROP_OLDEST) rb_push_drop_oldest(s);
else rb_push_drop_newest(s); // fallback
}
// ====== Consumer: обробка / логування ======
void consume_some(uint8_t max_items) {
uint32_t now = micros();
for (uint8_t i = 0; i < max_items; i++) {
Sample s;
if (!rb_pop(s)) break;
uint32_t age_us = now - s.t_us;
// Лог у форматі CSV: t_us,seq,value,age_us,occ,dropped,overflows
// (зручно копіювати в Excel/Google Sheets для графіків)
Serial.print(now); Serial.print(",");
Serial.print(s.seq); Serial.print(",");
Serial.print(s.value); Serial.print(",");
Serial.print(age_us); Serial.print(",");
Serial.print(rb_occupancy()); Serial.print(",");
Serial.print(g_dropped); Serial.print(",");
Serial.println(g_overflows);
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_SENSOR, INPUT);
pinMode(PIN_LOAD_BTN, INPUT_PULLUP); // якщо кнопки нема — можна лишити, не заважає
Serial.println("t_us,seq,value,age_us,occ,dropped,overflows");
}
void loop() {
static uint32_t next_sample_us = 0;
uint32_t now = micros();
// 1) Producer: без delay, по таймеру (мікросекунди)
while ((int32_t)(now - next_sample_us) >= 0) {
next_sample_us += SAMPLE_PERIOD_US;
produce_sample();
}
// 2) Consumer: беремо порцію елементів
// Нормальний режим: обробляємо швидко
// Режим перевантаження: штучно “гальмуємо” consumer
bool overload = (digitalRead(PIN_LOAD_BTN) == LOW); // натиснув кнопку => overload
if (overload) {
// імітація важкої обробки / повільного I/O
// (під час цього producer все одно продовжує накопичувати дані в буфері)
delayMicroseconds(6000); // підбирай, щоб спровокувати переповнення
consume_some(2);
} else {
consume_some(8);
}
// 3) Дрібний “відпочинок” (не обов’язково), щоб не засмічувати Serial надто швидко
delay(1);
}