I'm using two of these together to create a single 16bit register which will give me access to 16 outputs. I have gotten them to work but not exactly how I imagined them to work. The code written is for a 18650 cell charger/discharger. The first 6 bits (0-5) of the shift register will be connected to 6 NPNs which will turn on or off each of their tp4056s connected in series. The next 6 bits (5-11) will be connected to 6 different constant current loads. The user selects if they want to charge or discharge their cell and depending on the mode, that particular cell's charger or current sink is turned on (but both not on the same time). In reality, I have to firstly shift 6 bits manually by selecting a mode and then pressing select for all 6 cells in the register for it to show something and even after that it misses bits randomly. btw last 4 bits (12-15) will also get used in the final product. For experimenting, I've connected LEDs to all outputs.

#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);
Encoder encoder(2, 3);

//software constants
const int numOfCells = 6;
const int analogInput[numOfCells] = {A0, A1, A2, A3, A6, A7};
const float arduinoPowerLine = 4.80;
const long interval = 60000; //60 seconds
const char* functions [2] = {"CHARGE   ", "DISCHARGE"}; //mode select array
const int dataPin  = 12;//DS - pin 9
const int latchPin = 11;//ST_CP --pin12
const int clockPin = 8;//SH_CP --pin11

//Encoder setup
long oldPostion = -999;
long newPostion = 0;
long prevPostion;

//making battery arrays and setting their default flags
double batteryVoltage[numOfCells];
bool noBatteryFlag[numOfCells] = {LOW, LOW, LOW, LOW, LOW, LOW}; 
float minimum = 3.00; //Cutoff voltage
float capacity[numOfCells];
//mode variables
int modeState[numOfCells];
int confirmedModeState[numOfCells];

//button setup
int inputPin = 5;
int inputState;
int lastInputState = LOW;
int inputFlag = LOW;

//debounce timer
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

//lcd varuables
int currentCell = -1;

//Cell timers
unsigned long prevMillis[numOfCells];
int minute[numOfCells];
unsigned long currentMillis;

//shift resister variables
word outputs = 0B0000000000000000; 

void updateShiftRegister(word bits){//takes bits from the resolvemode function and sets them appropriately
  byte resisterOne  = lowByte(outputs);//right most byte
  byte resisterTwo = highByte(outputs);//left most byte
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, resisterTwo);
  shiftOut(dataPin, clockPin, MSBFIRST, resisterOne); 
  digitalWrite(latchPin, HIGH);
void setup() {
  pinMode(inputPin, INPUT_PULLUP);
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin,  OUTPUT);  
  pinMode(clockPin, OUTPUT);
  updateShiftRegister(outputs);//Resets all shift resister output to LOW
