MCF52259 "interrupt" I2C driver enhancements

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

MCF52259 "interrupt" I2C driver enhancements

Jump to solution
3,248 Views
trailman
Contributor V

After using the "interrupt driven" I2C driver (ii2c) intensively on a MCF52259, in master-only mode, I had the following problems :
- the task doing I2C accesses heavily was consuming nearly all CPU time
- the bus sometimes hanged but as no timeout was supported by the driver, this was fatal

After some investigation I discovered that :

 

- the driver was not fully interrupt driven : even if data is received or sent in interrupt mode using internal buffer, this is not the

case when you call IO_IOCTL_FLUSH_OUTPUT ioctl (or fflush()) where polling at full speed is performed; as also does IO_IOCTL_I2C_STOP ioctl. As a result :
    - when you want to receive some data, you have to call read() multiple times until some data is available, which is actually a polling if you do that in a loop as in MQX I2C examples (you do nothing else except calling read())
    - when you send some data and wait for the end of transfer before going further you have to call IO_IOCTL_FLUSH_OUTPUT but this call also does full speed polling.

 

- there"s no timeout for polling in IO_IOCTL_FLUSH_OUTPUT nor IO_IOCTL_I2C_STOP so this can be deathlock. I had the problem when probing some temperature sensors on which power supply was removed at any time (including during a transfer), while MCF52259 remained powered.

To solve that, I made the following changes to the I2C driver :
- added support for sleeping in read() / write() / IO_IOCTL_FLUSH_OUTPUT calls, and removed polling in IO_IOCTL_I2C_STOP
    - when read() is called the task sleeps in this call until all requested data has been received or RX buffer full
    - write() call only sleeps if the amount of data to be writtent is bigger than the buffer.
    - IO_IOCTL_FLUSH_OUTPUT ioctl (or fflush()) sleeps until data has been transfered
    - in any case check the return value to know the amount of data processed or if a timeout occured.
- added support for a settable timeout (default 20mS) for these calls, using two new ioctls : IO_IOCTL_I2C_SET_TIMEOUT_MS and IO_IOCTL_I2C_GET_TIMEOUT_MS. In cas of timeout :
    - read() and write() return -IO_ERROR_TIMEOUT (negative value; easier than returing IO_ERROR and setting errno to IO_ERROR_TIMEOUT)
    - IO_IOCTL_FLUSH_OUTPUT returns IO_ERROR_TIMEOUT (positive value)

Below is a zip file containing modified files for MQX 3.7 under mqx/source/io/i2c

WARNING : this breaks support for slave mode I2C : the interrupt handler has to be modified for its slave part to support these changes and I did not have the opprtunity to do that (however this should be very simple). So only use this zip if you use I2C as master-only.

NOTE : in case of timeout, if you retry and also get another timeout, the bus is probably hang. You may unlock the bus as described in this post :
https://community.freescale.com/thread/99204

TODO :
- add the required code to support slave-only mode
- fix the code to support both master and slave mode at the same time : this may require to use separate buffers for master and slave, separate semaphore. TBD : use same file descriptor for master and slave or open ii2c device twice ?

1 Solution
1,388 Views
jausel
Contributor II

Sorry for the delay,

Last week I could work on it and it works!!

 

thank you.

View solution in original post

0 Kudos
11 Replies
1,388 Views
trailman
Contributor V

A new update for MQX 3.7.

This adds some fixes to prevent false "arbitration lost" detection when mixing slave and master accesses concurrently on the interface.

Please keep in mind that when the MCF52259 is addressed as a slave, the master must support clock stretching (because of latencies in interrupt processing).

You can custom the interrupt handler to have the slave part customized for your application. For example, when an IAAS for master write is received (receive mode on MCF52259), you can use the first byte received to select a command/register, and use the remaining bytes (if any) as data supplied to this command/register. Also when an IAAS for master read is received (transmit mode on MCF52259), you can send bytes of data for this command/register (until the last byte that is not acknowledged by the master)

Here are some notes for using I2C interface both as master and slave concurrently :

I've added _int_disable() / _int_enable() to macro I2C_INT_ENABLE() to prevent the slave part of handler from modifying I2CR while it is beeing modified by the code run for master accesses (when arbitration between masters in progress)

The bad thing with the MCF52259 hardware is that even if disabling all interrupts (_int_disable()), the I2CR may be modified by hardware (MSTA cleared by arbitration lost for example) while it's beeing modified by the code for master access. It would be easier to have two fully separated hardware blocks per interface : one for master and one for slave mode, but things are as they are ...

