Understanding non-blocking I2C-lib nI2C from nitacku

Hi everybody,

inspired by a possibly I2C-problem of a user I came across this non-blocking I2C-library from Github-user nitacku

I want to test this library but I do not yet understand the documentation and the example-codes

I started to analyse the demo-example

I added questions and comments to the demo-code

/*
   Copyright (c) 2018 PhotonicFusion LLC

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:
   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
   IN THE SOFTWARE.

   @file        Nonblocking_Read_RTC.ino
   @summary     Non-blocking read example using the PCF2129 RTC device
   @version     1.0
   @author      nitacku
   @data        16 August 2018
*/

#include <inttypes.h>
#include <nI2C.h>

enum address_i2c_t : uint8_t
{
  ADDRESS_I2C = 0x51, // Address of I2C device
};

// Device register addresses for PCF2129 RTC
enum address_t : uint8_t
{
  ADDRESS_CONTROL_1       = 0x00,
  ADDRESS_CONTROL_2       = 0x01,
  ADDRESS_CONTROL_3       = 0x02,
  ADDRESS_TIME            = 0x03,
  ADDRESS_DATE            = 0x06,
  ADDRESS_ALARM           = 0x0A,
  ADDRESS_CLOCKOUT        = 0x0F,
  ADDRESS_TIMESTAMP       = 0x12,
};

// Global handle to I2C device
CI2C::Handle g_i2c_handle;

// Global buffer for received I2C data
// Could be allocated locally as long as the buffer remains in scope for the
// duration of the I2C transfer. Easiest guarantee is to simply make global.

// my questions:
// what determines the size of the buffer-array?
// what will happen if the buffersize is too small?
uint8_t g_rx_buffer[7];

// Callback function prototype
void RxCallback(const uint8_t error);


void setup(){
  // Register device with address size of 1 byte using Fast communication (400KHz)

  // my question:
  // what does the  "->" mean???
  g_i2c_handle = nI2C->RegisterDevice(ADDRESS_I2C, 1, CI2C::Speed::FAST);

  // Initialize Serial communication
  Serial.begin(115200);
}


void loop(){
  // Perform non-blocking I2C read
  // RxCallback() will be invoked upon completion of I2C transfer

  // my questions:
  // does this line of code do the following:
  // do a read-operation on device registered with g_i2c_handle
  // read register specified in ADDRESS_TIME
  // use uint8_t array named "g_rx_buffer" to store the bytes sended by the I2C-slave 
  // read/receive the number of bytes specified by sizeof(g_rx_buffer)
  // if receiving has finished call   callbackfunction named "RxCallback"
  uint8_t status = nI2C->Read(g_i2c_handle, ADDRESS_TIME, g_rx_buffer, sizeof(g_rx_buffer), RxCallback);

  // some more questions:
  // what does happen if the adress to read from is wrong?
  if (status){
    /*
      Status values are as follows:
      0:success
      1:busy
      2:timeout
      3:data too long to fit in transmit buffer
      4:memory allocation failure
      5:attempted illegal transition of state
      6:received NACK on transmit of address
      7:received NACK on transmit of data
      8:illegal start or stop condition on bus
      9:lost bus arbitration to other master
    */

    Serial.print("Communication Status #: ");
    Serial.println(status);
  }

  // Wait 1 second before transmitting again
  delay(1000);
}

// Convert BCD numbers to human-readable format
uint8_t BCD_to_DEC(const uint8_t b){
  return (b - (6 * (b >> 4)));
}


void RxCallback(const uint8_t status) {
  // Check that no errors occurred
  if (status == 0){
    uint8_t second = BCD_to_DEC(g_rx_buffer[0]);
    uint8_t minute = BCD_to_DEC(g_rx_buffer[1]);
    uint8_t hour   = BCD_to_DEC(g_rx_buffer[2]);

    char time[15];
    const char* format_string = PSTR("time: %02u:%02u:%02u");
    snprintf_P(time, 15, format_string, hour, minute, second);

    Serial.println(time);
  }
  else{
    /*
      Status values are as follows:
      0:success
      1:busy
      2:timeout
      3:data too long to fit in transmit buffer
      4:memory allocation failure
      5:attempted illegal transition of state
      6:received NACK on transmit of address
      7:received NACK on transmit of data
      8:illegal start or stop condition on bus
      9:lost bus arbitration to other master
    */

    Serial.print("Communication Status #: ");
    Serial.println(status);
  }
}

last but not least: does anybody have experience with this library?

best regards Stefan

"->" is the syntax for accessing a class / struct member via a pointer. If you look in ' nI2C.h', you see that nI2C is declared a pointer to an object of class CI2C:

extern CI2C* nI2C;

It's instantiated in 'nI2C.cpp'

// Assign global object pointer
CI2C* nI2C = &CI2C::GetInstance();

Hi gfvalvo,

thank you for answering. So if I try to explain in my own words:

the name of the class is "CI2C" (capital letters)
like declared in the file nI2C.h

// Using singleton design pattern
class CI2C
{
    public:
    
    enum status_t : uint8_t
    {
        STATUS_OK = 0,
        STATUS_BUSY,
...

"nI2C" is the name of the object (the instance)

inside the file nI2C.h at the bottom there is the code

extern CI2C* nI2C;

Does this mean: the "object-instance"-name that is used to access the functions inside the class with name "CI2C" is "nI2C"

as the whole library is named "nI2C.h" (the exact same letters )
is it a must that this name for the object-instance has to be the same = nI2C ?

or could I code
extern CI2C* my_nI2C_object;

?

There is this ""-symbol. This "" declares it as pointer? instead of what?
I mean what would it be if it would be coded

extern CI2C nI2C; // no "*"

As it is coded with "*"-symbol

is it a must to then code

g_i2c_handle = nI2C->RegisterDevice(

= it is not possible to code

g_i2c_handle = nI2C.RegisterDevice(

which would be the way I'm used to

or as an alternative is it possible to use

g_i2c_handle = nI2C::RegisterDevice(ADDRESS_I2C, 1, CI2C::Speed::FAST);

just the same way as with

CI2C::Speed

?

best regards Stefan

No, it's the name of a pointer to an instance.

As above, access to the class's public variables and functions is through the pointer named "nI2C".

No.

That would be an instance (or object) of the CI2C class. However, you could not do that in this case as the author has written the class as a Singleton.

That's not the correct syntax when accessing a struct / class member via a pointer. You could dereference the pointer first and then use the "dot" syntax:

g_i2c_handle = (*nI2C).RegisterDevice();

However, nobody does it that way.

Again, not with a pointer.

'Speed' is an enum class defined within the CI2C class. That's why it's accessed with a scope specifer.

Hi gfvalvo,

thank you very much for your explanations and the link to the explaining of the singleton. The fog starts to disappear.

There are two pictures explaining what a singleton is that made me laugh

best regards Stefan

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.