John Bradnam
Published © GPL3+

The Ultimate Logic Probe

A small logic probe for CMOS and TTL circuits. Measures 5 states, detects pulses and can play different tones for the different states.

IntermediateFull instructions provided8 hours3,729
The Ultimate Logic Probe

Things used in this project

Hardware components

Microchip ATtiny1614
×1
TSS-307EWA 7-Seg 0.36in CC display
×1
LM1117-33 3.3V Regulator
SOT-223 package
×1
PTS 645 Series Switch
C&K Switches PTS 645 Series Switch
6mm shaft
×1
SMT-916 speaker
×1
3.6V 400mW Zener Diode
SOD80C Package
×2
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
SOD-123 Package
×2
Passive components
5 x 180R 0805, 1 x 91R 0805, 2 x 1K 0805, 2 x 3K 0805, 1 x 15K 0805 resistors; 2 x 0.1uF 0805, 1 x 10uF 0805, 1 x 100uF/10V 7343 capacitors
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

STL files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Gerber Files

Gerber files for PCB Manufacturing

Code

Logic_Probe_V1.ino

C/C++
/**************************************************************************
 ATtiny1614 Logic Probe

 Schematic & PCB at https://www.hackster.io/john-bradnam/contact-digital-thermometer-ed18d2
 
 2021-08-10 John Bradnam (jbrad2089@gmail.com)
   Create program for ATtiny1614

 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 20MHz
  millis()/micros(): "Enabled (default timer)"
  Programmer: jtag2updi (megaTinyCore)

  ATTiny1614 Pins mapped to Ardunio Pins
 
              +--------+
          VCC + 1   14 + GND
  (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
        1 PA5 + 3   12 + PA2 9  (MISO)
  (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
        3 PA7 + 5   10 + PA0 11 (UPDI)
  (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
  (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
              +--------+
  
 **************************************************************************/

//Debug mode will use the TX pin to send serial messages. As this is also used
//for Segment A, the display is disabled when DEBUG mode is enabled
//#define DEBUG 

//Display Pins
#define A_PIN 5     //PB2
#define B_PIN 4     //PB3
#define C_PIN 3     //PA7
#define D_PIN 0     //PA4
#define EF_PIN 1    //PA5
#define G_PIN 2     //PA6

//Inputs
#define VIN_PIN 9   //PA2
#define PROBE_PIN 8 //PA1
#define MODE_PIN 10 //PA3

#define ADC_PROBE ADC_MUXPOS_AIN1_gc
#define ADC_VIN ADC_MUXPOS_AIN2_gc


//Outputs
#define SPKR_PIN 6  //PB1

//Frequency for different states
#define VDD_TONE 1760
#define HIGH_TONE 880
#define LOW_TONE 220
#define GND_TONE 110
#define BTN_TONE 440

//Pin and mask mapping table
typedef struct {
  int8_t pin;
  int8_t mask;
} SEG;

#define SEG_COUNT 6
SEG segments[] = {
  {A_PIN,  B00000001},
  {B_PIN,  B00000010},
  {C_PIN,  B00000100},
  {D_PIN,  B00001000},
  {EF_PIN, B00010000},
  {G_PIN,  B00100000}
};

//Character set
#define CHAR_COUNT 10
#define CHAR_SPACE 0
#define CHAR_VDD 1
#define CHAR_HIGH 2
#define CHAR_FLOAT 3
#define CHAR_LOW 4
#define CHAR_GND 5
#define CHAR_PULSE 6
#define CHAR_CMOS 7
#define CHAR_TTL 8
#define CHAR_LS 9

uint8_t charset[] = {
  B00000000,  //CHAR_SPACE
  B00000001,  //CHAR_VDD
  B00000110,  //CHAR_HIGH
  B00100000,  //CHAR_FLOAT
  B00011111,  //CHAR_LOW
  B00001000,  //CHAR_GND
  B00110011,  //CHAR_PULSE
  B00011001,  //CHAR_CMOS
  B00111000,  //CHAR_TTL
  B00011000   //CHAR_LS
};

