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

Delays Revisited 401

Delays Revisited

With semaphores available, we can improve on the delay function by removing the wait loop from within the function to the calling function, where its use can be dictated by the program. In this case, the calling function will create a semaphore and send the semaphore to the delay function. The proposed delay function is

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

/*********************************************************** Delay by t milliseconds. This delay controls a semaphore that will be examined by the calling program. When the semaphore is released, the delay is completed. It is assumed that fast interrupts are enabled when this func­ tion is called and that the interrupt handler has been appropriately set up.

*****************************************************************/

static int sem; /*

this variable needs to be file global */

void delay(WORD t,

int semaphore)

{

 

long count;

 

sem=semaphore;

/* save semaphore so isr can see it */

ITCSR.EN = ON;

/* enable the pit */

ITCSR.OVW=ON;

/* enable write through */

/* an interrupt

will occur in t milliseconds */

count =(long)t*1000; /* make the delay in us */

ITDR =count/122;

/* 122 us per tick */

ITCSR.ITIE = ON;

/* enable pit interrupt */

FIER.EF8=ON;

/* enable the fast interrupts */

} /* return to the

calling program, we are done here */

void pit_isr(void) /* interrupt occurs when delay time expires */

{

ITCSR.ITIF

= ON;

/* turn interrupt flag off */

ITCSR.ITIE = OFF;

/* disable the PIT interrupt */

ITCSR.EN

=

OFF;

/*

disable the PIT */

FIER.EF8

=

OFF;

/*

disable the pit fast interrupt */

release_semaphore(sem); /* release semaphore to calling program */

}

Listing 8-3: Alternate Delay Routine

402 Chapter 8 MCORE, A RISC Machine

The program that calls the delay() function above has some responsibilities in making this function work. This delay is using the PIT interrupt. Most of the code necessary to implement the interrupt is contained in the delay function. There are, however, a couple of items that have to be taken care of outside the delay routine. The first is to place the address of the interrupt handler into the fast interrupt vector. Also, the system fast interrupts must be enabled. You will see these matters are taken care of in the following test function.

In the previous routine, the semaphore number is saved externally so that it can be accessed by the interrupt service routine. Next the PIT is enabled and the ITDR to ITADR write through is enabled so that the value written to the ITDR is the value to be counted down in the timer. The count value is next calculated. Remember, the counter is driven at 8192 Hz. This value is obtained by counting the output from a crystal-controlled oscillator running at 32768 Hz by 4. The time per count is approximately 122 microseconds. The delay time passed to the function is in milliseconds, so to calculate the count value to be placed into the ITDR/ITADR, the program first converts the delay time to microseconds by multiplying it by 1000. Then, the count value is calculated by dividing the microseconds by 122. This value is written to the ITDR and it is automatically written through to the ITADR where it is counted down by the hardware.

The remaining code in the delay()routine enables the PIT interrupt and also enables the fast interrupt, bit 8, that is connected to the output from the PIT. Control is then passed back to the calling program.

#include “mmc2001.h” #include “serial.h”

#define FAST_AUTOVECTOR 0x3000002c

/* function prototypes */ void handler(void);

int attach_semaphore(void); void wait_for_semaphore(int);

main()

{

UWORD count=0; int semaphore;

Delays Revisited 403

inituart(38400); Enable_Fast_Interrupts(); vector(handler,FAST_AUTOVECTOR); while(count++<300)

{

if((semaphore=attach_semaphore())==-1)

{

puts(“semaphore attachment error\n”); exit(0);

}

delay(1000,semaphore); wait_for_semaphore(semaphore); printd(count);

putchar(‘\r’);

}

}

Listing 8-4: Delay Test Routine

The delay test routine first initializes the on-board UART so that signals can be sent to a terminal and then enables the fast interrupts and places the handler address in the FAST_AUTOVECTOR location as needed. The main test attaches a semaphore, executes a delay of 1000 milliseconds, waits for the delay to expire and prints a value to the screen. This sequence is placed in a loop that is executed 300 times. Note that it is important that attach_semaphore() be checked for a –1 return. If not, you could attempt to attach too many semaphores, and your code would not catch the error. In the above case, the program was exited with the exit() function. It is not usually necessary to abort the program when there is no semaphore when one is needed. You can merely wait for a semaphore with any value between 0 and 9. When that semaphore is released, you can proceed with a request to attach a semaphore and it should then succeed.

