Last Updated: 11/03/2024
#71 CD4021BE Shift Register
Projects >> #71 CD4021BE Shift Register
CD4021BE Shift Register - Adding Extra inputs
What happens when you run out of pins on your UNO, simple answer buy an Arduino Mega. What if you still need more pins?
One of the simplest and cheapest ways is to use a shift register. This allows for almost limitless inputs from 3 wires.
Also the good news is they are very cheap.
A pack of 10 is avaialable for under £5. That would add an extra 80 input pins!!
Introduction
Whiole working on a project for model railways I needed a way of adding a lot of button inputs for a control panel. Asd the project I'm working on can be used on any layout I needed a flexible system and so required a minimum of 100 button inputs. For anyone who has seen control panels on large layouts this number is easily reached.
I also wanted something cheap and easily scalable and the CD4021 shift register fits the requirements nicely.
Normally when using buttons they are each attached to an individual poin with a PULL_DOWN of PULL_UP resistor using digitalRead() to check the state of the pins.
This requires a pin per button.
In the example below we see the CD4021BE shift register is controlled by just 3 pins but has 8 inputs I have only put a single button, in practice a button would be placed in the same style on each of the CD4021BE registers.
The great thing about shift registers is that they can be daisly chained with no addressing system in the style shown below.We now have 16 inputs using the same 3 Arduino pins.
More shift registers can be daisly chained as required.
WARNING: Although there is no real technical limit to the number of shift registers added there is a limit to the current draw on the Arduino.
If more than 3 are added it would be best to supply the 5v from a seperate power supply and just share a common GND with the Arduino.
How does it work?
This is a rather simplistic explanation of how the system works.
Each register on the CD4021BE storesd a 1 or 0 deopending if the pin is HIGH or LOW (from button press).
When the latch Pin opens the lock the data can be requested 1 byte at a time, each byte of data carrying the pins states on the register,
The clock pin prepares for more data if another CD4021BE is connected.
If multiple CD4021BE shift registers are connected we can repeate the data call and each connected boards pin states will be received. A bit like sucking the data down a hose pipe.
We just need to call the number of boarda attached.
The problem?
I thought this would be quite straightforward as there is even an example at https://www.arduino.cc/reference/en/language/functions/advanced-io/shiftin/
However when I tried it I wasn't getting anything on register 0 and the other registers seem to have the wrong values. After taking everything apart and rebuilding a few trimes, changing chips etc I started to think I had bought a bad set of chips.
Searching online for the problem I found a reference to someone who had exactly the same problem and thankfully it had been solved by someone called runaway_pancake (Thanks). I did some tests with his code and it worked great but was not ideal for multiple calls so using his code as a basis I rewrote it to create a simple system that was easily configurable and could be connected to as many CD4021BE chips as I would need. Also as I hate delay() I reduced the delayMicroseconds() to the lowest value I could get away with.
The main loop is also timed using millis() to enable the code to interact with other code without any blocking.
The system puts all the data into a byte array, each CD4021BE putting it's data in it's own segment of the array.
The code uses the circuit in the images above.
Example 1: ESP-NOW ESP32 example
Please download the code as the HTML can distort the code in places
Click to Download code: cd4021V3.ino
Rather than reinventing I have written a code example based on the tutorials at:
The system is set up by defining the number of CD4021BE in the system in the line
#define NUMCD4021CHIPS 2
In case I am just using 2 for testing. This then defines the size of the array needed to store all the data.
/* cd4021Vv3
11/03/2024
This version will work with as many CD4021's as needed.
All data is stored in the myButtons[] array
Many thanks to runaway_pancake for writing some code
that got me pointed in the right direction
// Demo4021
//
// A --1 16-- +
// x --2 15-- B
dataPin/SO --3 14-- C
// E --4 13-- D
// F --5 12-- x
// G --6 11-- +
// H --7 10-- clk
// gnd --8 9-- latch
*/
#define NUMCD4021CHIPS 2
byte myButtons[NUMCD4021CHIPS];//sets the array to the number of chips
int latchPin = 8;
int clockPin = 7;
int dataPin = 9;
unsigned long currentMillis;
unsigned long delayMillis;
int delayTimer = 2000;//2 seconds
void setup (){
Serial.begin(9600);
Serial.println("cd4021Vv3");
pinMode (latchPin, OUTPUT);
pinMode (clockPin, OUTPUT);
pinMode (dataPin, INPUT);
}
void loop (){
int q;
int w;
currentMillis = millis();
if(currentMillis - delayMillis >= delayTimer){
delayMillis = currentMillis;
//This sets the system up to read the data
latch4021(latchPin);//This function MUST be called before the get4021Byte() is called
for(q=0;q < NUMCD4021CHIPS;q++){
myButtons[q] = get4021Byte(dataPin,clockPin); //Put the bytes into the myButtons[] array
}
//At this point all the data is in the myButtons[] array and can be used as required
//This just shows the results
for(w=0;w < NUMCD4021CHIPS;w++){
Serial.print("myButtons[w] : ");
Serial.print(w);
Serial.print(" = ");
Serial.println(myButtons[w],BIN);
for(q=0;q<8;q++){//work though the bytes
if(bitRead(myButtons[w],q) == 1){//if a bit is set
Serial.print("Button Pressed : ");//tells us the button pressed 0-7
Serial.println(q);
}
}
Serial.println(" ");
}
}
}
void latch4021(int funcPin){
digitalWrite (funcPin, HIGH);
delayMicroseconds(5); //works... I want the smallest stable value possible
digitalWrite (funcPin, LOW);
}
byte get4021Byte(int funcDataPin, int funcClockPin){
byte registerContent;
int bitContent;
for (int idx = 0; idx < 8; idx++)
{
bitContent = digitalRead (funcDataPin);
if (bitContent == 1)
{
bitWrite (registerContent,idx,1);
}
else
{
bitWrite (registerContent,idx,0);
}
digitalWrite (funcClockPin, LOW);
delayMicroseconds(5); //works... I want the smallest stable value possible
digitalWrite (funcClockPin, HIGH);
}
return registerContent;
}
Comments
For help or suggestions on new projects, please email the address in this image: and use #71 CD4021BE Shift Register as a reference.