char debugset[] = {
  ' ',  //CHAR_SPACE
  '+',  //CHAR_VDD
  '1',  //CHAR_HIGH
  '?',  //CHAR_FLOAT
  '0',  //CHAR_LOW
  '-',  //CHAR_GND
  'P',  //CHAR_PULSE
  'C',  //CHAR_CMOS
  'T',  //CHAR_TTL
  'L'   //CHAR_LS
};

typedef struct {
  float low;    //Maximum voltage a LOW state can be (fixed voltage)
  float high;   //Minumum voltage a HIGH state can be as a percentage of VDD
  uint8_t chr;  //Character to display for this family
} FAMILY;

//This table defines the voltage levels for each family that the probe can measure
#define NUMBER_OF_FAMILIES 3
FAMILY families[NUMBER_OF_FAMILIES] = {
  {0.8,80,CHAR_CMOS},   //CMOS family with tones
  {0.4,48,CHAR_TTL},    //0.4, 2.4 (1987 - National LS S TTL Logic Databook)
  {0.5,54,CHAR_LS}      //0.5, 2.7 (1987 - National LS S TTL Logic Databook)
};

enum STATES { STATE_UNKNOWN, STATE_VDD, STATE_HIGH, STATE_FLOAT, STATE_LOW, STATE_GND, STATE_PULSE };

#define PULSE_PERIOD 100            //Maximum time between state changes to be detected as a pulse (1/2 the period => 5Hz)

uint8_t activeFamily = 0;           //Current chip family being tested
STATES lastState = STATE_UNKNOWN;   //Last state measured
STATES activeState = STATE_UNKNOWN; //Current state at probe
float supplyVoltage = 0;            //VDD from 3V3 regulator
float probeVoltage = 0;             //Last reading from probe
float vinVoltage = 0;               //Last reading from VIN
long pulseTimeout = 0;              //Timer used to measure pulses
bool waitingOnChange = false;       //Enabled when waiting on pulseTimeout
bool soundEnabled = false;          //Whether tones are played for the states

//-------------------------------------------------------------------------
// Initialise Hardware
void setup(void)
{
  for (int i = 0; i < SEG_COUNT; i++)
  {
    pinMode(segments[i].pin, OUTPUT);
    digitalWrite(segments[i].pin, LOW);
  }

  #ifdef DEBUG
    Serial.begin(115200);
  #endif

  pinMode(VIN_PIN, INPUT);
  pinMode(PROBE_PIN, INPUT);
  pinMode(MODE_PIN, INPUT_PULLUP);
  pinMode(SPKR_PIN, OUTPUT);

  //Setup ADC
  VREF.CTRLA = VREF_ADC0REFSEL_1V1_gc;
  ADC0.CTRLC = ADC_REFSEL_VDDREF_gc | ADC_PRESC_DIV256_gc; // 78kHz clock
  ADC0.CTRLA = ADC_ENABLE_bm;                              // Single, 10-bit

  measureSupplyVoltage();
}

//--------------------------------------------------------------------
// Main program loop
void loop(void)
{
  if (buttonPressed())
  {
    activeFamily++;
    if (activeFamily == NUMBER_OF_FAMILIES)
    {
      activeFamily = 0;
      soundEnabled = !soundEnabled;   //Turn/off audio
    }
    showChar(families[activeFamily].chr);
    noTone(SPKR_PIN);
    if (soundEnabled)
    {
      tone(SPKR_PIN, BTN_TONE);
    }
    waitForButtonRelease();
    noTone(SPKR_PIN);

    //Force an update
    waitingOnChange = false;
    lastState = STATE_UNKNOWN;
  }
  testProbe();
}

//--------------------------------------------------------------------
// Test if button pressed
bool buttonPressed()
{
  bool result = false;
  if (digitalRead(MODE_PIN) == LOW)
  {
    delay(10);    //Debounce
    return (digitalRead(MODE_PIN) == LOW);
  }
  return result;
}

//--------------------------------------------------------------------
// Wait until the button is released
void waitForButtonRelease()
{
  while (digitalRead(MODE_PIN) == LOW) ;
}

