-
Notifications
You must be signed in to change notification settings - Fork 28
Implementation notes
The implementation is layered. It consists of:
Layer | Purpose | Main files / classes |
---|---|---|
User interface | The top most layer mplements the user interface like selecting a voltage, configuration mode, LED color control. | file main.cpp
|
PD sink | The PD sink layer provides a simple interface for the USB power sink. It notifies about the advertised source capabilities (supported voltages) and can request a voltage from the source. This layer takes care of encoding and decoding USB PD messages (header and payload). | classes pd_sink and pd_header
|
PD controller | This layer implements the the details of communicating with the FUSB302 controller, monitoring for a power source, sending and receiving USB PD messages. | class fusb302
|
HAL | The hardware abstraction layer (HAL) takes care of the board and MCU specific details of the LED, button, I2C communication and timers. | classes hal and i2c_bit_bang
|
The sink layer mainly consists of the pd_sink
class and provides the interface to control the power sink and source. As part of the interface, it provides member variables representing the current state of the sink:
- a list source capabilities as advertised by the source
- the active voltage and maximum current
- the requested voltage and current (different for active values during power negotiation)
Internally, the class is also responsible for decoding and encoding USB PD messages.
The class pd_sink
processes or sends the following messages:
- Capabilities: The source announces the supported voltages (called "capabilities"). The sink must immediately request one of them.
- Request: The sink requests a specific voltage and maximum current.
- Accept: The source confirms the requested voltage. The new voltage is not ready yet.
- Reject: The source rejects the requested voltage.
- PS_RDY: The source indicates that the requested voltage has been applied.
The "user interface" with the single button switches between the available fixed voltages. Only in edge cases will it use programmable (PPS) voltages even though the class pd_sink
fully supports it. If a PPS capability is request, it needs to be re-requested every 10 seconds. Otherwise the source will return to 5V. This is implemented. pd_sink
takes care of it.
The PD controller layer mainly consists of the fusb302
class. As the name implies, it is very FUSB302 specific. It configures the chip, checks for interrupts, reads the status register, coverts USB PD messages into the FUSB302 specific token stream etc.
This class works around a peculiarity of the ZY12PDN board. As the FUSB302 interrupt line is shared with the SWDIO debug line, it does not user the interrupt line until USB PD communication has been detected. That way the SWD debug port is active if the ZY12PDN board is not plugged into a USB PD power supply.
In order to achieve it, the code switches manually between CC1 and CC2 until USB PD communication has been detected on one of the CC inputs and it is clear, which one is used in the current setup. The FUSB302 chip could do this automatically. But it would require using the interrupt line.
In order to achieve it, the class attaches the FUSB302 meaure block to CC1 and CC2 in turn. After 10ms, it reads the status register to check if a USB PD source has been detected. If not, the measure block is attached to the other CC pin.
If a power source is detected, the FUSB302 is configured fully and the interrupt line is now used. It will also start a period of 300ms for receiving the first USB PD message. If no message is retrieved, the code starts over (after retry wait period).
This can be represented in a state diagram:
The hardware abstraction layer (HAL) encapsulates board and microcontroller specific features:
-
Clock configuration
-
Simple timer with millisecond resolution (get timestamp, check for expired time)
-
Simple interface to LED (color and blink rate can be set)
-
Simple interface to button (including debouncing, reporting each button press only once, long press detection)
-
I2C communication
The I2C communication is a bit-bang implementation as the FUSB302's I2C lines are not connected to the correct microcontroller pins for using hardware I2C. The bit-bang implementation can be found in the i2c_bit_bang
class.
Since the STM32F030 has no EEPROM to save configuration values, an EEPROM must be emulated in flash memory. The two top most flash pages are used for that purpose, i.e. the flash space from 14K to 16K (relative to the start). This is not explicitly declared in a linker script. So there will be no warning if the code is bigger than 14K and interfers with the EEPROM emulation.
The basic concepts are also outlined in the AN4061 Application Note – EEPROM emulation in STM32F0xx microcontrollers. They are:
-
Since a flash page needs to be erased before writing, the flash memory could quickly wear out if the page was erased every time a configuration value is saved. So small entries with key/value pairs are appended until the flash page is full. The last entry for a given key is the current one. The flash page only needs to be erased when it is full.
-
When a flash page is full, the current value for each key is copied to the other page. This page then becomes the active page. The copying is done in a way that a valid state can be recovered if the system failed during the copy process.
Since the USB PD protocol is timing sensitive, using blocking serial communication for debugging output would interfer with successful USB PD communication and thus introduce failures that are just an artifact of debugging. So asynchronous serial output is used.
Debugging output is written to a circular buffer. The data in the buffer is then transmitted piece by piece over UART using DMA. Sending the debugging output will never block the code. However, if the buffer is full, debugging output will be discarded.
The only available debug function is debug_log(const char* msg, int val)
. The msg
parameter is actually a printf
format. So it is possible to either output a fixed string or a string with an integer number, e.g.:
debug_log("start\r\n");
debug_log("selected voltage: %d\r\n", voltage);
debug_log("register value: 0x%04x\r\n", voltage);
This debugging code is only compiled if the flag PD_DEBUG
has been defined. If it is not defined, debug_log()
calls will be removed by the compiler.
The queue
class is a FIFO queue. In this software, it is used to communicate events from the PD controller (fusb302
) to the PD sink (pd_sink
). Events can occur in rapid succession as PD messages are usually shorter than the time it takes to read status and the message over I2C. So fusb302::poll()
will process all available events and store them in the queue before returning.
The queue is multi-threading and interrupt safe as long as there is only one reader and one writer. This feature is not used at the moment.
Internally, the queue uses a circular buffer with separate "pointers" for writer's head and the reader's tail.