我正在做一个使用多个 i2c IC/传感器的项目。Adafruit 为各个 IC 提供了非常好的基本库,这非常方便(节省了数小时挖掘数据表的时间)。但是他们的库是不灵活的。我试图通过应用依赖注入来缓解这种情况。
所以解决方案是使用 TwoWire 实例并将其作为对(在我的情况下)MCP23008 库(依赖注入)的引用传递,并让该库使用相同的 TwoWire 实例来执行所有 i2c 调用。
为了使主题更具可读性——但仍然提供我已经测试过的内容和原因的见解——我将首先提到这个问题,然后在底部提供更多背景信息。
// part from the modified library for the MCP23008
// i2c port expander by Limor Fried
MyLib_MCP23008.cpp
// I added a constructor
MyLib_MCP23008::MyLib_MCP23008(TwoWire *w)
{
// Constructors
this->_wire = w;
}
// convenience begin with default address
void MyLib_MCP23008::begin() {
begin(MCP23008_ADDRESS); // defined in .h as 0x20
}
void MyLib_MCP23008::begin(uint8_t addr) {
if (addr > 7) {
addr = 7;
}
this->i2caddr = addr;
// set defaults!
// _wire->begin(); don't do this here, expect the dependent TwoWire to do this...
// there is a condition for ARDUINO >= 100 else in the original...
_wire->beginTransmission(MCP23008_ADDRESS | i2caddr);
_wire->write((byte)MCP23008_IODIR);
_wire->write((byte)0xFF); // all inputs
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->write((byte)0x00);
_wire->endTransmission();
}
我也改变了所有的“电线”。在其他方法中引用“_wire->”。
MyLib_MCP23008.h:
// I left out the defines for better reading
class MyLib_MCP23008 {
public:
MyLib_MCP23008(TwoWire *w = &Wire);
void begin(uint8_t addr);
void begin(void);
void pinMode(uint8_t p, uint8_t d);
void digitalWrite(uint8_t p, uint8_t d);
void pullUp(uint8_t p, uint8_t d);
uint8_t digitalRead(uint8_t p);
uint8_t readGPIO(void);
void writeGPIO(uint8_t);
private:
uint8_t i2caddr;
uint8_t read8(uint8_t addr);
void write8(uint8_t addr, uint8_t data);
TwoWire *_wire;
};
在我的测试草图(mcp23008_test.ino)中:
TwoWire myWire(&sercom2, 4, 3);
MyLib_MCP23008 mcp23008(&myWire);
const int mcp_address = 0x20;
void setup()
{
myWire.begin();
pinPeripheral(4, PIO_SERCOM_ALT);
pinPeripheral(3, PIO_SERCOM_ALT);
// if I can find mcp23008:
myWire.beginTransmission(mcp_address);
if (myWire.endTransmission() == 0) {
mcp23008.begin();
}
// irrelevant setup stuff for pinMode / Serial etc.
mcp23008.pinMode(mcpLED, OUTPUT);
}
void loop() {
mcpLEDState = !mcpLEDState;
mcp23008.digitalWrite(mcpLED, !mcpLEDState); // i2c LED alas nothing...
digitalWrite(hbLED, mcpLEDState); // heartbeat: works fine
// after this I scan the i2c wire
// using begin/endTransmission calls
// and output it to Serial
// result: works fine...
}
程序不会崩溃(我使用心跳 LED 并且串行输出继续工作,i2c 总线也保持响应(开始/结束)但连接到 mcp23008 的 LED 在库的 TwoWire 依赖注入版本中没有反应)。
我将尝试的一件事是在 i2c 总线上添加一个逻辑分析仪,看看会出现什么消息……但是由于“被黑”库解决方案确实可以正常工作,我猜想还有其他事情发生。
请注意,我有一个解决方法,即将所有库内容作为子 ino 文件直接包含在主草图中,这可行,但当然不是很可靠。
背景资料:
为了使事情易于管理,我首先关注 MCP23008 IC:我已将 LED 连接到其中一个引脚,向其写入低电平将使 LED 亮起(当我将其降至 3.3V 时),高电平将其关闭. 到目前为止一切顺利,如果我破解库以支持我的特定 i2c 端口并且这可以正常工作:
TwoWire myWire(&sercom2, 4, 3);
然后开始我做:
myWire.begin();
pinPeripheral(4, PIO_SERCOM_ALT);
pinPeripheral(3, PIO_SERCOM_ALT);
在库的 begin 方法中。
然后,我将库中的所有 Wire 引用替换为 myWire。
到目前为止,到目前为止一切都很好,我可以让 LED 从我的主草图等上闪烁。
所以这适用于我的基本情况,但它是一个非常丑陋的解决方案:您可能又想为 MCP23008 使用一些不同的端口。在我的情况下,我想使用多个 i2c 设备,因为它们都使用 myWire.begin(); 在他们的开始方法中,i2c 总线冻结等。
正如我之前所说:人们并不真正希望库本身做 myWire.begin 的东西(如果需要额外的 pinPeripheral 调用等):人们希望主草图这样做。
SparkFun Electronics 的 Nathan Seidle 写了一些关于 Arduino 库不灵活问题的好文章:https ://www.sparkfun.com/news/2194
但是他的解决方案和他基于他的解决方案的解决方案对我不起作用:我可以在总线上看到 MCP23008,但在使用修改后的库时它对我的命令没有反应。
我还查看了https://github.com/arduino/ArduinoCore-samd/blob/master/libraries/Wire/Wire.cpp/.h,因为它与我尝试完成的工作几乎相同,但比在 Wire等级。我没有看到我的代码与该示例有任何偏差,但查看自己的代码当然总是一个弱点。
更新
这种简约的实现确实有效:我使用了标准的 Wire | 主作家作为一个板上的基本草图和标准电线 | 另一块板上的从接收器。
我换了主人:
#include <Wire.h>
#include "GenericWireTest.h"
TwoWire myWire(&sercom3, 0, 1);
GenericWireTest genericWire(&myWire);
void setup()
{
Wire.begin(); // join i2c bus (address optional for master)
genericWire.begin();
Serial.begin(115200);
}
byte x = 0;
void loop()
{
genericWire.sendMessage(x);
Serial.print("Wrote: x = ");
Serial.println(x);
x++;
delay(500);
}
CPP 文件:
#include "Wire.h"
#include "GenericWireTest.h"
GenericWireTest::GenericWireTest(TwoWire *w)
{
this->_wire = w;
}
void GenericWireTest::sendMessage(int x) {
_wire->beginTransmission(4); // transmit to device #4
_wire->write("x is "); // sends five bytes
_wire->write(x); // sends one byte
_wire->endTransmission(); // stop transmitting
}
void GenericWireTest::begin(uint8_t addr) {
// nothing to do in this case...
begin();
}
void GenericWireTest::begin(void) {
_wire->begin();
}
H 文件:
#include "Wire.h"
class GenericWireTest {
public:
GenericWireTest(TwoWire *w = &Wire);
void begin(uint8_t addr);
void begin(void);
void sendMessage(int x);
private:
uint8_t i2caddr;
TwoWire *_wire;
};
这按预期工作......注意:我添加了 _wire->begin(); 为了可能混淆Wire通道,但即使那样它也不会混淆。
更新 2:
我实际上能够确认注入可以与其他 i2c 外围设备一起使用,包括 FXOS8700 加速度计/磁力计。所以它可能只是与 MCP 有关,或者我在某个地方犯了一个小错误。也许是时候深入研究 MCP 的数据表了。
更新 3: 也适用于 FXAS21002C,所以我猜这是一些与 MCP 相关的问题。我现在通过将 MCP 代码添加到我的主项目的 ino 文件中创建了一个解决方法,然后我可以毫无问题地使用依赖注入,不像我想要的那样整洁,但现在可行......