/** ****************************************************************************** * * @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 #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(); /* 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:<BR> * <UL> * <LI>I2C_Read: a common Read transfer * <LI>I2C_Write: a common Write transfer * <LI>I2C_Write_WithoutStop: don't send stop condition after transfer to allow * a restart condition (e.g. used to access EEPROMs) * \param[in] address of I2C device (bit 0 always cleared) * \param[in] *buffer pointer to transmit/receive buffer * \param[in] len number of bytes which should be transmitted/received */ void PIOS_I2C_StartTransfer(I2CTransferTypeDef transfer, uint8_t address, uint8_t *buffer, uint16_t len) { I2CRecTypeDef *i2cx = &I2CRec; // Should not be busy if (i2cx->transfer_state.BUSY) { Assert(0); return; } #ifdef USE_FREERTOS // Consume Ready semaphore in case it would be there for some reason xSemaphoreTake(i2cx->sem_readySignal, 0); Assert(xSemaphoreTake(i2cx->sem_readySignal, 0) == pdFALSE); #endif // Clear state i2cx->transfer_state.ALL = 0; i2cx->transfer_error = 0; // Set buffer length and start index i2cx->buffer_len = len; i2cx->buffer_ix = 0; if(transfer == I2C_Read) { /* Take new address/buffer/len */ /* Set bit 0 for read operation */ i2cx->i2c_address = address | 1; /* Ensure that previous TX buffer won't be accessed */ i2cx->tx_buffer_ptr = NULL; i2cx->rx_buffer_ptr = buffer; // Ack the bytes we will be getting I2C_AcknowledgeConfig(i2cx->base, ENABLE); } else if(transfer == I2C_Write || transfer == I2C_Write_WithoutStop) { /* Take new address/buffer/len */ /* Clear bit 0 for write operation */ i2cx->i2c_address = address & 0xfe; i2cx->tx_buffer_ptr = buffer; /* Ensure that nothing will be received */ i2cx->rx_buffer_ptr = NULL; /* Option to skip stop-condition generation after successful write */ i2cx->transfer_state.WRITE_WITHOUT_STOP = transfer == I2C_Write_WithoutStop ? 1 : 0; } else { i2cx->transfer_error = I2C_ERROR_UNSUPPORTED_TRANSFER_TYPE; return; } // Start the transfer I2C_GenerateSTART(i2cx->base, ENABLE); TransferStart(i2cx); } /** * Internal function for handling IIC event interrupts */ static void EV_IRQHandler(I2CRecTypeDef *i2cx) { uint8_t b; uint32_t event; DebugPinHigh(DEBUG_PIN_ISR); // Update state i2cx->transfer_state.INIRQ = 1; #ifdef USE_FREERTOS i2cx->xHigherPriorityTaskWoken = pdFALSE; #endif /* Read SR1 and SR2 at the beginning (if not done so, flags may get lost) */ event = I2C_GetLastEvent(i2cx->base); /* The order of the handling blocks is chosen by test results @ 1MHZ */ /* Don't change this order */ /* RxNE set, will be cleared by reading/writing DR */ /* Note: also BTF will be reset after a read of SR1 (TxE flag) followed by either read/write DR */ /* Or a START or STOP condition generated */ /* Failsave: really requested a receive transfer? If not, continue to check TXE flag, if not set, */ /* We'll end up in the unexpected event handler. */ if(event & I2C_FLAG_RXNE && i2cx->rx_buffer_ptr != NULL) { /* Get received data */ b = I2C_ReceiveData(i2cx->base); /* Failsave: still place in buffer? */ if(i2cx->buffer_ix < i2cx->buffer_len) { i2cx->rx_buffer_ptr[i2cx->buffer_ix++] = b; } /* Last byte received, disable interrupts and return. */ if(i2cx->transfer_state.STOP_REQUESTED) { TransferEnd(i2cx); goto isr_return; } /* Request NAK and stop condition before receiving last data */ if(i2cx->buffer_ix >= i2cx->buffer_len-1) { /* Request NAK */ I2C_AcknowledgeConfig(i2cx->base, DISABLE); /* Request stop condition */ I2C_GenerateSTOP(i2cx->base, ENABLE); i2cx->transfer_state.STOP_REQUESTED = 1; } goto isr_return; } /* ADDR set, TRA flag not set (indicates transmitter/receiver mode). */ /* ADDR will be cleared by a read of SR1 followed by a read of SR2 (done by I2C_GetLastEvent) */ /* If transmitter mode is selected (TRA set), we go on, TXE will be catched to send the first byte */ if((event & I2C_FLAG_ADDR) && !(event & I2C_FLAG_TRA)) { /* Address sent (receiver mode), receiving first byte - check if we already have to request NAK/Stop */ if(i2cx->buffer_len == 1) { /* Request NAK */ I2C_AcknowledgeConfig(i2cx->base, DISABLE); /* Request stop condition */ I2C_GenerateSTOP(i2cx->base, ENABLE); i2cx->transfer_state.STOP_REQUESTED = 1; } goto isr_return; } /* TxE set, will be cleared by writing DR, or after START or STOP was generated */ /* This handling also applies for BTF, as TXE will alway be set if BTF is. */ /* Note: also BTF will be reset after a read of SR1 (TxE flag) followed by either read/write DR */ /* Or a START or STOP condition generated */ if(event & I2C_FLAG_TXE) { /* Last byte already sent, disable interrupts and return. */ if(i2cx->transfer_state.STOP_REQUESTED) { TransferEnd(i2cx); goto isr_return; } if(i2cx->buffer_ix < i2cx->buffer_len) { /* Checking tx_buffer_ptr for NULL is a failsafe measure. */ I2C_SendData(i2cx->base, (i2cx->tx_buffer_ptr == NULL) ? 0 : i2cx->tx_buffer_ptr[i2cx->buffer_ix++]); goto isr_return; } /* Peripheral is transfering last byte, request stop condition / */ /* On write-without-stop transfer-type, request start condition instead */ i2cx->transfer_state.STOP_REQUESTED = 1; if(!i2cx->transfer_state.WRITE_WITHOUT_STOP) { I2C_GenerateSTOP(i2cx->base, ENABLE); } else { DebugPinHigh(2); } if(i2cx->buffer_len == 0) { TransferEnd(i2cx); } else { /* Disable the I2C_IT_BUF interrupt after sending the last buffer data */ /* (last EV8) to not allow a new interrupt just with TxE - only BTF will generate it */ /* If this is not done, BUSY will be cleared before the transfer is finished */ I2C_ITConfig(i2cx->base, I2C_IT_BUF, DISABLE); } goto isr_return; } // Send address /* SB set, cleared by reading SR1 (done by I2C_GetLastEvent) followed by writing DR register */ if(event & I2C_FLAG_SB) { /* Don't send address if stop was requested (WRITE_WITHOUT_STOP - mode, start condition was sent) */ /* We have to wait for the application to start the next transfer */ if(i2cx->transfer_state.STOP_REQUESTED) { TransferEnd(i2cx); DebugPinLow(DEBUG_PIN_ISR); return; } /* Send IIC address */ I2C_Send7bitAddress(i2cx->base, i2cx->i2c_address, (i2cx->i2c_address & 1) ? I2C_Direction_Receiver : I2C_Direction_Transmitter); goto isr_return; } DebugPinHigh(DEBUG_PIN_ASSERT);DebugPinLow(DEBUG_PIN_ASSERT); // // FredericG: Despite the comments below, it seems to me that this situation can happen and can // be ignored without ill effects... // For now this condition does not stop the transfer, but further investigation in needed // // Assert(0); // // /* This code is only reached if something got wrong, e.g. interrupt handler is called too late, */ // /* The device reset itself (while testing, it was always event 0x00000000). we have to stop the transfer, */ // /* Else read/write of corrupt data may be the result. */ // // /* Notify error */ // PIOS_I2C_UnexpectedEvent = event; // i2cx->transfer_error = I2C_ERROR_UNEXPECTED_EVENT; // // TransferEnd(i2cx); // // /* Do dummy read to send NAK + STOP condition */ // I2C_AcknowledgeConfig(i2cx->base, DISABLE); // b = I2C_ReceiveData(i2cx->base); // I2C_GenerateSTOP(i2cx->base, ENABLE); isr_return: // Cause task-switch when needed #ifdef USE_FREERTOS portEND_SWITCHING_ISR(i2cx->xHigherPriorityTaskWoken); #endif // Update state i2cx->transfer_state.INIRQ = 0; DebugPinLow(DEBUG_PIN_ISR); } /** * Internal function for handling IIC error interrupts */ static void ER_IRQHandler(I2CRecTypeDef *i2cx) { /* Read SR1 and SR2 at the beginning (if not done so, flags may get lost) */ uint32_t event = I2C_GetLastEvent(i2cx->base); /* Note that only one error number is available */ /* The order of these checks defines the priority */ /* Bus error (start/stop condition during read */ /* Unlikely, should only be relevant for slave mode?) */ if(event & I2C_FLAG_BERR) { I2C_ClearITPendingBit(i2cx->base, I2C_IT_BERR); i2cx->transfer_error = I2C_ERROR_BUS; } /* Arbitration lost */ if(event & I2C_FLAG_ARLO) { I2C_ClearITPendingBit(i2cx->base, I2C_IT_ARLO); i2cx->transfer_error = I2C_ERROR_ARBITRATION_LOST; } /* No acknowledge received from slave (e.g. slave not connected) */ if(event & I2C_FLAG_AF) { I2C_ClearITPendingBit(i2cx->base, I2C_IT_AF); i2cx->transfer_error = I2C_ERROR_SLAVE_NOT_CONNECTED; /* Send stop condition to release bus */ I2C_GenerateSTOP(i2cx->base, ENABLE); } /* Notify that transfer has finished (due to the error) */ TransferEnd(i2cx); } /* Interrupt vectors */ void I2C2_EV_IRQHandler(void) { EV_IRQHandler((I2CRecTypeDef *)&I2CRec); } void I2C2_ER_IRQHandler(void) { ER_IRQHandler((I2CRecTypeDef *)&I2CRec); } #endif