DIP/DIL Switch-based 8-Byte ROM via daisy-chained 4051 multiplexers and Arduino



Circuit schematics & Pictures (click on any picture to enlarge)

Originalmente concepito come tributo alla Core Rope memory cucita a mano dell'Apollo Guidance Computer, la mia idea è stata successivamente semplificata a "trova un modo per immagazzinare una breve stringa di caratteri in una serie di DIP switch, poi recuperala e mostrala su un display LCD tramite Arduino UNO".
Sono stato ispirato dal (o meglio: ho plagiato il) progetto descritto qui: https://forum.arduino.cc/index.php?topic=446780.0: il prototipo si basa su un circuito molto semplice e su nove multiplexer a 8 canali connessi in modalità "daisy chain" (un multiplexer guida gli altri otto).
Considerato che ogni IC 4051 può gestire fino a 8 linee, il numero totale di bit nella ROM sarà 64 (8 Byte), con ogni bit settabile individualmente tramite il suo DIP switch. Un separato multiplexer 4051 legge uno a uno i bit di ogni Byte, quello rimanente seleziona quale Byte leggere.
La ROM sarà testata memorizzando 8 Byte di testo come caratteri ASCII, che saranno mostrati da Arduino su un display LCD a 2 linee.

Originally conceived as a tribute to the hand-sewn Core Rope memories of the Apollo Guidance Computer, my idea was successively simplified to "find a way to store a short character string in a series of DIP switches, then retrieve and display that text via an Arduino UNO".
I was inspired by (or better: I plagiarized) the project described here: https://forum.arduino.cc/index.php?topic=446780.0: the prototype is based on a very simple circuit and nine 8-channel multiplexers in a "MUX daisy chain" arrangement (one multiplexer drives the other eight).
Considering that each 4051 IC can manage up to 8 lines, the total number of bits in the ROM will be 64 (8 Byte), with each bit set independently via its own dip switch. A separate 4051 multiplexer reads one by one the bits in each Byte, the remaining one selects which byte to read.
The ROM will be tested by storing 8 Byte of text as ASCII characters, that will be displayed by Arduino on a 2 line LCD display.

Per ogni Byte, sono a disposizione due banchi da 4 DIP switch. La scelta di blocchi da 4 interruttori è stata dettata solo dalla convenienza economica (...una svendita).
Benché temessi che, per consentire una corretta lettura del voltaggio da parte di integrati non-sinking, si dovessero alimentare i banchi di ROM con un separato alimentatore da 5V, è stato evidente che letture egualmente affidabili si possono ottenere alimentando il circuito interno della ROM e quello esterno dei multiplexer, con due pin indipendenti 5V/GND dello stesso Arduino.
Per rendere il cablaggio più facile, ho usato reti resistive bussed da 10 KΩ (5 pin + common, anche esse un affare: difatti, mi sarebbero bastati 4 pin + common).
La breadboard da 4096 punti è stata sfruttata in modo insolito: in particolare, le due tracce di alimentazione lungo la mezzeria sono state connesse e usate come uscita comune per tutti i DIP switch.
Questa disposizione ha escluso a priori la possibilità di alimentare ogni banco nel modo più facile, con una connessione tra il "common" di ogni rete resistiva e un punto delle tracce di alimentazione. La linea GND è stata propagata come illustrato nella foto a destra. Forse noterete immediatamente le tracce di un errore marchiano da parte mia: il circuito corretto ha la fase +5V connesse all'anello interno, e le reti resistive connesse a GND all'esterno, e ogni interruttore chiuso lascia passare la corrente e viene letto come "alto".

For each Byte, two banks of 4-bit DIP switches are provided. The choice of 4-switch packages has been dictated only by economic convenience (...a sale).
Although I expected that - to allow correct voltage reading by non-sinking IC's - the ROM banks should be independently powered by a separate 5V DC supply, it became apparent that equally successful readings can be obtained by powering the inner ROM circuit and the outer circuit with multiplexers, by two independent +5V/GND pins on the same Arduino.
To make wiring easier, bussed 10KΩ resistive network (5 pins +common, also a bargain: in fact, I would have needed just 4 pins+common) have been used.
The 4096 points breadboard has been exploited in an unusual way: in particular, the two power rails along the midline have been connected and used as common exit for all the DIP switches.
This arrangement ruled out the possibility to power each bank the easy way, via one connection per resistive network to one of the breadboard power rails. The GND line was propagated as illustrated in the picture at left. Perhaps you'll find immediatly the traces of an huge mistake on my part: the correct circuit has +5V connected to the inner ring, grounded resistor chains on the outer ring, and each closed switch lets the current flow and is read as "high".