void BatteryVoltage(){//reading each cells and converting the adc value into voltage
  for (int i = 0; i < numOfCells; i++){
    int reading = analogRead(analogInput[i]);
    batteryVoltage[i] = (reading * arduinoPowerLine) / 1023;
    //flagging stuff
    if (batteryVoltage[i] <= 0.50) {//checking if a battery is inserted in the holder
    noBatteryFlag[i] = HIGH;
      noBatteryFlag[i] = LOW;
void ButtonDebounce(){//checking input state and flaggint button
  int reading = digitalRead(inputPin);
  if (reading != lastInputState){
    lastInputState = millis();
  if ((millis() - lastInputState > debounceDelay)){//debouncing button 
    if (reading != inputState){
      inputState = reading;
      if (inputState == HIGH) {
        inputFlag = HIGH;
  lastInputState = reading;
void ResolveFlags(){
   if (inputFlag == HIGH){ //incrementing currentscreen 
       if (currentCell == numOfCells - 1){ //if counter at its limit ,it wraps back to the start
        currentCell = 0;
      } else {
      confirmedModeState[currentCell] = modeState[currentCell]; //mode selected in stored
      lcd.clear();//clears lab every time button is pressed
        inputFlag = LOW; 
void BatteryTimeKeeper(){//keeping track of battery time while it is discharging
for (int i = 0; i < numOfCells; i++){
  if (currentMillis - prevMillis[i] > interval){
    if (noBatteryFlag[i] == LOW){//if a battery is in the holder
      if (batteryVoltage[i] > minimum){//and if it is greater then 3v, it can be discharged
   prevMillis[i] = currentMillis;
  if (batteryVoltage[i] <= minimum){
    capacity[i] = (minute[i] / 60) * 500; //total time taken divided by 500mA (constant current load)
    minute[i] = 0;
    if (noBatteryFlag[i] == HIGH){//if a battery is removed, its timer is reset back to 0
      minute[i] = 0;
void EncoderFunctionNavigator(){//tracks encoder
  newPostion = encoder.read();
  if (newPostion != oldPostion){
    oldPostion = newPostion;
  if(prevPostion < newPostion){
  } else if(prevPostion > newPostion){
  if (modeState[currentCell] == 2){ //wraps back selection mode
    modeState[currentCell] = 0;
  } else if (modeState[currentCell] < 0){
    modeState[currentCell] = 1;
void ResolveMode(){//sets shift resister output on or off depending on the mode selected
  for (int i = 0; i < numOfCells; i++){
    if (confirmedModeState[i] == 0){
      bitWrite(outputs, i, HIGH) && bitWrite(outputs, i+6, LOW);
    } else if (confirmedModeState[i] == 1){
      bitWrite(outputs, i, LOW) && bitWrite(outputs, i+6, HIGH);
void PrintScreen(){//printing all the info in lcd
  int cellCounter = currentCell + 1; //this is here because I have ocd
  char buffer[25];
  sprintf(buffer, "Cell#:%d    %s", cellCounter, functions[modeState[currentCell]]);
  lcd.setCursor(0, 1);
  lcd.setCursor(4, 1);
  lcd.setCursor(0, 2);
  lcd.setCursor(4, 2);
void loop() {
 currentMillis = millis(); //main currentmillis variable
 prevPostion = newPostion; //records the last postion of encoder
  • Its register, not resistor
    – chrisl
    Jun 21, 2020 at 18:39
  • 1
    Have you tried a simple test sketch with only the shift registers tl test, if they work as expected? Have you checked, if the data, that you want to shift out, is exactly what you expect it to be (through printing it over serial beforehand)? And why are you updating the shift registers for every bit, that you set in ResolveMode()? I would first set every bit in the data, that needs to be set, and then at the end update the shift registers only once with all new data at once
    – chrisl
    Jun 21, 2020 at 18:49
  • And what do you mean by "missing bits randomly"? Please explain exactly, what you were expecting and what actually happened
    – chrisl
    Jun 21, 2020 at 18:50
  • register* my bad. Yes I have tried a simple sketch and I can toggle all the different bits and by missing bits I mean randomly one led stops working in the middle Jun 21, 2020 at 19:53
  • That's a good idea, ill try it and report back my results. thankyou for your answer Jun 21, 2020 at 19:54

1 Answer 1


To put the comments into an answer:

You are controlling the shift registers in this function

void ResolveMode(){//sets shift resister output on or off depending on the mode selected
  for (int i = 0; i < numOfCells; i++){
    if (confirmedModeState[i] == 0){
      bitWrite(outputs, i, HIGH) && bitWrite(outputs, i+6, LOW);
    } else if (confirmedModeState[i] == 1){
      bitWrite(outputs, i, LOW) && bitWrite(outputs, i+6, HIGH);

Since the updateShiftRegister() call is inside the for loop, you are updating the shift registers for every bit, that you set. I guess this is the point, where you get your weird behavior. You don't need to update for every bit. Instead change all needed bits inside the outputs variable, and then at the end of ResolveMode() you can update the registers with all data at once.

So just move updateShiftRegister(outputs); one line down to outside of the for loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.