This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
I want to use the I2C Master peripheral in a non-blocking way (no infinite while loops).
To do so, I need to learn how to use the TI HalCoGen I2C API with Interrupts.
Sadly I didn't find any examples:
- The TI e2e search feature doesn't show me any results that discuss this topic
- The Official documentation does not contain examples using HalCoGen generated code
- The HalCoGen example folder has 0 mentions of i2cNotification
So I'm turning to this forum :^ )
---
I'm mainly looking for guidance regarding
- How to properly handle STOP in i2cNotification?
- How to properly handle errors in i2cNotification?
- How to properly wait for the bus to be ready before doing other things?
Here's my code right now:
static volatile bool g_rx_done = false; static volatile bool g_tx_done = false; int i2c_transmit(i2cBASE_t *base, // uint8_t slave_address, size_t data_size, uint8_t const data[data_size]) { if (i2cIsMasterReady(base) != true) { return -1; //< Busy } g_tx_done = false; i2cSetSlaveAdd(base, slave_address); i2cSetDirection(base, I2C_TRANSMITTER); i2cSetMode(base, I2C_MASTER); i2cSetCount(base, data_size); i2cSetStop(base); i2cSetStart(base); i2cSend(base, data_size, data); return 0; } bool i2c_is_transmit_done(i2cBASE_t *base) { return i2cIsMasterReady(base) && g_tx_done; // } int i2c_start_receiving(i2cBASE_t *base, // uint8_t slave_address, size_t data_size, uint8_t data[data_size]) { if (i2cIsMasterReady(base) != true) { return -1; //< Busy } g_rx_done = false; i2cSetSlaveAdd(base, slave_address); i2cSetDirection(base, I2C_RECEIVER); i2cSetMode(base, I2C_MASTER); i2cSetCount(base, data_size); i2cSetStop(base); i2cSetStart(base); i2cReceive(base, data_size, data); return 0; } bool i2c_is_receive_done(i2cBASE_t *base) { return i2cIsMasterReady(base) && g_rx_done; // } void i2cNotification(i2cBASE_t *base, uint32 flags) { if (flags & I2C_SCD_INT) //< Stop condition detect { i2cClearSCD(base); } if (flags & I2C_TX_INT) //< Transmit data ready { g_tx_done = true; } if (flags & I2C_RX_INT) //< Receive data ready { g_rx_done = true; } if (flags & I2C_AL_INT) //< Arbitration lost (AL) { } if (flags & I2C_NACK_INT) //< No acknowledgement (NACK) { } if (flags & I2C_ARDY_INT) //< Register access ready (ARDY) { } if (flags & I2C_AAS_INT) //< Address as slave (AAS) { } }
Any advice is appreciated.
Regards,
Gabriel
Hi Gabriel,
I agreed with you that there is no well tested example on i2c with interrupt.
I will suggest you refer below two threads that might help you for some extent.
- How to properly handle STOP in i2cNotification?
It just depends on your requirement.
For example, if i want to send some n bytes to slave and want to send stop condition then we can just set stop condition after set the count bytes and before calling i2csend function something like what you did.
i2cSetSlaveAdd(base, slave_address); i2cSetDirection(base, I2C_TRANSMITTER); i2cSetMode(base, I2C_MASTER); i2cSetCount(base, data_size); i2cSetStop(base); i2cSetStart(base); i2cSend(base, data_size, data);
What happens in this case was that it will send the configured number of bytes in interrupt mode (if we enable) and then it will send stop condition automatically.
Similar procedure for receiving also.
However, in some cases, we might require restarting the i2c without sending stop condition.
For example, see above temperature sensor reading method, here we are doing both sending and reading without stop condition in between. In these cases, we should not send stop condition after configuring number of bytes. Instead of after completing sending bytes we should again set start condition and do the receiving operation and then we can send the stop condition.
I want to use the I2C Master peripheral in a non-blocking way (no infinite while loops).
Even though we don't want to wait like infinite loops in polling method but again we should need to build a state machine on while(1) to check the status and do the next operation.
For example i am giving code for above temperature reading procedure.
int state =0; unsigned char Command_Byte = 0xAA; bool prev_cmd = false; unsigned char Temperature[2]; int main(void) { i2cInit(); while(1) { I2c_Temperature_Reading(); /*Other Tasks*/ } return 0; } void I2c_Temperature_Reading(void) { switch(state) { case 0: i2cSetSlaveAdd(base, slave_address); i2cSetDirection(base, I2C_TRANSMITTER); i2cSetMode(base, I2C_MASTER); i2cSetCount(base, 1); i2cSetStart(base); i2cSend(base, data_size, &Command_Byte); state =1; break; case 1: if(prev_cmd == true) { prev_cmd = false; i2cSetSlaveAdd(base, slave_address); i2cSetDirection(base, I2C_RECEIVER); i2cSetMode(base, I2C_MASTER); i2cSetCount(base, 2); i2cSetStop(base); i2cSetStart(base); i2cReceive(base, data_size, Temperature); } state =2; break; case 2: if(prev_cmd == true) { prev_cmd = false; /*Now we can Necessary operation on temperature data like printing/displaying/using for other tasks*/ /*If we want to read temperature again then we should call case-0 again*/ state =0; } break; default: break; } } void i2cNotification(i2cBASE_t *base, uint32 flags) { if (flags & I2C_SCD_INT) //< Stop condition detect { i2cClearSCD(base); } if (flags & I2C_TX_INT) //< Transmit data ready { g_tx_done = true; prev_cmd = true; } if (flags & I2C_RX_INT) //< Receive data ready { g_rx_done = true; prev_cmd = true; } if (flags & I2C_AL_INT) //< Arbitration lost (AL) { state =0; } if (flags & I2C_NACK_INT) //< No acknowledgement (NACK) { } if (flags & I2C_ARDY_INT) //< Register access ready (ARDY) { } if (flags & I2C_AAS_INT) //< Address as slave (AAS) { } }
As you can see in above example we are not waiting until the bytes are sending instead of we just verifying whether the previous command executed or not successfully. If it is executed, then we are doing next step.
I hope this will be helpful for you to understand and implement i2c in interrupt mode.
- How to properly handle errors in i2cNotification?
It is again depending on your application requirement. For example, as you can see in my shared code, i am just restarting temperature reading my procedure if any arbitration.
--
Thanks & regards,
Jagadish.
Hi Jagadish,
Thanks for your answer.
if i want to send some n bytes to slave and want to send stop condition then we can just set stop condition after set the count bytes and before calling i2csend function something like what you did.
Thank you for clarifying how to use the i2cSetStop() function.
I'm still not sure about how to handle the Stop condition detect flag.
In your example, I see that you also clear SCD in the interrupt. Is this always a good idea?
What if the communication breaks and we never receive the I2C_SCD_INT interrupt? How do we recover from that?
if (flags & I2C_SCD_INT) //< Stop condition detect { i2cClearSCD(base); }
Thank you for sharing this code.
I see that you reset your state machine when you received an Arbitration Lost (AL).
Why don't you do the same for NACK ?
What about ARDY and AAS ?
How should we handle them?
Btw, in your state machine, is it possible that
state =2;
should be inside the if condition?
Regards,
Gabriel
I have another question for you Jagadish.
I don't understand the difference between those API:
bool i2cIsMasterReady(i2cBASE_t *i2c); bool i2cIsBusBusy(i2cBASE_t *i2c); uint32 i2cIsTxReady(i2cBASE_t *i2c); uint32 i2cIsRxReady(i2cBASE_t *i2c);
What are the nuances between them?
What's the difference between those polling APIs and receiving an I2C_TX_INT or an I2C_RX_INT interrupt?
Hi Gabriel,
What's the difference between those polling APIs and receiving an I2C_TX_INT or an I2C_RX_INT interrupt?
There is no difference between them. The only thing is that, you have to use those API's if you are developing your code in polling method.
For example, as you can see below highlighted code:
In this polling code, to write one new byte to the data register we are just waiting until the previous byte shifted by polling the I2C_TX_INT flag. If this flag sets means the previous written data value to the data register got moved into the shift register and we can write a new value into it. Here driver directly verifying the I2C_TX_INT bit but instead of here we can also use "i2cIsTxReady" API right?
But if it is a interrupt mode, we no need to poll this bit instead of we can just enable the I2C_TX_INT interrupt flag in the IMR register. If we did this then, if the data register value moved to the shift register then we immediately will get interrupt and control will shift to the handler code and there we can write an new value to the data register.
Similar behavior for I2C_RX_INT and i2cIsRxReady API as well.
i2cIsBusBusy - This API will also be helpful in polling mode to identify all the bytes are transmitted or all the bytes are received. I mean after start condition sent this busy flag will get set and after receiving stop condition it will get cleared.
So, it can be used as below:
As you can see after setting number of bytes to transmit and calling i2cSend API we are just waiting by polling i2cbusy flag to identify the transfer completed or not.
i2cIsMasterReady- Whenever a stop condition generated, that means a transaction completed then first BB(Bus Busy) flag will get cleared and then MST bit also get cleared.
Before starting a new transaction, this bit must be cleared, if this bit not cleared then we should not start the new transaction.
This checking should need to be add whenever we start a new transaction. For example as you can see above code, on very first transaction we didn't use this checking but before starting second transaction we are using this checking to make sure the first transaction completed or not. This checking can also be used in interrupt mode of code as well, after first transaction before starting second transaction it would be better to check this condition.
--
Thanks & Regards,
Jagadish.
Hi Jagadish,
Thanks for your answer.
It makes i2cIsMasterReady clearer.
This HalCoGen code you shared has an interesting implication.
If I transmit a message of only 1 byte, I will never receive a I2C_TX_INT, which means prev_cmd will never be set to True.
Without using polling, how can you tell if a transmission is done, if no interrupt is ever generated?
Also, for errors management:
When I send a badly formatted message, I receive a NACK.
After receiving a NACK it seems like I can't recover from it: the i2cIsMasterReady function always returns False.
How do we recover the bus from errors of that sort?
Hi Gabriel,
This HalCoGen code you shared has an interesting implication.
Yes, you are right. There is a bug and please refer below workaround from QJ.
--
Thanks & regards,
Jagadish.
Hi Gabriel,
When I send a badly formatted message, I receive a NACK.
After receiving a NACK it seems like I can't recover from it: the i2cIsMasterReady function always returns False.
How do we recover the bus from errors of that sort?
Why don't you enable the NACK interrupt and if that interrupt got generated then just try to restart the communication.
Restart the communication means, first send a Stop condition and then followed by start again and data etc.
--
Thanks & regards,
Jagadish.
Yes, you are right. There is a bug and please refer below workaround from QJ.
This is very helpful thank you!
It seems like I have the same problem as @user5967417: the RX Interrupt works but not the TX Interrupt.
When I put a breakpoint in the i2cInterrupt function I don't receive any TXRDY interrupt.
Here are my HalCoGen I2C configurations:
Pin mux
Peripheral
ISR
Do you have any Idea why I would not receive TXRDY ?
Why don't you enable the NACK interrupt and if that interrupt got generated then just try to restart the communication.
Interesting. I will test this idea today.
Thanks for your help.
Hi Gabriel,
Do you have any Idea why I would not receive TXRDY ?
What is the slave device that you connected to the master i2c?
What are the pullup resistors values you are using for i2c lines?
Can i get your complete project for verification?
--
Thanks & regards,
Jagadish.
Hi Jagadish,
What is the slave device that you connected to the master i2c?
I have tested with a SHT3x-DIS and a PT7C4563.
What are the pullup resistors values you are using for i2c lines?
The hardware has been validated with blocking i2c code. It would be surprising to me that it would be a hardware problem.
Can i get your complete project for verification?
Here's the halcogen file https://pastebin.com/VCtxvySA
Regards,
Hi Gabriel,
Make sure that slave sending ACK to the master.
If slave doesn't send ACK to the master, then master will not send any data to the slave.
For example, as you can see in above pic, master sends 0x08 address to the slave, but slave doesn't send any ACK to the master, so master will not send any further data to the slave.
If you see above pic master sends the 0x4C address to the slave and slave gave ACK and master shifting the data bytes further. So, make sure the following things from your end for to make proper communication between master and slave.
1. Make sure master SDA and SCL lines properly connected with slave SDA and SCL lines.
2. Make sure to connect external pull-up resistors on SDA and SCL lines. The pullup should neither be too high and nor be too small, preferably in between 2.2K to 4.7K.
3. Make sure to send a correct slave address from the master.
--
Thanks & Regards,
Jagadish.
Hi Jagadish,
Thanks for your feedback.
As the i2c transmission is working fine don't think it's hardware related.
I didn't verify the ACK from the slave, but as I received multiple bytes from the slave correctly, it seems unlikely that I would not receive NACK.
Are you sure there's no possibility it's another HalCoGen bug?
In this thread, it looks like even with the modified code, the user can't receive TX ISR.
Regards,
Gabriel
jagadish gundavarapu If you want I can create a separated thread, as we are entering a new subject: the HalCodeGen TX ISR problem.
Ok here's the new thread: e2e.ti.com/.../rm46l852-i2c-txrdy-interrupts-never-fire-while-the-rxrdy-works-fine