So when doing full speed accesses from an external master to the MCF52259 as slave, while doing full speed master accesses from the MCF52259 as master to an external slave, I sometimes get a an "arbitration lost" on external master with MCF52259 waiting the master to read more data. This leads to a (recoverable) bus timeout on external master. This seems to occur only when the external master reads from the MCF52259, not when the external master only writes to the MCF52259. At full speed at 100Khz I get around one bus timeout per minute.

1,388 Views
trailman
Contributor V

A new update for MQX37.

Now mixing both master and slave accesses concurrently works fine without spurious "arbitration lost" issue.

This update also add support for ACK checking when doing only I2C read accesses, without write+restart before. For this, do as follows :

IO_IOCTL_I2C_SET_MASTER_MODE ioctl

IO_IOCTL_I2C_SET_DESTINATION_ADDRESS

IO_IOCTL_I2C_SET_RX_REQUEST with parameter = 0 (read 0 bytes)

dummy read of 0 bytes : fread (&data8, 1, 0, fd)

IO_IOCTL_FLUSH_OUTPUT, checking ack in output value of ioctl

== If NOACK, do an IO_IOCTL_I2C_STOP; otherwise continue below ===

IO_IOCTL_I2C_SET_RX_REQUEST with actual nbbytes number of bytes to read

read of nbbytes bytes : fread (buffer, 1, nbbytes, fd)

IO_IOCTL_FLUSH_OUTPUT or fflush(fd)

IO_IOCTL_I2C_STOP

Please note that the driver does not wait for STOP completion at IO_IOCTL_I2C_STOP because there's no reason to wait here before running the rest of the application code (and also because no interrupt is generated by the MCF52259 on STOP completion, so a polling would be inefficient).

However a bus BUSY may be reported by IO_IOCTL_I2C_SET_MASTER_MODE when doing a new access immediately after the last one. In this case (and for any other cause of bus BUSY such as arbitration lost), the access should be retried.

0 Kudos
1,388 Views
trailman
Contributor V

I re-attach the zip file for MQX37 interrupt I2C driver enhancements (master-only) because it has been lost during forum migration.

1,388 Views
c0170
Senior Contributor III

Thank you gilles buloz for all reuploaded code !

Regards,

MartinK

0 Kudos
1,388 Views
trailman
Contributor V

Here is my "final" update to MQX37 to support both MASTER and SLAVE operation (dynamic switching) in a fully interrupt driven way, with multimaster/arbitration support.

The code I posted 3 days ago had some issues when doing concurrent outgoing master accesses with incomming slave accesses, so I fixed it and post the updated code here.

I also post a library showing how to manage the master mode and to handle the related errors.

As said before, the slave part of handler has to be completed to return some real data, but is already fully functional and can be modified to your needs.

I will probably add some generic ioctls to receive and send some data in slave mode (keeping read()/write() syscalls unchanged for master operation), and i will post the updated code.

NOTE : when accessed as a slave, the external master must support clock stretching (SCL held low) to handle the latencies of the I2C interrupt handler. This may not be supported by a very basic master.

0 Kudos
1,388 Views
trailman
Contributor V

A new zip file with an updated i2c_int_mcf52xx.c to fix arbitration loss detection issues.

0 Kudos
1,388 Views
trailman
Contributor V

My patch was an enhancement for master only mode.

