/* | |
FreeRTOS V6.0.5 - Copyright (C) 2010 Real Time Engineers Ltd. | |
*************************************************************************** | |
* * | |
* If you are: * | |
* * | |
* + New to FreeRTOS, * | |
* + Wanting to learn FreeRTOS or multitasking in general quickly * | |
* + Looking for basic training, * | |
* + Wanting to improve your FreeRTOS skills and productivity * | |
* * | |
* then take a look at the FreeRTOS eBook * | |
* * | |
* "Using the FreeRTOS Real Time Kernel - a Practical Guide" * | |
* http://www.FreeRTOS.org/Documentation * | |
* * | |
* A pdf reference manual is also available. Both are usually delivered * | |
* to your inbox within 20 minutes to two hours when purchased between 8am * | |
* and 8pm GMT (although please allow up to 24 hours in case of * | |
* exceptional circumstances). Thank you for your support! * | |
* * | |
*************************************************************************** | |
This file is part of the FreeRTOS distribution. | |
FreeRTOS is free software; you can redistribute it and/or modify it under | |
the terms of the GNU General Public License (version 2) as published by the | |
Free Software Foundation AND MODIFIED BY the FreeRTOS exception. | |
***NOTE*** The exception to the GPL is included to allow you to distribute | |
a combined work that includes FreeRTOS without being obliged to provide the | |
source code for proprietary components outside of the FreeRTOS kernel. | |
FreeRTOS is distributed in the hope that it will be useful, but WITHOUT | |
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
more details. You should have received a copy of the GNU General Public | |
License and the FreeRTOS license exception along with FreeRTOS; if not it | |
can be viewed here: http://www.freertos.org/a00114.html and also obtained | |
by writing to Richard Barry, contact details for whom are available on the | |
FreeRTOS WEB site. | |
1 tab == 4 spaces! | |
http://www.FreeRTOS.org - Documentation, latest information, license and | |
contact details. | |
http://www.SafeRTOS.com - A version that is certified for use in safety | |
critical systems. | |
http://www.OpenRTOS.com - Commercial support, development, porting, | |
licensing and training services. | |
*/ | |
/* Standard includes. */ | |
#include <stdlib.h> | |
/* Scheduler include files. */ | |
#include "FreeRTOS.h" | |
#include "task.h" | |
#include "queue.h" | |
#include "semphr.h" | |
/* Application includes. */ | |
#include "i2c.h" | |
/*-----------------------------------------------------------*/ | |
/* Bit definitions within the I2CONCLR register. */ | |
#define i2cSTA_BIT ( ( unsigned char ) 0x20 ) | |
#define i2cSI_BIT ( ( unsigned char ) 0x08 ) | |
#define i2cSTO_BIT ( ( unsigned char ) 0x10 ) | |
#define i2cAA_BIT ( ( unsigned char ) 0x04 ) | |
/* Status codes for the I2STAT register. */ | |
#define i2cSTATUS_START_TXED ( 0x08 ) | |
#define i2cSTATUS_REP_START_TXED ( 0x10 ) | |
#define i2cSTATUS_TX_ADDR_ACKED ( 0x18 ) | |
#define i2cSTATUS_DATA_TXED ( 0x28 ) | |
#define i2cSTATUS_RX_ADDR_ACKED ( 0x40 ) | |
#define i2cSTATUS_DATA_RXED ( 0x50 ) | |
#define i2cSTATUS_LAST_BYTE_RXED ( 0x58 ) | |
/* Constants for operation of the VIC. */ | |
#define i2cCLEAR_VIC_INTERRUPT ( 0 ) | |
/* Misc constants. */ | |
#define i2cJUST_ONE_BYTE_TO_RX ( 1 ) | |
#define i2cBUFFER_ADDRESS_BYTES ( 2 ) | |
/* End the current transmission and free the bus. */ | |
#define i2cEND_TRANSMISSION( lStatus ) \ | |
{ \ | |
I2C_I2CONCLR = i2cAA_BIT; \ | |
I2C_I2CONSET = i2cSTO_BIT; \ | |
eCurrentState = eSentStart; \ | |
lTransactionCompleted = lStatus; \ | |
} | |
/*-----------------------------------------------------------*/ | |
/* Valid i2c communication states. */ | |
typedef enum | |
{ | |
eSentStart, /*<< Last action was the transmission of a start bit. */ | |
eSentAddressForWrite, /*<< Last action was the transmission of the slave address we are to write to. */ | |
eSentAddressForRead, /*<< Last action was the transmission of the slave address we are to read from. */ | |
eSentData, /*<< Last action was the transmission of a data byte. */ | |
eReceiveData /*<< We expected data to be received. */ | |
} I2C_STATE; | |
/*-----------------------------------------------------------*/ | |
/* Points to the message currently being sent. */ | |
volatile xI2CMessage *pxCurrentMessage = NULL; | |
/* The queue of messages waiting to be transmitted. */ | |
static xQueueHandle xMessagesForTx; | |
/* Flag used to indicate whether or not the ISR is amid sending a message. */ | |
unsigned long ulBusFree = ( unsigned long ) pdTRUE; | |
/* Setting this to true will cause the TCP task to think a message is | |
complete and thus restart. It can therefore be used under error states | |
to force a restart. */ | |
volatile long lTransactionCompleted = pdTRUE; | |
/*-----------------------------------------------------------*/ | |
void vI2CISRCreateQueues( unsigned portBASE_TYPE uxQueueLength, xQueueHandle *pxTxMessages, unsigned long **ppulBusFree ) | |
{ | |
/* Create the queues used to hold Rx and Tx characters. */ | |
xMessagesForTx = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( xI2CMessage * ) ); | |
/* Pass back a reference to the queue and bus free flag so the I2C API file | |
can post messages. */ | |
*pxTxMessages = xMessagesForTx; | |
*ppulBusFree = &ulBusFree; | |
} | |
/*-----------------------------------------------------------*/ | |
/* The ISR entry point. */ | |
void vI2C_ISR_Wrapper( void ) __attribute__ (( naked )); | |
/* The ISR function to perform the actual work. This must be a separate | |
function from the wrapper to ensure the correct stack frame is set up. */ | |
void vI2C_ISR_Handler( void ); | |
/*-----------------------------------------------------------*/ | |
void vI2C_ISR_Wrapper( void ) | |
{ | |
/* Save the context of the interrupted task. */ | |
portSAVE_CONTEXT(); | |
/* Call the handler to perform the actual work. This must be a | |
separate function to ensure the correct stack frame is set up. */ | |
vI2C_ISR_Handler(); | |
/* Restore the context of whichever task is going to run next. */ | |
portRESTORE_CONTEXT(); | |
} | |
/*-----------------------------------------------------------*/ | |
void vI2C_ISR_Handler( void ) | |
{ | |
/* Holds the current transmission state. */ | |
static I2C_STATE eCurrentState = eSentStart; | |
static long lMessageIndex = -i2cBUFFER_ADDRESS_BYTES; /* There are two address bytes to send prior to the data. */ | |
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; | |
long lBytesLeft; | |
/* The action taken for this interrupt depends on our current state. */ | |
switch( eCurrentState ) | |
{ | |
case eSentStart : | |
/* We sent a start bit, if it was successful we can | |
go on to send the slave address. */ | |
if( ( I2C_I2STAT == i2cSTATUS_START_TXED ) || ( I2C_I2STAT == i2cSTATUS_REP_START_TXED ) ) | |
{ | |
/* Send the slave address. */ | |
I2C_I2DAT = pxCurrentMessage->ucSlaveAddress; | |
if( pxCurrentMessage->ucSlaveAddress & i2cREAD ) | |
{ | |
/* We are then going to read bytes back from the | |
slave. */ | |
eCurrentState = eSentAddressForRead; | |
/* Initialise the buffer index so the first byte goes | |
into the first buffer position. */ | |
lMessageIndex = 0; | |
} | |
else | |
{ | |
/* We are then going to write some data to the slave. */ | |
eCurrentState = eSentAddressForWrite; | |
/* When writing bytes we first have to send the two | |
byte buffer address so lMessageIndex is set negative, | |
when it reaches 0 it is time to send the actual data. */ | |
lMessageIndex = -i2cBUFFER_ADDRESS_BYTES; | |
} | |
} | |
else | |
{ | |
/* Could not send the start bit so give up. */ | |
i2cEND_TRANSMISSION( pdFAIL ); | |
} | |
I2C_I2CONCLR = i2cSTA_BIT; | |
break; | |
case eSentAddressForWrite : | |
/* We sent the address of the slave we are going to write to. | |
If this was acknowledged we can go on to send the data. */ | |
if( I2C_I2STAT == i2cSTATUS_TX_ADDR_ACKED ) | |
{ | |
/* Start the first byte transmitting which is the | |
first byte of the buffer address to which the data will | |
be sent. */ | |
I2C_I2DAT = pxCurrentMessage->ucBufferAddressHighByte; | |
eCurrentState = eSentData; | |
} | |
else | |
{ | |
/* Address was not acknowledged so give up. */ | |
i2cEND_TRANSMISSION( pdFAIL ); | |
} | |
break; | |
case eSentAddressForRead : | |
/* We sent the address of the slave we are going to read from. | |
If this was acknowledged we can go on to read the data. */ | |
if( I2C_I2STAT == i2cSTATUS_RX_ADDR_ACKED ) | |
{ | |
eCurrentState = eReceiveData; | |
if( pxCurrentMessage->lMessageLength > i2cJUST_ONE_BYTE_TO_RX ) | |
{ | |
/* Don't ack the last byte of the message. */ | |
I2C_I2CONSET = i2cAA_BIT; | |
} | |
} | |
else | |
{ | |
/* Something unexpected happened - give up. */ | |
i2cEND_TRANSMISSION( pdFAIL ); | |
} | |
break; | |
case eReceiveData : | |
/* We have just received a byte from the slave. */ | |
if( ( I2C_I2STAT == i2cSTATUS_DATA_RXED ) || ( I2C_I2STAT == i2cSTATUS_LAST_BYTE_RXED ) ) | |
{ | |
/* Buffer the byte just received then increment the index | |
so it points to the next free space. */ | |
pxCurrentMessage->pucBuffer[ lMessageIndex ] = I2C_I2DAT; | |
lMessageIndex++; | |
/* How many more bytes are we expecting to receive? */ | |
lBytesLeft = pxCurrentMessage->lMessageLength - lMessageIndex; | |
if( lBytesLeft == ( unsigned long ) 0 ) | |
{ | |
/* This was the last byte in the message. */ | |
i2cEND_TRANSMISSION( pdPASS ); | |
/* If xMessageCompleteSemaphore is not null then there | |
is a task waiting for this message to complete and we | |
must 'give' the semaphore so the task is woken.*/ | |
if( pxCurrentMessage->xMessageCompleteSemaphore ) | |
{ | |
xSemaphoreGiveFromISR( pxCurrentMessage->xMessageCompleteSemaphore, &xHigherPriorityTaskWoken ); | |
} | |
/* Are there any other messages to transact? */ | |
if( xQueueReceiveFromISR( xMessagesForTx, &pxCurrentMessage, &xHigherPriorityTaskWoken ) == pdTRUE ) | |
{ | |
/* Start the next message - which was | |
retrieved from the queue. */ | |
I2C_I2CONSET = i2cSTA_BIT; | |
} | |
else | |
{ | |
/* No more messages were found to be waiting for | |
transaction so the bus is free. */ | |
ulBusFree = ( unsigned long ) pdTRUE; | |
} | |
} | |
else | |
{ | |
/* There are more bytes to receive but don't ack the | |
last byte. */ | |
if( lBytesLeft <= i2cJUST_ONE_BYTE_TO_RX ) | |
{ | |
I2C_I2CONCLR = i2cAA_BIT; | |
} | |
} | |
} | |
else | |
{ | |
/* Something unexpected happened - give up. */ | |
i2cEND_TRANSMISSION( pdFAIL ); | |
} | |
break; | |
case eSentData : | |
/* We sent a data byte, if successful send the next byte in | |
the message. */ | |
if( I2C_I2STAT == i2cSTATUS_DATA_TXED ) | |
{ | |
/* Index to the next byte to send. */ | |
lMessageIndex++; | |
if( lMessageIndex < 0 ) | |
{ | |
/* lMessage index is still negative so we have so far | |
only sent the first byte of the buffer address. Send | |
the second byte now, then initialise the buffer index | |
to zero so the next byte sent comes from the actual | |
data buffer. */ | |
I2C_I2DAT = pxCurrentMessage->ucBufferAddressLowByte; | |
} | |
else if( lMessageIndex < pxCurrentMessage->lMessageLength ) | |
{ | |
/* Simply send the next byte in the tx buffer. */ | |
I2C_I2DAT = pxCurrentMessage->pucBuffer[ lMessageIndex ]; | |
} | |
else | |
{ | |
/* No more bytes in this message to be send. Finished | |
sending message - send a stop bit. */ | |
i2cEND_TRANSMISSION( pdPASS ); | |
/* If xMessageCompleteSemaphore is not null then there | |
is a task waiting for this message to be sent and the | |
semaphore must be 'given' to wake the task. */ | |
if( pxCurrentMessage->xMessageCompleteSemaphore ) | |
{ | |
xSemaphoreGiveFromISR( pxCurrentMessage->xMessageCompleteSemaphore, &xHigherPriorityTaskWoken ); | |
} | |
/* Are there any other messages to transact? */ | |
if( xQueueReceiveFromISR( xMessagesForTx, &pxCurrentMessage, &xHigherPriorityTaskWoken ) == pdTRUE ) | |
{ | |
/* Start the next message from the Tx queue. */ | |
I2C_I2CONSET = i2cSTA_BIT; | |
} | |
else | |
{ | |
/* No more message were queues for transaction so | |
the bus is free. */ | |
ulBusFree = ( unsigned long ) pdTRUE; | |
} | |
} | |
} | |
else | |
{ | |
/* Something unexpected happened, give up. */ | |
i2cEND_TRANSMISSION( pdFAIL ); | |
} | |
break; | |
default : | |
/* Should never get here. */ | |
eCurrentState = eSentStart; | |
break; | |
} | |
/* Clear the interrupt. */ | |
I2C_I2CONCLR = i2cSI_BIT; | |
VICVectAddr = i2cCLEAR_VIC_INTERRUPT; | |
if( xHigherPriorityTaskWoken ) | |
{ | |
portYIELD_FROM_ISR(); | |
} | |
} | |
/*-----------------------------------------------------------*/ | |