Affordable and flexible platform to ease prototyping using a STM32F401RET6 microcontroller.

NUCLEO-F401RE I2C slave problem

25 Aug 2016

I am having a problem using NUCLEO-F401RE board for testing the i2c slave. I was using the mbed OS 5 i2c slave example (https://docs.mbed.com/docs/mbed-os-api-reference/en/5.1/APIs/interfaces/digital/I2CSlave/) with the bit rate standard 100K bit/s. I used the Total Phase Aardvark I2C host adapter sending bytes (8 bytes) to NUCLEO-F401RE board. The NUCLEO-F401RE i2c slave always holds down the SCL line forever in middle of the data transfer, which can be viewed clearly by a oscilloscope or a I2C protocol analyzer. Please help!

The I2C slave testing project is here:

Import programI2CSlaveTest

mbed OS-5 I2C Slave using Nucleo-F401RE board.

The following is the snippet of the test code:

NUCLEO-F401RE I2C slave test

include <mbed.h>

I2CSlave slave(PB_9, PB_8);
 
int main() {
   char buf[10];
   char msg[] = "Slave!";
 
   slave.address(0x0A);
   while (1) {
       int i = slave.receive();
       switch (i) {
           case I2CSlave::ReadAddressed:
               slave.write(msg, strlen(msg) + 1); // Includes null char
               break;
           case I2CSlave::WriteGeneral:
               slave.read(buf, 10);
               printf("Read G: %s\n", buf);
               break;
           case I2CSlave::WriteAddressed:
               slave.read(buf, 10);
               printf("Read A: %s\n", buf);
               break;
       }
       for(int i = 0; i < 10; i++) buf[i] = 0;    // Clear buffer
   }
}
14 Sep 2016

Hello sorry for not answering earlier. I somehow reproduced the issue this morning and please find below my first findings:

First I used another NUCLEO board as a master and used the test code below

char buf[] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}; i2c.write(ADDR, buf, SIZE); wait(1); i2c.write(ADDR, buf, SIZE);

the slave is properly getting the answer and printing result : Read A: 1234567890 Read A: 1234567890

Then next thing I did is to remove the wait(1); in the master code. char buf[] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}; i2c.write(ADDR, buf, SIZE); wait(1); i2c.write(ADDR, buf, SIZE); In this case, I reproduce a similar issue as you see and I will try to investigate how to recover from the error.

Nevertheless, there is an easy way to avoid the problem on the slave side. In order to keep real time answer, it's better to get rid of the printf (useful for quick debug, but not adapted to real time answer needed by an I2C slave).

So if by removing call to printf, I have the test working fine: case I2CSlave::WriteAddressed: slave.read(buf, 10); printf("Read A: %s\n", buf); break;

I could check on a scope that the 10 bytes are well received, and SCL as well as SDA are back to normal (high) state.

So please remove the call to printf, or add a suitable pause / wait on the master side to allow printf to happen before sending the next command

cheers Laurent

14 Sep 2016

Hi again

I just captured the UART TX (in green below) to show what happens. /media/uploads/LMESTM/i2cslave.png

the UART tx of printf "Read A: 1234567890" is in green. You can see this is blocking the system from reading I2C lines. By the time you finally read the incoming byte, the Master side will be in timeout I think. You will also note that the lines are back to normal states after some time.

16 Sep 2016

Folks, I'm having the exact same problem. Unfortunately, this is causing me to rethink the leap from a prototype using Arduino over to an MBED/STM32 environment (maybe more of a step then a leap, but you get the idea). I2C is how the multi-MCU system I'm building was supposed to communicate.

I tried the same code and see the same results using 3 different STM32 NUCLEO boards:

  • NUCLEO-F042K6
  • NUCLEO-L432KC
  • NUCLEO-F446RE

Looks like someone else is in a similar situation here:

