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

Linux下能否通过I2C设备发送自定义消息帧?——基于i.MX7D开发板的技术咨询

嘿,这个需求我刚好在折腾i.MX7D开发板的时候碰到过,咱们一步步说清楚:

可行性结论

完全可行!不过要注意:标准Linux I2C子系统默认是按「从机地址+数据」的标准帧格式来操作的,自动插入起始/停止位。要实现自定义完整帧、跳过从机地址、手动控制起止位的需求,你有两个核心方案可选:

  1. 直接操作I2C控制器寄存器:通过/dev/mem映射i.MX7D的I2C硬件寄存器,完全手动控制总线时序(效率高,依赖芯片细节)
  2. 软件模拟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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 19:52:42