1/100th of second momentary contact timer

I used chatGPT. It was some work and some time to get this result, which I have tested but not to the extent it shoukd be tested.

Work of a kind different to programming. Way less real time from start to finish, with the sketch incorporating a number of enhancements I never thought of thinking about. I can say I also thought of a few things to make good better, like waving a magic wand or I guess telling the programmers you supervise to make a few changes. I will never know that kind of power for realz.

There were a few missteps. Amusing. Ordinarily I wouldn't waste time debugging an AI sketch, but this was quite plausible at glance and I felt it was worth trying to salvage.

I'm on the way to no longer in the lab. I will post my first prompt and the code when I get chance. A few of the things the AI didn't get right away are interesting as well.

Play with the bounce analyzer/contact timer here:

TBH if I wrote this it would be much smaller and do only a half-assed job of the matter.

Fun: in the wokwi you can turn off bouncing at the
pushbutton level; it made for a test of the essential logic which after all should not care whether the switch actually bounces or not.

a7

Hey Jim,

I won't get to my storage locker for several weeks... looking forward to running the various code suggestions.

This is a great group!

Thanks for the input!
R

Hey A7,

Didn't know there was a simulator available.
You tested bounce... but if the entire code was fed in would it test AI generated code?

Looking forward to getting my supplies out of storage later this month!

Best,
R

The simulator compiles and runs complete sketches. It has no way of knowing who (or what) wrote them.

The sketch in the simulation measures the bouncing periods of instability, and the time the switch was closed and stable.

I copy/pasted the code into the code panel. I did make a few edits of a purely aesthetic kind.

a7

Presumably the problems are:

  • Most button libraries and debounce algorithms tend to wait till the END of bouncing to report a switch state change.
  • Trying to measure switch state durations that are in the same ballpark as bounce times is always going to be ... annoying and/or ambiguous.

A logic-analyzer or scope would be a good start starting point.
For 0.01s timeframes, you can easily make a single-channel scope or logic analyzer using an AVR Arduino.
It should be sufficient to use "attachInterrupt()" to a pin, and log (to an array, for "later" printing) micros()... attachInterrupt() should have a latency of 10us ot so, and micros() has a resolution of 4us. It won't be perfect, but it should be close enough.

The debounce/contact timer from #21:

Code
/*
  Button-bounce timing + bounce counting (UNO)
  ------------------------------------------------------------
  Goal:
    - Time the *bounce span* (first edge -> last edge) using micros()
    - Count bounces (edges after the first) on each press and release
    - Interrupt on D2 (CHANGE) feeds an FSM in loop()
    - Avoid "false open/close" paths: all chatter is bounce
    - Use settle windows (15 ms each) to decide when bouncing has ended
    - Build report text with snprintf into buffers (no inline Serial prints)
    - When the FSM transitions back to OPEN, print the entire report for
      that full sequence (OPEN -> OTWUP -> CLOSED -> OTWDOWN -> OPEN)

  Wiring:
    - Pushbutton between D2 and GND (INPUT_PULLUP)
    - LEDs:
        D5 = RED    (OPEN)
        D6 = YELLOW (OTWUP / OTWDOWN)
        D7 = GREEN  (CLOSED)

  Timing:
    - UP_SETTLE_US, DOWN_SETTLE_US: quiet time after the LAST edge
    - Reported bounce_us does NOT include the settle tail:
        bounce_us = lastEdgeUs - firstEdgeUs
      (Your ~2 ms switch should show ~2000 us here.)
*/

# include <Arduino.h>
# include <stdarg.h>

// ---------------- pins ----------------
const byte BTN    = 2;  // INT0 on UNO (PD2)
const byte LED_R  = 5;
const byte LED_Y  = 6;
const byte LED_G  = 7;

// -------------- timing ----------------
const unsigned long UP_SETTLE_US   = 15000UL;  // 15 ms after last edge
const unsigned long DOWN_SETTLE_US = 15000UL;  // 15 ms after last edge

// -------------- states ----------------
enum { OPEN, OTWUP, CLOSED, OTWDOWN };

// -------- ISR -> loop ring buffer -----
// 128 events costs ~640 bytes RAM (timestamps + levels), reasonable on UNO.
constexpr int EVT_BUF_SZ = 128;
volatile unsigned long evt_t[EVT_BUF_SZ];
volatile uint8_t       evt_level[EVT_BUF_SZ];
volatile uint8_t       evt_head = 0; // next write index
volatile uint8_t       evt_tail = 0; // next read index
volatile uint16_t      evt_overflow = 0;

