Generic I2C driver?

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

Generic I2C driver?

4,212 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Wed Jul 20 14:43:19 MST 2011
Hi,

unfortunately it seems that the search function couldn't find 'I2C'. I assume that at least four characters must be entered, so I'm sorry if this question has been already asked.

I want to dig deeper into I2C. So I build a small test circuit for LPCxpresso boards. Now, the software needs to be written :)
I know about the examples, but they're just that - examples. Written for special purposes like EEPROM or something like that.
But I couldn't find a generic approach which allows to have the I2C routines and just build the device driver on top of them, without having to modify the I2C routines and interrupt.

So, I thought about possible implementations for a generic driver. Reading the LPC 11xx/13xx/17xx user manual shows up that for most of the I2C status codes there are two or more possible status codes following, depending on the situation and the need of the device driver.
Now, I wonder if the following approach would be suitable:
A buffer is used for communication, along with transmit and receive counters and pointers. If the transmit counter is non-zero, the buffer will be sent (LPC acting as a master transmitter). The same with the receive counter, but LPC as a master receiver. If both are non-zero, the data will be sent and a repeated start instead of a stop is generated and the data is read.
So, that's the easy part and this approach is mostly used as far as I can see.

Now, assume there are (at least) two devices on the bus. For one device it is neccessary to repeat a transfer if e.g. the device sends a unexpected NACK during the transfer. For the other device it isn't neccessary or even not allowed to repeat the transfer (e.g. because the other device must be served within a given time or something like that).
One approach would be to write the interrupt service routine in a way that distinguishes between the two devices and acting accordingly. But that wouldn't be generic.

So, what about two additional status bytes for each data byte in the buffer? Each status byte contains the next status depending on a ACK/NACK. This allows to write a generic I2C driver, where the buffer contents also controls the behaviour of the communication.
Of course, at the moment this approach is only seen from master side, slave implementation is still open.

I hope it's clear what I mean :) What do you think of this approach? To complicated? Worth a try?

Regards,

Ralf
0 Kudos
Reply
23 Replies

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Thu Jul 28 00:59:03 MST 2011
@John:

Quote:
Some info will need to be retained in  between recursive function calls. So the only way to do that is to use  either static or global variables.


Ah, I misunderstood, sorry. I used statics only in functions, not modules, because the 8051 compiler/linker I used was able to overlay memory locations for  variables, but only on a module (C-file) level. That means to get the  most optimum code it was neccessary to have each function in separate  C-file. Therefore only globals were useful, not global statics :(
As far as I can see with GCC/LPCxpresso there is no need to it in the  same way, however I don't know if data overlaying is supported in the  same way (but that would be another question).


Quote:
I like to use static variables instead of global whenever possible. For  the most part it just depends on your coding style on which one you  choose. A static variable is hidden from other modules in the  compilation which I think is good for a driver.


Yes, I prefer the static variables, too.

@Rob:

Quote:
Using globals or static vars in a function is asking for problems, so don't - or at least know what you are doing.


I never had problems using statics, globals or global statics. It all depends on a well designed software flowchart before coding ;)


Quote:
In general ARM code is reentrant as long as you keep a good eye on the use of globals and static vars.


Good to know that it is generally reentrant. Currently I don't think that it will bite me, because each I2C engine will have its own set of variables.

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Rob65 on Wed Jul 27 23:48:23 MST 2011
Ralf,


Quote: RA1981

:confused: Why static or global? Okay, a global variable for returning status messages, but why static?



Global or static variables are out of the question.
Using globals or static vars in a function is asking for problems, so don't - or at least know what you are doing.
Static vars behave like globals but are private for a given function.

The ARM uses a proper stack, quite different from a 8051 or other small microcontrollers.

I have just looked at the code of the I2C driver and it looks to be reentrant.
Of course there is a set of global variables used to keep track of the state of the state machine being used, and the buffers (+ length and index) that you need to have for each I2C interface being used but the rest looks OK.

In general ARM code is reentrant as long as you keep a good eye on the use of globals and static vars.

Hope This Helps,

Rob
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by atomicdog on Wed Jul 27 23:45:37 MST 2011

Quote: RA1981
:confused: Why static or global? Okay, a global variable for returning status messages, but why static?

Some info will need to be retained in between recursive function calls. So the only way to do that is to use either static or global variables.

I like to use static variables instead of global whenever possible. For the most part it just depends on your coding style on which one you choose. A static variable is hidden from other modules in the compilation which I think is good for a driver.
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Wed Jul 27 23:13:11 MST 2011
Hi John,


Quote:
I know that some 8051 compilers need you to mark the function as reentrant...


That's why I ask :)


Quote:
... but I don't think you need to on ARM gcc.
You should only need to write the function in a reentrant friendly way (i.e use static/global variable).


:confused: Why static or global? Okay, a global variable for returning status messages, but why static?

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by atomicdog on Wed Jul 27 20:42:57 MST 2011

Quote: RA1981
I think this common I2C handler has to be reentrant. I wonder if functions are always reentrant or if I have to mark it as reentrant? If so, how to mark it reentrant?

