/** ****************************************************************************** * * @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; #endif 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; /* 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 // FIXME: temp assert #define assert(exp) {if (!(exp)) while(1){DebugPinHigh(DEBUG_PIN_ASSERT);DebugPinLow(DEBUG_PIN_ASSERT);};}; /** * 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; #ifdef USE_FREERTOS vSemaphoreCreateBinary(I2CRec.sem_readySignal); #endif 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); } /** * 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->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; } /** * 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:
*