chapter4.htm

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

HTM
957
字号
<head>
<title>The Asynchronous Serial Port</title>
</head>
<body>
<h1>Chapter 4  The Asynchronous Serial Port</h1>
<p>	The main focus of this chapter is the asynchronous serial communications system 
found on the M68HC16Y1.  There is one point that should be considered outside of the 
serial services that can be placed here.  Access to and creation of all peripheral devices 
can cause errors.  We will need a clean error handling system to help recover from these 
errors.

<h3><a name="error_handler">Error Handler</a></h3>

<p>	In the earlier chapters, occasions where access to an error handler were pointed 
out, but the error handling routines were stubbed out when it came to functioning 
programs.  Here we are beginning to write code for real microcontrollers, and it is now 
time to consider the ramifications of a real error handler.  This handler will be used with 
the asynchronous serial communications systems here, and in fact, it is general enough 
that it can be used in support of any peripheral object devices developed in this text.  
<p>	There are two types of errors that we will consider.  The first type comes about 
when the system itself is in trouble, and the second type occurs when there is some type 
of peripheral failure.  For example, a NULL return from a malloc() transaction indicates 
that there is no memory left on the system heap when more memory is needed.  This type 
of error is quite serious in that the program cannot continue operating in its current state.  
Such an error is of the first type.  On any serial communications system, it is possible that 
the computer system will not get around to servicing some data until that data are no 
longer valid.  Serial input data can reside in a buffer only until the next character has 
arrived in the input shift register.  At that time, either the old data must be overwritten by 
the new character, or the next character arriving will destroy the contents of the shift 
register before it is transferred into the input buffer.  Either event causes the input data 
stream to have an overrun error, but neither event is catastrophic in regards to the ability 
of the system to continue its designated operation.  We shall call errors of the first type 
TYPE_1 errors, and non catastrophic errors will be called TYPE_2 errors.
<p>	An error handler will have to dodifferent things for these types of errors.  In the 
first case, something must be done that attempts to restore the integrity of the system.  In 
the second case, it is probably satisfactory to log the fact that an error occurred and 
proceed with the program.  Of course, in the event that there is a plan to correct for this 
type of error, the plan will be put into effect.  For example, a TYPE_2 error in a serial 
communications driver can be corrected.  A message could be sent that requests that the 
last block of data be resent in an attempt to correct the error. In the absence of a specific 
plan, we will assume that the program will merely logs the error and returns to its normal 
business.
<p>TYPE_1 errors, on the other hand, require some additional consideration.  Any 
TYPE_1 error indicates that there is a system failure, and normal processing cannot 
proceed.  In such an event, the response should probably similar to that that one would 
choose with a watchdog or computer operating properly--COP--time-out.   Most 
computer systems cannot distinguish between a power on reset and a COP reset.  We 
have an advantage here in that we can direct the code to wherever we please when the 
TYPE_1 error occurs and are not quite limited to the code contained in the reset 
initialization portion of the program.  
<p>	What should be done when a TYPE_1 error occurs?  One thing is clear, most 
memory operations should be initialized and the stack pointer reset to its initial value.  
Unfortunately, the error handler itself requires some global memory, and it would be 
counterproductive to initialize this memory in the event of a TYPE_1 error.  Part of this 
memory is an error log that should be avaliable for later diagnosis of the system 
performance, so this memory cannot be initialized by an error based restart.  Actually, we 
will see that this problem is simplified somewhat by putting the initialization of the error 
log into the constructor for the error handler and controlling the initialization by a flag 
that is reset during the power on reset.  This flag cannot be reset during an error based 
reset, but it must be reset during a power on reset.
<p>	How the error handler should be incorporated into the program is a serious 
question.  One of the popular approaches is to use a setjmp() and longjmp() function that 
is a part of the language.  This approach permits a transfer of control from anywhere in 
the program to a specified location.  Of course, the specified location in this case would 
be the error handler.  This approach is fine for the TYPE_1 errors, but there is no 
convenient mechanism for returning to the program that generated the error as is needed 
for a TYPE_2 error.  An alternate approach that is certainly in line with object oriented 
programming is to place the error handler into a base class like the class object in 
Chapter 3.  Then  new classes created for the program are each inherited from the base 
abstract class.  With this approach, each object will have its own access to the error 
handler and there will be no problems with longjmp() or other calls to global functions.  
With a TYPE_1 error, the error handler will transfer control of the program to a point in 
the initialization program that follows global memory initialization for the program but 
preceeds the initialization of the stack pointer.  Important external memory contents 
needed for the error logging will thus be saved, but the program will be restarted 
otherwise.  The TYPE_2 error will merely increment the appropriate entry in the error 
log and return to the program where the error occurred.
<p>	A header file for error is shown below.  The error handler will be a descendent of 
the class Object.  We will see interesting uses for this approach in later chapters.  The 
ERROR_HANDLER define statement contains no attributes, a reference to the #defined 
macro OBJECT found in the object.h header, and a single pointer to the error_hand() 
function.  A macro defines the function error_handler(a,b,c) which will be used to access 
this object from any program.  The first parameter, a, is a pointer to the object in which 
the error has occurred.  The parameter b is the error type, and the parameter c is a 
numeric identifier of the class that generated the error.  
<pre><code>		
	#ifndef ERROR_H
	#define ERROR_H

	#include "defines.h"
	#include "object.h"

	enum {TYPE_1,TYPE_2};

	#define ERROR_HANDLER  \
		OBJECT             \                    
		void (*error_hand)(void*,int,Error_source);

	typedef struct err
	{
		ERROR_HANDLER
	} Error_handler;

	#define error_handler(a,b,c) (*a->error_hand)(a,b,c)

	Error_handler *error_(void);
	void error__(Error_handler *);

	#endif
