Linux下能否通过I2C设备发送自定义消息帧?——基于i.MX7D开发板的技术咨询
嘿,这个需求我刚好在折腾i.MX7D开发板的时候碰到过,咱们一步步说清楚:
可行性结论
完全可行!不过要注意:标准Linux I2C子系统默认是按「从机地址+数据」的标准帧格式来操作的,自动插入起始/停止位。要实现自定义完整帧、跳过从机地址、手动控制起止位的需求,你有两个核心方案可选:
- 直接操作I2C控制器寄存器:通过
/dev/mem映射i.MX7D的I2C硬件寄存器,完全手动控制总线时序(效率高,依赖芯片细节) - 软件模拟I2C(比特 bang):把I2C的SDA/SCL引脚当作普通GPIO,手动控制电平变化来构建任意帧(更灵活,不依赖驱动支持)
两种方案都需要root权限才能运行,下面分别给出C/C++和Python的实现示例:
C/C++实现方法
方案1:直接操作I2C寄存器
i.MX7D的I2C0基地址是0x30A20000(具体以芯片手册和开发板原理图为准),我们可以通过内存映射直接读写寄存器来控制总线:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <stdint.h> #define I2C0_BASE 0x30A20000 #define I2C0_SIZE 0x1000 // 寄存器偏移 #define I2CR 0x04 // 控制寄存器 #define I2SR 0x08 // 状态寄存器 #define I2DR 0x0C // 数据寄存器 #define IFDR 0x14 // 分频寄存器 // I2CR位定义 #define I2CR_IEN (1 << 7) // 使能I2C控制器 #define I2CR_MSTA (1 << 5) // 主模式 #define I2CR_MTX (1 << 4) // 发送模式 #define I2CR_STO (1 << 1) // 发送停止位 // I2SR位定义 #define I2SR_IBB (1 << 5) // 总线忙(起始位发送完成标志) #define I2SR_ICF (1 << 7) // 传输完成标志 int main() { int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("Failed to open /dev/mem (need root!)"); return 1; } void *i2c_regs = mmap(NULL, I2C0_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, I2C0_BASE); if (i2c_regs == MAP_FAILED) { perror("Failed to map I2C registers"); close(fd); return 1; } // 设置波特率为100kHz(分频值参考芯片手册调整) *(volatile uint16_t *)(i2c_regs + IFDR) = 0x1F; // 初始化控制器:主模式+发送模式+使能 *(volatile uint8_t *)(i2c_regs + I2CR) = I2CR_IEN | I2CR_MSTA | I2CR_MTX; // 等待起始位发送完成(总线变忙) while (!(*(volatile uint8_t *)(i2c_regs + I2SR) & I2SR_IBB)) { usleep(10); } // 发送目标字节0xAB *(volatile uint8_t *)(i2c_regs + I2DR) = 0xAB; // 等待字节发送完成 while (!(*(volatile uint8_t *)(i2c_regs + I2SR) & I2SR_ICF)) { usleep(10); } // 发送停止位 *(volatile uint8_t *)(i2c_regs + I2CR) |= I2CR_STO; // 等待停止位发送完成(总线空闲) while (*(volatile uint8_t *)(i2c_regs + I2SR) & I2SR_IBB) { usleep(10); } // 清理资源 munmap(i2c_regs, I2C0_SIZE); close(fd); printf("Successfully sent 0xAB via raw I2C register access\n"); return 0; }
编译运行:
gcc -o raw_i2c raw_i2c.c sudo ./raw_i2c
方案2:软件模拟I2C(比特 bang)
把I2C的SDA/SCL引脚配置为GPIO,手动控制电平构建帧(示例用GPIO1_IO20和GPIO1_IO21,需根据开发板原理图调整):
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #define SDA_PIN "1_20" #define SCL_PIN "1_21" #define BIT_DELAY_US 5 // 比特周期,对应200kHz波特率,按需调整 void gpio_export(const char *pin) { int fd = open("/sys/class/gpio/export", O_WRONLY); if (fd < 0) { perror("export failed"); exit(1); } write(fd, pin, strlen(pin)); close(fd); } void gpio_set_dir(const char *pin, const char *dir) { char path[100]; snprintf(path, sizeof(path), "/sys/class/gpio/gpio%s/direction", pin); int fd = open(path, O_WRONLY); if (fd < 0) { perror("set direction failed"); exit(1); } write(fd, dir, strlen(dir)); close(fd); } void gpio_set_val(const char *pin, int val) { char path[100]; snprintf(path, sizeof(path), "/sys/class/gpio/gpio%s/value", pin); int fd = open(path, O_WRONLY); if (fd < 0) { perror("set value failed"); exit(1); } char val_str[2] = {val ? '1' : '0', '\0'}; write(fd, val_str, 1); close(fd); } void i2c_start() { gpio_set_val(SDA_PIN, 1); gpio_set_val(SCL_PIN, 1); usleep(BIT_DELAY_US); gpio_set_val(SDA_PIN, 0); usleep(BIT_DELAY_US); gpio_set_val(SCL_PIN, 0); usleep(BIT_DELAY_US); } void i2c_stop() { gpio_set_val(SDA_PIN, 0); gpio_set_val(SCL_PIN, 1); usleep(BIT_DELAY_US); gpio_set_val(SDA_PIN, 1); usleep(BIT_DELAY_US); } void i2c_send_byte(uint8_t byte) { for (int i = 7; i >= 0; i--) { gpio_set_val(SDA_PIN, (byte >> i) & 1); usleep(BIT_DELAY_US); gpio_set_val(SCL_PIN, 1); usleep(BIT_DELAY_US); gpio_set_val(SCL_PIN, 0); usleep(BIT_DELAY_US); } // 忽略ACK响应(按需调整) gpio_set_val(SDA_PIN, 1); usleep(BIT_DELAY_US); gpio_set_val(SCL_PIN, 1); usleep(BIT_DELAY_US); gpio_set_val(SCL_PIN, 0); usleep(BIT_DELAY_US); } int main() { gpio_export(SDA_PIN); gpio_export(SCL_PIN); usleep(100000); // 等待GPIO初始化 gpio_set_dir(SDA_PIN, "out"); gpio_set_dir(SCL_PIN, "out"); // 构建自定义帧:起始位 → 0xAB → 停止位 i2c_start(); i2c_send_byte(0xAB); i2c_stop(); printf("Successfully sent 0xAB via bitbang I2C\n"); return 0; }
Python实现方法
方案1:软件模拟I2C(比特 bang)
用sysfs操作GPIO,代码更简洁,适合快速验证:
import time import os SDA_PIN = "1_20" SCL_PIN = "1_21" BIT_DELAY = 0.000005 # 5us,对应200kHz波特率 def gpio_export(pin): try: with open("/sys/class/gpio/export", "w") as f: f.write(pin) except Exception as e: print(f"Export pin {pin} failed: {e}") def gpio_set_direction(pin, direction): path = f"/sys/class/gpio/gpio{pin}/direction" with open(path, "w") as f: f.write(direction) def gpio_set_value(pin, value): path = f"/sys/class/gpio/gpio{pin}/value" with open(path, "w") as f: f.write(str(value)) def i2c_start(): gpio_set_value(SDA_PIN, 1) gpio_set_value(SCL_PIN, 1) time.sleep(BIT_DELAY) gpio_set_value(SDA_PIN, 0) time.sleep(BIT_DELAY) gpio_set_value(SCL_PIN, 0) time.sleep(BIT_DELAY) def i2c_stop(): gpio_set_value(SDA_PIN, 0) gpio_set_value(SCL_PIN, 1) time.sleep(BIT_DELAY) gpio_set_value(SDA_PIN, 1) time.sleep(BIT_DELAY) def i2c_send_byte(byte): for i in range(7, -1, -1): bit = (byte >> i) & 1 gpio_set_value(SDA_PIN, bit) time.sleep(BIT_DELAY) gpio_set_value(SCL_PIN, 1) time.sleep(BIT_DELAY) gpio_set_value(SCL_PIN, 0) time.sleep(BIT_DELAY) # 跳过ACK gpio_set_value(SDA_PIN, 1) time.sleep(BIT_DELAY) gpio_set_value(SCL_PIN, 1) time.sleep(BIT_DELAY) gpio_set_value(SCL_PIN, 0) time.sleep(BIT_DELAY) if __name__ == "__main__": try: gpio_export(SDA_PIN) gpio_export(SCL_PIN) time.sleep(0.1) gpio_set_direction(SDA_PIN, "out") gpio_set_direction(SCL_PIN, "out") i2c_start() i2c_send_byte(0xAB) i2c_stop() print("Successfully sent 0xAB via bitbang I2C") except Exception as e: print(f"Error: {e}")
运行:
sudo python3 bitbang_i2c.py
方案2:直接操作I2C寄存器
用mmap映射寄存器,类似C的实现:
import mmap import os import time I2C0_BASE = 0x30A20000 I2C0_SIZE = 0x1000 # 寄存器偏移 I2CR_OFFSET = 0x04 I2SR_OFFSET = 0x08 I2DR_OFFSET = 0x0C IFDR_OFFSET = 0x14 # I2CR位定义 I2CR_IEN = 1 << 7 I2CR_MSTA = 1 << 5 I2CR_MTX = 1 << 4 I2CR_STO = 1 << 1 # I2SR位定义 I2SR_IBB = 1 << 5 I2SR_ICF = 1 << 7 def main(): fd = os.open("/dev/mem", os.O_RDWR | os.O_SYNC) try: i2c_map = mmap.mmap(fd, I2C0_SIZE, mmap.PROT_READ | mmap.PROT_WRITE, mmap.MAP_SHARED, offset=I2C0_BASE) # 设置分频寄存器(100kHz,小端字节序) i2c_map[IFDR_OFFSET:IFDR_OFFSET+2] = b'\x1F\x00' # 初始化控制器 i2c_map[I2CR_OFFSET] = I2CR_IEN | I2CR_MSTA | I2CR_MTX # 等待起始位发送完成 while not (i2c_map[I2SR_OFFSET] & I2SR_IBB): time.sleep(0.00001) # 发送0xAB i2c_map[I2DR_OFFSET] = 0xAB # 等待传输完成 while not (i2c_map[I2SR_OFFSET] & I2SR_ICF): time.sleep(0.00001) # 发送停止位 current_i2cr = i2c_map[I2CR_OFFSET] i2c_map[I2CR_OFFSET] = current_i2cr | I2CR_STO # 等待总线空闲 while i2c_map[I2SR_OFFSET] & I2SR_IBB: time.sleep(0.00001) print("Successfully sent 0xAB via raw register access") except Exception as e: print(f"Error: {e}") finally: os.close(fd) if __name__ == "__main__": main()
内容的提问来源于stack exchange,提问作者Livius
相关产品推荐
相关产品推荐

