cdyn
cdyn

Reputation: 11

Design Pattern for Serial Peripherals

I am working on a bare-metal software where several classes have serial communication peripherals (UART, I2C, SPI... etc). And these peripherals have to be interchangeable in order to minimize possible future work.

By interchangeable, I mean that it should be possible for an business-logic module to use any peripheral without extra (software development) work. My sub-system is in an SoC, and communicates with FPGA and lots of other sensors and sub-systems. Especially FPGA communication is implemented with a lot of soft peripheral IPs, so peripherals' interchangeability is a big concern for me.

These peripheral classes can have identical common read and write functions, but their properties differ a lot (some have master-slave relations, some have device IDs, some have address-size modes and so on). I think those differences can be managed with a single init() function.

My problem is, I haven't been able to come up with a working design pattern to meet my need. I have tried Strategy Pattern, but it resulted in a highly-coupled system (strategy classes needed to "know" communication classes in order to initialize them). I have been looking up Object Adapter pattern, but I am not sure if it is going to work for me. Actually, I would have guessed that this is a common design problem, especially for embedded software design, but I have not been able to find the same scenario online.

Anyone has a solution for this problem? Or am I overthinking about it and maybe I don't need a design pattern for this?

Upvotes: 1

Views: 537

Answers (1)

Lundin
Lundin

Reputation: 214300

Normal design patterns are probably not a great fit for this, you'll probably just end up writing an useless abstraction layer on top of a useful abstraction layer and that's one layer too many.

In fact these buses don't have a whole lot in common, so swapping one for another in software doesn't make sense most of the time. You probably just want to route a certain pin to a certain hardware peripheral and let that peripheral's driver take over from there, with no abstraction on top.

If you insist on this design still, then I suppose make a list of everything the different buses have in common: they send, they receive, they may have semi-duplex or full duplex, they have baudrates, they have various forms of communication errors etc. This can form an abstract base class from which SPI, UART etc inherit from. Maybe some or all of them use DMA and the DMA driver could be integrated in the base class? Also in case there may be several hardware peripherals of the same kind, you can't implement the drivers as singletons but must allow multiple instances of each class.

Closer to the hardware you'll have to use some manner of double-buffering - no matter if the actual drivers use interrupts or DMA, you'll want to clearly separate the overhead abstraction layer logic from the drivers, so that you don't accidentally drag in slow C++ concepts like vtables into the real-time critical code. Have the abstraction layer work with one data set, the driver with another - you'll need at least 2 buffers for re-entrancy reasons anyhow.

Note that it is far easier to slip and create a mess when doing this in C++ than in C. If you insist on C++, you must disassemble more often to check that the code didn't go haywire and inserted some silent function/library calls etc. The main advantage of C++ over C here is easier implementation of inheritance and that's about it.

Upvotes: 3

Related Questions