</code></pre><p>
<h4><pre>
	Listing 4.1 Error Handler Class Definition File
</pre></h4>
	The error manager implementation file is shown below.   For the moment, it is 
assumed that the maximum number of different error sources is 10.  There is a small 
function named restart() contained within this class, and it is to be private to the error 
manager, so it is declared to be static.  This little function is a jump to the location error1 
declared as an external destination.  Such a program must be written in assembly 
language, and it requires but a single instruction and the external reference to the address 
as is seen in the function.  
<p>	The error handler function examines type.  If the error is TYPE_1, the appropriate 
error log array entry is incremented and the program is restarted.  If it is TYPE_2, the 
error log entry is incremented, and the program control is returned to the program that 
called the error handler.  Otherwise, if type is neither TYPE_1 nor TYPE_2, the error 
handler was called by error and the program is restarted. 
<pre><code>
	#include "error.h"

	#define ERROR_SOURCES 10 /* max number of error sources */

	static void restart(void);
	static int *error_log;

	static void error_hand(Error_handler *error, 
				     int type,  		
				     Error_source error_source)
	{
		if(type==TYPE_1)
	    {
			++error_log[error_source]; 
			restart();
	    }			     
	    else if(type==TYPE_2)               
			++error_log[error_source]; 
	    else
			restart();		       
						               
	}

	Error_handler *error_(void)
	{
		Error_handler *this;
		Object *temp=object_();

		this=(Error_handler *)malloc(sizeof(Error_handler));
		if(!error_log)
			error_log=(int*)calloc(ERROR_SOURCES,sizeof(int));
		memmove(this, temp, sizeof(Object));
		this->error_hand=error_hand;
		object__(temp);
		return this;
	}

	void error__(Error_handler *this)
	{
		free(this);
	}

	static void restart(void)
	{
	       _asm("jmp _error1\n");		       
	       _asm(".external _error1\n");
	       _asm(".external ._error1\n");             
	}
</code></pre><p>

<h4><pre>
	Listing 4.2  Error Handler Class Implementation File
</h4></pre><p>
The error handler class is made a descendent of the class Object by inclusion of the a 
copy of an Object in the class Error. 
<p>	In the above files, there is a frequent use of the type void where one might expect 
the type Error_handler.  The void type in these cases define a generic pointer.  The 
parameter in each case must be a pointer, but we will later use the type of inherited 
objects to access the error manager.  In these cases, a specific type of pointer in the basic 
error manager code would cause the compiler to report an error whenever the inherited 
object type would attempt to access functions in the base type object.  This little 
subterfuge avoids problems where types are frequently miscast with the coding approach 
we are using here.
<p>	As many error handlers as are needed can be instantiated in a program.  
Regardless of the number of error handlers, there can be only one error log.  The error 
handler is created with a malloc() call to allocate space for the object.  When the program 
is started, the variable error_log will be contained in a memory area that is initialized to 
zero. Therefore, whenever the error_() routine is executed the first time after program 
initialization, error_log will be zero and the calloc() routine will be called.  This function 
allocates a memory space large enough to hold an array of ERROR_SOURCES unsigned 
integer variables. Because the variable error_log is declared to be static, it will work 
exactly the same as does a static variable found in C++.  There is only one copy of this 
variable created regardless of the number of error handlers instantiated.  This copy of the 
variable will be visible to all of the error handlers.  Therefore, there will be one copy of 
the array error_log, and regardless of the number of error handlers created, error_log is 
the record of transactions for all the error handlers.
<p>	The remainder of the constructor and the destructor function are straight forward.  
The function restart() contains three assembly lines.  The first is a jump instruction to the 
20 bit location named _error1 and the next two are external declarations of the 20 bit 
field _error1.  These values will be filled in by the linker at link time for the program. 

<h3><a name="development_environment">Development Environment</a></h3>

<p>	The programs that will be developed in the remainder of this text will all be  
compiled, debugged and tested.  All of the code will be compiled primarily for the 
M68HC16Y1 microcontroller.  The user's manual for this part can be obtained from 
Motorola.    This chip is based on the CPU16 core which is an expanded version of the 
MC68HC11.  The CPU16 is truly a 16 bit processor with twenty bit data and program 
address spaces.  The CPU16 works with the InterModual Bus structure that is also used 
with the M683xx family of components.  The peripheral devices placed on these two 
families of parts are identical.  Therefore, the interface approach to these peripherals is 
identical on these two families.
<p>	The <a href="appendxb.htm#hc16y1_h">MC68HC16Y1</a> has several built-in peripheral components.  These 
peripherals include:
<pre>
	Analog-to-Digital Converter
	Multichannel Communications Interface
	General Purpose Timer
	Timer Processor Unit
	Standby RAM
</pre>
We will be building  software components that access these peripherals as components.  
All of the code developed here has been compiled using the Cosmic HC16 compiler.  
Access to this compiler is described in Appendix A.  There you will find some command 
files that  aid in compilation of your programs with this compiler.  
<p>	The  code has been debugged using the M68HC61Y1EVS evaluation system.  
The debug code that  was used is supplied by P&E .  This program is available from the  
Motorola Freeware Bulletin Board 512 891 3733.  Also, the program ICD16 will work.  
All of the debugging was done in the Background Debug Mode. This mode allows 
noninvasive debugging of a program.  The debugger does not reside in the 
microcontroller memory so the only time that the debugger is active is when the 
computer enters the BDM.  Otherwise, the microcontroller runs at full speed without any 
notice of the presence of a debugging environment.  
<h3><a name="a_serial_driver">A Serial Driver</a></h3>

⌨️ 快捷键说明

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