I2C implementation for STM32

This forum is dedicated to feedback, discussions about ongoing or future developments, ideas and suggestions regarding the ChibiOS projects are welcome. This forum is NOT for support.
User avatar
Badger
Posts: 346
Joined: Mon Apr 18, 2011 6:07 pm

Re: I2C implementation for STM32

Post by Badger »

I've been using the latest I2C driver and I like it! nice and simple. Managed to work out how I can issue background I2C reads by wrapping the driver up into a C++ class, where each instance has a thread which handles the reading:
i2cDevice.hpp:

Code: Select all

using namespace chibios_rt;

class i2cDevice : public EnhancedThread<128> {
private:
    uint8_t *outBuffer;
    I2CDriver *i2cp;
    uint16_t address;
    uint8_t tbuf[2];
    static i2cflags_t errors;
    static BinarySemaphore bsem_start;
    static BinarySemaphore bsem_finish;
    static uint8_t txbytes, rxbytes;
protected:
    virtual msg_t Main(void);
public:
    void write(uint8_t *data, size_t bytes);
    void write(uint8_t reg, uint8_t data);
    void startRead(uint8_t reg, size_t bytes);
    void read(uint8_t reg, size_t bytes);
    void waitForReadComplete(void);
    void set_outbuff(uint8_t *out);
    i2cDevice(I2CDriver *i2cp_in, uint16_t address_in, uint8_t *out);
    i2cDevice(I2CDriver *i2cp_in, uint16_t address_in, uint8_t *out, const char *tname);
};
 

i2cDevice.cpp

Code: Select all

#include "ch.hpp"
#include "hal.h"

#include "i2cDevice.hpp"

/* static member variables */
i2cflags_t i2cDevice::errors;
BinarySemaphore i2cDevice::bsem_start;
BinarySemaphore i2cDevice::bsem_finish;
uint8_t i2cDevice::txbytes;
uint8_t i2cDevice::rxbytes;

/* Each I2C device has a thread to issue read commands in */
msg_t i2cDevice::Main(void) {
    while(true) {
        /* wait for the semaphore to be unlocked */
        chBSemWait(&bsem_start);
        /* get access to the bus */
        i2cAcquireBus(i2cp);
        /* make the transaction */
          i2cMasterTransmit(i2cp, address, tbuf, txbytes, outBuffer, rxbytes, &errors, TIME_INFINITE);
        /* release the bus */
          i2cReleaseBus(i2cp);
        /* signal to the issuing thread that the read completed */
        chBSemSignal(&bsem_finish);
    }
}

void i2cDevice::read(uint8_t reg, size_t bytes) {
    this->startRead(reg, bytes);
    this->waitForReadComplete();
}

void i2cDevice::startRead(uint8_t reg, size_t bytes) {
    tbuf[0] = reg;
    
    if
(bytes == 1) {
        bytes = 2;
    }
    txbytes = 1;
    rxbytes = bytes;
    
    
/* release the reading thread */
    chBSemSignal(&bsem_start);
}

void i2cDevice::waitForReadComplete(void) {
    /* wait for the read to complete */
    if(!chBSemGetStateI(&bsem_finish))
        chBSemWait(&bsem_finish);
}

void i2cDevice::write(uint8_t reg, uint8_t data) {
    tbuf[0] = reg;
    tbuf[1] = data;
    write(tbuf, 2);
}
  
void i2cDevice
::write(uint8_t *data, size_t bytes) {
    i2cAcquireBus(i2cp);
      i2cMasterTransmit(i2cp, address, data, bytes, tbuf, 0, &errors, TIME_INFINITE);
      i2cReleaseBus(i2cp);
}

void i2cDevice::set_outbuff(uint8_t *out) {
    this->outBuffer = out;
}

i2cDevice::i2cDevice(I2CDriver *i2cp_in, uint16_t address_in, uint8_t *out) : 
    EnhancedThread
<128>("I2CDevice"), i2cp(i2cp_in), address(address_in), outBuffer(out) {
    chBSemInit(&bsem_start, TRUE);
    chBSemInit(&bsem_finish, TRUE);
}

i2cDevice::i2cDevice(I2CDriver *i2cp_in, uint16_t address_in, uint8_t *out, const char *tname) : 
    EnhancedThread
<128>(tname), i2cp(i2cp_in), address(address_in), outBuffer(out) {
    chBSemInit(&bsem_start, TRUE);
    chBSemInit(&bsem_finish, TRUE);
}
 

An example usage of the class for an ITG3200:

Code: Select all

#include "i2c/i2cDevice.hpp"
#include "i2c/itg3200.hpp"

int16_t *ITG3200::read() {
    i2cDevice::read(ITG3200_GYRO_XOUT_H, 6);
    return getData();
}

void ITG3200::startRead() {
    i2cDevice::startRead(ITG3200_GYRO_XOUT_H, 6);
}

int16_t *ITG3200::getData(void) {
    waitForReadComplete();
    uint8_t *u8mTmp = (uint8_t *)data;
    u8mTmp[0] =  rx[1]; u8mTmp[1] =  rx[0];
    u8mTmp[2] =  rx[3]; u8mTmp[3] =  rx[2];
    u8mTmp[4] =  rx[5]; u8mTmp[5] =  rx[4];
    return data;
}

void ITG3200::reset(void) {
    write(ITG3200_PWR_MGM, ITG3200_RESET);
}

void ITG3200::init(void) {
    reset();
        
    tx
[0] = ITG3200_SMPLRT_DIV;
    // Sampling frequency: 8khz / (7+1) => 1khz
    tx[1] = 7;
    /*
     * DLPF register:
     *     - FS_SEL = 0x03 (2000deg/s)
     *     - DLPF_CFG = 0 (8khz internal sampling)
     */
    tx[2] = (0x03 << 3);
    /*
     * Interrupt Configuration
     * - off for now
     */
    tx[3] = 0;
    
    write
(tx, 4);

    /* select the PLL source */
    tx[0] = ITG3200_PWR_MGM;
    tx[1] = 0x01;
    
    write
(tx, 2);
}

