Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming Microcontrollers in C, 2-nd edit (Ted Van Sickle, 2001).pdf
Скачиваний:
296
Добавлен:
12.08.2013
Размер:
7.2 Mб
Скачать

432 Chapter 8 MCORE, A RISC Machine

Every function in the program with the exception of main() has a function prototype at the beginning of the program. In this case, the functions are all void functions that require no arguments. It is important, however, that every programmer get into the habit of placing these prototypes at the beginning of their programs, either in the form of a header file or a direct listing in the program.

Keyboard

The MMC2001 chip has a built-in keyboard interface. This interface can be either synchronously polled by the program, or it can be asynchronously controlled through the use of an interrupt. We will examine the asynchronous control here. This program requires three parts. The first is the initialization routine. This routine sets up the operation of the keyboard to cause an interrupt whenever a key is depressed. The interrupt service detects which key is depressed and saves it in a buffer. The program also saves a semaphore passed to the initialization routine. This semaphore is released when the end of a line is detected in the input. Then the main program prints the buffer to the terminal screen.

The initialization routine is shown below:

#include “mmc2001.h” #include “keypad.h” #include “intctl.h”

static int semaph;

static BYTE *data,*savedata; static int leng;

/* key board initialization routine */ void kpinit(int sem, BYTE *s, int length)

{

KPCRN.LOROWS=0xf;

/* enable keypad rows */

KPDRB.COLUMNS=0x0;

/* write 0 to KPDR[15:8] */

KPCRN.LOCOLS=0xf;

/* make cols open drain */

KDDRN.LOCOLS=0xf;

/* make cols outputs */

KDDRN.LOROWS=0x0;

/* and rows inputs */

KPSR.KPKD=ON;

/* clear KPKD status flag */

KPSR.KDSC=ON;

/* clear key depress synchronizer */

KPSR.KDIE=ON;

/* set KDIE bit to enable depress

 

Interrupt */

 

Keyboard 433

 

 

KPSR.KRIE=OFF;

/* disable release interrupt */

FIER.EF6=ON;

/* enable Fast Interrupt number 6 */

semaph=sem;

/* save the semaphore */

data=s;

 

savedata=s;

/* and array information for */

leng=length;

/* use in the isr */

}

It is expected that the calling program will attach a semaphore and provide a buffer into which the input data will be stored. The semaphore is passed to the initialization routine as sem, and the array by a pointer to its first element and its length. We plan to use a 4 by 4 matrix keyboard. This keyboard will be connected to the least significant four rows and columns of the keyboard controller interface. There are several registers that attach to these interfaces. The KPCR is a control register, the KPDR is the data register, and the KDDR is the data direction register. In creating the header file for the keyboard controller, I added an extra letter, N or B, to these pseudovariable names to indicate that they have been broken into groups of bits rather than a register of single bits. A register name that has the letter N appended is split into two 8-bit fields named COLUMNS and ROWS. Those with a letter B appended are split into four fields named HICOLS, LOCOLS, HIROWS and LOROWS. Therefore, the command

KPCRN.LOROWS=0xf;

will cause the least significant bits of the ROWS field in KPCR to be set to 1.

The comments above explain the various actions that take place in the initialization routine. The Key Pad Data Register, KPDR, is a register that connects to the external keyboard interface. The most significant 8 bits are to connect to columns and the least significant bits connect to the rows. The plan is to detect cross connections between a column and a row. The columns serve as outputs and the rows inputs. Therefore, the columns will be set as open drain and as outputs through the data direction register. In the KBSR, Key Board Status Register, the Key Pad Key Depressed bit, KPKR, is set whenever the system detects a depressed key. This bit is reset by writing a 1 to it. On both key depress and key release, there is a synchronizer that aims to hide any key bounce that might occur. The key depress synchronizer is cleared by writing a 1 to KPSR.KDSC.

434 Chapter 8 MCORE, A RISC Machine

Next the key depress interrupt is enabled and the key release interrupt is disabled. The parameters passed to the initialization routine are now saved in static external variables. These parameters are used in the interrupt service routine.

The column outputs on the four least significant bits are all set to zero so that any key depression will cause the normally high inputs sensed by the key inputs to be pulled low. This action will initiate the key depress synchronizer operation. Approximately four milliseconds later, if the input key has the same state, a key depressed interrupt will be requested. The key release operates similarly. As long as a key remains depressed, nothing happens. When the key is released, the key release synchronizer initiates its action. If the key has the same state after the synchronizer time-out, a key release interrupt can be requested. For these operations to work, the column outputs must all be held at zero.