La continuità del circuito interno è stata testata molto facilmente: con tutti gli switch aperti, il circuito nel suo assieme è aperto. Chiudere ogni singolo switch comporta una lettura di 10 KΩ, chiudere una qualsiasi coppia dà una letture di 5 KΩ, e così via.

Inner circuit continuity was tested very easily: with all switch open, the circuit as a whole is open. Closing any single switch gives a reading of 10 KΩ, closing any two gives a reading of 5 KΩ, and so on.

Considerato che l'intero progetto riguarda scansire bit per bit la ROM, per ottenere una lettura "high" per ogni interruttore chiuso ("bit alto"), mi serve un circuito in cui il voltaggio risente il meno possibile del numero di interruttori chiusi. Essendo privo delle nozioni di base, ho dovuto verificare il voltaggio in varie configurazioni della ROM.
Mi è occorso un po' di tempo per capire come verificare il voltaggio. Poi ho capito che, per far sì che la corrente scorresse nel tester solo quando l'interruttore era chiuso, avevo bisogno che la corrente dall'alimentazione attraversasse il tester, con il GND comune del tester connesso a monte dell'interruttore da testare.
Ovviamente, qualsiasi interruttore chiuso lascia scorrere la corrente. La chiusura di ciascun interruttore aggiuntivo causa una caduta di tensione di pochi millivolt. Questa conclusione può aiutare a decidere se usare o meno porte digitali o analogiche su Arduino.

Destra: tutti gli switch chiusi, nessun flusso di corrente in alcuna linea

Considering that the whole project is about scanning bit by bit the ROM, to get an "high" reading for each closed switch (high bit), I need a circuit where the voltage is affected as little as possible by the number of closed switches. Lacking basic notions, I needed to check the voltage in different ROM configurations.
It took a little time for me to understand how I could I check the voltage. Then I grasped that, to let the current flow through my tester only in case a switch was closed, I needed to let the current from the power supply pass through the tester, with the tester's common ground connected uphill from the switch that I wanted to test.
Quite obviously, any open switch wouldn't let the current flow. Each additional closed switch apparently causes a voltage drop of just a few mV. This conclusion may determine whether or not to use digital or analog Arduino ports.

Left: all switches closed, no current flow in any line

Destra: un interruttore chiuso, lettura 5.93 V

Left: one switch closed, 5.93 V reading

Destra: tutti gli interruttori chiusi,lettura 5.52 V in qualsiasi linea

Left: all switches closed, 5.52 V reading in any line

Lo "stadio di controllo" (qui citato anche come "circuito esterno") coi multiplexer connessi a margherita si è ispirato allo schema a destra, di PaulRB dal seguente post nel forum di Arduino: https://forum.arduino.cc/index.php?topic=446780.0 in cui Arduino è utilizzato per controllare 64 linee in un processo a due passi:

  1. (usando le tre address line del "master") seleziona uno dei 4051 "schiavi" (uno per "Byte") attraverso le tre linee S0/S1/S2 del 4051 "master"
  2. (usando le tre address line in comune tra tutti gli "slave") considerato che tutti i pin S0, S1 e S2 dei "Byte" sono in comune, a prescindere da quale "Byte" è selezionato, basta ciclare attraverso gli 8 indirizzi disponibili: solo il "Byte" selezionato risponderà, e farà una lettura per ogni bit
  3. vai a 1) e seleziona il successivo "Byte"

The "control stage" (here also cited as "outer circuit") by daisy-chained multiplexers was inspired by the schematics on the left, by PaulRB from the following Arduino forum post: https://forum.arduino.cc/index.php?topic=446780.0 where Arduino is used to control 64 lines in a two-step process:

  1. (using the three address lines of the "master") select one of the "slave" 4051 IC's (one per "Byte") via the three lines S0/S1/S2 of the "master" 4051
  2. (using the three address lines shared by all the "slaves") considering that all the S0, the S1 and the S2 pins of the "Bytes" are in common, regardless of which "Byte" is selected, it suffices to cycle through the eight available addresses: only the selected "Byte" will answer, and make a reading for each bit
  3. go to 1) and select the next "Byte"

