Target Communications may be implemented on any target processor that has an asynchronous serial peripheral. This section describes how to link the serial peripheral to the Target Communications Library (TCL), expressed as a programming interface, so that the target may communicate with a DeviceMate unit.
6.1 Overview
The TCL is a framework of ANSI C code that implements the majority of the Target Communications (TC) Protocol. Since the serial peripheral interface is highly specific to each target architecture, a certain amount of code will need to be created to bind the serial peripheral to the TCL framework. This is called the adapter.
Figure 1. Block Diagram of Software Components for a DeviceMate System with a NonRabbit-Based Target 6.1.1 Steps for Porting to a Non Supported Target
When porting the TCL to a target that is not currently supported by Z-World, it may be necessary to modify or re-implement some of the source code that is provided. The following list summarizes the steps to take.
- Choose the supported source code base which is written for an architecture most similar to the intended target. For example, if targeting the MC68000 line of processors, then the Sparc version would be the best starting point.
- Determine the style of interaction between the asynchronous serial port and the software. This may be either a polling or an interrupt-driven style. Interrupt-driven is preferable, however polling may be preferable in the case that direct control of the serial port hardware is made difficult by the operating environment.
- Edit
tc_conf.h. This header file contains many macros that describe the target architecture.- Implement the serial port interface. For an interrupt-driven style, use
tc_386ex.c(from the 80386EX examples) as a starting point. For polling style, trytc_sysv.cfrom the Sparc or Linux examples.- Implement any other routines that are required by the definitions in
tc_conf.h. Target processors with a non-linear address space will require the most work.6.1.2 Sample Architectures
Examples of porting the TCL to several different architectures are provided with Dynamic C. The sample architectures are:
- Intel 80386EX: This processor is used in many embedded systems. The sample programs assume that an embedded DOS environment is available, however there is minimal dependence on DOS. This sample will also work on a desktop PC running DOS. This sample was tested using the Borland C++ 4.52 command-line compiler.
- Sparc: This processor is unlikely to be used in embedded systems, however it demonstrates use of the TCL on a big-endian RISC processor. This code could be used almost unaltered on Motorola-based processors. This sample was tested on a Sun Ultra Sparc, using the gcc compiler for Solaris 8.
- Linux on 80x86: Embedded Linux is becoming a popular choice for high-end embedded systems. Naturally, the sample will also work on a desktop Linux system with any reasonably recent kernel and gcc.
The Sparc and Linux samples assume a Unix-like operating system is available. Ideally, TCL would be implemented as a device driver, however the samples run as normal user-mode processes.
NOTE The samples will require you to provide the appropriate operating environment and compiler. 6.2 NonRabbit-Based Target Properties
A nonRabbit-based target is assumed to have the following properties:
- Sends and receives 8-bit characters with no parity and 1 stop bit.
- Can operate at one of the "standard" rates of 2400, 4800, 9600, 19200, 38400, 57600 or 115200 bits per second.
- Configuration of the serial device is under control of the application and/or OS; TCL does not provide any support for setting the communications parameters.
- Only requires connection of TxData, RxData and signal ground.
- Operates in "raw" mode i.e. the operating system, if any, does not perform any insertion or deletion of characters such as XON/XOFF software flow control.
- When a character is received, the application may be asynchronously notified via an interrupt service routine, or something which acts like or simulates an ISR. The ISR is considered to be part of the adapter.
- When a character has been transmitted, the application may be asynchronously notified that a new character may be transferred to the serial peripheral for transmission.
- The serial peripheral may or may not have any buffering of its own (e.g. a FIFO), or buffering may or may not be provided by the operating system (e.g. a small circular buffer).
It is assumed that there is no operating system as such, however the existence of an operating system may be advantageous if it can handle some of the details of serial communication.
6.3 TCL Interface
The interface works in two directions: the serial peripheral ISR calls TCL functions to process received data (or obtain data to transmit), and the TCL calls some functions provided by the adapter in order to notify the adapter of any significant change of state in the TCL. Since some of the routines may be invoked asynchronously, the adapter may also provide serialization routines.
6.3.1 TCL Data-Handling API
The data-handling API comprises functions that either generate data for transmission or process received data. These functions are called by the ISR part of the adapter. Since these functions are not called from TCL itself, it is possible for them to be in-lined directly in the ISR, saving one level of function call overhead. These functions perform a minimal amount of processing that is roughly constant for each transmitted or received byte. This makes direct call from an ISR practicable, since interrupt service latency is minimized.
These functions depend on state information saved in a single instance of a
TCStatestructure. This structure is defined intc.c. It is assumed that static variables can be accessed from the ISR.6.3.1.1 Data Types
The TCL relies heavily on data types with definite sizes. The following definitions are assumed:
typedef ??? uint8; // Unsigned 8-bit byte
typedef ??? uint16; // Unsigned 16-bit word
typedef ??? int16; // Signed 16-bit word
typedef ??? uint32; // Unsigned 32-bit longword
typedef ??? faraddr_t; // Far pointer to uint8??? denotes the appropriate basic type for the target architecture. The default definitions, in
tcconfig.h, are appropriate for the majority of architectures: ashortis assumed to be 16 bits, and alongis assumed to be 32 bits.6.3.1.1.1 Far Pointers
faraddr_tis defined for architectures which need to make a distinction between near and far pointers. Architectures with a single, linear, address space can define this astypedef uint8 * faraddr_t;Other architectures should define this using the appropriate qualifier, or as a "long" type. Some macros that define how to access memory using far pointers must be defined--please see Configuring the TCL Framework.
It is assumed that if a
faraddr_tis incremented (i.e. add 1) then the next byte of data space storage, considered as a linear array of bytes, will thereby be addressed. This implicitly limits TCL to run on byte-addressable architectures. This assumption holds true iffaraddr_tis defined as either auint8*or as an integer type.6.3.1.2 Received Data Handler
Each character received by the serial peripheral is passed to the received data handler. This may be performed on a character-by-character basis, or a sequence of more than one character at a time. Single character processing may be used if the peripheral does not buffer more than one character, and it is desired to process each character as it arrives. Peripherals that support a FIFO buffer, or operating systems which provide a small amount of buffering, may pass multiple characters in one call. This may improve efficiency.
Since the receive handler may be called from an ISR, it is important that the data be handled as quickly as possible. If necessary, the receive data handler in the TCL may be recoded in the target CPU's assembler language.
The prototype for the receive data handler is:
void _tc_rxdata(uint8 * data, uint16 len)The ISR calls this routine whenever a new character arrives. The address of the character is provided in
data, and the number of characters at that address inlen.lenwill be 1 for unbuffered (character-by-character) processing._tc_rxdata()must be called exactly once for any and all received characters.6.3.1.3 Transmit Data Handlers
Two functions are provided for the purpose of obtaining the next character to transmit. The first function is simpler to use since it performs all necessary processing, but the second function may allow greater efficiency. If the second function is used, then the first function may be deleted from the TCL code base since it will not otherwise be used.
The first function's prototype is:
uint16 _tc_txdata(uint8 * data, uint16 max_len)This function is called whenever the serial peripheral is able to accept the next character (or several characters) for transmission. This is typically performed from the "transmit buffer ready" ISR. The ISR provides a pointer to a memory area to store the data, and specifies the length of that area in
max_len. The specified length must be at least 1. The return value is the number of characters that were actually stored in the given data area. This may be less thanmax_len. Specifically, zero may be returned when there is nothing to transmit. In this case, the ISR should let the serial peripheral become idle i.e. not transmit any further data. If a non-zero length is returned, then the ISR must queue the returned character(s) for transmission.The second function returns a complete Target Communications packet for transmission. This is more complex to use, since a data structure is returned which must be scanned to determine where and how much data is to be transmitted. In addition, the caller is responsible for inserting HDLC frame start, escape sequences and a final checksum.
The second function's prototype is
_TCGatherSeg * _tc_txpacket(void)The definition of the returned structure is
typedef struct {
uint16 len;
faraddr_t addr;
} _TCGatherSeg;The return value from
_tc_txpacket()actually points to an array of the above structures. The last element in the array is determined by examining thelenfield: this is zero for the terminating entry. The terminating entry does not actually specify any data to transmit. The adapter must scan the array of_TCGatherSegstructures, transmitting each segment in sequence.addris the far address of the data to transmit, andlenis the length (in characters) of the data starting at that address. When the data from one segment is completely transmitted, the next array element is examined and its data transmitted. The process halts when the element contains a zerolenfield. At this point, the next packet may be obtained by another call to_tc_txpacket().The last segment will have a length of 2 and its data will be initialized to zeros. This is the final checksum for the entire frame. It must be computed by the caller based on the checksum of all data previously transmitted in the packet, not counting the frame start character or any escaping transformations (described below). When all segments except for the last (checksum) have been transmitted, then the checksum is sent with appropriate HDLC escapes but without further updating of the checksum itself. The checksum algorithm is described below.
If
NULLis returned then there is no data to transmit. The serial peripheral transmitter should be allowed to become idle in this case.The data locations returned are guaranteed to be stable until the next call to this function. Calling the function again implicitly releases the old data buffers to be re-used in the new return. This implies that the adapter must not try to "remember" to position of any data from a previous call to this function, since that data may be overwritten by the current call.
The data returned is raw data. The adapter must insert a frame start character before transmitting data from the first returned segment. The frame start character is 0x7E. Each data byte must be examined before transmission. If the data is either 0x7E or 0x7D, then two characters must be transmitted:
- 0x7E must be transmitted as 0x7D 0x5E
- 0x7D must be transmitted as 0x7D 0x5D
- All other characters are transmitted without change.
This is standard HDLC frame start and escape sequencing. Note that
_tc_txdata()already performs all HDLC processing; only this function requires it to be performed by the adapter.If the adapter makes calls to this function, it must not make calls to
_tc_txdata(), and vice-versa.6.3.1.4 Checksum Algorithm
The checksum used by the TCL is a 16-bit Fletcher checksum. This has the advantage of being computable using 8-bit arithmetic on machines that have an instruction to add the carry flag to the accumulator. The following C code shows how the checksum is updated given the next byte in the stream,
c.aandb, the 1st and 2nd bytes of the checksum, are initialized to zero.uint8 c; // next character
uint16 a; // 1st byte of checksum (in LSB)
uint16 b; // 2nd byte of checksum (in LSB)
a += c;
if (a > 255) a -= 255;
b += a;
if (b > 255) b -= 255;Checksums are used to protect the TC header, as well as the entire TC packet. The TC header checksum is automatically computed.
6.3.2 Adapter Notification API
TCL notifies the adapter of significant events by calling functions which must be implemented by the adapter.
6.3.2.1 Transmission Start
Whenever a new packet is available for transmission, the transmission start function is called:
void _tc_start(void)This is provided as a wake-up call to the serial peripheral transmitter, since it may have become idle since the last packet was transmitted. Typically, this function starts the ball rolling by determining the first new character to transmit, and using that character to prime the transmit shift register. Thereafter, the serial peripheral will generate transmit-ready interrupts in order to retrieve subsequent characters.
6.3.2.2 Event Signal
Whenever a new packet has completed arrival, or has completed transmission, this function is called by the TCL to notify the adapter. The adapter may then notify the application. This will allow the application to terminate a blocking (wait) state.
void _tc_signal(void)The occurrence of a call to this function means that the internal state of the TCL has changed in a way that will be meaningful to the application. If the application happens to be waiting for an event, this is an appropriate time for the application to make further calls to the TCL API.
This function is primarily intended for multitasking OS support, where it may indeed issue a "signal" or post to a semaphore. If there is no OS, then typically there will be a main loop which continually polls TCL for activity (via the
devmate_tick()function). In this case, there is no need for this function to do anything; however it should still be coded.6.3.3 Configuring the TCL Framework
TCL is provided as a portable framework of routines coded in ANSI C. Because of the wide range of target architectures, it will often be necessary to make some modifications to the TCL code. This process is made easier by defining a set of macros which tell TCL how to contend with the various quirks of the target architecture. Some miscellaneous support routines are also required, which are described in this section.
The main issues to contend with are
- Structure packing conventions.
- Byte ordering.
- Memory model (linear, segmented etc.).
- Interaction with operating system, if any.
- Resource serialization.
The macros are defined in
tcconfig.h. A description of each macro is included in the header file, with some additional information below.6.3.3.1 Byte Swapping and Packing
Since TC uses a little-endian format, big-endian target processors will need to perform byte swapping as well as packing. Little-endian targets will only need to perform packing. Targets which are both little-endian and have no alignment restrictions (such as the Rabbit processor and other Intel-derived 8-bit processors) do not need to define these functions.
If the preprocessor macro
TC_BIG_ENDIANis defined intc_config.h, then the appropriate byte-swapping code will be automatically included. A function defined intc.c,_tc_reorder(), is used to perform table-driven byte swapping for an entire data structure.If
TC_STRICT_ALIGNis defined, then TCL assumes that the compiler may introduce padding into structure definitions, in order to align elements on the most efficient boundary for memory access purposes. In this case, TCL automatically includes pack/unpack code to convert from internal structure format into transmission format (which is fully packed).If, in addition to
TC_STRICT_ALIGN, the macroTC_BUS_ALIGNis defined, then the architecture is assumed to require that multi-byte elements (uint16, uint32 etc.) be stored to and loaded from addresses which are a multiple of their size, as opposed to architectures such as the 80x86 which do not strictly require address alignment. (Address alignment is usually more efficient, which is why compilers add padding to structures; however, some architectures actually require alignment.)6.3.3.2 Memory Model
tcconfig.hcontains some configuration items to specify the memory model. TCL assumes that there are two types of pointers: near and far. This is the case on the Rabbit processor, as well as many other architectures with a segmented memory model. TCL can be configured to ignore this distinction, if the memory address space is linear and all pointers are the same size. Some architectures' C compiler can automatically model a limited linear address space, even on a segmented architecture. This is the case on the 80x86 processor using the smaller memory models, where the compiler simply makes all pointers near.Since TCL is a frugal resource consumer, it is usually sufficient to use a small memory model. Otherwise, if there is a distinction between near and far pointers, then TCL uses far memory to hold data buffers, and near memory for everything else.
If
TC_LINEAR_MEMORYis defined, then it is assumed that the address space is linear. Many of the internal conversion calls are replaced with in-line code or a call to thememcpy()function.If
TC_LINEAR_MEMORYis not defined, then a number of routines must be defined for dealing with data movement between near and far locations. All far pointers are referred to by thefaraddr_ttypedef, which is basically a far pointer touint8(an unsigned char).6.3.3.2.1 Adapter Routines
The routines which must be implemented by the adapter are listed below. The default implementation is used if
TC_LINEAR_MEMORYis defined.faraddr_t xalloc(long sz)
- Allocate
szbytes of storage, returning the base address (orFARADDR_NULLif error). Default implementation is(uint8 *)malloc(sz).faraddr_t paddr(void * virt)
- Convert the given near address to a far address. TCL assumes that this is always possible. Default implementation is a pointer cast (i.e. no code generated).
void xmem2root(void *dest, faraddr_t src, unsigned len)
- Copy memory from a far to a near address. Default implementation uses
memcpy().void root2xmem(faraddr_t dest, void *src, unsigned len)
- Copy memory from a near to a far address. Default implementation uses
memcpy().void xmem2xmem(faraddr_t dest, faraddr_t src, unsigned len)
- Copy memory from a far to a different far address. Default implementation uses
memcpy().void _tc_setnext(faraddr_t buf, faraddr_t next)
- Assuming
bufpoints to afaraddr_t(not auint8), set the pointer at*bufto next. Default implementation is a macro to the effect of*(faraddr_t *)buf = next.faraddr_t _tc_getnext(faraddr_t buf)
- Assuming
bufpoints to afaraddr_t(not auint8), retrieve the pointer at*buf. Default implementation is a macro to the effect of*(faraddr_t *)buf.uint8 _tc_getbyte(faraddr_t buf)
- Return the
uint8(unsigned char) pointed to bybuf. Default implementation is a direct dereference ofbuf.6.3.3.3 Interaction with Operating System
TCL assumes that very minimal facilities are available from an operating system. The only interaction is between TCL and the serial peripheral, the application, and a simple timer. Only the timer is described here. The other interactions are described by the rest of this chapter.
TCL assumes that a simple real-time timer is available in the form of a function that returns the number of milliseconds since some arbitrary epoch. This function should take no parameters and return a uint16 value which is a snapshot of the current millisecond timer.
uint16 _tc_getMilliseconds(void)If the OS maintains a more precise timer, or a timer with greater range than 65536 milliseconds, then the value returned should reflect the millisecond bit in the LSB. For example, if a hypothetical OS maintained a 32-bit counter (
OS_TIMER) which incremented every microsecond, then an appropriate function definition would beuint16 _tc_getMilliseconds(void)
{
return (uint16)(OS_TIMER >> 10);
}which is shifting by 10 to divide by approximately 1000. The absolute accuracy of the timer does not need to be any more precise than about +/-20%, and the resolution does not need to be any better than about 50ms. For example, if the counter increments by 50 every 50ms, then this is sufficient resolution.
The counter should wrap around modulo 65536.
Another simple real-time timer is available, also in the form of a function, this one returning the number of seconds (as a long) since some arbitrary epoch.
uint32 _tc_getSeconds(void)If this is not available, some of the demos may not link properly, however the seconds timer is not required by TCL itself.
6.3.3.4 Serialization
TCL uses buffer queues to perform all resource serialization. Two queue manipulation functions must be defined. If the preprocessor symbol
TC_CALLED_FROM_ISRis defined, then these functions must be implemented by the adapter. Otherwise, it is assumed that TCL is single-threaded and there is no serialization requirement.TC_CALLED_FROM_ISRindicates that another execution thread (typically a serial peripheral ISR) calls TCL routines. In this case, it is important to define the queue management routines so that they operate atomically with respect to the ISR and the main application.The two functions
_tc_queue_buffer()and_tc_get_buffer()may be coded simply to wrap interrupt-disabling code around calls to the non-protected queue management functions. The non-protected functions are defined with the same prototype and name, except that an additional underscore precedes the name. For example, a protected_tc_get_buffer()function may be coded as:
where
enable()anddisable()are architecture-specific functions. Note that the enabling and disabling of interrupts should nest correctly. If desired, one level of function calls may be eliminated by modifying__tc_get_buffer()and__tc_queue_buffer()directly, andnotdefiningTC_CALLED_FROM_ISR.6.4 Multitasking Environment
Subsystems must be safe in a preemptive multitasking environment. The implementation of binary semaphores that map to µC/OS-II functions can be ported for use with some other preemptive multitasking environment. The following macros must be redefined by the user.
6.4.1 Locking Macros
The following macros are in
dm_app.lib:TC_LOCKING
Locking is enabled if this macro is defined in the subsystem code. Locking is automatically defined when µC/OS-II is being used.
TC_LOCKTYPE
This macro specifies the data type of the lock. If it is not defined in the subsystem and µC/OS-II is being used,
TC_LOCKTYPEwill be defined asOS_EVENT*: a pointer able to access anOS_EVENTdata structure that will identify a binary semaphore.TC_CREATELOCK()
This macro takes no parameters. It returns a lock handle of type
TC_LOCKTYPE. For µC/OS-II, this will use the functionOSSemCreate(), with 1 as the parameter (the initial count of the semaphore).TC_LOCK(lock)
This macro takes a lock handle of type
TC_LOCKTYPEas a parameter. It will block until the given semaphore can be acquired (unless locking is not being used, in which case it will do nothing). Under µC/OS-II, this will wrap the functionOSSemPend(), with the time-out as 0 (infinity) and the error status being read into a dummy global variable.By default this macro will expand to nothing.
TC_UNLOCK(lock)
This macro takes a lock handle of type
TC_LOCKTYPEas a parameter. It will release the given semaphore (unless locking is not being used, in which case it will do nothing). Under µC/OS-II, this will wrap the functionOSSemPost().By default this macro will expand to nothing.
6.4.2 Critical Sections
A critical section of code is a part of the program that accesses shared data.
The critical section macros are:
TC_ENTER_CRITICAL()andTC_EXIT_CRITICAL(). They take no parameters. Under µC/OS-II they map toOS_ENTER_CRITICAL()andOS_EXIT_CRITICAL().They should be used with caution as they disable interrupts.
| Z-World http://www.zworld.com Voice: (530) 757-3737 FAX: (530) 757-3792 sales@zworld.com |