Here is some additional code to be put in the interrupt routine _mcf52xx_i2c_isr(), as a replacement for the commented-out code (the one in the "#if 0  // Not yet tested with new interrupt code .....).

This makes the I2C interfaces BOTH WORK AS MASTER AND SLAVE (default I2C slave address is 0x6F in BSP). Multimaster support (arbitration) when acting as a master is still supported.

For now, when addressed as a slave, the handler ignores writes from master and returns 00 when read.

But you can easily modify this slave handler (see comments in code) to return some data according to the data sent by the master (for example to implement some read-write registers seen from I2C and from your MQX application, with the selection of the target register made from I2C with the first byte sent by the master). This would implement the same mechanism as the one used for example to access I2C devices such as temperature sensors.

NOTE : If the data to return is not immediately available to be sent, it shoud be sent from outside the interrupt routine by waking up a task that will perform this task in background, and return immediately from the interrupt routine.

NOTE : when sending some data as master and the arbitration is lost, the system call will return a timeout because the I2C driver will switch to slave mode. This works fine like that, but it would be cleaner to add some code in the slave code of the interrupt routine to abort the pending master system call immediately instead of waiting its timeout.

0 Kudos
1,388 Views
jausel
Contributor II

Hello,

Quite new on MQX, I'm using the version 3.8 and I have problems in I2C, sending or receiving more than one byte.

I tried I2C using polling and interrupt, having same bad results.

I tried adding controls in order to check if functions calls return wrong values, but all seems ok.

With oscilloscope I can seen very well the bytes sent, but only one.

 

I've read this post and tried to do the changes on the 3.8 version but MQX do not complie, some errors in some defines.

I will be happy if someone can help.

 

Here I copy the driver I'm trying to program:

 

MQX_FILE_PTR  i2c_port;  
/*.........................................................................*/
void I2C_Init()
{    
    uint_32 param;        
    uint_8 mem;    
    
    i2c_port = fopen ("ii2c0:", NULL);
    ioctl (i2c_port, IO_IOCTL_I2C_SET_MASTER_MODE, NULL);        

    param = 100000;
    ioctl (i2c_port, IO_IOCTL_I2C_SET_BAUD, &param);    
    
    // This is just to check the line
    fwrite (&mem, 1, 0, i2c_port);
    ioctl(i2c_port, IO_IOCTL_FLUSH_OUTPUT, &param);
    ioctl(i2c_port, IO_IOCTL_I2C_STOP, NULL);
}

/*.........................................................................*/
int I2CWriteToSlave(uint_16 address, unsigned char *mess, unsigned int len)
{
    uint_8 result;
    /* Set the destination address */
   ioctl (i2c_port, IO_IOCTL_I2C_SET_DESTINATION_ADDRESS, &address);
      /* Write 'len'  bytes of data */
    fwrite (&mess, 1, len, i2c_port);  
      fflush (i2c_port);
      /* Send out stop */
      ioctl (i2c_port, IO_IOCTL_I2C_STOP, NULL);    
      return 1;
}

/*.........................................................................*/
int I2CReadFromSlave(uint_16 address, unsigned char *mess, unsigned int len)
{
    uint_8 result;
      //Set the I2C destination address
      ioctl (i2c_port, IO_IOCTL_I2C_SET_DESTINATION_ADDRESS, &address);
      //Set how many bytes to read
    ioctl (i2c_port, IO_IOCTL_I2C_SET_RX_REQUEST, &len);
      //Read n bytes of data and put it into the recv_buffer
      fread (&mess, 1, len, i2c_port);
      //Wait for completion
      fflush (i2c_port);
      //Send out stop
      ioctl (i2c_port, IO_IOCTL_I2C_STOP, NULL);    
    return 1;
}

/*.........................................................................*/
int I2CReadAfterWrite(uint_16 address, unsigned char *tmess, unsigned int tlen,
                                unsigned char *rmess, unsigned int rlen)
{
    uint_8 result;
      //Set the I2C destination address
      ioctl (i2c_port, IO_IOCTL_I2C_SET_DESTINATION_ADDRESS, &address);
    fwrite (&tmess, 1, tlen, i2c_port);
      //Wait for completion
      fflush (i2c_port);
      //Do a repeated start to avoid giving up control
      ioctl (i2c_port, IO_IOCTL_I2C_REPEATED_START, NULL);
      //Set how many bytes to read
      ioctl (i2c_port, IO_IOCTL_I2C_SET_RX_REQUEST, &rlen);
      //Read n bytes of data and put it into the recv_buffer
      fread (&rmess, 1, rlen, i2c_port);
      //Wait for completion
      fflush (i2c_port);
      //Send out stop
      ioctl (i2c_port, IO_IOCTL_I2C_STOP, NULL);    
    return 1;
}

thank you!

0 Kudos
1,388 Views
PetrM
Senior Contributor I

Hello,

 

please take a look at MQX i2c example, eeprom_int.c.

Interrupt i2c driver read/write calls are non-blocking, you have to add active waiting for completion.

 

Regards,

PetrM

1,389 Views
jausel
Contributor II

Sorry for the delay,

Last week I could work on it and it works!!

 

thank you.

0 Kudos
1,388 Views
squadmcu
NXP Employee
NXP Employee

Hi Jaume! Could you let us know how you fixed it? Thanks!

0 Kudos