The attach semaphore is in the calling program in this case. The semaphore number is sent to the delay program where it is in turn passed to the pit interrupt service routine. When the interrupt service routine, after the proper delay, is executed, the specified semaphore is released. Therefore, in the calling program, the wait for semaphore routine is executed until the semaphore is released. If needed, the semaphore status can be polled synchronously by the program to determine when the delay time has expired. In other words, the

404 Chapter 8 MCORE, A RISC Machine

computer does not have to sit in a wait loop until the delay time is over to implement the delay.

The two delay functions, those shown in Listings 8-1 and 8-3, are examples of code that are very close to the microcontroller. The application program shown in Listing 8-4 is the type of program that could almost be viewed as divorced from the microcontroller. In 8-4 the two operations

Enable_Fast_Interrupts();

vector(handler,FAST_AUTOVECTOR);

are clearly processor dependent, but these processor dependent functions are written as functions, or function-like macros. Therefore, if it is necessary to move to another processor, it is necessary only to adjust the contents of these functions to make the program usable. The two delay functions, on the other hand, are a mass collection of processor-specific commands that would have no meaning if the processor were to be changed. It is a good idea when writing code for embedded microcontrollers to collect together all of the processor specific code into functions by themselves and allow the general code to be as free from processor specifics as possible. We will see this idea in the following section where several general functions interface with several processor-specific functions.

Serial Input/Output

Serial input/output is an important capability of almost all microcontrollers that allows the programmer or user to communicate with the processor. Most of these functions are rather simple to implement. Some of the functions are quite processor-specific and others are more generally applicable to many microcontrollers. As mentioned above, it is important that you split these functions apart so that as much of the code that you generate as possible is portable. Let us look at the functions contained in serial1.c.

/*********************************************************** These routines implement a serial port on the MMC2001.

The UART1 is used and the default is set to 9600 b/s, 8 bit, no parity, and 1 stop bit. The baud rate is passed as an integer to the inituart() function. There are several i/o functions included. These are:

 

Serial Input/Output 405

 

 

inituart()

Initializes the uart to the passed baud

 

rate and sets 8 bit, no parity, and one

 

stop bit.

getchar()

tests for receive ready and echo

getch()

no echo

getce()

no ready test or echo

kbhit()

returns TRUE if a key has been hit,

 

FALSE otherwise

putchar()

Tests for transmit ready

puts()

sends out the indicated string

gets()

reads in a string into the buffer

getse()

reads in a string and echos

printd()

convert an unsigned long integer to an

 

ascii string representation of a decimal

 

integer and send it out the serial port

printx()

convert an unsigned long integer to an

 

ascii string representation of a

 

hexidecimal integer and send it out the

 

serial port

*********************************************************************/

#include “mmc2001.h” #include “uart.h” #include “serial.h”

#define U1SRint (*(volatile unsigned short *)(0x1000a086)) enum {TRDYint=8192};

#define U1RXint (*(volatile unsigned short *)(0x1000a000)) enum {CHARRDYint=32768};

#define CLOCK_FREQ 32000000L

/* the functions */

/* Initialize the UART1 */ void inituart(int baud)

{

/* Parity is disabled at reset by default */

/* stop bits is set to 1 by

default

*/

/* assumes a 32 mHz system clock */

 

U1CR1.UARTEN=ON;

/* Turn the uart on */

U1CR1.TXEN=ON;

/* enable

transmitter and receiver */

U1CR1.RXEN=ON;

 

 

 

U1CR2.IRTS=ON;

/* ignore

request

to send */

U1CR2.WS=ON;

/* 8 bit word */

 

/* Baud rate=CLOCK_FREQ/16/baud= */ U1BRGR.CD=(UHWORD)(CLOCK_FREQ/16/baud);

406 Chapter 8 MCORE, A RISC Machine

U1PCR.PC3=ON;

/* connect all i/o pins to the uart */

U1PCR.PC2=ON;

 

U1PCR.PC1=ON;

 

U1PCR.PC0=ON;

 

U1DDR.PDC1=ON;

/* make output pin for the uart */

}

/* probably not needed */

/* send a character out the serial port when it is ready */ static void put(BYTE x)

{

while((U1SRint & TRDYint)==0)

; /* wait until character is ready */ 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;

}

/* 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;

}

Serial Input/Output 407

/* return TRUE when a key has been hit and FALSE otherwise */ int kbhit(void)

{

return U1RX.CHARRDY;

}

/* convert a long to a series of ascii characters and send it out the serial port */

void printd(unsigned long x)