The only way I found around this was to throttle the master such that it would not write until the slowest slave could finish reading/processing the previous write (sort of like having a small 2 byte buffer). This was done by simply throttling with wait_ms() calls (as suggested above). The real master, however, will be processing input over serial at 31250 bps which must be pushed to other processors in real-time. As long as the receivers have some wiggle room (with proper I2C library buffering), the apps should not be throttling on a per write basis.

I do have similar code running on a Teensy 3.2 using the Arduino (Wire) library and this blocking/long-ACK behavior is not present.

Question: So is this a general MBED I2C problem or something specific to the MBED I2C Slave implementation with STM32 NUCLEO boards?

18 Sep 2016

Hi Laurent, Thank you for answering my problem. I did remove the printf statements from the code and added a wait(1) at end of the loop. However, I still getting the problem. I used same master/slave setup for testing the I2C using STM32Cube and it works fine. There is no problem on the target hardware or the master. I think it is the issue on mbed library implementation. We already spent too much time on unreliable mbed develop environment and we are think to find alternative solution for the development.

Here is the snippet of the revised code:

#include "mbed.h"

I2CSlave slave(PB_9, PB_8);

int main() {
   char buf[10];
   char msg[] = "Slave!";

   slave.address(0x0A);
   while (1) {
       int i = slave.receive();
       switch (i) {
           case I2CSlave::ReadAddressed:
               slave.write(msg, strlen(msg) + 1); // Includes null char
               break;
           case I2CSlave::WriteGeneral:
               slave.read(buf, 10);
               break;
           case I2CSlave::WriteAddressed:
               slave.read(buf, 10);
               break;
       }
       wait(1);
   }
}
22 Sep 2016

Hello Jingxi Zhang, The wait() call I proposed earlier is on the _master_ side, not the _slave_ side. If you put wait() on the slave side you would certainly lose bytes :-( So you shall remove the wait(1); in your above code. cheers Laurent PS: I will try to have a deeper look at Manny post later

13 Oct 2016

Unfortunately, I got the same problem by removing statement in the I2C slave.

18 Oct 2016

Hello, can you please share latest version of the code for both master and slave side that you use when you see the problem ? Thanks Laurent

01 Nov 2016

Hi, I have the same issue connecting a GPS on a Nucleo L476. Any update on this issue?

10 Mar 2017

I too would like to know. When using the I2C slave having weird issues.

24 Mar 2017

I'm using nucleo L432kc as I2Cslave without issues....make sure to shift the address of nucleo if the i2c master using 7-bit i2c address format (i.e. in the master code if you are writing to address 0x18, then in the nucleo code -> slave.address(0x30)). You may also need pullup resistors (4.7k) on SDA and SCL lines (I'm fine without them).

21 Apr 2017

I am quite sure the issue is with mbed.

I used mbed to write an I2C slave code and it brings the SCL line down with or without pull up.

I used SPL to write the same I2C slave code and it works fine.

I think there is something wrong with mbed initialising the SCL and SDA pin that messed the whole thing up.

I would be very happy if I were wrong though but I think i'll just stick with SPL and HAL for now.

18 May 2017

Hello. After reading about this issue, invested some time to configure the same Nucleo STM32F401 board as the I2C slave with I2C ID of 0xa0 using the posted example. Configured our CAS-1000 tool as the I2C Bus Master. Here are the results which appear to show the posted code works. It is critical that the READ and WRITE calls inside the I2CSlave code match the data length and the same length values are used by the I2C Master else there will be timeout / SCL locks.

Code used in the testing:

#include "mbed.h"

I2CSlave slave(PB_9, PB_8);

unsigned char my_i2c_slave_address = 0xa0;

