chapter4.htm

来自「嵌入式软件开发.rar」· HTM 代码 · 共 957 行 · 第 1/4 页

HTM
957
字号
<p>	A serial communications driver requires that the calling program send quite a bit 
of data to the driver when setting up the communications system and doing the serial 
transfer.  There are different ways that these parameters can be delivered to the driver.  
The first is to have several argument values for the functions that will be needed.  A 
second is to build a table that and communicate a pointer to the table as the function 
parameter.  The table contains all of the important parameters needed to establish the 
communications or complete the communications which ever is needed.  We will use 
both approachs here. 
<p>	The serial communications device on the M68HC16Y1 is called the 
MultiChannel Communications Interface, <a href="appendxb.htm#mcci_h">MCCI</a>.
  The MCCI contains two asynchronous 
serial communications interfaces, SCI, along with a synchronous serial peripheral 
communications interface, SPI.  We will work with the asynchronous system in this 
chapter.  Our software must address both of the SCI ports, but the SPI will be treated 
elsewhere.  Since we are using a class/object approach to the interface design, is it 
desirable to design the class so that several different instances of the serial interface can 
be established at the same time?  Of course, we can establish more serial 
communications driver objects than there are ports, but no more than two serial ports can 
be worked at the same time.  We will now look at building a serial communications 
driver for a single SCI port and plan to extend the code to the second port later. 
<p>	The serial i/o header file shown below contains components similar to all of the 
classes that we have developed so far.  There are also some serial port control business 
that should be included in the header file to control any serial port.  These additional 
peripherial specific entries in the header files will become more common now that the 
code being written is specifically addressing peripheral components.  The first include 
file is the error header file, and the serial driver will be a descendent class of the error 
handler.  Next, you will observe a #define statements that defines an important value for 
the program.  The EOM, end of message, value is zero.  
<p>	The enum{} construct has not been used much until now.  This useful scheme 
assigns numerical values to the various arguments starting at zero and incrementing the 
value for each additional argument.  They are very useful for assigning values to 
mnemonics where the value is not important, just having a unique value for each name is 
important.  There are two enum{} statements in this header.  Two commands will be 
used by the serial i/o programs, INPUT and OUTPUT.  The enum io_command will 
define these two values.  Finally, there are several possible errors that can occur in the  
serial i/o programs.  These errors are given values in the enum io_errors.
<p>	Whenever a serial i/o takes place, the  program must receive several parameters 
and also, the program can receive several parameters from the driver program.  As 
mentioned above, these parameters are all collected into a single structure called an 
input/output control block.  This structure is declared and type defined as a type 
io_command_block.  The first three parameters, io_command, buffer, and buffer_length 
are each parameters sent to the serial driver by the calling program.  The last three 
parameters, characters_transferred, call_complete and error, are parameters returned to 
the calling program by the serial driver.  The meanings of these various parameters are 
straight forward.
<p>	In the implementation file for the serial i/o shown below, a header file iodefs.h is 
included. A listing if this file is
<pre><code>
	#define MCCI_IARB       3
	#define MCCI_LEVEL      3
	#define MCCI_INTV       0x40    /* MCCI vector at 0x40 */ 
</code></pre>
There are three constants are values used in the set-up of the MCCI on the 
microcontroller.  It may seem excessive to have a series of definition statements for such 
parameters, but it is extremely important for good programming practice to avoid the use 
of "magic" values.                                           
<pre><code>
	#ifndef SERIO_H
	#define SERIO_H

	#include "error.h"
	#include "iodefs.h"
	#include "defines.h"

	#define EOM             0      /* End Of Message */

	#define SERIO_METHODS \
			void (*do_rec)(void);  \
			void (*do_trans)(void);

	typedef struct
	{
		SERIO_METHODS
	} Serio_Methods;

	enum io_commands{INPUT,OUTPUT };
	              
	enum io_errors{NO_ERROR, 
	               BUFFER_OVERFLOW,
	               COMMAND_ERROR};

	/* i/o command block */               

	typedef struct /* See enumerates above for commands and error returns */
	{
	    int io_command;                 /* input  parameters */
	    BYTE *buffer;         
	    int buffer_length;
	    int characters_transferred;     /* output  parameters */
	    Boolean call_complete;
	    int error;
	} io_command_block;

	#define  SERIAL_IO	\
	    BYTE (*i_o)(struct serio *,io_command_block *);
	   
	typedef struct serio
	{
		ERROR_HANDLER
		SERIAL_IO
	} Serial_io;

	#define serial_io(a,b) (*a->i_o)(a,b)

	/* Function prototypes */
	Serial_io *serial_io_(int baud_rate,int vector,int iarb,int int_level);
	void serial_io__(Serial_io *);
	static BYTE getch(void);
	static void putch(BYTE ) ;
	static void do_receive(void);
	static void do_transmit(void);

	#endif
</code></pre><p>
<pre><h4>
	Listing 4.3 Serial Input/Output Class Definition File
