/*
  6-channel digital analyzer (D6..D11) for SSD1306 OLED
  D3: cycle sample interval options (microseconds)
  Non-blocking capture, no infinite waits (prevents "freeze")
  Optimized for Arduino Nano
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <avr/io.h>   // for direct PINx reading

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3D

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// digital inputs mapped to D6..D11
const uint8_t digitalPins[6] = {6, 7, 8, 9, 10, 11};

// button to change sample interval
#define BUTTON_SAMPLE 3

// sample intervals in microseconds (small -> fast sampling)
const unsigned int sampleIntervals[] = {10, 50, 100, 200, 500, 1000, 2000, 5000};
const uint8_t NUM_INTERVALS = sizeof(sampleIntervals) / sizeof(sampleIntervals[0]);
uint8_t sampleIntervalIndex = 3; // start with a moderate value (200 us)

uint8_t sample[128];  // 128 samples of 6-bit signals

// button debounce
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 200;

void setup() {
  Wire.begin();
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    // if display init fails, hang here (or you can handle differently)
    for (;;);
  }

  // configure inputs
  for (uint8_t i = 0; i < 6; i++) pinMode(digitalPins[i], INPUT);
  pinMode(BUTTON_SAMPLE, INPUT_PULLUP);

  display.clearDisplay();
  display.display();
}

// ---- main loop ----
void loop() {
  handleSampleButton();
  captureDigitalFast(sampleIntervals[sampleIntervalIndex]);
  drawFrame(sampleIntervals[sampleIntervalIndex]);
  display.display();
  // small pause between frames so OLED has time (optional)
  delay(30);
}

// ---- handle D3 button: cycle sample interval ----
void handleSampleButton() {
  bool reading = digitalRead(BUTTON_SAMPLE);
  if (reading == LOW && lastButtonState == HIGH && (millis() - lastDebounceTime) > debounceDelay) {
    sampleIntervalIndex++;
    if (sampleIntervalIndex >= NUM_INTERVALS) sampleIntervalIndex = 0;
    lastDebounceTime = millis();
  }
  lastButtonState = reading;
}

// ---- capture digital channels quickly (direct port read) ----
// mapping of bits in returned byte:
// bit0 = D6 (PD6), bit1 = D7 (PD7), bit2 = D8 (PB0), bit3 = D9 (PB1), bit4 = D10 (PB2), bit5 = D11 (PB3)
void captureDigitalFast(unsigned int interval_us) {
  for (int x = 0; x < 128; x++) {
    uint8_t pd = PIND;
    uint8_t pb = PINB;
    uint8_t bits = ((pd >> 6) & 0x03) | ((pb & 0x0F) << 2);
    sample[x] = bits;
    if (interval_us > 0) delayMicroseconds(interval_us);
  }
}

// ---- draw frame and signals ----
void drawFrame(unsigned int interval_us) {
  display.clearDisplay();

  // border
  display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);

  // small tick markers top & bottom
  for (int i = 20; i < SCREEN_WIDTH; i += 20) {
    display.drawPixel(i, 1, SSD1306_WHITE);
    display.drawPixel(i, SCREEN_HEIGHT - 2, SSD1306_WHITE);
  }

  // header: sample interval
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(50, 0);
  display.print("dt=");
  display.print(interval_us);
  display.print("us");

  // channel labels left and horizontal positioning
  // allocate 6 rows, vertical spacing tuned to fit 64px height
  for (int ch = 0; ch < 6; ch++) {
    display.setCursor(1, 6 + ch * 9);
    display.print("CH");
    display.print(ch+1);
  }

  // draw signals: for x=0..126, draw line from sample[x] to sample[x+1]
  for (int x = 0; x < 127; x++) {
    uint8_t s1 = sample[x];
    uint8_t s2 = sample[x + 1];

    for (int ch = 0; ch < 6; ch++) {
      int ybase = 10 + ch * 9;     // center for channel ch
      int yLow  = ybase + 3;       // low level
      int yHigh = ybase - 3;       // high level

      int y1 = (s1 & (1 << ch)) ? yHigh : yLow;
      int y2 = (s2 & (1 << ch)) ? yHigh : yLow;

      display.drawLine(x, y1, x + 1, y2, SSD1306_WHITE);
    }
  }
}
