PIC32 Keypad Project

Keypad Interface

This is a good use of the ports and also an interesting lesson in switch debounce. The keypad chosen for this project is a small 4 x 3 way:

kepad side viewrear view

This is a typical keypad. It contains 12 switches (pushbuttons) that form a matrix:

switch matrix

When a button is pressed it connects a particular row to a particular column. The technique for reading which key has been pressed is to scan the columns (or rows, makes no difference) with a voltage going from logic 0 to logic 1 (3v3 in our case). Each column will be held at logic 0 at a particular time. If say switch 8 is pressed then when column 2 is held low, row 3 will go low. For this to work properly the rows need to ne normally held high when they are not being pressed. This can be done by external pull up resistors, say 10k or use a port with built in pull up's. In this example we will do the latter.

Wiring

keypad port wiring

Conveniently port b 0:5 can be configured for weak pull ups and so will fit the bill for the row inputs. On the same connector, JP3 port e has 5:7 and so these will be used for the interface.

Practical

The 'experimenters'' version of the BV513 has been used for this and because it has suitable sockets there is no need at this stage for a breadboard.

keypad practical set up

Seven flying leads have been taken from the keypad and put into socket JP3 which conveniently has port b and port e, see the port diagrams for more details on this. There is no need for any other external components.

Programming

The first thing that is required is to initialise the registers and ports, this is done with this code.

// Keypad initialisation
constant AD1PCFG% 0xbf809060
constant CPNUE% 0xbf8861e0
// initialise ports including setting the AtoD channels
// to digital
function kp_start
    pokei(AD1PCFG%+8)=0x0f // just set the ones used
    pokei(CPNUE%+8)=0x3c // weak pull ups on CN5:2
    portw "bts" 0x0f // port b 3:0 = i/p
    portw "etc" 0xe0 // port e 7:5 = o/p
    portw "eos" 0xe0 // all high
endf

Port b is initialised to be input and also the weak pull ups are set in the CPNUE register.

CPNUE register

This extract from the datasheet shows that rb0 to rb3 maps to bits 2:5 of the CPNUE register, this is:
0000 0000 0011 1100 (0x3c) and that is why the CPNUE register has those bits set high. Also because at reset, analogue configuration is the default and port b is used as the analogue input, the AD1PCFG register has to be set. The relevant bits of port e have been set to output.

// scans the keypad by successively lowering the column
// and checking port b for not being all high
// returns a code based on the input
function kp_scan
dim i,p%
    for i = 1 to 3
        portb "eoc" 4+i // col 5-7 low
        portw "bi" p% // get data
        p%=and(p%,0x0f)
        portb "eos" 4+i // col 5-7 high
        breakif p% <> 0x0f
    next
    result p%*i*i
endf

This function will scan the keypad once and return a scan code representing which key has been pressed. At the start all of port e is high and the first iteration into the loop sets one of the lines low, in doing so it checks port b to see if any of those lines have gone low, if not it sets the next line low. If one of the port b lines is low then the loop breaks.

To obtain a scan code the value on port b is multiplied by the square of the row number (represented by i). The square is used because if only 'i' were used then two scan codes, representing the numbers 1 and 0 are the same at 14.  If just scan codes are required then that is complete, however some more work could be done:

// this returns a value depending on the scan
// code, the codes received are, in number order
// input 14,56,126,13,52,117,11,44,99,7,28,63 and 240 for no key
// output 1,2, 3, 4, 5, 6, 7, 8, 9,10,0, 11 and 12
function kp_translate(scancode&)
dim rv, keyp& // only character storage needed
dim k&[13]
    k&[1]=28:k&[2]=14:k&[3]=56:k&[4]=126:k&[5]=13:k&[6]=52
    k&[7]=117:k&[8]=11:k&[9]=44:k&[10]=99:k&[11]=7:k&[12]=63
    k&[13]=240
    // kp_start // for a larger program put this with the other init code
    for rv = 1 to 13
        breakif scancode&=k&[rv]
    next
    result rv-1
endf

This function will return a value that represents the actual key pressed and the number 12 for no keys pressed. As the scan codes are all under 255, character storage can be used and with an array this is reasonably efficient as the name is only stored once in RAM.

In Use

To test the program simply type:

kp_start

followed by

print kp_scan

The value returned should be 240, with your finger on a key the result will be different. For example pressing the key 1 returns a value of 14. This actually may be different on your system as it depends on exaclty how the columnds and rwows have been wired.

print kp_translate(14) returns 1 as expected.

The most obvious way to use this code is to place it within the main program loop however there are a couple of problems that needs to be solved when used properly that are not obvious now.

function xx
dim k
    kp_start // ** moved to here
    while key?(2) = 0 // just to get out of loop
        k=kp_scan
        if k < 240 then
        print kp_translate(k)
    endif
    wend
endf

