Technical Question Regarding digitalWrite()

Status
Not open for further replies.

t6r7

New member
Hello!

This is my first post here, so I am sorry if I am not sufficiently clear about the problem or if I don't post necessary information about the problem.

So, let me explain what I am doing:

I have a SparkFun's 4by4 RGB LED Button pad and I am using a Teensy 3.6 to multiplex both the RGB LEDs and to scan the button presses. I do have a bit more happening in my setup (MP3 player and LCD screen), but my question regards only the button press scanning of the pad (the code I will post also only regards that). How the button presses work in my current setup is that I have characters assigned to each position of the matrix, and whenever a button is pressed, I add the character corresponding to the pressed button to a 1by16 buffer array (this gets printed to the LCD in the complete setup). The problem I was encountering is that whenever I pressed a few specific buttons, the character added to the code would sometimes correspond to the button that was in the next column and in the same row as the pressed button (even if the next column button was not pressed OR connected to the teensy). For example, if I the pressed button with index [0][0] (this has the character 'R'), the character that would sometimes get stored in the buffer was the character corresponding to index [1][0] (the character was 'A'). This happened with 4 specific buttons, but did not happen with any of the others. Another interesting thing that I noted was that the pin corresponding to column 0 would read LOW when the teensy scanned [0][0] and [1][0], but not when it scanned [2][0] or [3][0].

It took me a long, long time to find the source of the problem. In order to scan the matrix, I set a row LOW and then checked each column to see if any of the columns were pulled low (the columns are connected to pins which are setup as input pullup). After I scanned the rows, I set the current column HIGH and moved on to the next column. Thus, I used digitalWrite to set a column HIGH and immediately moved to the next column and used a digitalWrite to set it LOW. It turns out that if I wrote a delay in between those digitalWrites the problem would go away, even with a delay as small as 1 microsecond. Although I am glad that the problem is fixed, it still bugs me that I don't understand why the problem happens. It seems to me that without the delay, the current row isn't set as HIGH until after the next row is scanned, but I am not sure. Might there be some interaction with calling two digitalWrites in succession? Or is there just a significant hardware delay for setting a pin HIGH? I would appreciate any help.

The code I used is below. I am using Arduino IDE for coding.
Code:
int outputButton[4] = {19,18,17,16}; //row pins
char codeBuffer[16] = {
  ' ',' ',' ',' ',' ',' ',' ',' ',
  ' ',' ',' ',' ',' ',' ',' ',' '
}; //buffer for LCD
char buttonArray[4][4] = {{'R','E','Y','N'}, //character array for matrix
                      {'A','L','D','O'},
                      {' ','F','A','R'},
                      {'I','A','S','<'}};   
int lcdCol = 0;
bool buttonSwitch = true;
int inputButton[4] = {24,25,26,27}; //column pins
#define MAX_DEBOUNCE (3)
static int8_t debounce_count[4][4];
uint8_t val;

void setup() {
  Serial.begin(9600);
  
  for(int i=0;i<4;i++){ // sets all column pins to output mode, button inputs to input pullups and button outputs to outputs
    pinMode(inputButton[i], INPUT_PULLUP);
    pinMode(outputButton[i], OUTPUT);
}

for(int i=0;i<4;i++){ // sets column pins to HIGH (closed switch) and buttonOutputs to HIGH
    digitalWrite(outputButton[i],HIGH);

  }
}

void loop() {
  for(int i=0;i<4;i++){ //scanning for loops
    digitalWrite(outputButton[i],LOW);
    for(int j = 0;j < 4;j++){
      scan(i,j); //scanning function taken from the SparkFun website. I added a few things to work with a buffer
    }
  digitalWrite(outputButton[i],HIGH);//this is the HIGH that sometimes isnt set as high.
  delayMicroseconds(1); //<-- this is the delay that fixes the problem
}
 for(int s = 0;s<16;s++){
            Serial.print(codeBuffer[s]); //debugging print. prints the buffer that characters get stored in
          }
Serial.println("");//debugging print
Serial.println(lcdCol); //debugging print
delay(10); // used to get a clean serial port reading (no weird jumping and the such)
}

