A Generic I2C High Level Driver

I’m working with the I2C bus recently a lot. I’m using it in a project to reverse-engineering skimming (credit card fraud) devices. I needed to improve one of my applications for the lecture classes where a MCF52259 is communicating with a TWR-LCD display over I2C. And I want to add RTC (Real-Time-Clock) capabilities to my Arduino Data Logger Shield which requires I2C.

The same time I want to have things working with ARM Cortex-M4 and M0+ devices. And here the challenge started: using the I2C_LDD (Logical Device Driver) Processor Expert components for the ARM Kinetis devices is definitely not simple and easy. I want to use my software compatible for both the ARM cores and say for S08 and ColdFire cores. So what I ended up is to write a ‘generic’ I2C driver on top of the low level Processor Expert components: named GenericI2C.

Generic I2C Component

Generic I2C Component

The component works both with ‘classic’ I2C hardware and software components, and as well with the I2C_LDD (e.g. for Kinetis and ARM cores). For this, the property has LDD and non-LDD settings:

GenericI2C Properties

GenericI2C Properties

The component features an optional RTOS interface: that way access to the bus is protected by the RTOS semaphore (if configured as such).

‘Memory’ Access

The driver implements a common way to get access to ‘memory’ over I2C. This is a common method used for many I2C EEPROM devices, or the Maxim Real-Time Clock devices (e.g. DS3232 or DS1307). That DS1307 is well on my Arduino Data Logger Shield. The component allows to use 1, 2, 3 or 4 byte addresses (address sizes), and makes reading and writing such devices really easy.

Below are example read and write routines I used in an I2CSpy project:

static uint8_t ReadReg(I2CSPY_TDataState *device, uint32_t addr, uint8_t *data, word dataSize, const FSSH1_StdIOType *io) {
  uint8_t res = ERR_OK;
  uint8_t addrBuf[4];

  addrBuf[3] = (uint8_t)(addr&0xff);
  addrBuf[2] = (uint8_t)((addr&0xff00)>>8);
  addrBuf[1] = (uint8_t)((addr&0xff0000)>>16);
  addrBuf[0] = (uint8_t)((addr&0xff000000)>>24);
  res = GI2C1_ReadAddress(device->deviceAddr, &addrBuf[4-device->addrSize], device->addrSize, data, dataSize);
  if (res!=ERR_OK && io!=NULL) {
    FSSH1_SendStr((unsigned char*)"*** error/timeout while waiting for device.\r\n", io->stdErr);
  }
  return res;
}

static uint8_t WriteReg(I2CSPY_TDataState *device, uint32_t addr, uint8_t *data, uint8_t dataSize, const FSSH1_StdIOType *io) {
  uint8_t addrBuf[4], res;

  addrBuf[3] = (uint8_t)(addr&0xff);
  addrBuf[2] = (uint8_t)((addr&0xff00)>>8);
  addrBuf[1] = (uint8_t)((addr&0xff0000)>>16);
  addrBuf[0] = (uint8_t)((addr&0xff000000)>>24);
  res = GI2C1_WriteAddress(device->deviceAddr, &addrBuf[4-device->addrSize], device->addrSize, data, dataSize);
  if (res!=ERR_OK && io!=NULL) {
    FSSH1_SendStr((unsigned char*)"*** error/timeout while waiting for device.\r\n", io->stdErr);
  }
  return res;
}

The above code reads/writes to different devices on the I2C bus, with different address sizes. That I2CSpy project is very cool, that I might feature it in another blog post :-).

Or here is another usage example, which writes multiple values to the bus (I2C_LCD_ADDR is the I2C device address), I2C_LCD_ADDR_ACCEL_X is the start address of the accelerometer values in the device:

void I2C_SendAccelerometerValues(void) {
  int16_t xyz[3];
  uint8_t addr = I2C_LCD_ADDR_ACCEL_X;

  ACCEL_GetValues(&xyz[0], &xyz[1], &xyz[2]);
  (void)GI2C1_WriteAddress(I2C_LCD_ADDR, &addr, 1, (unsigned char*)&xyz, sizeof(xyz));
}

Limitations

Right now the component only supports the Master mode. For hardware I2C Processor Expert components, the low level drivers need to use interrupts. But it works as well with the software bit-banging I2C driver (GenericSWI2C). And as always: without warranty :-).

Software

The component is available for download from this link: GenericI2C.

Happy I2Cing 🙂