{

if(x/10)

printd(x/10);

putchar(x%10+’0');

}

/* same as above but output hexidecimal */ void printx(unsigned long x)

{

if(x/16)

printx(x/16);

putchar(x%16+((x%16>9)?(‘a’):(‘0’)));

}

Listing 8-5: Serial Input/Output Functions

At the beginning of the program, the usual headers are included. In this case, mmc2001.h is needed to identify the address of the UART, the header uart.h contains all of the definitions needed for the UART registers, and the serial.h header contains function prototypes for all of the serial input/output functions. These functions are for demonstration purposes only. They all work. I have used these functions at 115200 bit rates and had no problems. None of them have any built-in tests for errors on reception like parity tests, framing errors, or overrun errors. These tests need to be included and the errors handled properly if you want to have a production quality input/output library.

In the text that follows, each of the several small functions contained in the above listing will be described individually. The first function is the inituart() function.

#include “mmc2001.h” #include “uart.h” #include “serial.h”

408Chapter 8 MCORE, A RISC Machine

#define CLOCK_FREQ 32000000L

/* Initialize the UART1 */ void inituart(int baud)

{

/* Parity is disabled at reset by default */

/* stop bits is set to 1 by default */

/* assumes a 32 mHz system clock */

U1CR1.UARTEN=ON;

/* Turn the uart on */

U1CR1.TXEN=ON;

/* enable transmitter and receiver */

U1CR1.RXEN=ON;

 

U1CR2.IRTS=ON;

/* ignore request to send */

U1CR2.WS=ON;

/* 8 bit word */

/* Baud rate=CLOCK_FREQ/16/baud= */

U1BRGR.CD=(UHWORD)(CLOCK_FREQ/16/baud);

U1PCR.PC3=ON;

/* connect all i/o pins to the uarts */

U1PCR.PC2=ON;

 

U1PCR.PC1=ON;

 

U1PCR.PC0=ON;

 

U1DDR.PDC1=ON;

/* make output pin for the uart */

}

Listing 8-6: The inituart() Function

Each of these functions will have the same three header functions shown in Listing 8-6. In this case, the function requires the clock speed of the chip. This speed can be anything, and in our particular case, it is 32 MHz. The macro definition CLOCK_FREQ makes this value 32000000. If you ever change the clock speed of the computer, this value should be changed. The several instructions in the function are all commented with their operations. This function should be executed whenever the serial I/O system is to be used by a program. Pass the desired baud rate to the function as an integer. The integer size on the DIAB compiler used for this system is 32 bits; therefore, there will be no overflow problem for any of the usual choices for baud rates.

Following the inituart() function above, there is the series of five functions shown in Listing 8-7. These functions are all placed together because they each access on-board features of the MCORE chip. Almost always, it is best to collect operations that access onboard registers that control peripheral devices into individual functions that can be called from the applications portion of the program. This way, the detailed features of the chip are tightly contained in functions

Serial Input/Output 409

that can be easily changed if the chip itself is changed. The defines and enums set at the beginning of Listing 8-7 are from the top of the serial1.c program. The values established by these devices are used in the functions of Listing 8-7. These #defines and enums would not be necessary if the compiler worked as expected. Generally, if you want to wait until it is safe to send a character out the serial port, the code that you might use is

while(U1SR.TDRY ==0)

;/* wait here until TDRY is 1 */

This code will cause the computer to sit in a loop while the TDRY bit found in U1SR is 0. Unfortunately, the code created by the compiler failed to reload the value of the bit inside the loop, so the computer went into an infinite loop when it executed this code.

There are always ways around such problems and with C it is not usually necessary to jump to assembly language when such a problem is found. Notice the code below. Rather than the argument shown above, the argument was converted to a simple bit-wise AND, which did compile correctly. Remember, the addition of a #define or an enum in your code does not add or subtract from the executable code in your program.

The first two functions shown below are put() and getchar(). The function put() sends a character to the serial port number 1 on the board. It works with the function putchar(), which works exactly the same as the putchar() function that you are used to using in your programs. Here putchar() sends a character to the serial port rather than to the device stdout. The function getchar() receives a character from the serial port 1. In C, when a ‘\n’ character is sent to the output the program executes a carriage return and a line feed. Therefore, to make getchar() here the same, you will see in the next group of functions that the data received by getchar() is tested. If it is a ‘\n’ character, two characters, ‘\n’ and ‘\r’, are sent to the serial port. Otherwise, the character passed to the function is sent to the serial port. This operation mimics the operation of the normal getchar(), but it also requires a special function to output the character to the serial port. The function put(BYTE x) is that function. Since the function put() is never to be used outside of this file, it is designated as static.

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