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:


This is a typical keypad. It contains 12 switches (pushbuttons) that form a 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

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.

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.
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.

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.
// 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:
// 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.
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.
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.
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.
// 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:
- 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.
- 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.
// 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:
// 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.

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.