Ho messo lo schema su breadboard come illustrato a destra: la foto e il prototipo Fritzing non includono le connessioni tra ogni 4951 "slave" (Byte) e i suoi 8 DIP Switch ("bit"), che sono ovviamente necessarie per far funzionare il prototipo ma che renderebbero l'immagine illeggibile. Anche la connessione a ground dei pin 6 e 7 in ogni 4051 manca dalla fotografia, come pure i ponticelli per ripristinare la continuità delle linee di alimentazione in alto, interrotte alla mezzeria.

I breadboarded the schematics as illustrated at left: the photo and the Fritzing prototype do not include the lines between each "slave" 4051 (Byte) and its 8 DIP switches ("bits"), that are obviously needed to make the prototype work but that would make the picture unreadable. Also the connection to ground of pin 6&7 in each 4051 are missing from the photo, as well as the "bridges" to restore continuity of the topmost horizontal power rails, interrupted at midway.

Destra: Schema Fritzing con il solo "Byte" 0 connesso ai suoi "bit"

Left: Fritzing schema with only "Byte" 0 connected to its "bits"

Destra: Dettaglio della connessione tra ciascun "Byte" 0 e i suoi "bit"

Left: Detail of the connection between each "Byte" and its "bits"

Destra: L'intera breadboard con ogni "Byte" connesso ai suoi "bit"


L'immagine a destra è stata scattata con il circuito interno ancora collegato a un alimentatore esterno con polarità invertita.
Voglio ribadire che:
  1. I DIP switch vanno alimentati con +5V che corre sulle linee al centro della breadboard, e l'anello esterno costituito da reti resistive interconnesse.
  2. Entrambi i circuiti (quello che alimenta la schiera dei DIP switch, e quello che alimenta e connette la "daisy chain" di multiplexer) possono benissimo essere alimentati dallo stesso Arduino, a patto che per ciascun circuito si utilizzi una coppia separata di pin +5V/GND.
Aggiungo che so benissimo che avrei potuto svolgere una "digital read" dello stato degli switch, anziché un'analog read. La lettura analogica era una soluzione "paracadute" concepita quando non ero ancora sicuro se chiudere molti switch avrebbe causato un calo di tensione tale da prevenire il raggiungimento del voltaggio minimo necessartio per un "digital HIGH". Benché ora il circuito sia sicuro da tale punto di vista, ho mantenuto il conservativo approccio analogico.

Left: The full breadboard with each "Byte" connected to its "bits"


The picture at left was taken with the inner circuit still attached to an external power supply with reversed polarity.
I want to state again that:
  1. The DIP switches should be powered with +5V running along the inner rails at the center of the breadboard, and the outer ring made of interconnected resistive networks.
  2. Both the circuits (the one that powers the DIP switch array, and the one that powers and connects the daisy-chained multiplexers) may well be powered by the same Arduino, provided that a separate +5V/GND couple of pins is used for each circuit.
I add that I'm well aware that I could have performed a digital read of the switch status instead of an analog read. Analog read was a "parachute" solution when I still wasn't sure whether closing many switches would cause a voltage drop that would have prevented reaching the minimum voltage for digital HIGH. Even though the circuit now is safe in that respect, I retained the conservative analog approach.

Destra: Il prototipo completamente assemblato, comprensivo di un display LCD che mostra i contenuti della ROM da 8 Byte

Left: the fully assembled prototype, including an LCD display that shows the content of the 8-Byte ROM

Sommario per la costruzione

Manufacturing summary


Source code

