基于C++抽象类的OLED屏幕控制程序架构实现咨询
这个需求刚好是C++抽象类+依赖倒置原则的典型应用场景,能完美实现分层解耦和可替换性。我给你一步步拆解实现思路和代码示例:
核心设计逻辑
先明确三层的职责边界和依赖关系:
- 通信接口管理类(最底层):抽象所有硬件通信的共性能力(发送命令/数据),不管是I2C、SPI还是UART,都要实现这套统一接口
- OLED基础命令类(中间层):依赖通信接口的抽象,实现OLED芯片的基础操作(设像素、初始化),自身也做成抽象类,让不同芯片的具体实现去继承
- 用户接口类(最上层):依赖OLED基础命令的抽象,提供友好的上层绘图API(画圆、画线等),仅对用户暴露这一层
层级依赖关系是用户接口类持有OLED基础命令类的抽象指针,OLED基础命令类持有通信接口类的抽象指针,全程依赖抽象而非具体实现,这样替换底层模块时上层完全不需要改动。
1. 通信接口管理类(抽象基类)
先定义最底层的通信抽象,所有通信方式都必须实现这些方法:
#include <cstdint> #include <cstddef> // 通信接口抽象基类 class ICommInterface { public: // 必须声明虚析构,确保子类能被正确销毁 virtual ~ICommInterface() = default; // 发送单字节命令到OLED virtual bool sendCommand(uint8_t cmd) = 0; // 发送多字节数据到OLED virtual bool sendData(const uint8_t* data, size_t len) = 0; };
然后写具体的通信实现,比如I2C版本:
#include <cstring> // I2C通信实现类 class I2CComm : public ICommInterface { private: uint8_t device_addr; // OLED的I2C设备地址 // 这里可以添加硬件相关的句柄(比如STM32的I2C_HandleTypeDef) public: explicit I2CComm(uint8_t addr) : device_addr(addr) {} bool sendCommand(uint8_t cmd) override { // 实际硬件I2C发送逻辑:启动传输→发设备地址→发命令标志位→发命令字节→停止传输 // 示例代码仅做示意,实际需对接硬件驱动 return true; } bool sendData(const uint8_t* data, size_t len) override { // 实际硬件I2C发送数据逻辑 return true; } };
如果要换SPI通信,只需要再写一个SPIComm类继承ICommInterface,实现对应的发送逻辑即可。
2. OLED基础命令类(抽象基类)
这一层依赖通信接口的抽象,同时定义OLED芯片的基础操作抽象:
// OLED基础命令抽象基类 class IOLEDCommand { protected: ICommInterface* comm_interface; // 持有通信接口的抽象指针 public: // 通过构造函数注入通信接口实例 explicit IOLEDCommand(ICommInterface* comm) : comm_interface(comm) {} virtual ~IOLEDCommand() = default; // 设置单个像素点的亮灭 virtual bool setPixel(uint8_t x, uint8_t y, bool on) = 0; // 初始化OLED芯片(不同芯片初始化命令不同) virtual bool init() = 0; // 清屏操作 virtual bool clearScreen() = 0; // 设置对比度(可选的基础命令) virtual bool setContrast(uint8_t level) = 0; };
然后写具体芯片的实现,比如SSD1306:
#include <cstring> // SSD1306芯片的OLED命令实现 class SSD1306Command : public IOLEDCommand { private: static constexpr uint8_t SCREEN_W = 128; static constexpr uint8_t SCREEN_H = 64; uint8_t frame_buffer[SCREEN_W * SCREEN_H / 8] = {0}; // 显存缓冲区 public: explicit SSD1306Command(ICommInterface* comm) : IOLEDCommand(comm) {} bool init() override { // 发送SSD1306的初始化命令序列 comm_interface->sendCommand(0xAE); // 关闭显示 comm_interface->sendCommand(0xD5); // 设置时钟分频 comm_interface->sendCommand(0x80); comm_interface->sendCommand(0xA8); // 设置多路复用率 comm_interface->sendCommand(0x3F); // ... 其他初始化命令省略 return true; } bool setPixel(uint8_t x, uint8_t y, bool on) override { if (x >= SCREEN_W || y >= SCREEN_H) return false; uint8_t byte_idx = (y / 8) * SCREEN_W + x; if (on) { frame_buffer[byte_idx] |= (1 << (y % 8)); } else { frame_buffer[byte_idx] &= ~(1 << (y % 8)); } // 可选:立即刷新该像素到屏幕,或者攒够数据批量刷新 return comm_interface->sendData(&frame_buffer[byte_idx], 1); } bool clearScreen() override { memset(frame_buffer, 0, sizeof(frame_buffer)); return comm_interface->sendData(frame_buffer, sizeof(frame_buffer)); } bool setContrast(uint8_t level) override { comm_interface->sendCommand(0x81); comm_interface->sendCommand(level); return true; } };
如果要适配SH1106芯片,只需要写一个SH1106Command类继承IOLEDCommand,实现对应的初始化、设像素等方法即可。
3. 用户接口类(对外暴露的唯一类)
这个类持有OLED基础命令的抽象指针,提供用户友好的高层API:
// 用户接口类,仅对用户开放 class OLEDUserInterface { private: IOLEDCommand* oled_cmd; // 依赖OLED命令的抽象指针 public: explicit OLEDUserInterface(IOLEDCommand* cmd) : oled_cmd(cmd) {} ~OLEDUserInterface() = default; // 画圆API(使用Bresenham算法) bool drawCircle(uint8_t center_x, uint8_t center_y, uint8_t radius) { int x = radius; int y = 0; int err = 0; while (x >= y) { // 绘制8个对称点 oled_cmd->setPixel(center_x + x, center_y + y, true); oled_cmd->setPixel(center_x + y, center_y + x, true); oled_cmd->setPixel(center_x - y, center_y + x, true); oled_cmd->setPixel(center_x - x, center_y + y, true); oled_cmd->setPixel(center_x - x, center_y - y, true); oled_cmd->setPixel(center_x - y, center_y - x, true); oled_cmd->setPixel(center_x + y, center_y - x, true); oled_cmd->setPixel(center_x + x, center_y - y, true); if (err <= 0) { y += 1; err += 2*y + 1; } if (err > 0) { x -= 1; err -= 2*x + 1; } } return true; } // 画线API(示例) bool drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { // 实现画线算法,底层调用setPixel return true; } // 转发底层基础操作给用户(可选) bool initOLED() { return oled_cmd->init(); } bool clearOLED() { return oled_cmd->clearScreen(); } };
4. 用户使用示例
用户只需要创建具体的通信类、OLED命令类,注入到用户接口类即可,完全不需要关心底层细节:
int main() { // 1. 创建通信实例(这里用I2C) I2CComm i2c_comm(0x3C); // SSD1306默认I2C地址 // 如果换SPI,改成:SPIComm spi_comm(CS_PIN, DC_PIN); // 2. 创建OLED命令实例(这里用SSD1306) SSD1306Command ssd1306_cmd(&i2c_comm); // 如果换SH1106,改成:SH1106Command sh1106_cmd(&i2c_comm); // 3. 创建用户接口实例,开始使用 OLEDUserInterface oled_ui(&ssd1306_cmd); // 4. 调用高层API操作OLED oled_ui.initOLED(); oled_ui.drawCircle(64, 32, 20); // 在屏幕中心画一个半径20的圆 oled_ui.clearOLED(); return 0; }
核心优势
- 完全解耦:更换通信方式只需要替换
ICommInterface的子类,更换OLED芯片只需要替换IOLEDCommand的子类,用户接口层完全不用修改 - 扩展性强:新增通信方式或OLED芯片,只需要实现对应的抽象类即可,符合开闭原则
- 可测试性高:可以编写Mock的通信类或OLED命令类,方便单元测试用户接口层的绘图逻辑
内容的提问来源于stack exchange,提问作者noearchimede
相关产品推荐
相关产品推荐

