-
Notifications
You must be signed in to change notification settings - Fork 120
Description
I've refactored the Wire library to be fully async and natively support DMA with minimal changes to the public API's, being restricted to the constructor and requestFrom(). In this process, I kept the library lightweight. It has a small queue buffer that stores the operations to be performed, along with pointers to the associated callback and data container. Thus, the user can decide at runtime whether to allocate more or less storage space based on their needs.
One observation I made while implementing this is that the DMA actions can be shared between SPI/UART/Wire in the SERCOM.h(cpp) library. This can be leveraged to track which serial protocol is attached to which SERCOM. But moving DMA to the core is a major structural change to the code. This is why I am posting this message to get your feedback on whether you would be willing to entertain such a major code restructuring.
Code Structure:
SERCOM.hhandles the common DMA architectureconfigureDma(),releaseDma(),startDmaWrite(size_t length, bool targetSlave = false),startDmaRead(), and the necessary objects to track the DMA tx/rx state, including setting protocol-specific tx/rx completion callbacks.SERCOM.hmaintains a lightweight ringbuffer (generalization of the ArduinoRingBufferthat stores structs instead of bytes) for buffering serial traffic while preserving order.TwoWire()automatically sets the SDA/SCL pins based on which pins are allowed for I2C, which pads 0/1 for SDA/SCL, and which SERCOM based on FunctionCorD. I would mirror this setup for SPI and UART. This is why the Wire constructors are a bit different; you don't set the pins, you set the SERCOM and the MUX function. I have a Python script that generates a Wire.inc that has the device-specific tables. Right now, it supports all SAM D21/DA1/D5x/E5x. I use a series of CSV files parsed from the data sheets to construct theincusing a Python script. The compiler then grabs the correct configuration based on the defined chipset. This library also fully supports (sm/fm, fm+, and hs) for both Host and Client modes.SPI()would use the common architecture inSERCOM.h, streamlining the code. This would also eliminate the massive amount of memory that is allocated to support SPI. Keep the library memory footprint light and let the user chew up memory at runtime. Also, in refactoringTwoWire(), I learned a good deal about the SERCOM interrupt structure and how it interfaces with DMA. With the sercom queue, this library will be fairly easy to refactor to be fully async.Uart()I would have to refactor, but after having gone throughTwoWire(), I'm not worried about this as UART is much more straightforward than Wire.
As a result, all libraries will be fully and natively async with minimal API changes, sharing common code where possible and natively supporting DMA transactions where possible. TwoWire has to use async byte-by-byte under certain conditions because of how DMA handles restarts, transactions longer than 255 bytes, and zero-byte transmissions.
While I am writing this code for myself, I would love for it to be used by more people than just me. I figured it would be best to ask for feedback on the general structure before dropping the massive PR. My main concern here is that I need to move ZeroDMA from libraries to core, otherwise I have to copy methods from SERCOM.h into the respective protocol library, along with all the common items I outlined above; that just makes my soul hurt.
I have a note to future self as well that DMA supports hardware CRC, so with native DMA support for all protocols, implementing hardware CRC is entirely possible across the board.