Keyboard Scanning - A trap for young and old.
Cluso99
Posts: 18,069
I just fell into a trap that is not prevalent on older and slower micros. It may have been dealt with on a past thread, but I will explain here just in case.
When scanning keys it is usual to use a row & column matrix, such that each key shorts between 1 row and 1 column. A pullup or pulldown resistor is usually found on either the row or column set, and these are the inputs to the micro. Lets assume the resistors are pulldowns (10K in my case) and on the rows. What is then usual is that you enable 1 column at a time (it is set high in the case of pulldowns) and read the corresponding rows. Only 1 row will be high, and all others will be pulled low by the resistors.
So this is how you determine the key that has been pressed. It assumes only 1 key can be pressed at a time.
In my case, I want a scan code and by using a 4x4 matrix, 16 keys can be decoded. I wanted a scancode, so I just scanned each column, and read the 4 bit row values and shifted them until I had 16 bits. I then pass this to the spin routine to use a case statement to decode the 16 posible combinations.
I have ignored debouncing, and repeating keys.
The Problem
Pasm is very fast and the prop has high impedance inputs. This permits the prop to do ADC inputs (see the SigmaDelta writeups for more information). This is what caused my problem because while scanning from one column to the next, the capacitive effect was holding my row pins high for longer than the column was an output, such that when I enabled and read the next column the older row that was high was still mostly high due to the slow decay because of lead length, capacitance in the prop and the 10K discharge resistors.
The Solution
Between each column scan, set all column and row pins =0 (if pulldowns), set them all as outputs, then disable them as outputs (this clears the residual capacitance. Then set them all as =1 (because you already have this as a mask for setting all outputs) and enable 1 column as an output, read the row inputs. Now repeat the process for the remaining 3 columns (i.e. set all =0 and enable all, etc).
Sample Code
When scanning keys it is usual to use a row & column matrix, such that each key shorts between 1 row and 1 column. A pullup or pulldown resistor is usually found on either the row or column set, and these are the inputs to the micro. Lets assume the resistors are pulldowns (10K in my case) and on the rows. What is then usual is that you enable 1 column at a time (it is set high in the case of pulldowns) and read the corresponding rows. Only 1 row will be high, and all others will be pulled low by the resistors.
So this is how you determine the key that has been pressed. It assumes only 1 key can be pressed at a time.
In my case, I want a scan code and by using a 4x4 matrix, 16 keys can be decoded. I wanted a scancode, so I just scanned each column, and read the 4 bit row values and shifted them until I had 16 bits. I then pass this to the spin routine to use a case statement to decode the 16 posible combinations.
I have ignored debouncing, and repeating keys.
The Problem
Pasm is very fast and the prop has high impedance inputs. This permits the prop to do ADC inputs (see the SigmaDelta writeups for more information). This is what caused my problem because while scanning from one column to the next, the capacitive effect was holding my row pins high for longer than the column was an output, such that when I enabled and read the next column the older row that was high was still mostly high due to the slow decay because of lead length, capacitance in the prop and the 10K discharge resistors.
The Solution
Between each column scan, set all column and row pins =0 (if pulldowns), set them all as outputs, then disable them as outputs (this clears the residual capacitance. Then set them all as =1 (because you already have this as a mask for setting all outputs) and enable 1 column as an output, read the row inputs. Now repeat the process for the remaining 3 columns (i.e. set all =0 and enable all, etc).
Sample Code
dat '' 10K pulldowns on rows 1-4(P0-3), columns 1-4(P4-7) '' scan the keypad: return keycode scan mov keycode, #0 ' initialise keycode mov scancol, #%0001_0000 ' preset col 1 = P4 mov count,#4 ' 4 cols to scan '' scan a column loop mov outa,#0 ' reset any residual capacitance by outputting 0 to all rows+cols mov dira,scanmask ' all outputs mov dira,scancol ' set one col as output only, all others inputs mov outa,scanmask ' set the active col output =1 (all 1's but only one will be output) mov t1,ina ' read rows 4:1 = P3-0 and t1,#%1111 ' extract 4 row bits shl keycode,#4 ' shift accumulated row bits ready for next set or keycode,t1 ' & add new accumulated row bits shl scancol, #1 ' prepare for next column djnz count,#loop scan_ret ret scanmask long %0000_0000_0000_0000_0000_0000_1111_1111 'P7-0 scancol long 0 count long 0
Comments
Hope I'll remember it once I stumble over a similar problem ;o)
Interesting post, thanks
Instead of doing you fix of clearing the charge by switching to outputs and back to inputs could you have just scanned at a slower pace.
Using waitcnt in you scanning loop. Do you have a reason for scanning the keyboard at light speed. Seems like pull ups work better for
this but maybe I am just use to pull ups and not pull downs.
Tom
Heresy !
I guess folks around here are so used to squeezing every last cycle out of their Prop code such a solution as "go slower" never comes to mind:)
Never mind that last one.
As for pullups, the old method was to use pullups (for us oldies). However, when you use pullups you usually have to invert the data read. Nowadays, there is no difference between pullups and pulldowns, so I use pulldowns if it saves inversion. That said, you will often see pullups like on the -CS of the SD card and SRAMs because you want the default to be disabled.
The problem is worse if you have several inch long traces on a keyboard with the lines running next to each other. So if you have a rectangular keyboard with the wires connected near the top, then the buttons near the bottom of the board might exhibit more of this problem because the traces going to those buttons are longer, running next to each other, and therefore have more capacitance.
Another problem is when using ribbon cables to connect the keyboard to the microcontroller board. Notice that many keyboard ribbon cables have the wires spaced apart! --- As opposed to a ribbon cable used for disk drives, where the wires are right next to each other (greater capacitance between wires run next to each other).
If you are using a ribbon cable with the wires close to each other, one solution is to use scissors and cut the wires apart. Leave them loose so it looks like a rat's nest, then this solves the capacitance problem with the ribbon cable. - A case where the "rat's nest prototype" works, but the "neat and tidy finished product" does not work!
I agree with localroger on the need for current limiting resistors if the port doing the scanning is
not open drain or collector but a low impedence drive. If two switches on the same column are
pressed you can walk through the switches and connect two port outputs together with one driving
the level high and the other trying to drive it low and damage could occur to the port without current
limiting resistors.
Assume (D0,D1,D2) are high and D3 is low in order to test the first column of switches to see if one
is pressed. The red line shows the danger path if both the F and B key are pressed at same time.
The drawing shows open drain port with pull ups and would not be a problem as you just tie two pull ups
together and assuming the port can sink that current there is no issue , but if you were using a port that
drove the output high or low there would be a fight .
Your circuit maybe different.
Tom
No matter how big I make the pic when I insert it, it gets rescalled small.
Tom
The intention of the thread was to state the potential problems and a solution.