Я работаю над проектом, в котором я использую несколько микросхем / датчиков i2c. Adafruit имеет очень хорошие базовые библиотеки для отдельных микросхем, и это очень удобно (экономит часы на копании таблиц данных). Но их библиотеки негибки. Я пытался облегчить это, применяя инъекцию зависимостей.
Таким образом, решение состоит в том, чтобы использовать экземпляр 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...
}
Программа не дает сбоя (я использую индикатор сердцебиения, а последовательный выход продолжает работать, шина i2c также остается отзывчивой (начало / конец), но индикатор подключенного к mcp23008 не реагирует в версии инъекции зависимостей TwoWire библиотека).
Я попытаюсь добавить логический анализатор к шине i2c и посмотреть, какое сообщение приходит ... Но поскольку решение «взломанной» библиотеки работает без проблем, я думаю, что-то еще происходит.
Примечание. У меня есть обходной путь, который включает все библиотечные материалы непосредственно в основной эскиз в виде вложенных ino-файлов, которые работают, но, конечно, не очень твердо.
Справочная информация:
Чтобы сделать все управляемым, я сначала сфокусировался на микросхеме MCP23008: я подключил светодиод к одному из выводов, и, записав НИЗКИЙ, он включится (как я опустил до 3,3 В), и ВЫСОКИЙ выключи. Пока все хорошо, если я взломаю библиотеку для поддержки моего конкретного порта i2c, и это будет работать без проблем:
TwoWire myWire(&sercom2, 4, 3);
Затем я начинаю:
myWire.begin();
pinPeripheral(4, PIO_SERCOM_ALT);
pinPeripheral(3, PIO_SERCOM_ALT);
внутри метода begin библиотеки.
Затем я заменяю все ссылки на провода в библиотеке на myWire.
Опять же, пока этот момент пока хорош, я могу позволить светодиоду мигать на моем основном наброске и т. Д.
Так что это работает для моего основного случая, но это очень уродливое решение: у вас снова может быть какой-то другой порт, который вы хотите использовать для своего MCP23008. И в моем случае я хочу использовать более одного устройства i2c, и как все они делают myWire.begin (); в их начальном методе шина i2c зависает и т. д.
И, как я сказал ранее: на самом деле не нужно, чтобы сама библиотека выполняла работу myWire.begin (и, если необходимо, дополнительные вызовы pinPeripheral и т. Д.): Кто-то хочет, чтобы основной эскиз делал это.
Натан Сейдл из SparkFun Electronics написал прекрасную статью о негибкости проблемы библиотек 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 (); для того, чтобы запутать проводной канал, но даже тогда он не запутается.
Обновление 2:
Я действительно смог подтвердить, что впрыск работает с другими периферийными устройствами i2c, включая акселерометр / магнитометр FXOS8700. Так что, возможно, это просто связано с MCP, или я где-то допустил небольшую ошибку. Может быть, пришло время углубиться в таблицу данных MCP.
Обновление 3:
Также отлично работает для FXAS21002C, поэтому я думаю, что это проблема, связанная с MCP. На данный момент я создал обходной путь, добавив код MCP в файл ino в своем основном проекте, после чего я мог бы использовать внедрение зависимостей без каких-либо проблем, не так аккуратно, как хотелось бы, но пока работоспособно ...