7 years, 9 months ago.

I2C read from ticker

Hi there,

I have a problem. I want to read an I2C sensor periodically with a ticker, but it isn not working. I attached my logic analyser and the first write is working but as soon as the ticker is attached the SCL and SDA pins are just pulled down and there is no clock and nothing happens

I2C i2c(I2C_SDA,I2C_SCL);

Ticker my_ticker; // ticker for interrupt

void read()
{   
     char data[7];
    data[0] = 0x10;
    i2c.write(0x70, data, 1, true);
    
}


int main()
{
    char data[7];
    data[0] = 0x51;
    i2c.write(0x50, data, 1, true);

    my_ticker.attach(&read, 1);
}

EDIT:

Here is the new code, which does not work either:

#include "mbed.h"

I2C i2c(I2C_SDA,I2C_SCL);

DigitalOut pin(PA_5);

Ticker my_ticker; // ticker for interrupt

void readBNO()
{   
    pin = 1;

    char data[7];
    data[0] = 0x10;
    i2c.write(0x70, data, 1, true);
    
    
    pin = 0;
    
}


int main()
{
    my_ticker.attach(&readBNO, 1);
  
    while(true) 
    {
        
    }
  
}

Here is a screenshot from logic of the output pins /media/uploads/format_robotik/logic_screenshot.png

Thanks for helping Lorenz

7 Answers

7 years, 9 months ago.

I have a suspicion on what's going on but can you try this first and see if I2C works?

I2C i2c(I2C_SDA,I2C_SCL);
 
//Ticker my_ticker; // ticker for interrupt
 
void read()
{   
     char data[7];
    data[0] = 0x10;
    i2c.write(0x70, data, 1, true);
    
}
 
 
int main()
{
    char data[7];
    data[0] = 0x51;
    i2c.write(0x50, data, 1, true);
 
    while(1){
    wait(1);read();
    }
}

Thanks for your answer but I know that the I2C works. I have a logic analyzer hooked up to SCL and SDA and I can see that the first command is working and that the data is correct. When the interrupt is called SCL and SDA both go from high to low, stay that way for 60ms and go up again

posted by Lorenz Ruprecht 15 Feb 2017

Ummm...I did not say I2C doesn't work... I said "see if it works"... I suspect that the timers used for ticker are pending any i2C service. My test would prove that.

posted by Bill Bellis 15 Feb 2017

I just tried your code and I2C is working fine so your assumption is probably right. Do you have any idea how i could solve this problem?

posted by Lorenz Ruprecht 17 Feb 2017
6 years, 7 months ago.

Is there any workaround to call i2c function periodically ticker like without ticker?

If you are using the RTOS there are some methods that use a "worker thread" to call the function. The crude way is to have a thread that calls the function then waits then loops.

If you are not using RTOS your program probably has a "main loop", so add code to check if sufficient time has passed then call the function. That way the call is made from "main" and is "safe".

posted by Oliver Broad 03 Apr 2018
7 years, 9 months ago.

Seems you try to read from a device at slaveaddress 0x50 after selecting a specific register (0x51) in the slave's internal registerspace. You skip the stop condition in the register selection phase and then want to use the ticker to repeatedly read from that same registeraddress. In that case the I2C operation in the the ticker interrupt should be a read instead of a write operation and it should use the same slaveaddress. You now have:

..
i2c.write(0x70, data, 1, true);
..

The incorrect slave address may confuse the slave and hang the I2C bus..

That should probably be

..
i2c.read(0x50, data, 1, true);
..

Note that this could still cause problems when the slave auto-increments the registeraddress,

To make sure everything works as expected first try the complete read operation inside the Ticker.

I2C i2c(I2C_SDA,I2C_SCL);
 
Ticker my_ticker; // ticker for interrupt

void read() {   
   char data[7];
   data[0] = 0x51;
   i2c.write(0x50, data, 1, true);  // Set the registeraddress, repeated start

   i2c.read(0x50, data, 1); // Read from the register and issue stop condition
}

int main()
{
    my_ticker.attach(&read, 1);

    while(1){
       wait(1);
    }
}

No this is not the problem. There is no slave attached. The problem is that the I2C clock does not start to cycle between high and low, no matter what you try to write to it.

posted by Lorenz Ruprecht 15 Feb 2017

The I2C operations will terminate when there is no ack on the transmitted data. In case of an incorrect address or a slave that is not even attached you will not see any traffic after the address.

posted by Wim Huiskamp 16 Feb 2017

I know that. At the first write the address is written an then it terminates because there is no ack but in the ticker the address is not transmitted in the first place so there is nothing a slave could acknowledge

posted by Lorenz Ruprecht 16 Feb 2017

The aborted write may not end with a stop since that was disabled. Could lead to problem with the I2C engine getting in an undefined state.

posted by Wim Huiskamp 16 Feb 2017

I just tested it and it is not the problem. you can see it on the screenshot i just added to the main post.

posted by Lorenz Ruprecht 17 Feb 2017