It is most convenient to use both the key depress and the key release interrupts for the task at hand. Each time a key is depressed, it is detected and the position of the key in the keypad is saved in a buffer. Conditions to cause a key release interrupt are then set up. Then control is returned to the interrupted program. When the key is released, a key release interrupt is requested, and in the interrupt service routine, a key depress interrupt is set up so that the next key press will be detected. Each time a key position is entered into the buffer, the next position in the buffer is marked with a 0xff.

I speak of the key position. In the interrupt service routine below, it is first tested to determine if the interrupt was caused by a key depress or a key release. If it is a key depress interrupt, control is immediately passed to a for loop. Within this loop, the columns are first loaded with a 0xe, which will have the three highest bits in the column high and the least significant bit low. A test checks to determine if all of the column bits are high. If not the value of column is impressed on KPDRB.COLUMNS. The next test checks the value found on KPDRB.ROWS. If there is no button depressed that connects column 1 with any of the rows, the result will be 0xff. Therefore, control will go to the next column. If the value found on rows is not 0xff, the least significant four bits will indicate the row in which the switch is depressed. Then, the row and column data is combined into a single value that is stored in the data array. The column contains 4

Keyboard 435

bits, as does the row. These bits are packed into a single 8-bit value by the instruction

c=*data++=(~(KPDRB.COLUMNS<<4 | (KPDRB.ROWS&0xf))) & 0xff;

The column value is shifted left by four bits and the result is bit wise ANDed with the row value. These values are the ones-complement of the values we want, so the ones-complement is taken. The final result is ANDed with a mask of 0xff to delete any bits outside of the eight bits needed for the final result. This result is stored in the array back in the calling program. Also at this time, the result is stored in a local variable for later use. The count of the number of characters entered into the array are incremented. If this value exceeds the length of the array, there is an error and the input is completely terminated when the program is restarted at its very beginning.

#define COL_0 0x0e

/* key board interrupt service routine */ void kb_isr(void)

{

UHWORD column,input; static BYTE c;

if(KPSR.KPKD) /* if a key has been depressed */

{

for(column=COL_0;column!=0xf;column=(column<<1 |0x1) & 0xf)

{

KPDRB.COLUMNS=column; /* kill some time */ if(KPDRB.ROWS!=0xff)

{

c=*data++=(~(KPDRB.COLUMNS<<4 | (KPDRB.ROWD&oxf))) & 0xff;

if(++n>=leng) /* data exceed the array */

{

puts(“input data overflow\n”); _start(); /* bad error, restart the

program */

}

}

}

*data=0xff; /* needed to terminate decode */ if(c==0x11) /* ‘=’ reached the end of the input */

436 Chapter 8 MCORE, A RISC Machine

{

decode(savedata); release_semaphore(semaph);

}

KPDRB.COLUMNS=0X0; /* write 0 to KPDR[15:8] */ KPSR.KDIE=OFF;/* disable key depress interrupt */

KPSR.KRIE=ON; /* enable key release interrupt */

}

else /* key release was detected */

{

KPSR.KPKR=ON;

KPSR.KPKD=ON; /* clear key release and depress */ KPSR.KRSS=ON; /* set key release synchronizer */ KPSR.KDSC=ON; /* clear key depress synchronizer */ KPDRB.COLUMNS=0X0;/* write 0 to KPDR[15:8] */ KPSR.KDIE=ON; /* enable key depress interrupt */ KPSR.KRIE=OFF;/* disable key release interrupt */

}

}

Notice in the code above that after the columns value is set and before the row values are tested, there is a comment to kill some time. It turns out that some time is required to allow propagation from the command to the electrical connection on the external switch. The delay() routine with an argument of 1, a 1-ms delay, worked well, but this routine also uses the PIT. It is intended to use this routine with the clock shown in the earlier section, so it is very inconvenient to use the clock. Therefore, a series of instructions around the keyboard controller were put together that would use up the necessary time. These instructions would have to be executed to set up correct conditions prior to the return from interrupt, so it is not too much a waste of computer resources. To get enough delay, I used some of these instructions more than one time. The code for this time delay is shown below. The last six lines of code are required to set up for the exit from the isr, and the first four are duplicates needed to round out the time.

KPSR.KRSS=ON;

 

KPSR.KPKD=ON;

/* flip and flop some innocuous bits */

KPSR.KDIE=OFF;

/* just to kill time to synchronize */

KPSR.KRIE=OFF;

/* the command with data arrival at */

KPSR.KPKR=ON;

/* the chip’s edge */

KPSR.KPKD=ON;

 

KPSR.KRSS=ON;

 

Keyboard 437

KPSR.KDSC=ON;

KPSR.KDIE=OFF;

KPSR.KRIE=OFF;

