ConnectWare Library (CWL) Task
CWL Task
The CWL task execution is single-threaded and can run as part of a non-blocking main loop, or a conditionally blocking task or thread in a RTOS or Linux environment.
The CWL is non-reentrant. This means that library functions cannot be called from more than one execution context. For example, it is not valid to call library functions from an interrupt context. It is also not valid to call library functions from the context of two or more tasks or threads when using an RTOS or Linux OS. Doing so will invariably cause the internal state of the library to become invalid and not function properly.
The CWL is non-blocking. This means the library functions will not block code execution for an extended period. For example, the library will not sit, blocked and waiting to receive a data byte from the serial port that may never be received.
Implementors building their application code on top of the library must also ensure that their code, or other high priority tasks, will not block execution of the CWL task for an extended period. However, implementors will provide synchronization that will momentarily block the CWL task, such as turning off interrupts when reading a byte from the serial RX queue.
In general, the CWL is tolerant of frequent, brief 1-50ms, and infrequent, longer 100-1000ms blocking delays because the serial communication bus timeouts are generous, 5 seconds. Regardless, implementors that minimize the duration and frequency of blocking delays will find that the library behaves more responsively.
void CWPortInitTask(void* initData, bool isFullDuplexBus, int8_t* devIdStr);
CWPortInitTask() must be called before invoking CWPortRunTask(). The first parameter, initData, is a pointer to a platform specific data structure. isFullDuplexBus should be set to true for a full duplex RS-232 or USB interface or false for a half-duplex RS-485 connection to the CWM. devIdStr is a NULL terminated string that has the globally unique device ID of the system controller. For example, a valid device ID string might be the ASCII hexadecimal representation of a 64-bit MAC address, like "00C0B70102030405". The devIdStr is included in every message sent to the OKC, so please collaborate with the OKC team to determine a valid device ID format for your device that will not collide with any other device
The CWPortInitTask() must do the following: Open and initialize the serial communication port, flush any previously pending data out of the serial receive queue, call CWMsgInit(), and set the initialization flag to true.
void CWPortRunTask(bool returnToCaller);
Implementors then must call CWPortRunTask() so that the CWL task can run. For bare metal systems running the CWL task from its main loop CWPortRunTask() should be invoked with the returnToCaller parameter set true. This causes CWPortRunTask() to return control back to the main loop after the task updates the CWL.
For RTOS or Linux implementations that have a dedicated task/thread for the CWL, they may call CWPortRunTask() with returnToCaller set false. The function will not return, instead it will sit in a loop and service the CWL task.
Reopening Dynamically Instantiated USB-based Serial Communication Devices
Some types of serial communication port devices are dynamically instantiated when a connection is made with another device. For example, USB CDC device class serial communication ports are instantiated after the USB device enumerates with the USB host. Applications, such as ones using the CWL, must open the serial communication port after it is instantiated, otherwise the open request will fail.
For CWL applications it is possible that USB enumeration has not occurred before CWPortInitTask() is invoked, which means the serial communication port will not be opened successfully.
It is also possible that after CWPortInitTask() is invoked the USB device disconnects and subsequently reconnects and enumerates with the USB host, causing the previously opened port handle to become invalid. USB enumeration may occur even if the USB cable is not physically disconnected and reconnected, for example when the USB host or device reboot. CWL applications must handle these conditions gracefully.
For USB CDC device class comports and other USB-based serial communication ports, implementors should define CW_PORT_IS_COMPORT_REOPEN_ENABLED as 1 in cwport.h. This allows the CWL to gracefully handle reopening the serial communication port if it disconnects and subsequently reconnects and enumerates. Implementors must ensure that the name of the serial communication port device does not change between enumerations since the CWL will always attempt to reopen the same device name.
Implementors may optionally set CW_PORT_IS_COMPORT_REOPEN_ENABLED as 1 for hardwired comports, such as RS232, RS485 and TTL, that are guaranteed to be instantiated at boot up before CWPortInitTask() is invoked and will never require reopening. However, implementors may set CW_PORT_IS_COMPORT_REOPEN_ENABLED as 0 for hardwired comports to reduce code size and complexity.
CW_PORT_IS_COMPORT_REOPEN_ENABLED only affects the behavior of how the CWL re-opens serial communication ports so that they can be read and written. CW_PORT_IS_COMPORT_REOPEN_ENABLED does not change the behavior of how the CWL handles hardwired serial communication ports when a cable is physically disconnected and reconnected since that will not cause the serial port to require reopening.
Conditionally Blocking the CWL Task
For bare metal systems running the CWL Task from the main loop can have a very basic implementation for CWPortRunTask(true) since the main loop is always meant to continuously run:
void CWPortTask(bool returnToCaller)
{
CW_ASSERT(_CWPortIsInited;
do { CWMsgRunTask(); } while (!returnToCaller); /* Service task */
}
However, for RTOS or Linux platforms running the CWL task in a dedicate thread, it will make sense to deliberately block execution of the CWL task, otherwise the CWL task may starve out lower priority tasks, or unnecessarily use CPU time when there's no work to perform. There are two approaches implementors can take to implement blocking, unconditional or conditional.
The following pseudocode outlines how the CWL task can be unconditionally blocked to throttle the task. The task loop simply includes an unconditional 5ms delay between each call into the task handler. Because of the CWL library architecture and the generous 5 second communication timeouts delaying 5ms every loop cycle will only cause minor throughput reduction on the serial communication port. The advantage of this approach is simplicity, but it is still not as efficient as possible because incoming serial receive data does not wake the take immediately.
The following pseudocode outlines how the CWL task can be conditionally blocked using select(). In the pseudocode select() the first argument is a file descriptor for the serial receive buffer being readable. The second argument is a timeout that will cause the select to unblock if no serial data is received within the timeout period. If the timeout is NULL then the select will block until data is received on the serial port.
The CWL task will run whenever there is buffered receive serial data or when the timeout expires. This will have better receive performance than unconditional blocking.
Note: This is not the proper way to invoke the standard implementation of select(). The pseudocode example has been deliberately simplified to more effectively convey the design principle. Also, some RTOS platforms may not have select(), but will likely have other OS primitives that can operative in a similar manner.