Arduino Forum

Using Arduino => Audio => Topic started by: jakesthetester on Dec 10, 2018, 08:55 am

Title: Using CD4021 Daisy Chain (not matrix) to Send noteOn and noteOff Messages
Post by: jakesthetester on Dec 10, 2018, 08:55 am
Hello all!
I am a fresh beginner on Arduino.  No previous experience coding, but I know a thing or two about midi hex.  Aside from the 20+ hours I've spent racking by brain trying to get this to work, I have no other experience.  Arduino isn't supposed to be this frustrating, is it?

I need to use (12x) CD4021BE shiftIn registers to send button press information (from 96 buttons) to an Arduino Leonardo, then have the Arduino turn each buttonPress & buttonRelease event into midi noteOn & noteOff commands using the USBMIDI library.  I don't want it to keep playing noteOn & noteOff commands at a set interval (like a loop), just only when a button state change happens.

I know you might want to suggest that I try to matrix them instead of daisy chain, but I'm just burnt at this point.  I'm sorry, I understand CD4021 daisy chaining and that's about it haha.

So... for my masterpiece (in progress).  I have set my circuit up exactly like it is set up here ( except mine will have 12 CD4021's.  Here is the code so far (mostly patched here and there from different sources, so I apologize for how scatterbrained it is).  I'll explain the trouble I'm having after the code...

Code: [Select]
#include "MIDIUSB.h"

void noteOn(byte channel, byte note, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, note, velocity};

void noteOff(byte channel, byte note, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, note, velocity};

// #define DEBUG

// General settings
#define NUM_INPUTS 24  // 32 switches for AGO Pedalboard C2-G4
#define BTN_DEBOUNCE 75  // Delay to prevent switch bouncing

// MIDI Settings
#define MIDI_CHANNEL 1
#define MIDI_NOTE_OFFSET 36  // MIDI note 36 = C2

// Arduino Pins
#define CLOCKPIN 7  // To CD4012 pin 10 (CLOCK)
#define LATCHPIN 8  // To CD4012 pin 9 (P/S C)
#define DATAPIN 9   // To CD4012 pin 3 (Q8)


byte midiState[NUM_REGISTERS];    // Hold last register state HIGH=OFF
byte switchState[NUM_REGISTERS];  // Hold shift register state HIGH=OFF
unsigned long lastSwitch = 0;     // Holds last switch read time for debouncing switches

#if defined(DEBUG)

void setup() {
  // Initialize last switch state array
  for (byte i = 0; i < NUM_REGISTERS; i++) {
    midiState[i] = 0;  // 11111111


  // Initialize MIDI
  // Define pin modes
  pinMode(DATAPIN, INPUT);


void loop() {

  // Pulse the latch pin: set it to 1 to collect parallel data, then wait
  digitalWrite(LATCHPIN, 1);

  // Set latch pin to 0 to transmit data serially 
  digitalWrite(LATCHPIN, 0);

  readShiftRegisters();  // Read current switch positions
  processSwitches();  // Process switches & send MIDI

  #if defined(DEBUG)
  // Add delay to keep up with print statements

// Read the shift registers
void readShiftRegisters() {

  #if defined(DEBUG)
  // Serial.println("-------------------");
  // Serial.println("Reading registers");

  // While the shift register is in serial mode collect each shift register
  // into a byte. NOTE: the register attached to the chip comes in first.
  for (byte i = 0; i < NUM_REGISTERS; i++) {
    switchState[i] = shiftIn(DATAPIN, CLOCKPIN);
    #if defined(DEBUG)
    // Serial.print("Register #");
    // Serial.print(i);
    // Serial.print(": ");
    // Serial.print(switchState[i]);
    // Serial.print("-");
    // Serial.println(switchState[i], BIN);

void processSwitches() {

  if (micros() - lastSwitch > BTN_DEBOUNCE) {  // Ignore if we're in the debounce time
    lastSwitch = micros();

    // Process all registers
    for (byte i = 0; i < NUM_REGISTERS; i++) {
      if (switchState[i] != midiState[i]) {  // Don't process if no switches changed
        #if defined(DEBUG)
        // Serial.print("Register #");
        // Serial.print(i);
        // Serial.print(": ");
        // Serial.print(switchState[i]);
        // Serial.print("-");
        // Serial.println(midiState[i]);
        for (byte j = 0; j < 8; j++ ) {
          // Read old & new switch states
          byte switchStateValue = bitRead(switchState[i], j);
          byte midiStateValue   = bitRead(midiState[i], j);
          // Switch has changed, send MIDI command
          if (switchStateValue != midiStateValue) {
            byte note = (i * 8) + j;  // MIDI note for current switch
            if (switchStateValue == 1) {  // HIGH = switch open
              noteOn(note, MIDI_NOTE_ON_VELOCITY, MIDI_CHANNEL);
            } else {  // LOW = switch closed
              #if defined(DEBUG)
              noteOff(note, MIDI_NOTE_ON_VELOCITY, MIDI_CHANNEL);
            #if defined(DEBUG)
            //Serial.print(": ");
        midiState[i] = switchState[i];  // Save current switch state

// Returns a byte with each bit in the byte corresponding
// to a pin on the shift register. leftBit 7 = Pin 7 / Bit 0= Pin 0
// From:
byte shiftIn(int myDataPin, int myClockPin) {
  int i;
  int temp = 0;
  int pinState;
  byte myDataIn = 0;

  pinMode(myClockPin, OUTPUT);
  pinMode(myDataPin, INPUT);

  // Hold the clock pin high 8 times (0,..,7) at the end of each time through
  // the for loop. At the beginning of each loop when the clock is set low,
  // it will be doing the necessary low to high drop to cause the shift
  // register's DataPin to change state based on the value of the next bit in
  // its serial information flow. The register transmits the information about
  // the pins from pin 7 to pin 0 so that is why our function counts down.
  for (i=7; i>=0; i--) {
    digitalWrite(myClockPin, 0);  // Set clock low
    temp = digitalRead(myDataPin);
    if (temp) {
      pinState = 1;
      // Set the bit to 0 no matter what
      myDataIn = myDataIn | (1 << i);
    } else {
      // Turn it off -- only necessary for debuging
      // print statement since myDataIn starts as 0
      pinState = 0;

    digitalWrite(myClockPin, 1);
  return myDataIn;

So... after you've had a good chuckle at the frankenstein code I've created, hopefully you know how easy (or hard) of a problem I have on my hands.

When I run the code on my Leonardo, I am able to send midi commands to my computer.  The button sends the correct information for noteOn/noteOff, note, velocity, and channel.  My problem, I believe, is with how the code handles the shift registers.  I think it's the timing.  I have to press a button 8 times (for 16 state changes) before the shift register releases the information into the Arduino and the Arduino sends the midi messages.  The result is I get 16 midi messages all at once for all of the last 8 noteOn and 8 noteOff events.

So, given my circuit and my code, what do I need to do to make it so the moment a button is pressed, a noteOn (with proper note, channel, velocity) is played, and noteOff when that button is released?

If it'd be considerably (and I mean considerably) easier to re-configure my circuit given the parts I have (based on the schematic at the link above), let me know!  Thank you so much in advance!
Title: Re: Using CD4021 Daisy Chain (not matrix) to Send noteOn and noteOff Messages
Post by: MarkT on Dec 10, 2018, 12:19 pm
You're not flushing the MIDIUSB stream
Title: Re: Using CD4021 Daisy Chain (not matrix) to Send noteOn and noteOff Messages
Post by: Grumpy_Mike on Dec 11, 2018, 06:00 am
I have set my circuit up exactly like it is set up here...
Oh dear, their are no decoupling capacitors on that circuit. These are needed for reliable operation, they are not optional. You need a 0.1uF ceramic capacitor between the power and ground pin of each shift register. (