//--------------------------------------------------------------------
// Measure the voltages and dislay the results if changed
void testProbe()
{
  
  //Voltages are feed through divide by 4 resistor voltage converter 
  //so they need to multiplied by 4. 
  probeVoltage = measureVoltage(ADC_PROBE) * 4;
  vinVoltage = measureVoltage(ADC_VIN) * 4;

  //Calculate HIGH and VDD thresholds from VIN voltage
  float vdd = vinVoltage - 0.1;
  float high = families[activeFamily].high * vinVoltage / 100;
  bool pulse = false;
  
  //Workout state
  if (probeVoltage < 0.1)
  {
    activeState = STATE_GND;
  }
  else if (probeVoltage <= families[activeFamily].low)
  {
    activeState = STATE_LOW;
  }
  else if (probeVoltage < high)
  {
    activeState = STATE_FLOAT;
  }
  else if (probeVoltage <= vdd)
  {
    activeState = STATE_HIGH;
  }
  else
  {
    activeState = STATE_VDD;
  }

  
  if (activeState != lastState) //Only do something if state changes
  {
    #ifdef DEBUG
      Serial.println("p=" + String(probeVoltage) + ", vin=" + String(vinVoltage) + ", vdd=" + String(supplyVoltage));
      delay(200);
    #endif
    
    if ((activeState == STATE_HIGH) ^ (lastState == STATE_HIGH)) //Change in state from either LOW to HIGH or HIGH to LOW
    {
      pulse = (waitingOnChange && millis() < pulseTimeout); //If state change within PULSE_PERIOD signal it as a pulse
      pulseTimeout = millis() + PULSE_PERIOD;               //Set next timeout
      waitingOnChange = true;                               //and signal we are waiting on a state change
    }
    else
    {
      //This isn't a pulse
      waitingOnChange = false;
      pulse = false;
    }

    //Turn off any sound from the last state
    noTone(SPKR_PIN);

    //Show the result
    int chr = (pulse) ? CHAR_PULSE : (uint8_t)activeState;
    showChar(chr);

    if (soundEnabled) //Play sound if enabled
    {
      switch(chr)
      {
        case CHAR_VDD: tone(SPKR_PIN, VDD_TONE); break;
        case CHAR_HIGH: tone(SPKR_PIN, HIGH_TONE); break;
        case CHAR_LOW: tone(SPKR_PIN, LOW_TONE); break;
        case CHAR_GND: tone(SPKR_PIN, GND_TONE); break;
      }
    }

    //Record last state
    lastState = activeState;
  }
}

//--------------------------------------------------------------------
// Display character on 7 segment display
// chr - character to show (0 <= chr < CHAR_COUNT)
void showChar(uint8_t chr)
{
  if (chr < CHAR_COUNT)
  {
    #ifdef DEBUG
      Serial.println(debugset[chr]);
    #else
      uint8_t mask = charset[chr];
      for (int i = 0; i < SEG_COUNT; i++)
      {
        digitalWrite(segments[i].pin, (mask & segments[i].mask) ? HIGH : LOW);
      }
    #endif
  }    
}

//--------------------------------------------------------------------------
// Measure supply voltage
// Source: David Johnson-Davies - www.technoblogy.com - 13th April 2021
void measureSupplyVoltage() 
{
  ADC0.MUXPOS = ADC_MUXPOS_INTREF_gc;                  // Measure INTREF
  ADC0.COMMAND = ADC_STCONV_bm;                        // Start conversion
  while (ADC0.COMMAND & ADC_STCONV_bm);                // Wait for completion
  uint16_t adc_reading = ADC0.RES;                     // ADC conversion result
  supplyVoltage = 1126.4 / adc_reading;
}

//--------------------------------------------------------------------------
// Measure voltage of a given pin
// adcMux - ADC_Ax constant for the pin to measure
float measureVoltage(uint8_t adcMux) 
{
  ADC0.MUXPOS = adcMux;                                // Measure Analog pin
  ADC0.COMMAND = ADC_STCONV_bm;                        // Start conversion
  while (ADC0.COMMAND & ADC_STCONV_bm);                // Wait for completion
  uint16_t adc_reading = ADC0.RES;                     // ADC conversion result
  return supplyVoltage * adc_reading / 1024;
}

Credits

John Bradnam

John Bradnam

145 projects • 177 followers

Comments