// ---------- reporting buffers ----------
constexpr uint8_t  REP_MAX_LINES = 5;
constexpr uint16_t REP_LINE_LEN  = 96;

char    rep[REP_MAX_LINES][REP_LINE_LEN];
uint8_t repN = 0;

static void repClear() {
  repN = 0;
  for (uint8_t i = 0; i < REP_MAX_LINES; i++) rep[i][0] = '\0';
}

static void repAdd(const char *fmt, ...) {
  if (repN >= REP_MAX_LINES) return;
  va_list ap;
  va_start(ap, fmt);
  vsnprintf(rep[repN], REP_LINE_LEN, fmt, ap);
  va_end(ap);
  repN++;
}

static void repPrintAll() {
  for (uint8_t i = 0; i < repN; i++) {
    Serial.println(rep[i]);
  }
  Serial.println("");
}

// ------------- small helpers ----------
static void setLEDs(bool r, bool y, bool g) {
  digitalWrite(LED_R, r ? HIGH : LOW);
  digitalWrite(LED_Y, y ? HIGH : LOW);
  digitalWrite(LED_G, g ? HIGH : LOW);
}

static void setLEDsForState(byte s) {
  switch (s) {
    case OPEN:    setLEDs(true,  false, false); break; // red
    case OTWUP:   setLEDs(false, true,  false); break; // yellow
    case CLOSED:  setLEDs(false, false, true ); break; // green
    case OTWDOWN: setLEDs(false, true,  false); break; // yellow
  }
}

static inline uint8_t next_idx(uint8_t i) { return (uint8_t)((i + 1) & (EVT_BUF_SZ - 1)); }
static_assert((EVT_BUF_SZ & (EVT_BUF_SZ - 1)) == 0, "EVT_BUF_SZ must be a power of two");

// Fast read for D2 on UNO (PD2). INPUT_PULLUP => HIGH=open, LOW=pressed.
static inline uint8_t readBtnFast() {
  return (PIND & _BV(PD2)) ? HIGH : LOW;
}

// -------------- ISR -------------------
void isrButtonChange() {
  unsigned long t = micros();
  uint8_t level = readBtnFast();

  uint8_t h  = evt_head;
  uint8_t nh = next_idx(h);

  if (nh == evt_tail) {
    evt_overflow++;
  } else {
    evt_t[h]     = t;
    evt_level[h] = level;
    evt_head     = nh;
  }
}

static bool popEvent(unsigned long &t, uint8_t &level) {
  noInterrupts();
  if (evt_head == evt_tail) {
    interrupts();
    return false;
  }
  uint8_t idx = evt_tail;
  t = evt_t[idx];
  level = evt_level[idx];
  evt_tail = next_idx(idx);
  interrupts();
  return true;
}

static uint16_t takeOverflowCount() {
  noInterrupts();
  uint16_t ovf = evt_overflow;
  evt_overflow = 0;
  interrupts();
  return ovf;
}

// -------- FSM bookkeeping -------------
byte state = OPEN;

// Current episode info (valid in OTWUP/OTWDOWN)
unsigned long episodeStartUs   = 0;  // first edge timestamp
unsigned long lastEdgeUs       = 0;  // most recent edge timestamp
unsigned long settleUs         = 0;  // settle window for this episode
unsigned int  bounceCount      = 0;  // edges after the first edge

// Optional: for context in the report
unsigned long seqStartUs       = 0;  // when the sequence began (first press edge)
unsigned int  seqPressNum      = 0;  // sequence counter

static void startEpisode(unsigned long tUs, unsigned long settlePeriodUs) {
  episodeStartUs = tUs;
  lastEdgeUs     = tUs;
  settleUs       = settlePeriodUs;
  bounceCount    = 0;
}

static bool episodeSettled(unsigned long nowUs) {
  return (unsigned long)(nowUs - lastEdgeUs) >= settleUs;
}

void setup() {
  pinMode(BTN, INPUT_PULLUP);

  pinMode(LED_R, OUTPUT);
  pinMode(LED_Y, OUTPUT);
  pinMode(LED_G, OUTPUT);

  Serial.begin(115200);
  delay(20);

  byte pinNow = (byte)digitalRead(BTN);
  state = (pinNow == HIGH) ? OPEN : CLOSED;
  setLEDsForState(state);

  repClear();
  repAdd("event\tbounce_us\tquiet_us\tbounces\toverflow");

  attachInterrupt(digitalPinToInterrupt(BTN), isrButtonChange, CHANGE);
}

