点击此处查看详细分析解答
如何在 openUBMC 中向自定义协议芯片发送 I2C 透传命令并获取响应
根据您提供的问题描述,您的目标是通过 SMC 控制器与挂载在其下的 Exp 芯片(I²C 地址 0xA2)进行通信,使用 自定义二进制协议帧格式完成命令发送与响应接收。当前遇到的核心问题是:mdbctl call 使用 ComboWriteRead 发送的数据被错误解析或未按预期格式传递到底层 I2C 总线。
我们将结合 BMC、I2C、Chip 配置模型和底层通信机制,提供一个全面的分析和解决方案。
一、问题定位与根本原因分析
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 ... 时无法识别帧头,返回错误
- 实际并未按自定义协议完整发送整包数据
结论:BlockIO::ComboWriteRead 适用于标准 I2C 寄存器读写(即:写地址 → 写值 或 读地址 → 读值),不适用于完全自主定义的原始二进制协议帧(如包含帧头、校验和的大包数据)
二、推荐解决方案:使用 BitIO + 自定义 Lua 脚本 或 bmc.kepler.Bus.BlockIO WriteRead
要实现对 Exp 芯片的 原始字节级控制,建议采用以下两种方案之一:
方案一:使用 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:使用 busctl 或 mdbctl 发送原始数据包
# 使用 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 字节) |
优点:此方式完全绕开 “offset” 概念,直接以 主设备身份向 I2C 设备发送任意字节序列
注意:部分底层驱动可能限制 WriteRead 中读写地址必须一致,确保硬件支持该操作
方案二:编写自定义 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()
三、配置优化建议
您当前的 Chip_Exp 配置基本正确,但可根据需求微调:
"Chip_Exp": {
"Address": 162,
"AddrWidth": 1,
"OffsetWidth": 0, // ⚠️ 若不使用“偏移”概念,设置为0更安全
"WriteTmout": 100,
"ReadTmout": 100,
"HealthStatus": 0,
"WriteRetryTimes": 1,
"ReadRetryTimes": 1
}
关键点:OffsetWidth: 0 可防止系统尝试添加“offset”字段到通信中,避免干扰原始数据帧
四、调试技巧与验证方法
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 在线
五、可选高级方案:实现 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 解析响应。但这属于深度定制,适用于产品化场景。
总结
| 方案 |
是否适用 |
说明 |
Chip.BlockIO.ComboWriteRead |
不推荐 |
会将 data 当作 offset 处理,导致协议异常 |
Bus.BlockIO.WriteRead |
强烈推荐 |
绕过 offset 限制,直接操作 I2C 总线 |
| 自定义 Lua 脚本 |
推荐 |
可复用,易于维护,适合复杂协议 |
修改 Chip 配置 OffsetWidth=0 |
建议 |
防止额外偏移字段插入 |
最终答案:如何向芯片发送自定义 I2C 请求?
要正确发送完整的自定义协议帧(包括帧头、命令、校验等),必须使用 I2C 总线级别的原始接口,避免使用依赖“register offset”的 Chip 抽象。
正确做法如下:
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 模块支持自定义协议类型。