/**
******************************************************************************
*
* @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_DONT_USE_I2C)
/* Options */
//#define USE_DEBUG_PINS
/* 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;
};
} 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;
volatile int32_t last_transfer_error;
volatile uint8_t i2c_semaphore;
} 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;
/* Macros */
#ifdef USE_DEBUG_PINS
#define DEBUG_PIN_ISR 0
#define DEBUG_PIN_BUSY 1
#define DebugPinHigh(x) PIOS_DEBUG_PinHigh(x)
#define DebugPinLow(x) PIOS_DEBUG_PinLow(x)
#else
#define DebugPinHigh(x)
#define DebugPinLow(x)
#endif
/**
* 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();
/* Now accessible for other tasks */
I2CRec.i2c_semaphore = 0;
I2CRec.last_transfer_error = 0;
/* 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);
/* 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;
PIOS_DEBUG_PinLow(0);
i2cx->transfer_error = 0;
/* Configure I2C peripheral */
I2C_Init(i2cx->base, &I2C_InitStructure);
}
/**
* Semaphore handling: requests the IIC interface
* \param[in] semaphore_type is either IIC_Blocking or IIC_Non_Blocking
* \return Non_Blocking: returns -1 to request a retry
* \return 0 if IIC interface free
*/
int32_t PIOS_I2C_LockDevice(I2CSemaphoreTypeDef semaphore_type)
{
volatile I2CRecTypeDef *i2cx = &I2CRec;
int32_t status = -1;
do {
PIOS_IRQ_Disable();
if(!i2cx->i2c_semaphore) {
i2cx->i2c_semaphore = 1;
status = 0;
}
PIOS_IRQ_Enable();
} while(semaphore_type == I2C_Blocking && status != 0);
/* Clear transfer errors of last transmission */
i2cx->last_transfer_error = 0;
i2cx->transfer_error = 0;
return status;
}
/**
* Semaphore handling: releases the IIC interface for other tasks
* \return < 0 on errors
*/
int32_t PIOS_I2C_UnlockDevice(void)
{
I2CRec.i2c_semaphore = 0;
/* No error */
return 0;
}
/**
* Returns the last transfer error
* Will be updated by PIOS_I2C_TransferCheck(), so that the error status
* doesn't get lost (the check function will return 0 when called again)
* Will be cleared when a new transfer has been started successfully
* \return last error status
*/
int32_t PIOS_IIC_LastErrorGet(void)
{
return I2CRec.last_transfer_error;
}
/**
* Internal function called at the start of a transfer
*/
static void TransferStart(I2CRecTypeDef *i2cx)
{
DebugPinHigh(DEBUG_PIN_BUSY);
i2cx->transfer_state.BUSY = 1;
}
/**
* Internal function called at the end of a transfer
*/
static void TransferEnd(I2CRecTypeDef *i2cx)
{
DebugPinLow(DEBUG_PIN_BUSY);
i2cx->transfer_state.BUSY = 0;
}
/**
* Checks if transfer is finished
* \return 0 if no ongoing transfer
* \return 1 if ongoing transfer
* \return < 0 if error during transfer
* \note Note that the semaphore will be released automatically after an error
* (PIOS_I2C_TransferBegin() has to be called again)
*/
int32_t PIOS_I2C_TransferCheck(void)
{
I2CRecTypeDef *i2cx = &I2CRec;
/* Ongoing transfer? */
if(i2cx->transfer_state.BUSY) {
return 1;
}
/* Error during transfer? */
/* (must be done *after* BUSY check to avoid race conditon!) */
if(i2cx->transfer_error) {
/* Store error status for PIOS_IIC_LastErrorGet() function */
i2cx->last_transfer_error = i2cx->transfer_error;
/* Clear current error status */
i2cx->transfer_error = 0;
/* Release semaphore for easier programming at user level */
i2cx->i2c_semaphore = 0;
/* And exit */
return i2cx->last_transfer_error;
}
/* No transfer */
return 0;
}
/**
* Waits until transfer is finished
* \return 0 if no ongoing transfer
* \return < 0 if error during transfer
* \note Note that the semaphore will be released automatically after an error
* (PIOS_I2C_TransferBegin() has to be called again)
*/
int32_t PIOS_I2C_TransferWait(void)
{
I2CRecTypeDef *i2cx = &I2CRec;
uint32_t repeat_ctr = PIOS_I2C_TIMEOUT_VALUE;
uint16_t last_buffer_ix = i2cx->buffer_ix;
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 or error detected */
if(check_state <= 0) {
if(check_state < 0) {
/* Release semaphore for easier programming at user level */
i2cx->i2c_semaphore = 0;
}
return check_state;
}
}
/* Timeout error - something is stalling... */
/* Send stop condition */
I2C_GenerateSTOP(i2cx->base, ENABLE);
/* Re-initialize peripheral */
PIOS_I2C_InitPeripheral();
/* Release semaphore (!) */
i2cx->i2c_semaphore = 0;
return (i2cx->last_transfer_error = I2C_ERROR_TIMEOUT);
}
/**
* Starts a new transfer. If this function is called during an ongoing
* transfer, we wait until it has been finished and setup the new transfer
* \param[in] transfer type:
*