I can't write and read pin fast succesfully using registers on Arduino UNO R3

I'm developing a midi keyboard with an old dynamic keyboard and arduino uno. I created 2 different sketch:
The first one, using digitalRead and digitalWrite, works but I can't get accurate key short-long stroke timing intervals because the sketch loop is too slow.
In the second I try to use direct register access to speed up pin reading and writing, but I receive to the serial monitor unaspetted bytes and not my attended debug messages. I can't find what is wrong in the sketch.
This is the fast not working version:

#define INPINS 8
#define OUTPINS 4
#define KEYS 64
#define NSTATE 16
#define BUFFER_NOTEON 128
#define MIDINOTE_C2 36
unsigned long shortStroke[KEYS];//millis of short pressure
unsigned long keyboardON[KEYS];//-1 if keyoff, 0.127 pressure if key on
unsigned long keyboardPrev[KEYS];


unsigned long last_millis;

byte channel = 2; //channel 3
byte i = 0;
byte k = 0; //variable for reading input
byte base = 0; //octave base
byte state = 15;
byte nota = 0;
int pin_in[INPINS] = {2, 3, 4, 5, 6, 7, 8, 9}; //KD0,KD1,KD2,KD3,KD4,KD5,KD6,KD7
int pin_out[OUTPINS] = {10, 11, 12, 13}; //KA0, KA1, KA2, KA3


void setup() {
  // put your setup code here, to run once:
  state = 0;
  for (i = 0; i < KEYS; i++) {
    keyboardON[i] = (unsigned long)0;  //set to zero all keybord keys state
    keyboardPrev[i] = (unsigned long)0;
    shortStroke[i] = (unsigned long)0;
  }
  //for (i=0;i<INPINS;i++){pinMode(pin_in[i],INPUT_PULLUP);}
  //for(i=0;i<OUTPINS;i++){pinMode(pin_out[i],OUTPUT); }
  DDRD =  DDRD & B00000011; //set pin D0 and D1 for output and D2..D7 as input
  DDRB = DDRB | B00111100; //set pin D8 and D9 as input and D10-D13 as output

  //pinMode(A0,OUTPUT);//link the disble-enable decoders pin to A0 autput
  //digitalWrite(A0,LOW);//enable decoders
  DDRC = DDRC | B00000001; //set A0 for Output
  PINC = PINC | B00000001; //disable decoders A0=low and leave other pins as they are

  //for (i=0;i<OUTPINS;i++){digitalWrite(pin_out[i],HIGH*((state>>i)&1));}
  PINB = (PINB & B11000011) | (state << 2); //leave pin D8 and D9 as they are and set PIN D10-D13 like bynary state value
  PINC = PINC & B11111110; //enable decoders A0=low and leave other pins as they are

  Serial.begin(31250);
  last_millis = millis();
  Serial.print("PINB=");  Serial.println(PINB);
  Serial.print("PINC=");  Serial.println(PINC);
}


void readPins() {
  k = (PIND & B11111100) >> 2;
  k = k + ((PINB & B00000011) << 6); //statodei 8 primi switch
  //k=state/2*8+i;//indice del tasto sulla tastiera da 0 a 60
  base = state / 2 * 8; //activated 8 switch
  for (i = 0; i < INPINS; i++)
  { nota = base + i;
    if ((k & (1 << i)) == LOW) //key pressed
    { if (keyboardPrev[nota] == (unsigned long)0)
      {
        if ((state % 2) == 0) //short stroke
        {
          if (shortStroke[nota] == (unsigned long)0L) {
            shortStroke[nota] = last_millis;
          }
        }
        else //long stroke
        {
          if (shortStroke[nota] > 0) {
            keyboardON[nota] = min(max((last_millis - shortStroke[nota]) >> 3, (unsigned long)1), 100);
            shortStroke[nota] = (unsigned long)0;
          }
        }
      }
    }
    else //key released
    { if (keyboardPrev[nota] > (unsigned long)0) {
        keyboardON[nota] = (unsigned long)0; // (((state%2)==1)||((state%2)==0))
      }
    }
  }

}
void MidiSend(byte cmd, byte data1, byte data2)
{
  Serial.write(cmd | channel) ;
  Serial.write(data1) ;
  Serial.write(data2) ;
}

//
// MIDI note ON message
//
void MidiNoteOn(byte note, byte velocity)
{
  MidiSend(0x90, MIDINOTE_C2 + note, velocity);
}

//
// MIDI note OFF message
//
void MidiNoteOff(byte note)
{
  MidiSend(0x80, MIDINOTE_C2 + note, 0) ;
  //MidiSend(0x90, MIDINOTE_C2+note, 0) ;
}

