PREV NEXT INDEX



Chapter 6. Porting Guidelines for NonRabbit-Based Targets

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.

6.1.2 Sample Architectures

Examples of porting the TCL to several different architectures are provided with Dynamic C. The sample architectures are:

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:

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 TCState structure. This structure is defined in tc.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: a short is assumed to be 16 bits, and a long is assumed to be 32 bits.

6.3.1.1.1 Far Pointers

faraddr_t is defined for architectures which need to make a distinction between near and far pointers. Architectures with a single, linear, address space can define this as


typedef 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_t is 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 if faraddr_t is defined as either a uint8* 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 in len. len will 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 than max_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 the len field: 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 _TCGatherSeg structures, transmitting each segment in sequence. addr is the far address of the data to transmit, and len is 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 zero len field. 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 NULL is 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:

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. a and b, 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

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_ENDIAN is defined in tc_config.h, then the appropriate byte-swapping code will be automatically included. A function defined in tc.c, _tc_reorder(), is used to perform table-driven byte swapping for an entire data structure.

If TC_STRICT_ALIGN is 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 macro TC_BUS_ALIGN is 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.h contains 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_MEMORY is 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 the memcpy() function.

If TC_LINEAR_MEMORY is 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 the faraddr_t typedef, which is basically a far pointer to uint8 (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_MEMORY is defined.


faraddr_t xalloc(long sz)
Allocate sz bytes of storage, returning the base address (or FARADDR_NULL if 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 buf points to a faraddr_t (not a uint8), set the pointer at *buf to next. Default implementation is a macro to the effect of *(faraddr_t *)buf = next.

faraddr_t _tc_getnext(faraddr_t buf)
Assuming buf points to a faraddr_t (not a uint8), 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 by buf. Default implementation is a direct dereference of buf.

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 be


uint16 _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_ISR is 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_ISR indicates 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:


faraddr_t _tc_get_buffer(faraddr_t *chain)
{
faraddr_t rc;
int was_enabled = currently_enabled();

if (was_enabled)
disable();                 // Disable interrupts
rc = __tc_get_buffer(chain);  // Call non-protected version
if (was_enabled)
enable();                  // Enable if previously enabled
return rc;
}

where enable() and disable() 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, and not defining TC_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_LOCKTYPE will be defined as OS_EVENT*: a pointer able to access an OS_EVENT data 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 function OSSemCreate(), with 1 as the parameter (the initial count of the semaphore).

TC_LOCK(lock)

This macro takes a lock handle of type TC_LOCKTYPE as 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 function OSSemPend(), 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_LOCKTYPE as 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 function OSSemPost().

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() and TC_EXIT_CRITICAL(). They take no parameters. Under µC/OS-II they map to OS_ENTER_CRITICAL() and OS_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
PREV NEXT INDEX