After the key position is detected, and its value stored in the array, the value 0xff is placed in the next location in the array. This value is used in the decode routine that changes the key positions to ASCII characters for display. A termination character is needed to show the end of the input data has been reached. There is an ‘=’, equal sign on the keyboard, and it was decided to use it as the termination character. We will see later that the position value for the ‘=’ symbol is 0x11. Therefore, if the value c is 0x11 at this point in the code flow, it is time to decode the buffer contents and proceed further. After the data are decoded, the semaphore is released and control is returned to the interrupted program. The COLUMNS bits are all set to zero, the key depress interrupt is disabled and the key release interrupt is enabled prior to the return.

It turns out that it is next to impossible to get your finger off a key soon enough to allow this code to work without detecting the key release. The key release interrupt is set up in the key depress interrupt service routine. The main operation that takes place in the key release isr is to disable the key release interrupt and set up for a key depress interrupt. Without this approach, each time the key is depressed, it will get through the isr several times before the key is released and the buffer will be filled with duplicate values each time the isr is executed. The last seven lines of code in the isr above are executed in the key release isr.

The decode function converts a value that represents the position of the depressed key on the keyboard to an ASCII representation of the character. The position data is an 8-bit unit with the upper 4 bits representing the column and the lower 4 representing the row. Only one column or row bit should be enabled at a time. More than one row or column is enabled if multiple keys have been depressed. The acceptable row and column values are 1, 2, 4, and 8. The circuitry fixes the row value of 1 corresponding to the bottom row with the value 8 as the top row. Also, the right-most column corresponds to column 1 and the left-most column corresponds to column 8. The inner rows and columns follow the pattern established above. A look at the keyboard has the top row, read from left to right, containing

438 Chapter 8 MCORE, A RISC Machine

‘+’, ‘7’, ’8’, and ‘9’. The next row contains ‘-‘, ’4’, ’5’, and ‘6’. The third row from the top contains ‘*’, ‘1’, ‘2’, and ‘3’. The bottom row ‘/’, ‘0’, ‘.’, and ‘=’. Remember that the right-most column is column 1 in the position and the bottom-most row is row 1. Therefore, one can build a matrix as shown below that converts the position of the key to its ASCII value. Now the positions must be indices into a twodimensional array rather than the position values. The decode routine must convert the position values to indices.

The decode routine works on an array that contains several key positions. This data array is terminated with a character 0xff. The string is passed as a pointer to the beginning of the string. In the function, the value of the pointer is copied to another pointer to a type BYTE that is used in the program. A while loop is entered which will walk the length of the array that contains all of the key position data. This loop is terminated when the data 0xff is found in the input array. Each piece of input data is now split into two parts, the rows and the columns, and a switch/case statement is used to convert the 1, 2, 4, 8 values that the input data contains into the 0, 1, 2, 3 values used for indices into the two-dimensional array. In the event of a multiple key hit, a default value of 4 is passed to either the row or column. These values are tested for, prior to access to the keys[][] array. If a 4 is found in either case, the decode loop is skipped to the next character. This approach does not prevent errors in decoding, but it does keep control and allows the procedure to be restarted.

static

const BYTE keys[][4]=

{{‘=’,’.’,’0',’/’},

 

 

{‘3’,’2',’1',’*’},

 

 

{‘6’,’5',’4',’-’},

 

 

{‘9’,’8',’7',’+’}};

/* the

key position decode routine */

static

void decode(BYTE *s)

 

{

 

 

int

r,c;

 

BYTE *p=s;

while(*p !=0xff)

{

switch(*p & 0xf)

{

case 8: r=0;

Keyboard 439

break; case 4: r=1;

break; case 2: r=2;

break; case 1: r=3;

break; default: r=4;

}

switch(*p & 0xf0)

{

case 0x10: c=0; break;

case 0x20: c=1; break;

case 0x40: c=2; break;

case 0x80: c=3; break;

default: c=4;

}

if(r==4||c==4)

break; /* got a multiple key hit, skip it*/ *p++=keys[r][c];

}

*p=’\0';

data=savedata;

}

Lastly, after the character is read from the array and stored into the array, the loop is continued. When the loop is terminated, a null character is written to the next entry in the array. This character is a string terminator and the data can be used as such with any of the input/output functions. In the initialization routine, two values of pointers to the array a[] were saved. The value saved in data has been corrupted by now and it is necessary to restore it to its original value by assigning savedata to data.

Next, we have to test the code above. The following program tests the keyboard initialization and isr routines. To use these routines, you must first adjust the code in the program handler() and recompile it. This code must be linked to the following program. The address of the routine kb_isr() must be placed in the specified location in the vector table found with the handler() function.