void sendMidiUpdate()
{ for (i = 0; i < INPINS; i++)
  { k = state / 2 * 8 + i; //indice del tasto sulla tastiera da 0 a 60
    if (keyboardON[k] != keyboardPrev[k])
    { if (keyboardON[k] == (unsigned long)0) {
        MidiNoteOff(k);
        keyboardPrev[k] = (unsigned long)0;
      }
      else {
        MidiNoteOn(k, 127 - keyboardON[k]);
        keyboardPrev[k] = keyboardON[k];
      }
    }
  }

}
void nextState() {

  //digitalWrite(A0,HIGH);//disable decoders//all output are HIGH
  PINC = PINC | B00000001; //set HIGH A0
  state = (state + 1) % NSTATE; //scan 8 notes * 2 stroke (short-long)
  /*for (i=0;i<OUTPINS;i++)
    {digitalWrite(pin_out[i],HIGH*((state>>i)&1));}    //activate decoder input
  */
  PINB = (PINB & B11000011) | (state << 2);
  //digitalWrite(A0,LOW);//enable decoders ALL OUTPUT LOW, BUT ONE HIGH
  PINC = PINC & B11111110; //set LOW A0
  Serial.print("PINB=");  Serial.println(PINB);
  Serial.print("PINC=");  Serial.println(PINC);
}


void loop() {


  readPins();
  sendMidiUpdate();
  nextState();
  if (state == 0) {
    last_millis = millis();
  }

}

This is the slow working version:

#define INPINS 8
#define OUTPINS 4
#define KEYS 64
#define NSTATE 16
#define BUFFER_NOTEON 128
#define MIDINOTE_C2 36
unsigned long shortStroke[KEYS];//millis of short pressure
unsigned long keyboardON[KEYS];//-1 if keyoff, 0.127 pressure if key on
unsigned long keyboardPrev[KEYS];


unsigned long last_millis;

byte channel = 2; //channel 3
byte i = 0;
byte k = 0;
byte state = 15;

int pin_in[INPINS] = {2, 3, 4, 5, 6, 7, 8, 9}; //KD0,KD1,KD2,KD3,KD4,KD5,KD6,KD7
int pin_out[OUTPINS] = {10, 11, 12, 13}; //KA0, KA1, KA2, KA3


void setup() {
  // put your setup code here, to run once:
  state = 0;
  for (i = 0; i < KEYS; i++) {
    keyboardON[i] = (unsigned long)0;  //set to zero all keybord keys state
    keyboardPrev[i] = (unsigned long)0;
    shortStroke[i] = (unsigned long)0;
  }
  for (i = 0; i < INPINS; i++) {
    pinMode(pin_in[i], INPUT_PULLUP);
  }
  for (i = 0; i < OUTPINS; i++) {
    pinMode(pin_out[i], OUTPUT);
  }
  //DDRD= B00000010;//set pin D0 and D1 for output and D2..D7 as input
  //DDRB= B00111100;//set pin D8 and D9 as input and D10-D13 as output

  pinMode(A0, OUTPUT); //link the disble-enable decoders pin to A0 autput
  digitalWrite(A0, LOW); //abilita denoders
  //DDRC=B00000001; //set A0 for Output
  //PORTC=PORTC & B11111110;enable decoders A0=low and leave other pins as they are

  for (i = 0; i < OUTPINS; i++) {
    digitalWrite(pin_out[i], HIGH * ((state >> i) & 1));
  }
  //PORTB=(PORTB & B00000011)|(state*4);//leave pin D8 and D9 as they are and set PIN D10-D13 like bynary state value
  Serial.begin(31250);
  last_millis = millis();


}


void readPins() {
  for (i = 0; i < INPINS; i++)
  { k = state / 2 * 8 + i; //indice del tasto sulla tastiera da 0 a 60
    if (digitalRead(pin_in[i]) == LOW) //key pressed
    { if (keyboardPrev[k] == (unsigned long)0)
      {
        if ((state % 2) == 0) //short stroke
        {
          if (shortStroke[k] == (unsigned long)0L) {
            shortStroke[k] = last_millis;
          }
        }
        else //long stroke
        {
          if (shortStroke[k] > 0) {
            keyboardON[k] = min(max((last_millis - shortStroke[k]) >> 3, (unsigned long)1), 100);
            shortStroke[k] = (unsigned long)0;
          }
        }
      }
    }
    else //key released
    { if (keyboardPrev[k] > (unsigned long)0) {
        keyboardON[k] = (unsigned long)0; // (((state%2)==1)||((state%2)==0))
      }
    }
  }

}
void MidiSend(byte cmd, byte data1, byte data2)
{
  Serial.write(cmd | channel) ;
  Serial.write(data1) ;
  Serial.write(data2) ;
}