I did some testing on an F103 and the problem is related to the I2C read inside the Ticker. A call to read inside the mainloop works as expected. The read inside the Ticker callback fails. Bill's assumption that something is wrong with the timers is very likely. It happened somewhere in mbed lib release 133. When you select revision 132 or below it still works as it should. There is a revision note for Rev 133 referring to ''STM32 refactor lp_ticker", that could be a possible cause. This problem needs a bug report on github.

posted by Wim Huiskamp 17 Feb 2017

Thanks for helping! I just switched the mbed library back to version 132 and i will try it tomorrow and report the results. If it works i will write a bug report on github.

posted by Lorenz Ruprecht 18 Feb 2017

I just tried it, and it does not work, no matter what version of the library i use. Can you tell me more about how you got it to work?

posted by Lorenz Ruprecht 20 Feb 2017

I finally got it to work using i2c.transfer. For more details se the main post.

posted by Lorenz Ruprecht 20 Feb 2017

Ok, but even if it is working with the i2c.transfer it should also work for regular block write methods. There must be a problem in the Ticker & I2C combo. A bug report should be submitted so ST can have a look.

posted by Wim Huiskamp 01 Mar 2017
7 years, 4 months ago.

I also have very similar problem with Lorenz Ruprecht. I use Nucleo F446RE instead. Sadly, i need i2c.read instead of i2c.write. Surely, It must be reported to github.

7 years, 4 months ago.

OK this isn't a researched answer but as I understand it the "ticker" class is an interrupt "wrapper" meaning that the callback takes place inside an interrupt. I2C operations are a bit slow for use in an interrupt, and raise the possibility of some interesting "collision" events. Personally I would advise setting a flag in the ticker and polling for it in the main program. That way the I2C is timed by the ticker but called from "main".

I'm going to clarify this and add that newer versions of the I2C library may have added "locks" for RTOS compatibility, meaning they MUST NOT BE CALLED FROM INTERRUPTS. Old examples that abuse interrupts will break with newer MBED builds.

7 years, 4 months ago.

My understanding of the problem is as follows; To get the variation of speed to the I2C timing MBED use Timers NOT the SYSTICK (which is separate from the general purpose timers) as you would expect. The Interrupt also uses the same timers broken down into small bite size timers to give the seconds delay and stack loading unloading ensues, hence I2C is not Interrupt safe but you can set a flag (i.e. service the Interrupt ultra-quick as Others have suggested) and then service the flag within main() and load/unload stack once per Interrupt. Hence we observe the issue both in the latest OS2 as well as OS5.4 Mutex locks are also used in i2c.cpp which shouldn't be used/permitted within an ISR

e.g. (This is not a complete listing but hopefully you get the idea....)

bool ISR_Flag=false;
int T_Temp,T_Press;
void isr(void);
void isr2(void);
int main()
{
    printf("Reading Pressure and Temperature from BMP180 Sensor\n\r");
 
    Ticker ticktock;
 
    ticktock.attach(&isr2, 2); //NB isr2 is just a flag setting and return. Whereas &isr WILL NOT WORK does all the I2C negotiating
 
    BMP180_Initialise();
        while(1)
        {
            if (ISR_Flag==true){isr();}
            //wait(1);
            //sleep();
        }
 
}
 
void isr(void){
            T_Temp=Temperature();
            float fTemp=(float)T_Temp/10;       //For the purpose of display divide by ten into a float
                     
            T_Press=Pressure();
            float fPress=(float)T_Press/100;    //For the purpose of display divide by hundred into a float
           
            printf("Temp: %0.1f DegC   Press: %4.1f mBar\r\n",fTemp,fPress);
 
            ISR_Flag=false;
}
 
void isr2(void){
      ISR_Flag=true;
}

7 years, 4 months ago.

Maybe this is been answered already, but the I2C component is not interrupt safe.

The I2C component takes locks to make it thread-safe, which of course should not be used in an ISR.

See https://docs.mbed.com/docs/mbed-os-handbook/en/latest/concepts/thread_safety/

Note carefully the definitions of interrupt-safe and thread-safe in this document.

This then begs the question how do we do precise timing with I2C devices?

I have a design with an accelerometer that provides data update interrupts at 125 msec intervals. In the ISR I add the message to the main processing queue (or mailbox) and it gets processed at the next possible interval in the main thread. Since the accelerometer itself does not update the holding registers until the next interrupt I have 125 msec to get the job done. Data time-stamping can be done in the ISR to go along with the message in the input queue if necessary. If I can't process in the 125 msec interval that's a problem - and the device will flag me that I failed to service the data in time...that's another issue tho...

posted by Bill Bellis 01 Jul 2017

Thanks for confirming it. In my opinion some of the older pre-RTOS example code takes excessive liberties with interrupts, such as the old serial port RPC example that called a blocking string read (I think it was pc.gets) from the interrupt. Send anything not ending in a valid termination and it would hang.

posted by Oliver Broad 01 Jul 2017