You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言无类时在PIC32平台实现OOP的替代方案咨询

C语言中实现面向对象式外设管理的可行方案

针对PIC32MX250F128D芯片上多I2C外设(双SSD1306 OLED)的管理需求,以下是几种实用的C语言替代OOP的实现方案,既能避免代码冗余,又能解决你遇到的switch分支导致的显示异常问题:

1. 结构体封装实例状态与外设指针(最推荐)

核心思路是把每个I2C设备的硬件寄存器指针和私有状态数据封装到一个结构体中,操作函数以结构体指针作为第一个参数,模拟类的成员函数逻辑。

实现代码

#include <stdint.h>

// 解析I2C状态寄存器的共用体(统一位字段定义)
typedef union {
    uint16_t raw;
    struct {
        uint16_t :14;          // 占位未使用位
        uint16_t TRSTAT :1;    // 发送状态位(对应PIC32的TRSTAT)
        uint16_t :1;           // 剩余位
    } bits;
} I2CStatUnion;

// I2C设备实例结构体:包含硬件指针+私有状态
typedef struct {
    // 外设寄存器指针
    volatile uint16_t *stat_reg;   // 对应I2CxSTAT
    volatile uint16_t *trn_reg;    // 对应I2CxTRN
    volatile uint16_t *con_reg;    // 对应I2CxCON
    // 设备私有状态
    uint32_t last_tx_timestamp;    // 最后发送时间戳(用PIC32的系统计数器实现)
    uint8_t oled_slave_addr;       // SSD1306的I2C从地址
    uint8_t oled_framebuf[128*8];  // OLED帧缓存(按需添加)
} I2CDevice;

// 发送数据的成员函数(接收设备实例指针)
void I2CDevice_Send(I2CDevice *dev, char data) {
    // 写入发送寄存器
    *dev->trn_reg = data;
    
    // 等待发送完成
    I2CStatUnion stat;
    do {
        stat.raw = *dev->stat_reg;
    } while (stat.bits.TRSTAT);
    
    // 处理ACK状态(根据PIC32寄存器定义调整位偏移)
    if (*dev->stat_reg & (1 << 15)) {  // 假设ACKSTAT在第15位
        // 处理NAK错误:发送停止位、重置状态等
    } else {
        // 发送成功,更新时间戳
        dev->last_tx_timestamp = _CP0_GET_COUNT();  // PIC32系统计数器
    }
}

// 初始化两个I2C设备实例
I2CDevice i2c_oled1 = {
    .stat_reg = &I2C1STAT,
    .trn_reg = &I2C1TRN,
    .con_reg = &I2C1CON,
    .oled_slave_addr = 0x3C,
    .last_tx_timestamp = 0
};

I2CDevice i2c_oled2 = {
    .stat_reg = &I2C2STAT,
    .trn_reg = &I2C2TRN,
    .con_reg = &I2C2CON,
    .oled_slave_addr = 0x3C,
    .last_tx_timestamp = 0
};

使用方式

// 向第一个OLED发送命令/数据
I2CDevice_Send(&i2c_oled1, 0x00);  // SSD1306命令字节
// 向第二个OLED发送命令/数据
I2CDevice_Send(&i2c_oled2, 0x40);  // SSD1306数据字节

优势

  • 每个设备实例完全独立,不会出现switch分支带来的时序或优化问题
  • 状态数据(时间戳、帧缓存)和硬件操作绑定,逻辑清晰
  • 易于扩展:添加新设备只需初始化新的结构体实例

2. 函数指针表实现抽象接口(多态模拟)

如果需要对不同类型外设(比如I2C、SPI)提供统一操作接口,可以用函数指针表模拟类的虚函数表,实现"多态"效果。

实现代码

// 通用外设操作接口
typedef struct PeripheralOps {
    void (*send)(void *dev, char data);
    void (*init)(void *dev);
    // 可添加receive、reset等其他操作
} PeripheralOps;

// I2C设备结构体,包含接口和私有数据
typedef struct {
    PeripheralOps ops;              // 操作接口表
    volatile uint16_t *stat_reg;
    volatile uint16_t *trn_reg;
    volatile uint16_t *con_reg;
    uint32_t last_tx_timestamp;
    uint8_t oled_slave_addr;
} I2CDevice;

// 实现I2C的发送函数
void I2CDevice_Send(void *dev, char data) {
    I2CDevice *i2c_dev = (I2CDevice*)dev;
    // 复用之前的发送逻辑
    *i2c_dev->trn_reg = data;
    I2CStatUnion stat;
    do {
        stat.raw = *i2c_dev->stat_reg;
    } while (stat.bits.TRSTAT);
    // ... 错误处理和时间戳更新
}

// 初始化I2C设备的接口表
void I2CDevice_Init(I2CDevice *dev) {
    dev->ops.send = I2CDevice_Send;
    // 可初始化其他接口函数
}

使用方式

I2CDevice_Init(&i2c_oled1);
I2CDevice_Init(&i2c_oled2);

// 通过统一接口发送数据
i2c_oled1.ops.send(&i2c_oled1, 0x00);
i2c_oled2.ops.send(&i2c_oled2, 0x40);

优势

  • 统一外设操作接口,便于后续扩展其他类型设备(如SPI OLED)
  • 代码结构更接近面向对象的抽象设计

3. 宏封装简化重复代码(轻量方案)

如果不需要存储设备状态,只是想减少重复的寄存器操作代码,可以用预编译宏生成针对不同I2C外设的操作函数。

实现代码

// 定义宏,生成指定I2C外设的发送函数
#define GEN_I2C_SEND_FUNC(prefix, i2c_num) \
void prefix##Send(char data) { \
    I2C##i2c_num##TRN = data; \
    I2CStatUnion stat; \
    do { \
        stat.raw = I2C##i2c_num##STAT; \
    } while (stat.bits.TRSTAT); \
    if (I2C##i2c_num##STATbits.ACKSTAT) { \
        // NAK错误处理 \
    } \
}

// 生成I2C1和I2C2的发送函数
GEN_I2C_SEND_FUNC(I2C1_, 1)
GEN_I2C_SEND_FUNC(I2C2_, 2)

使用方式

I2C1_Send(0x00);
I2C2_Send(0x40);

优势

  • 代码极简,无运行时开销
  • 适合无状态的简单外设操作

内容的提问来源于stack exchange,提问作者NaturalDemon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 14:05:23