//
// MIDI note ON message
//
void MidiNoteOn(byte note, byte velocity)
{
  MidiSend(0x90, MIDINOTE_C2 + note, velocity);
}

//
// MIDI note OFF message
//
void MidiNoteOff(byte note)
{
  MidiSend(0x80, MIDINOTE_C2 + note, 0) ;
  //MidiSend(0x90, MIDINOTE_C2+note, 0) ;
}

void sendMidiUpdate()
{ for (i = 0; i < INPINS; i++)
  { k = state / 2 * 8 + i; //indice del tasto sulla tastiera da 0 a 60
    if (keyboardON[k] != keyboardPrev[k])
    { if (keyboardON[k] == (unsigned long)0) {
        MidiNoteOff(k);
        keyboardPrev[k] = (unsigned long)0;
      }
      else {
        MidiNoteOn(k, 127 - keyboardON[k]);
        keyboardPrev[k] = keyboardON[k];
      }
    }
  }

}
void nextState() {

  digitalWrite(A0, HIGH); //disable decoders//all output are HIGH
  state = (state + 1) % NSTATE; //scan 8 notes * 2 stroke (short-long)
  for (i = 0; i < OUTPINS; i++)
  {
    digitalWrite(pin_out[i], HIGH * ((state >> i) & 1)); //activate decoder input
  }
  //PORTB=(PORTB & B00000011) |(state*4);
  digitalWrite(A0, LOW); //enable decoders ALL OUTPUT LOW, BUT ONE HIGH

}


void loop() {


  readPins();
  sendMidiUpdate();
  nextState();
  if (state == 0) {
    last_millis = millis();
  }

}

Not going to read all that, but you should be using PIND and PINB instead of PORTD and PORTB.

1 Like

Thank you very much. I'll try an give a feedback.

Thanks, I formatted the code with ctrl+t as requested!

I used PINB, PINC and PIND instead of PORTB, PORTC and PORTD, but nothing changed: the serial monitor recieves a lot of data but not my messages. Have you got any suggestions or ideas about?

All those loops will be much faster if the line or two is simply written 8 times. Each for loop has to spend time initializing a local variable, the conditional check to stay in the loop and the increment after performing the functions contained in the loop. Each of these additional steps take clock cycles, and not just one but several.

Rather than confuse yourself with input and output registers and toggle instructions, you should try using the DigitalWriteFast library. Available through the library manager.

https://github.com/ArminJo/digitalWriteFast

Thanks for your suopport, I agree, but I think now my bottleneck is the speed in reading and writing pin.
Every digitalRead takes about 3.5 us x 120 (number of switches to check in the keyboard)= 0.5 ms and 366 us x16 (split keyboard switches in 16 zones) = 6ms (a lot of wasted time)
Varable Initialization takes about 0.12 us x 1000(I didn't count them!)=0.12 ms
Do you have any idea about my code errors in direct pins manipulation (it causes writing data to serial monitor)

Thank you, I downloaded the zipped library from github. I'll use it!

If you place all your inputs on one port, and all your outputs on another port, you can use direct port manipulation to change the state of the matrix in one clock and read it on the next. Using Digital read has some calls to a lookup table for the pin map etc. It's another time waster.

Why you used & in the first case and | in the second if the operation the same?
In fact neither first nor second is correct, because you can't set some bits and clear another in a single operation.
For each DDRx registers you require a two lines:

DDRD =  DDRD | B00000011; //set bits 0 & 1
DDRD =  DDRD & B00000011; //clear bits 2-7

or just clear all bits first and and then set only those you need:

DDRD =0;  // clear all bits
DDRD =  DDRD | B00000011; //set bits 0 & 1

It seems to me that your code contains a lot of another similar errors. Before you start working with registers, you need to master bit operations

What data are you seeing, then? If you are sending "lots" of data to the serial port, that can significantly slow things down. A single call to Serial.print() will take many more cycles than your PINx inputs, even if it DOESN'T block. If it DOES block, it will take 32us per byte.

Likewise, try to get rid of the use of modulus and division operations on state.
if (state > NSTATE) state = 0 is much faster than state = state % NSTATE, and it's not even slower if the compiler optimizes the modulus to an AND.

When using direct pin access, you can still use pinMode() in setup to set the pin directions; speed there doesn't matter, and using pinMode() can be more obvious and portable.

1 Like

Thank you for your precious help!

Thanks a lot, now I have a more clear and complete overview of port manipulation.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.