/*
 
DIP/DIL Switch-based 8-Byte ROM via daisy-chained 4051 multiplexers and Arduino

Circuit and comments: 
See http://www.cesarebrizio.it/Arduino/DIP_Switch_ROM.html
Circuit is as illustrated here: http://www.cesarebrizio.it/Arduino/Final%20Fritzing%20Schema.jpg
 
 created 03 Feb 2019
 by Cesare Brizio
 modified ----

This example code is in the public domain.

Originally conceived as a tribute to the hand-sewn Core Rope memories of the Apollo Guidance Computer, 
my idea was successively simplified to "find a way to store a short character string in a series of 
DIP switches, then retrieve and display that text via an Arduino UNO".

I was inspired by (or better: I plagiarized) the project and the schematics by PaulRB described here: 

https://forum.arduino.cc/index.php?topic=446780.0

I have 64 DIP switches, each one capable of storing a bit of information, in eaght groups of eight switches
(closed = non-null voltage reading = binary 1 - opened = null voltage reading = binary 0) 

Each group of eight switches, that I will call a "Byte", is connected to the eight I/O channels of a 
separate "slave" 4051. 

The three address lines S0/S1/S2 of all the slave multiplexers are common: one S0, one S1, and one S2 address all
the slave multiplexers simultaneously

I am using a master multiplexer 4051 whose I/O channels are connected to the com out/in of each slave multiplexer
Thus, by a two-step process:
a) select the "byte" (=slave multiplexer) to read via the S0/S1/S2 of the master 4051
b) select which "bit" to read from each slave via the common S0/S1/S2 of the slave 4051's
I can access one switch at a time and make individual reading.

As each byte is meant to store an 8-bit ASCII character, I compose the bytes by the successive readings from
their bits, and translate them into ASCII values, that are added to the string.

The string obtained is displayed on an LCD display driven by Arduino.

By changing the open/closed DIP switch configuration, I can vary the displayed string in real time.


  ==========================
  The circuit for LCD board:
  ==========================
 * LCD RS pin to digital pin 15
 * LCD Enable pin to digital pin 14
 * LCD D4 pin to digital pin 5
 * LCD D5 pin to digital pin 4
 * LCD D6 pin to digital pin 3
 * LCD D7 pin to digital pin 2
 * LCD R/W pin to ground
 * LCD VSS pin to ground
 * LCD VCC pin to 5V
 * 10K resistor:
 * ends to +5V and ground
 * wiper to LCD VO pin (pin 3)

*/
// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
const int rs = 13, en = 12, d4 = 11, d5 = 10, d6 = 9, d7 = 8;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

// "Slave" 4051 ADDRESS PINS :
int slave_S0 = 2;
int slave_S1 = 3;
int slave_S2 = 4;

// "Master" 4051 ADDRESS PINS :
int master_S0 = 5;
int master_S1 = 6;
int master_S2 = 7;

//
int analogPin = A0;

int ReadVolt = 0;

int bitValue;

int asciiCode;

char asciiChar ="";

int powerOfTwo = 0;

char asciiString[8];



void setup() {
    // set up the LCD's number of columns and rows:
    lcd.begin(16, 2);
    // Print a message to the LCD.
    lcd.print("8-bit ROM says: ");
    // CONFIGURE ADDRESS PINS
    pinMode(master_S0, OUTPUT);
    pinMode(master_S1, OUTPUT);
    pinMode(master_S2, OUTPUT);
    pinMode(slave_S0, OUTPUT);
    pinMode(slave_S1, OUTPUT);
    pinMode(slave_S2, OUTPUT);
    
  Serial.begin(9600);   
  
}


void loop() {

    for( int x = 0; x < 8;  ++x )
          asciiString[x] = (char)0;

 
    // For each "Byte" 
    for (int i = 0; i < 8; i++) {
        
        // Select current Byte
        cycle4051('M',i);
        
        delay(5);
        
        //Serial.print("Byte ");
        //Serial.print(i);
        //Serial.println(" readings:");
        //Serial.print(">"); 

        asciiCode=0;

        for (int j = 7; j >=0; j--) {
        
            // Select current bit  
            cycle4051('S',j);   

            delay(5);
    
            ReadVolt = analogRead(analogPin); 
            
            if(ReadVolt>500) // check if current is running
            {
                bitValue=1; // Switch closed, bit high
                asciiCode = asciiCode + powerOfTwo;
            }
            else
            {
                bitValue=0; // Switch open, bit low
            }  
            
            //Serial.print(bitValue); 
            //Serial.print(ReadVolt); 
            //Serial.print("|"); 

        }

        //Serial.print(asciiCode); 
        //Serial.println("|");
        asciiString[i]=asciiCode;

    
    }
    //Serial.println("--- End series");    
    //Serial.println(asciiString);   

    for( int z = 0; z < 8;  ++z )
        {
        // (note: line 1 is the second row, since counting begins with 0):
        lcd.setCursor(z, 1);
        // print on the LCD the current character from the string:
        lcd.print(asciiString[z]);
        // print it also on the Serial Monitor
        Serial.print(asciiString[z]);
        }      
    Serial.println("");
    delay(10);
}