</h4></pre>
<p>	Use will be made of virtual functions in this class.  Therefore, the first entries in 
the header file are the serio header file are those items necessary to create a virtual 
function table.  This table will have two entries.
<p>	The SERIAL_IO define statement contains a single pointer to a function that 
returns a type BYTE and takes two arguments.  The first argument is a pointer to the 
serial i/o object being activated, and the second argument is a pointer to the io command 
block that contains the needed information to process the i/o.  The type Serial_io is 
created as a combination of the SERIAL_IO statement and the ERROR_HANDLER 
statement found in the  error.h header.  
<p>	The constructor has four arguments.  These arguments are needed to set-up the 
serial port on the microcontroller.  Note that the program required to set up the serial port 
does not depend in any way on the parameters needed in the io control block.  Therefore, 
the io control block is not a part of the constructor.  The destructor function prototype is 
similar to all seen before.  There are several internal functions that are included in this 
class.  The functions getch() and putch() do exactly as you would expect.  These 
functions either get a character from or put a character to the serial port.  The functions 
do_receive() and do_transmit() are virtual funcitons that can be changed by inherited 
classes.  
<p>	Until now, there has been little reference to the microcontroller being used for 
these programs.  We must now consider the microcontroller in some detail.  The header 
files included in the serio.c file shown below include the header for the M68HC16Y1 
along with the header for the Multi Channel Communications Interface module found in 
the M68HC16Y1.  Listings of these two header files will be found in the Appendices, and 
they contain important definitions of values used for programming these parts.    
<p>	There are two internal functions needed for the serial port driver.  These functions 
are both declared as static and they are the functions getch() and putch().  getch() reads a 
single character from the input of the serial port, and putch() sends a single character out 
the serial port.  While there is no error checking within these two functions, it is here 
where overrun errors, parity errors, framing errors, and noise errors can be detected.   
These errors are all TYPE_2 errors, and the strategy for handling these errors should be 
included within these functions.  In this case, these errors were ignored.  Perhaps, in a 
real application, these error events should be considered as more important than they 
were here. 
<p>	The next function, i_o(), is the object method call that establishes an input or 
output.  This function receives an io_command_block and the identification of a specific 
serial input output object that has already been created.  Within this function, commands 
are evaluated, and the port is established as either an input or an output.  By the way, 
there is no reason that the program could not process both an input and an output at the 
same time.  Separate calls to i_o() with different io_commands will be needed to set-up 
simultaneous input and output transactions.  The main work that takes place inside of this 
function is controlled by a case statement that selects the proper code based on the 
io_command found in the block parameter.  
<p>	There are two static pointers to the type io_command_block.  These pointers are 
used to save the pointers to the two possible command blocks that are being used 
currently.  It is assumed that all i/o transactions are block oriented.  In other words, a 
block of data are transferred with a single transaction.  This block is passed to i_o() as a 
buffer along with the length of the message contained in the buffer.
<p>	In both cases, input or output, the output parameters that are contained in the 
io_command_block are given proper values.  The characters transferred is set to zero, the 
call complete flag is set FALSE and the error value contained within the command block 
is set to NO_ERROR.  These are each values that will be changed as the input or output 
proceeds.  These parameters in the command block are the three output parameters.  The 
remaining three parameters are input parameters that pass data into this function.  
<p>	The receiver and the transmitter are usually kept in the OFF condition and are 
turned ON only when they are needed.  Similarly, the receiver and transmitter interrupts 
are kept OFF until needed.  These flags are set in the microcontroller by the code in 
either the INPUT or OUTPUT  setup areas.  After these appropriate bits are set, the 
function  returns NO_ERROR to the calling program.
<p>	All of the setup code for either the INPUT or OUTPUT commands is directed by 
a case statement.  There is only two choices, INPUT or OUTPUT.  If another command 
is sent to the i_o() program, a COMMAND_ERROR is returned to the calling program.    
<pre><code>
	#include "serio.h"
	#include "hc16y1.h"   
	#include "mcci.h"

	/* ISR prototypes */
	@port void SCR_Isr(void) ;

	/* This function reads in a single character from the  SCI */

	static BYTE getch(void)
	{
	    while(SCSRA.RDRF==OFF) /* wait until data are available */
	        ;
	    return SCDRA.DATA;     /* and reset RDRF bit */
	}

	/* This function sends a single character to SCI */

	static void putch(BYTE x)
	{
	    while(SCSRA.TDRE==OFF)  /* wait until register is empty */
	        ;
	    SCDRA.DATA=x;           /* and send out the data */
	}				    /* which resets TDRE bit */	

	io_command_block *giob, *goob; /* used in ISR */

	static BYTE i_o(Serial_io * this, io_command_block *block)
	{
	    switch(block->io_command)
	    {
	        case INPUT :  giob=block;
	                      giob->characters_transferred=0; /* none sent yet */
	                      giob->call_complete=FALSE; /* reset when done */
	                      giob->error=NO_ERROR;
	                      SCCR1A.RE=ON;    /* enable  receiver*/
	                      SCCR1A.RIE=ON;   /* enable receiver interrupt */
	                      return NO_ERROR;
	                                                                 
	                     
	        case OUTPUT : goob=block;
	                      goob->characters_transferred=0; /* none sent now */
	                      goob->call_complete=FALSE; /* reset when done */
	                      goob->error=NO_ERROR;
	                      SCCR1A.TE=ON;   /* enable transmitter */
	                      SCCR1A.TIE=ON;  /* enable transmit interrupt */
	                      return NO_ERROR; 
	        
	        default : return COMMAND_ERROR;
	    }
	}
</code></pre>
<p>	The next code that we observe is the serial_io_() constructor.  This constructor 
must do several things that we have not seen before.  It is necessary to set-up many of the 
internal components within the controller when we need to use these modules.  There are 
four special considerations that must be set-up in this serial system.  If you examine the 
code that follows, you will find that there are four parameters,  int_level, iarb, vector and 
baud rate,  that are put into specific registers in the part.  The interrupt level is a number 
between one and seven.  The higher level interrupt will be processed before a lower level 
interrupt, and level seven interrupts are nonmaskable. The iarb is a number between 1 
and 15.  This number is used to resolve simultaneous interrupts that occur at the same 

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?