- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?
- Labels:
-
PSoC 5LP
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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,
- I2C_MasterWriteBuf(uint8 slaveAddress, uint8 * wrData, uint8 cnt, uint8 mode)
- uint8 I2C_MasterReadBuf(uint8 slaveAddress, uint8 * rdData, uint8 cnt, uint8 mode)
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)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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;
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks Bob, that's not a bad idea.