ITG3200::ITG3200(I2CDriver *i2cp) : i2cDevice(i2cp, ITG3200_ADDR, rx, "ITG3200") {
    //init();
} 


The advantage of this class is that it makes it very easy to issue read commands for several different devices on different I2C buses simultaneously, and not worry about the order they are issued in or waiting for one to complete before doing the next etc:

Code: Select all

    /* issue read commands */
        gyro.startRead();
        acc.startRead();
        mag.startRead();
        
        g 
= gyro.getData();
        data.gyro[0] = g[0]; data.gyro[1] = g[1]; data.gyro[2] = g[2];
        a = acc.getData();
        data.acc[0] = a[0];  data.acc[1] = a[1];  data.acc[2] = a[2];
        m = mag.read();
        data.mag[0] = m[0];  data.mag[1] = m[1];  data.mag[2] = m[2]; 


Is using binary semaphores in the way I have a good idea? Basically the idea is to have a thread for each sensor which is sleeping until the main thread decides to wake it. The thread then goes off and does some stuff and notifies the main thread that the I2C process is complete, at which point the main thread may or may not be sleeping waiting for a binary semaphore signal.
User avatar
barthess
Posts: 861
Joined: Wed Dec 08, 2010 7:55 pm
Been thanked: 7 times

Re: I2C implementation for STM32

Post by barthess »

Hi Badger,
Using semaphores for synchronization of different threads is good idea because semaphores designed for that. Just add timeout possibility to "supervisor" thread to increase robustness of the whole system.
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: I2C implementation for STM32

Post by Giovanni »

I am glad the I2C driver is getting better.

Good work with the C++ classes, those are very useful examples, I hope to improve the old C++ kernel wrapper before stable release.

Giovanni
marobi
Posts: 3
Joined: Tue Dec 27, 2011 2:20 pm

Re: I2C implementation for STM32

Post by marobi »

Hi, this is my first post here.

I am having 2 challenges with the current I2C driver for the STM32F4.

1) There is a busy waiting in i2c_lld_wait_bus_free. And that's exacty the place where things get stuck.

2) When running SD2 and I2CD1 over a longer period of time, the I2C bus gets stuck (Error 2). Reason unknown, but if I do not use SD2 output all is stable.

I am using one of the latest trunk (3645) and playing with the DROTEK-board.
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: I2C implementation for STM32

Post by Giovanni »

Hi,

Are you running with debug options activated in chconf.h? if not please activate them and verify if some error is trapped.

Giovanni
User avatar
barthess
Posts: 861
Joined: Wed Dec 08, 2010 7:55 pm
Been thanked: 7 times

Re: I2C implementation for STM32

Post by barthess »

In addition to Giovanni's words:
marobi wrote:1) There is a busy waiting in i2c_lld_wait_bus_free. And that's exacty the place where things get stuck.

means that one SDC or SCL line pulled down by something (probably stacked slave).
1. Try to slow down I2C clock.
2. Are you access to bus from different threads without i2cAcquireBus()?
3. What pull up resistors used in I2C lines?
marobi
Posts: 3
Joined: Tue Dec 27, 2011 2:20 pm

Re: I2C implementation for STM32

Post by marobi »

Thanx for all the responses.

I updated to latest trunk (somethings with FPU support/ context switching) issues on STM32F4. This solved my stability problem.

Pullups are already on the STM32F4discovery board, 4K7 I believe.

I use the i2cAcuireBus-call. Only one thread uses the interface.

So in principle I am very happy. I can read all 4 I2C devices on the Drotek-board. Going on with scaling and integrating.

But I still don't like the busy-waiting in the driver.

Rien

BTW I build with Atollic/Windows
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: I2C implementation for STM32

Post by Giovanni »

Hi Rien,

The busy waiting in the driver is there because a limitation of the STM32 I2C hardware, it does not allow to have an interrupt after a STOP has been sent, so you have to poll for that. It is not like we didn't try to remove it.

The workaround is to insert a small delay before calling a I2C function, for example 2mS (this guarantees a delay between 1 and 2 milliseconds), this makes sure that the polled part is skipped immediately.

Giovanni
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: I2C implementation for STM32

Post by Giovanni »

HI,

We are starting a refinement of the I2C driver so starting from revision 3684 the driver may not compile or be unstable.

Giovanni
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: I2C implementation for STM32

Post by Giovanni »

Hi,

Barthess, I committed the following changes to the high level I2C driver:

1) Added function i2cGetError() in order to pass one less parameter to I/O functions.
2) Added a suffix "Timeout" to function names in order to follow the style of other drivers.
3) Added a type i2xaddr_t to be defined in the low level, some implementations may want the 10 bits addressing and would define that as a 16 bits value.
4) Modified the return meaning of I/O functions, RDY_RESET means that there has been an error that can be retrieved using i2cGetError().
5) Removed the busy waiting from the high level, it is an STM32-specific thing, should go in the low level.
6) Removed the handling of thread waiting from the high level, it should go in the low level because in some architectures we may need an entirely polled driver and the thread would not go to sleep at all.
7) The driver state now is handled entirely in the high level, the low level does not need to do transitions.
8) The I/O functions now call the low level within a critical zone, the low level can eventually release it and lock again before returning. This ensures atomicity of state checks and transitions.

About the low level implementation, we should make sure it always has an escape way under timeout, the driver should never lock inside.

Let me know what you think about the above changes.

It is on revision 3684.

Giovanni
Post Reply