In embedded firmware systems, communication peripherals such as UART and SPI are often accessed through a common abstract interface. This allows higher-level modules to interact with different hardware drivers without changing application logic.
In this problem, you will implement an abstract communication driver using Embedded C++ concepts.
You are given a base class CommDriver. Your task is to declare two pure virtual member functions in this class:
send(const string& data)receive() → returns stringTwo derived classes, UartDriver and SpiDriver, already implement these functions.
The program will:
"UART" or "SPI")receive()This exercise focuses on abstract base classes, pure virtual functions, and runtime polymorphism, which are commonly used in embedded HAL (Hardware Abstraction Layer) design.
Input / Output Specification:
Input
type → "UART" or "SPI"message → non-empty, single-word string (no spaces)Output
<TYPE> SEND: <message><TYPE> RECV: <message>Example:
Input:
UART Hello
Output:
UART SEND: Hello
UART RECV: HelloConstraints & Assumptions:
Abstraction is the process of exposing only the essential features of an object while hiding the complex implementation details ("the wiring") from the user.
Think of a Car:
In C++, we achieve this using Access Specifiers (public/private) and Abstract Classes (Interfaces).
1. Data Abstraction (The Public API)
Designing a class where the user sees simple functions, but the complex logic happens privately.
class WiFiModule {
private:
// Complex hidden details (User doesn't need to see these)
void spi_write(uint8_t byte) { /* ... */ }
void handshake_tcp() { /* ... */ }
int socket_id;
public:
// Simple Abstraction (User sees only this)
void connect(const char* ssid, const char* pass) {
spi_write(0x01); // Internal logic
handshake_tcp(); // Internal logic
}
};
int main() {
WiFiModule wifi;
// The user calls one simple function.
// They don't know (or care) that it triggered 50 SPI transactions.
wifi.connect("HomeNet", "1234");
}
2. Abstract Classes (Pure Interfaces)
Defining a blueprint that enforces what a device must do, without defining how.
// Abstract Base Class
class IMotor {
public:
virtual void setSpeed(int speed) = 0; // Pure Virtual
virtual void stop() = 0;
};
// The user code works with the "IMotor" abstraction,
// ignoring whether it's a DC Motor or Stepper Motor.
void emergency_shutdown(IMotor* m) {
m->stop();
}
These two are often confused but are distinct.
| Feature | Encapsulation | Abstraction |
|---|---|---|
| Focus | Information Hiding. | Implementation Hiding. |
| Goal | Protect data from external corruption. | Reduce complexity for the user. |
| Mechanism | Getters/Setters, private variables. | Interfaces, Abstract Classes. |
| Analogy | The plastic casing around a wire. | The simple "On/Off" switch. |
1. HAL (Hardware Abstraction Layer)
This is the textbook definition of abstraction in firmware.
You write code like GPIO_Write(PIN_5, HIGH).
PORTB. On STM32, it writes to BSRR register. On Linux, it writes to a file /sys/class/gpio. Your application logic relies on the abstraction, making it portable.2. Reducing Cognitive Load
A Junior Developer can use a complex driver (e.g., a FAT32 filesystem wrapper) by just calling file.open() and file.write(). They don't need to understand sectors, clusters, or allocation tables to use it effectively.
| Pitfall | Details |
|---|---|
| ❌ Leaky Abstractions | When implementation details "leak" out. Example: A generic |
| ❌ Over-Abstraction | Creating wrappers around wrappers (Driver -> Hal -> LL -> Register). Too many layers add overhead and make debugging harder ("Spaghetti Code"). Keep it flat where possible. |
| ✅ Design from the User's View | When writing a class, write the main() code first (how you want to use it). Then implement the class to match that simple API. |