Serial Driver Interface
An OKE system communicates over a local half (RS-485) or full duplex (RS-232 or USB 2.0 CDC) serial communication channel to the CWM.
OKE Linux udev Rule for Mapping Dynamic USB Device Name to Well-known Device Name
When using the USB host interface to communicate to a real CWM on a Linux platform, add a udev rule to map the dynamically created serial CDC device class communication port to a static device name. Use the static device name when opening the communication port in the OKE.
As root user from a shell interface on the Linux target platform, create a new file /etc/udev/rules.d/99-tty.rules containing the following line:
KERNEL=="ttyACM[0-9]*",ATTRS{product}=="CDC RS-232 Emulation Demo",SYMLINK="ttyCWM", GROUP="dialout", MODE="0660"
Reboot the HLUI platform. The OKE will use /dev/ttyCWM to communicate to the CWM regardless of the actual device name that is created when the CWM attaches.
OKE Discovery
The CWM has several serial interfaces, a TTL UART, RS232 UART, and a RS485 UART. It also has a USB CDC serial interface that can operate in either device or host mode.
The OKE only implements one of these serial interfaces for CWM communication.
When the CWM has not yet discovered an attached OKE the CWM will perform a discovery sequence where it will send requests at different baud rates over its various serial interfaces.
When the OKE responds to the ping request the CWM remembers the serial interface configuration in non-volatile memory. The next time the CWM boots it will restore the serial configuration from non-volatile memory and start communication with the OKE without performing discovery again.
The CWM can be factory reset to force discovery again.
Baud Rates
It is recommended that OKEs use 115200 as the baud rate to communicate to the CWM. An OKE may optionally choose to implement the baud rate as 9600, 19200, 38400, 57600, or 230400. Faster baud rates may speed up large file transfers but will not have a noticeable performance impact for typical telemetry communications. For the 230400 baud rate communication strict cable requirements, such as maximum length, load capacitance, and shielding may apply to reliably achieve this speed under expected operating conditions. Once selected the OKE should always use the same baud rate to communicate with the CWM.
The serial interface always uses no parity bits, 8 data bits, and 1 stop bit; N81.
The USB CDC interface ignores the pseudo baud rate configuration settings.
Software Buffering
Regardless of the physical layer, the CWL requires both receive and transmit software buffering.
Receive software buffering prevents the CWL task from blocking to receive data and decouples the CWL task from needing to receive and process data at line rate to avoid hardware buffer overflows.
Transmit software buffering prevents the CWL task from blocking to transmit data and decouples the CWL task from needing to feed the transmit hardware buffer so that line rate transmission speed can be maintained.
Most platforms will likely already implement both receive and transmit software buffering.
Due to the internal architecture of the CWL's serial network and data link layers only 255 bytes of receive and 255 bytes of transmit software buffer is required. This amount of software buffering will consistently and properly service all normal operating conditions.
The Port Layer has two functions that interface to the receive and transmit software buffers respectively. Both functions must be synchronized to protect shared data structures from simultaneous access. For example, a serial interrupt handler must not interfere with the CWL task while it is manipulating the software buffers.
bool CWPortGetRxQueueByte(uint8_t *rxByte);
The CWPortGetRxQueueByte() function takes a pointer to an uint8_t. If it returns false, then no data was available in the receive software buffer. If it returns true, then the data pointed to by rxByte will be set to the newly received byte. This function must never block waiting to receive data bytes.
void CWPortSetTxQueueByte(uint8_t txByte);
The CWPortSetTxQueueByte() function takes a data byte to transmit. The data byte is put onto the transmit software buffer. If the data byte is not accepted onto the software transmit buffer, then the function should assert. The function must never block waiting for the transmit to complete.
There are a multitude of different ways that synchronized software buffering and the CWPortGetRxQueueByte() and CWPortSetTxQueueByte()functions may be properly implemented. However, for each proper way there are far more improper ones.
For reference, the next section shows example code of a successful implementation.
MSP430 Pseudocode Example
This section has pseudocode taken from a working MSP430-based embedded platform that properly implements synchronized receive and transmit software buffering. Hardware UART peripheral registers will vary in function and behavior between microprocessor platforms.
When an MSP430 interrupt is taken global interrupts are disabled. This is why only the CWPortGetRxQueueByte() and CWPortSetTxQueueByte() functions explicitly use critical sections (that disable and enable global interrupts) whereas the interrupt handler does not. The critical sections are what synchronize the serial interrupt and CWL execution contexts so that the software buffering structures do not get corrupted.
This example also takes advantage of 8-bit integer overflow to wrap the head and tail pointers automatically. This both speeds up and simplifies the interrupt handler.
On the receive side the interrupt handler will be invoked when a character is received. The interrupt handler simply puts the new character onto the receive FIFO. From the CWL task context (not shown) the Serial Data Link Layer will attempt to read a byte from the receive FIFO by periodically calling CWPortGetRxQueueByte(). Before checking the receive FIFO status interrupts are disabled. This prevents the interrupt handler from interfering with CWPortGetRxQueueByte() checking and pulling a data byte off the receive FIFO. Before returning CWPortGetRxQueueByte() reenables interrupts. If a new byte was read the Serial Data Link Layer will process it, meanwhile the interrupt is now free to continue putting data into the receive FIFO.
On the transmit side the Serial Data Link Layer will call CWPortSetTxQueueByte() to put a byte of data into the transmit FIFO. Before manipulating the transmit FIFO CWPortSetTxQueueByte() disables interrupts. This prevents the interrupt handler from interfering with CWPortSetTxQueueByte() while it puts a byte on the transmit FIFO. After putting the data byte on the transmit FIFO the transmit interrupt will be setup and kicked off if it is not already running. A transmit interrupt will be generated as the hardware transmit buffer empties and will continue until the software transmit buffer is empty at which point the transmit interrupt is disabled again.
#define UART_FIFO_SIZE 256
typedef struct UART_FIFO
{
uint8_t buf[UART_FIFO_SIZE] ;
uint8_t head ;
uint8_t tail ;
} UART_FIFO_t ;
volatile UART_FIFO_t UART_Tx ;
volatile UART_FIFO_t UART_Rx ;
#pragma vector=USCI_A1_VECTOR
__interrupt void UartIsr(void)
{
uint8_t ifg = UART_PORT(IFG); /* Grab the interrupt flags */
if (ifg & UCRXIFG)
{ /* Handle RX */
/* Put newly received byte onto FIFO */
UART_Rx.buf[UART_Rx.head] = UART_PORT(RXBUF) ;
UART_Rx.head++;
CW_ASSERT(UART_Rx.head != UART_Rx.tail);
}
if (ifg & UCTXIFG)
{ /* Handle TX */
/* Disable TX interrupts when buffer is empty */
if (UART_Tx.head == UART_Tx.tail)
{
/* Last byte is done, disable TX interrupts */
UART_PORT(IE) &= ~UCTXIE ;
}
else
{
/* Transmit next byte from FIFO */
UART_PORT(TXBUF) = UART_Tx.buf[UART_Tx.tail] ;
UART_Tx.tail++ ;
}
}
}
bool CWPortGetRxQueueByte(uint8_t *rxByte)
{
bool empty;
CW_REQUIRE(rxByte != NULL);
CS_ENTER(); /* Turns off global interrupts */
empty = UART_Rx.head == UART_Rx.tail;
if (!empty)
{
*rxByte = UART_Rx.buf[UART_Rx.tail];
UART_Rx.tail++;
}
CS_EXIT(); /* Turns on global interrupts */
return !empty;
}
void CWPortSetTxQueueByte(uint8_t txByte)
{
CS_ENTER(); /* Turn off global interrupts */
UART_Tx.buf[UART_Tx.head] = txByte;
UART_Tx.head++;
CW_ASSERT(UART_Tx.head != UART_Tx.tail);
/* If TX interrupts not enabled then trigger transmission */
if ((UART_PORT(IE) & UCTXIE) != UCTXIE)
{
UART_PORT(IE) |= UCTXIE ;
UART_PORT(TXBUF) = UART_Tx.buf[UART_Tx.tail] ;
UART_Tx.tail++;
}
CS_EXIT(); /* Turn on global interrupts */
}
RS-485 Bus Arbitration
The RS-485 bus is half-duplex, this means only the CWM or an OKE system can be transmitting at given time. The CWL has a built-in bus arbitration scheme that requires no application layer involvement. The only requirement is that an OKE system implement the following logic in their serial drivers:
By default, an OKE system must not assert bus ownership and be in receive mode.
When the transmit software buffer goes from an empty to a non-empty state the OKE must assert bus ownership and be in transmit mode before filling the hardware transmit buffers.
When the transmit software buffer goes from a non-empty to empty state and all of the hardware transmit buffers are empty the OKE must de-assert bus ownership and go back into receive mode.
Half duplex mode increases the complexity and code in the CWL serial data link layer. OKE implementors only using full-duplex mode can reduce the code complexity and size of the CWL serial data link layer by defining CW_PORT_IS_HALF_DUPLEX_ENABLED as 0.