void scan(int m, int n){
  
    val = digitalRead(inputButton[n]);
    Serial.println("Column " + String(m) + " and row " + String(n));
    Serial.println("Input Value: " + String(val)); //debugging prints
    if (val == LOW)
    {
      // active low: val is low when btn is pressed
      if ( debounce_count[m][n] < MAX_DEBOUNCE)
      {
        debounce_count[m][n]++;
        if ( (debounce_count[m][n] == MAX_DEBOUNCE)&&(buttonSwitch==true) )
        {
          buttonSwitch = false;
          if(lcdCol<15){
            if(buttonArray[m][n]=='<'){
              codeBuffer[lcdCol-1] = ' ';
              lcdCol--;
            }
            else{
            codeBuffer[lcdCol] = buttonArray[m][n];
            lcdCol++;
            }  
          }
          else if(lcdCol==15){
          Serial.println("Max Size");
           }
        }
      }
    }
    else
    {
      // otherwise, button is released
      if ( debounce_count[m][n] > 0)
      {
        debounce_count[m][n]--;
        if ( debounce_count[m][n] == 0 )
        {
          buttonSwitch = true;

        }
      }
    }

}

If more information is necessary for understanding the problem, just ask, I will happily provide it. Thanks!
 
When you use INPUT_PULLUP, the weak pullup resistors are approximately 33K ohms (but can be up to 50K). Each signal has some capacitance. The pin in Teensy has about 5 to 10 pF. When you connect wires and buttons, each thing adds a little more capacitance.

If you end up with 20 pF capacitance, the time constant with 33K ohms is 0.66 us. When the pin was LOW and becomes HIGH due only to the pullup resistor, that's the time needed for the voltage to change to 63% of the final logic HIGH voltage. The pin should see anything above ~2 volts as logic high, and since the pullup is to 3.3V, one time constant gets you to ~2.1 volts. This is all very approximate... the actual resistance of the pullup, the actual logic low-to-high threshold, and the actual capacitance of the pin and stuff you've added are all known only approximately and some of these probably vary with temperature. Still, about 0.6 us is probably a good estimate for the amount of time needed for the voltage to rise from zero up to the logic threshold. If you have long wires and a large number of buttons, maybe plan on slightly more "worst case" capacitance.

On slow 8 bit AVR, usually code will take at least a few microseconds. It's possible with very careful optimization (sometimes in assembly) to get very short times, but the usual C-only approach with Arduino functions and ordinary programming results in much more than 0.6 us delay.

Of course a 180 MHz ARM chip is a lot faster. It's very easy to end up with much less than the required 0.6 us for a pullup resistor to bring a signal high. This is one of the common problems when porting code from AVR to ARM. Normally faster is better, but with pullup resistors the slow rise time that's not usually an issue when running with a slow processor becomes something you need to consider with such a fast CPU.
 
Hello!

Thank you for taking your time to answer my question!

I did initially think that the pullup resistors were a problem (because I read a thread on I2C where you mentioned that the pullup resistors were bad and we should use external pullups). However, I didn't try doing external pullups because I was lazy and didn't want more clutter in my breadboard. As is, the slow response from the RC circuit seems to be a problem. What I still wonder is why only a few specific buttons exhibited this problem. Do you think it just happened to be a chance combination of capacitance/resistance that gave different time constants for each button?

Additionally, I realized that the Teensy would read the wrong button only when the wrong button was pulled low first. For example, in a single scan [0][0] (pressed) reads high and [1][0] (not pressed) reads low -- in the following scans both read low. Otherwise, if [0][0] reads low and [1][0] reads low in the first scan, then the correct button would be identified. The only way I can think of this happening is if I press [0][0] after it has been scanned but before [1][0] has been scanned. However, that just seems like a probabilistic thing to me and I am unsure if its true. Is there also an RC time constant for pulling the a pin low (in this case the row pin)?

Thank you for your help!
 
Status
Not open for further replies.
Back
Top