Firmware frequently sends diagnostic and debug messages to external systems.
Depending on system configuration or hardware availability, the same firmware image may need to transmit logs over different communication transports such as UART or SPI.
The logging logic must not depend on transport-specific details like registers, flags, or framing rules.
Instead, logging must be performed through a runtime-selected stream abstraction, allowing the transport to be changed without modifying application logic.
Your task is to complete the design so that log messages are written through an abstract output stream selected at runtime.
The application logic must remain completely independent of the chosen transport.
Program Flow:
Input Format:
Input is provided via standard input (stdin).
1 → UART transport2 → SPI transport1 ≤ N ≤ 10Output Format:
For UART:
UART:<message> For SPI:
SPI:<message> Rules:
main() must not be modifiedExample:
Example 1
Input:
1 3
SystemOK
TempHigh
Reset Output:
UART:SystemOK
UART:TempHigh
UART:Reset Example 2
Input:
2
2
Ping
Pong
Output:
SPI:Ping
SPI:Pong
Constraints:
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. |