Option 2: SPI-Based Debugging
-
Once we have the three 74HC595 shift registers wired, we connect the logic analyzer as seen in the previous post.
- After a few years of assembling the circuit on a solderless breadboard, a prototyping board becomes more appropriate.

-
To use the Interface's API, we need to make a few changes to our application (sketch) code.
- In the same directory as our sketch, add the two .h Tabs:
- DebugAnalyzerConfig.h
- LogicAnalyzerDebug.h
- We need to make a few changes to our .ino file as seen in the sketch below.
-
The original ZIP file in Post #1 has all the files below plus two documentation Tabs.
-
You do not need to define a State Machine if you are not using one.
-
When your application code includes a State Machine, use the enum format as seen in the example sketch below.
- Use the SET_STATE(SystemState next, unsigned long duration) function to change State and to set the State TIMER duration.
- SET_STATE(STATE1, LED0_ON_Time);
The next State is STATE1 set TIMER to LED0_ON_Time.
-
If we upload the example sketch and start the logic analyzer, we should see a similar analyzer screen:
Application (Sketch) code:
//
//================================================^================================================
// Non-Blocking State Machine with Logic Analyzer Interface
//================================================^================================================
//
// File: LogicAnalyzerVersion2.5.ino
//
// URL
//
// LarryD
//
// Version YY/MM/DD Comments
// ======= ======== ========================================================================
// 2.4 26/04/04 Running code
// 2.5 26/04/10 Added an Interface "Pulse Generation Macro"
//
//
//
// • Documentation is found in the ReadMe and Preamble Tabs.
//
// Sketch Overview:
// -------------------------------------
//
// MyProject
// |
// +-- MyProject.ino (Application / user sketch)
// | |
// | +-- StateMachine (Application logic)
// |
// +-- Preamble (Project notes / overview)
// |
// +-- ReadMe (Documentation)
// |
// +-- DebugAnalyzerConfig.h (Configuration for Debug API)
// | |
// | +-- EVENTs (Event definitions)
// |
// +-- LogicAnalyzerDebug.h (Public Debug API)
//
//
// Integration Notes:
// -------------------------------------
//
// • Lines marked with ◄◄◄◄◄ must be added to an existing sketch.
//
// • Sections between ▼▼▼▼▼ and ▲▲▲▲▲ contain required framework code.
//
// • Lines marked <---<<<< D e b u g are examples of debug usage.
//
//
// START ▼▼▼▼▼▼▼▼▼▼▼▼▼
//▼ When comment out disable debugging.
#define DEBUG_ANALYZER
//================================================
// State decoding:
// When defined: Reg1–Reg5 carry STATE/EVENT encoding.
// EVENT temporarily overrides STATE bits (Reg1–Reg5),
// then restores previous state after MARK.
// This allows precise event tagging without losing state context.
//
//
//▼ When commented out, Reg1–Reg5 act as a regular 5-bit data bus.
#define DEBUG_DECODE_STATE
//===============================
#ifdef DEBUG_ANALYZER
#include "DebugAnalyzerConfig.h"
//===============================
#else
// When we are not using Interface debug code macros, undefine them, i.e. they are not compiled.
// Pulse Generation.
#define DEBUG_PULSE_COUNT_START(bit, count)
#define DEBUG_PULSE_COUNT_SERVICE()
#define DEBUG_PULSE_COUNT_BUSY()
#define DEBUG_PULSE_COUNT_CANCEL()
// Bit manipulation.
#define DEBUG_MARK()
#define DEBUG_INIT()
#define DEBUG_TOGGLE(x)
#define DEBUG_HIGH(x)
#define DEBUG_LOW(x)
#define DEBUG_SET_REG(bit, value)
#define DEBUG_FAST_PULSE(x)
#define DEBUG_EVENT_FAST(EVENT)
#define DEBUG_EVENT(EVENT)
// Bit delayed manipulation.
#define DEBUG_DELAYED_MARK(bit, delay_us)
#define DEBUG_DELAYED_HIGH(bit, delay_us)
#define DEBUG_DELAYED_LOW(bit, delay_us)
#define DEBUG_DELAYED_PULSE(bit, delay_us, width_us)
#define DEBUG_DELAYED_SERVICE()
// Debug LEDs.
#define DEBUG_LED_ON(bit)
#define DEBUG_LED_OFF(bit)
#define DEBUG_LED_PULSE(bit,duration)
#define DEBUG_LEDsService()
#define DEBUG_INTERFACE_HEARTBEAT(rate)
#endif
// END ▲▲▲▲▲▲▲▲▲▲▲▲▲
//================================================^================================================
// A p p l i c a t i o n c o d e
//================================================^================================================
//===============================
// State Machine
//===============================
// NOTE: State Machine typically use 0x00–0x0F.
// When using the logic analyzer, EVENTs use (0x10–0x1F).
//
// For clarity, it helps to keep "STATE##_" in the variable name.
enum SystemState : byte
{
// State 43210 Decimal Change names as needed:
STATE0 = 0b00000, // 00 e.g. STATE0_POWER_UP
STATE1 = 0b00001, // 01 STATE1_WallSwitchPushedUp
STATE2 = 0b00010, // 02 STATE2_GarageDoorGoingUp
STATE3 = 0b00011, // 03 STATE3_WallSwitchPushedDown
STATE4 = 0b00100, // 04 STATE4_GarageDoorGoingDown
STATE5 = 0b00101, // 05 etc.
STATE6 = 0b00110, // 06
STATE7 = 0b00111, // 07
STATE8 = 0b01000, // 08
STATE9 = 0b01001, // 09
STATE10 = 0b01010, // 10
STATE11 = 0b01011, // 11
STATE12 = 0b01100, // 12
STATE13 = 0b01101, // 13
STATE14 = 0b01110, // 14
STATE15 = 0b01111 // 15
};
// Start out in STATE0.
SystemState mState = STATE0;
//===============================
// Sketch Macros
//===============================
#define LEDon HIGH // [Pin]---[220R]---A[LED]K---GND
#define LEDoff LOW
#define PRESSED LOW // +5V---[InternalPullup]---[Pin]---[Switch]---GND
#define RELEASED HIGH
#define IMMEDIATE 0UL // Used when we want the new State to immediately execute.
#define WAIT_FOREVER 0xFFFFFFFFul // Stay in the new State forever.
//===============================
// GPIOs
//===============================
const byte LEDs[] = {2, 3, 4, 5}; // +5v---[220R]---A[LED]K---Pin
const byte ledArraySize = sizeof(LEDs) / sizeof(LEDs[0]);
const byte mySwitch = 6; // +5V---[internal Pullup]---[Switch]---GND
const byte heartbeatLED = 7; // +5v---[220R]---A[LED]K---Pin
//===============================
// Variables
//===============================
byte lastMySwitchState = LOW;
//========================
// State duration timing.
const unsigned long STATE0_Time = 500ul; // 500ms
const unsigned long LED0_ON_Time = 250ul; // 250ms
const unsigned long LED1_ON_Time = 500ul; // 500ms
const unsigned long LED2_ON_Time = 750ul; // 750ms
const unsigned long LED3_ON_Time = 1000ul; // 1000ms
//===============================
// TIMERs
//===============================
unsigned long heartbeatTime;
unsigned long heartbeatInterval = 500ul; // 500ms
unsigned long switchesTime;
unsigned long switchesInterval = 50ul; // 50ms
unsigned long machineTime;
unsigned long machineInterval = 1000ul; // 1000us or 1ms
unsigned long stateTimer;
unsigned long statePeriod;
//========================
// When we are not using a logic analyzer, we use the function below
// to change the State of the State Machine.
#ifndef DEBUG_ANALYZER // ◄◄◄◄◄◄◄◄◄◄◄
// N E X T _ S T A T E ( )
//================================================^================================================
//
void SET_STATE(SystemState next, unsigned long duration)
{
mState = (next);
statePeriod = (duration);
//Restart this TIMER.
stateTimer = millis();
} // END of SET_STATE()
#endif
//========================
// s e t u p ( )
//================================================^================================================
//
void setup()
{
//Serial.begin(115200);
// We need to "initialize" the analyzer's code before we use it. ✅ REQUIRED ◄◄◄◄◄◄◄◄◄◄◄
DEBUG_INIT();
// Initialyze the LEDs
for (byte x = 0; x < ledArraySize; x++)
{
pinMode(LEDs[x], OUTPUT);
digitalWrite(LEDs[x], LOW);
}
pinMode(heartbeatLED, OUTPUT);
pinMode(mySwitch, INPUT_PULLUP);
lastMySwitchState = digitalRead(mySwitch);
// We will use the analyzer's Reg7 bit to display the mySwitch state. // <---<<<< D e b u g
DEBUG_SET_REG(Reg7, lastMySwitchState ); // <---<<<< D e b u g
// Initialize the state machine and its timer.
//
SET_STATE(STATE0, STATE0_Time);
} // END of setup()
// l o o p ( )
//================================================^================================================
//
void loop()
{
// START ▼▼▼▼▼▼▼▼▼▼▼▼▼
//========================
//Logic Analyzer stuff:
//========================
// Toggle the last LED on the 3rd 595 (Analyzer's heartbeat). // <---<<<< D e b u g
DEBUG_INTERFACE_HEARTBEAT(1000ul); // <---<<<< D e b u g ✅ REQUIRED
// Check for delayed bit TIMER expirations. // <---<<<< D e b u g
DEBUG_DELAYED_SERVICE(); // <---<<<< D e b u g ✅ REQUIRED
// Check for LED TIMER expirations. // <---<<<< D e b u g
DEBUG_LEDsService(); // <---<<<< D e b u g ✅ REQUIRED
// Pulse Generator. // <---<<<< D e b u g
DEBUG_PULSE_COUNT_SERVICE(); // <---<<<< D e b u g ✅ REQUIRED
// We could check loop() execution time. // <---<<<< D e b u g
//DEBUG_FAST_PULSE(Reg11); // <---<<<< D e b u g
// END ▲▲▲▲▲▲▲▲▲▲▲▲▲
//========================
// Normal sketch code
//========================
checkHeartbeat();
checkSwitches();
serviceStateMachine();
//================================================
// Other non blocking code goes here
//================================================
} // END of loop()
// c h e c k H e a r t b e a t ( )
//================================================^================================================
//
void checkHeartbeat()
{
// Is it time to toggle the heartbeat LED ?
if (millis() - heartbeatTime < heartbeatInterval)
{
return;
}
// Restart this TIMER.
heartbeatTime = millis();
// Toggle the heartbeat LED.
digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
// We will use Reg6 to monitor this sketches heartbeat. // <---<<<< D e b u g
DEBUG_TOGGLE(Reg6); // <---<<<< D e b u g
} //END of checkHeartbeat()
// S t a t e M a c h i n e
//================================================^================================================
//
void serviceStateMachine()
{
//================================================
// Is it time to service this State Machine ?
//
if (micros() - machineTime < machineInterval)
{
return;
}
// Restart the machineTime TIMER.
machineTime = micros();
//================================================
// WAIT_FOREVER(0xFFFFFFFFUL), stay in the current State forever.
// IMMEDIATE (0), causes the State to be immediately executed (no delay).
//
// Is the State Machine's TIMER expired ?
if (statePeriod != WAIT_FOREVER &&
(statePeriod == IMMEDIATE || millis() - stateTimer >= statePeriod))
{
//========================
switch (mState)
{
//============
case STATE0:
{
digitalWrite(LEDs[0], LEDon);
// Set the NEXT State and initialize the State Machine TIMER.
SET_STATE(STATE1, LED0_ON_Time);
}
break;
//============
case STATE1:
{
digitalWrite(LEDs[0], LEDoff);
digitalWrite(LEDs[1], LEDon);
// Set the NEXT State and initialize the State Machine TIMER.
SET_STATE(STATE2, LED1_ON_Time);
}
break;
//============
case STATE2:
{
digitalWrite(LEDs[1], LEDoff);
digitalWrite(LEDs[2], LEDon);
// Set the NEXT State and initialize the State Machine TIMER.
SET_STATE(STATE3, LED2_ON_Time);
}
break;
//============
case STATE3:
{
digitalWrite(LEDs[2], LEDoff);
digitalWrite(LEDs[3], LEDon);
// Set the NEXT State and initialize the State Machine TIMER.
SET_STATE(STATE4, LED3_ON_Time);
}
break;
//============
case STATE4:
{
digitalWrite(LEDs[3], LEDoff);
// Set the NEXT State and initialize the State Machine TIMER.
SET_STATE(STATE5, IMMEDIATE);
}
break;
//============
case STATE5:
{
// Set the NEXT State and initialize the State Machine TIMER.
SET_STATE(STATE0, STATE0_Time);
}
break;
//============
default:
{
// Do something.
}
break;
} // END of switch/case
} // END of if(...)
} // END of serviceStateMachine()
// c h e c k S w i t c h e s ( )
//================================================^================================================
//
void checkSwitches()
{
// Is it time to check the switches ?
if (millis() - switchesTime < switchesInterval)
{
return;
}
// Restart this TIMER.
switchesTime = millis();
byte switchState;
//======================================================================== mySwitch
switchState = digitalRead(mySwitch);
// Was there a change in this switch's state ?
if (lastMySwitchState != switchState)
{
// Update to the new state.
lastMySwitchState = switchState;
//========================
// Is this switch pressed ?
if (switchState == PRESSED)
{
// Do something.
// If we change the State Machine's State, "always" use SET_STATE(x,y)
// example: make State Machine go to STATE8 set the TIMER to 200ms.
// SET_STATE(STATE8, 200000ul);
static byte counter = 0;
counter = counter + 1;
//================================================
// Some analyzer interface examples.
//================================================
// Reg7 is used to display the mySwitch state. // <---<<<< D e b u g
DEBUG_SET_REG(Reg7, lastMySwitchState ); // <---<<<< D e b u g
// Reg1-Reg5 can be used in DATA Mode // <---<<<< D e b u g
//DEBUG_SET_REG(Reg1, lastMySwitchState); // <---<<<< D e b u g
// Reg10 Flags/marks the first of 10 presses. // <---<<<< D e b u g
DEBUG_SET_REG(Reg10, HIGH ); // <---<<<< D e b u g
// Generate a pulse train equal to an integer variable. // <---<<<< D e b u g
//DEBUG_PULSE_COUNT_START(Reg8, counter); // <---<<<< D e b u g
// We could display a variable's value. (0 to 32) // <---<<<< D e b u g
// To be useful, DEBUG_DECODE_STATE is commented out. // <---<<<< D e b u g
//DEBUG_EVENT_FAST(counter); // <---<<<< D e b u g
// Every 10 switch presses.
if (counter == 10)
{
// Pulse the "6th DEBUG LED" (Reg20LED6) for 2 seconds.
// Pulse the 6th Debug LED. // <---<<<< D e b u g
DEBUG_LED_PULSE(Reg22LED6, 3000ul); // <---<<<< D e b u g
// Mark this with event with EVENT1F (decimal 31) // <---<<<< D e b u g
DEBUG_EVENT(EVENT31); // <---<<<< D e b u g
// Reg10 flags/marks the end of 10 presses. // <---<<<< D e b u g
DEBUG_SET_REG(Reg10, LOW ); // <---<<<< D e b u g
// Counting starts over.
counter = 0;
}
//More examples:
//============
// Use Reg11 to make a measurable 25ms pulse. // <---<<<< D e b u g
DEBUG_DELAYED_PULSE(Reg11, 0, 25000ul); // <---<<<< D e b u g
// Pulse the Debug LED1 for 1 second. // <---<<<< D e b u g
DEBUG_LED_PULSE(Reg17LED1, 1000ul); // <---<<<< D e b u g
}
//========================
// The switch was released:
else
{
// Reg7 is used to display the mySwitch state. // <---<<<< D e b u g
DEBUG_SET_REG(Reg7, lastMySwitchState ); // <---<<<< D e b u g
}
} // END of mySwitch
} // END of checkSwitches()
//
//================================================^================================================
//
DebugAnalyzerConfig.h
//================================================
// DebugAnalyzerConfig.h
// Configuration for the Logic Analyzer Debug API
//================================================
//
#define ANALYZER_LATCH_PIN 10 // Pin 10 is the UNO's SS pin, other pins can be used.
#define ANALYZER_MARK_PIN 9 // ~240ns pulses will be seen on this pin, other pins can be used.
// SPI Pins (fixed by hardware):
// UNO: MOSI=11, SCK=13, SS=10
// Mega: MOSI=51, SCK=52, SS=53
// If the your sketch uses SPI, make sure the latch pin is different from the analyzer's.
// A L L your sketch SPI code must use SPI.beginTransaction() / SPI.endTransaction().
//
// You must wire 74HC595 accordingly, see the interface schematic.
// https://europe1.discourse-cdn.com/arduino/original/4X/f/8/5/f8575b22873ae0259a6e47ad715b762f82df5146.jpeg
//===============================
// Requires about 1.8k to 2.5k of flash memory.
//===============================
#include "LogicAnalyzerDebug.h"
//===============================
// EVENT Bus definitions
//===============================
// Three 74HC595 shift registers are used for the analyzer's register (3 X 8 = 24 bits).
//
// NOTE:
// Register numbers (Reg1–Reg24) are 1-based for readability.
// Internally, 595 bits are 0-23, i.e. ZERO-based:
// Reg1 = bit 0
// Reg2 = bit 1
// . . .
// Reg24 = bit 23 (last register bit)
//
// UNO D9 Connects to logic analyzer CH0, this is a MARK pulse (~240ns) NOT a periodic clock.
// MARK pulse indicates:
// • exact moment of state transition
// • or event boundary (depending on usage)
//
//
// Analyzer's first 5 bits are typically used for the State / EVENT field.
// (They may also be used as general purpose bits in DATA mode.)
//
// By definition:
// Reg5 is the MSB of a 5-bit field:
// • when 0, we use the: enum SystemState (0x00–0x0F)
// • when 1, we use the: enum EVENT (0x10–0x1F)
//
// For clarity, it helps to keep "RegX" in the variable name.
//
// N O T E S :
// CH0 D9 MARK
// 75HC595 Function
#define Reg1 0 // Logic analyzer CH1 State0, D0 of the parallel bits -+
#define Reg2 1 // Logic analyzer CH2 State1, D1 of the parallel bits |
#define Reg3 2 // Logic analyzer CH3 State2, D2 of the parallel bits > State/EVENT Mode
#define Reg4 3 // Logic analyzer CH4 State3, D3 of the parallel bits |
#define Reg5 4 // Logic analyzer CH5 State4, D4 of the parallel bits -+
//
#define Reg6 5 // Logic analyzer CH6 e.g. Reg6_heartbeat
#define Reg7 6 // Logic analyzer CH7 Reg7_Timing
#define Reg8 7 // Logic analyzer CH8
#define Reg9 8 // Logic analyzer CH9
#define Reg10 9 // Logic analyzer CH10
#define Reg11 10 // Logic analyzer CH11
#define Reg12 11 // Logic analyzer CH12
#define Reg13 12 // Logic analyzer CH13
#define Reg14 13 // Logic analyzer CH14
#define Reg15 14 // Logic analyzer CH15
//
#define Reg16_Scope 15 // Scope trigger Oscilloscope Trigger
//
#define Reg17LED1 16 // Debug LED1 First interface debug LED
#define Reg18LED2 17 // Debug LED2
#define Reg19LED3 18 // Debug LED3
#define Reg20LED4 19 // Debug LED4
#define Reg21LED5 20 // Debug LED5
#define Reg22LED6 21 // Debug LED6
#define Reg23LED7 22 // Debug LED7 Last interface debug LED
//
#define Reg24LED8 23 // Debug LED8 Interface's debug heartbeat LED
//===============================
//EVENT bits
//===============================
// Example usage:
// DEBUG_EVENT(EVENT1F); //When we are in the State/EVENT mode, make a narrow event mark, #31.
//
// NOTE: Event IDs typically use 0x10–0x1F to distinguish from SystemState (0x00–0x0F).
//
// For clarity, it helps to keep "EVENT##_" in the variable name.
enum EVENT : uint8_t
{
// ID 43210 Decimal Change names as needed:
EVENT16 = 0b10000, // 16 e.g. EVENT10_ISR_ENTRY
EVENT17 = 0b10001, // 17 EVENT11_ISR_EXIT
EVENT18 = 0b10010, // 18 EVENT12_ERROR1
EVENT19 = 0b10011, // 19 EVENT13_ERROR2
EVENT20 = 0b10100, // 20 EVENT14_SWITCH_PRESS
EVENT21 = 0b10101, // 21 EVENT15_SWITCH_RELEASE
EVENT22 = 0b10110, // 22 etc.
EVENT23 = 0b10111, // 23
EVENT24 = 0b11000, // 24
EVENT25 = 0b11001, // 25
EVENT26 = 0b11010, // 26
EVENT27 = 0b11011, // 27
EVENT28 = 0b11100, // 28
EVENT29 = 0b11101, // 29
EVENT30 = 0b11110, // 30
EVENT31 = 0b11111, // 31
};
//===============================
// Analyzer bus
//===============================
uint32_t debugBus = 0;
uint8_t debugBus_event5 = 0;
bool debugEventActive = false;
//================================================
// State transition macro
//================================================
// Bits 0–4 of debugBus are reserved for SystemState (5-bit state).
// To change the State Machine's State "always" use SET_STATE(x,y).
// Where x is the new State and Y is the State TIMER interval.
// Debug version
#define SET_STATE(next, duration) \
do { \
mState = (next); \
STATE_DEBUG_UPDATE(next); \
STATE_DEBUG_MARK(); \
statePeriod = (duration); \
stateTimer = millis(); \
} while(0)
//========================
// Should we be decoding the State/EVENT and make a MARK on ANALYZER_MARK_PIN (D9)?
#ifdef DEBUG_DECODE_STATE
// Yes we do.
#define STATE_DEBUG_UPDATE(next) DEBUG_SET_STATE5(next)
#define STATE_DEBUG_MARK() DEBUG_MARK()
//
//========================
#else
//
// No we don't.
#define STATE_DEBUG_UPDATE(next) do {} while(0)
#define STATE_DEBUG_MARK() do {} while(0)
//========================
#endif
/*
//================================================
// Logic Analyzer Macros:
//================================================
DEBUG_PULSE_COUNT_START(bit, count)
DEBUG_PULSE_COUNT_SERVICE()
DEBUG_PULSE_COUNT_BUSY()
DEBUG_PULSE_COUNT_CANCEL()
DEBUG_TOGGLE(bit);
DEBUG_HIGH(bit);
DEBUG_LOW(bit);
DEBUG_SET_REG(bit, value);
DEBUG_FAST_PULSE(bit);
DEBUG_EVENT_FAST(id);
DEBUG_EVENT(id);
DEBUG_TRIGGER(); //Oscilloscope Trigger Reg16 only.
//All DEBUG_DELAYED_*() macros use a shared timing engine. Only one delayed
//operation can be active at a time—subsequent calls overwrite any previous delayed action.
DEBUG_DELAYED_MARK(bit, delay_us);
DEBUG_DELAYED_HIGH(bit, delay_us);
DEBUG_DELAYED_LOW(bit, delay_us);
DEBUG_DELAYED_PULSE(bit, delay_us, width_us);
DEBUG_DELAYED_SERVICE();
DEBUG_LED_ON(bit);
DEBUG_LED_OFF(bit);
DEBUG_LED_PULSE(bit,duration);
DEBUG_INTERFACE_HEARTBEAT(period);
DEBUG_LEDsService();
*/
LogicAnalyzerDebug.h
//================================================
// LogicAnalyzerDebug.h
// Public API: SPI debug bus driver and debug LED utilities
//================================================
#include <Arduino.h>
#include <SPI.h>
#define DEBUG_NOP() __asm__ __volatile__("nop\n\t")
// About 240ns Mark pulse.
#define DEBUG_PULSE_DELAY() \
do { \
DEBUG_NOP(); \
DEBUG_NOP(); \
DEBUG_NOP(); \
} while(0)
#ifndef DEBUG_SPI_SETTINGS
#define DEBUG_SPI_SETTINGS SPISettings(8000000, MSBFIRST, SPI_MODE0)
#endif
#ifndef LOGIC_ANALYZER_DEBUG_H
#define LOGIC_ANALYZER_DEBUG_H
//================================================
// Logic analyzer hardware configuration
//================================================
#if ANALYZER_MARK_PIN == 10 || \
ANALYZER_MARK_PIN == 11 || \
ANALYZER_MARK_PIN == 12 || \
ANALYZER_MARK_PIN == 13
#error "ANALYZER_MARK_PIN cannot be an SPI pin (10,11,12,13)"
#endif
#if (ANALYZER_LATCH_PIN < 2) || (ANALYZER_LATCH_PIN > 17)
#error "ANALYZER_LATCH_PIN must be a digital pin between 2 and A3"
#endif
#if (ANALYZER_MARK_PIN < 2) || (ANALYZER_MARK_PIN > 17)
#error "ANALYZER_MARK_PIN must be a digital pin between 2 and A3"
#endif
#if ANALYZER_LATCH_PIN == ANALYZER_MARK_PIN
#error "ANALYZER_LATCH_PIN and ANALYZER_MARK_PIN must be different pins"
#endif
#define LATCH_LOW *portOutputRegister(digitalPinToPort(ANALYZER_LATCH_PIN)) &= ~digitalPinToBitMask(ANALYZER_LATCH_PIN)
#define LATCH_HIGH *portOutputRegister(digitalPinToPort(ANALYZER_LATCH_PIN)) |= digitalPinToBitMask(ANALYZER_LATCH_PIN)
//================================================
// Fast analyzer pulse on: pins 2–9, A0–A3. (~200–250ns)
//================================================
#if ANALYZER_MARK_PIN == 2
#define PULSE240 do { cli(); PIND = _BV(PD2); DEBUG_PULSE_DELAY(); PIND = _BV(PD2); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 3
#define PULSE240 do { cli(); PIND = _BV(PD3); DEBUG_PULSE_DELAY(); PIND = _BV(PD3); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 4
#define PULSE240 do { cli(); PIND = _BV(PD4); DEBUG_PULSE_DELAY(); PIND = _BV(PD4); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 5
#define PULSE240 do { cli(); PIND = _BV(PD5); DEBUG_PULSE_DELAY(); PIND = _BV(PD5); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 6
#define PULSE240 do { cli(); PIND = _BV(PD6); DEBUG_PULSE_DELAY(); PIND = _BV(PD6); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 7
#define PULSE240 do { cli(); PIND = _BV(PD7); DEBUG_PULSE_DELAY(); PIND = _BV(PD7); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 8
#define PULSE240 do { cli(); PINB = _BV(PB0); DEBUG_PULSE_DELAY(); PINB = _BV(PB0); sei(); } while(0)
// Perferred pin.
//
#elif ANALYZER_MARK_PIN == 9
#define PULSE240 do { cli(); PINB = _BV(PB1); DEBUG_PULSE_DELAY(); PINB = _BV(PB1); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 14
#define PULSE240 do { cli(); PINC = _BV(PC0); DEBUG_PULSE_DELAY(); PINC = _BV(PC0); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 15
#define PULSE240 do { cli(); PINC = _BV(PC1); DEBUG_PULSE_DELAY(); PINC = _BV(PC1); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 16
#define PULSE240 do { cli(); PINC = _BV(PC2); DEBUG_PULSE_DELAY(); PINC = _BV(PC2); sei(); } while(0)
#elif ANALYZER_MARK_PIN == 17
#define PULSE240 do { cli(); PINC = _BV(PC3); DEBUG_PULSE_DELAY(); PINC = _BV(PC3); sei(); } while(0)
#else
#error "Unsupported ANALYZER_MARK_PIN"
#endif
//================================================
// Update the debug bus.
//================================================
#define DEBUG_SET_STATE5(n) \
do { \
uint8_t _n = (uint8_t)(n); \
DEBUG_SAFE_UPDATE( \
debugBus = (debugBus & 0xFFFFFFE0UL) | (_n & 0x1F) \
); \
} while(0)
//================================================
// Public timing marker, the user can use this any time,
// when in debug mode.
//================================================
#define DEBUG_MARK() PULSE240
//================================================
// External variables supplied by sketch
//================================================
extern uint32_t debugBus;
extern uint8_t debugBus_event5;
extern bool debugEventActive;
extern unsigned long stateTimer;
extern unsigned long statePeriod;
//================================================
// Initialize analyzer hardware
//================================================
static inline void DEBUG_INIT()
{
// Power-up settling
delay(20);
// Ensure MCU stays SPI master (required on AVR)
pinMode(SS, OUTPUT);
// Latch pin (74HC595 RCLK)
pinMode(ANALYZER_LATCH_PIN, OUTPUT);
digitalWrite(ANALYZER_LATCH_PIN, HIGH);
// Marker pin (timing pulse)
pinMode(ANALYZER_MARK_PIN, OUTPUT);
digitalWrite(ANALYZER_MARK_PIN, LOW);
// Initialize SPI hardware
SPI.begin();
// Known startup state
debugBus = 0;
debugEventActive = false;
uint32_t value = debugBus;
// Send initial cleared frame (use transaction!)
SPI.beginTransaction(DEBUG_SPI_SETTINGS);
LATCH_LOW;
SPI.transfer(value >> 16); // 3rd 74HC595
SPI.transfer(value >> 8); // 2nd 74HC595
SPI.transfer(value); // 1st 74HC595
LATCH_HIGH;
SPI.endTransaction();
}
//================================================
// Send debug state to shift registers
//================================================
static inline void DEBUG_UPDATE()
{
uint32_t value = debugBus;
if (debugEventActive)
{
value = (value & 0xFFFFFFE0UL) | (debugBus_event5 & 0x1F);
}
// Begin SPI transaction (protect bus)
SPI.beginTransaction(DEBUG_SPI_SETTINGS);
LATCH_LOW;
uint8_t b2 = value >> 16; // 3rd 595
uint8_t b1 = value >> 8; // 2nd 595
uint8_t b0 = value; // 1st 595
SPDR = b2;
while (!(SPSR & _BV(SPIF)));
SPDR = b1;
while (!(SPSR & _BV(SPIF)));
SPDR = b0;
while (!(SPSR & _BV(SPIF)));
LATCH_HIGH;
// End SPI transaction
SPI.endTransaction();
}
// Debug bit helpers
//================================================^================================================
//
//------------------------------------------------
// NON-BLOCKING EDGE-COUNT PULSE GENERATOR
//------------------------------------------------
//
// Generates N pulses (N falling edges) at loop() speed.
//
// Each loop() = 1 pulse (via DEBUG_FAST_PULSE)
//
// Analyzer counts FALLING edges = exact value
//
// Usage:
// DEBUG_PULSE_COUNT_START(Reg8, value);
// DEBUG_PULSE_COUNT_SERVICE(); // call every loop()
//
//------------------------------------------------
static uint16_t _pc_remaining = 0;
static uint8_t _pc_bit = 0;
static bool _pc_active = false;
// Start pulse generation
//========================
#define DEBUG_PULSE_COUNT_START(bit, count) \
do { \
_pc_bit = (bit); \
_pc_remaining = (count); \
_pc_active = true; \
} while(0)
// Service routine (call in loop)
//========================
#define DEBUG_PULSE_COUNT_SERVICE() \
do { \
if (_pc_active) { \
if (_pc_remaining > 0) { \
DEBUG_FAST_PULSE(_pc_bit); \
_pc_remaining--; \
} else { \
_pc_active = false; \
} \
} \
} while(0)
// Status
//========================
#define DEBUG_PULSE_COUNT_BUSY() (_pc_active)
// Cancel
//========================
#define DEBUG_PULSE_COUNT_CANCEL() \
do { \
_pc_active = false; \
_pc_remaining = 0; \
} while(0)
//------------------------------------------------
//========================
// Output a Reg16 trigger pulse to the oscilloscope.
#define DEBUG_TRIGGER() \
DEBUG_FAST_PULSE(Reg16_Scope)
//========================
#define DEBUG_EVENT_FAST(id) \
do { \
uint8_t _id = (id) & 0x1F; \
\
uint8_t sreg = SREG; \
cli(); \
\
debugBus_event5 = _id; \
debugEventActive = true; \
DEBUG_UPDATE(); \
\
DEBUG_MARK(); \
\
debugEventActive = false; \
DEBUG_UPDATE(); \
\
SREG = sreg; \
} while(0)
//========================
#define DEBUG_EVENT(id) DEBUG_EVENT_WINDOW(id)
//========================
#define DEBUG_EVENT_WINDOW(id) \
do { \
uint8_t _id = (uint8_t)(id); \
uint32_t _prevBus; \
\
uint8_t sreg = SREG; \
cli(); \
_prevBus = debugBus; \
SREG = sreg; \
\
DEBUG_SET_STATE5(_id); \
DEBUG_PULSE_DELAY(); \
DEBUG_MARK(); \
\
DEBUG_SAFE_UPDATE(debugBus = _prevBus); \
DEBUG_MARK(); \
} while(0)
//========================
// ATOMIC
#define DEBUG_SAFE_UPDATE(code) \
do { \
uint8_t sreg = SREG; \
cli(); \
code; \
DEBUG_UPDATE(); \
SREG = sreg; \
} while(0)
//========================
#define DEBUG_TOGGLE(bit) \
DEBUG_SAFE_UPDATE(debugBus ^= (1UL << bit))
//========================
#define DEBUG_HIGH(bit) \
DEBUG_SAFE_UPDATE(debugBus |= (1UL << bit))
//========================
#define DEBUG_LOW(bit) \
DEBUG_SAFE_UPDATE(debugBus &= ~(1UL << bit))
//========================
// Set a debugBus bit from a variable (0 = LOW, non-zero = HIGH)
#define DEBUG_SET_REG(bit, value) \
do { \
uint8_t _v = (value); \
if (_v) \
DEBUG_HIGH(bit); \
else \
DEBUG_LOW(bit); \
} while(0)
//========================
// ATOMIC
#define DEBUG_FAST_PULSE(bit) \
do { \
uint8_t sreg = SREG; \
cli(); \
\
debugBus ^= (1UL << (bit)); \
DEBUG_UPDATE(); \
\
debugBus ^= (1UL << (bit)); \
DEBUG_UPDATE(); \
\
SREG = sreg; \
} while(0)
//========================
// Delayed event engine
//========================
// OFF event
static unsigned long delayedOffTime = 0;
static bool delayedOffPending = false;
static byte delayedOffBit = 0;
#define DELAYED_LOW 0
#define DELAYED_HIGH 1
#define DELAYED_PULSE 2
// ON / marker event
static unsigned long delayedMarkerTime = 0;
static bool delayedMarkerPending = false;
static byte delayedMarkerBit = 0;
static byte delayedMarkerLevel = 0; // 0=LOW, 1=HIGH, 2=PULSE
// All DEBUG_DELAYED_*() macros use a shared timing engine. Only one delayed
// operation can be active at a time—subsequent calls overwrite any previous delayed action.
//========================
#define DEBUG_DELAYED_MARK(bit, delay_us) \
do { \
uint32_t _delay = (delay_us); \
delayedMarkerTime = micros() + _delay; \
delayedMarkerPending = true; \
delayedMarkerBit = (bit); \
delayedMarkerLevel = DELAYED_PULSE; \
delayedOffPending = false; \
} while(0)
//========================
#define DEBUG_DELAYED_HIGH(bit, delay_us) \
do { \
delayedMarkerTime = micros() + (delay_us); \
delayedMarkerPending = true; \
delayedMarkerBit = (bit); \
delayedMarkerLevel = DELAYED_HIGH; \
delayedOffPending = false; \
} while(0)
//========================
#define DEBUG_DELAYED_LOW(bit, delay_us) \
do { \
delayedMarkerTime = micros() + (delay_us); \
delayedMarkerPending = true; \
delayedMarkerBit = (bit); \
delayedMarkerLevel = DELAYED_LOW; \
delayedOffPending = false; \
} while(0)
//========================
#define DEBUG_DELAYED_PULSE(bit, delay_us, width_us) \
do { \
uint32_t _delay = (delay_us); \
uint32_t _width = (width_us); \
uint32_t _start = micros() + _delay; \
\
delayedMarkerTime = _start; \
delayedMarkerPending = true; \
delayedMarkerBit = (bit); \
delayedMarkerLevel = DELAYED_HIGH; \
\
delayedOffTime = _start + _width; \
delayedOffPending = true; \
delayedOffBit = (bit); \
} while(0)
//======================== (call in loop)
//
#define DEBUG_DELAYED_SERVICE() \
do { \
unsigned long now = micros(); \
\
/* Handle ON / pulse */ \
if (delayedMarkerPending && \
(long)(now - delayedMarkerTime) >= 0) \
{ \
if (delayedMarkerLevel == DELAYED_PULSE) \
{ \
DEBUG_FAST_PULSE(delayedMarkerBit); \
} \
else if (delayedMarkerLevel == DELAYED_HIGH) \
{ \
DEBUG_HIGH(delayedMarkerBit); \
\
/* Adjust OFF timing to actual execution time */ \
if (delayedOffPending) \
{ \
delayedOffTime = now + (delayedOffTime - delayedMarkerTime); \
} \
} \
else \
{ \
DEBUG_LOW(delayedMarkerBit); \
} \
\
delayedMarkerPending = false; \
} \
\
/* Handle OFF */ \
if (delayedOffPending && \
(long)(now - delayedOffTime) >= 0) \
{ \
DEBUG_LOW(delayedOffBit); \
delayedOffPending = false; \
} \
} while(0)
//================================================
// Debug LED system
//================================================
//========================
#define DEBUG_LED_ON(bit) \
do{ \
if((bit) >= DEBUG_LED_FIRST && (bit) <= DEBUG_LED_LAST){ \
DEBUG_SAFE_UPDATE \
( \
byte idx = (bit) - DEBUG_LED_FIRST; \
debugLedManual[idx] = true; \
debugBus |= (1UL << (bit)); \
); \
} \
} \
while(0)
//========================
#define DEBUG_LED_OFF(bit) \
do{ \
if((bit) >= DEBUG_LED_FIRST && (bit) <= DEBUG_LED_LAST){ \
DEBUG_SAFE_UPDATE \
( \
byte idx = (bit) - DEBUG_LED_FIRST; \
debugLedManual[idx] = false; \
debugBus &= ~(1UL << (bit)); \
); \
} \
} \
while(0)
//========================
#define DEBUG_LED_PULSE(bit,duration) \
do{ \
if((bit) >= DEBUG_LED_FIRST && (bit) <= DEBUG_LED_LAST){ \
DEBUG_SAFE_UPDATE \
( \
byte idx = (bit) - DEBUG_LED_FIRST; \
debugBus |= (1UL << (bit)); \
debugLedTimer[idx] = millis(); \
debugLedDuration[idx] = (duration); \
debugLedActive[idx] = true; \
); \
} \
} \
while(0)
//======================== (call in loop)
//
#define DEBUG_LED_FIRST 16 //First debug LED register number, ZERO Indexed.
#define DEBUG_LED_COUNT 7 //7 LEDs can be timed.
#define DEBUG_LED_LAST 22 //Last LED that can be a timed.
// Note: 23 is used exclusively for interface heartbeat LED.
static unsigned long debugLedTimer[DEBUG_LED_COUNT];
static unsigned long debugLedDuration[DEBUG_LED_COUNT];
//
static bool debugLedActive[DEBUG_LED_COUNT];
static bool debugLedManual[DEBUG_LED_COUNT];
// Iterate through the debug LEDs.
// Check if they are used as a timed LED.
//
static inline void DEBUG_LEDsService()
{
unsigned long now = millis();
for (byte i = 0; i < DEBUG_LED_COUNT; i++)
{
// When this LED is being timed, has its TIMER expired ?
if (debugLedActive[i])
{
if (now - debugLedTimer[i] >= debugLedDuration[i])
{
// Disable this TIMER
debugLedActive[i] = false;
DEBUG_SAFE_UPDATE(
{
if (debugLedManual[i])
{
debugBus |= (1UL << (i + DEBUG_LED_FIRST));
}
else
{
debugBus &= ~(1UL << (i + DEBUG_LED_FIRST));
}
});
}
}
}
}
//========================
// This LED is the analyzer's heartbeat LED.
// If it toggles, the interface is wired properly.
//
const byte DEBUG_HEARTBEAT_LED = 23; //Zero-Indexed
static unsigned long debugHeartbeatTime = 0;
// Is it time to toggle the interface heartbeat LED ?
#define DEBUG_INTERFACE_HEARTBEAT(period) \
do { \
uint32_t _p = (period); \
unsigned long now = millis(); \
if(now - debugHeartbeatTime >= _p) \
{ \
debugHeartbeatTime = now; \
DEBUG_SAFE_UPDATE(debugBus ^= (1UL << DEBUG_HEARTBEAT_LED)); \
} \
} \
while(0)
#endif
//================================================^================================================