Wire library on OpenCR (Wire library updated to use Hardware I2C)

For the fun of it I was looking at the Wire library for the OpenCR board. My initial reason for looking is to add the Wire1 object that would use pins on the expansion connector (default pins 50, 51). Which is when I figured out that the Wire library is not currently using the underlying hardware I2C system, but doing software bitbanging). So I started working on trying to implement the Wire library using the underlying hardware system.

I have made some progress on this. I have discussed some of this on the OpenCR github project as an issue:

As well as on the Robotis forum:

@ROBOTIS-Will suggested I post up here as well so…

I believe I have most of the Wire Master code working, including Wire1 object, although still need to test (and probably) fix some issues associated with send_stop and restart code. Need to find or create test sketch for this.

So started working on trying to implement the Wire slave code. That is where your sketch does stuff like:

  Wire1.onReceive(&process_OnReceive);
  Wire1.onRequest(&process_onRequest);
  Wire1.begin(SLAVE_ADDRESS);    // Start up Wire1 as a slave

Where the user function process_OnReceive will be called when the I2C Master does a transmission to the slave address. and the onRequest will be called when the master does a requestFrom…

So I put in some initial code that I thought should setup the receive interrupt in my slave begin method:

 HAL_I2C_Slave_Receive_IT(hi2c_, rxBuffer, sizeof(rxBuffer));

And setup test case to trigger it, and then ran it and nothing happened. That is the HAL_I2C_EV_IRQHandler was not called. I verified this by setting up to have an IO pin go high when this function was called and go low when it was to return… Nothing happened.

Finally figured out there was no glue(code) in system, that setup the I2C interrupts, they were set to the default handler, and they were not enabled. So I added code similar to how UARTS do it and finally later yesterday, the HAL handler is begin called :smiley:

Currently I am testing this with simple test app, where on my OpenCR board I have the Wire (Master) pins connected to the Wire1 (Slave) pins and the test app simply waits for user input on Serial. When it receives it it sends the data out over Wire. My Wire1 onReceive method then echoes this data back on to Serial…
Current sketch (Still can not attach zip files here…)

#include <Wire.h>
#define SLAVE_ADDRESS 0x66

// 

void setup() {
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  pinMode(12, OUTPUT);
  digitalWrite(12, LOW);
  while (!Serial && millis() < 5000) ;
  Serial.begin(115200);
  delay(400);
  Serial.println("Start Wire to Wire1 test");
  Wire1.onReceive(&process_OnReceive);
  Wire1.onRequest(&process_onRequest);
  Wire1.begin(SLAVE_ADDRESS);    // Start up Wire1 as a slave
  Wire.setClock(400000);  // Lets try 400K speed
  Wire.begin();         // Start up Wire as master
  Serial.printf("I2C1 ISR:%x CR1:%x CR2:%x OAR1:%x\n", I2C1->ISR, I2C1->CR1,
      I2C1->CR2, I2C1->OAR1);
  Serial.printf("I2C2 ISR:%x CR1:%x CR2:%x OAR1:%x\n", I2C2->ISR, I2C2->CR1,
      I2C2->CR2, I2C2->OAR1);

#if 0
  // Quick hack to see if I see anything
  for (byte address = 0; address < 127; address++) {
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();
    if (error == 0) Serial.printf("Found something: %x\n", address);
  }
#endif  
  for(int i=0;i<4;i++) {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, (GPIO_PinState)1);    // digitalWrite(13, Low);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (GPIO_PinState)0);    // digitalWrite(12, low);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, (GPIO_PinState)0);    // digitalWrite(13, Low);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (GPIO_PinState)1);    // digitalWrite(12, low);
  }
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (GPIO_PinState)0);    // digitalWrite(12, low);

}

void loop() {
  Serial.print("Enter Text to send: ");
  while (Serial.available() == 0) ;

  Wire.beginTransmission(SLAVE_ADDRESS);
  int ch;
  while((ch = Serial.read()) != -1) {
    if ((ch >= ' ') && (ch <= '~')) Wire.write(ch);
    Serial.write(ch);
  }
  Wire.endTransmission();
  delay(5);
#if 0 
  Serial.printf("I2C1 ISR:%x CR1:%x CR2:%x OAR1:%x\n", I2C1->ISR, I2C1->CR1,
      I2C1->CR2, I2C1->OAR1);
  Serial.printf("I2C2 ISR:%x CR1:%x CR2:%x OAR1:%x\n", I2C2->ISR, I2C2->CR1,
      I2C2->CR2, I2C2->OAR1);
#endif
  // Lets see what happens when we call Wire.requestFrom
  uint8_t cb = Wire.requestFrom(SLAVE_ADDRESS, 4);
  Serial.printf("Wire.requestFrom return value: %d\n", cb);
  Serial.printf("I2C1 ISR:%x CR1:%x CR2:%x OAR1:%x\n", I2C1->ISR, I2C1->CR1,
      I2C1->CR2, I2C1->OAR1);
  Serial.printf("I2C2 ISR:%x CR1:%x CR2:%x OAR1:%x\n", I2C2->ISR, I2C2->CR1,
      I2C2->CR2, I2C2->OAR1);
}

