Porting from Arduino: Wire.h

I’m continuing porting code from Arduino to the VScode environment (what should I call that? Simply “DaisySP”??). In my Arduino code, I use a library from DFRobot for an MCP23017 breakout which uses the Wire library. I located an STM32 copy of Wire.h/Wire.cpp in the Arduino libraries, copied it into my VSCode project. Whoops, I need utilities/twi.h. Copied that, whoops I need stm32_def.h and PeripheralPins.h … Not going down this rabbit hole :laughing: Is there a reasonable way to do this? Thanks!

I’ve done some homework, located I2CHandle in libdaisy, and can now ask a somewhat more focused question :grinning:

It looks to me like I can write an adapter for Wire.h with a bit more understanding. I’d like to begin by implementing the Arduino sketch “i2cScanner” using libdaisy; that sketch uses Wire methods beginTransmission() and endTransmission(). I’m not clear on the corresponding functions in I2CHandle (the code for both Wire and I2CHandle is beyond my know-how). The correspondences I am assuming are:

     Wire                          I2CHandle
   --------                       -----------
   instantiation, begin()          Init()
   write()                         TransmitBlocking()   
   requestFrom(), read()           ReceiveBlocking()         

Could I get confirmation of this, and some guidance on adapter code for beginTransmission() and endTransmission()? Thanks.

Hi Elby, the beginTransmission and endTransmission functions are handled within the TransmitBlocking and ReceiveBlocking functions.

The first thing done by TransmitBlocking is to send the I2C Start message to the device address, followed by the contents you’re intending to transmission. Once the data has been sent out the I2C Stop message is sent at the end of the backing.

Hope that helps!

Yes, Stephen, that helps a lot! I can see that my original goal to leave the DFRobot code untouched and write a Wire adapter was misguided; converting will be quite easy. There’s two major constructs 1) beginTransmission() → series of writes → endTransmission() which I’ll replace with TransmitBlocking, and 2) requestFrom() → series of reads → endTransmission(), which I’ll replace with ReceiveBlocking. Looks easy now :slight_smile: The one piece of code I’m not sure how to replace is this:

int DFRobot_MCP23017::i2cdetect(uint8_t addr){
  _pWire->beginTransmission(addr);
  if(_pWire->endTransmission() == 0){
      DBG("Addr ok!");
      return  ERR_OK;
  }
  return ERR_ADDR;
}

Can you give me an idea of what this looks like in libdaisy-land? Thanks much.

Glad to hear it!

So that’s essentially just starting a transmission and hoping for the ACK signal back from the device. We don’t currently have a one-to-one drop in for that function, though it would be nice. Basically, the current equivalent would be if TransmitBlocking returned I2CHandle::Result::ERR. It just might take the timeout amount of milliseconds to fail instead of quickly like the above would.

Makes sense. I’ll structure the code so that the return code from the first write is checked, rather than a stand-alone scanner call. BTW, what’s a reasonable timeout?

I do have one small concern about efficiency with this new code structure. Here’s a segment of “old-style” write code:

    _pWire->beginTransmission(_addr);
    _pWire->write(&reg, 1);

    for(uint16_t i = 0; i < size; i++)
    {
        _pWire->write(_pBuf[i]);
    }
    _pWire->endTransmission();

In the new world, this will need to be either 1) two separate TransmitBlocking calls, or 2) change the code so that the value of “reg” is stored in the first byte of _pBuf[] to support a single TransmitBlocking call. If I go with [1], how much overhead am I incurring? Thanks, as always, for taking the time to educate!

If the address is the same for both you can pack them together into a single buffer. It would definitely be quite a bit more efficient just for the fact that it wouldn’t have to set up the transaction, send the start, and stop commands.

That said, depending on your application that overhead may not be of much concern.

If the IC requires that the data follows the first write without a stop command then you should bundle them up to prevent the additional stop/start from happening.

I thought I understood everything based on your comments, Stephen, but I’m having no success. I wonder if you’d have a look at my code?

Here’s the i2cScanner code from Arduino that I’m porting:

#include <Wire.h>
 
void setup()
{
  Wire.begin();
  Serial.begin(9600);
}
 
 
void loop()
{
  Serial.println("Scanning...");
 
  int nDevices = 0;
  for(byte address = 1; address < 127; address++ )
  {
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16) Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");
 
      nDevices++;
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
}

And the output (connected via A4/A5 to 3 daisy-chained (!) GPIO boards:

Scanning...
I2C device found at address 0x25  !
I2C device found at address 0x26  !
I2C device found at address 0x27  !
done

Here’s my libdaisy version:

#include "daisy_seed.h"

using namespace daisy;

int main(void)
{
    static DaisySeed seed;

    seed.Configure();
    seed.Init();

    seed.StartLog(false);
    System::Delay(5000);

    static constexpr I2CHandle::Config _i2c_config
        = {I2CHandle::Config::Peripheral::I2C_1,
           {{DSY_GPIOB, 8},  // SCL
            {DSY_GPIOB, 9}}, // SDA
           I2CHandle::Config::Speed::I2C_1MHZ};

    I2CHandle _i2c;
    _i2c.Init(_i2c_config);

    seed.PrintLine("Scanning...");

    int      nDevices = 0;
    for(unsigned char address = 1; address < 127; address++)
    {
        uint8_t           testData = 0;
        I2CHandle::Result i2cResult
            = _i2c.TransmitBlocking(address, &testData, 1, 500);

        if(i2cResult == I2CHandle::Result::OK)
        {
            int prAddress = (address < 16) ? 0 : address;
            seed.PrintLine("I2C device found at address %x !", prAddress);
            nDevices++;
        }
    }
    if(nDevices == 0)
        seed.PrintLine("No I2C devices found");
    else
        seed.PrintLine("done");


    while(1) {}
}

And the output when the Seed is connected (pins 12/13 on the pinout):

Daisy is online
===============
Scanning...
No I2C devices found

I’ve tried it at 100KHz, 400KHz, 1MHz with the same result. I can tell that something is happening, because the code completes in 3-4 seconds; with no pin connections, it takes a bit over 60 seconds (as expected to scan 127 addresses with a 1/2 second timeout). I’m probably doing something dumb, but I’m not seeing it. Can you help? Thanks.

Problem solved. Something dumb, as I said in my last post. “Common ground”, “Common ground”, “Common ground” … Maybe if I repeat this mantra often enough, I’ll remember it!

i2cScanner code in my previous message works just fine!

2 Likes