Effective architecture for multi UART application
I have an application that has to talk to 8 UART devices and a handful of other I2C and SPI devices. My current architecture is getting kind of complicated and I'd like to see how more experienced programmers handle a situation like this. I'll limit this to the UART code
I have a UART base class that provides basic UART functions like initialize, startDMAXfer, startITXfer and a few others. I have a separate file for each of the 8 UARTS that has a struct with the setup parameters specific to that UART like the DMA controller number, the RX and TX stream number, whether this specific port is DMA or Interrupt (IT) and so on. And each of these files also has the IRQ and DMA Handlers and callbacks for that specific port. The UART class also has an enumeration for every device connected to a UART, like a GPS receiver or a motor controller or an oceanographic instrument. Like this:
#pragma once
#include "USART1.hpp"
class UART
{
public:
UART()
{
}
enum devices
{
gps1 = 0,
ctd1,
pumpMotor,
msc1,
ocr1,
optode1,
flbb1,
lastDevice
};
void initUART(UART::devices name);
void startXfer(UART::devices deviceNum, uint8_t txBuff[], uint32_t txBuffSize, uint8_t rxBuff[], uint32_t rxBuffSize);
bool isMyDataReady(devices deviceNum);
private:
};
The code for any one device resides in its own file. Each device instantiates it's own version of the UART class and passes its deviceNum in the constructor like this.
UART ioPort;
ioPort.initUART(UART::gps1);
UART.initUART looks like this
void UART::initUART(UART::devices name)
{
switch(name)
{
case gps1:
usart1Init();
break;
}
}
and usart1Init() looks like this:
void usart1Init()
{
usart1::MX_USART1_UART_Init(); //This is straight from CubeMX inside STM32CubeIDE
//USART must be disabled to set character match character
LL_USART_Disable(USART1);
LL_USART_EnableIT_CM(USART1);
//Set character match character to LF (decimal 10);
USART1->CR2 |= (10 << 24);
LL_USART_Enable(USART1);
LL_USART_Disable(USART1);
LL_USART_DisableOverrunDetect(USART1);
LL_USART_ClearFlag_ORE(USART1);
//Stream 0 = RX
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_0);
and so on
I was hoping this approach would 1) be reasonably readable and understandable by any developer with moderate C skills and a minimum understanding of classes. 2) Keep all the code specific to one UART in one file. 3) Provide functions common to all UARTs again with all the common code in one file and no cutting and pasting into other parts of the code. 4) Minimize unnecessary classes. For example, the UART specific files are namespaces, not classes, since the UART specific code is almost entirely a bunch of functions that don't interact or need to maintain any state variables. 5) Minimize coupling and maximize encapsulation. For example, only the UART class needs to #include the UART specific files.
The embedded background I have is with C# where everything basically has to be a class. However, my new design guideline is to only use a class where it makes the code more readable and/or reduces cutting and pasting. Regardless, I'm thinking that this problem has been solved by any number of more experienced programmers with and without classes and I'd appreciate comments and other alternatives.
Thanks