void process_OnReceive(int num_bytes) {
  Serial.printf("OnRecieve Called: %d = ", num_bytes);
  while (Wire1.available()) {
    Serial.write(Wire1.read());
  }
  Serial.println();
}

void process_onRequest() {
  Serial.println("onRequest called");
}

Current status
The current WIP code is up on my fork/branch on github:
https://github.com/KurtE/OpenCR/tree/Wire-Library
Currently this branch is based off of my SPI fork, which has been merged into the main develop branch, so soon I will try to rebase this off of the develop branch… As I am not a git wizard, I may do this by copying the changed code to safe place, switch to develop branch, create new branch off of develop and then copy in changed code…

As I mentioned the onReceive code appears to be functioning (at least for this test case), Figured out that I needed to reissue the call to HAL_I2C_Slave_Receive_IT after each time I received something.

so starting to try to setup code for the onRequest. Simple test added as part of code above, where after I send data to slave I ask the slave for 4 bytes.

Some output from test app:

Start Wire to Wire1 test
I2C1 ISR:1 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:1 CR1:fd CR2:2000000 OAR1:80cc
Enter Text to send: abcd
OnRecieve Called: 4 = abcd
Wire.requestFrom return value: 0
I2C1 ISR:8001 CR1:1 CR2:20404cc OAR1:0
I2C2 ISR:cd8003 CR1:fd CR2:2000000 OAR1:80cc
Enter Text to send: 

Right now trying to figure out how to setup the glue for doing either onReceive or onRequest.
From the above output, I do see that the Master is trying to talk to Slave after the call returns, the status of the two wire objects both show busy.

The shows that the TXIS bit is set saying that the Wire object wants data. But currently the CR1 is setup for Receive so the HAL_I2C_EV_IRHandler will not call of to the I2C_SlaveTransmit_ISR.

Note: the slave transmit and receive call both use the same internal buffers, so not sure if it makes sense to enable both at the same time? That is I could have begin code in addition to calling: HAL_I2C_Slave_Receive_IT also call of to HAL_I2C_Slave_Transmit_IT

I think that would be problematic, with the Wire library as I believe the I2C_SlaveTransmit_ISR just wants to send the data that currently sits in it’s own buffer and not call of that time to client code to tell it what to send…

Suggestions? Am I missing something obvious?

Thanks

As I have mentioned either here or Issue or forum, that I would probably create a new branch, which started off with an up to date version of develop and only the wire changes.

It is now up there:
https://github.com/KurtE/OpenCR/tree/Wire_develop
I will soon delete the other branch.

Note, I have made some progress with the onRequest., The two sides are talking, but I am now debugging some either timing or order of updates issue. That is I have the Master ask for 4 bytes,
The slave code when it detects an address match and the direction flag is set, I now have it calling off to my process_onRequest, which does:

void process_onRequest() {
  Serial.println("onRequest called");
  Wire1.write("1234");
}

It looks like the 1234 is properly output, but on the first time through only the 123 are returned on master side, it also aborts with STOPF condition. Then if I do it again, this time, the master receives 4 bytes: 4 1 2 3

Example output:

Start Wire to Wire1 test
I2C1 ISR:1 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:1 CR1:9 CR2:2000000 OAR1:80cc
Enter Text to send: abcd
OnRecieve Called: 4 = abcd
onRequest called
STOPF
TwoWire::requestFrom RXNE 1 1
Wire.requestFrom return value: 0 - 123
I2C1 ISR:5 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:cd0003 CR1:9 CR2:2008000 OAR1:80cc
Enter Text to send: efgh
OnRecieve Called: 4 = efgh
onRequest called
Wire.requestFrom return value: 4 - 4123
I2C1 ISR:5 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:cd0003 CR1:9 CR2:2008000 OAR1:80cc

The first one that gives me the STOPF condition while waiting for RXNE, the Logic Analyzer output looks like:

