【i2c读取】如何与自定义协议的芯片通信

// 此模板仅供参考,如果不适用可以修改

问题描述

有个exp芯片是下挂在smc下的,如图所示:

通过smc透传信息给bmc,配置如下:

"Smc_EnclSMC": {
      "Buses": [
        "I2cMux_SMC_1",
        "I2cMux_SMC_2",
        "I2cMux_SMC_3",
        "I2cMux_SMC_4",
        "I2cMux_SMC_5",
        "I2cMux_SMC_6",
        "I2cMux_SMC_7",
        "I2cMux_SMC_8",
        "I2cMux_SMC_9"
      ]
    },
"I2cMux_SMC_9": {
      "Chips": [
        "Chip_Exp"
      ]
    },
"Chip_Exp": {
      "Address": 162,
      "AddrWidth": 1,
      "OffsetWidth": 4,
      "WriteTmout": 100,
      "ReadTmout": 100,
      "HealthStatus": 0
    },

自定义协议如下:
BMC发送命令:
帧头(固定) 操作码 数据长度 数据 校验和
0xAA 0x55 0x01 0x00 0x00 空 0xF7
EXP应答数据:
帧头(固定) 操作码 数据长度 数据 校验和
0xAA 0x55 0x01 0x00 0x02 0x00 0x23 0x40

~ ~ $ mdbctl call Chip_Exp_010103 bmc.kepler.Chip.BlockIO ComboWriteRead 0 0xaa550100 6 0xaa 0x55 0x01 0x00 0x00 0xf7 0xaa550100 8
[170,85,1,0,2,17,3,226]

发现响应报错17,3,就是0x11 0x03,就是CRC校验失败

 0xAA 0x55     0x03    0x00 0x02     0x11 0x05     0x34            (有效时间)
 0xAA 0x55     0x03    0x00 0x02     0x11 0x02     0x21            (无效帧头)
 0xAA 0x55     0x03    0x00 0x02     0x11 0x03                     (CRC校验错误)

好像就是把0xaa550100当做完整数据发送给exp芯片了,有没有办法能够将0xaa 0x55 0x01 0x00 0x00 0xf7完整数据发送给exp芯片,同时获取响应

环境信息

openUBMC2512

重现步骤

NA

期望结果

如何向chip发送自定义的i2c请求

实际结果

[实际发生了什么,包含错误信息]

尝试过的解决方案

[列出您已经尝试过的解决方案及结果]

答案内容由AI生成,仅供参考:
:white_check_mark: 总结

方案 是否适用 说明
Chip.BlockIO.ComboWriteRead :cross_mark: 不推荐 会将 data 当作 offset 处理,导致协议异常
Bus.BlockIO.WriteRead :white_check_mark: 强烈推荐 绕过 offset 限制,直接操作 I2C 总线
自定义 Lua 脚本 :white_check_mark: 推荐 可复用,易于维护,适合复杂协议
修改 Chip 配置 OffsetWidth=0 :white_check_mark: 建议 防止额外偏移字段插入
点击此处查看详细分析解答

如何在 openUBMC 中向自定义协议芯片发送 I2C 透传命令并获取响应

根据您提供的问题描述,您的目标是通过 SMC 控制器与挂载在其下的 Exp 芯片(I²C 地址 0xA2)进行通信,使用 自定义二进制协议帧格式完成命令发送与响应接收。当前遇到的核心问题是:mdbctl call 使用 ComboWriteRead 发送的数据被错误解析或未按预期格式传递到底层 I2C 总线。

我们将结合 BMCI2CChip 配置模型和底层通信机制,提供一个全面的分析和解决方案。


:magnifying_glass_tilted_left: 一、问题定位与根本原因分析

1. 当前命令行为分析

mdbctl call Chip_Exp_010103 bmc.kepler.Chip.BlockIO ComboWriteRead \
  0 0xaa550100 6 0xaa 0x55 0x01 0x00 0x00 0xf7 0xaa550100 8
  • 这条命令意图是执行 一次写 + 一次读
  • 参数结构如下:
    ComboWriteRead(
      flags,
      write_offset, write_len, [write_data...],
      read_offset, read_len
    )
    
  • 实际上,write_offset = 0xaa550100 是一个 非法偏移量(超过 OffsetWidth=4 的限制)
  • OffsetWidth: 4 表示最大地址偏移只能为 4 字节(uint32)
  • 因此,0xaa550100 被强行截断或误处理为 I2C 命令中的“register address”字段
  • 最终可能导致:
    • BMC 将 0xAA 作为起始字节尝试写入某个“寄存器”
    • 芯片接收到 0xAA 0x55 ... 时无法识别帧头,返回错误
    • 实际并未按自定义协议完整发送整包数据

