13 exmux-总线上拉
作者:陈取德 | 最后修改:2026-04-10
一、概述
在实际开发过程中,时常会遇到 i2c 或者 spi 通讯失败的情况,测量总线的波形图时低于模组 IO 电平,必须要将同一路 i2c 或者 spi 总线上设备全部打开才能够正常通讯。按照 i2c 和 spi 的总线设计来说,理论上应该支持单总线同时挂载多台设备,并且多台设备之间不互相干扰的。为什么实际上却出现了干扰呢?同样的情况出现在了我们合宙对外公开售卖的开发板中,经过我们的测试发现,原因出在了两个地方:
- LDO:很多客户在使用i2c/spi设备时,参考设备的建议电路设计,会将i2c/spi总线上拉到设备供电的LDO上,这个逻辑是没有问题的,设备打开同时,拉高总线工作。但是问题就出在LDO上,以LP5907MFX为例,其在关闭时,LDO电源输出管脚会有下拉电阻到地,进而造成i2c电平拉低,比如Air780EHM电平设置为3.3V时,会被拉低到2.2V甚至2V以下,造成i2c通讯不正常。

- 设备芯片:类似ES8311或者CH390这类芯片,他们的芯片设计时每个管脚都接了下拉电阻,在设备关断时并不会进入高阻态,而是经过下拉电阻到了地,导致i2c/spi总线被拉低了,通讯不正常。
为解决以上的问题,我们在每个产品的硬件设计建议中增加了 i2c/spi 的说明(详情见各型号目录下”硬件开发资料“),并制作了 exmux 库,用于在单总线挂多设备的应用场景中,能够统一管理每个 i2c/spi 总线上所有设备的供电使能,确保总线不会因为 LDO 或者设备芯片原因被拉低,导致通讯失败。
注意事项
exmux 库已内置 Air780 系列、Air8000 系列开发板的配置,涵盖如下版本:
- Air780系列的V1.2开发板 - "DEV_BOARD_780_V1.2"
- Air780系列的V1.3开发板 - "DEV_BOARD_780_V1.3"
- Air8000系列的V2.0开发板 - "DEV_BOARD_8000_V2.0"
在 exmux.setup()时填入对应开发板的型号字符串即可;
如果是使用自己的板子,则填入自定义的配置表;
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的相关示例:demo/camera:
-- 使用开发板
exmux.setup("DEV_BOARD_8000_V2.0")
-- 打开i2c1总线上的设备
exmux.open("i2c1")
-- 关闭i2c1总线上的设备
exmux.close("i2c1")
-- 使用自定义配置
ex_device_param = {
i2c1 = {
pwr1 = 2, -- 设备电源使能
pwr2 = 20, -- 设备电源使能
pwr3 = 23 -- 设备电源使能
},
spi1 = {
pwr1 = 1, -- 设备电源使能
cs1 = 8, -- 片选引脚
pwr2 = 3, -- 设备电源使能
cs2 = 9 -- 片选引脚
}
}
exmux.setup(ex_device_param)
-- 打开i2c1分组
exmux.open("i2c1")
-- 关闭spi1分组
exmux.close("spi1")
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
合宙 LuatOS 内核固件中定义的核心库常量,和此扩展库功能有关,所以也会放到本文描述。
该函数库无常量
四、函数详解
4.1 exmux.setup(ex_device_param)
功能
初始化 IO 管理配置表;
注意事项
根据实际硬件设计中的 i2c 和 spi 填写,并按照总线分组,初始化时使用 i2c1 则填在 i2c1 的表格中,spi 同理;
增加管脚时按照表格的格式增加,勿修改表格格式和名称;
参数
ex_device_param
常量含义:IO管理配置信息;
数据类型:string or table;
是否必选:必须传入此参数;
注意事项:如i2c或者spi总线仅挂单个设备时无需配置,该功能仅对单路总线多设备场景;
示例代码:string格式是合宙公开售卖的开发板,Air780系列所有型号均通用一个配置表,只有版本差异,目前支持以下三种型号的配置表,直接复制填入即可:
"DEV_BOARD_780_V1.2"
"DEV_BOARD_780_V1.3"
"DEV_BOARD_8000_V2.0"
table格式是自定义配置表,根据硬件设计中所使用的i2c/spi分组填写对应表格,如果硬件设计上没有供电控制,开机后i2c/spi设备一直处于上电状态则不需要填入配置表
ex_device_param = {
参数含义:配置i2c1分组参数;
数据类型:table;
取值范围:i2c1、i2c0;
是否必选:可选;
注意事项:使用了哪路i2c就填哪路i2c总线号,没使用i2c时不填;
参数示例:
i2c1 = {
参数含义:i2c1总线上设备的供电使能管脚;
数据类型:munber;
取值范围:产品支持的 GPIO ID,可以查询具体产品的硬件手册;
是否必选:必选;
注意事项:需是挂载在i2c1总线上的设备,如果硬件设计上没有供电控制,开机后i2c设备一直有电时不需要添加到配置表中;
参数示例:
pwr1 = 2,
参数含义:i2c1总线上设备的供电使能管脚;
数据类型:munber;
取值范围:产品支持的 GPIO,可以查询具体产品的硬件手册;
是否必选:必选;
注意事项:需是挂载在i2c1总线上的设备,如果硬件设计上没有供电控制,开机后i2c设备一直有电时不需要添加到配置表中;
参数示例:
pwr2 = 20,
...
},
参数含义:配置spi1分组参数;
数据类型:table;
取值范围:spi1、spi0;
是否必选:可选;
注意事项:使用了哪路i2c就填哪路i2c总线号,没使用i2c时不填;
参数示例:
spi1 = {
参数含义:spi1总线上设备的供电使能管脚;
数据类型:munber;
取值范围:产品支持的 GPIO,可以查询具体产品的硬件手册;
是否必选:必选;
注意事项:需是挂载在spi1总线上的设备,如果硬件设计上没有供电控制,开机后spi设备一直有电时不需要添加到配置表中;
参数示例:
pwr1 = 1,
参数含义:spi1总线上设备的片选管脚;
数据类型:munber;
取值范围:产品支持的 GPIO,可以查询具体产品的硬件手册;
是否必选:必选;
注意事项:需是挂载在spi1总线上的设备,且是ex_device_param.spi1.pwr1控制设备的CS管脚,如果硬件设计上没有供电控制,开机后spi设备一直有电时不需要添加到配置表中;
参数示例:
cs1 = 8,
...
}
}
返回值
local result = exmux.setup(ex_device_param)
此处有一个返回值 result
result
参数含义:初始化i2c/spi各分组配置表结果;
数据类型:boolean;
取值范围:true or false;
注意事项:true则初始化成功,false则初始化失败,失败原因见打印;
返回示例:true;
示例
-- 使用开发板版本初始化
exmux.setup("DEV_BOARD_8000_V2.0")
-- 或使用自定义配置初始化
ex_device_param = {
i2c1 = {
pwr1 = 2, -- 设备电源使能
pwr2 = 20, -- 设备电源使能
pwr3 = 23 -- 设备电源使能
},
spi1 = {
pwr1 = 1, -- 设备电源使能
cs1 = 8, -- 片选引脚
pwr2 = 3, -- 设备电源使能
cs2 = 9 -- 片选引脚
}
}
exmux.setup(ex_device_param)
4.2 exmux.open(bus_id)
功能
i2c/spi 分组设备供电上拉,在设备需要工作前调用;
注意事项
请注意区分 i2c/spi 总线号,是字符串形式;
参数
bus_id
参数含义:i2c/spi的总线号;
数据类型:string;
取值范围:"i2c0"、"i2c1"、"spi0"、"spi1";
是否必选:必选;
注意事项:函数内已经自动处理了对应总线号的表格,所以只需要填写取值范围中的四个字符串即可;
示例代码:打开i2c0总线的所有设备;
exmux.open("i2c0")
返回值
local result = exmux.open(bus_id)
此处有一个返回值 result
result
参数含义:对应分组的打开结果;
数据类型:boolean;
取值范围:true or false;
注意事项:true的含义为打开成功或者此前已经打开过,不需要再打开;
false的含义为未找到对应的总线号,此前未设置或填写错误;
返回示例:true;
4.3 exmux.close(bus_id)
功能
i2c/spi 分组设备供电下拉,在关闭设备后调用;
注意事项
请注意区分 i2c/spi 总线号,是字符串形式;
参数
bus_id
参数含义:i2c/spi的总线号;
数据类型:string;
取值范围:"i2c0"、"i2c1"、"spi0"、"spi1";
是否必选:必选;
注意事项:函数内已经自动处理了对应总线号的表格,所以只需要填写取值范围中的四个字符串即可;
示例代码:关闭i2c0总线的所有设备;
exmux.close("i2c0")
返回值
local result = exmux.close(bus_id)
此处有一个返回值 result
result
参数含义:对应分组的关闭结果;
数据类型:boolean;
取值范围:true or false;
注意事项:true的含义为关闭成功或者此前已经关闭过,不需要再关闭;
false的含义为未找到对应的总线号,此前未设置或填写错误;
返回示例:true;
五、产品支持说明
支持 LuatOS 开发的 Air780 系列,Air8000 系列,Air8101 系列 产品都支持 exmux 库。