Hiermit möchte ich meine kleine Erweiterung für das c't-Lab vorstellen.
Mit dieser Erweiterung können digitale Logik-Schaltkreise getestet werden.
Es wird mindestens ein ADA-IO Modul benötigt. Außerdem habe auch mein DCG verwendet, es aber würde theoretisch auch ohne gehen.
Es können digitale Logikschaltkreise mit 14 bzw. 16 Pins auf ihre Funktion hin getestet werden. Der Testvorgang verläuft in drei Schritten:
1. Ermittelung der Datenrichtung der Pins (Ein-/Ausgänge)
2. Erfassen, welche Eingänge auf welche Ausgänge wirken
3. Bestimmung der Logik-Funktion(en)
Als kleine Beigabe wird noch der Stromverbrauch pro Gatter bestimmt und die eingesetzte Technologie (TTL, CMOS) erraten.
Hier ist die Ausgabe für den im Bild gezeigten D204:
Zum Schaltplan ist nicht wirklich viel zu sagen. Die Pins hängen über 1kOhm-Widerstände an den digitalen IOs. Zusätzlich können die AD-Wandler direkt an den Pins messen.Initialisation: ok
Power: U (5.00 V) * I (32.59 mA) = P (161.8 mW)
14-pin device
check DUT pin directions
Testing pin 1 low: 1.07 V hi: 5.03 V hiZ: 5.01 V --> direction: Input
Testing pin 2 low: 0.10 V hi: 0.15 V hiZ: 0.11 V --> direction: Output
Testing pin 3 low: 0.97 V hi: 4.94 V hiZ: 4.92 V --> direction: Input
Testing pin 4 low: 0.11 V hi: 0.15 V hiZ: 0.11 V --> direction: Output
Testing pin 5 low: 0.97 V hi: 4.94 V hiZ: 4.92 V --> direction: Input
Testing pin 6 low: 0.09 V hi: 0.13 V hiZ: 0.10 V --> direction: Output
Testing pin 8 low: 0.08 V hi: 0.12 V hiZ: 0.08 V --> direction: Output
Testing pin 9 low: 0.98 V hi: 4.96 V hiZ: 4.30 V --> direction: Input
Testing pin 10 low: 0.09 V hi: 0.13 V hiZ: 0.09 V --> direction: Output
Testing pin 11 low: 0.99 V hi: 4.96 V hiZ: 4.35 V --> direction: Input
Testing pin 12 low: 0.09 V hi: 0.13 V hiZ: 0.09 V --> direction: Output
Testing pin 13 low: 1.00 V hi: 4.96 V hiZ: 4.26 V --> direction: Input
6 input pins: [1, 3, 5, 9, 11, 13]
6 output pins: [2, 4, 6, 8, 10, 12]
check 64 logic combinations
[################################################################################] 100% Done...
found following equations:
input(s): [1], output: 2 --> 10 (NOT)
input(s): [3], output: 4 --> 10 (NOT)
input(s): [5], output: 6 --> 10 (NOT)
input(s): [9], output: 8 --> 10 (NOT)
input(s): [11], output: 10 --> 10 (NOT)
input(s): [13], output: 12 --> 10 (NOT)
Power per gate: P_gate = 27.0 mW
Technology : High Speed TTL
Cleanup
Done
Falls das jemand nachbauen möchte, hier noch ein paar Tipps:
- besorgt den Textool-Sockel vorher, damit genug Platz auf der Platine eingeplant wird und der Footprint passt,
- es gibt einige Chips (z.B. HEF4049), die Vcc an Pin 1 haben, dazu müsste noch ein Jumper vorgesehen werden,
- ein Stützkondensator kann bei diesen langen Zuleitungen auch nicht schaden
Der Quellcode ist nicht wirklich schön, sollte - Dank Python - aber verständlich sein.
Die Python-Bibliothek ctlab.py ist sicher auch für eigene Projekte verwendbar und natürlich erweiterbar.
Perspektivisch könnte man noch folgende Funktionen einbauen:
- Auswahl eines Chips aus einer Liste und Test ob er seine Funktion erfüllt,
- Unterstützung für Speicherchips bzw. Flip/Flops.
Wobei ich für letzteres noch keine Idee habe, wie man das Automatisieren könnte.
Falls man das Timing der Signale berücksichtigen möchte, bräuchte man ein komplett anderes Konzept.
Viele Grüße
Bart
Anhang: Quellcode
Code: Alles auswählen
#! /usr/bin/env python
# ctlab.py
import serial
import re
class ctlab( object):
def __init__( self, serialport):
self.ada = None
self.dcg = None
self.edl = None
self.dds = None
self.div = None
self.ser = serial.Serial( serialport, 38400, timeout = 0.1)
self.ser.parity = 'N'
self.ser.bytesize = 8
self.ser.stopbits = 1
def send_command_only( self, device, command):
def xorstr( s):
result = 0
for c in s:
result = result ^ ord( c)
return result
if self.ser.inWaiting() != 0:
print "ERROR: obsolete buffer content:", self.ser.readline().strip()
self.ser.flushInput()
cmdstr = "%i:%s!" % ( device, command)
self.ser.write( "%s$%02x\r" % ( cmdstr, xorstr( cmdstr)))
#print( "%s$%02x" % ( cmdstr, xorstr( cmdstr)))
return cmdstr
def send_command( self, device, command):
while True:
scmd = self.send_command_only( device, command)
# wait for result
while self.ser.inWaiting() == 0:
None
answer = self.ser.readline().strip()
resdev = re.search( r"#(.*?):", answer)
if resdev != None:
resdev = resdev.group( 1)
rescode = re.search( r"\[(.*?)\]", answer)
if rescode != None:
rescode = rescode.group( 1)
if rescode == "OK":
return
print "ERROR: command (%s) answer: %s (device: %s, code %s)" % ( scmd, answer, resdev, rescode)
def send_command_result( self, device, command):
while True:
cmdstr = self.send_command_only( device, command)
while self.ser.inWaiting() == 0:
None
result = self.ser.readline().strip()
if result.find( "7 [CHKSUM]") == -1:
if result == cmdstr:
return None
return result
def read_value( self, device, commnad):
result = None
while result == None:
value = self.send_command_result( device, commnad)
#print "command %s result %s" % ( commnad, value)
result = re.search( r"=(.*?)$", value)
return float( result.group( 1).strip())
def get_device_type( self, value):
result = re.search( r"\[(.*?)\s", value)
if result != None:
return result.group( 1).strip()
return result
def get_device_options( self, value):
result = re.search( r"\[.*;(.*?)\]", value)
if result != None:
return result.group( 1).strip()
return result
def check_devices( self, verbose = True):
if verbose:
print "Scan for devices:"
for index in range( 8):
result = self.send_command_result( index, "idn?")
if result != None:
devicetype = self.get_device_type( result)
if verbose:
print "Index %d: Device: %3s" % ( index, devicetype),
options = self.get_device_options( result)
if verbose:
if options != None:
print " Options: %s" % options
else:
print
# search for device indices
if ( devicetype == "ADA") and ( self.ada < 0):
self.ada = index
if ( devicetype == "DCG") and ( self.dcg < 0):
self.dcg = index
if ( devicetype == "DDS") and ( self.dds < 0):
self.dds = index
if ( devicetype == "EDL") and ( self.edl < 0):
self.edl = index
def dcg_measure_power( self):
return self.read_value( self.dcg, "msw?")
def dcg_measure_voltage( self):
return self.read_value( self.dcg, "msv?")
def dcg_set_voltage( self, voltage):
self.send_command( self.dcg, "dcv=%.3f" % voltage)
def dcg_measure_current_ma( self):
return self.read_value( self.dcg, "msa 1?")
def dcg_set_current( self, current):
self.send_command( self.dcg, "dca=%.3f" % ( current))
def dcg_set_current_ma( self, current):
self.send_command( self.dcg, "dca 1=%.3f" % ( current))
def dds_set_frequency( self, frequency):
self.send_command( self.dds, "frq=%.1f" % frequency)
def dds_set_amplitude( self, amplitude):
self.send_command( self.dds, "lvl=%.1f" % amplitude)
def ada_set_da_voltage( self, pin, voltage):
self.send_command( self.ada, "val%d=%.4f" % ( 20 + pin, frequency))
def ada_measure_adcint( self, pin):
return self.read_value( self.ada, "val %d?" % ( 0 + pin))
def ada_measure_adc( self, pin):
return self.read_value( self.ada, "val %d?" % ( 10 + pin))
def ada_set_direction( self, port, value):
self.send_command( self.ada, "dir%d=%d" % ( port, value))
def ada_set_port( self, port, value):
self.send_command( self.ada, "pio%d=%d" % ( port, value))
def ada_get_port( self, port):
return int( self.read_value( self.ada, "pio %d?" % port))
Code: Alles auswählen
#! /usr/bin/env python
# graycode.py
def bin(s):
return str(s) if s<=1 else bin(s>>1) + str(s&1)
import math
def log2(x):
""" Return log base 2 of x. """
#return math.log(x) / math.log(2)
return int( math.log( x, 2))
""" http://www.barricane.com/python-grey-code-algorithm """
def gray(i):
""" This function returns the i'th Gray Code. It is recursive and operates in O(log n) time. """
if i == 0:
return 0
if i == 1:
return 1
ln2 = int(log2(i))
# the grey code of index i is the same as the gray code of an index an
# equal distance on the other side of ln2-0.5, but with bit ln2 set
pivot = 2**(ln2) - 0.5 # TODO: double everything so that we use no floats
delta = i - pivot
mirror = int(pivot - delta)
x = gray(mirror) # get the grey code of the 'mirror' value
x = x + 2**(ln2) # set the high bit
return x
def main():
for i in range( 256):
print i, bin( 256 + gray( i))[1:]
if __name__ == "__main__":
main()
Code: Alles auswählen
#! /usr/bin/env python
# digitaltester.py
serialport = '/dev/ttyUSB0'
import ctlab
import graycode
import sys
############################################################
## helper stuff
## progress bar
############################################################
## found here:
# http://stackoverflow.com/questions/3160699/python-progress-bar/15860757#15860757
# update_progress() : Displays or updates a console progress bar
## Accepts a float between 0 and 1. Any int will be converted to a float.
## A value under 0 represents a 'halt'.
## A value at 1 or bigger represents 100%
def update_progress( progress, barLength = 10):
#barLength = 10 # Modify this to change the length of the progress bar
status = ""
if isinstance(progress, int):
progress = float(progress)
if not isinstance(progress, float):
progress = 0
status = "error: progress var must be float\r\n"
if progress < 0:
progress = 0
status = "Halt...\r\n"
if progress >= 1:
progress = 1
status = "Done...\r\n"
block = int(round(barLength*progress))
text = "\r[{0}] {1}% {2}".format( "#"*block + "-"*(barLength-block), "%2.0f" % (progress*100), status)
sys.stdout.write(text)
sys.stdout.flush()
############################################################
## helper stuff
## functions for bit testing
############################################################
# testBit() returns a nonzero result, 2**offset, if the bit at 'offset' is one.
def testBit(int_type, offset):
mask = 1 << offset
return(int_type & mask)
def testBit01(int_type, offset):
if testBit(int_type, offset) == 0:
return 0
return 1
############################################################
## helper stuff
## bit manipulation stuff
############################################################
# setBit() returns an integer with the bit at 'offset' set to 1.
def setBit(int_type, offset):
mask = 1 << offset
return(int_type | mask)
# clearBit() returns an integer with the bit at 'offset' cleared.
def clearBit(int_type, offset):
mask = ~(1 << offset)
return(int_type & mask)
# toggleBit() returns an integer with the bit at 'offset' inverted, 0 -> 1 and 1 -> 0.
def toggleBit(int_type, offset):
mask = 1 << offset
return(int_type ^ mask)
def xor( a, b):
return (a and not b) or (not a and b)
############################################################
## helper stuff
## list manipulation
############################################################
def deldoub1(liste):
res = []
tmp = []
for item in liste:
if not item[0] in tmp:
tmp.append(item[0])
res.append(item)
return res
############################################################
## main stuff
############################################################
class digitaltester( object):
def __init__( self, serialport, verbose = True):
self.lab = ctlab.ctlab( serialport)
self.lab.check_devices( verbose)
if self.lab.dcg == None:
print "Error: missing DCG"
raise StandardError
if self.lab.ada == None:
print "Error: missing ADA"
raise StandardError
# 100 mV
self.tolerance = 0.1
self.inputs = []
self.outputs = []
# switch power on
self.lab.dcg_set_voltage( 5.0)
self.lab.dcg_set_current_ma( 50)
# init digital ports
self.set_all_pin_voltage_none()
def set_all_pin_voltage_none( self):
self.port2 = 0
self.port3 = 0
self.dir2 = 0
self.dir3 = 0
self.update_pins()
def check_number_of_pins( self):
if self.test_pin( 7) == 'GND':
self.pinrange = range( 1, 7) + range( 10, 16)
self.pins = 14
else:
self.pinrange = range( 1, 8) + range( 9, 16)
self.pins = 16
return self.pins
def correct_pin_number( self, pin):
# only for 14 pin devices
if ( self.pins == 14) and ( pin > 7):
return pin - 2
else:
return pin
def set_pin_voltage( self, pin, value):
if value == None:
if ( pin >= 1) and ( pin <= 7):
self.dir2 = clearBit( self.dir2, pin - 1)
if ( pin >= 9) and ( pin <= 15):
self.dir3 = clearBit( self.dir3, pin - 9)
elif value == 0:
if ( pin >= 1) and ( pin <= 7):
self.dir2 = setBit( self.dir2, pin - 1)
self.port2 = clearBit( self.port2, pin - 1)
if ( pin >= 9) and ( pin <= 15):
self.dir3 = setBit( self.dir3, pin - 9)
self.port3 = clearBit( self.port3, pin - 9)
else:
if ( pin >= 1) and ( pin <= 7):
self.dir2 = setBit( self.dir2, pin - 1)
self.port2 = setBit( self.port2, pin - 1)
if ( pin >= 9) and ( pin <= 15):
self.dir3 = setBit( self.dir3, pin - 9)
self.port3 = setBit( self.port3, pin - 9)
def update_pins( self):
self.lab.ada_set_direction( 2, self.dir2)
self.lab.ada_set_direction( 3, self.dir3)
self.lab.ada_set_port( 2, self.port2)
self.lab.ada_set_port( 3, self.port3)
def get_pin_voltage( self, pin):
if ( pin >= 1) and ( pin <= 6):
return self.lab.ada_measure_adcint( 1 + pin)
if pin == 7:
return self.lab.ada_measure_adc( 7)
if pin == 8:
return 0.0
if (pin >= 9) and ( pin <= 15):
return self.lab.ada_measure_adc( pin - 9)
if pin == 16:
return self.lab.dcg_measure_voltage()
def get_pin_voltage_digital( self, pin):
if ( pin >= 1) and ( pin <= 7):
return testBit01( self.port2_in, pin - 1)
if pin == 8:
return 0
if (pin >= 9) and ( pin <= 15):
return testBit01( self.port3_in, pin - 9)
if pin == 16:
return 1
def read_pin_pattern( self):
result = ""
for pin in self.pinrange:
result += self.voltage_to_digital( self.get_pin_voltage( pin), self.tolerance)
return result
def voltage_to_digital( self, voltage, tolerance):
if ( voltage >= -tolerance) and ( voltage <= tolerance):
return '0'
elif ( voltage >= 5 - tolerance) and ( voltage <= 5 + tolerance):
return '1'
else:
return 'X'
return result
def selftest( self):
expected_pin_pattern = "10000000000000"
error_count = 0
for pin in range( 1, 8) + range( 9, 16):
# show activity in display
if pin == 1:
self.lab.send_command( self.lab.ada, "DSP=32")
if pin == 9:
self.lab.send_command( self.lab.ada, "DSP=33")
print "Testing pin %2d" % ( pin, self.correct_pin_number( pin)),
self.set_pin_voltage( pin, 1)
self.update_pins()
pattern = self.read_pin_pattern()
if pattern != expected_pin_pattern:
error_count += 1
print pattern, ( pattern == expected_pin_pattern)
self.set_pin_voltage( pin, 0)
self.update_pins()
# 'rotate' pattern
expected_pin_pattern = expected_pin_pattern[-1:] + expected_pin_pattern[:-1]
return error_count
def check_pin_directions( self):
def current_ma( set_voltage, voltage):
return ( set_voltage - voltage)
self.set_all_pin_voltage_none()
#print "Pinrange: ", self.pinrange
for pin in self.pinrange:
# show activity in display
if pin == 1:
self.lab.send_command( self.lab.ada, "DSP=32")
if pin == 9:
self.lab.send_command( self.lab.ada, "DSP=33")
print "Testing pin %2d" % ( self.correct_pin_number( pin)),
self.set_pin_voltage( pin, 0)
self.update_pins()
low = self.get_pin_voltage( pin)
self.set_pin_voltage( pin, 1)
self.update_pins()
high = self.get_pin_voltage( pin)
self.set_pin_voltage( pin, None)
self.update_pins()
highZ = self.get_pin_voltage( pin)
dir = self.guess_pin_direction( low, high, highZ)
print " low: %.2f V hi: %.2f V hiZ: %.2f V" % ( low, high, highZ),
#print " I_low: %.2f mA I_hi: %-.2f mA " % ( current_ma( 0.0, low), current_ma( highZ, high)),
print " --> direction: %s" % dir
if dir == 'Input':
self.inputs.append( pin)
if dir == 'Output':
self.outputs.append( pin)
self.set_all_pin_voltage_none()
def test_pin( self, pin):
self.set_pin_voltage( pin, 0)
self.update_pins()
low = self.get_pin_voltage( pin)
self.set_pin_voltage( pin, 1)
self.update_pins()
high = self.get_pin_voltage( pin)
self.set_pin_voltage( pin, None)
self.update_pins()
highZ = self.get_pin_voltage( pin)
return self.guess_pin_direction( low, high, highZ)
def guess_pin_direction( self, low, high, highZ):
tolerance = 2.0 # V
if ( low < 0.01) and ( high < 0.01) and ( highZ < 0.01):
return "GND"
elif ( low < tolerance) and ( high < 5 - tolerance):
return "Output"
elif ( low > tolerance) and ( high > 5 - tolerance):
return "Output"
elif ( low < tolerance) and ( high > 5 - tolerance):
return "Input"
else:
return "Unknown"
def check_logic( self):
mapping = []
count = 2**len( self.inputs)
print "check %d logic combinations" % count
oldinputs = graycode.gray( count - 1)
self.set_inputs( oldinputs)
oldoutput = self.read_outputs_digital()
for index in range( count):
update_progress( 1.0 * index / count, barLength = 80)
inputs = graycode.gray( index)
self.set_inputs( inputs)
output = self.read_outputs_digital()
#print "get out ", bin( output)
outdiff = output ^ oldoutput
if outdiff > 0:
inpin = graycode.log2( inputs ^ oldinputs)
outpin = graycode.log2( outdiff)
#pin = { "in" : inpin, "out": outpin}
pin = [ inpin, outpin]
if mapping.count( pin) == 0:
mapping.append( pin)
oldinputs = inputs
oldoutput = output
update_progress( 1.0, barLength = 80)
# sort mapping
#mapping.sort()
# uniq mapping
#print "einfach"
#print deldoub1( mapping)
# replace 'virtual pins' by real pin numbers
self.pinmap = []
for element in mapping:
self.pinmap.append([ self.inputs[ element[ 0]], self.outputs[ element[ 1]]])
self.equations = []
for o in self.outputs:
equation = [ [el[ 0] for el in self.pinmap if el[ 1] == o], o]
self.equations.append( equation)
return self.equations
# input: list with to elements
# element 0: list of input pins
# element 1: output pin
def check_equation( self, equation):
count = 2**len( equation[ 0])
shift = 0
result = ''
for index in range( count):
for pin in equation[ 0]:
self.set_pin_voltage( pin, testBit01( index, equation[ 0].index( pin)))
self.update_pins()
result += self.voltage_to_digital( self.get_pin_voltage( equation[ 1]), tolerance = 2)
shift += 1
return result
def set_inputs( self, value):
index = 0
for pin in self.inputs:
self.set_pin_voltage( pin, testBit01( value, index))
index += 1
self.update_pins()
def read_outputs( self):
for index in self.outputs:
print self.voltage_to_digital( self.get_pin_voltage( index), tolerance = 1.2),
print
def read_outputs_digital( self):
self.port2_in = self.lab.ada_get_port( 2)
self.port3_in = self.lab.ada_get_port( 3)
#print bin( self.port2_in), bin( self.port3_in),
shift = 0
result = 0
for index in self.outputs:
result += self.get_pin_voltage_digital( index) << shift
shift += 1
return result
def cleanup( self):
self.set_all_pin_voltage_none()
# switch (nearly) off
self.lab.dcg_set_voltage( 0.1)
self.lab.dcg_set_current_ma( 1)
def logic_table_name( self, value):
# no input
if value == "0":
return "logic low"
if value == "1":
return "logic high"
# one input
if value == "00":
return "logic low"
if value == "01":
return "EQUAL"
if value == "10":
return "NOT"
if value == "11":
return "logic high"
# two inputs
if value == "0000":
return "logic low"
if value == "0001":
return "AND"
if value == "0110":
return "XOR"
if value == "0111":
return "OR"
if value == "1000":
return "NOR"
if value == "1001":
return "XNOR"
if value == "1110":
return "NAND"
if value == "1111":
return "logic high"
else:
return "unknown"
def main():
print "Initialisation:",
digitest = digitaltester( serialport, verbose = False)
# check configuration
if digitest.lab.ada == None:
print "missing ADA module"
exit()
if digitest.lab.dcg == None:
print "missing DCG module"
exit()
print "ok"
# make sure no DUT is connected
#print "Selftest"
#digitest.selftest()
print "Power: ",
voltage = digitest.lab.dcg_measure_voltage()
current = digitest.lab.dcg_measure_current_ma()
power = digitest.lab.dcg_measure_power()
print "U (%.2f V) * " % voltage,
print "I (%.2f mA) = " % current,
print "P (%.1f mW)" % ( power * 1000)
if ( power > 0.200) or ( voltage < 4.5):
digitest.cleanup()
print "power consumtion too high!"
exit()
try:
# check number of pins
# also set correct ranges
print "%d-pin device" % digitest.check_number_of_pins()
print "check DUT pin directions"
digitest.check_pin_directions()
print "%d input pins: %s" % ( len( digitest.inputs), map( digitest.correct_pin_number, digitest.inputs))
print "%d output pins: %s" % ( len( digitest.outputs), map( digitest.correct_pin_number, digitest.outputs))
if len( digitest.inputs) == 0:
print "error: no input pins found"
raise StandardError
if len( digitest.outputs) == 0:
print "error: no output pins found"
raise StandardError
#( digitest.inputs, digitest.outputs ) = ( digitest.outputs, digitest.inputs)
equations = digitest.check_logic()
print "found following equations:"
for equation in equations:
check = digitest.check_equation( equation)
print "input(s): %25s, output: %2d --> %s (%s)" % ( map( digitest.correct_pin_number, equation[0]), digitest.correct_pin_number( equation[ 1]), check, digitest.logic_table_name( check))
power_per_gate = power * 1000 / len( digitest.outputs)
print "Power per gate: P_gate = %.1f mW" % power_per_gate
print "guess Technology :",
if power_per_gate < 0.5:
print "CMOS"
elif power_per_gate < 5:
print "Low Power TTL"
elif power_per_gate < 20:
print "TTL"
else:
print "High Speed TTL"
except KeyboardInterrupt, StandardError:
None
#except IndexError:
# print "program error"
print "Cleanup"
digitest.cleanup()
print "Done"
main()