void loop() {
  unsigned long t;
  uint8_t level;

  // Drain all pending events quickly (no Serial printing here).
  while (popEvent(t, level)) {
    switch (state) {
      case OPEN:
        // Start of a press episode: first contact conducts => level LOW.
        if (level == LOW) {
          seqPressNum++;
          seqStartUs = t;
          repClear();
          repAdd("seq\t%u\tstart_us\t%lu", seqPressNum, seqStartUs);
          repAdd("event\tbounce_us\tbounces\toverflow");

          state = OTWUP;
          setLEDsForState(state);
          startEpisode(t, UP_SETTLE_US);
        }
        break;

      case OTWUP:
        // Any change is bounce. Count edges after the first.
        if (t != episodeStartUs) bounceCount++;
        lastEdgeUs = t;
        break;

      case CLOSED:
        // Start of a release episode: first release opens => level HIGH.
        if (level == HIGH) {
          state = OTWDOWN;
          setLEDsForState(state);
          startEpisode(t, DOWN_SETTLE_US);
        }
        break;

      case OTWDOWN:
        if (t != episodeStartUs) bounceCount++;
        lastEdgeUs = t;
        break;
    }
  }

  // Check settle (quiet tail reached) and finalize episode(s).
  unsigned long nowUs = micros();

  if (state == OTWUP && episodeSettled(nowUs)) {
    // Press episode done -> declare CLOSED
    unsigned long bounceUs = (unsigned long)(lastEdgeUs - episodeStartUs);
    unsigned long quietUs  = (unsigned long)(nowUs - lastEdgeUs);
    uint16_t ovf = takeOverflowCount();

    repAdd("PRESS\t%lu\t%u\t%u", bounceUs, bounceCount, ovf);

    state = CLOSED;
    setLEDsForState(state);
    // Note: keep report buffers; we'll print when we return to OPEN.
  }
  else if (state == OTWDOWN && episodeSettled(nowUs)) {
    // Release episode done -> declare OPEN
    unsigned long bounceUs = (unsigned long)(lastEdgeUs - episodeStartUs);
    unsigned long quietUs  = (unsigned long)(nowUs - lastEdgeUs);
    uint16_t ovf = takeOverflowCount();

    repAdd("RELEASE\t%lu\t%u\t%u", bounceUs, bounceCount, ovf);

    state = OPEN;
    setLEDsForState(state);

    // Sequence complete: print the whole report now.
    repAdd("seq_end_us\t%lu\telapsed_us\t%lu", nowUs, (unsigned long)(nowUs - seqStartUs));
    repPrintAll();
  }

  // No delay(): we want to drain the ring buffer ASAP to avoid overflow.
}


First prompt:

With the first attempt I observed transitions from RISING to OPEN and from FALLING to CLOSED. Fortunately chatGPT commented the code. I wrote

From then on it was just enhancements mostly to grab as much of the activity as possible. chatGPT seemed obsessed; I kept letting it do more and more.

AI idea: create a ring buffer to grab transitions with times as quickly as possible.

a7 idea: buffer all printing and report after the button has been released.

And so forth.

RISING and FALLING are define elsewhere, we switched them to OTWUP and OTWDOWN, "on the way". chatGPT didn't notice that.

It also carved off many 96 character lines for buffering, I pointed out that there were only ever 5 lines pending. I was surprised at that error.

a7

Actually it is simple, connect a resistor so it conducts current then connect a oscilloscope across the contacts and cycle. You can then use the cursors to measure the time once you get a decent signal triggered.

Hi Gil,
What I didn't mention was that the solution needs to be 100% portable and displays the timing on it's own micro display.
My bad... but in a lab situation... I love your solution -- awesome!
Best,
R

They make battery powered scopes for under $100.00. Also the scope will show each subsequent bounce as there are usually several, a counting system will usually only get one.

I'm not sure what you mean by a counting system.

The code I posted counts the bounces, at some resolution.

A logic analyzer or oscilloscope would do a better job, but would not fit well in a "good enough" portable instrument based on an Arduino board.

And it is the duration of the stable contact that is of interest anyway. The code I posted does do a good enough job of measuring the bounce period if it does miss transitions during the periods of instability.

a7