Using I2C functions inside of Timer/Thread callbacks does not work within a class

I am writing an I2C library for a sensor. The sensor requires the data to be read out in 4 blocks and then processed every 100ms.

I need a non blocking implementation so i’m planning to call an update function that checks whether a block is complete (by reading a register on the sensor) and then either returns if not, starts the next block if the previous one has finished or processes the data if all 4 blocks have been read out.

What would be the best way to call my update function? software timer, hardware timer, thread or another method?

@joe4465, how fast do blocks become available and what is the interval between blocks?

the sensor refresh rate is variable from 2 - 60 Hz, set at 9Hz by default, at that speed each block will be available 27ms after requesting it.

@joe4465, so if I understand correctly, you want to request 4 blocks in a row (total of 108ms) then processed for less than 3ms (for total of 111ms or 9Hz) and the cycle starts over. To do this, you could use a 1ms software timer with a finite state machine in it to trigger at the different 27ms intervals to acquire the data as long as the acquire takes less than 1ms (time between timer callbacks). The final state would be processing which needs to either take less than 1ms OR you set a flag for loop() to process the data though timing might be off. You could double-buffer the acquired data so the timer fills one buffer, set a flag and lets loop() do the processing while the timer switches to the second buffer and so on. Using the double buffer approach gives loop() 108ms to process a buffer of data. :smile:

Yeah I would have to double buffer as it takes around 60ms to process the data.

I guess I’m just not sure what kind of timer to use as it needs to:

  • Trigger fairly accurately - within 1ms is probably OK
  • Allow me to read and write registers over I2C within the callback
  • Be resistant to double triggering, i.e. 1ms timer triggers and then triggers again whilst I’m in the original callback

Should I be asking the sensor for its state every 1ms knowing that I will get 26 replies back before the block is ready or will this sap resources? Alternatively I could ask every 3ms and expect 8 replies back before the block is ready, or every 10ms but this would mean I would waste 3ms where the block is ready but I have not requested the data.

Thanks

Using I2C within a software timer callback within a class does not seem to work. I can reproduce the issue with the following stripped back code.

program.ino

#include "application.h"
#include "HTPA32x32.h"

HTPA32x32 _HTPA32x32;

void setup()
{
    Serial.begin(9600);

    delay(3000);

    _HTPA32x32.begin();
}

void loop()
{
}

HTPA32x32.cpp

#ifndef __HTPA_32X32_CPP_
#define __HTPA_32X32_CPP_

#include "HTPA32x32.h"

HTPA32x32::HTPA32x32() {}

HTPA32x32::~HTPA32x32() {}

bool HTPA32x32::begin()
{
    Serial.printf("Started HTPA32x32\r\n");

    Wire.end();
    Wire.setSpeed(CLOCK_SPEED_400KHZ);
    Wire.begin();

    Serial.printf("Test\r\n");
    writeSensorByte(0x01, (0 << 4) | 0x09); //<--- prints 0 (success)

    _updateTimer = new Timer(1000, (void(*)())&HTPA32x32::update);
    _updateTimer->start();

    return true;
}

void HTPA32x32::update()
{
    Serial.printf("Update\r\n");
    writeSensorByte(0x01, (0 << 4) | 0x09); //<--- prints 3 (end of address transmission timeout)

    return;
}

bool HTPA32x32::writeSensorByte(int command, char data)
{
    Serial.printf("Write sensor byte. command: %d, data: %d\r\n", command, data);
    Wire.beginTransmission(SENSOR);
    Wire.write(command);
    Wire.write(data);
    Serial.printf("Success %d\r\n", Wire.endTransmission());
    return true;
}

#endif

HTPA32x32.h

#ifndef __HTPA_32X32_H_
#define __HTPA_32X32_H_

#include "application.h"

class HTPA32x32
{
  public:
    HTPA32x32();
    virtual ~HTPA32x32();

    bool begin();

  private:
    void update();
    bool writeSensorByte(int command, char data);

    const int SENSOR = 0x1A;

    Timer* _updateTimer;
};

#endif

Ok so the below code works (not in a class). The issue is something to do with using I2C in a software timer callback in a class.

#include "application.h"

const int SENSOR = 0x1A;