int main()
{
    char buf[10];
    char msg[] = "Slave!"; // 53 = S; 6C = l; 61 = a; 76 = v; 65 = e; 21 = !; 00 = end of string marker => read of 7 bytes by I2C master
    int counter=0;

    slave.address(my_i2c_slave_address);
    printf("Hello from I2CSlaveTest program for mbed.\r\n");

    while (1) {
        int i = slave.receive();
        switch (i) {
            case I2CSlave::ReadAddressed:
                printf("\r\nRead from I2C slave & sending local %02d byte string to I2C master %s",strlen(msg) + 1, msg);
                slave.write(msg, strlen(msg) + 1); // Includes null char
                break;
            case I2CSlave::WriteGeneral:
                slave.read(buf, 10);
                printf("\r\nRead from inside WRITE I2C routine... G: %s", buf);
                break;
            case I2CSlave::WriteAddressed:
                slave.read(buf, 10);
                printf("\r\n[%02d] Read A: %s", counter++,buf);
                break;
        }
        for(int i = 0; i < 10; i++) buf[i] = 0;    // Clear buffer
    } 
}

Observations:

During the launch of the posted code, the title is printed but never the first R/W I2C transaction which may be due to buffering of the printf.

Here is the I2C Master setup:

/media/uploads/juthi/mbed_i2c_master2.png

Teraterm screen after the I2C Master sends the data:

/media/uploads/juthi/mbed_i2c_slave.png

I2C Master send again 2 times:

/media/uploads/juthi/mbed_i2c_slave2.png

Next use I2C Master to read from I2CSlave mbed code (NB: The string length must match in the READ and TRANSMIT code!!):

/media/uploads/juthi/mbed_i2c_slave3.png

Teraterm screen after 7 byte Read by I2C Master:

/media/uploads/juthi/mbed_i2c_slave4.png

/media/uploads/juthi/cas1000_i2c_master.png

Tested with I2C Master (CAS-100)

@ 397khz for SCL -> works to read / write with the mbed I2CSlave.

@ 500khz for SCL -> works to read / write with the mbed I2CSlave.

@ 806khz for SCL -> works to read / write with the mbed I2CSlave. Impressive !! Higher speed than 806khz for SCL are flaky as noted below. Can perform random R/W transactions (but with the fixed string lengths) with the I2CSlave board without lock ups.

@ 1 Mhz for SCL -> works to write to the mbed I2CSlave but read from the I2CSlave locks with SCL. This SCL speed is beyond spec.

I2C pull up resistors are 4k7 for SDA and SCL pins.

These tests appear to show the I2CSlave code is working with the tested STM32F401RET6 based Nucleo. Welcome questions from other developers if you would like for me to test this setup beyond the above code.

Summary: The 'secret' to making this mbed code work is to take care to use the proper packet length value as shown in the above example.

Hope this helps someone.

22 Jun 2017

Interesting...

I have two custom boards for F405 and F446.

When I used exactly same code as the official I2C slave code, F405 board worked well but F446 didn't.

What I did was, connected the board to my RPI's I2C pins and tried two different methods as:

1. tested with i2cdetect to see the boards' I2C are fine.

2. wrote come C code to read data from F405 and F446 board.

22 Jun 2017

I tried to reproduce same issue here but surprisingly it works well today.

The succeeded case is...

- A custom F446 board connected to RPI via I2C pins.

- For F446, MBED OS 5.4 is used with the official I2C slave code.

- For RPI, BCM2835 library's I2C API is used.

I believe that the lengthe of bytes is the key factor as mentioned by Sanjiv.

However, I still have a little issue with the combination since the first character 'S' from "Slave!" because it comes as broken character 9 out of 10 tries.

Well.... for now, I am just gonna ignore the first byte and take advantage of the APIs.

23 Jun 2017

Additional Observation:

My RPI and F446 board work well to transfer 33 bytes 10 times every second.

However, I found some flaw of the i2c slave API of MBED.

When RPI requests longer data than MBED's API can reply, MBED's API stalls.

For example, when RPI executes "read_i2c_block_data(ADDR, 0, 34)", which means that it requests 34 bytes,

MBED should be prepared for that amount of bytes.

Thus the line of code I am using looks like this:

slave.write(msg, strlen(msg)+1);

'msg' is initialized as:

char msg[] = "_abcdefghijklmnopqrstuvwxyz0123456789";

So that the length is 33 and I added 1 from the line then the total length is 34.

I tested this code about a day so that that is not so confirmed yet but it seems fine for my simple application.