Файл: Programming Microcontrollers in C, 2-nd edit (Ted Van Sickle, 2001).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 15.06.2025
Просмотров: 4054
Скачиваний: 1
410 Chapter 8 MCORE, A RISC Machine
The function getchar() reads a single character from the serial port. This function also mimics the getchar() that you are used to using, because it echoes the character received to the serial port output. There might be an occasion in which you want to read in a character without echoing it to the serial output. In that case, you can use getch() shown below. This function works exactly the same as getchar() but it does not echo the data received.
#define U1SRint (*(volatile unsigned short *)(0x1000a086)) enum {TRDYint=8192};
#define U1RXint (*(volatile unsigned short *)(0x1000a000)) enum {CHARRDYint=32768};
/* send a character out the serial port when it is ready */ static void put(BYTE x)
{
while((U1SRint & TRDYint)==0)
; /* wait until register available */ U1TX.DATA =x; /* send the data out */
}
/* read in and echo a character through the serial port */ BYTE getchar(void)
{
BYTE a;
while((U1RXint & CHARRDYint)==0)
; /* wait till character is ready */ a=U1RX.DATA;
putchar(a); return a;
}
/* read in a character with no echo */ BYTE getch(void)
{
BYTE a;
while((U1RXint & CHARRDYint)==0)
; /* wait till character is ready */ a=U1RX.DATA;
return a;
}
Serial Input/Output 411
/* read in a character from the serial port. Do not check for the character ready. This routine should be used with kbhit(). */
BYTE getce(void)
{
return U1RX.DATA;
}
/* return TRUE when a key has been hit and FALSE otherwise */ int kbhit(void)
{
return U1RX.CHARRDY;
}
Listing 8-7: Direct I/O Functions
The last two functions shown in Listing 8-7 are useful when you need to exit a function if there is an asynchronous keyboard hit. The function kbhit() returns a logical TRUE or FALSE. Its return should be used as the argument to an if() or while() test. Whenever the keyboard is touched, kbhit() returns a TRUE. That means that a character has been entered into the keyboard. If you need to read and use the value entered, the test to determine if a character is ready is not necessary. Therefore, the function getce() was written to read in the value contained in the data register without involving a test to show that there is a character ready to be read. These two functions, kbhit() and getce() should be used together.
The next four functions shown below also access the serial input and output, but they all use the functions above for the direct access and have no computer specific code. The function putchar() checks to determine if the parameter x is a ‘\n’. If it is, the function put() is called twice with a ‘\n’ and a ‘\r’ argument. Otherwise, the character passed to the function is sent to put() where it is sent to the serial port.
/* Send a character to the serial port when the port is available. If a ‘\n’ is received send a ‘\n’ followed by a ‘\r’ sequence. */
void putchar(BYTE x)
{
if(x==’\n’)
412 Chapter 8 MCORE, A RISC Machine
{
put(‘\n’);
put(‘\r’);
}
else put(x);
}
/* send a string to the serial port */ void puts(BYTE *a)
{
while(*a!=’\0')
putchar(*a++);
}
/* This function reads a string into the buffer a. The length of the buffer is max. If the string is less than max long, the function returns the number of characters entered. If the string is longer than the buffer, the buffer is filled and a -1 is returned. The input string is terminated by either a ‘\n’ or a ‘\r’. */
int gets(BYTE *a,int max) /* no echo */
{
int i=0,c;
do /* read in data a byte at a time */
{
*a++=c=getch(); }while(c!=’\n’&&c!=’\r’&&++i<max-1); *a=’\0'; /* make it a string */ return (i>=max)?-1:i;
}
/* same as gets() but data entered are echoed */ int getse(BYTE *a,int max) /* with echo */
{
int i=0,c;
do
{
*a++=c=getchar(); }while(c!=’\n’&&c!=’\r’&&++i<max-1); *a=’\0';
return (i>=max)?-1:i;
}
Listing 8-8: General Serial Input/Output Functions
Handling Interrupts 413
The next three functions can be used to send and receive strings through the serial port. To use puts(), you use a string argument. This function will send characters from the string until it finds a zero value in the string. The get string functions perform similar to the standard fgets() function. The difference between gets() and getse() is that getse() echoes the characters read in to the serial output. Otherwise these two functions are identical. You pass gets() two parameters. The first is a pointer to a character array and the second is the dimension of this array. As characters are read into the program, they are stored in the character array. The string input is terminated by either a ‘\n’ or a ‘\r’ character. The input will also be terminated when the input character string is one less than the size of the array size. When termination is detected, the input is terminated with a character zero, making the data a string. The return to the calling program will be
–1 in the event that the character array was completely filled. Otherwise, the return is the number of characters entered into the character array.
The function gets() reads data from the serial port with the getch() function. This input does not echo the data entered. getse() uses the getchar() to read the data in. getchar() always echoes the input data to the serial output.
The above routines were separated into a series of individual functions, which were combined into an archive library file. This file is found on the CDROM under the name libserio.a. This file can be linked during the linking operation like any other library file.
Handling Interrupts
All of the onboard peripherals found on the MMC2001 that need access to interrupts are set up to use the core processor Auto Vector capability. Shown in Table 8-1 is a copy of the interrupt vector table. The vector table is placed at a location called the VBA, Vector Base Address, when the chip is initialized. You will note that the table is 0x200, two hundred hex bytes, long. The vector numbers are consecutive. The vector addresses move in steps of 4 and are usually dealt with as hexadecimal values. This table must be filled in some way when the program is initialized. Each vector will contain the address of the function executed when the corresponding exception occurs. Most of these vectors are self-explanatory. Of import here is the Fast Interrupt Autovector location. Note that the offset of this vector is 0x2c.
414 Chapter 8 MCORE, A RISC Machine
The use of this table is similar to that shown with the MC68HC16 family. Here, though, we will concentrate on the use of the autovector. The autovector is accessed when an interrupt is called with the autovector line to the core processor asserted. When this type of interrupt is executed, control of the processor is automatically transferred to the function addressed contained in the autovector vector. Note in the table that there are two autovector locations, the normal autovector and the fast autovector. The DIAB compiler automatically encodes interrupts to use the fast autovector, so the offset vector location is 0x2c from the Vector Base Address.
The MMC2001 has an internal peripheral called the Interrupt Controller. This device handles all interrupt sources from the onboard peripherals. There are several 32-bit registers in the Interrupt Controller. You will notice in the MMC2001 Reference Manual, Section 10, that these registers are each just collections of 32 single bits. These registers control interrupts. The first register is called the Interrupt Source Register, INTSCR. Whenever an interrupt is requested by this controller, it is assigned a level from 0 to 32 and the corresponding bit in the INTSCR is set. There are two interrupt enable registers: Normal Interrupt Enable, NIER and Fast Interrupt Enable, FIER. The program must set the corresponding bit in one of these registers to enable an interrupt to be detected.
When an interrupt is requested, the corresponding bit in the INTSCR is set and this register is automatically ORed with the Interrupt Enable registers. The results of these operations are stored in the proper Interrupt Pending Register. These registers, NIPND and FIPND, then contain a bit pattern that show all of the pending interrupts for the system.
There is one very convenient instruction available in the MCORE instruction set. This instruction, FF1, indicates “find the first 1 set” in a memory location. This instruction uses the system barrel shifter and requires only one clock cycle. The priority of the several interrupting sources is established by their individual bit locations in the various registers in the controller. In the FIPND registers, bit 31 contains the status of the highest priority pending interrupt, etc. When the FF1 instruction is executed on the FIPND, the result identifies the bit number of the highest priority pending fast interrupt. Table 8-2 shows the interrupt assignments of all on-board peripherals on this chip.
Handling Interrupts 415
The FF1 instruction returns a 0 if bit 31 is set and 31 if bit 0 is set. It also returns a 32 if no bit is set in the designated memory location. Therefore, if the PIT is the highest requested interrupt, the contents of the FIPND register are stored in R3, and the instruction
FF1 R3
is executed, R3 will contain the numeric value 24. We can use this sequence to vector to the correct interrupt service routine. Consider the code shown in Listing 8-9. This function is a general-purpose interrupt handler to control the autovector access. The program starts with the normal file inclusions. In this case, the interrupt controller is being used so the header intctl.h is included. The code that follows must access the contents of the fast pending interrupt register FPIND. The address of this register is placed into the location fIpnd1 for convenient access by the assembly program that is generated later.
The next entry defines a struct Table, which contains an array of 32 pointers to functions that require no parameters and return the type void. An instance of this structure is created and named table. The 32 entries in the array are each filled with a pointer to the function unused_vector(). It is difficult to decide what to fill unused vectors with. The choice of zero is not realistic. When an interrupt occurs that needs one of these vectors, control is passed to the address contained in the vector. If the vector contains zero, the computer goes to zero and starts executing instructions found there. The program will surely run amuck until who knows when if it is told to execute the code found at zero! I usually create a simple interrupt service routine that does nothing to help here. The function in the listing below is called unused_vector(). In this case, if an uninitialized interrupt occurs, it will be ignored and control will be passed back to the executing program. This particular approach has the drawback that you are never aware of the occurrence of uninitialized interrupts unless you put a break of some sort in the unused_vector() routine.
In practice, you will need to place the names of interrupt service routines in the proper vector locations shown below. We will do this in later code to show you how.
#include “mmc2001.h” #include “intctl.h”
UWORD const fIpnd1=INTCTL_+0X10;
416Chapter 8 MCORE, A RISC Machine
void handler(void);
void unused_vector(void);
static struct Table {
void (*vector[32]) (void);
};
struct Table table ={ unused_vector, /* 31 unused */ unused_vector, /* 30 unused */ unused_vector, /* 29 unused */ unused_vector, /* 28 INT7 */ unused_vector, /* 27 INT6 */ unused_vector, /* 26 INT5 */ unused_vector, /* 25 INT4 */ unused_vector, /* 24 INT3 */ unused_vector, /* 23 INT2 */ unused_vector, /* 22 INT1 */ unused_vector, /* 21 INT0 */ unused_vector, /* 20 ISPI */
unused_vector, /* 19 UART1 receive */ unused_vector, /* 18 UART0 receive */ unused_vector, /* 17 UART1 transmit */ unused_vector, /* 16 UART0 transmit */ unused_vector, /* 15 PWM5 */ unused_vector, /* 14 PWM4 */ unused_vector, /* 13 PWM3 */ unused_vector, /* 12 PWM2 */ unused_vector, /* 11 PWM1 */ unused_vector, /* 10 PWM0 */ unused_vector, /* 9 unused */ unsued_vector, /* 8 PIT */ unused_vector, /* 7 Time-of-day alarm */ unused_vector, /* 6 KPP control */ unused_vector, /* 5 UART0 RTS_DELTA */ unused_vector, /* 4 unused */ unused_vector, /* 3 unused */ unused_vector, /* 2 software3 */ unused_vector, /* 1 software2 */ unused_vector /* 0 software1 */
};
#define Do_Interrupt() asm(“ subi R0,32\n subi R0,28\n \ stm R1-R15,(R0)\n lrw R2,table\n \
lrw R3,fIpnd1\n ldw R3,(R3,0)\n \
ldw R3,(R3,0)\n FF1 R3\n lsli R3,2\n \
Handling Interrupts 417
addu R2,R3\n ldw R2,(R2,0)\n \ jsr R2\n ldm R1-R15,(R0)\n \ addi R0,32\n addi R0,28\n rfi\n”)
void unused_vector(void) {}
void handler(void)
{
Do_Interrupt();
}
Listing 8-9: Autovector Interrupt Handler
Following the table initialization, there is an assembly language sequence. This sequence is #defined as the function Do_Interrupt(), which is called in the interrupt service routine handler(). The first five assembly instructions clear 120 bytes on the stack, enough to save the contents of registers 1 through 15, and
Table 8-1 Exception Vector Assignments
Vector |
Vector |
Assignment |
|
Number(s) |
Offset (Hex) |
||
0 |
000 |
Reset |
|
1 |
004 |
Misaligned access |
|
2 |
008 |
Access error |
|
3 |
00C |
Divide by zero |
|
4 |
010 |
Illegal instruction |
|
5 |
014 |
Privilege violation |
|
6 |
018 |
Trace exception |
|
7 |
01C |
Breakpoint exception |
|
8 |
020 |
Unrecoverable error |
|
9 |
024 |
Soft reset |
|
10 |
028 |
INT autovector |
|
11 |
02C |
FINT autovector |
|
12 |
030 |
Hardware accelerator |
|
13 |
034 |
(Reserved) |
|
14 |
038 |
||
15 |
03C |
||
16-19 |
040-04C |
Trap #0-3 Instruction Vectors |
|
20-31 |
050-07C |
Reserved |
|
32-127 |
080-1FC |
Reserved for vectored |
|
interrupt controller use |