:white_check_mark: 结论BlockIO::ComboWriteRead 适用于标准 I2C 寄存器读写(即:写地址 → 写值 或 读地址 → 读值),不适用于完全自主定义的原始二进制协议帧(如包含帧头、校验和的大包数据)


:hammer_and_wrench: 二、推荐解决方案:使用 BitIO + 自定义 Lua 脚本 或 bmc.kepler.Bus.BlockIO WriteRead

要实现对 Exp 芯片的 原始字节级控制,建议采用以下两种方案之一:


:white_check_mark: 方案一:使用 Bus.BlockIO.WriteRead(推荐,最贴近硬件)

目的:绕过“Chip”抽象层的 register 地址限制,直接在 I2C 总线上执行原始数据收发。

步骤 1:找到 Chip 所属 I2C 总线(例如 I2c_9

根据您的配置,Chip_Exp 属于 I2cMux_SMC_9,需先确认它连接的是哪个物理 I2C 总线(如 I2c_9

假设 I2cMux_SMC_9 接入的是 I2c_9,路径为:

/bmc/kepler/Bus/I2c/I2c_9

步骤 2:使用 busctlmdbctl 发送原始数据包

# 使用 busctl 调用 I2C 总线级别的 WriteRead 接口(推荐)
busctl --user call bmc.kepler.hwproxy \
  /bmc/kepler/Bus/I2c/I2c_9 bmc.kepler.Bus.BlockIO WriteRead \
  a{ss}yayy \
  0 162 6 0xaa 0x55 0x01 0x00 0x00 0xf7 \
  0 162 8

参数解释:

参数 含义
a{ss} Flags(通常为 0)
y I2C 设备地址(十进制 162 = 0xA2)
ay 要写入的数据(6 字节:aa 55 01 00 00 f7
y 再次指定设备地址(读阶段)
y 要读取的数据长度(8 字节)

:white_check_mark: 优点:此方式完全绕开 “offset” 概念,直接以 主设备身份向 I2C 设备发送任意字节序列

:warning: 注意:部分底层驱动可能限制 WriteRead 中读写地址必须一致,确保硬件支持该操作


:white_check_mark: 方案二:编写自定义 Lua 脚本来实现协议封装

对于频繁使用的自定义协议,建议封装为 Lua 模块。

示例脚本:exp_chip.lua

local hwproxy = require("hwproxy")
local bus_name = "I2c_9"
local chip_addr = 162
local timeout = 1000

-- 构造请求帧
local function make_request(opcode, data)
    local frame = {
        0xAA, 0x55,         -- 帧头
        opcode,
        bit.arshift(#data, 8), bit.band(#data, 0xFF) -- 长度(高位在前)
    }
    for i=1, #data do
        table.insert(frame, data[i])
    end
    -- 计算简单和校验(示例)
    local checksum = 0
    for _, v in ipairs(frame) do
        checksum = (checksum + v) & 0xFF
    end
    table.insert(frame, checksum)
    return frame
end

-- 发送并接收
local function send_recv(request_data)
    local write_args = {
        Flags = 0,
        Data = {
            Address = chip_addr,
            Data = request_data
        }
    }

    local read_args = {
        Flags = 0,
        Data = {
            Address = chip_addr,
            Length = 8
        }
    }

    -- 获取代理对象
    local bus_proxy = hwproxy.get_object_proxy("bmc.kepler.Bus.BlockIO", "/bmc/kepler/Bus/I2c/" .. bus_name)

    -- 执行 WriteRead
    local ok, result = pcall(bus_proxy.WriteRead, bus_proxy, write_args, read_args)
    if not ok then
        print("Send failed:", result)
        return nil
    end

    return result.Data.Data -- 返回读取到的字节
end

return {
    send_power_status = function()
        local req = make_request(0x01, {0x00, 0x00})
        local resp = send_recv(req)
        if resp then
            print("Received:", table.unpack(resp))
        else
            print("Failed to get response")
        end
    end
}

使用方式:

# 进入 lua 环境
root@bmc:~# mc eval
> local exp = require('exp_chip')
> exp.send_power_status()

:toolbox: 三、配置优化建议

您当前的 Chip_Exp 配置基本正确,但可根据需求微调:

"Chip_Exp": {
  "Address": 162,
  "AddrWidth": 1,
  "OffsetWidth": 0,           // ⚠️ 若不使用“偏移”概念,设置为0更安全
  "WriteTmout": 100,
  "ReadTmout": 100,
  "HealthStatus": 0,
  "WriteRetryTimes": 1,
  "ReadRetryTimes": 1
}

:red_exclamation_mark:关键点:OffsetWidth: 0 可防止系统尝试添加“offset”字段到通信中,避免干扰原始数据帧


:pushpin: 四、调试技巧与验证方法

1. 抓取 I2C 总线数据

使用逻辑分析仪或内置调试工具查看真实的 I2C 波形是否符合预期帧格式。

2. 查看 BMC 日志

journalctl -u hwproxy -f

注意日志中是否出现:

  • Chip: Chip_Exp, bus: I2c_9, write failed
  • Input/Output Error → 表示芯片无响应
  • timeout → 检查布线/上拉电阻

3. 验证地址正确性

# 扫描 I2C 总线
i2cdetect -y 9

确保 0xa2 在线


:puzzle_piece: 五、可选高级方案:实现 SMC 类型协议代理

若此类 SMC 下挂芯片较多,可开发 SMC 通信代理模块,将自定义协议标准化,例如:

"Accessor_ExpPowerStatus": {
  "Chip": "#/Chip_Exp",
  "Offset": 1,
  "Size": 6,
  "Type": "CUSTOM_SMC_FRAME",
  "CustomCommand": [0xAA, 0x55, 0x01, 0x00, 0x00, 0xF7],
  "ResponseLength": 8
}

配合自定义 Lua Parser 解析响应。但这属于深度定制,适用于产品化场景。


:white_check_mark: 总结

方案 是否适用 说明
Chip.BlockIO.ComboWriteRead :cross_mark: 不推荐 会将 data 当作 offset 处理,导致协议异常
Bus.BlockIO.WriteRead :white_check_mark: 强烈推荐 绕过 offset 限制,直接操作 I2C 总线
自定义 Lua 脚本 :white_check_mark: 推荐 可复用,易于维护,适合复杂协议
修改 Chip 配置 OffsetWidth=0 :white_check_mark: 建议 防止额外偏移字段插入

:end_arrow: 最终答案:如何向芯片发送自定义 I2C 请求?

要正确发送完整的自定义协议帧(包括帧头、命令、校验等),必须使用 I2C 总线级别的原始接口,避免使用依赖“register offset”的 Chip 抽象。

:white_check_mark: 正确做法如下:

busctl --user call bmc.kepler.hwproxy \
  /bmc/kepler/Bus/I2c/I2c_9 bmc.kepler.Bus.BlockIO WriteRead \
  a{ss}yayy \
  0 162 6 0xaa 0x55 0x01 0x00 0x00 0xf7 \
  0 162 8

即可将完整帧 AA 55 01 00 00 F7 发送给 Exp 芯片,并期待收到 8 字节响应。

如需进一步封装,请开发 Lua 脚本或扩展 hwproxy 模块支持自定义协议类型。

有点麻烦了

可以参考 追踪器件读写数据命令 抓下数据看看

"OffsetWidth"配成0后执行
mdbctl call Chip_Exp_010103 bmc.kepler.Chip.BlockIO ComboWriteRead 0 0 6 0xaa 0x55 0x01 0x00 0x00 0xf7 0 8

直接报错了

我问了下smc的人,正常通信的情况下透传过去是严格按照帧格式,不会把offset当数据。可以先用命令抓下数据,如果还看不出来可以考虑用逻辑分析仪抓下波形。

image

读命令就是先写后读,但是这个调试只能看到返回的结果,不能看到发送过去的数据

trace下Smc_EnclSMC这个器件呢,应该能看到透传发的数据