void cycle4051(char master_slave, int stepNumber) {
     // It's very obvious that this subprogram could have been written in
     // a much more compact form. I prefer to use a more verbose style
     // for clarity, readability and ease of maintenance.
     // I do know that there are may valid alternatives to the stratagem of 
     // using the powerOfTwo variable - the native POW() exponentiation function
     // that I could well have used has its drawbacks.
     switch (master_slave) {
        case 'M':
            //Next "Byte" (= next slave 4051)
            //Select the next slave 4051, connected to one of the channels of the master 4051 
            switch (stepNumber) {
               case 0:
                   //Select Channel 0 of the master 4051
                   digitalWrite(master_S2, LOW);
                   digitalWrite(master_S1, LOW);
                   digitalWrite(master_S0, LOW);
                   break;
               case 1:
                   //Select Channel 1 of the master 4051
                   digitalWrite(master_S2, LOW);
                   digitalWrite(master_S1, LOW);
                   digitalWrite(master_S0, HIGH);
                   break;
               case 2:
                   //Select Channel 2 of the master 4051
                   digitalWrite(master_S2, LOW);
                   digitalWrite(master_S1, HIGH);
                   digitalWrite(master_S0, LOW);
                   break;
               case 3:
                   //Select Channel 3 of the master 4051
                   digitalWrite(master_S2, LOW);
                   digitalWrite(master_S1, HIGH);
                   digitalWrite(master_S0, HIGH);
                   break;
               case 4:
                   //Select Channel 4 of the master 4051
                   digitalWrite(master_S2, HIGH);
                   digitalWrite(master_S1, LOW);
                   digitalWrite(master_S0, LOW);
                   break;
               case 5:
                   //Select Channel 5 of the master 4051
                   digitalWrite(master_S2, HIGH);
                   digitalWrite(master_S1, LOW);
                   digitalWrite(master_S0, HIGH);
                   break;
               case 6:
                   //Select Channel 6 of the master 4051
                   digitalWrite(master_S2, HIGH);
                   digitalWrite(master_S1, HIGH);
                   digitalWrite(master_S0, LOW);
                   break;
               case 7:
                   //Select Channel 7 of the master 4051
                   digitalWrite(master_S2, HIGH);
                   digitalWrite(master_S1, HIGH);
                   digitalWrite(master_S0, HIGH);
                   break;
            }
            break;
        case 'S':
            //Next "bit" (= next channel in the currently selected slave 4051)
            //Select the next channel of the slave 4051, connected to one of the dip switches 
            switch (stepNumber) {
               case 0:
                   //Select Channel 0 of the slave 4051
                   digitalWrite(slave_S2, LOW);
                   digitalWrite(slave_S1, LOW);
                   digitalWrite(slave_S0, LOW);
                   powerOfTwo=1;
                   break;
               case 1:
                   //Select Channel 1 of the slave 4051
                   digitalWrite(slave_S2, LOW);
                   digitalWrite(slave_S1, LOW);
                   digitalWrite(slave_S0, HIGH);
                   powerOfTwo=2;                   
                   break;
               case 2:
                   //Select Channel 2 of the slave 4051
                   digitalWrite(slave_S2, LOW);
                   digitalWrite(slave_S1, HIGH);
                   digitalWrite(slave_S0, LOW);
                   powerOfTwo=4;                   
                   break;
               case 3:
                   //Select Channel 3 of the slave 4051
                   digitalWrite(slave_S2, LOW);
                   digitalWrite(slave_S1, HIGH);
                   digitalWrite(slave_S0, HIGH);
                   powerOfTwo=8;                   
                   break;
               case 4:
                   //Select Channel 4 of the slave 4051
                   digitalWrite(slave_S2, HIGH);
                   digitalWrite(slave_S1, LOW);
                   digitalWrite(slave_S0, LOW);
                   powerOfTwo=16;                   
                   break;
               case 5:
                   //Select Channel 5 of the slave 4051
                   digitalWrite(slave_S2, HIGH);
                   digitalWrite(slave_S1, LOW);
                   digitalWrite(slave_S0, HIGH);
                   powerOfTwo=32;                   
                   break;
               case 6:
                   //Select Channel 6 of the slave 4051
                   digitalWrite(slave_S2, HIGH);
                   digitalWrite(slave_S1, HIGH);
                   digitalWrite(slave_S0, LOW);
                   powerOfTwo=64;                   
                   break;
               case 7:
                   //Select Channel 7 of the slave 4051
                   digitalWrite(slave_S2, HIGH);
                   digitalWrite(slave_S1, HIGH);
                   digitalWrite(slave_S0, HIGH);
                   powerOfTwo=128;                   
                   break;
            }
            break;
    }


}