From a functionality point of view, any software architecture that works correctly will work. The difference is all about maintainability and modularity.
If you are 100% sure that you and everybody else involved in this project can still understand your spaghetti code after you have finished the project for three months, just go ahead.
If not, some code style management have to be done. I don't like C++ but I still do object oriented programming using C (and Objective-C conventions.) Also I have recursive layers of drivers for on-chip and off-chip peripherals. Most importantly, I have a cooperative scheduler. My code can look like this:
A generic driver that implements using an interface: (In reality PCF8574 driver also implements GPIO interface protocol, which will make the driver slightly more complicated)
typedef struct pcf8574
{
struct device super;
i2c_device_t device;
} *pcf8574_t;
pcf8574_t pcf8574_init(pch8574_t self, i2c_device_t device)
{
if (self = device_init((device_t)self))
{
self->device = device;
pcf8574_set_bits(self, 0);
}
return self;
}
void pcf8574_set_bits(pcf8574_t self, uint8_t bits)
{
i2c_write_byte(self->device, bits);
}
uint8_t pcf8574_get_bits(pcf8574_t self)
{
return i2c_read_byte(self->device)
}
And application code using it
app_t app_init(app_t self)
{
self->gpio_chip = pcf8574_init(class_alloc(PCF8574_SIZE), atm328p_i2c(0x20));
self->counter = 1;
return self;
}
void app_loop(app_t self)
{
pcf8574_set_bits(self->gpio_chip, self->counter);
self->counter = (self->counter == 0x80) ? 1 : (self->counter << 1);
}