Try the above test routine, the key?(2) is so that the infinite loop can be exited simply by pressing any key on the PC keyboard. If you press a number once you get multiple instances of the same number, it is actually very difficult to get just one number. The second problem is that if the program is doing something else how do you know if you have missed a key press?. There are many solutions to these problems and they very much depend on the final application.

The common solution to this problem is to wait until the user lets go of the key before scanning for another one. This is where you would normally get debounce problems. A key press, although just one press to us humans, to a microcontroller represents several key presses as the metal contacts slowly come together. I didn't get any debounce problems on the test machine, this is probably because it takes just about the right amount of time to scan all of the rows.

function xx
dim k
    kp_start // ** moved to here
    while key?(2) = 0 // just to get out of loop
        k=kp_scan
        if k < 240 then
            print kp_translate(k)
            while kp_scan < 240:wend // wait for release
        endif
    wend
endf

This code actually works well BUT it does hold up all processing whilst ever a key is held down.

Schedule It

Here is an introduction into scheduling or processes which the keypad application is ideally placed to take advantage of. It is possible in this Basic to schedule tasks to occur every so often. In the case of the keypad it would be ideal to scan it a couple of times with a gap between, this would take care of key bounce. A buffer can also be implemented so no keys are missed.

The idea is to have a circular buffer so that keys come out in the same order as they went in, if 1,2,3 were pressed then 1,2,3 would be required when reading the buffer. The way to do this is using a buffer and two pointers, an input pointer updated by the keypad and an output pointer updated by the extractor routine. The circular part comes in when the buffer gets full, the input pointer is simply reset to the beginning and the output pointer follows it. To schedule all of this requires global variables so that when a task is called it can keep track of what happened last time.

function kp_init_process
    kp_start // initialise
    dim kp_count=0, kp_value, kp_ibptr=1, kp_obptr=1
    dim kbuf&[31]
    keep
endf

In addition to the previous initialisation discussed before we now have additional variables: A 'kp_count', a 'kp_value' to keep track of the last value obtained form the keypad, two pointers, input and output and a buffer array. These must still exist when the function exists hence the 'keep' keyword.

// inserts any keypress into buffer, this relies on the interval
// for debounce so needs tuning to application, suggest
// start with an interval of 250
function kp_proc_scan
dim k
    k=kp_scan
    if kp_count > 1 then
        if k = 240 then
            kbuf&[kp_ibptr]=kp_value
            kp_ibptr=kp_ibptr+1
            if kp_ibptr>30 then
                kp_ibptr=1
            endif
            kp_count=0
        endif
    else
        if k <> 240 then
            kp_count=kp_count+1
            kp_value=k
        endif
    endif
endf

This function will be called every 35ms as a scheduled task, (called a process in PIC32-Basic) this is why it needs the global variables, they act as it's memory. If we start with kp_count as 0 then the 'else' part of the main if will be executed. If there is no key pressed (k=240) then the function will end. If a key is now pressed, k will not equal 240 and kp_count will be incremented, the value of k is also stored. If on the third pass the key is still pressed, because kp_count > 1 the first 'if' gets executed but because k <> 240 nothing else will happen and the function will exit.

At this stage when the user lets go of the key, the first 'if' will get execusted and k=240, so the buffer will be updated and the input buffer pointer 'kp_ibptr' will be incremented.

Some interesting points about this:

  1. As this is scheduled and the process takes a fixed time regardless of what the user is doing there is no 'blocking'. Blocking means holding up the processor so that nothing else can be done.
  2. 35ms x 2 is more then enough time for the key to debounce and settle to a valid value, 25 to 50ms would be better from a debounce point of view but this may effect response time of the keys.

To get the keys from the buffer, these two routines are used. The first one 'kp_get?' simply indicates if there is a key available. The second 'kp_get' will wait in the function until a key is pressed. Note how the output buffer chases the input buffer.

// checks to see if there is a key in the buffer
// retunrs 1 if there is, 0 otherwise
function kp_get?
dim rv=0
    if kp_ibptr <> kp_obptr then
        rv=1
    endif
    result rv
endf

// gets 1 key from pad, will not return until
// a key is pressed
function kp_get
dim rv
    while kp_get?=0:wend // wait
    rv=kp_translate(kbuf&[kp_obptr])
    kp_obptr=kp_obptr+1
    if kp_obptr > 30 then
        kp_obptr =1
    endif
    result rv
endf

All of this of course relies on kp_scan being in a process and so to get the whole system working this is used:

/ initialises the globals and starts the process
// can be called more than once as vexists prevents duplication
function kp_go
    if vexists("kp_flag")=0 then
        kp_init_process
        process kp_proc_scan every 35 // 35 ms debounce
        keep
    endif
endf

The 'go' function initialises the variables and places the function in a process, 125 is 12.5ms. The other two functions are for retrieving the keys pressed. Notice the technique to see if there are any keys in the buffer.

screen shot of running keypad process

The screen shot shows that when 'kp_go' is typed it comes back to the ok prompt. To verify that the process is running type ps and as can be seen from the screen shot the kp_process is running.

The full code for this project can be downloaded here.