I have started some tests with the new LPI2C in the KL28 but may have already found a bug which is not mentioned in its errata.
Take a look at a read of the accelerometer on the FRDM-KL28Z that follows:
The accelerometer is addressed for a write and the value 9 is subsequently written to select the internal register to be read from, then a repeated start is send to address it for the subsequent read. Only the start of the subsequent read is shown but all works fine.
I repeat, but this time set a break point after the first address byte has been sent – with the DBGEN set to '1' (meaning that the LPI2C will stop when debug mode is entered).
As can be seen, the start condition is sent, followed by the address - and the bus remains busy.
When the debugger continues, the second byte is sent, followed a stop condition.
Now I repeat again but this time with the DBGEN set to '0', meaning that the LPI2C continues to run in debug mode.:
Now it is seen that a STOP condition has been sent by the LPI2C.
When the code continues, it tries to send the data byte but can't because the bus is now idle – it can only continue if it sends a new start bit.
It turns out that the LPI2C sends a STOP condition if there is nothing in the Tx FIFO, meaning that if the FIFO can't be filled before this takes place the transfer will be terminated. Firmware must thus keep up otherwise any Tx FIFO underflow will terminate a cycle and hence cause failure.
This is exactly what is explained in the AUTOSTOP feature in the LPI2C_MCFGR1 register:
However, whether this is set to '1' or set to '0' doesn't change the behavior – it always reacts as if it were set.
This is what looks like an errata, whereby the workaround is to ensure that the firmware keeps up with the transmission by making use of its Tx FIFO depth, or by using just slow I2C clock speeds, or using DMA.
Also, working with DBGEN set to '0' more or less means that all I2C activity will fail, so BDGEN is required to have any chance (one can also take the NXP examples and try stepping them to see that they then fail). In comparison, doing such things on other Kinetis devices with the old fashioned I2C (non-buffered) controller never had issues with debugging – one could step the code and the I2C bus would pause/continue normally, although the I2C continues to run in debug mode.
So I did some work this morning to investigate this. What I found was that the DBGEN bit is definitely the culprit. I don't know that we really consider this a bug though. Here's what is happening to me. If I have the DBGEN bit set to 0, I see the behavior you had showed above where the STOP signal is put on the bus and the master won't transmit anymore until a new START signal is put on the bus (I didn't actually try to put a new START on the bus, but definitely was seeing the STOP signal).
I tried this with the different values for the Autostop feature and found that the Autostop setting did not affect the results. Which makes sense to me. My understanding of the Autostop feature is that it allows you to setup a buffer that does not include a STOP command for the last command and the STOP signal will automatically be sent once the buffer has been exhausted. I believe this is for use cases where maybe you have a variable length buffer that the master needs to transmit to a device and/or to simplify programming.
Here is the table that I put together for this trial.
Here is the code that I used. By the way, are you also using the SDK drivers in your project?
And just for reference, here's my oscilloscope screenshot with the Autostop feature enabled as well as the DBGEN = 1.
And the resume after capturing the breakpoint...
Could you double check your setup and let me know if you are seeing the same behavior I am seeing.
As for the DOZEN bit, I agree the description appears to be backwards. I would expect that DOZEN = 1 means the DOZEN feature is enabled and DOZEN = 0 means the feature is disabled. It may not actually be that way in silicon though. Will have to do some more investigating. I do see that the DOZEN bit is supposed to apply to both STOP and WAIT modes. So that is one thing to keep in mind.
One theory I have though, is maybe the interrupt signal from the LPI2C is waking up the processor? Could this be happening in your system?
Thanks again for looking into this.
Yes, you have seen the same behavior as myself when working with the debugger. DBGEN set to 0 means that the LPI2C sends a stop condition after the address. With DBGEN set to '1' the LPI2C freezes and so it continues correctly when the core operates again.
I don't understand the actual meaning of DBGEN since I would expect it to freeze with '0' set but this behavior has been confirmed by both of us and so it shows that it is not possible to use DGBEN at '0' but it is possible with DBGEN at '1' - that is the only option to allow LPI2C to operate correctly. Whether this a bug or not is another question but it is obviously the only possibility to actually work.
Since I was very worried that this behavior (sending STOP if the core didn't supply data on time) could happen in general operation too I made some more test without the debugger connected (in RUN, WAIT and STOP modes). I am pleased to report that I couldn't detect this error in any of these mode when the core doesn't supply the data on time - it is restricted to the operation in debug mode.
However I do detect a potential serious problem with the AUTOSTOP mode and I can confirm that the DOZEN description is inverted in the user's manual and that there is a difference between WAIT and STOP mode behavior.
This is the conclusion after I repeated the tests many times and checked carefully that I wasn't making any mistakes in configurations or measurements. I have attached a binary file for the FRDM-KL28Z board that can be used to see the behavior too.
1. DBGEN state has no effect when the processor is not connected to the debugger/paused.
2. DBGEN has an effect when paused by the debugger and only DBGEN = '1' allows operation without the LPI2C sending an incorrect stop bit. WORKAROUND: Always set DBGEN to '1' and there are no issues.
3. DOZEN = 1 means that the LPI2C runs as normal in both WAIT and STOP modes.
4. DOZEN = 0 means that the LPI2C runs as normal in WAIT mode but stops in STOP mode. This is not as detailed in the user's manual, where it is states that it also stops in WAIT mode. The polarity is also inverted in the user's manual.
5. When AUTOSTOP is set to '0' all read operation is as expected in all modes.
6. When AUTOSTOP is set to '1' all read operation is as expected in all modes as long as the core immediately command the read after sending the slave address.
If the core doesn't command the read "immediately" it stops sending, but it never sends a STOP condition.
WORKAROUND: Never leave the AUTOSTOP bit set during the transmission of the slave's read address!
7. When AUTOSTOP is set to '0' all transmission is as expected in all modes.
8. If AUTOSTOP mode is set to '1' the Tx buffer should be kept fed otherwise a STOP condition will be sent prematurely - also after the slave write address. This is however the correct operation and so it is up to the user to correctly make use of it.
>>One theory I have though, is maybe the interrupt signal from the LPI2C is waking up the processor? Could this be happening in your system?
As shown in one of the previous screen shots (and various below), there are interrupts taking place on each LPI2C data byte (tx and rx) and so the core is continuously moving between WAIT and RUN or STOP and RUN.
What I see when DOZEN = 1 is used and the core moves to STOP is that the LPI2C state 'freezes' (screen shots below). When the core moves to WAIT the LPI2C activity continues normally.
In the STOP state (and frozen LPI2C) I have a 1s interrupt for the RTC that wakes the processor momentarily to RUN and it then immediately returns the STOP state. Sometimes I see that the state of the LPI2C bus changes when this happens, showing that the LPI2C also operates during this moment before freezing again.
>>By the way, are you also using the SDK drivers in your project?
No, I am using the uTasker project and working on integration of the simulation of the LPI2C interface, where I have noticed deviations. This project uses dynamic low power management and so allows more flexible and meaningful tests in a complete working environment, rather than a single example setup.
In case you would like to repeat some tests you can load the attached binary file to a FRDM-KL28Z and control it as follows:
1. When the board starts it will continuously read the accelerometer (50kHz clock and almost no pauses on the bus)
2. It will be in WAIT mode whenever there is no activity (default behavior). The RED led is connected to a GPIO that is set to '1' when the processor is in RUN mode and '0' when in WAIT mode. This is seen below the 2 I2C lines in the screen shot and follows the LPI2C interrupts since there is not much else happening on the board (there is a 50ms TICK plus a 1s RTC interrupt [plus USB and LPUART that are irregular])
3. There is a command line interface on the VCOM UART and also on the USB (as CDC) so either can be used for control.
When the enter key is hit a menu appears:
1 Configure LAN interface
2 Configure serial interface
3 Go to I/O menu
4 Go to administration menu
5 Go to overview/statistics menu
6 Go to USB menu
7 Go to I2C menu
8 Go to utFAT disk interface
9 FTP/TELNET commands
a CAN commands
b Advanced commands
help Display menu specific help
quit Leave command mode
4. In menu 7 some I2C commands can be exercised.
up go to main menu
acc_on enable accelerometer output
acc_off disable output
tpause test potential bug [1/0]
rpause test potential bug [1/0]
dozen DOZEN [1/0]
dbgen DBGEN [1/0]
auto AUTOSTOP [1/0]
help Display menu specific help
quit Leave command mode
"acc_on" turns on the accelerometer output display and "acc_off" disables it again.
With "dozen 0" and "dozen 1" the state of the DOZEN control can be changed.
With "dbgen 0" and "dbgen 1" the state of the DBGEN control can be changed.
With "auto 0" and "auto 1" the state of the AUTOSTOP control can be changed.
With "tpause 0" and "pause 1" the behavior to delaying the write of the first data byte - it adds a 0.5ms delay between commanding the START + address and allowing further operation to continue
With "rpause 0" and "pause 1" the behavior to delaying the read of the first data byte - it adds a 0.5ms delay between commanding the START + address and allowing further operation to continue
Move up a menu level with "up"
5. In menu 4 (administration) the low power mode being used can be displayed (with "show_lp") and the mode changed with "set_lp 2" (2 sets STOP mode rather than the default WAIT mode ). In stop mode USB-CDC will stop and it may be necessary to reset to get out (possibly the UART responds but I don't know since the OpenSDA debugger on my board gets deleted all the time so I just use an external one [The DAP OpenSource debuggers are really awful - they either don't work or delete themselves when connected to a PC - their MSD and CDC are also only partly operational - I wish we could load a P&E or Segger version to this board ;-( ]).
6. This is the recording showing 0.5ms pause after writing the START + slave write address
As can be seen, the delay doesn't affect operation - there is a following STOP and START (rather than repeated start) due to the firmware slowness, but this is correct.
7. This is the recording of the same with AUTOSTOP enabled.
The slowness of the firmware causes the LPI2C to automatically send the STOP condition. This is premature for correct behavior but is the fault of the slow firmware for using a mode that it can't keep up with. Therefore OK.
8. This is a normal read reference (just start of multi-byte read)
and the effect of a pause of 0.5ms.
The LPI2C waits correctly.
9. This is the effect of using AUTOSTOP and a pause in the read.
Note that now the LPI2C module sets the SCLK line high but the SDA line stays low forever.
This never recovers! NEVER start a read with AUTOSTOP set !!!!
10 Finally, the effect of setting DOZEN to 0 and the STOP mode (commands "dozen 0" followed by "set_lp 2"):
When the STOP mode is set (lower line goes to '0' the LPI2C activity 'freezes in an active bus state). In WAIT mode it continues.
Looking at a bigger picture.
About 0.8s after the big freeze there is a momentary wake-up to RUN mode due to a RTC seconds interrupt, where it is seen that the LPI2C briefly continues with some activity during the short RUN state.
If I monitor this over a long period of time it is seen that there is a single LPI2C movement each time the RUN mode is executed for a few us so the LPI2C activity is operating but extremely slowly since it only operates a few us each second.....
[Note also that the 50ms TICK is from the SYSTICK in this configuration, and this also stops in STOP mode].
All permutations can be tested easily with the attached binary and each test is fully reproducible....
Please contact me at [mark (at) uTasker.com] so that I can give you access to the GIT repository.
You can then build and test on any HW that you have with LPI2C.
To enable the LPI2C test interface and the test code in the LPI2C driver you need to add a define (eg. to config.h) called TEMP_LPI2C_TEST
Thanks for following up again. I am pleased that you have been able to verify some deviations.
In case you would like to test the same operation that I used on a different board please tell me which one(s) you have. I have almost every board reference here and the same code runs on all of the them, meaning that I can do the same thing with any device with an LPI2C.
I'll look into this. One quick observation is that I think your misunderstanding the DBGEN values. DBGEN = 0 means the LPI2C will NOT run in debug. DBGEN = 1 means the LPI2C WILL run in debug.
I'll have to put some test code back together to look into the other aspect of your question.
Thanks for looking into this.
I don't remember whether I actually used '1' or '0' for DBGEN.
The DOZEN is in fact what causes some confusion (possibly I mixed them up) since its polarity is sort of inverted:
Comparison with the control in LPIT in the same cip:
In any case, the debug enable is not really the issue, but instead the fact that the LPI2C ends a STOP even though this is not commanded/programmed....
By the way, concerning the DOZEN:
When I program this to be "disabled" [set to '1'] and use WAIT mode it doesn't change behavior - it always runs - as shown by this recording:
The third line is '1' when the processor is in RUN and '0' when in WAIT mode (one sees that each I2C Rx interrupt wakes the processor for a short time).
When the DOZEN is set to '0' the behavior is identical.
If however the STOP mode is used instead of WAIT mode the LPI2C stops operating in STOP mode when DOZEN is '0' and keeps operating when DOZEN is '1' (in STOP mode).
This is the compliment to the description of the DOZEN bit, which suggests a manual error in this case.
Note that when I did such tests on the LPIT it matched with the polarity in the user's manual but it also stops in WAIT mode. That means that DOZE mode includes WAIT for the LPIT but not for the LPI2C. Is this is what is expected??
I2C was created to be a simple protocol. Any attempt at improving it seems to fail.
It is not so much that these things are broken, it is the fact that they never get fixed, and rarely properly documented, that irritates me the most.