/**
******************************************************************************
*
* @file pios_i2c.c
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
* Parts by Thorsten Klose (tk@midibox.org) (tk@midibox.org)
* @brief I2C Enable/Disable routines
* @see The GNU Public License (GPL) Version 3
* @defgroup PIOS_I2C I2C Functions
* @{
*
*****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Project Includes */
#include "pios.h"
#if defined(PIOS_INCLUDE_I2C)
/* Options */
//#define USE_DEBUG_PINS
#ifdef PIOS_INCLUDE_FREERTOS
#define USE_FREERTOS
#endif
/* Global Variables */
volatile uint32_t PIOS_I2C_UnexpectedEvent;
/* Local types */
typedef union {
struct {
unsigned ALL:8;
};
struct {
unsigned BUSY:1;
unsigned STOP_REQUESTED:1;
unsigned ABORT_IF_FIRST_BYTE_0:1;
unsigned WRITE_WITHOUT_STOP:1;
unsigned INIRQ:1;
};
} TransferStateTypeDef;
typedef struct {
I2C_TypeDef *base;
uint8_t i2c_address;
uint8_t *tx_buffer_ptr;
uint8_t *rx_buffer_ptr;
volatile uint16_t buffer_len;
volatile uint16_t buffer_ix;
volatile TransferStateTypeDef transfer_state;
volatile int32_t transfer_error;
#ifdef USE_FREERTOS
xSemaphoreHandle sem_readySignal;
portBASE_TYPE xHigherPriorityTaskWoken;
xSemaphoreHandle xBusyMutex;
#endif
} I2CRecTypeDef;
/* Local Prototypes */
static void PIOS_I2C_InitPeripheral(void);
static void EV_IRQHandler(I2CRecTypeDef *i2cx);
static void ER_IRQHandler(I2CRecTypeDef *i2cx);
/* Local Variables */
static I2CRecTypeDef I2CRec;
/* Local Functions */
static void TransferStart(I2CRecTypeDef *i2cx);
static void TransferEnd(I2CRecTypeDef *i2cx);
/* Macros */
#ifdef USE_DEBUG_PINS
#define DEBUG_PIN_ISR 0
#define DEBUG_PIN_BUSY 1
#define DEBUG_PIN_WAIT 2
#define DEBUG_PIN_ASSERT 7
#define DebugPinHigh(x) PIOS_DEBUG_PinHigh(x)
#define DebugPinLow(x) PIOS_DEBUG_PinLow(x)
#else
#define DebugPinHigh(x)
#define DebugPinLow(x)
#endif
#define Assert(exp) PIOS_DEBUG_Assert(exp)
/**
* Initializes IIC driver
* \param[in] mode currently only mode 0 supported
* \return < 0 if initialisation failed
*/
int32_t PIOS_I2C_Init(void)
{
/* Configure IIC pins in open drain mode */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = PIOS_I2C_SCL_PIN;
GPIO_Init(PIOS_I2C_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PIOS_I2C_SDA_PIN;
GPIO_Init(PIOS_I2C_GPIO_PORT, &GPIO_InitStructure);
PIOS_I2C_InitPeripheral();
#ifdef USE_FREERTOS
vSemaphoreCreateBinary(I2CRec.sem_readySignal);
I2CRec.xBusyMutex = xSemaphoreCreateMutex();
#endif // USE_FREERTOS
TransferEnd(&I2CRec);
/* Configure and enable I2C2 interrupts */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = I2C2_EV_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PIOS_I2C_IRQ_EV_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = I2C2_ER_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PIOS_I2C_IRQ_ER_PRIORITY;
NVIC_Init(&NVIC_InitStructure);
DebugPinLow(2);
/* No error */
return 0;
}
/**
* Internal function to (re-)initialize the I2C peripheral
*/
static void PIOS_I2C_InitPeripheral(void)
{
I2C_InitTypeDef I2C_InitStructure;
I2CRecTypeDef *i2cx = &I2CRec;
/* Prepare I2C init-struct */
I2C_StructInit(&I2C_InitStructure);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1 = 0;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
/* Define base address */
i2cx->base = I2C2;
/* enable peripheral clock of I2C */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
/* Set I2C clock bus clock params */
/* Note that the STM32 driver handles value <= 100kHz differently! (duty cycle always 1:1) */
/* Important: bus frequencies > 400kHz don't work stable */
I2C_InitStructure.I2C_DutyCycle = PIOS_I2C_DUTY_CYCLE;
I2C_InitStructure.I2C_ClockSpeed = PIOS_I2C_BUS_FREQ;
/* Trigger software reset via I2C_DeInit */
I2C_DeInit(i2cx->base);
/* Clear transfer state and error value */
i2cx->transfer_state.ALL = 0;
i2cx->transfer_error = 0;
/* Configure I2C peripheral */
I2C_Init(i2cx->base, &I2C_InitStructure);
}
#ifdef USE_FREERTOS
/**
* Semaphore handling: requests the IIC interface
* \param[in] timeout Timeout in ticks, 0 for no delay
* \return TRUE when the lock to the device was obtained
*/
bool PIOS_I2C_LockDevice(portTickType timeout)
{
if (xSemaphoreTake(I2CRec.xBusyMutex, timeout) == pdTRUE)
{
// Ok, got device
return TRUE;
}
else
{
return FALSE;
}
}
/**
* Semaphore handling: releases the IIC interface for other tasks
* \return < 0 on errors
*/
void PIOS_I2C_UnlockDevice(void)
{
xSemaphoreGive(I2CRec.xBusyMutex);
}
#endif // USE_FREERTOS
/**
* Internal function called at the start of a transfer
*/
static void TransferStart(I2CRecTypeDef *i2cx)
{
Assert(i2cx->transfer_state.BUSY == 0);
DebugPinHigh(DEBUG_PIN_BUSY);
i2cx->transfer_state.BUSY = 1;
// Enable Interrupts: I2V2 event, buffer and error interrupt
I2C_ITConfig(i2cx->base, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR, ENABLE);
}
/**
* Internal function called at the end of a transfer
*/
static void TransferEnd(I2CRecTypeDef *i2cx)
{
// Disable all interrupts
I2C_ITConfig(i2cx->base, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR, DISABLE);
DebugPinLow(DEBUG_PIN_BUSY);
i2cx->transfer_state.BUSY = 0;
#ifdef USE_FREERTOS
if (i2cx->transfer_state.INIRQ)
{
xSemaphoreGiveFromISR(i2cx->sem_readySignal, &i2cx->xHigherPriorityTaskWoken);
}
else
{
xSemaphoreGive(i2cx->sem_readySignal);
}
#endif
}
/**
* Checks if transfer is finished
* \return 1 if ongoing transfer
* \return <=0 no transfer is busy; return value indicates error value of last transfer
* (PIOS_I2C_TransferBegin() has to be called again)
*/
int32_t PIOS_I2C_TransferCheck(void)
{
I2CRecTypeDef *i2cx = &I2CRec;
if(i2cx->transfer_state.BUSY)
return 1;
return i2cx->transfer_error;
}
/**
* Stop the current transfer
* \param error the error that must be reported
*/
void PIOS_I2C_TerminateTransfer(uint32_t error)
{
I2CRecTypeDef *i2cx = &I2CRec;
/* Send stop condition */
I2C_GenerateSTOP(i2cx->base, ENABLE);
/* Re-initialize peripheral */
PIOS_I2C_InitPeripheral();
i2cx->transfer_error = error;
}
/**
* Waits until transfer is finished.
* \return error value of the transfer
*/
int32_t PIOS_I2C_TransferWait(void)
{
I2CRecTypeDef *i2cx = &I2CRec;
DebugPinHigh(DEBUG_PIN_WAIT);
#ifdef USE_FREERTOS
if (i2cx->transfer_state.BUSY)
{
// Wait until we see the ready signal
if (xSemaphoreTake(i2cx->sem_readySignal, PIOS_I2C_TIMEOUT_VALUE/portTICK_RATE_MS) == pdTRUE)
{
// OK, got the semaphore, release it again
Assert(i2cx->transfer_state.BUSY == 0);
}
else
{
PIOS_I2C_TerminateTransfer(I2C_ERROR_TIMEOUT);
}
}
#else
uint32_t repeat_ctr = PIOS_I2C_TIMEOUT_VALUE;
uint16_t last_buffer_ix = i2cx->buffer_ix;
if (i2cx->transfer_state.BUSY)
{
while(--repeat_ctr > 0)
{
/* Check if buffer index has changed - if so, reload repeat counter */
if(i2cx->buffer_ix != last_buffer_ix) {
repeat_ctr = PIOS_I2C_TIMEOUT_VALUE;
last_buffer_ix = i2cx->buffer_ix;
}
/* Get transfer state */
int32_t check_state = PIOS_I2C_TransferCheck();
/* Exit if transfer finished */
if(check_state <= 0)
{
DebugPinLow(DEBUG_PIN_WAIT);
return check_state;
}
}
/* Timeout error - something is stalling... */
PIOS_I2C_TerminateTransfer(I2C_ERROR_TIMEOUT);
}
#endif
DebugPinLow(DEBUG_PIN_WAIT);
return i2cx->transfer_error;
}
/**
* Perform a transfer. No previous transfer should be ongoing when this function is called.
* When the function returns, the transfer is finished.
* See PIOS_I2C_StartTransfer() for details on the parameters.
* \return 0 no error
* \return < 0 on errors
*
*/
int32_t PIOS_I2C_Transfer(I2CTransferTypeDef transfer, uint8_t address, uint8_t *buffer, uint16_t len)
{
PIOS_I2C_StartTransfer(transfer, address, buffer, len);
return PIOS_I2C_TransferWait();
}
/**
* Starts a new transfer. No previous transfer should be ongoing when this function is called.
* When this function returns, the new transfer is ongoing. PIOS_I2C_TransferWait() should be called
* to wait for the transfer to finish and to retrieve the result code.
* \param[in] transfer type:
*