Monday, July 21, 2014

Communication with embedded devices

It is always a hassle to communicate with embedded device. I do manage one time solution e.g. through USB to UART converter but it is always too much interleaved with application code to later switch to integrated USB peripheral.

Sometimes in the past I got requests to provide new or additional way of communication for device. Another use case I wrote this library for is when I found myself repeating writing same code. I had point to point serial communication implemented on some project. On another project I used serial bus approach (with CAN physical drivers - not important) and the communication was identical with addition of the byte header for destination address at the beginning of each packet. Repetition also happened on client and host side.

In short, Comm library offers a way to make communication entities modular thus easily replaceable. It also makes them stackable to make the whole through communication extending easy. First library implementation is in C [Google Code repository].

Communication is meant to be of  query - response from master (client) to slave (host). The use of library enforces a way of transmission and reception but the same design can be used on both sides of communication channel. This means that layer can have same implementation on client and host  side if both running on the same hardware platform.


From library point of view the communication entity is self sufficient communication layer implementing a set of predefined functions i.e. implementing an interface. As it turned out layers are of two types. In one stack there are more data formatting layers and single physical layer.

Physical layers are in charge of data storage and thus having communication buffer. Example of a layer on top of physical would be an encryption layer. Application interacts with Comm library functions when transferring data. Library then calls Comm layer implementation as needed. All functions have C++ style “this” pointer parameter. In the application accessible library functions this is a predefined Comm stack type holding a pointer to array of another predefined type instances each describing single Comm layer implementation. Comm layer instance is used as the "this" pointer in layer implementation functions and in library functions used for data transfer between layers. Comm layer instance has 5 pointers:
  • vague void pointer member to store reference to a custom type object for specific implementation
  • pointer to predefined type object holding function pointers of layer implementation
  • pointers to upper and lower layer if any
  • pointer to Comm stack to which layer belongs.

Instance custom pointer and pointer to functions are initialized in extra initialization function call which is custom for each specific layer implementation and additionally exposed to the application together with a custom instance type. This initialization function is not to be mistaken with layer implementation initialization function.
Pointers to surrounding layers and pointer to parent stack are initialized in library initialization call which should result in established connection after return.
Or everything again in objective language words: main communication stack object has methods for communication and holds references to layer objects each implementing the defined interface and custom init method which is only method called by application directly.
Below is a general sequence and internal states of the objects after each initialization step:

Initial state.

After first layer custom initialization call.

After last layer custom initialization call.

After Comm library initialization call.
Next to custom and implementation initialization method a Comm layer implements data transfer, event notification, and process methods.

Data transfer methods:

  • Send
    To send data in direction from application towards physical layer. Layer can optionally implement this method to transform data or add data to sent data. Additional data must be then in same fashion removed on receiving side in OnData call.

  • Store
    To save data in comm buffer. Called from OnData call back from upper layer. Usually only implemented in lowest, physical layer.

  • OnData
    To optionally unpack received data. Called from lower layer ReceiveProcess when new data received. Here is a chance to undo the changes to data made in Send and in PacketStart and in PacketEnd on transmitting side. If implemented it must forward remaining data with chained OnData call to upper layer or call Store if there is no upper layer like library does.

Event notifications:

  • PacketStart
    First call from application when sending a packet. Lower layers get this call first, upper last. Each layer can already put own header to transmitted packet with call to Send. This call gives layers a chance to clean up from previous application packet transfer. Send call(s) will follow.

  • PacketEnd
    Last call from application when sending a packet. Upper layers get this call first to append custom data to the packet with call to Send if needed. Physical layer can start actual transmission in this call.

  • OnNewPacket
    Call originates in physical layer ReceiveProcess method when start of new packet is detected. Here is a chance for layers to prepare for new packet. OnData call(s) will follow.

  • OnPacketEnd
    Called from physical layer ReceiveProcess  after complete packet was received.

Process methods:

  • TransmitProcess
    Called repeatedly from application after PacketEnd. Usually only implemented in physical layer. Returns Active status while data is being sent to medium and returns Idle status after all data is sent.

  • ReceiveProcess
    Called repeatedly from application to receive a packet. Usually only implemented in physical layer. Returns Idle status while there is no incoming data, optionally returns Active status while data is being received and returns NewPacket status after whole packet is received. Together with NewPacket status it returns the size of the packet and pointer to received data.

Flow of calls to send and receive the data with Comm library is as depicted below.

First call in client to send a packet to client; first call on host to send response.
PacketEnd behaves identically. Physical layer can start sending data in PacketEnd call.

Multiple calls to send data on client and host.

Multiple calls while transmitting data on client and host.

Repeatedly called on client while expecting a response from host; repeatedly called on host while waiting for new packet. OnPacketEnd behaves identically.

Repeatedly called on client while expecting a response from host; repeatedly called on host while waiting for new packet.
For test implementation I did a pair of embedded STM32F4 project and Windows client application communicating over USB. Windows side uses WinUSB library for communication and firmware reports a OS USB descriptor associating device with WinUSB driver so drivers or .inf files are not needed. Firmware code is in C and Windows client application uses C++ in Windows only code.

No comments:

Post a Comment