Intertask Communication and resource

tekkkon wrote on Monday, November 21, 2011:

Hi All,

I am doing a test program on LPC2292 processor.

There are three tasks running. TaskA and TaskB may want to read/write data on another device through I2C interfaces at some point. The two tasks can call my I2CRead and I2CWrite functions and then call the callback functions after the I2C network transaction is done. When TaskA and TaskB individually call the I2C functions, everything works OK. But when TaskA is keep reading slave device register every second, TaskB trying to write one byte to the slave device may blow up the processor.

I tried to use the resource management API(xSemaphoreCreateCounting), but it didn’t help. I am thinking probably because something goes wrong when TaskB try to write a byte while the I2C read is not finished in TaskA. TaskB has a higher priority than TaskA.

Do I need a semaphore in I2C ISR when the I2CRead is finished, to inform the task? 

Could it solve the problem if I use xSemaphoreTake and xSemaphoreGive in TaskA/TaskB before calling I2CRead and I2Cwrite?

Is a third task, like I2CTask needed? So every time when TaskA/TaskB want to get access to the I2C interface it will wake up the I2CTask and let the I2CTask do its job.

Thanks in advance!


edwards3 wrote on Monday, November 21, 2011:

I don’t know enough about your library code, but would think a mutex was what you need. Don’t use a mutex in an interrupt through!

something like:

xMutexTake( I2CMutex, portMAX_DELAY );
xMutexGive( I2CMutex );

Then the same for read. 

You should not give the mutex back until the I2C comms has finished. So you can poll the I2C and only return from I2CWrite when all the data is transmitted. Or if you are using interrupt, have the interrupt give a semaphore, to say the write is complete. Then you code would be something like

xMutexTake( I2CMutex, portMAX_DELAY );
xSemaphoreTake( xI2CFinishedSemaphore, portMAX_DELAY );
/* Now you know the mutex can be released. */
xMutexGive( I2CMutex );

and the I2C interrupt calls xSemaphoreGiveFromISR() on xI2CFinishedSemaphore.

tekkkon wrote on Monday, November 21, 2011:

Thank you so much edward, this is really helpful!!!

I don’t have xMutexTake and xMutexGive API in my library.
Can I use xSemaphoreCreateMutex, xSemaphoreTake and xSemaphoreGive instead of xMutexTake and xMutexGive?  I think they probably do the same thing.


edwards3 wrote on Monday, November 21, 2011:

Yes. My mistake. You would think I would know the API functions by now. You are exactly right.

tekkkon wrote on Tuesday, November 22, 2011:

Here is another question. How to do a fast context switch?

For example. In the mid of TaskA or TaskB, they need send data to slave and receive data back. So the TaskA/TaskB send out a message to the I2C task to wake it up, the I2C task finish all the read/write transactions as soon as possible and send data back to TaskA/TaskB. Then TaskA/TaskB keep running from where the scheduler switched to I2C task.

I think the above is possible. I know how to send message through the queue to wake the I2C task up. But how to send the processed message back to TaskA/TaskB? Another queue?  This may happened frequently when the scheduler start running, so is there an optimal  inter-task communication mechanism to use?

The I2C task read/write 16 bytes generally cost 2.8ms. Is there anything I need to pay special attention to? Like the size of the queue(used to communication between TaskA/TaskB and I2CTask)?   And the timing issue?

Thank you for your help!


richard_damon wrote on Tuesday, November 22, 2011:

First, in FreeRTOS all task switches take the same time, so there are not “fast” switches. If you need to send “data” between tasks, the simplest thing to use is a queue. If you just need to send a “done” signal than a semaphore may be sufficient (which is really just a queue without a data area).

If you are sending 16 byte messages than it probably makes sense to have the queue be at least 16 bytes long so the task can queue up the message and then resume processing (unless it needs to wait for a response). An alternate option would be rather than send all the characters via the queue, to send a buffer address, and the receiving task just uses the data where it was, and somehow lets the sending task know the buffer is free again to reuse.

An alternative method, which is what I have been using, is to NOT make a special I2C task, but have a library that any task that wants to send a message out via I2C call, which first interlocks with a mutex so only a given task has “control” over the I2C when it is active, and then it queues up the data for an ISR, and then kick starts the hardware. The ISR then continues to send the data from the queue. If the operation is a read, then the library waits on another queue where the ISR puts the return data. Since there isn’t much to do on each interrupt, it doesn’t make much sense to actually drop down to a task to do that level of I/O.