Multiple Shift Registers Test
In the last post, we successfully got a laser/sensor pair working with the laser tripwire test. This is great, but my design calls for 72 of these tripwires (36 on the X-axis and 36 on the Y-axis). Unfortunately, the Arduino Uno only has 14 digital input/output pins. So we need to find a way to read signals from 72 phototransistors with only 14 IO pins to work with. In this post, we’ll be testing a method of reading many inputs with just a few Digital Input pins.
Shift Registers to The Rescue!
Luckily, we’re not the first to run in to an issue like this. There’s a piece of hardware out there known as a shift register, which can take signals from multiple sources and transmit it through a single channel, or take signals from one source and transmit it to multiple channels. These two Types of shift registers are known as PISO (Parallel In/Serial Out) or SIPO (Serial In/ Parallel Out) respectively. Since we’re trying to pull data from many sources to just a few pins, we’ll need a PISO shift register. We’ll be using a SN74HC165 8-Bit Parallel Load Shift Register. Using this, we can read 8 phototransistors with a single Digital Input pin.
Shift Register Test 1
For this test, we’ll be ignoring the lasers, and just focusing on reading in data from our phototransistors. When the phototransistors are exposed to light (bright ambient light should do), they should send a value of 1 to our Arduino, and when the light is blocked, they should be sending a 0.
First we’ll wire up our shift registers and our phototransistors to our breadboard, then wire that breadboard to the Arduino with power and ground.
The shift register will have 3 connections to our Arduino (SH/!LD, CLK, and !CE). These 3 pins on the Arduino are set to OUTPUT, and the Arduino will use these to control the data it reads through the shift registers.
Then the shift register will be connected to an INPUT pin on the Arduino. This connection is where all our data will be passed through to our Arduino.
Once everything is wired up, and we’ve added our test code (below), we’ll go ahead and run our sketch, and see what data we get back through the serial monitor. We’ve set the whole thing up so for our initial reading, all our phototransistors are in bright light, so they should all be reading 1. Then for the next 8 readings, we’ll cover just one of the transistors, which should turn that one to 0, and for the final reading, we’ll cover all of them so they should all read 0.
1 Shift Register Test Sketch Code
// HARDWARE CONNECTIONS const int data_pin = 11; // Connect Pin 11 to SER_OUT (serial data out) const int shld_pin = 8; // Connect Pin 8 to SH/!LD (shift or active low load) const int clk_pin = 12; // Connect Pin 12 to CLK (the clock that times the shifting) const int ce_pin = 9; // Connect Pin 9 to !CE (clock enable, active low) byte incoming; // Variable to store the 8 values loaded from the shift register // The part that runs once void setup() { // Initialize serial to gain the power to obtain relevant information, 9600 baud Serial.begin(9600); // Initialize each digital pin to either output or input // We are commanding the shift register with each pin with the exception of the serial // data we get back on the data_pin line. pinMode(shld_pin, OUTPUT); pinMode(ce_pin, OUTPUT); pinMode(clk_pin, OUTPUT); pinMode(data_pin, INPUT); // Required initial states of these two pins according to the datasheet timing diagram digitalWrite(clk_pin, HIGH); digitalWrite(shld_pin, HIGH); } // The part that runs to infinity and beyond void loop() { incoming = read_shift_regs(); // Read the shift register, it likes that // Print out the values being read from the shift register Serial.println("\nThe incoming values of the shift register are: "); Serial.print("ABCDEFGH : "); print_byte(incoming); // Print every 1 and 0 that correlates with A through H //Serial.println(incoming,BIN); // This way works too but leaves out the leading zeros delay(10000); // Wait for some arbitrary amount of time } // This code is intended to trigger the shift register to grab values from it's A-H inputs byte read_shift_regs() { byte the_shifted = 0; // An 8 bit number to carry each bit value of A-H // Trigger loading the state of the A-H data lines into the shift register digitalWrite(shld_pin, LOW); delayMicroseconds(5); // Requires a delay here according to the datasheet timing diagram digitalWrite(shld_pin, HIGH); delayMicroseconds(5); // Required initial states of these two pins according to the datasheet timing diagram pinMode(clk_pin, OUTPUT); pinMode(data_pin, INPUT); digitalWrite(clk_pin, HIGH); digitalWrite(ce_pin, LOW); // Enable the clock // Get the A-H values the_shifted = shiftIn(data_pin, clk_pin, MSBFIRST); digitalWrite(ce_pin, HIGH); // Disable the clock return the_shifted; } // A function that prints all the 1's and 0's of a byte, so 8 bits +or- 2 void print_byte(byte val) { byte i; for(byte i=0; i<=7; i++) { Serial.print(val >> i & 1, BIN); // Magic bit shift, if you care look up the <<, >>, and & operators } Serial.print("\n"); // Go to the next line, do not collect $200 }
1 Shift Register Test Output
We can see from the output below, that it seems to be working! The 8 sensors, named A through H start off reading values of 1, then as I cover each one individually, we see the 0 bit move down the line. For the last one, I cover all 8, and they all read 0.
The incoming values of the shift register are: ABCDEFGH : 11111111 The incoming values of the shift register are: ABCDEFGH : 01111111 The incoming values of the shift register are: ABCDEFGH : 10111111 The incoming values of the shift register are: ABCDEFGH : 11011111 The incoming values of the shift register are: ABCDEFGH : 11101111 The incoming values of the shift register are: ABCDEFGH : 11110111 The incoming values of the shift register are: ABCDEFGH : 11111011 The incoming values of the shift register are: ABCDEFGH : 11111101 The incoming values of the shift register are: ABCDEFGH : 11111110 The incoming values of the shift register are: ABCDEFGH : 00000000
Multiple Shift Registers
To read our total of 72 inputs, we’ll need 9 shift registers. So let’s try wiring up 2 of these shift registers at the same time. We’ll have to tweak our code and wiring a bit to accomplish this.
There are 2 strategies to achieve this, wiring the shift registers in parallel, or in serial (known as daisy-chaining).
If we add more shift registers via daisy-chaining, the output from one shift register feeds into another (and another, and another…) until one feeds into the Arduino. We can keep adding more inputs without having to use any more of our Arduino’s inputs. This is great, but could slow down the reading of the input data, as it’s all flowing through one channel.
The method we’re going to go with is wiring the registers in parallel. This is going to mean each register’s output is wired directly to the Arduino. This will use more IO pins, but should read data faster. 9 shift registers wired to the Arduino is going to take 12 pins total. Each register’s output will be connected to the Arduino inputs (9 pins) plus the 3 Arduino outputs (SH/!LD, CLK, and !CE) which will be connected each one all the shift registers in parallel. Since the Arduino has 12 digital IO pins, we’ve got 2 to spare.
2 Shift Register Wiring Diagram
Here’s a wiring diagram of 2 shift registers wired in parallel (due to physical limitations, it not exactly how we wired it in real life, but the connections are the same). See a more interactive version here.
2 Shift Register Test Code
This code is mostly the same as our first test, with a few differences. First, the data_pins are stored as an array (we’re using pins 6 & 7). Also, you’ll notice we had to implement my own version of the Arudino library’s shiftIn() function, called shiftInCustom(). This is because we’re using the same signal for both CLK pins, so each time we write HIGH to that pin, we need to read data from both registers, not just one.
// HARDWARE CONNECTIONS const int ce_pin = 9; // Connect Pin 9 to !CE (clock enable, active low) const int shld_pin = 10; // Connect Pin 10 to SH/!LD (shift or active low load) const int clk_pin = 11; // Connect Pin 11 to CLK (the clock that times the shifting) const int num_registers = 2; //Number of shift registers const int data_pins[num_registers] = { 6, 7 }; //Array to store the data pins byte incoming[num_registers] = { 0, 0 }; // Array to store the values loaded from the shift registers // The part that runs once void setup() { // Initialize serial to gain the power to obtain relevant information, 9600 baud Serial.begin(9600); // Initialize each digital pin to either output or input // We are commanding the shift register with each pin with the exception of the serial // data we get back on the data_pin line. pinMode(shld_pin, OUTPUT); pinMode(ce_pin, OUTPUT); pinMode(clk_pin, OUTPUT); //Set all data pins to inputs int i = 0; for(int i = 0; i<num_registers; i++) { pinMode(data_pins[i], INPUT); } // Required initial states of these two pins according to the datasheet timing diagram digitalWrite(clk_pin, HIGH); digitalWrite(shld_pin, HIGH); } // The part that runs to infinity and beyond void loop() { read_shift_regs(); // Read the shift registers // Print out the values being read from the shift register Serial.println("\nThe incoming values of the shift register are: "); for (int i = 0; i<num_registers; i++){ print_byte(incoming[i]); // Print every 1 and 0 that correlates with A through H Serial.print("\n"); } Serial.print("\n"); delay(2000); // Wait for some arbitrary amount of time } // This code is intended to trigger the shift register to grab values from it's A-H inputs void read_shift_regs() { // Trigger loading the state of the A-H data lines into the shift register digitalWrite(shld_pin, LOW); delayMicroseconds(5); // Requires a delay here according to the datasheet timing diagram digitalWrite(shld_pin, HIGH); delayMicroseconds(5); // Required initial states of these two pins according to the datasheet timing diagram pinMode(clk_pin, OUTPUT); for ( int i=0; i<num_registers; i++) { pinMode(data_pins[i], INPUT); } digitalWrite(clk_pin, HIGH); digitalWrite(ce_pin, LOW); // Enable the clock // Get the A-H values shiftInCustom(MSBFIRST); digitalWrite(ce_pin, HIGH); // Disable the clock } //Custom shiftin function #include "wiring_private.h" void shiftInCustom(uint8_t bitOrder) { uint8_t value[num_registers] = { 0, 0 }; uint8_t i; for (i = 0; i < 8; ++i) { digitalWrite(clk_pin, HIGH); for (int j=0; j<num_registers; j++){ if (bitOrder == LSBFIRST) { value[j] |= digitalRead(data_pins[j]) << i; } else { value[j] |= digitalRead(data_pins[j]) << (7 - i); } } digitalWrite(clk_pin, LOW); } for (int k = 0; k<num_registers; k++) { incoming[k] = value[k]; } } // A function that prints all the 1's and 0's of a byte, so 8 bits +or- 2 void print_byte(byte val) { byte i; for(byte i=0; i<=7; i++) { Serial.print(val >> i & 1, BIN); // Magic bit shift, if you care look up the <<, >>, and & operators } }
2 Shift Register Test Output
Now that it’s all set up, we’ll run the sketch, and cover the sensors in the same manner as before. Here are the results on our serial monitor:
The incoming values of the shift register are: 11111111 11111111 The incoming values of the shift register are: 01111111 11111111 The incoming values of the shift register are: 10111111 11111111 The incoming values of the shift register are: 11011111 11111111 The incoming values of the shift register are: 11101111 11111111 The incoming values of the shift register are: 11110111 11111111 The incoming values of the shift register are: 11111011 11111111 The incoming values of the shift register are: 11111101 11111111 The incoming values of the shift register are: 11111110 11111111 The incoming values of the shift register are: 11111111 01111111 The incoming values of the shift register are: 11111111 10111111 The incoming values of the shift register are: 11111111 11011111 The incoming values of the shift register are: 11111111 11101111 The incoming values of the shift register are: 11111111 11110111 The incoming values of the shift register are: 11111111 11111011 The incoming values of the shift register are: 11111111 11111101 The incoming values of the shift register are: 11111111 11111110 The incoming values of the shift register are: 00000000 00000000
And there we are! By wiring this shift registers in parallel, we’re quickly reading data from 16 phototransistors using just 5 IO pins on our Arduino. The next step will be to wire up all 9 with this method, giving us the 72 phototransistors we’ll need to make Throw.