I know that some 8051 compilers need you to mark the function as reentrant but I don't think you need to on ARM gcc.
You should only need to write the function in a reentrant friendly way (i.e use static/global variable).
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Wed Jul 27 13:51:27 MST 2011
Hi,

another small question: beside the LPCxpresso LPC1114 board I also have the LPC1343 and LPC1769 boards. The LPC1769 has three I2C engines.

Now, I've seen driver implementations where each of the corresponding interrupt handlers call a single I2C interrupt handler with a parameter indicating which I2C engine has fired the interrupt.
This approach saves code space and I want to prepare my driver to use a single interrupt handler. I think this common I2C handler has to be reentrant. I wonder if functions are always reentrant or if I have to mark it as reentrant? If so, how to mark it reentrant?

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Tue Jul 26 22:29:03 MST 2011
@Rob:

Quote:
That driver does not repeat the transfer on a NACK but it will send this  result to the caller. If the slave does not respond on the slave  address (slave does not exist) the result returned is I2CSTATE_SLA_NACK  but when the slare returns a NACK when sending a databyte (e.g. to  denote this was the last byte) an I2CSTATE_NACK is returned.


This is close to what I want - operation result returned to the caller so the caller can react flexible.


Quote:
Unfortunately there is only limited documentation ...


Doesn't matter. If there would be any questions I know where I can ask ;)

@qili:

Quote:
I don't know if that is to be the case. I can think of a few cases where  one may want to just hang the code, display an error message, set a  flag, etc. i think it is more up to you as to how you want to handle it  for your specific application.


Okay, hanging the code is a debug method, I couldn't image a real application which want's to hang with intent.
To handle it application specific means the driver must be able to return a result, and the driver must not act fully independent from the application (e.g. by simply repeating a transfer when a device NACKs). That's what I would call a generic driver...


Quote:
which can be a valid response - depending on your design objective.


Windows users have enough endless-loop applications, I don't need another one :rolleyes:
Seriously: I always try to write applications and drivers in a way which allows a non-blocking approach. If the application really needs blocking, it could be realized simply by checking the result of a driver operation, so it's really flexible.

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by thiennv0109 on Mon Jul 25 21:03:04 MST 2011
hello friends, I come from Vietnam, are pleased to participate in this forum
I wish you health, success
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by qili on Mon Jul 25 17:50:50 MST 2011

Quote:
as far as I can see the most used usual approach when a I2C device NACKs a transfer due to a fault is to repeat the complete transfer, right?



I don't know if that is to be the case. I can think of a few cases where one may want to just hang the code, display an error message, set a flag, etc. i think it is more up to you as to how you want to handle it for your specific application.

the "typical" approach is to allow a i2c_write() routine to return the ack response from a slave, as a way to detect if a device / address is present on the i2c bus.


Quote:
But repeating the transfer will give a endless loop...



which can be a valid response - depending on your design objective.
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Rob65 on Mon Jul 25 12:01:47 MST 2011
Some time ago I reworked the NXP example into a usable driver - usable for me that is ;)

Check out my drivers page, there is a version of the I2C driver that might do what you want.
That driver does not repeat the transfer on a NACK but it will send this result to the caller. If the slave does not respond on the slave address (slave does not exist) the result returned is I2CSTATE_SLA_NACK but when the slare returns a NACK when sending a databyte (e.g. to denote this was the last byte) an I2CSTATE_NACK is returned.

Unfortunately there is only limited documentation ...

Rob
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Mon Jul 25 10:27:00 MST 2011
@qilli:

Quote:
I typically structure my code so that it has multiple layers, and in this case at least two layers:
1) ...
2) ...


That's how I write my drivers, too.


Quote:
at that point, your task of writing a generic i2c library becomes much  simpler: you can either write a library that deals with a large number  of chips, or you write multiple ones each dealing with specific chips.


Exactly. However, it seems that I'm still missing something: As I mentioned above, as far as I can see the most used usual approach when a I2C device NACKs a transfer due to a fault is to repeat the complete transfer, right? I also mentioned the situation where this is not wanted, e.g. when the software initially doesn't know which devices are present on the bus. So a non-present device will be the same as a NACKing device. But repeating the transfer will give a endless loop...
In the meantime, I read through several I2C examples. Some of them simply try to restart the transfer, some of them stop the transfer when a unexpected NACK occurs.
But some of them retry only for a configurable count of transfers -> So, this is a approach I didn't think about: Instead of my approach where each data byte has two additional status bytes the implementation of a 'retry counter' would cost much less memory but is nearly the same in effect -> perfect :D

So I think I've found a approach which fully fits to my requirements.

Thanks a lot for your help.

@Rob65:

Quote:
The current I2C driver as given in the lpc13xx examples is generic.
...
NXP's device driver is perfect.


Yes, this driver was one of the examples I read through. Each (example) implementation I read helps me to grow my understanding of I2C ;)
So, it seems that I don't have to reinvent the wheel again, but I've to find the implementation which fits best to my needs...

Thanks to all for your help.

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by qili on Fri Jul 22 10:56:55 MST 2011