The second one which returns 4 bytes (using last byte probably of previous … Looks similar except a NAK instead of ACK on last byte:

Still debugging. If anyone would like to try it out, that would be great.

Edit/Update - pushed up some additional changes, warning some debug code is enabled mainly with Slave code.

Updates include; setClock should set for 100k, 400k, 1000K

Added some support for sendStop (turned off) on requestFrom…

Still debugging slave mode. Right now I have the slave mode ISR keeping track of some of the I2c State for each call to it, which I then print out after I try a Transfer to the slave and a request back from slave. Hopefully making some progress on understanding… Current sample run output look like:

Start Wire to Wire1 test
I2C1 ISR:1 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:1 CR1:9 CR2:2000000 OAR1:80cc
Enter Text to send: abcd
OnRecieve Called: 4 = abcd
Wire.requestFrom return value: 4 - 1234
ISR:00cc8009 CR1:00000009 CR2:02000000
ISR:00cc8005 CR1:000000fd CR2:02000000
ISR:00cc8005 CR1:000000fd CR2:02000000
ISR:00cc8005 CR1:000000fd CR2:02000000
ISR:00cc8005 CR1:000000fd CR2:02000000
ISR:00cc0021 CR1:000000fd CR2:02000000
ISR:00cd8009 CR1:00000009 CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8010 CR1:000000fb CR2:02000000
ISR:00cd0020 CR1:000000fb CR2:02000000
I2C1 ISR:1 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:cd0000 CR1:9 CR2:2008000 OAR1:80cc
Enter Text to send: efgh
OnRecieve Called: 4 = efgh
Wire.requestFrom return value: 4 - 1234
ISR:00cc8008 CR1:00000009 CR2:02000000
ISR:00cc8004 CR1:000000fd CR2:02000000
ISR:00cc8004 CR1:000000fd CR2:02000000
ISR:00cc8004 CR1:000000fd CR2:02000000
ISR:00cc8004 CR1:000000fd CR2:02000000
ISR:00cc0020 CR1:000000fd CR2:02000000
ISR:00cd8008 CR1:00000009 CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8003 CR1:000000fb CR2:02000000
ISR:00cd8010 CR1:000000fb CR2:02000000
ISR:00cd0020 CR1:000000fb CR2:02000000
I2C1 ISR:1 CR1:1 CR2:2000000 OAR1:0
I2C2 ISR:cd0000 CR1:9 CR2:2008000 OAR1:80cc
Enter Text to send: ijkl
OnRecieve Called: 4 = ijkl
Wire.requestFrom return value: 4 - 

The request from values are not working all the time. Should currently always return 4 bytes 1, 2, 3, 4…

Notes on the above outputs…
When you see the first part where CR1 = 0x9… This is a slave address match… The first part here is the transmission from Master to slave, which appears to be working… Note ISR upper word low bits are 0xcc, so direction bit is not set…

Then you will see another CR1 again - 0x9 - This again is the address match and on this part the ISR low byte of upper word is 0xcd so low bit set it is a request… Can explain more if interested

still debugging…

@OpusK and @ROBOTIS-Will (and others)

Thought I should mention, that if anyone wishes to try it out, the code was updated on github, and it currently still has the debug code enabled: I also included current test app showing the above data in the Issue:

Here shows some of the Logic Analyzer output, for just the area where the slave is trying to send the data back:

Couple things of interest here in the Logic Analyzer output. Obviously the top two lines is the I2C communications.

The fifth line shows where the Slave ISR function is active. As I mentioned above each call here I will queue up the values of ISR, CR1, CR2, which I print out later.

The line below this, is when the Master code retrieves a byte

The line above this shows some points of information about when some things are called from the ISR. The first wider one is when the code is processing the address match state (example): ISR:00cd8009 CR1:00000009 CR2:02000000
The code will see this, see that the direction bit is set, will call the users onRequest…

The next several narrow spikes is when the ISR is asking for data: ISR:00cd8003 CR1:000000fb CR2:02000000
With these, I use the data the user provided in the onRequest to send. However it appears to want one extra byte so I provide a 0. If I do not provide it, the ISR will continue to be called until it gets cancelled later.

The last double spike on that line, is where the system I believe is telling me that we have processes(sent last byte and the master turns on the NAK state: ISR:00cd8010 CR1:000000fb
I believe as the slave I am supposed to acknowledge this and turn off the NAK

what is interesting here is that the NAK state ISR happens after the master has already retrieved the data and the NAK state is shown in the I2C communications… After this ISR returns, We get one more which is for the STOPF condition: ISR:00cd0020 CR1:000000fb

Again it would great if some one else wishes to take a look and maybe see if there is something obvious I am missing or I am misunderstanding.

It would also be good if someone could try out using some other examples of the code: like Camera using Wire1 object…

Again back to debugging

Hi Kurt,

You made so much progress while we are wrestling with unorganized boxes and tables.
I’ll catch up with your code as soon as possible.
And thank you for posting the Logic Analyzer image which helps me to have better understanding on what is going on with the code and the result :smiley:
This also could improve my previous project that I’m trying to refine for the better performance!
Thanks again and have a lot of fun!

You are welcome!

My current Wire library changes have now been merged into the main develop branch. :smiley:

Now back to hopefully putting my Turtlebot 3 Waffle PI->UP robot back together and using some of this stuff.

At some point will probably pick up a 2nd OpenCR board to make it easier to make these level of changes without needing to pull the top deck off of the Waffle :wink:

1 Like