Thank you for your feedback, Mark. The task, TASK_LED_DRIVER, does not use and fnRead() functions; it only uses fnWrite() and assumes the data was transferred. The task TASK_ALTITUDE_SENSOR needs to read the altitude and temperature from the sensor, so it uses both fnRead() and fnWrite() commands. Therefore, there should not be any confusion on which task was commanding the read, but I think I have identified the source of the problem I am having.
Looking over the uTasker code, I think there is the possibility of a missed wake up owner of fnRead() task:
1. A fnRead() takes place in TASK_ALTITUDE_SENSOR and changes the ucState to RX_ACTIVE and TX_ACTIVE in file kinetis_LPI2C.h line 358 of function fnTxI2C:
I2C_tx_control[Channel]->ucState |= (RX_ACTIVE | TX_ACTIVE);
2. The uTaskerStateChange(ptrRxControl->wake_task, UTASKER_ACTIVATE) does not happen until all bytes are transferred into the I2CPortID message queue in the interrupt handler function, fnI2C_Handler(), file kinetis_LPI2C.h:
static void fnI2C_Handler(KINETIS_LPI2C_CONTROL *ptrLPI2C, int iChannel) // general LPI2C interrupt handler
{
I2CQue *ptrTxControl = I2C_tx_control[iChannel];
if ((ptrLPI2C->LPI2C_MIER & ptrLPI2C->LPI2C_MSR) & LPI2C_MSR_SDF) { // stop condition has completed
fnLogEvent('S', ptrLPI2C->LPI2C_MSR);
ptrLPI2C->LPI2C_MIER = 0; // disable all interrupt sources
WRITE_ONE_TO_CLEAR(ptrLPI2C->LPI2C_MSR, LPI2C_MSR_SDF); // clear interrupt flag
if ((ptrTxControl->ucState & RX_ACTIVE) != 0) {
I2CQue *ptrRxControl = I2C_rx_control[iChannel];
ptrRxControl->msgs++;
if (ptrRxControl->wake_task != 0) { // wake up the receiver task if desired
uTaskerStateChange(ptrRxControl->wake_task, UTASKER_ACTIVATE); // wake up owner task
}
}
else {
if (ptrTxControl->wake_task != 0) {
uTaskerStateChange(ptrTxControl->wake_task, UTASKER_ACTIVATE); // wake up owner task since the transmission has terminated
}
}
ptrTxControl->ucState &= ~(TX_WAIT | TX_ACTIVE | RX_ACTIVE); // interface is idle
What I think is happening is that while the fnRead() communication on the physical I2C channel 0 is taking place, a fnWrite() command is called in task TASK_LED_DRIVER. Calling fnWrite() will set the ptrTxControl->ucState to TX_ACTIVE and clear RX_ACTIVE before the " if ((ptrTxControl->ucState & RX_ACTIVE) != 0)" conditional is executed in the interrupt handler fnI2C_Handler(). If this happens, then the uTaskerStateChange(ptrRxControl->wake_task, UTASKER_ACTIVATE) statement will not happen. This will cause the TASK_ALTITUDE_SENSOR not to wake up and handle the data read from the I2C in the I2CPortID buffer.
I have found that if I disable the TASK_LED_DRIVER, the returned data from the fnRead()s in TASK_ALTITUDE_SENSOR are handled, otherwise, the data is not handled because the TASK_LED_DRIVER is not scheduled to wake up. By disabling TASK_LED_DRIVER, I can see that it does affect the wake up scheduling of TASK_ALTITUDE_SENSOR. I checked the address of both where uState is set, line 358 in file kinetis_LPI2C.h, function fnTxI2C() :
if ((ucAddress & 0x01) != 0) { // reading from the slave
(358) I2C_tx_control[Channel]->ucState |= (RX_ACTIVE | TX_ACTIVE);
ptI2CQue->I2C_queue.chars -= 3;
fnLogEvent('g', (unsigned char)(ptI2CQue->I2C_queue.chars));
I2C_rx_control[Channel]->wake_task = *ptI2CQue->I2C_queue.get++; // enter task to be woken when reception has completed
if (ptI2CQue->I2C_queue.get >= ptI2CQue->I2C_queue.buffer_end) {
ptI2CQue->I2C_queue.get = ptI2CQue->I2C_queue.QUEbuffer; // handle circular buffer
}
}
else {
I2C_tx_control[Channel]->ucState &= ~(RX_ACTIVE);
I2C_tx_control[Channel]->ucState |= (TX_ACTIVE); // writing to the slave
ptI2CQue->I2C_queue.chars -= (ptI2CQue->ucPresentLen + 1); // the remaining queue content
fnLogEvent('h', (unsigned char)(ptI2CQue->I2C_queue.chars));
}
And where it is checked, line 135 in file kinetis_LPI2C.h, function fnI2C_Handler():
static void fnI2C_Handler(KINETIS_LPI2C_CONTROL *ptrLPI2C, int iChannel) // general LPI2C interrupt handler
{
I2CQue *ptrTxControl = I2C_tx_control[iChannel];
if ((ptrLPI2C->LPI2C_MIER & ptrLPI2C->LPI2C_MSR) & LPI2C_MSR_SDF) { // stop condition has completed
fnLogEvent('S', ptrLPI2C->LPI2C_MSR);
ptrLPI2C->LPI2C_MIER = 0; // disable all interrupt sources
WRITE_ONE_TO_CLEAR(ptrLPI2C->LPI2C_MSR, LPI2C_MSR_SDF); // clear interrupt flag
(135) if ((ptrTxControl->ucState & RX_ACTIVE) != 0) {
I2CQue *ptrRxControl = I2C_rx_control[iChannel];
ptrRxControl->msgs++;
if (ptrRxControl->wake_task != 0) { // wake up the receiver task if desired
uTaskerStateChange(ptrRxControl->wake_task, UTASKER_ACTIVATE); // wake up owner task
}
}
else {
if (ptrTxControl->wake_task != 0) {
uTaskerStateChange(ptrTxControl->wake_task, UTASKER_ACTIVATE); // wake up owner task since the transmission has terminated
}
}
ptrTxControl->ucState &= ~(TX_WAIT | TX_ACTIVE | RX_ACTIVE); // interface is idle
Both "I2C_tx_control[Channel]->ucState" and "ptrTxControl->ucState" point to the same location (0x200001CF) using the segger j-link debugger. This tells me that it is possible that ucState can be changed before it is handled. Was this the intention of uTasker I2C design? If not, could you provide a solution (fix) that allows an I2C read to be handled even if another task is executing a I2C write? My thought on a work around right now is to queue up all I2C reads and writes from the two tasks and handle them in one task that will allow only one fnRead() or fnWrite() command at a time until the task that commanded the read or write is woken.
Please let me know you thoughts. I appreciate your feedback.