Non-blocking communications

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
Anonymous
Not applicable

I'm setting up some I2C communications (although, quickly perusing the datasheet for the UART and SPI components, I think that analogous issues may exist there too) and I'm confused by the API.

   

It seems that all the sending/receiving functions are blocking. Are there no functions for initiating a transfer, letting the hardware mess with it at the much slower I2C clock, and then have it trigger an interrupt?

   

I may just be confused. I thought the idea of the hardware I2C transceiver was to allow it to operate on the slow clock while the processor does other things on the fast clock? Or is this scenario supported through messing directly with the registers, but not supported through the API?

0 Likes
9 Replies
Anonymous
Not applicable

Not all APIs are blocking APIs. It is possible to delegate the I2C data transfer job to the I2C hardware block while the CPU is busy with something else. This is possible with the two non-blocking statements,

   
        
  1. I2C_MasterWriteBuf(uint8 slaveAddress, uint8 * wrData, uint8 cnt, uint8 mode) 
  2.     
  3. uint8 I2C_MasterReadBuf(uint8 slaveAddress, uint8 * rdData, uint8 cnt, uint8 mode)
  4.    
   

The paraameter mode determines the nature of the transfer(Complete I2C transfer, No Stop bit, Starts with a repeat start and so on).

   

Note that there are other set of APIs which are clearly described as blocking statement in the datasheet:

   

Example: uint8 I2C_MasterSendStart(uint8 slaveAddress, uint8 R_nW)

   

uint8 I2C_MasterWriteByte(uint8 theByte)

0 Likes
Anonymous
Not applicable

Ahh, so they are.

   

So am I correct in thinking that code like this will be basically ok?

   

 

   

// this function should transfer one byte (the slave's register address) to the slave, then issue a restart, then read two bytes from the slave

   

uint16 ReadSlaveRegister(uint8 slaveAddress, uint8 slaveRegisterAddress)

   

{

   

uint16 result;

   
   

uint8 status;

   

status = I2C_MasterWriteBuf(slaveAddress, &slaveRegisterAddress, sizeof(uint8), I2C_MODE_NO_STOP);

   

// error handling

   

// call FreeRTOS to sleep this thread for a while until the I2C is done doing its thing, presumably by adding a line to the end of its ISR that will give our semaphore once the transfer is over or breaks

   

status = I2C_MasterReadBuf(slaveAddress, &result, sizeof(uint16), I2C_MODE_REPEAT_START | I2C_MODE_COMPLETE_XFER);

   

// error handling

   

// call FreeRTOS to sleep again in the same way

   

return result;

   

}

0 Likes
Anonymous
Not applicable

Oh, and that it will NAK the 2nd byte of the read and then send a stop condition as well? (Since I2C_MODE_COMPLETE_XFER was specified.)

0 Likes
Anonymous
Not applicable

It does seem to work that way (up to endian-ness, which I had a little hiccup with since I hadn't had prior occasion to learn that the PSoC5 is little-endian).

   

Is there a better option than tinkering with the auto-generated ISR? It doesn't seem to include a block that the auto-generator won't overwrite, it doesn't seem to have a callback.

   

Maybe the component should include an option for an irq output that is pulsed when a transfer completes (successfully or otherwise), and I could hook my own ISR to that?

0 Likes
Anonymous
Not applicable

Hi Doug McClean,

   

 

   

In your previous post, you had used the API as follows:

   

status = I2C_MasterReadBuf(slaveAddress, &result, sizeof(uint16), I2C_MODE_REPEAT_START | I2C_MODE_COMPLETE_XFER);

   

 

   

The macro definition for

   

1) I2C_MODE_REPEAT_START = 0x01

   

2) I2C_MODE_COMPLETE_XFER = 0x00

   

3) I2C_MODE_NO_STOP = 0x02.

   

 

   

Hence, it is the 2 least significant bits which determine whether STOP is generated and REPEAT START is issued.

   

When LSB is set (1), REPEAT START is sent, else START is sent.

   

When last but one LSB is set (1), STOP is not generated. Else, STOP is generated.

   

 

   

Hence, ORing with  I2C_MODE_COMPLETE_XFER is not really necessary as it is equivalent to ORing with 0x00.

0 Likes
Anonymous
Not applicable

Thanks dasq, that makes perfect sense.

   

Do you have any advice on the recommended way to get an interrupt at the end of the hardware- and cypress-generated-firmware- handled portion of an I2C transaction?

   

An optional irq output pin on the component might be a lot to ask, and would needlessly tie up another processor interrupt.

   

Perhaps a better approach would be just to add another section similar to this one (which I assume is maintained unmodified by the code generator):

   

                            /*******************************************/
                            /*  Place code to prepare read buffer here */
                            /*******************************************/
                            /* `#START SW_PREPARE_READ_BUF` */

                            /* `#END`  */

   

to I2C_INT.c?

   

Unfortunately it looks like there is no one line in the ISR where this could be added. If I understand properly, it would need to be each place where I2C_State is assigned the new value I2C_SM_IDLE? Maybe introduce a new local called I2C_State_Next, assign I2C_SM_IDLE to it, and then have one place after all the nested if/else logic but before assigning I2C_State_Next to I2C_State where the developer could add some custom logic to the ISR?

   

What I am trying to do is call the FreeRTOS function xSemaphoreGiveFromISR whenever the hardware-and-generated-firmware part of an I2C transaction is finished, so that the thread that requested it will be awoken by the scheduler.

0 Likes
Anonymous
Not applicable

I'm bumping this back up the forum because I have tried everything I can think of and I still can't find the appropriate place to put the xSemaphoreGiveFromISR.

   

Does anyone have any suggestions?

0 Likes
Bob_Marlowe
Level 10
Level 10
First like given 50 questions asked 10 questions asked

That looks like a complicatetd one:

   

The IDLE-State is set (of course) within the interrupt-routine of the I2C communication. To raise another interrupt within an interrupt-routine where a RTOS-function for task-switching is called looks a bit hazardous.

   

So let's try to bake smaller breads.

   

What, if you use an interrupt-driven polling? Ie. a timer that interrupts and just checks for the state of I2C and when it's IO is done releases the Semaphore? Due to the lengthy i/o of I2C (compared to the CPU) it won't matter much, if the semaphore release comes 1 to 10ms later than the end of transmission really was.

   

Well, I know that tis thing is not as easy as I just wrote, the interrupt-routine will have to know about started I/O and other things, but it can be an approach.

   

Happy coding, don't let you be interrupted

   

Bob

0 Likes
Anonymous
Not applicable

Thanks Bob, that's not a bad idea.

0 Likes