I'm starting to work on a basic UGen template and guide for reading/writing to I2C sensors from SuperCollider UGens.

The Trill_SC UGens (https://github.com/jreus/Trill_SC) by @jonathan are quite a good start.

I was wondering whether there are any other examples (besides the Trill library for Bela) for reading I2C sensors from Bela (in C++) that could serve as a template for the access to the sensors themselves (so interfacing from C++ to the I2C device).

I'm guessing a template for reading/writing to I2C devices would be useful for C++ Bela code writing as well.

I'm imagining ending up with a guide that would explain how to move from an available Arduino library to interface with an I2C sensor to an implementation for Bela for that same sensor.

Any suggestions?

    nescivi Trill library for Bela

    That's probably the best example we have. There is also one for MPR121 and a port of an Arduino library for an IMU integrated with libpd on Bela. Another I2C application is Bela's audio codec driver.

    Ultimately, there is nothing Bela-specific to how I2C works on Bela: it just leverages the Linux driver which exposes the I2C bus in /dev/i2c*, does one ioctl() to set the device address, then calls read()/write() on it. As long as that all happens in a dedicated thread (doesn't have to be a Bela AuxiliaryTask*), this is completely generic Linux. What could become useful on RT Linux systems (e.g.: Bela) is a way of safely exchanging data between an I/O thread and a RT thread (see below).

    A thing with I2C is that not all devices behave the same in terms of how to interact with them. The I2C protocol provides the transport, addressing and arbitration, but it does not specify what the bytes mean. Many devices (e.g.: Bela audio codec), for instance, expect that the first byte written by the host in each transaction determines the memory location to which the successive bytes in the same transmission will be read from / written to. Some others (e.g.: Trill) do not work that way: you first do a transaction where you write once to give it the memory address you are interested in, then you keep reading from the device without writing the address any more. The first behaviour is the one the readRegister() and writeRegister() functions in I2c_Codec.cpp rely upon and it's probably common enough that it would be worth having it as a library function.

    I made an attempt a few years ago at exposing decently complete I2C functionalities that would be accessible from a RT thread. This used two lock-free ringbuffers to communicate between the RT thread and another thread which would actually do the processing (again, all of this was Bela-independent). I also integrated into Bela's libpd so that one could use Pd to control an I2C device in a RT-safe way. See here (recently rebased): https://github.com/BelaPlatform/Bela/tree/better-i2c-rebased-2020


    * a Bela AuxiliaryTask is just a wrapper for a Xenomai wrapper for a pthread which gets managed internally so that:
    - you don't have to destroy/join it
    - the callback runs in a loop which waits on a condition variable signalled from the audio thread
    The convenience of it is that it can be scheduled from the audio thread with minimal overhead in a RT-safe way and it can be assigned a "Xenomai" priority which is powerful for keeping DSP co-routines (e.g.: FFT processing) at high priority. However, in the case where it is used for I/O, given how you are going through the Linux driver, its priority becomes much less relevant, in that it will still have to revert to the Linux scheduler in order to access the driver. In many cases, having a std::thread with a usleep in there would yield a very similar behaviour, while being more portable.

    a year later

    Just writing here to support nescivi 's idea. It is especially important for those teaching with Bela (like me). By the moment I have collected some typical I2C examples. If you need any help/test let us know how to contribute!