59 thoughts on “A Generic I2C High Level Driver

  1. I will mention a problem I found on our system yesterday because it is something that is hard to find related to our i2c driver. I wrote an i2c driver based on the eeprom example in the MQX distribution. It was recommended to us by MQX team and FSL support to use the polled driver. This driver works under light interrupt activity, however under heavy system interrupt load it can hang. Unless the i2c driver traps for ACK’s, it is possible to miss an ACK and the code will just hang, killing the entire app. MQX has not yet implemented Queued IO timeouts so without a lot of coding to handle restarting the driver, it can be problematic to use a polled driver. The drivers I have used with PE in the past have been interrupt driven, and the driver we had a problem with was the MQX driver, this may not be an issue in freeRTOS or the i2c driver you have. As I say I just mention it because I found this problem hard to track down.

    Like

    • Yes, I have seen that problem with polled drivers too. I just have fixed yesterday a similar problem with the bit banging driver (which is not using interrupts) too: it was not dealing correctly with the repeated start condition.

      Like

  2. This web page is just not handling programming symbols correctly .

    the ‘>>’ is reproduced as ‘& gt ; & gt ;’ (without the spaces).

    This is very confusing at first glance.

    Like

  3. Pingback: KL25Z and I2C: Missing Repeated Start Condition | MCU on Eclipse

  4. Pingback: Processor Expert Maxim I2C RTC Driver the Arduino Data Logger Shield | MCU on Eclipse

  5. the bitbang component does not work if you select the actual I2C pins on the CFV1. (the GetRawVal is not supported on that pin) Since the I2C component does not exist in CW6.3 (stuck in the stone age here), I’m sorta stuck.

    Bob

    Like

  6. I think its straightened out. I deleted all the related components (24AA, I2C, and Wait) updated them, and added them to the project. The error is gone now. I haven’t tested it. My EEPROM has a really small page size, how does that impact the component?

    Thanks!!!

    Bob

    Like

  7. Pingback: Tutorial: Creating a Processor Expert Component for an Accelerometer | MCU on Eclipse

  8. Pingback: Extended Driver for the MMA8451Q Accelerometer | MCU on Eclipse

  9. Hi !!! I’m working on interfacing a TI’s potensiostat using I2C with MCF51JM128VLD, and wanted to try “GenericI2C” on Codewarrior 6.3. I’ve updated the package, but I cannot see the bean on the bean inspector… any ideas ? Thank you !!! Wonderful Site !!!

    Like

  10. Hey Eric,

    I’m trying to use I2CSpy but the “scan” command is not correctly identifying the fact that a device is found at a certain address. Instead every address simply says timeout. I can see the device ACK’s it’s address on a scope. Is this a bug? How can I get it to accurately identify present addresses on the bus?

    I also found that in order for timeout to work for the GenericI2C component, I had to include my own TimerInt component and call it’s AddTick function from the TI1_OnInterrupt handler or the GenericI2C component would hang forever waiting for a timeout that was never going to expire.

    Thanks,
    Vic

    Like

    • I’ve traced it to the following res = ERR_FAILED being reached…

      do { /* Wait until data is sent */
      isTimeout = TMOUT1_CounterExpired(timeout);
      if (isTimeout) {
      break; /* break while() */
      }
      } while (!GI2C1_deviceData.dataTransmittedFlg);
      TMOUT1_LeaveCounter(timeout);
      if (isTimeout) {
      res = ERR_FAILED;
      break; /* break for(;;) */
      }

      I don’t understand why that code is being reached as my scope doesn’t show an ACK being missed, and I have my Timeout set to 500ms. Is the Timeout not being reset correctly? I seem to correctly detect the response to the general call address in the ‘scan’ command, but not at the real address.

      Like

      • or perhaps I’m not using the Timer correctly? Here is my Timer implementation:

        void TI1_OnInterrupt(void)
        {
        /* Write your code here … */
        TMOUT1_AddTick();
        }

        Does calling AddTick need to be guarded in some way here?

        Like

  11. If I disable timeout in the component and restrict the range of the ScanDevices function to just my device it correctly reports “Device responded!” as well…

    Like

    • ignore this comment, I deceived myself by regenerating PE code, what I was seeing was in fact a response to general call.

      Like

      • Hi Victor,
        just in summmary: yes, if you are using the Timeout component, you need to call the TMOUT1_AddTick() either from your own timer or e.g. from an RTOS tick counter, with the specifed frequency in the Timeout component properties (e.g. every 10 ms).
        Going through the history of your comments, I’m not sure if you have resolved everything now?

        Like

      • Erich,

        No I haven’t resolved it. I only get a response to Address 0x00 from my sensor using the I2CSpy scan function. Nothing at it’s real I2C address, 0x5A. Any ideas on what I could be doing wrong?

        Like

  12. suggestion: move this define:
    #define I2Cbus_WRITE_BUFFER_SIZE NN

    to the .h file from the .c file.

    I just ran into this – trying to load a big pile of bytes (actually, code into a SigmaDSP) with I2Cbus_WriteAddress(), and it bombed out because the data size was too big. So I have to split it up, but to do this I need to know the buffer size.

    Also, I wonder if it would be possible for this component to also instantiate the I2C_Init component, since it allows setting up tweaky stuff like the glitch filter, which the I2C_LDD component doesn’t access, or would that create a conflict?

    Like

  13. Hello Erich,

    Do you still have the “Freedom_Accel” example code revision before you updated it with the Processor Expert component for the magnetometer? I mean the version where you introduced the GenericI2C component and nothing else.

    I really appreciate all the improvements on the “Freedom Accel” project. I can see myself using all of these improvements in future projects. But now I find it confusing to try to figure out how to talk to any “generic” I2C-capable sensor without an RTOS, shell, I2C Spy logger and all that good stuff. Like you have said before in your “printf” and KL25Z tutorial: sometimes its better to start with less to give people a strong understanding of the basic concept. I’ve been jumping a lot between all your I2C tutorials and example code to figure it out myself but I’ve been stuck for several days now. I have to admit that this is beyond my current level of experience.

    I figured out that I mainly need to call I2Cx_WriteAddress and I2Cx_ReadAddress but I don’t know where (which files and at which point in the program flow) to call the Init and Deinit methods or if I need to call any other methods at all besides the write and read methods.

    Thank you and Ich wünsch Dir e schöne Daag!

    Like

  14. Is it possible to change this component so that on the Kinetis you can select either the non-LDD (bit-bang) I2C or the I2C_LDD? Currently it doesn’t allow this. (why? you might ask… well, I’m chasing a bug, and right now the prime suspect is the I2C peripheral in the Kinetis – being able to swap in the other driver would make tracking this down much easier)

    Like

    • Hi Marc,
      yes, technically this is doable, so this would be something else on my growing list for the week-end ;-).
      I used the bit-banging version of the I2C drivers on S08 and ColdFire, but I have to admit that I faced some timing issues there. But I can give it a try. But again: it would have to wait for the week-end.

      Like

        • Yes, master only, and same code as GenericSWI2C component.
          I have seen some timing issues talking that code with an S08 in I2C slave mode. The GenericSWI2C worked fine for me with I2C devices like EEPROM or RTC.

          Like

      • OK I hacked GenericI2C.chg and was able to select the non-LDD, then instantiated a GenericSWI2C and selected it as the driver and voila! works.

        I’m using this with FreeRTOS though and I ran into a situation where I got a NACK from the slave, and the component then tries N times to recapture the bus or some such (N defaults to 256, tried 16 also)… and when that happens I get broken at PE_ISR(Cpu_Interrupt), with the next thing up in the stack as vPortStartFirstTask()… so I wonder if all those delays that don’t allow FreeRTOS to get control lead it to crash?

        Like

  15. Pingback: Bit-Banging I2C with ResetBus() Functionality | MCU on Eclipse

  16. Hi Erich,

    I’ve been trying to use specific components i downloaded off of git hub with the processor expert in KDS, but it keeps giving me the error: “This component is not supported in kinetis sdk project mode”. I really want to use the components that i have downloaded off github, I have even tried deselecting sdk when i create my project. but then it will not let me select processor expert which puts me back at square one. If there is anything you could do to help that would be greatly appreciated.

    Like

    • Which processor are you using? Indeed, there is a huge problem with the Kinetis SDK that it does *not* support the previous LDD (Logical Device Drivers) and ‘high level beans’. I have made some of my components (FreeRTOS, Wait, Trigger, …) compatible with the Kinetis SDK. But as soon as my components use LDD components, then Freescale does not support this any more 😦

      Like

      • I am using the TWR-K22f, which does not work with all the components, specifically the FXOS8700CQ which i am trying to work with. I did however manage to get my FRDM K64F board to work with all your components, so thats great. Now i have another question though, I am trying to write a program using the accelerometer with some leds, like you do in a few of your tutorials, but i don’t seem to know how to use the accelerometer, specifically, trying to get x,y,z values so that i can flash some leds when i tilt the board. Right now we are trying to write everything in our main, is this wrong? how should we initialize such a component? I can send you what i have so far if it helps

        Like

        • Yes, the K22F do not work with my Processor Expert components in general because Freescale has decided to break any backward compatibility. Everything is now supported with the Kinetis SDK which is a very disruptive change. So all the existing software does not work any more :-(.
          You can do everything in main() if you want, nothing wrong with this. But I usually prefer to move things into separate files once things get larger.

          Like

      • uint8_t res=ERR_OK;
        uint8_t xyz[3];

        RedLED_On();
        BlueLED_On();
        GreenLED_On();
        res = FX1_Init();
        while (res==ERR_OK) {
        res = FX1_GetRaw8XYZ(&xyz[0]);
        RedLED_Put(xyz[0]>50);
        BlueLED_Put(xyz[1]>50);
        GreenLED_Put(xyz[2]>50);
        }
        RedLED_Off();
        BlueLED_Off();
        GreenLED_Off();

        This is what i have written in my main

        Like

  17. Thanks, the previous code worked…

    I have a new problem i would like to ask you about. We are using the FRDM K22 and the TWR K22 and programming in KDS, so your previous components are not compatible with these. Instead we are trying to use i2c to communicate with the on board accelerometer. We’ve tried various tutorials to do this and the code always seem to get stuck in a reset handler when we call an i2c write function. This problem is right at the beginning when we are trying to initialize the accelerometer, hence we can’t even begin writing the program that we need to. Our main approach is by using the fsl_i2c processor expert component which is given for KDS.

    Like

    • Yes, unfortunately if you use the Kinetis SDK, it introduces a completely new and incompatible API, so all the previous things do not work any more. But if you have the latest updates for KDS v2.0.0 and service packs, then it is possible to create a Processor Expert project for the K22 (without SDK), and then the previous approach still works. Not sure if this will be still the case for other newer devices.

      Like

  18. Hi Erich.

    About timeout component:

    /*
    ** ===================================================================
    ** Method : TMOUT1_AddTick (component Timeout)
    ** Description :
    ** Method to be called from a periodic timer or interrupt. It
    ** will decrement all current counters by one down to zero.
    ** Parameters : None
    ** Returns : Nothing
    ** ===================================================================
    */
    void TMOUT1_AddTick(void);

    I suspect small bug, TMOUT1_AddTick() recommended to use from interrupt routine, but internals variables are not volatile!

    variables are:
    TMOUT1_CounterType TMOUT1_Counters
    TMOUT1_FreeCounters[TMOUT1_NOF_COUNTERS];

    Please comment this
    /Best Regards

    Like

    • Hi Greg,
      ‘volatile’ does not make things reentrant: it only tells the compiler that accessing it might create side effects, nothing more.
      Reentrancy is ensured with critical sections (EnterCritical() and ExitCritical()) in the Timeout module implementation.
      Have you seen a real problem? If so, I’m of course interested in it as there always could be a bug. But in my view not using volatile is not a bug.

      Like

  19. Hi Erich

    I did not mean “reentrant problem” but optimizing potential problem. ‘volatile’ should be used whenever variable is access from 2 task or e.g.: main task and ISR.
    Please have a look here:
    http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

    In this case, TMOUT1_Counters[] is been modified from ISR but accessed from different task. Compiler is allowed to assume that TMOUT1_Counters[] may be optimized (somehow, in only compiler known way).

    From my practise and study of compilers (few years ago), compilers do not optimizing global arrays. This may obviously change recently, some aggressive commercial compilers may do some ‘advantage’ of lack volatile keyword (of course some kind of optimization has to be enabled).

    Many thanks for your PE library.

    Like

    • Hi Greg,
      yes, I understand that (I wrote compilers :-)). If I would poll a variable (like a flag) in that case, the usage of volatile would be perfectly valid. But this is not the case here: all what is needed is to ensure reentrancy which is done with the critical section. I would agree with you if the application code would directly access the counters array. But this is not the case as handled by the module. So using volatile in this case is not needed, it would be even bad because it would prevent the compiler to generate good code. So using volatile in this case is not necessary or even counter productive.
      I hope that makes sense.

      Like

      • Hi Erich, I’m just fishing through some of the NXP examples for I2C with DMA and they seem a little complex. I’m purely wanting to send a fixed number of bytes and receive a fixed number of bytes from a slave device – that can’t be too hard, can it? The eDMA for Kinetis example seems a bit lacking in instruction, very convoluted and doesn’t seem to demonstrate how its possible to have both TX and RX on the same I2C channel. Do you have any DMA advice you could please share? Thank you.

        Like

        • Hi Kevin,
          yes, DMA is definitely complex. But I have not done DMA with I2C because I had no need for this. You might have a look my other DMA examples for ADC and for the WS2812B LEDs (google for DMA and Mcuoneclipse, and you should find a few posts).
          I hope this can help?

          Like

  20. Hi Erich,
    I’m using the two I2C units of the K22FN512 in my project and had all kinds of flakeyness until we just found that the two remote I2C sensors had only 0.01uF decoupling caps, once we changed them to the recommended 0.1 uF, all the flakeyness went away (Yeah!).
    I eventually was using the GenericI2C component for each of them, with the kinetis hardware I2C beneath. When I switched to the software I2C it was a very simple switch and it worked first time.
    Now I want to go back to the hardware I2C for at least one of the channels, and I am having problems. The hardware I2C just doesn’t seem to do anything any more. I have tried deleting and re-adding all the components invlolved (including the GenericI2C) but still it is like the hardware is broken. I can switch to an ancient version and the hardware I2C works fine.
    Any Ideas???

    I suppose I should be using source control so I can go back to a specific version (dropbox does record all the older versions for me, but finding the one I want is tedious).
    What source control works seamlessly with KDS?

    Brynn

    Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.