Quote:
It follows a state machine approach exactly as the I2C protocol does.



I haven't read "the I2C protocol" so I may be completely off. but I would be surprised if a protocol, any protocol, is tied to one particular "approach".

to me, a protocol is a collection of ways of doing things. how those things are done is likely none of the protocol's business.

but then I have known to be completely wrong, :).
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Rob65 on Fri Jul 22 10:16:10 MST 2011
I am not sure what you arre discussing about.
The current I2C driver as given in the lpc13xx examples is generic.
I am crerating different transactions for different devices without any problem, you just create the required transactionw (write only or write/read) as required.

NXP's device driver is perfect.
It follows a state machine approach exactly as the I2C protocol does.
Mess with this and you're bound to mess up your code :eek:

Rob
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by qili on Fri Jul 22 08:25:32 MST 2011

Quote:
how would you solve it when you have a scenario where you need more than one of these options?



i guess that it depends on what "devices" you are talking about. all nxp i2c are highly alike so it is possible (but I haven't explored it myself to be 100% sure) to write a more generic i2c code that works on a large number of nxp chips, if not all of them.

having said that, your issue (of writing a library to interact a i2c device) is actually unrelated to the topic above.

putting aside nxp-specific i2c issues, I typically structure my code so that it has multiple layers, and in this case at least two layers:

1) protocol specific routines: those would be routines that deal exclusively with a particular protocol. for i2c, i would have routines that I call to send a start condition, or a stop condition, or to write to / read from the i2c bus. etc. those routines perform a pre-determined and identical tasks - for example, the i2c_start() will always send a start condition, regardless if it is big-bang / i2c hardware, on a nxp, or a st or a 8051, etc.

2) device specific routines: those would be routines that interact with the device (in your case a eeprom) utilizing the protocol specific routines. they would read or write a byte from a given address for example. this set of routines doesn't need to know how an i2c start condition is actually sent, nor does it care. all it cares is that it is sent.

so if you hook in the software i2c routines, your code of writing to / reading from a i2c eeprom will work on a chip without i2c hardware. or it will work on other vendors' chips too.

at that point, your task of writing a generic i2c library becomes much simpler: you can either write a library that deals with a large number of chips, or you write multiple ones each dealing with specific chips.

the issue with nxp's i2c engine is that it doesn't follow the traditional approach of performing certain tasks on demand - it is a state machine based approach.

but it can be rewritten to perform specific tasks.

hope it helps.
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Fri Jul 22 06:57:02 MST 2011

Quote:
maybe i mis-understood it. what did you mean by "generic"?


It means that the I2C driver doesn't need to be modified for a device.
I've seen drivers where the interrupt service routine "knows" to which device the current transfer is related to. But this means each time a new device is added the ISR must be modified additionally to writing the device driver (of course it could be that this simply was a bad implementation of the I2C interrupt).

For me, the question for a generic driver came up while I was studying the I2C chapter of LPC11xx user manual.
For example, take table 170, page 175:
[I]I2C status code 0x20, SLA+W has been transmitted; NACK has been received.[/I]
Now, the next possible states are:
1. Data byte will be transmitted; ACK bit will be received.
2. Repeated START will be transmitted.
3. STOP condition will be transmitted;
4. STOP condition followed by a START condition will be transmitted;

So, how would you solve it when you have a scenario where you need more than one of these options?
As a beginner I thought of busy polling for a EEPROM after a write operation. Instead of sending a STOP or a STOP/START it would be simply a (repeated) START condition in this case (and again check polling). Now, consider a second device where you don't want to restart if NACKed, e.g. if the software want's to know if the device is present on start-up (could be also the EEPROM again).

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by qili on Fri Jul 22 06:12:22 MST 2011
maybe i mis-understood it. what did you mean by "generic"?
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Fri Jul 22 04:44:24 MST 2011
@qili:
 
Quote:
it shouldn't be that difficult to write a generic master driver, even for lpc's i2c hardware.


Yes, I think it wouldn't be difficult, however getting it generic would be the challenge :)

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by qili on Thu Jul 21 08:00:48 MST 2011
it shouldn't be that difficult to write a generic master driver, even for lpc's i2c hardware.

writing the slave side is a little bit tougher.
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by RA1981 on Thu Jul 21 00:41:09 MST 2011

Quote:
To me tripling the ram usage is a lot . What I don't understand about your proposal is how it would be used/useful.


It avoids the need to have special I2C driver which have to be modified for the connected devices. Drawback is the mentioned additional memory usage.
Maybe currently I see a need for this approach where it isn't really neccessary - a beginners view.

Regards,

Ralf
0 Kudos
Reply

3,610 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by atomicdog on Thu Jul 21 00:02:20 MST 2011

Quote: RA1981

I don't think so. Currently it seems that my approach would 'only' need the additional memory used for the status bytes. So if the transmit/receive buffer is e.g. 16 bytes, the whole buffer would use additional 32 bytes = 48 bytes.

To me tripling the ram usage is a lot . What I don't understand about your proposal is how it would be used/useful.
0 Kudos
Reply