How about an alternate I2C API?

I understand that supporting the Wire API is crucial since Particle tries to be Arduino compatible.

It would be nice if your Wire API was built on a more traditional embedded I2C API. I include the mbed API below since is is similar to the API provided by Keil, ChibiOS, and others vendors.

This removes the pain of 32 byte internal buffers and difficulties of dealing with complex devices that require many start, repeated start, and stop conditions.

Here is the mbed master I2C API:

I2C (PinName sda, PinName scl)
  Create an I2C Master interface, connected to the specified pins.

void frequency (int hz)
  Set the frequency of the I2C interface.

int read (int address, char *data, int length, bool repeated=false)
  Read from an I2C slave.

int read (int ack)
  Read a single byte from the I2C bus.

int write (int address, const char *data, int length, bool repeated=false)
  Write to an I2C slave.

int write (int data)
  Write single byte out on the I2C bus.

void start (void)
  Creates a start condition on the I2C bus.

void stop (void)
  Creates a stop condition on the I2C bus.

int transfer (int address, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, const event_callback_t &callback, int event=I2C_EVENT_TRANSFER_COMPLETE, bool repeated=false)
 Start non-blocking I2C transfer.

void abort_transfer ()
  Abort the on-going I2C transfer.

It is easy to wrap the more traditional I2C API with a Wire API. I have done this.

Here is the mbed I2C class with more detail.

2 Likes

I’m sure we could expose some of the lower level aspects of I2C comms using the existing Wire class. I’d hold of opening up to the mbed api since we don’t support I2C in arbitrary pins - until we have that, it would be simpler for users if we stick with the Wire API, augmented with methods for start, stop, and sending/receiving blocks of data.

mbed doesn’t support I2C on all pins, it just identifies the interface by sda/scl.

You could use a different convention in the creator. I like a creator that allows selecting an interface.

At first I found specifying the interface by pins unusual but I now like it. The problem is that a creator can’t return an error.

Using a creator is cleaner than the Arduino style pre-defined Serial, Serial1, Serial2, Serial3 when you want to write a library that works with every interface.

In general, I think Particle should provide a more modern C++ interface to devices and provide Arduino compatibility with wrappers.

Arduino was great in 2005 when it brought almost bare metal embedded programming to the DIY world. Embedded systems have come a long way since the early 1970s when we did bare metal for large systems.

Particle firmware should support modern RTOS features with a clean C++ API.

There are not a lot of good C++ API examples since most popular RTOSs like FreeRTOS, μC/OS, Keil, ChibiOS, and others are still “100% ANSI C”.

The mbed approach allows incredible flexibility in specifying an interface configuration.

A given instance of an I2C interface on STM32 can be configured to various pins so specifying pin names allows several choices for a given interface.

Here is the mbed HAL I2C pin map for a STM32F429ZI.

const PinMap PinMap_I2C_SDA[] = {
    {PB_7,  I2C_1, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C1)},
    {PB_9,  I2C_1, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C1)},
    {PB_11, I2C_2, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C2)},
    {PC_9,  I2C_3, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C3)},
    {PF_0,  I2C_2, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C2)},
    {PH_5,  I2C_2, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C2)},
    {PH_8,  I2C_3, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C3)},
    {NC,    NC,    0}
};

const PinMap PinMap_I2C_SCL[] = {
    {PA_8,  I2C_3, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C3)},
    {PB_6,  I2C_1, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C1)},
    {PB_8,  I2C_1, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C1)},
    {PB_10, I2C_2, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C2)},
    {PF_1,  I2C_2, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C2)},
    {PH_4,  I2C_2, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C2)},
    {PH_7,  I2C_3, STM_PIN_DATA(STM_MODE_AF_OD, GPIO_NOPULL, GPIO_AF4_I2C3)},
    {NC,    NC,    0}
};

I looked at Ii2c_hal.c and decided it would be difficult to allow long transfers so I wrote a new library for I2C master. I also provided a Wire like API.

I published the library as I2cMaster and it is located here.

Here is the API:

/** Initialize the I2cMaster interface.
 *
 * @param hz The bus frequency in hertz
 *
 * @returns true for success else false.
 */
bool begin(uint32_t hz = 1000000);

/** Disable the I2C interface.
 *
 * @returns true for success else false.
 */
bool end();

/** Set scl frequency.
 *
 * @param[in] hz The bus frequency in Hz.
 *
 * @returns true for success else false.
 */
bool frequency(uint32_t hz);

/** Read from an I2C slave
 *
 * @param[in] address Right justified 7-bit address.
 * @param[out] buf Buffer for read data.
 * @param[in] count Number of bytes to read.
 * @param[in] stop Generate stop if true.
 *
 * @returns true for success else false.
 */
bool read(uint8_t address, void* buf, size_t count, bool stop = true);

/** Return low level driver info.
 *
 * @returns See low level driver.
 */
int rtn() {return m_rtn;}

/** Creates a stop condition.
 *
 * @returns true for success else false.
 */
bool stop();

/** Write single byte to a selected slave.
 *
 * @param[in] data data to write out on bus
 *
 * @returns true for success else false.
 */
bool write(uint8_t data, bool stop);

/** Write to a selected slave.
 *
 * Continue after a write without a stop.
 *
 * @param[in] buf Data to send.
 * @param[in] count Number of bytes to send.
 * @param[in] stop Generate stop if true.
 *
 * @returns true for success else false.
 */  
bool write(const void* data, size_t count, bool stop);

/** Write to an I2C slave
 *
 * @param[in] address Right justified 7-bit address.
 * @param[in] buf Data to send.
 * @param[in] count Number of bytes to send.
 * @param[in] stop Generate stop if true.
 *
 * @returns true for success else false.
 */
bool write(uint8_t address, const void* buf, size_t count, bool stop = true);
1 Like

I can confirm this works for large read and writes.

Well done @whg :smile:

I just updated the I2cMaster library on GitHub and published the library on Particle build.

I removed SINGLE_THREADED_SECTION() so the library no longer blocks the scheduler like the system firmware I2C implementation.

The library also contains the Wire API so you can try it as an alternative to the standard firmware master Wire.

3 Likes