Hello everyone,
I am having some trouble finding examples of how to organize embedded projects, especially for custom hardware. There are a lot of examples for how to do something on off-the-shelf hardware like Arduinos, but not so much when you are building potentially large workspaces for something that does not have a BSP.
So - the below is my understanding of the logical sections of a large workspace, including features like FPGA interfaces, reusable drivers, and interchangeable hardware. I would appreciate any sort of suggestions for anything that works better in practice.
PAC
Peripheral access controllers are standard and generated from svd files. FPGAs fabric that generates SVD would create one of these separate from the main core's SVD (e.g., zynqs)
HAL
HALs are handwritten and are meant to provide abstraction over PACs. They provide all kinds of interfaces, including ways to construct the embedded-hal
types. In general, these seem to get quite complicated with traits & types (e.g. samd-hal
)
There might also be something custom that provides traits for things not in embedded-hal
.
Drivers
Drivers are generic and depend only on embedded-hal
- should be generic over embedded-hal
traits. These provide functionality to use ICs that somehow interface with the main IC (e.g. tcal9539 i2c i/o expander, mt25q qspi flash, tmp107 uart temp sensor, tja1043 can controller, ethernet phys)
BSPs
BSPs depend on HAL crates, and expose correctly named (and sometimes typed) interfaces. This should be set during PCB design, and can actually help check for pinout errors if the HAL has very strong typing. If pinout changes, this should be the only component to change (I think). BSPs for similar things should aim to export identical names, for easy reuse in application code. I don't think any reliance on drivers belongs here.
I have also seen helper functions for configuring peripherals (uart, i2c, etc), such as the samd-hal crate. These always seem a bit tricky to write, but potentially nice to have.
Application Code
This is the actual entrypoint, imports the desired bsp as bsp
and performs the desired tasks. Which bsp it imports may be feature gated.
Different app code crates may depend on something like rtic
, embassy
, etc as needed.
So, all that in mind, I'd wind up with a directory structure like this:
# Cargo.toml in each leaf directory
ffi/ # Bindings to existing C code to be called
drivers/ # Drivers for external interfaces
pca9535/
tmp107/
pac/ # PACs for nonstandard peripherals
fpga-config-1/
fpga-config-2/
hal/ # Nonstandard HALs, feature gated for
fpga-hal/ # custom PACs
bsp/ # Define interfaces to the main chip here
pcb-1/
pcb-2-chip-1/
pcb-2-chip-2/
app/ # Run the actual code
application-1/
application-2/
Cargo.toml # cargo workspace config
Please let me know what parts of the above structure work well, and what needs some tweaking to be more accurate to what works well.