In a previous post, we discussed MCCI’s USB C® support in TrueTask USB, our embedded USB host and device stacks. This post covers some features of our implementation.
Our Type-C implementation approach emphasizes reliability and robustness.
- We avoid conditional compiles (
#if) in our code. We allow them in our header files to adjust to the target processor using abstract types; and we have certain macros for debugging that expand differently in debug builds than in release builds. But product configuration is generally accomplished at link time rather than at compile time; this greatly reduces the number of separate compiles needed for checking the configuration space, and greatly increases the readability of code by reducing the compile-time configuration parameters.
- We implement our finite state machines as tables of functions, rather than as large switch statements. The Type C Connector specification has seven state diagrams for different kinds of products (Source, Sink, Sink with Accessory, DRP, DRP with Accessory and Try.SRC, DRP with Accessory and Try.SNK, and Charge-Through). Implementing the FSM with a giant
switchstatement would be less typing, but doesn’t allow the compiler to discard unused states unless we use
#if. Using function tables allows different FSMs to share functions as needed.
- We further harden the implementation by representing the allowed transitions in the state tables, rather than embedding them in the code. For clarity, the state functions still request the next state by name; but this state must be one of the transitions allowed in the FSM description; otherwise the Type C FSM goes to error recovery (which puts the Type C port into a safe state).
- We are very careful to keep layers separate. The Type C layer knows nothing about the TCPC; code in that layer can’t include the header files because they’re not in the
#includepath. The TCPC implementation, in turn, is unaware of how registers are accessed at the low level; it uses an asynchronous abstract driver to reach its hardware.
- As mentioned in the previous post, we use a micro virtual-machine to access the TCPC registers. This allows simple register operations to be chained together, with simple logic operations, without the tedious and error-prone process of writing callbacks for each asynchronous register access. Programmer tedium is important to minimize in software of this complexity; otherwise, attention will wander and errors will occur. Writing asynchronous code with lots of callbacks is in any case hard to do, and hard to maintain; it becomes very hard to figure out what is going on.
- We implemented a TCPC register-access log recorder and a careful log display routine, to make it easier to debug problems.
Interested in embedded USB Type C support? Mail us at firstname.lastname@example.org, and our engineers will help you understand all the options.