Timer timer(1000, update);

void setup()
{
    Wire.end();
    Wire.setSpeed(CLOCK_SPEED_400KHZ);
    Wire.begin();

    delay(3000);

    Serial.printf("Test\r\n");
    writeSensorByte(0x01, (0 << 4) | 0x09); //<--- prints 0 (success)

    timer.start();
}

void loop()
{
}

void update()
{
    Serial.printf("Update\r\n");
    writeSensorByte(0x01, (0 << 4) | 0x09); //<--- prints 0 (success)
}

bool writeSensorByte(int command, char data)
{
    Serial.printf("Write sensor byte. command: %d, data: %d\r\n", command, data);
    Wire.beginTransmission(SENSOR);
    Wire.write(command);
    Wire.write(data);
    Serial.printf("Success: %d\r\n", Wire.endTransmission());
    return true;
}

Any ideas?

Also does not work when using a thread to call the update function.

HTPA32x32.cpp

#ifndef __HTPA_32X32_CPP_
#define __HTPA_32X32_CPP_

#include "HTPA32x32.h"

HTPA32x32::HTPA32x32() {}

HTPA32x32::~HTPA32x32() {}

bool HTPA32x32::begin()
{
    Serial.printf("Started HTPA32x32\r\n");

    Wire.end();
    Wire.setSpeed(CLOCK_SPEED_400KHZ);
    Wire.begin();

    Serial.printf("Test\r\n");
    writeSensorByte(0x01, (0 << 4) | 0x09); //<--- prints 0 (success)

    _updateThread = new Thread("updateThread", (void(*)())&HTPA32x32::update, OS_THREAD_PRIORITY_DEFAULT);

    return true;
}

void HTPA32x32::update()
{
    while(true)
    {
        Serial.printf("Update\r\n");
        writeSensorByte(0x01, (0 << 4) | 0x09); //<--- prints 3 (end of address transmission timeout)
        delay(1000);
    }
}

bool HTPA32x32::writeSensorByte(int command, char data)
{
    Serial.printf("Write sensor byte. command: %d, data: %d\r\n", command, data);
    Wire.beginTransmission(SENSOR);
    Wire.write(command);
    Wire.write(data);
    Serial.printf("Success %d\r\n", Wire.endTransmission());
    return true;
}

#endif

HTPA32x32.h

#ifndef __HTPA_32X32_H_
#define __HTPA_32X32_H_

#include "application.h"

class HTPA32x32
{
  public:
    HTPA32x32();
    virtual ~HTPA32x32();

    bool begin();

  private:
    void update();
    bool writeSensorByte(int command, char data);

    const int SENSOR = 0x1A;

    Thread* _updateThread;
};

#endif

I am at a loss of what to try next - maybe the SparkIntervalTimer but I think it will be the same.

I can’t really call the update function using millis() from my main loop as the code needed to process the full set of blocks takes around 60ms to finish and is blocking.

Should I2C calls work within call backs or is this a known problem?

Could it be something to with how I attach the function callback to the timer (void(*)())&HTPA32x32::update?

I would really appreciate any advice, ideas or pointers to help me get this resolved.

Thanks

_updateTimer = new Timer(1000, (void(*)())&HTPA32x32::update);

This isn’t going to work as you expect, unless you make the HTPA32x32::update method static. As it is, the real signature of that function is

void HTPA32x32__update(HTPA32x32* const _this);

But you’re casting it to function that takes no arguments, so the this pointer will be random, rather than a pointer to the object instance.

I will add support for class function callbacks in the 0.4.9 release next week. In the meantime, you can subclass Timer to include a pointer to your HTPA32x32 instance:

class MyTimer : public Timer
{
   HTPA32x32* target;

public:
   MyTimer(unsigned period, HTPA32x32* target) : Timer(period, timer_callback_fn(nullptr)) {
       this->target = target;
   }

   virtual void timeout() override {
       target->update();
   }
}

That should give you what you’re looking for.

2 Likes

OK, thank you for your help.

I will give this a try and report back.

@mdma the code you posted caused my Photon to lock up when the timer is started.

Is the support for class function callbacks included in the 0.4.9 release coming out on Monday? I am happy to wait for that if that’s the case.