Last active
June 6, 2018 14:34
-
-
Save jackhumbert/dc4d61d1acb0d6f5dc6008348bcbd332 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/os/hal/include/hal_i2c.h b/os/hal/include/hal_i2c.h | |
index c9c8409cb..4a90ba98d 100644 | |
--- a/os/hal/include/hal_i2c.h | |
+++ b/os/hal/include/hal_i2c.h | |
@@ -1,5 +1,5 @@ | |
/* | |
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | |
+ ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
@@ -19,15 +19,15 @@ | |
*/ | |
/** | |
- * @file hal_i2c.h | |
+ * @file i2c.h | |
* @brief I2C Driver macros and structures. | |
* | |
* @addtogroup I2C | |
* @{ | |
*/ | |
-#ifndef HAL_I2C_H | |
-#define HAL_I2C_H | |
+#ifndef _I2C_H_ | |
+#define _I2C_H_ | |
#if (HAL_USE_I2C == TRUE) || defined(__DOXYGEN__) | |
@@ -35,7 +35,6 @@ | |
/* Driver constants. */ | |
/*===========================================================================*/ | |
-/* TODO: To be reviewed, too STM32-centric.*/ | |
/** | |
* @name I2C bus error conditions | |
* @{ | |
@@ -49,8 +48,22 @@ | |
reception. */ | |
#define I2C_TIMEOUT 0x20 /**< @brief Hardware timeout. */ | |
#define I2C_SMB_ALERT 0x40 /**< @brief SMBus Alert. */ | |
+#define I2C_UNKNOWN_ERROR 0x80 /**< @brief internal error (base value - current mode value added) */ | |
+ | |
+#define I2C_STOPPED ((i2cflags_t)(-1)) | |
+ /**< @brief stop condition or i2cStop() called */ | |
/** @} */ | |
+/** | |
+ * @name I2C function return codes | |
+ * @{ | |
+ */ | |
+#define I2C_OK (MSG_OK) | |
+#define I2C_ERR_TIMEOUT (MSG_TIMEOUT) | |
+#define I2C_ERROR (MSG_RESET) | |
+/** @} */ | |
+ | |
+ | |
/*===========================================================================*/ | |
/* Driver pre-compile time settings. */ | |
/*===========================================================================*/ | |
@@ -62,10 +75,35 @@ | |
#define I2C_USE_MUTUAL_EXCLUSION TRUE | |
#endif | |
+/** | |
+ * @brief Enables 'lock' capability needed in I2C slave mode | |
+ */ | |
+#if !defined(HAL_USE_I2C_LOCK) || defined(__DOXYGEN__) | |
+#define HAL_USE_I2C_LOCK FALSE | |
+#endif | |
+ | |
+/** | |
+ * @brief Determines whether master mode required to be supported | |
+ */ | |
+ #if !defined(HAL_USE_I2C_MASTER) || defined(__DOXYGEN__) | |
+ #define HAL_USE_I2C_MASTER TRUE | |
+ #endif | |
+ | |
+/** | |
+ * @brief Determines whether slave mode required to be supported | |
+ */ | |
+ #if !defined(HAL_USE_I2C_SLAVE) || defined(__DOXYGEN__) | |
+ #define HAL_USE_I2C_SLAVE FALSE | |
+ #endif | |
+ | |
/*===========================================================================*/ | |
/* Derived constants and error checks. */ | |
/*===========================================================================*/ | |
+#if I2C_USE_MUTUAL_EXCLUSION && !CH_CFG_USE_MUTEXES && !CH_CFG_USE_SEMAPHORES | |
+#error "I2C_USE_MUTUAL_EXCLUSION requires CH_CFG_USE_MUTEXES and/or CH_CFG_USE_SEMAPHORES" | |
+#endif | |
+ | |
/*===========================================================================*/ | |
/* Driver data structures and types. */ | |
/*===========================================================================*/ | |
@@ -129,6 +167,7 @@ typedef enum { | |
#define i2cMasterReceive(i2cp, addr, rxbuf, rxbytes) \ | |
(i2cMasterReceiveTimeout(i2cp, addr, rxbuf, rxbytes, TIME_INFINITE)) | |
+ | |
/*===========================================================================*/ | |
/* External declarations. */ | |
/*===========================================================================*/ | |
@@ -150,10 +189,17 @@ extern "C" { | |
i2caddr_t addr, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout); | |
+ | |
+#if HAL_USE_I2C_LOCK /* I2C slave mode support */ | |
+ void i2cLock(I2CDriver *i2cp, systime_t lockDuration); | |
+ void i2cUnlock(I2CDriver *i2cp); | |
+#endif | |
+ | |
#if I2C_USE_MUTUAL_EXCLUSION == TRUE | |
void i2cAcquireBus(I2CDriver *i2cp); | |
void i2cReleaseBus(I2CDriver *i2cp); | |
-#endif | |
+#endif /* I2C_USE_MUTUAL_EXCLUSION */ | |
+ | |
#ifdef __cplusplus | |
} | |
@@ -161,6 +207,6 @@ extern "C" { | |
#endif /* HAL_USE_I2C == TRUE */ | |
-#endif /* HAL_I2C_H */ | |
+#endif /* _I2C_H_ */ | |
/** @} */ | |
diff --git a/os/hal/include/hal_i2cslave.h b/os/hal/include/hal_i2cslave.h | |
new file mode 100644 | |
index 000000000..955b41e25 | |
--- /dev/null | |
+++ b/os/hal/include/hal_i2cslave.h | |
@@ -0,0 +1,423 @@ | |
+/* | |
+ ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010, | |
+ 2011,2012,2013 Giovanni Di Sirio. | |
+ | |
+ This file is part of ChibiOS/RT. | |
+ | |
+ ChibiOS/RT 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. | |
+ | |
+ ChibiOS/RT 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+ --- | |
+ | |
+ A special exception to the GPL can be applied should you wish to distribute | |
+ a combined work that includes ChibiOS/RT, without being obliged to provide | |
+ the source code for any proprietary components. See the file exception.txt | |
+ for full details of how and when the exception can be applied. | |
+*/ | |
+/* | |
+ Slave I2C support contributed by Brent Roman of the | |
+ Monterey Bay Aquarium Research Institute | |
+ */ | |
+ | |
+/** | |
+ * @file i2cslave.h | |
+ * @brief Slave Mode for the I2C Driver. | |
+ * | |
+ * @addtogroup I2C | |
+ * @{ | |
+ */ | |
+#ifndef _I2CSLAVE_H_ | |
+#define _I2CSLAVE_H_ | |
+ | |
+#if HAL_USE_I2C_SLAVE || defined(__DOXYGEN__) | |
+ | |
+#include <hal_i2c.h> | |
+ | |
+#ifdef __cplusplus | |
+extern "C" { | |
+#endif | |
+ | |
+/** | |
+ * @brief Configure to respond to messages directed to the given i2cadr | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C bus address | |
+ * - @a 0 matches "all call" | |
+ * . | |
+ * @return Length of message OR the type of event received | |
+ * @retval I2C_OK Success | |
+ * @retval I2C_ERROR Cannot match address in addition of those already | |
+ * | |
+ * @details MatchAddress calls are cumulative. | |
+ * Specify address zero to match I2C "all call" | |
+ * Most hardware supports matching only a signle nonzero address. | |
+ * | |
+ * @api | |
+ */ | |
+int i2cMatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr); | |
+ | |
+ | |
+/** | |
+ * @brief Configure to ignore messages directed to the given i2cadr | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C bus address | |
+ * - @a 0 matches "all call" | |
+ * . | |
+ * @details A message being transferred that has already matched the | |
+ * specified address will continue being processed. | |
+ * Requests to unmatch an address that is not currently being matched | |
+ * are ignored. | |
+ * | |
+ * @api | |
+ */ | |
+void i2cUnmatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr); | |
+ | |
+ | |
+/** | |
+ * @brief Configure to ignore all messages | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @details A message being transferred that has already matched the | |
+ * specified address will continue being processed. | |
+ * | |
+ * @api | |
+ */ | |
+void i2cUnmatchAll(I2CDriver *i2cp); | |
+ | |
+ | |
+/** | |
+ * @brief Configure to respond to messages directed to the given i2cadr | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C bus address | |
+ * - @a 0 matches "all call" | |
+ * . | |
+ * @return non-zero implies failure. | |
+ * | |
+ * @details Identical to i2cMatchAddress(), but called from interrupt context | |
+ * | |
+ * @api | |
+ */ | |
+static inline msg_t | |
+ i2cMatchAddressI(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_matchAddress(i2cp, i2cadr); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure to ignore messages directed to the given i2cadr | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C bus address | |
+ * - @a 0 matches "all call" | |
+ * . | |
+ * @details Identical to i2cUnmatchAddress(), but called from interrupt context | |
+ * | |
+ * @api | |
+ */ | |
+static inline void | |
+ i2cUnmatchAddressI(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ i2c_lld_unmatchAddress(i2cp, i2cadr); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure to ignore all messages | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @details Identical to i2cUnmatchAll(), but called from interrupt context | |
+ * | |
+ * @api | |
+ */ | |
+static inline void | |
+ i2cUnmatchAllI(I2CDriver *i2cp) | |
+/* | |
+ Notes: | |
+ Must be called from interrupt context | |
+ Does not affect the processing of any message currently being received | |
+*/ | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ i2c_lld_unmatchAll(i2cp); | |
+} | |
+ | |
+ | |
+/* I2C Bus activity timeout configuration */ | |
+ | |
+/** | |
+ * @brief return maximum number of ticks a slave bus transaction may last | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @return maximum number of ticks a slave bus transaction my last | |
+ * | |
+ * @details initialized to TIME_INFINITE (disabling slave mode bus timeouts) | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ systime_t i2cSlaveTimeout(I2CDriver *i2cp) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_get_slaveTimeout(i2cp); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief set the maximum number of ticks a slave bus transaction may last | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] ticks maximum number of ticks a slave bus transaction my last | |
+ * - @a TIME_INFINITE disables slave mode bus timeouts | |
+ * - @a TIME_IMMEDIATE is invalid | |
+ * . | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ void i2cSlaveSetTimeout(I2CDriver *i2cp, systime_t ticks) | |
+{ | |
+ osalDbgCheck(i2cp != NULL && ticks != TIME_IMMEDIATE); | |
+ i2c_lld_set_slaveTimeout(i2cp, ticks); | |
+} | |
+ | |
+ | |
+/* bus transaction attributes */ | |
+ | |
+/** | |
+ * @brief return bit mask of errors associated with this slave transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @return I2C bus error conditions described in i2c.h | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ i2cflags_t i2cSlaveErrors(I2CDriver *i2cp) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_get_slaveErrors(i2cp); | |
+} | |
+ | |
+/** | |
+ * @brief return number of bytes transferred during this slave transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @return number of bytes actually transferred on the bus | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ size_t i2cSlaveBytes(I2CDriver *i2cp) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_get_slaveBytes(i2cp); | |
+} | |
+ | |
+/** | |
+ * @brief return i2c address to which this message was targetted | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @return i2c address to which this message was targetted | |
+ * | |
+ * @details The address returns will be one of those | |
+ * specified earlier as an argument to i2cMatchAddress() | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ i2caddr_t i2cSlaveTargetAdr(I2CDriver *i2cp) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_get_slaveTargetAdr(i2cp); | |
+} | |
+ | |
+ | |
+/* | |
+ An event service thread based API library called i2cevent supports processing | |
+ slave messages on a dedicated thread. This facility is built upon the | |
+ low-level driver's asynchronous callback functions described below: | |
+ | |
+ Each callback function may alter the processing of subsequent I2C | |
+ messages and read requests by calling i2cSlaveReceive() and | |
+ i2cSlaveReply(), respectively. Further, callbacks may alter their | |
+ i2cSlaveMsg structs in RAM, but only those for their own channel. | |
+ Such changes take immediate affect. This facility can be used to | |
+ avoid copying message buffers. | |
+ | |
+ If receive buffers become full or a reply to a read request cannot be | |
+ generated immediately, the relevant I2CSlaveMsg struct may be substituted | |
+ for another whose body pointer is NULL or whose body size is zero. | |
+ Note that, I2CSlaveMsg structs may be modified | |
+ in place within a channel's callbacks to the same effect. | |
+ | |
+ A NULL body pointer or zero size causes the slave to signal the master node | |
+ to wait by holding the I2C clock signal low, "stretching it", during the next | |
+ transaction to which that I2CSlaveMsg applies. | |
+ The I2C clock resumes only after a i2cSlaveSetReceive() or SetReply() is | |
+ called with an I2CSlaveMsg containing a non-NULL body, | |
+ or after the transaction timeout expires. | |
+ | |
+ Therefore, if a NULL body pointer is replaced with a non-NULL one or | |
+ a zero length is replaced with a non-zero one, i2cSlaveReceive() or | |
+ i2cSlaveReply() MUST be called -- even if with the same pointer values -- | |
+ to inform the i2c driver that the transaction may resume. | |
+ | |
+ Note that Receive and Reply processing is initially "locked". | |
+*/ | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for message reception & query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] rxMsg @p I2CSlaveMsg struct for processing subsequent messages | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Must be called from a thread | |
+ * Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @api | |
+ */ | |
+void i2cSlaveConfigure(I2CDriver *i2cp, | |
+ const I2CSlaveMsg *rxMsg, const I2CSlaveMsg *replyMsg); | |
+ | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for message reception | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] rxMsg @p I2CSlaveMsg struct for processing subsequent messages | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Must be called from a thread | |
+ * Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @api | |
+ */ | |
+void i2cSlaveReceive(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg); | |
+ | |
+/** | |
+ * @brief return @p I2CSlaveMsg for processing received messages | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @returns @p I2CSlaveMsg struct for processing subsequent messages | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ const I2CSlaveMsg *i2cSlaveReceiveMsg(I2CDriver *i2cp) | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_get_slaveReceive(i2cp); | |
+} | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Must be called from a thread | |
+ * Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @api | |
+ */ | |
+void i2cSlaveReply(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg); | |
+ | |
+ | |
+/** | |
+ * @brief return @p I2CSlaveMsg for processing received messages | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @returns @p I2CSlaveMsg struct for processing subsequent messages | |
+ * | |
+ * @api | |
+ */ | |
+static inline | |
+ const I2CSlaveMsg *i2cSlaveReplyMsg(I2CDriver *i2cp) | |
+/* | |
+ processing descriptor for the next reply message | |
+*/ | |
+{ | |
+ osalDbgCheck(i2cp != NULL); | |
+ return i2c_lld_get_slaveReply(i2cp); | |
+} | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for message reception | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] rxMsg @p I2CSlaveMsg struct for processing subsequent messages | |
+ * | |
+ * @details Must be called from an interrupt context | |
+ * | |
+ * @api | |
+ */ | |
+static inline void | |
+ i2cSlaveReceiveI(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg) | |
+{ | |
+ osalDbgCheck(i2cp != NULL && rxMsg != NULL); | |
+ i2c_lld_slaveReceive(i2cp, rxMsg); | |
+} | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent messages | |
+ * | |
+ * @details Must be called from an interrupt context | |
+ * | |
+ * @api | |
+ */ | |
+static inline void | |
+ i2cSlaveReplyI(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg) | |
+/* | |
+ Prepare to reply to I2C read requests from bus masters | |
+ according to the replyMsg configuration. | |
+ | |
+ Notes: | |
+ Must be called from interrupt context | |
+ Does not affect the processing of any message reply being sent | |
+*/ | |
+{ | |
+ osalDbgCheck(i2cp != NULL && replyMsg != NULL); | |
+ i2c_lld_slaveReply(i2cp, replyMsg); | |
+} | |
+ | |
+#ifdef __cplusplus | |
+} | |
+#endif | |
+ | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
+#endif /* _I2CSLAVE_H_ */ | |
diff --git a/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.c b/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.c | |
index 20b8cf8b6..45f0632ee 100644 | |
--- a/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.c | |
+++ b/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.c | |
@@ -1,5 +1,5 @@ | |
/* | |
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | |
+ ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
@@ -16,10 +16,16 @@ | |
/* | |
Concepts and parts of this file have been contributed by Uladzimir Pylinsky | |
aka barthess. | |
+ | |
+ I2C Slave mode support added by Brent Roman ([email protected]) | |
+ Ported to ChibiOs V3 by steved | |
+ Latest updates from original code applied 18.12.15 | |
+ | |
+ Note that all I2C registers are 16-bit (not 32-bit, as on the later devices in the series) | |
*/ | |
/** | |
- * @file I2Cv1/hal_i2c_lld.c | |
+ * @file STM32/I2Cv1/i2c_lld.c | |
* @brief STM32 I2C subsystem low level driver source. | |
* | |
* @addtogroup I2C | |
@@ -30,6 +36,9 @@ | |
#if HAL_USE_I2C || defined(__DOXYGEN__) | |
+// Some master code has been brought in line with V3 - set next define to zero to stick with original code | |
+#define USE_OLD_MASTER_STARTUP 0 | |
+ | |
/*===========================================================================*/ | |
/* Driver local definitions. */ | |
/*===========================================================================*/ | |
@@ -59,31 +68,41 @@ | |
STM32_I2C3_TX_DMA_CHN) | |
/*===========================================================================*/ | |
-/* Driver constants. */ | |
+/* Driver constants -- see ST document RM0038 figure 208 */ | |
/*===========================================================================*/ | |
+#define I2C_EV5_MASTER_MODE_SELECT \ | |
+ (((uint32_t)I2C_SR2_MSL << 16) | I2C_SR1_SB) | |
-#define I2C_EV5_MASTER_MODE_SELECT \ | |
- ((uint32_t)(((I2C_SR2_MSL | I2C_SR2_BUSY) << 16) | I2C_SR1_SB)) | |
+#define I2C_EV6_MASTER_TRA_MODE_SELECTED \ | |
+ (((uint32_t)(I2C_SR2_MSL | I2C_SR2_TRA) << 16) | I2C_SR1_ADDR) | |
-#define I2C_EV6_MASTER_TRA_MODE_SELECTED \ | |
- ((uint32_t)(((I2C_SR2_MSL | I2C_SR2_BUSY | I2C_SR2_TRA) << 16) | \ | |
- I2C_SR1_ADDR | I2C_SR1_TXE)) | |
+#define I2C_EV6_MASTER_REC_MODE_SELECTED \ | |
+ (((uint32_t)I2C_SR2_MSL << 16) | I2C_SR1_ADDR) | |
-#define I2C_EV6_MASTER_REC_MODE_SELECTED \ | |
- ((uint32_t)(((I2C_SR2_MSL | I2C_SR2_BUSY)<< 16) | I2C_SR1_ADDR)) | |
- | |
-#define I2C_EV8_2_MASTER_BYTE_TRANSMITTED \ | |
- ((uint32_t)(((I2C_SR2_MSL | I2C_SR2_BUSY | I2C_SR2_TRA) << 16) | \ | |
- I2C_SR1_BTF | I2C_SR1_TXE)) | |
+#define I2C_EV8_2_MASTER_BYTE_TRANSMITTED \ | |
+ (((uint32_t)(I2C_SR2_MSL | I2C_SR2_TRA) << 16) | I2C_SR1_BTF) | |
#define I2C_EV9_MASTER_ADD10 \ | |
((uint32_t)(((I2C_SR2_MSL | I2C_SR2_BUSY) << 16) | I2C_SR1_ADD10)) | |
-#define I2C_EV_MASK 0x00FFFFFF | |
+#if HAL_USE_I2C_SLAVE | |
+#define I2C_EV1_SLAVE_RXADRMATCH \ | |
+ ((uint32_t)I2C_SR1_ADDR) | |
+ | |
+#define I2C_EV1_SLAVE_TXADRMATCH \ | |
+ (((uint32_t)I2C_SR2_TRA << 16) | I2C_SR1_ADDR) | |
-#define I2C_ERROR_MASK \ | |
+#define I2C_EV2_SLAVE_RXSTOP \ | |
+ (I2C_SR1_STOPF) | |
+#endif | |
+ | |
+#define I2C_EV_MASK ( \ | |
+ ((uint32_t)(I2C_SR2_MSL|I2C_SR2_TRA)<<16) | \ | |
+ (I2C_SR1_SB|I2C_SR1_ADDR|I2C_SR1_STOPF|I2C_SR1_BTF)) | |
+ | |
+#define I2C_ERROR_MASK \ | |
((uint16_t)(I2C_SR1_BERR | I2C_SR1_ARLO | I2C_SR1_AF | I2C_SR1_OVR | \ | |
- I2C_SR1_PECERR | I2C_SR1_TIMEOUT | I2C_SR1_SMBALERT)) | |
+ I2C_SR1_PECERR | I2C_SR1_TIMEOUT | I2C_SR1_SMBALERT)) | |
/*===========================================================================*/ | |
/* Driver exported variables. */ | |
@@ -104,35 +123,268 @@ I2CDriver I2CD2; | |
I2CDriver I2CD3; | |
#endif | |
-/*===========================================================================*/ | |
-/* Driver local variables and types. */ | |
-/*===========================================================================*/ | |
+/* quick and dirty queue to record event interrupts */ | |
+#define QEVENTS 32 | |
+#if QEVENTS > 0 | |
+typedef struct i2cQ_t { | |
+ uint8_t code; | |
+ uint8_t state; | |
+ uint16_t param; | |
+} i2cQ_t; | |
+i2cQ_t i2cQ[QEVENTS]; | |
+unsigned i2cI = QEVENTS; | |
+#define qEvt(posn,info) {if (++i2cI >= QEVENTS) i2cI = 0; \ | |
+ i2cQ[i2cI].code=(posn); i2cQ[i2cI].state=(i2cp->mode); i2cQ[i2cI].param=(info); } | |
+#else | |
+#define qEvt(posn,info) | |
+#endif | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+void I2CSlaveDummyCB(I2CDriver *i2cp) | |
+/* | |
+ dummy callback -- placeholder to ignore event | |
+*/ | |
+{(void)i2cp;} | |
+ | |
+ /* lock bus on receive or reply message */ | |
+const I2CSlaveMsg I2CSlaveLockOnMsg = { | |
+ 0, NULL, I2CSlaveDummyCB, I2CSlaveDummyCB, I2CSlaveDummyCB | |
+}; | |
+ | |
+#endif | |
/*===========================================================================*/ | |
/* Driver local functions. */ | |
/*===========================================================================*/ | |
/** | |
- * @brief Aborts an I2C transaction. | |
+ * @brief Resets and disables the I2C channel | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* | |
* @notapi | |
*/ | |
-static void i2c_lld_abort_operation(I2CDriver *i2cp) { | |
+static void i2cReset(I2CDriver *i2cp) { | |
I2C_TypeDef *dp = i2cp->i2c; | |
- | |
- /* Stops the I2C peripheral.*/ | |
+ /* reset the I2C registers.*/ | |
dp->CR1 = I2C_CR1_SWRST; | |
dp->CR1 = 0; | |
dp->CR2 = 0; | |
dp->SR1 = 0; | |
- /* Stops the associated DMA streams.*/ | |
+ /* Stop the associated DMA streams.*/ | |
dmaStreamDisable(i2cp->dmatx); | |
dmaStreamDisable(i2cp->dmarx); | |
} | |
+/** | |
+ * @brief Aborts an I2C transaction. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static void i2cAbortOperation(I2CDriver *i2cp) { | |
+ /* save control registers */ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ uint16_t cr2 = dp->CR2, cr1 = dp->CR1; | |
+#if HAL_USE_I2C_SLAVE | |
+ uint16_t oar1 = dp->OAR1, oar2 = dp->OAR2; | |
+#endif | |
+ uint16_t ccr = dp->CCR, trise = dp->TRISE; | |
+ | |
+ /* reset the I2C channel */ | |
+ i2cReset(i2cp); | |
+ | |
+ /* restore control registers */ | |
+ dp->TRISE = trise; dp->CCR = ccr; | |
+#if HAL_USE_I2C_SLAVE | |
+ /* restore address mataching */ | |
+ dp->OAR1 = oar1; dp->OAR2 = oar2; | |
+#endif | |
+ | |
+ /* Enable interrrupts */ | |
+ dp->CR2 = (cr2 & 0x3F) | I2C_CR2_ITERREN | I2C_CR2_DMAEN | I2C_CR2_ITEVTEN; | |
+ | |
+ /* Finish restoring and enable pheripheral */ | |
+ dp->CR1 = I2C_CR1_ACK | I2C_CR1_PE | (cr1 & (I2C_CR1_SMBUS | I2C_CR1_SMBTYPE)) | |
+#if HAL_USE_I2C_SLAVE | |
+ | (cr1 & I2C_CR1_ENGC) | |
+#endif | |
+ ; | |
+} | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE || HAL_USE_I2C_LOCK | |
+/** | |
+ * @brief stop transaction timeout countdown | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void stopTimer(I2CDriver *i2cp) | |
+{ | |
+ osalSysLockFromISR(); | |
+ chVTResetI(&i2cp->timer); | |
+ osalSysUnlockFromISR(); | |
+} | |
+#else | |
+#define stopTimer(ignored) {} | |
+#endif | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+/** | |
+ * @brief return the address matched | |
+ * | |
+ * @param[in] dp pointer to the @p I2C registers object | |
+ * @param[in] sr2 I2C SR2 register contents | |
+ * | |
+ * @notapi | |
+ * Only supports 7-bit addressing for now | |
+ */ | |
+static inline i2caddr_t matchedAdr(I2C_TypeDef *dp, uint32_t sr2) { | |
+ if (sr2 & I2C_SR2_GENCALL) | |
+ return 0; | |
+ if (sr2 & I2C_SR2_DUALF) | |
+ return (dp->OAR2>>1) & 0x7f; | |
+ return (dp->OAR1>>1) & 0x7f; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief report error via slave exception callback | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @note moves back to the idle mode | |
+ * @notapi | |
+ */ | |
+static inline void reportSlaveError(I2CDriver *i2cp) { | |
+#if HAL_USE_I2C_STARTFIX | |
+ i2cp->config->disarmStartDetect(); | |
+#endif | |
+ { | |
+ const I2CSlaveMsg *xfer = i2cp->mode >= i2cSlaveReplying ? | |
+ i2cp->slaveReply : i2cp->slaveRx; | |
+ if (xfer->exception) | |
+ xfer->exception(i2cp); /* in this case, i2cp->slaveErrors == 0 */ | |
+ } | |
+ i2cp->mode = i2cIdle; | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Handling of stalled slave mode I2C transactions. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static void slaveTimeExpired(void *i2cv) { | |
+ I2CDriver *i2cp = i2cv; | |
+ | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ i2cAbortOperation(i2cp); | |
+ reportSlaveError(i2cp); | |
+ } | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief start or restart slave mode transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void startSlaveAction(I2CDriver *i2cp, i2caddr_t targetAdr) | |
+{ | |
+ stopTimer(i2cp); | |
+ i2cp->targetAdr = targetAdr; | |
+ i2cp->slaveBytes = 0; | |
+ i2cp->slaveErrors = 0; | |
+ if (i2cp->slaveTimeout != TIME_INFINITE) | |
+ { | |
+ osalSysLockFromISR(); | |
+ chVTSetI(&i2cp->timer, i2cp->slaveTimeout, slaveTimeExpired, i2cp); | |
+ osalSysUnlockFromISR(); | |
+ } | |
+} | |
+ | |
+/** | |
+ * @brief end slave receive message DMA | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void endSlaveRxDMA(I2CDriver *i2cp) | |
+{ | |
+ size_t bytesRemaining = dmaStreamGetTransactionSize(i2cp->dmarx); | |
+ if (i2cp->slaveBytes) | |
+ i2cp->slaveBytes += 0xffff - bytesRemaining; | |
+ else | |
+ i2cp->slaveBytes = i2cp->slaveRx->size - bytesRemaining; | |
+ dmaStreamDisable(i2cp->dmarx); | |
+} | |
+ | |
+/** | |
+ * @brief end slave transmit DMA | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] bytesRemaining bytes lost in output queue | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void endSlaveReplyDMA(I2CDriver *i2cp, size_t bytesRemaining) | |
+{ | |
+ bytesRemaining += dmaStreamGetTransactionSize(i2cp->dmatx); | |
+ if (i2cp->slaveBytes) | |
+ i2cp->slaveBytes += 0xffff - bytesRemaining; | |
+ else | |
+ i2cp->slaveBytes = i2cp->slaveReply->size - bytesRemaining; | |
+ dmaStreamDisable(i2cp->dmatx); | |
+} | |
+ | |
+#endif | |
+ | |
+ | |
+/** | |
+ * @brief Wakes up a waiting thread. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] msg wakeup message | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void wakeup_isr(I2CDriver *i2cp, msg_t msg) | |
+{ | |
+ do { | |
+ osalSysLockFromISR(); | |
+ osalThreadResumeI(&(i2cp)->thread, msg); | |
+ osalSysUnlockFromISR(); | |
+ } while(0); | |
+ | |
+#if 0 | |
+ chSysLockFromISR(); | |
+ thread_t *tp = i2cp->thread; | |
+ if (tp != NULL) { | |
+ i2cp->thread = NULL; | |
+ tp->p_u.rdymsg = msg; | |
+ chSchReadyI(tp); | |
+ } | |
+ chSysUnlockFromISR(); | |
+#endif | |
+} | |
+ | |
+ | |
/** | |
* @brief Set clock speed. | |
* | |
@@ -158,55 +410,69 @@ static void i2c_lld_set_clock(I2CDriver *i2cp) { | |
regCCR = 0; | |
clock_div = I2C_CCR_CCR; | |
- if (clock_speed <= 100000) { | |
- /* Configure clock_div in standard mode.*/ | |
- osalDbgAssert(duty == STD_DUTY_CYCLE, "invalid standard mode duty cycle"); | |
- | |
- /* Standard mode clock_div calculate: Tlow/Thigh = 1/1.*/ | |
- osalDbgAssert((STM32_PCLK1 % (clock_speed * 2)) == 0, | |
- "PCLK1 must be divisible without remainder"); | |
+ switch (duty) { | |
+ case STD_DUTY_CYCLE: | |
+ /* Standard mode clock_div calculate: Tlow/Thigh = 1/1.*/ | |
+#ifndef STM32_I2C_LOOSE_CLK | |
+ /* Configure clock_div in standard mode.*/ | |
+ osalDbgAssert(clock_speed <= 100000, | |
+ "#1 - STD_DUTY_CYCLE limited to 100khz"); | |
+ osalDbgAssert(STM32_PCLK1 % (clock_speed * 2) == 0, | |
+ "#2 - PCLK1 not divisible by 2*I2Cclk"); | |
+#endif | |
clock_div = (uint16_t)(STM32_PCLK1 / (clock_speed * 2)); | |
+ | |
osalDbgAssert(clock_div >= 0x04, | |
"clock divider less then 0x04 not allowed"); | |
- regCCR |= (clock_div & I2C_CCR_CCR); | |
+ regCCR |= (clock_div & I2C_CCR_CCR); | |
- /* Sets the Maximum Rise Time for standard mode.*/ | |
- dp->TRISE = I2C_CLK_FREQ + 1; | |
- } | |
- else if (clock_speed <= 400000) { | |
- /* Configure clock_div in fast mode.*/ | |
- osalDbgAssert((duty == FAST_DUTY_CYCLE_2) || | |
- (duty == FAST_DUTY_CYCLE_16_9), | |
- "invalid fast mode duty cycle"); | |
- | |
- if (duty == FAST_DUTY_CYCLE_2) { | |
- /* Fast mode clock_div calculate: Tlow/Thigh = 2/1.*/ | |
- osalDbgAssert((STM32_PCLK1 % (clock_speed * 3)) == 0, | |
- "PCLK1 must be divided without remainder"); | |
+ /* Sets the Maximum Rise Time for standard mode.*/ | |
+ dp->TRISE = (uint16_t)(I2C_CLK_FREQ + 1); | |
+ break; | |
+ | |
+ case FAST_DUTY_CYCLE_2: | |
+ case FAST_DUTY_CYCLE_16_9: | |
+ /* Configure clock_div in fast mode.*/ | |
+#ifndef STM32_I2C_LOOSE_CLK | |
+ osalDbgAssert(clock_speed > 100000 && clock_speed <= 400000, | |
+ "#4 - I2Cclk out of range for FAST_DUTY_CYCLE"); | |
+#endif | |
+ if (duty == FAST_DUTY_CYCLE_2) { | |
+ /* Fast mode clock_div calculate: Tlow/Thigh = 2/1.*/ | |
+#ifndef STM32_I2C_LOOSE_CLK | |
+ osalDbgAssert((STM32_PCLK1 % (clock_speed * 3)) == 0, | |
+ "#6 - PCLK1 not divisible by 3*I2Cclk"); | |
+#endif | |
clock_div = (uint16_t)(STM32_PCLK1 / (clock_speed * 3)); | |
- } | |
- else if (duty == FAST_DUTY_CYCLE_16_9) { | |
- /* Fast mode clock_div calculate: Tlow/Thigh = 16/9.*/ | |
- osalDbgAssert((STM32_PCLK1 % (clock_speed * 25)) == 0, | |
- "PCLK1 must be divided without remainder"); | |
+ }else{ /* FAST_DUTY_CYCLE_16_9 */ | |
+ /* Fast mode clock_div calculate: Tlow/Thigh = 16/9.*/ | |
+#ifndef STM32_I2C_LOOSE_CLK | |
+ osalDbgAssert(STM32_PCLK1 % (clock_speed * 25) == 0, | |
+ "#7 - PCLK1 not divisible by 25*I2Cclk"); | |
+#endif | |
clock_div = (uint16_t)(STM32_PCLK1 / (clock_speed * 25)); | |
- regCCR |= I2C_CCR_DUTY; | |
- } | |
+ regCCR |= I2C_CCR_DUTY; | |
+ } | |
osalDbgAssert(clock_div >= 0x01, | |
"clock divider less then 0x04 not allowed"); | |
- regCCR |= (I2C_CCR_FS | (clock_div & I2C_CCR_CCR)); | |
+ regCCR |= (I2C_CCR_FS | (clock_div & I2C_CCR_CCR)); | |
- /* Sets the Maximum Rise Time for fast mode.*/ | |
- dp->TRISE = (I2C_CLK_FREQ * 300 / 1000) + 1; | |
+ /* Sets the Maximum Rise Time for fast mode.*/ | |
+ dp->TRISE = (uint16_t)((I2C_CLK_FREQ * 300 / 1000) + 1); | |
+ break; | |
+ | |
+ default: | |
+ osalSysHalt("Invalid I2C duty_cycle"); | |
} | |
osalDbgAssert((clock_div <= I2C_CCR_CCR), "the selected clock is too low"); | |
- dp->CCR = regCCR; | |
+ dp->CCR = (uint16_t)regCCR; | |
} | |
+ | |
/** | |
* @brief Set operation mode of I2C hardware. | |
* | |
@@ -221,79 +487,513 @@ static void i2c_lld_set_opmode(I2CDriver *i2cp) { | |
regCR1 = dp->CR1; | |
switch (opmode) { | |
- case OPMODE_I2C: | |
+ case OPMODE_I2C: | |
regCR1 &= (uint16_t)~(I2C_CR1_SMBUS|I2C_CR1_SMBTYPE); | |
- break; | |
- case OPMODE_SMBUS_DEVICE: | |
- regCR1 |= I2C_CR1_SMBUS; | |
+ break; | |
+ case OPMODE_SMBUS_DEVICE: | |
+ regCR1 |= I2C_CR1_SMBUS; | |
regCR1 &= (uint16_t)~(I2C_CR1_SMBTYPE); | |
- break; | |
- case OPMODE_SMBUS_HOST: | |
+ break; | |
+ case OPMODE_SMBUS_HOST: | |
regCR1 |= (I2C_CR1_SMBUS|I2C_CR1_SMBTYPE); | |
- break; | |
} | |
- dp->CR1 = regCR1; | |
+ dp->CR1 = (uint16_t)regCR1; | |
} | |
+ | |
+ | |
+#if USE_OLD_MASTER_STARTUP | |
/** | |
- * @brief I2C shared ISR code. | |
+ * @brief Handling of stalled master mode I2C transactions. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* | |
* @notapi | |
*/ | |
-static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { | |
+static void i2c_lld_safety_timeout(void *p) { | |
+ I2CDriver *i2cp = p; | |
+ | |
+ stopTimer(i2cp); | |
+#if HAL_USE_I2C_SLAVE /* abort any slave operation in progress */ | |
+ if (!(i2cp->i2c->SR2 & I2C_SR2_MSL)) | |
+ slaveTimeExpired(i2cp); /* in case slave preventing master bus access */ | |
+ else | |
+#endif | |
+ { | |
+ i2cAbortOperation(i2cp); | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ wakeup_isr(i2cp, I2C_ERR_TIMEOUT); | |
+} | |
+#endif | |
+ | |
+ | |
+#if HAL_USE_I2C_LOCK /* I2C bus locking support */ | |
+ | |
+/** | |
+ * @brief Handling of expired master bus lock timer | |
+ * | |
+ * @param[in] i2cv pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static void lockExpired(void *i2cv) { | |
+ I2CDriver *i2cp = i2cv; | |
+ | |
+ if (i2cp->mode == i2cIsMaster && !i2cp->thread) { /* between transactions */ | |
+ i2cp->i2c->CR1 |= (uint16_t)(I2C_CR1_STOP | I2C_CR1_ACK); | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ i2cp->lockDuration = TIME_IMMEDIATE; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Lock I2C bus at the beginning of the next message | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] lockDuration max number of ticks to hold bus locked | |
+ * - @a TIME_INFINITE no timeout. | |
+ * - @a TIME_IMMEDIATE unlock the bus immediately | |
+ * . | |
+ * | |
+ * Lock I2C bus at the beginning of the next message sent | |
+ * for a maximum of lockDuration ticks. No other I2C masters will | |
+ * be allowed to interrupt until i2cUnlock() is called. | |
+ * | |
+ * @notapi | |
+ **/ | |
+void i2c_lld_lock(I2CDriver *i2cp, systime_t lockDuration) | |
+{ | |
+ i2cp->lockDuration = lockDuration; | |
+ if (i2cp->mode >= i2cIsMaster) { | |
+ stopTimer(i2cp); | |
+ if (lockDuration == TIME_IMMEDIATE) | |
+ lockExpired(i2cp); | |
+ else if (lockDuration != TIME_INFINITE) | |
+ chVTSetI(&i2cp->timer, lockDuration, lockExpired, i2cp); | |
+ } | |
+} | |
+ | |
+#endif | |
+ | |
+ | |
+ | |
+/** | |
+ * @brief report error waking thread or invoking callback as appropriate | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] errCode error bit mask or code | |
+ * | |
+ * @notapi | |
+ */ | |
+static void reportErrs(I2CDriver *i2cp, i2cflags_t errCode) | |
+{ | |
+qEvt(0xee,errCode); | |
I2C_TypeDef *dp = i2cp->i2c; | |
+ if (i2cp->mode <= i2cIdle) /* failing to master bus */ | |
+ i2cAbortOperation(i2cp); | |
+ else if (dp->SR2 & I2C_SR2_MSL) { | |
+#if HAL_USE_I2C_LOCK /* I2C bus locking support */ | |
+ i2cp->mode = i2cIsMaster; | |
+ switch (i2cp->lockDuration) { | |
+ case TIME_INFINITE: | |
+ break; | |
+ case TIME_IMMEDIATE: | |
+ stopTimer(i2cp); | |
+ default: | |
+ if (!chVTIsArmedI(&i2cp->timer)) { | |
+ dp->CR1 |= (uint16_t)(I2C_CR1_STOP | I2C_CR1_ACK); | |
+ i2cp->mode = i2cIdle; | |
+ i2cp->lockDuration = TIME_IMMEDIATE; | |
+ } | |
+ } | |
+#else /* signal stop condition on any error */ | |
+ dp->CR1 |= (uint16_t)(I2C_CR1_STOP | I2C_CR1_ACK); | |
+ i2cp->mode = i2cIdle; | |
+#endif | |
+ } | |
+#if HAL_USE_I2C_SLAVE | |
+ else if (i2cp->mode < i2cIsMaster) { | |
+ i2cp->slaveErrors = errCode; | |
+ i2cAbortOperation(i2cp); | |
+ stopTimer(i2cp); | |
+ reportSlaveError(i2cp); | |
+ return; | |
+ } | |
+#endif | |
+ /* wake any waiting master mode handling thread. */ | |
+ i2cp->errors = errCode; | |
+ wakeup_isr(i2cp, I2C_ERROR); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief I2C error handler. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] sr content of the SR1 register to be decoded | |
+ * | |
+ * @notapi | |
+ */ | |
+static void i2c_lld_serve_error_interrupt(I2CDriver *i2cp, uint16_t sr) { | |
+#if HAL_USE_I2C_SLAVE | |
+/* NACK of last byte transmitted in slave response is NORMAL -- not an error! */ | |
+ if (i2cp->mode == i2cSlaveReplying && (sr & I2C_ERROR_MASK) == I2C_SR1_AF) { | |
+qEvt(0xcc,sr); | |
+ endSlaveReplyDMA(i2cp, 1); | |
+#if HAL_USE_I2C_STARTFIX | |
+ i2cp->config->disarmStartDetect(); | |
+#endif | |
+ if (i2cp->slaveReply->processMsg) | |
+ i2cp->slaveReply->processMsg(i2cp); | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+ stopTimer(i2cp); | |
+ i2cp->mode = i2cIdle; | |
+ return; | |
+ } | |
+#endif | |
+ i2cflags_t errs = 0; | |
+ | |
+ if (sr & I2C_SR1_BERR) /* Bus error. */ | |
+ errs = I2C_BUS_ERROR; | |
+ | |
+ if (sr & I2C_SR1_ARLO) /* Arbitration lost. */ | |
+ errs |= I2C_ARBITRATION_LOST; | |
+ | |
+ if (sr & I2C_SR1_AF) /* Acknowledge fail. */ | |
+ errs |= I2C_ACK_FAILURE; | |
+ | |
+ if (sr & I2C_SR1_OVR) /* Overrun. */ | |
+ errs |= I2C_OVERRUN; | |
+ | |
+ if (sr & I2C_SR1_TIMEOUT) /* SMBus Timeout. */ | |
+ errs |= I2C_TIMEOUT; | |
+ | |
+ if (sr & I2C_SR1_PECERR) /* PEC error. */ | |
+ errs |= I2C_PEC_ERROR; | |
+ | |
+ if (sr & I2C_SR1_SMBALERT) /* SMBus alert. */ | |
+ errs |= I2C_SMB_ALERT; | |
+ | |
+ if (!errs) | |
+ errs = I2C_UNKNOWN_ERROR; | |
+qEvt(0xcb,errs); | |
+ | |
+ /* Disable any active DMA */ | |
+ dmaStreamDisable(i2cp->dmatx); | |
+ dmaStreamDisable(i2cp->dmarx); | |
+ | |
+ reportErrs(i2cp, errs); | |
+} | |
+ | |
+ | |
+static inline void endMasterAction(I2CDriver *i2cp, uint32_t regCR1) | |
+{ | |
+#if HAL_USE_I2C_LOCK | |
+ if (i2cp->lockDuration != TIME_IMMEDIATE && ( | |
+ chVTIsArmedI(&i2cp->timer) || i2cp->lockDuration == TIME_INFINITE)) { | |
+ i2cp->mode = i2cIsMaster; | |
+ return; | |
+ } | |
+ stopTimer(i2cp); | |
+#endif | |
+ i2cp->i2c->CR1 = (regCR1 | I2C_CR1_STOP | I2C_CR1_ACK); | |
+ i2cp->mode = i2cIdle; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief I2C event handler ISR | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ uint16_t regCR1 = dp->CR1; | |
uint32_t regSR2 = dp->SR2; | |
- uint32_t event = dp->SR1; | |
- | |
- /* Interrupts are disabled just before dmaStreamEnable() because there | |
- is no need of interrupts until next transaction begin. All the work is | |
- done by the DMA.*/ | |
- switch (I2C_EV_MASK & (event | (regSR2 << 16))) { | |
- case I2C_EV5_MASTER_MODE_SELECT: | |
- if ((i2cp->addr >> 8) > 0) { | |
- /* 10-bit address: 1 1 1 1 0 X X R/W */ | |
- dp->DR = 0xF0 | (0x6 & (i2cp->addr >> 8)) | (0x1 & i2cp->addr); | |
- } else { | |
- dp->DR = i2cp->addr; | |
+ uint32_t event = dp->SR1 | (regSR2 << 16); | |
+ | |
+ | |
+ switch (event & I2C_EV_MASK) { | |
+ | |
+#define chkTransition(expectedMode) { \ | |
+ if (i2cp->mode != (expectedMode)) goto invalidTransition;} | |
+ | |
+ invalidTransition: /* error on known event out of expected sequence */ | |
+ i2cAbortOperation(i2cp); /* reset and reinit */ | |
+ qEvt(0x10, (event & 0xffff)); | |
+ reportErrs(i2cp, I2C_UNKNOWN_ERROR + i2cp->mode); | |
+ break; | |
+ | |
+#if HAL_USE_I2C_SLAVE | |
+ case I2C_EV1_SLAVE_RXADRMATCH : /* Slave mode - receive a message */ | |
+qEvt(0x11,0); | |
+ { | |
+ i2caddr_t targetAdr = matchedAdr(dp, regSR2); | |
+ switch (i2cp->mode) { | |
+ case i2cIdle: | |
+#if HAL_USE_I2C_STARTFIX | |
+ i2cp->config->armStartDetect(); | |
+#endif | |
+ break; | |
+ case i2cSlaveRxing: | |
+ endSlaveRxDMA(i2cp); | |
+ if (i2cp->slaveRx->processMsg) | |
+ i2cp->slaveRx->processMsg(i2cp); | |
+ break; | |
+ case i2cSlaveReplying: /* Master did not NACK last transmitted byte */ | |
+ endSlaveReplyDMA(i2cp, 2); | |
+ if (i2cp->slaveReply->processMsg) | |
+ i2cp->slaveReply->processMsg(i2cp); | |
+ break; | |
+ default: | |
+ goto invalidTransition; | |
+ } | |
+ startSlaveAction(i2cp, targetAdr); | |
+ } | |
+ { | |
+ const I2CSlaveMsg *rx = i2cp->slaveNextRx; | |
+ if (rx->adrMatched) // Q: Can rx ever be NULL? | |
+ rx->adrMatched(i2cp); // Execute callback on address match if specified | |
+ rx = i2cp->slaveRx = i2cp->slaveNextRx; | |
+ if (rx->body && rx->size) { | |
+ (void)dp->SR2; /* clear I2C_SR1_ADDR */ | |
+ /* Receive buffer available - can receive immediately. Set up slave RX DMA */ | |
+ dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmarx, rx->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmarx, rx->size); | |
+ dmaStreamEnable(i2cp->dmarx); | |
+ i2cp->mode = i2cSlaveRxing; | |
+ }else{ | |
+ dp->CR2 &= (uint16_t)(~I2C_CR2_ITEVTEN); /* No reply set up - hold clock low and wait */ | |
+ i2cp->mode = i2cLockedRxing; | |
+ } | |
} | |
break; | |
- case I2C_EV9_MASTER_ADD10: | |
- /* Set second addr byte (10-bit addressing)*/ | |
- dp->DR = (0xFF & (i2cp->addr >> 1)); | |
+ | |
+ case I2C_EV2_SLAVE_RXSTOP: /* STOP received - possibly without NAK */ | |
+qEvt(0x22,0); | |
+ dp->CR1 = (uint16_t)regCR1; /* clear STOPF */ | |
+ i2cp->slaveErrors = I2C_STOPPED; /* indicate that bus has been released */ | |
+ switch (i2cp->mode) { | |
+ case i2cSlaveRxing: | |
+ endSlaveRxDMA(i2cp); | |
+ if (i2cp->slaveRx->processMsg) | |
+ i2cp->slaveRx->processMsg(i2cp); | |
+ break; | |
+ case i2cSlaveReplying: /* Master did not NACK last transmitted byte */ | |
+ endSlaveReplyDMA(i2cp, 2); | |
+ if (i2cp->slaveReply->processMsg) | |
+ i2cp->slaveReply->processMsg(i2cp); | |
+ break; | |
+ default: | |
+ goto invalidTransition; | |
+ } | |
+#if HAL_USE_I2C_STARTFIX | |
+ i2cp->config->disarmStartDetect(); | |
+#endif | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+ stopTimer(i2cp); | |
+ i2cp->mode = i2cIdle; | |
+ break; | |
+ | |
+ case I2C_EV1_SLAVE_TXADRMATCH: /* Slave mode - reply */ | |
+qEvt(0x33,0); | |
+ { | |
+ i2caddr_t targetAdr = matchedAdr(dp, regSR2); | |
+ (void)dp->SR2; /* clear I2C_SR1_ADDR */ // TODO: Check | |
+ switch (i2cp->mode) { | |
+ case i2cIdle: | |
+#if HAL_USE_I2C_STARTFIX | |
+ i2cp->config->armStartDetect(); | |
+#endif | |
+ break; | |
+ case i2cSlaveRxing: /* Previous transaction not completed properly */ | |
+ endSlaveRxDMA(i2cp); | |
+ if (i2cp->slaveRx->processMsg) | |
+ i2cp->slaveRx->processMsg(i2cp); | |
+ break; | |
+ default: | |
+ goto invalidTransition; | |
+ } | |
+ startSlaveAction(i2cp, targetAdr); | |
+ } | |
+ { | |
+ const I2CSlaveMsg *reply = i2cp->slaveNextReply; | |
+ if (reply->adrMatched) // Q: Can reply be NULL? | |
+ reply->adrMatched(i2cp); | |
+ reply = i2cp->slaveReply = i2cp->slaveNextReply; // Q: Duplicate action? | |
+ if (reply->body && reply->size) { | |
+ /* Reply message available - can send immediately. Set up slave TX DMA */ | |
+ dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmatx, reply->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmatx, reply->size); | |
+ dmaStreamEnable(i2cp->dmatx); | |
+ i2cp->mode = i2cSlaveReplying; | |
+ }else{ | |
+ dp->CR2 &= (uint16_t)(~I2C_CR2_ITEVTEN); | |
+ i2cp->mode = i2cLockedReplying; | |
+ } | |
+ } | |
+ break; | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
+ case I2C_EV5_MASTER_MODE_SELECT: | |
+qEvt(0x55,0); | |
+ dp->DR = (uint16_t)i2cp->addr; | |
+ switch (i2cp->mode) { | |
+ case i2cIdle: | |
+#if HAL_USE_I2C_LOCK | |
+ { | |
+ systime_t lockDuration = i2cp->lockDuration; | |
+ if (lockDuration != TIME_IMMEDIATE && lockDuration != TIME_INFINITE) | |
+ { | |
+ osalSysLockFromISR(); | |
+ chVTSetI(&i2cp->timer, lockDuration, lockExpired, i2cp); | |
+ osalSysUnlockFromISR(); | |
+ } | |
+ } | |
+#endif | |
+ break; | |
+ case i2cIsMaster: | |
+ case i2cMasterStarted: | |
+ break; | |
+ default: | |
+ goto invalidTransition; | |
+ } | |
+ i2cp->mode = i2cMasterSelecting; | |
break; | |
- case I2C_EV6_MASTER_REC_MODE_SELECTED: | |
- dp->CR2 &= ~I2C_CR2_ITEVTEN; | |
+ | |
+ case I2C_EV6_MASTER_REC_MODE_SELECTED: | |
+qEvt(0x66,0); | |
+ chkTransition(i2cMasterSelecting); | |
+ if (!i2cp->masterRxbytes) { /* 0-length SMBus style quick read */ | |
+ endMasterAction(i2cp, regCR1); | |
+ (void)dp->SR2; /* clear I2C_SR1_ADDR */ | |
+ wakeup_isr(i2cp, I2C_OK); | |
+ break; | |
+ } | |
+ (void)dp->SR2; /* clear I2C_SR1_ADDR */ | |
+ /* RX DMA setup.*/ | |
+ dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmarx, i2cp->masterRxbuf); | |
+ dmaStreamSetTransactionSize(i2cp->dmarx, i2cp->masterRxbytes); | |
dmaStreamEnable(i2cp->dmarx); | |
dp->CR2 |= I2C_CR2_LAST; /* Needed in receiver mode. */ | |
- if (dmaStreamGetTransactionSize(i2cp->dmarx) < 2) | |
- dp->CR1 &= ~I2C_CR1_ACK; | |
+ if (i2cp->masterRxbytes < 2) | |
+ dp->CR1 = (uint16_t)(regCR1 & ~I2C_CR1_ACK); | |
+ i2cp->mode = i2cMasterRxing; | |
break; | |
- case I2C_EV6_MASTER_TRA_MODE_SELECTED: | |
- dp->CR2 &= ~I2C_CR2_ITEVTEN; | |
- dmaStreamEnable(i2cp->dmatx); | |
+ | |
+ case I2C_EV6_MASTER_TRA_MODE_SELECTED: | |
+qEvt(0x77,0); | |
+ (void)dp->SR2; /* clear I2C_SR1_ADDR */ | |
+ chkTransition(i2cMasterSelecting); | |
+ switch (i2cp->masterTxbytes) { | |
+ case 0: | |
+ goto doneWriting; | |
+ case 1: | |
+ dp->DR = i2cp->masterTxbuf[0]; | |
+ break; | |
+ case 2: | |
+ dp->DR = i2cp->masterTxbuf[0]; | |
+ dp->DR = i2cp->masterTxbuf[1]; | |
+ break; | |
+ default: | |
+ /* TX DMA setup.*/ | |
+ dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmatx, i2cp->masterTxbuf); | |
+ dmaStreamSetTransactionSize(i2cp->dmatx, i2cp->masterTxbytes); | |
+ dmaStreamEnable(i2cp->dmatx); | |
+ } | |
+ i2cp->mode = i2cMasterTxing; | |
break; | |
- case I2C_EV8_2_MASTER_BYTE_TRANSMITTED: | |
+ | |
+ case I2C_EV8_2_MASTER_BYTE_TRANSMITTED: | |
+qEvt(0x88,0); | |
/* Catches BTF event after the end of transmission.*/ | |
- if (dmaStreamGetTransactionSize(i2cp->dmarx) > 0) { | |
+ (void)dp->DR; /* clears BTF flag */ | |
+ chkTransition(i2cMasterTxing); | |
+doneWriting: | |
+ if (i2cp->masterRxbuf) { | |
/* Starts "read after write" operation, LSB = 1 -> receive.*/ | |
- i2cp->addr |= 0x01; | |
- dp->CR1 |= I2C_CR1_START | I2C_CR1_ACK; | |
- return; | |
+ dp->CR1 = (uint16_t)(regCR1 | I2C_CR1_START | I2C_CR1_ACK); | |
+ i2cp->addr |= 1; | |
+ i2cp->mode = i2cMasterStarted; | |
+ }else{ | |
+ endMasterAction(i2cp, regCR1); | |
+ wakeup_isr(i2cp, I2C_OK); | |
} | |
- dp->CR2 &= ~I2C_CR2_ITEVTEN; | |
- dp->CR1 |= I2C_CR1_STOP; | |
- _i2c_wakeup_isr(i2cp); | |
- break; | |
- default: | |
break; | |
+ | |
+ case 0: /* quietly ignore "uninteresting" events (i.e. i2c bus busy) */ | |
+ qEvt(0x0000); | |
+ break; | |
+ | |
+ default: /* unhandled event -- abort transaction, flag unknown err */ | |
+qEvt(0x9999,0); | |
+ //i2cAbortOperation(i2cp); | |
+ i2c_lld_serve_error_interrupt(i2cp, event); | |
+ } | |
+} | |
+ | |
+ | |
+#if HAL_USE_I2C_STARTFIX | |
+/** | |
+ * @brief external device detected start condition | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @details invoked from ISR if a START CONDITION detected during the time | |
+ * the startDetector is armed. | |
+ * This is a workaround for the STM32's lack of a repeated start event | |
+ * | |
+ * @notapi | |
+ */ | |
+void i2c_lld_startDetected(I2CDriver *i2cp) | |
+{ | |
+qEvt(0xdddd,0); | |
+ switch (i2cp->mode) { | |
+ case i2cIdle: | |
+ i2cAbortOperation(i2cp); /* quietly reset and reinit */ | |
+ return; | |
+ case i2cSlaveRxing: | |
+ endSlaveRxDMA(i2cp); | |
+ if (i2cp->slaveRx->processMsg) | |
+ i2cp->slaveRx->processMsg(i2cp); | |
+ break; | |
+ case i2cSlaveReplying: /* Master did not NACK last transmitted byte */ | |
+ endSlaveReplyDMA(i2cp, 2); | |
+ if (i2cp->slaveReply->processMsg) | |
+ i2cp->slaveReply->processMsg(i2cp); | |
+ break; | |
+ default: | |
+ i2cAbortOperation(i2cp); /* reset and reinit */ | |
+ reportErrs(i2cp, I2C_UNKNOWN_ERROR + i2cp->mode); | |
+ return; | |
} | |
- /* Clear ADDR flag. */ | |
- if (event & (I2C_SR1_ADDR | I2C_SR1_ADD10)) | |
- (void)dp->SR2; | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+ stopTimer(i2cp); | |
+ i2cp->mode = i2cIdle; | |
} | |
+/** | |
+ * @brief dummy placeholder for armStartDetector() and disarmStartDetector() | |
+ * | |
+ * @details *MUST* be placed in the configuration struct in cases where | |
+ * HAL_USE_I2C_STARTFIX is configured and no startDetector is defined, | |
+ * otherwise NULL pointers will be called for these functions! | |
+ * | |
+ * @notapi | |
+ */ | |
+void i2c_lld_noStartDetector(void) {} | |
+#endif | |
+ | |
+ | |
/** | |
* @brief DMA RX end IRQ handler. | |
* | |
@@ -305,6 +1005,7 @@ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { | |
static void i2c_lld_serve_rx_end_irq(I2CDriver *i2cp, uint32_t flags) { | |
I2C_TypeDef *dp = i2cp->i2c; | |
+qEvt(0xaa,0); | |
/* DMA errors handling.*/ | |
#if defined(STM32_I2C_DMA_ERROR_HOOK) | |
if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) { | |
@@ -316,10 +1017,25 @@ static void i2c_lld_serve_rx_end_irq(I2CDriver *i2cp, uint32_t flags) { | |
dmaStreamDisable(i2cp->dmarx); | |
- dp->CR2 &= ~I2C_CR2_LAST; | |
- dp->CR1 &= ~I2C_CR1_ACK; | |
- dp->CR1 |= I2C_CR1_STOP; | |
- _i2c_wakeup_isr(i2cp); | |
+#if HAL_USE_I2C_SLAVE | |
+ if (i2cp->mode < i2cIsMaster) { | |
+ static uint8_t bitbucket; | |
+ if (i2cp->slaveBytes) | |
+ i2cp->slaveBytes += 0xffff; // TODO: Why?? (slaveBytes is uint32) | |
+ else | |
+ i2cp->slaveBytes = i2cp->slaveRx->size; | |
+ /* discard data overrunning available rx buffer, but record total length */ | |
+ dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode & ~STM32_DMA_CR_MINC); | |
+ dmaStreamSetMemory0(i2cp->dmarx, &bitbucket); | |
+ dmaStreamSetTransactionSize(i2cp->dmarx, 0xffff); | |
+ dmaStreamEnable(i2cp->dmarx); | |
+ return; | |
+ } | |
+#endif | |
+ | |
+ dp->CR2 &= (uint16_t)(~I2C_CR2_LAST); | |
+ endMasterAction(i2cp, dp->CR1); | |
+ wakeup_isr(i2cp, I2C_OK); | |
} | |
/** | |
@@ -330,8 +1046,7 @@ static void i2c_lld_serve_rx_end_irq(I2CDriver *i2cp, uint32_t flags) { | |
* @notapi | |
*/ | |
static void i2c_lld_serve_tx_end_irq(I2CDriver *i2cp, uint32_t flags) { | |
- I2C_TypeDef *dp = i2cp->i2c; | |
- | |
+qEvt(0xbb,0); | |
/* DMA errors handling.*/ | |
#if defined(STM32_I2C_DMA_ERROR_HOOK) | |
if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) { | |
@@ -340,57 +1055,22 @@ static void i2c_lld_serve_tx_end_irq(I2CDriver *i2cp, uint32_t flags) { | |
#else | |
(void)flags; | |
#endif | |
- | |
- dmaStreamDisable(i2cp->dmatx); | |
- /* Enables interrupts to catch BTF event meaning transmission part complete. | |
- Interrupt handler will decide to generate STOP or to begin receiving part | |
- of R/W transaction itself.*/ | |
- dp->CR2 |= I2C_CR2_ITEVTEN; | |
-} | |
- | |
-/** | |
- * @brief I2C error handler. | |
- * | |
- * @param[in] i2cp pointer to the @p I2CDriver object | |
- * @param[in] sr content of the SR1 register to be decoded | |
- * | |
- * @notapi | |
- */ | |
-static void i2c_lld_serve_error_interrupt(I2CDriver *i2cp, uint16_t sr) { | |
- | |
- /* Clears interrupt flags just to be safe.*/ | |
dmaStreamDisable(i2cp->dmatx); | |
- dmaStreamDisable(i2cp->dmarx); | |
- | |
- i2cp->errors = I2C_NO_ERROR; | |
- | |
- if (sr & I2C_SR1_BERR) /* Bus error. */ | |
- i2cp->errors |= I2C_BUS_ERROR; | |
- if (sr & I2C_SR1_ARLO) /* Arbitration lost. */ | |
- i2cp->errors |= I2C_ARBITRATION_LOST; | |
- | |
- if (sr & I2C_SR1_AF) { /* Acknowledge fail. */ | |
- i2cp->i2c->CR2 &= ~I2C_CR2_ITEVTEN; | |
- i2cp->i2c->CR1 |= I2C_CR1_STOP; /* Setting stop bit. */ | |
- i2cp->errors |= I2C_ACK_FAILURE; | |
+#if HAL_USE_I2C_SLAVE | |
+ if (i2cp->mode < i2cIsMaster) { | |
+ const I2CSlaveMsg *reply = i2cp->slaveReply; | |
+ if (i2cp->slaveBytes) | |
+ i2cp->slaveBytes += 0xffff; // TODO: Why?? (slaveBytes is uint32) | |
+ else | |
+ i2cp->slaveBytes = reply->size; | |
+ /* repeat the last byte in the reply */ | |
+ dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode & ~STM32_DMA_CR_MINC); | |
+ dmaStreamSetMemory0(i2cp->dmatx, reply->body+reply->size-1); | |
+ dmaStreamSetTransactionSize(i2cp->dmatx, 0xffff); | |
+ dmaStreamEnable(i2cp->dmatx); | |
} | |
- | |
- if (sr & I2C_SR1_OVR) /* Overrun. */ | |
- i2cp->errors |= I2C_OVERRUN; | |
- | |
- if (sr & I2C_SR1_TIMEOUT) /* SMBus Timeout. */ | |
- i2cp->errors |= I2C_TIMEOUT; | |
- | |
- if (sr & I2C_SR1_PECERR) /* PEC error. */ | |
- i2cp->errors |= I2C_PEC_ERROR; | |
- | |
- if (sr & I2C_SR1_SMBALERT) /* SMBus alert. */ | |
- i2cp->errors |= I2C_SMB_ALERT; | |
- | |
- /* If some error has been identified then sends wakes the waiting thread.*/ | |
- if (i2cp->errors != I2C_NO_ERROR) | |
- _i2c_wakeup_error_isr(i2cp); | |
+#endif | |
} | |
/*===========================================================================*/ | |
@@ -508,6 +1188,9 @@ void i2c_lld_init(void) { | |
I2CD1.i2c = I2C1; | |
I2CD1.dmarx = STM32_DMA_STREAM(STM32_I2C_I2C1_RX_DMA_STREAM); | |
I2CD1.dmatx = STM32_DMA_STREAM(STM32_I2C_I2C1_TX_DMA_STREAM); | |
+#if HAL_USE_I2C_LOCK || HAL_USE_I2C_SLAVE | |
+ chVTObjectInit(&I2CD1.timer); | |
+#endif | |
#endif /* STM32_I2C_USE_I2C1 */ | |
#if STM32_I2C_USE_I2C2 | |
@@ -516,6 +1199,9 @@ void i2c_lld_init(void) { | |
I2CD2.i2c = I2C2; | |
I2CD2.dmarx = STM32_DMA_STREAM(STM32_I2C_I2C2_RX_DMA_STREAM); | |
I2CD2.dmatx = STM32_DMA_STREAM(STM32_I2C_I2C2_TX_DMA_STREAM); | |
+#if HAL_USE_I2C_LOCK || HAL_USE_I2C_SLAVE | |
+ chVTObjectInit(&I2CD2.timer); | |
+#endif | |
#endif /* STM32_I2C_USE_I2C2 */ | |
#if STM32_I2C_USE_I2C3 | |
@@ -524,6 +1210,9 @@ void i2c_lld_init(void) { | |
I2CD3.i2c = I2C3; | |
I2CD3.dmarx = STM32_DMA_STREAM(STM32_I2C_I2C3_RX_DMA_STREAM); | |
I2CD3.dmatx = STM32_DMA_STREAM(STM32_I2C_I2C3_TX_DMA_STREAM); | |
+#if HAL_USE_I2C_LOCK || HAL_USE_I2C_SLAVE | |
+ chVTObjectInit(&I2CD3.timer); | |
+#endif | |
#endif /* STM32_I2C_USE_I2C3 */ | |
} | |
@@ -537,9 +1226,6 @@ void i2c_lld_init(void) { | |
void i2c_lld_start(I2CDriver *i2cp) { | |
I2C_TypeDef *dp = i2cp->i2c; | |
- /* If in stopped state then enables the I2C and DMA clocks.*/ | |
- if (i2cp->state == I2C_STOP) { | |
- | |
i2cp->txdmamode = STM32_DMA_CR_PSIZE_BYTE | STM32_DMA_CR_MSIZE_BYTE | | |
STM32_DMA_CR_MINC | STM32_DMA_CR_DMEIE | | |
STM32_DMA_CR_TEIE | STM32_DMA_CR_TCIE | | |
@@ -549,11 +1235,14 @@ void i2c_lld_start(I2CDriver *i2cp) { | |
STM32_DMA_CR_TEIE | STM32_DMA_CR_TCIE | | |
STM32_DMA_CR_DIR_P2M; | |
+ /* If in stopped state then enables the I2C and DMA clocks.*/ | |
+ if (i2cp->state == I2C_STOP) { | |
+ | |
#if STM32_I2C_USE_I2C1 | |
if (&I2CD1 == i2cp) { | |
bool b; | |
- rccResetI2C1(); | |
+ rccResetI2C1(); // **** From trunk | |
b = dmaStreamAllocate(i2cp->dmarx, | |
STM32_I2C_I2C1_IRQ_PRIORITY, | |
(stm32_dmaisr_t)i2c_lld_serve_rx_end_irq, | |
@@ -579,7 +1268,7 @@ void i2c_lld_start(I2CDriver *i2cp) { | |
if (&I2CD2 == i2cp) { | |
bool b; | |
- rccResetI2C2(); | |
+ rccResetI2C2(); // *** From trunk | |
b = dmaStreamAllocate(i2cp->dmarx, | |
STM32_I2C_I2C2_IRQ_PRIORITY, | |
(stm32_dmaisr_t)i2c_lld_serve_rx_end_irq, | |
@@ -605,7 +1294,7 @@ void i2c_lld_start(I2CDriver *i2cp) { | |
if (&I2CD3 == i2cp) { | |
bool b; | |
- rccResetI2C3(); | |
+ rccResetI2C3(); // *** From trunk | |
b = dmaStreamAllocate(i2cp->dmarx, | |
STM32_I2C_I2C3_IRQ_PRIORITY, | |
(stm32_dmaisr_t)i2c_lld_serve_rx_end_irq, | |
@@ -628,21 +1317,32 @@ void i2c_lld_start(I2CDriver *i2cp) { | |
#endif /* STM32_I2C_USE_I2C3 */ | |
} | |
- /* I2C registers pointed by the DMA.*/ | |
- dmaStreamSetPeripheral(i2cp->dmarx, &dp->DR); | |
- dmaStreamSetPeripheral(i2cp->dmatx, &dp->DR); | |
+ /* I2C registers pointed by the DMA.*/ | |
+ dmaStreamSetPeripheral(i2cp->dmarx, &dp->DR); | |
+ dmaStreamSetPeripheral(i2cp->dmatx, &dp->DR); | |
/* Reset i2c peripheral.*/ | |
dp->CR1 = I2C_CR1_SWRST; | |
dp->CR1 = 0; | |
- dp->CR2 = I2C_CR2_ITERREN | I2C_CR2_DMAEN; | |
- /* Setup I2C parameters.*/ | |
- i2c_lld_set_clock(i2cp); | |
- i2c_lld_set_opmode(i2cp); | |
+ i2cp->mode = i2cIdle; | |
+#if HAL_USE_I2C_LOCK | |
+ i2cp->lockDuration = TIME_IMMEDIATE; | |
+#endif | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ i2cp->slaveNextReply = i2cp->slaveNextRx = &I2CSlaveLockOnMsg; | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+ i2cp->slaveTimeout = TIME_INFINITE; | |
+#endif | |
+ | |
+ /* Setup I2C parameters.*/ | |
+ i2c_lld_set_clock(i2cp); | |
+ i2c_lld_set_opmode(i2cp); | |
- /* Ready to go.*/ | |
- dp->CR1 |= I2C_CR1_PE; | |
+ /* Enable interrrupts */ | |
+ dp->CR2 |= I2C_CR2_ITERREN | I2C_CR2_DMAEN | I2C_CR2_ITEVTEN; | |
+ /* Ready to go.*/ | |
+ dp->CR1 |= I2C_CR1_PE | I2C_CR1_ACK; | |
} | |
/** | |
@@ -654,11 +1354,11 @@ void i2c_lld_start(I2CDriver *i2cp) { | |
*/ | |
void i2c_lld_stop(I2CDriver *i2cp) { | |
- /* If not in stopped state then disables the I2C clock.*/ | |
+ /* If not in stopped state, then disable the I2C clock.*/ | |
if (i2cp->state != I2C_STOP) { | |
/* I2C disable.*/ | |
- i2c_lld_abort_operation(i2cp); | |
+ i2cReset(i2cp); | |
dmaStreamRelease(i2cp->dmatx); | |
dmaStreamRelease(i2cp->dmarx); | |
@@ -685,55 +1385,71 @@ void i2c_lld_stop(I2CDriver *i2cp) { | |
rccDisableI2C3(FALSE); | |
} | |
#endif | |
+ | |
+ reportErrs(i2cp, I2C_STOPPED); /* wake any blocked thread */ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ reportErrs(i2cp, I2C_STOPPED); /* ensure both master and slave notified */ | |
+#endif | |
} | |
} | |
+ | |
/** | |
- * @brief Receives data via the I2C bus as master. | |
- * @details Number of receiving bytes must be more than 1 on STM32F1x. This is | |
- * hardware restriction. | |
+ * @brief Starts an I2C bus master transaction. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
- * @param[in] addr slave device address | |
- * @param[out] rxbuf pointer to the receive buffer | |
- * @param[in] rxbytes number of bytes to be received | |
* @param[in] timeout the number of ticks before the operation timeouts, | |
* the following special values are allowed: | |
* - @a TIME_INFINITE no timeout. | |
* . | |
* @return The operation status. | |
- * @retval MSG_OK if the function succeeded. | |
- * @retval MSG_RESET if one or more I2C errors occurred, the errors can | |
+ * @retval I2C_OK if the function succeeded. | |
+ * @retval I2C_ERROR if one or more I2C errors occurred, the errors can | |
* be retrieved using @p i2cGetErrors(). | |
- * @retval MSG_TIMEOUT if a timeout occurred before operation end. <b>After a | |
- * timeout the driver must be stopped and restarted | |
- * because the bus is in an uncertain state</b>. | |
+ * @retval I2C_ERR_TIMEOUT A timeout occurred before operation end. <b>After a | |
+ * timeout the driver should be stopped and restarted | |
+ * because the bus may in an uncertain state</b>. | |
+ * Drivers that support slave mode require only the the driver be restarted | |
+ * after a timeout, as stopping and restarting may result in missed events. | |
+ * | |
+ * There's a system lock active from higher levels when this is called | |
* | |
* @notapi | |
*/ | |
-msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
- uint8_t *rxbuf, size_t rxbytes, | |
- systime_t timeout) { | |
- I2C_TypeDef *dp = i2cp->i2c; | |
- systime_t start, end; | |
+#if USE_OLD_MASTER_STARTUP | |
+// This is the 'original' from the V2.x master/slave code | |
+static msg_t startMasterAction(I2CDriver *i2cp, systime_t timeout) | |
+{ | |
+ osalDbgAssert((i2cp->mode <= i2cIsMaster), "busy"); | |
+ | |
+ i2cp->i2c->CR1 |= I2C_CR1_START | I2C_CR1_ACK; | |
+ | |
+ virtual_timer_t vt; /* Global timeout for the whole operation.*/ | |
+ chVTObjectInit(&vt); | |
+ | |
+ if (timeout != TIME_INFINITE) | |
+ chVTSetI(&vt, timeout, i2c_lld_safety_timeout, i2cp); | |
+ i2cp->errors = 0; | |
+ i2cp->thread = chThdGetSelfX(); | |
+ chSchGoSleepS(CH_STATE_SUSPENDED); | |
+ chVTResetI(&vt); | |
+ return chThdGetSelfX()->p_u.rdymsg; | |
+} | |
-#if defined(STM32F1XX_I2C) | |
- osalDbgCheck(rxbytes > 1); | |
-#endif | |
- /* Resetting error flags for this transfer.*/ | |
- i2cp->errors = I2C_NO_ERROR; | |
+#else | |
- /* Initializes driver fields, LSB = 1 -> receive.*/ | |
- i2cp->addr = (addr << 1) | 0x01; | |
- /* Releases the lock from high level driver.*/ | |
- osalSysUnlock(); | |
- /* RX DMA setup.*/ | |
- dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
- dmaStreamSetMemory0(i2cp->dmarx, rxbuf); | |
- dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes); | |
+// This version uses same logic as the 'original' V3.x master-only driver. | |
+// Note: may need adjustment to work in multi-master mode as well - see i2c_lld_safety_timeout() | |
+static msg_t startMasterAction(I2CDriver *i2cp, systime_t timeout) | |
+{ | |
+ systime_t start, end; | |
+ | |
+ osalDbgAssert((i2cp->mode <= i2cIsMaster), "busy"); | |
+ | |
+ osalSysUnlock(); | |
/* Calculating the time window for the timeout on the busy bus condition.*/ | |
start = osalOsGetSystemTimeX(); | |
@@ -746,7 +1462,7 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
/* If the bus is not busy then the operation can continue, note, the | |
loop is exited in the locked state.*/ | |
- if (!(dp->SR2 & I2C_SR2_BUSY) && !(dp->CR1 & I2C_CR1_STOP)) | |
+ if (!(i2cp->i2c->SR2 & I2C_SR2_BUSY) && !(i2cp->i2c->CR1 & I2C_CR1_STOP)) | |
break; | |
/* If the system time went outside the allowed window then a timeout | |
@@ -757,13 +1473,59 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
osalSysUnlock(); | |
} | |
- /* Starts the operation.*/ | |
- dp->CR2 |= I2C_CR2_ITEVTEN; | |
- dp->CR1 |= I2C_CR1_START | I2C_CR1_ACK; | |
+ | |
+ i2cp->i2c->CR1 |= I2C_CR1_START | I2C_CR1_ACK; | |
/* Waits for the operation completion or a timeout.*/ | |
return osalThreadSuspendTimeoutS(&i2cp->thread, timeout); | |
} | |
+#endif | |
+ | |
+/** | |
+ * @brief Receives data via the I2C bus as master. | |
+ * @details Number of receiving bytes must be 0 or more than 1 on STM32F1x. | |
+ * This is hardware restriction. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] addr slave device address | |
+ * @param[out] rxbuf pointer to the receive buffer | |
+ * @param[in] rxbytes number of bytes to be received | |
+ * @param[in] timeout the number of ticks before the operation timeouts, | |
+ * the following special values are allowed: | |
+ * - @a TIME_INFINITE no timeout. | |
+ * . | |
+ * @return The operation status. | |
+ * @retval I2C_OK if the function succeeded. | |
+ * @retval I2C_ERROR if one or more I2C errors occurred, the errors can | |
+ * be retrieved using @p i2cGetErrors(). | |
+ * @retval I2C_ERR_TIMEOUT A timeout occurred before operation end. <b>After a | |
+ * timeout the driver should be stopped and restarted | |
+ * because the bus may in an uncertain state</b>. | |
+ * Drivers that support slave mode require only the the driver be restarted | |
+ * after a timeout, as stopping and restarting may result in missed events. | |
+ * | |
+ * @notapi | |
+ */ | |
+msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
+ uint8_t *rxbuf, size_t rxbytes, | |
+ systime_t timeout) { | |
+ osalDbgAssert((rxbytes < (1<<16)), | |
+ "#1 - >64Kbytes"); | |
+#if defined(STM32F1XX_I2C) | |
+ osalDbgAssert((rxbytes != 1), | |
+ "#2 - rxbytes==1"); | |
+#endif | |
+ osalDbgAssert((i2cp->thread==NULL), | |
+ "#3 - reentry"); | |
+ | |
+ /* Initializes driver fields, LSB = 1 -> receive.*/ | |
+ i2cp->addr = (addr << 1) | 1; | |
+ | |
+ /* store away DMA info for later activation in event ISR */ | |
+ i2cp->masterRxbuf = rxbuf; | |
+ i2cp->masterRxbytes = (uint16_t) rxbytes; | |
+ return startMasterAction(i2cp, timeout); | |
+} | |
/** | |
* @brief Transmits data via the I2C bus as master. | |
@@ -781,12 +1543,16 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
* - @a TIME_INFINITE no timeout. | |
* . | |
* @return The operation status. | |
- * @retval MSG_OK if the function succeeded. | |
- * @retval MSG_RESET if one or more I2C errors occurred, the errors can | |
+ * @retval I2C_OK if the function succeeded. | |
+ * @retval I2C_ERROR if one or more I2C errors occurred, the errors can | |
* be retrieved using @p i2cGetErrors(). | |
- * @retval MSG_TIMEOUT if a timeout occurred before operation end. <b>After a | |
- * timeout the driver must be stopped and restarted | |
+ * @retval I2C_ERR_TIMEOUT A timeout occurred before operation end. <b>After a | |
+ * timeout the driver should be stopped and restarted | |
* because the bus is in an uncertain state</b>. | |
+ * Drivers that support slave mode require only the the driver be restarted | |
+ * after a timeout, as stopping and restarting may result in missed events. | |
+ * | |
+ * There's a system lock active on entry from higher level driver | |
* | |
* @notapi | |
*/ | |
@@ -794,62 +1560,186 @@ msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
const uint8_t *txbuf, size_t txbytes, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout) { | |
- I2C_TypeDef *dp = i2cp->i2c; | |
- systime_t start, end; | |
- | |
+ if (rxbuf == NULL) | |
+ rxbytes = 0; | |
+ osalDbgAssert(((rxbytes | txbytes) < (1<<16)), "#1 - >64Kbytes"); | |
#if defined(STM32F1XX_I2C) | |
osalDbgCheck((rxbytes == 0) || ((rxbytes > 1) && (rxbuf != NULL))); | |
#endif | |
+ osalDbgAssert((i2cp->thread==NULL), "#3 - reentry"); | |
+ | |
+ /* Initializes driver fields, LSB = 0 -> write.*/ | |
+ i2cp->addr = addr << 1; | |
+ /* store away DMA info for later activation in event ISR */ | |
+ i2cp->masterTxbuf = txbuf; | |
+ i2cp->masterTxbytes = (uint16_t) txbytes; | |
+ i2cp->masterRxbuf = rxbuf; | |
+ i2cp->masterRxbytes = (uint16_t) rxbytes; | |
+ return startMasterAction(i2cp, timeout); | |
+} | |
- /* Resetting error flags for this transfer.*/ | |
- i2cp->errors = I2C_NO_ERROR; | |
- /* Initializes driver fields, LSB = 0 -> transmit.*/ | |
- i2cp->addr = (addr << 1); | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
- /* Releases the lock from high level driver.*/ | |
- osalSysUnlock(); | |
+/* These bits are undocumented, but used in STM32l1xx I2C example driver */ | |
+#define I2C_OAR1_Ack_7bit (0x4000) /*enable 7 bit address acknowledge*/ | |
+#define I2C_OAR1_Ack_10bit (0xC000) /*enable 10 bit address acknowledge*/ | |
- /* TX DMA setup.*/ | |
- dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
- dmaStreamSetMemory0(i2cp->dmatx, txbuf); | |
- dmaStreamSetTransactionSize(i2cp->dmatx, txbytes); | |
+/** | |
+ * @brief Reconfigure I2C channel to respond to indicated address | |
+ * in addition to those already matched | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C network address | |
+ * | |
+ * @return Length of message OR the type of event received | |
+ * @retval I2C_OK Success | |
+ * @retval I2C_ERROR Cannot match address in addition of those already | |
+ * | |
+ * @details MatchAddress calls are cumulative. | |
+ * Specify address zero to match I2C "all call" | |
+ * Does not support 10-bit addressing. | |
+ * | |
+ * @notapi | |
+ **/ | |
+msg_t i2c_lld_matchAddress(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ if (i2cadr == 0) { | |
+ dp->CR1 |= I2C_CR1_ENGC; | |
+ dp->OAR1 |= I2C_OAR1_Ack_7bit; | |
+ }else{ | |
+ uint16_t adr = i2cadr << 1; | |
+ uint16_t ownAdr = dp->OAR1 & (0x7f<<1); | |
+ if (ownAdr == 0 || ownAdr == adr) | |
+ dp->OAR1 = adr | I2C_OAR1_Ack_7bit; | |
+ else if (!(dp->OAR2 & I2C_OAR2_ENDUAL)) | |
+ dp->OAR2 = adr | I2C_OAR2_ENDUAL; | |
+ else if ((dp->OAR2 & (0x7f<<1)) != adr) | |
+ return I2C_ERROR; /* cannot add this address to set of those matched */ | |
+ } | |
+ return I2C_OK; | |
+} | |
- /* RX DMA setup.*/ | |
- dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
- dmaStreamSetMemory0(i2cp->dmarx, rxbuf); | |
- dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes); | |
- /* Calculating the time window for the timeout on the busy bus condition.*/ | |
- start = osalOsGetSystemTimeX(); | |
- end = start + OSAL_MS2ST(STM32_I2C_BUSY_TIMEOUT); | |
+/** | |
+ * @brief Reconfigure I2C channel to no longer match specified address | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C network address | |
+ * | |
+ * @details A message being transferred that has already matched the | |
+ * specified address will continue being processed. | |
+ * Requests to unmatch an address that is not currently being matched | |
+ * are ignored. | |
+ * Does not support 10-bit addressing. | |
+ * | |
+ * @notapi | |
+ **/ | |
+void i2c_lld_unmatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ if (i2cadr == 0) { | |
+ dp->CR1 &= (uint16_t)~I2C_CR1_ENGC; | |
+ if ((dp->OAR1 & (0x7f<<1)) == 0) | |
+ dp->OAR1 = 0; | |
+ }else{ | |
+ uint16_t adr = i2cadr << 1; | |
+ if ((dp->OAR1 & (0x7f<<1)) == adr) { | |
+ if (dp->OAR2 & I2C_OAR2_ENDUAL) | |
+ dp->OAR1 = (dp->OAR2 & (0x7f<<1)) | I2C_OAR1_Ack_7bit; | |
+ else | |
+ dp->OAR1 = dp->CR1 & I2C_CR1_ENGC ? I2C_OAR1_Ack_7bit : 0; | |
+ }else if (dp->OAR2 & I2C_OAR2_ENDUAL && (dp->OAR2 & (0x7f<<1)) == adr) | |
+ dp->OAR2 &= ~I2C_OAR2_ENDUAL; | |
+ } | |
+} | |
- /* Waits until BUSY flag is reset or, alternatively, for a timeout | |
- condition.*/ | |
- while (true) { | |
- osalSysLock(); | |
- /* If the bus is not busy then the operation can continue, note, the | |
- loop is exited in the locked state.*/ | |
- if (!(dp->SR2 & I2C_SR2_BUSY) && !(dp->CR1 & I2C_CR1_STOP)) | |
- break; | |
+/** | |
+ * @brief Reconfigure I2C channel to no longer match any address | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @details Causes all subsequent messages to be ignored. | |
+ * A message being transferred that has already matched a | |
+ * slave address will continue being processed. | |
+ * | |
+ * @notapi | |
+ **/ | |
+void i2c_lld_unmatchAll(I2CDriver *i2cp) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ dp->CR1 &= (uint16_t)~I2C_CR1_ENGC; | |
+ dp->OAR1 = 0; | |
+ dp->OAR2 = 0; | |
+} | |
- /* If the system time went outside the allowed window then a timeout | |
- condition is returned.*/ | |
- if (!osalOsIsTimeWithinX(osalOsGetSystemTimeX(), start, end)) | |
- return MSG_TIMEOUT; | |
- osalSysUnlock(); | |
+/** | |
+ * @brief Configure callbacks & buffers to receive messages | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent received messages | |
+ * | |
+ * @details Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @notapi | |
+ */ | |
+void i2c_lld_slaveReceive(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg) | |
+{ | |
+ osalDbgCheck((rxMsg && rxMsg->size <= 0xffff)); | |
+ i2cp->slaveNextRx = rxMsg; | |
+ if (i2cp->mode == i2cLockedRxing && rxMsg->body && rxMsg->size) { | |
+ /* We can receive now! */ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ (void)dp->SR1, dp->SR2; /* clear I2C_SR1_ADDR */ | |
+ i2cp->slaveRx = rxMsg; | |
+ /* slave RX DMA setup */ | |
+ dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmarx, rxMsg->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmarx, rxMsg->size); | |
+ dmaStreamEnable(i2cp->dmarx); | |
+ i2cp->mode = i2cSlaveRxing; | |
+ dp->CR2 |= I2C_CR2_ITEVTEN; | |
} | |
+} | |
- /* Starts the operation.*/ | |
- dp->CR2 |= I2C_CR2_ITEVTEN; | |
- dp->CR1 |= I2C_CR1_START; | |
- /* Waits for the operation completion or a timeout.*/ | |
- return osalThreadSuspendTimeoutS(&i2cp->thread, timeout); | |
+/** | |
+ * @brief Configure callbacks & buffers for query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @notapi | |
+ */ | |
+void i2c_lld_slaveReply(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg) | |
+{ | |
+ osalDbgCheck((replyMsg && replyMsg->size <= 0xffff)); | |
+ i2cp->slaveNextReply = replyMsg; | |
+ if (i2cp->mode == i2cLockedReplying && replyMsg->body && replyMsg->size) { | |
+ i2cp->slaveReply = replyMsg; | |
+ /* slave TX DMA setup -- we can reply now! */ | |
+ dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmatx, replyMsg->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmatx, replyMsg->size); | |
+ dmaStreamEnable(i2cp->dmatx); | |
+ i2cp->mode = i2cSlaveReplying; | |
+ i2cp->i2c->CR2 |= I2C_CR2_ITEVTEN; | |
+ } | |
} | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
#endif /* HAL_USE_I2C */ | |
/** @} */ | |
diff --git a/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.h b/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.h | |
index a812ceb3f..b733fb7b6 100644 | |
--- a/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.h | |
+++ b/os/hal/ports/STM32/LLD/I2Cv1/hal_i2c_lld.h | |
@@ -1,5 +1,5 @@ | |
/* | |
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | |
+ ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
@@ -15,19 +15,19 @@ | |
*/ | |
/* | |
Concepts and parts of this file have been contributed by Uladzimir Pylinsky | |
- aka barthess. | |
+ aka barthess. I2C Slave API contributed by Brent Roman ([email protected]) | |
*/ | |
/** | |
- * @file I2Cv1/hal_i2c_lld.h | |
+ * @file STM32/I2Cv1/i2c_lld.h | |
* @brief STM32 I2C subsystem low level driver header. | |
* | |
* @addtogroup I2C | |
* @{ | |
*/ | |
-#ifndef HAL_I2C_LLD_H | |
-#define HAL_I2C_LLD_H | |
+#ifndef _I2C_LLD_H_ | |
+#define _I2C_LLD_H_ | |
#if HAL_USE_I2C || defined(__DOXYGEN__) | |
@@ -40,6 +40,12 @@ | |
*/ | |
#define I2C_CLK_FREQ ((STM32_PCLK1) / 1000000) | |
+/** | |
+ * @brief Invalid I2C bus address | |
+ */ | |
+#define i2cInvalidAdr ((i2caddr_t) -1) | |
+ | |
+ | |
/*===========================================================================*/ | |
/* Driver pre-compile time settings. */ | |
/*===========================================================================*/ | |
@@ -54,7 +60,7 @@ | |
* @note The default is @p FALSE. | |
*/ | |
#if !defined(STM32_I2C_USE_I2C1) || defined(__DOXYGEN__) | |
-#define STM32_I2C_USE_I2C1 FALSE | |
+#define STM32_I2C_USE_I2C1 FALSE | |
#endif | |
/** | |
@@ -63,7 +69,7 @@ | |
* @note The default is @p FALSE. | |
*/ | |
#if !defined(STM32_I2C_USE_I2C2) || defined(__DOXYGEN__) | |
-#define STM32_I2C_USE_I2C2 FALSE | |
+#define STM32_I2C_USE_I2C2 FALSE | |
#endif | |
/** | |
@@ -72,9 +78,25 @@ | |
* @note The default is @p FALSE. | |
*/ | |
#if !defined(STM32_I2C_USE_I2C3) || defined(__DOXYGEN__) | |
-#define STM32_I2C_USE_I2C3 FALSE | |
+#define STM32_I2C_USE_I2C3 FALSE | |
+#endif | |
+ | |
+/** | |
+ * @brief Enables support for I2C slave mode operation | |
+ */ | |
+#if !defined(HAL_USE_I2C_SLAVE) || defined(__DOXYGEN__) | |
+#define HAL_USE_I2C_SLAVE FALSE | |
+#define HAL_USE_I2C_STARTFIX FALSE | |
#endif | |
+/** | |
+ * @brief Enables additional code needed with V1 I2C | |
+ */ | |
+#if !defined(HAL_USE_I2C_STARTFIX) || defined(__DOXYGEN__) | |
+#define HAL_USE_I2C_STARTFIX FALSE | |
+#endif | |
+ | |
+ | |
/** | |
* @brief I2C timeout on busy condition in milliseconds. | |
*/ | |
@@ -86,21 +108,21 @@ | |
* @brief I2C1 interrupt priority level setting. | |
*/ | |
#if !defined(STM32_I2C_I2C1_IRQ_PRIORITY) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C1_IRQ_PRIORITY 10 | |
+#define STM32_I2C_I2C1_IRQ_PRIORITY 10 | |
#endif | |
/** | |
* @brief I2C2 interrupt priority level setting. | |
*/ | |
#if !defined(STM32_I2C_I2C2_IRQ_PRIORITY) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C2_IRQ_PRIORITY 10 | |
+#define STM32_I2C_I2C2_IRQ_PRIORITY 10 | |
#endif | |
/** | |
* @brief I2C3 interrupt priority level setting. | |
*/ | |
#if !defined(STM32_I2C_I2C3_IRQ_PRIORITY) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C3_IRQ_PRIORITY 10 | |
+#define STM32_I2C_I2C3_IRQ_PRIORITY 10 | |
#endif | |
/** | |
@@ -149,7 +171,7 @@ | |
* @note This option is only available on platforms with enhanced DMA. | |
*/ | |
#if !defined(STM32_I2C_I2C1_RX_DMA_STREAM) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0) | |
+#define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 0) | |
#endif | |
/** | |
@@ -157,7 +179,7 @@ | |
* @note This option is only available on platforms with enhanced DMA. | |
*/ | |
#if !defined(STM32_I2C_I2C1_TX_DMA_STREAM) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) | |
+#define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) | |
#endif | |
/** | |
@@ -165,7 +187,7 @@ | |
* @note This option is only available on platforms with enhanced DMA. | |
*/ | |
#if !defined(STM32_I2C_I2C2_RX_DMA_STREAM) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) | |
+#define STM32_I2C_I2C2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) | |
#endif | |
/** | |
@@ -173,7 +195,7 @@ | |
* @note This option is only available on platforms with enhanced DMA. | |
*/ | |
#if !defined(STM32_I2C_I2C2_TX_DMA_STREAM) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) | |
+#define STM32_I2C_I2C2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) | |
#endif | |
/** | |
@@ -181,7 +203,7 @@ | |
* @note This option is only available on platforms with enhanced DMA. | |
*/ | |
#if !defined(STM32_I2C_I2C3_RX_DMA_STREAM) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) | |
+#define STM32_I2C_I2C3_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 2) | |
#endif | |
/** | |
@@ -189,17 +211,17 @@ | |
* @note This option is only available on platforms with enhanced DMA. | |
*/ | |
#if !defined(STM32_I2C_I2C3_TX_DMA_STREAM) || defined(__DOXYGEN__) | |
-#define STM32_I2C_I2C3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) | |
+#define STM32_I2C_I2C3_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) | |
#endif | |
#else /* !STM32_ADVANCED_DMA */ | |
/* Fixed streams for platforms using the old DMA peripheral, the values are | |
valid for both STM32F1xx and STM32L1xx.*/ | |
-#define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) | |
-#define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) | |
-#define STM32_I2C_I2C2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 5) | |
-#define STM32_I2C_I2C2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) | |
+#define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 7) | |
+#define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6) | |
+#define STM32_I2C_I2C2_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 5) | |
+#define STM32_I2C_I2C2_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 4) | |
#endif /* !STM32_ADVANCED_DMA*/ | |
@@ -385,25 +407,84 @@ typedef enum { | |
} i2cdutycycle_t; | |
/** | |
- * @brief Type of I2C driver configuration structure. | |
+ * @brief Type of a structure representing an I2C driver. | |
+ */ | |
+typedef struct I2CDriver I2CDriver; | |
+ | |
+/** | |
+ * @brief Driver configuration structure. | |
*/ | |
typedef struct { | |
- /* End of the mandatory fields.*/ | |
i2copmode_t op_mode; /**< @brief Specifies the I2C mode. */ | |
uint32_t clock_speed; /**< @brief Specifies the clock frequency. | |
@note Must be set to a value lower | |
than 400kHz. */ | |
i2cdutycycle_t duty_cycle; /**< @brief Specifies the I2C fast mode | |
duty cycle. */ | |
+#if HAL_USE_I2C_STARTFIX && HAL_USE_I2C_SLAVE | |
+ void (*armStartDetect)(void); /**< @brief Arm Start Condition Detector */ | |
+ void (*disarmStartDetect)(void);/**< @brief Disarm Start Condition Detector */ | |
+#endif | |
} I2CConfig; | |
-/** | |
- * @brief Type of a structure representing an I2C driver. | |
- */ | |
-typedef struct I2CDriver I2CDriver; | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+typedef struct I2CSlaveMsg I2CSlaveMsg; | |
+ | |
+/* | |
+ returns the current I2C slave message receive configuration | |
+*/ | |
+I2CSlaveMsg *i2cSlaveGetReceiveMsg(I2CDriver *i2cp); | |
+ | |
+ | |
+/* | |
+ returns the current I2C slave message reply configuration | |
+*/ | |
+I2CSlaveMsg *i2cSlaveGetReplyMsg(I2CDriver *i2cp); | |
+ | |
+ | |
+/* | |
+ I2C Slave Message Call Back. | |
+ Invoked from interrupt context just after | |
+ the last byte of the message is transferred or slaveAdr is matched. | |
+ | |
+ Use i2cSlaveReceiveMsg() or i2cSlaveReplyMsg() to access | |
+ the relevant message handling configuration | |
+*/ | |
+typedef void I2CSlaveMsgCB(I2CDriver *i2cp); | |
+ | |
+ | |
+/* | |
+ I2CSlaveMsg message handling configurations are normally | |
+ stored in read-only memory. | |
+ They describe either a buffer to contain incoming messages from | |
+ a bus master and associated callback functions, or one | |
+ preloaded with an outgoing reply to a read request and its callbacks. | |
+*/ | |
+ | |
+struct I2CSlaveMsg { | |
+ size_t size; /* sizeof(body) -- zero if master must wait */ | |
+ uint8_t *body; /* message contents -- or NULL if master must wait */ | |
+ I2CSlaveMsgCB *adrMatched; /* invoked when slave address matches */ | |
+ I2CSlaveMsgCB *processMsg; /* invoked after message is transferred */ | |
+ I2CSlaveMsgCB *exception; /* invoked if error or timeout during transfer */ | |
+}; | |
+ | |
+ | |
+I2CSlaveMsgCB I2CSlaveDummyCB; | |
+/* | |
+ dummy callback -- placeholder to ignore event | |
+*/ | |
+ | |
+ /* lock bus on receive or reply -- force master to wait */ | |
+extern const I2CSlaveMsg I2CSlaveLockOnMsg; | |
+ | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
/** | |
- * @brief Structure representing an I2C driver. | |
+ * @brief Structure representing an I2C driver. | |
*/ | |
struct I2CDriver { | |
/** | |
@@ -419,10 +500,14 @@ struct I2CDriver { | |
*/ | |
i2cflags_t errors; | |
#if I2C_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__) | |
+#if CH_CFG_USE_MUTEXES || defined(__DOXYGEN__) | |
/** | |
* @brief Mutex protecting the bus. | |
*/ | |
- mutex_t mutex; | |
+ mutex_t mutex; | |
+#elif CH_CFG_USE_SEMAPHORES | |
+ semaphore_t semaphore; | |
+#endif | |
#endif /* I2C_USE_MUTUAL_EXCLUSION */ | |
#if defined(I2C_DRIVER_EXT_FIELDS) | |
I2C_DRIVER_EXT_FIELDS | |
@@ -436,6 +521,22 @@ struct I2CDriver { | |
* @brief Current slave address without R/W bit. | |
*/ | |
i2caddr_t addr; | |
+ /** | |
+ * @brief Master RX DMA buffer size. | |
+ */ | |
+ uint16_t masterRxbytes; | |
+ /** | |
+ * @brief Master TX DMA buffer size. | |
+ */ | |
+ uint16_t masterTxbytes; | |
+ /** | |
+ * @brief Master RX DMA buffer base. | |
+ */ | |
+ uint8_t *masterRxbuf; | |
+ /** | |
+ * @brief Master TX DMA buffer base. | |
+ */ | |
+ const uint8_t *masterTxbuf; | |
/** | |
* @brief RX DMA mode bit mask. | |
*/ | |
@@ -456,6 +557,72 @@ struct I2CDriver { | |
* @brief Pointer to the I2Cx registers block. | |
*/ | |
I2C_TypeDef *i2c; | |
+ | |
+ /** | |
+ * @brief low level I2C interface / protocol state | |
+ */ | |
+ enum i2cMode { | |
+ i2cIdle=1, /* awaiting address or inactive */ | |
+ i2cSlaveRxing, /* receiving message */ | |
+ i2cLockedRxing, /* stretching clock before receiving message */ | |
+ i2cSlaveReplying, /* replying to query */ | |
+ i2cLockedReplying, /* stretching clock before replying to query */ | |
+ | |
+ i2cIsMaster=0x11, /* sent start bit (mastering bus) */ | |
+ i2cMasterStarted, /* repeated start after write */ | |
+ i2cMasterSelecting, /* sending slave address */ | |
+ i2cMasterRxing, /* receiving reply from slave */ | |
+ i2cMasterTxing /* sending message to slave */ | |
+ } mode; | |
+ | |
+#if HAL_USE_I2C_LOCK || HAL_USE_I2C_SLAVE | |
+ /** | |
+ * @brief I2C transaction timer | |
+ */ | |
+ virtual_timer_t timer; | |
+#endif | |
+#if HAL_USE_I2C_LOCK | |
+ /** | |
+ * @brief I2C bus lock duration | |
+ */ | |
+ systime_t lockDuration; | |
+#endif | |
+#if HAL_USE_I2C_SLAVE | |
+ /* additional fields to support I2C slave transactions */ | |
+ | |
+ /** | |
+ * @brief slave address of message being processed | |
+ */ | |
+ i2caddr_t targetAdr; | |
+ /** | |
+ * @brief Error Mask for last slave message | |
+ */ | |
+ i2cflags_t slaveErrors; | |
+ /** | |
+ * @brief Length of most recently transferred slave message | |
+ */ | |
+ uint32_t slaveBytes; | |
+ /** | |
+ * @brief Maximum # of ticks slave may stretch the I2C clock | |
+ */ | |
+ systime_t slaveTimeout; | |
+ /** | |
+ * @brief Pointer to slave message reception handler | |
+ */ | |
+ const I2CSlaveMsg *slaveRx; | |
+ /** | |
+ * @brief Pointer to slave message Reply handler | |
+ */ | |
+ const I2CSlaveMsg *slaveReply; | |
+ /** | |
+ * @brief Pointer to handler for next slave received message | |
+ */ | |
+ const I2CSlaveMsg *slaveNextRx; | |
+ /** | |
+ * @brief Pointer to handler for next slave reply message | |
+ */ | |
+ const I2CSlaveMsg *slaveNextReply; | |
+#endif | |
}; | |
/*===========================================================================*/ | |
@@ -471,6 +638,86 @@ struct I2CDriver { | |
*/ | |
#define i2c_lld_get_errors(i2cp) ((i2cp)->errors) | |
+ | |
+#if HAL_USE_I2C_LOCK | |
+/** | |
+ * @brief Unlock I2C bus after the end of the next transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ **/ | |
+#define i2c_lld_unlock(i2cp) (i2cp->lockDuration = TIME_IMMEDIATE) | |
+#endif | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+/** | |
+ * @brief Get slave errors from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveErrors(i2cp) ((i2cp)->slaveErrors) | |
+ | |
+/** | |
+ * @brief Get slave message bytes transferred from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveBytes(i2cp) ((i2cp)->slaveBytes) | |
+ | |
+ | |
+/** | |
+ * @brief Get slave timeout in ticks from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveTimeout(i2cp) ((i2cp)->slaveTimeout) | |
+ | |
+/** | |
+ * @brief Set slave timeout in ticks for I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_set_slaveTimeout(i2cp,ticks) ((i2cp)->slaveTimeout=(ticks)) | |
+ | |
+/** | |
+ * @brief Get slave target address from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveTargetAdr(i2cp) ((i2cp)->targetAdr) | |
+ | |
+/** | |
+ * @brief Get slave receive message descriptor from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveReceive(i2cp) ((i2cp)->slaveNextRx) | |
+ | |
+/** | |
+ * @brief Get slave reply message descriptor from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveReply(i2cp) ((i2cp)->slaveNextReply) | |
+ | |
+#endif | |
+ | |
/*===========================================================================*/ | |
/* External declarations. */ | |
/*===========================================================================*/ | |
@@ -502,12 +749,29 @@ extern "C" { | |
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout); | |
+ | |
+#if HAL_USE_I2C_LOCK /* I2C slave mode support */ | |
+ void i2c_lld_lock(I2CDriver *i2cp, systime_t lockDuration); | |
+#endif | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ msg_t i2c_lld_matchAddress(I2CDriver *i2cp, i2caddr_t i2cadr); | |
+ void i2c_lld_unmatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr); | |
+ void i2c_lld_unmatchAll(I2CDriver *i2cp); | |
+ void i2c_lld_slaveReceive(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg); | |
+ void i2c_lld_slaveReply(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg); | |
+#if HAL_USE_I2C_STARTFIX | |
+ void i2c_lld_startDetected(I2CDriver *i2cp); | |
+ void i2c_lld_noStartDetector(void); | |
+#define i2cNoStartDetector i2c_lld_noStartDetector | |
+#endif | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif /* HAL_USE_I2C */ | |
-#endif /* HAL_I2C_LLD_H */ | |
+#endif /* _I2C_LLD_H_ */ | |
/** @} */ | |
diff --git a/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.c b/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.c | |
index f2c9c95e9..cdad35e56 100644 | |
--- a/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.c | |
+++ b/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.c | |
@@ -1,5 +1,5 @@ | |
/* | |
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | |
+ ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
@@ -14,8 +14,35 @@ | |
limitations under the License. | |
*/ | |
+/* | |
+TODO: | |
+1. Make sure slave mode registers updated OK | |
+2. Test what of the newly inserted defines etc is actually necessary | |
+3. Check whether all relevant features available on all ports on all processors (they are on F7, F051) | |
+4. Do we need timer to get involved on master mode transactions? (May be multimaster requirement) | |
+5. What about multimaster? Possibly nothing special to do, other than support all transitions round master and slave modes. | |
+(Manual: "By default, it operates in slave mode. The interface automatically switches from slave to | |
+master when it generates a START condition, and from master to slave if an arbitration loss | |
+or a STOP generation occurs, allowing multimaster capability") | |
+6. Understand use of lock timer - may only be relevant for multimaster, or even unnecessary | |
+7. Check slave transfers > 255 bytes | |
+ | |
+NOTES: | |
+1. 10-bit addressing, masking options not currently supported in software in slave mode | |
+ (OAR1 hardware supports single 10-bit address matching; OAR2 supports 7-bit addressing with mask) | |
+2. Address zero supported by setting 'General Call' flag in CR1 in slave mode (set with call to match address zero) | |
+3. Clock stretching always enabled | |
+4. Relevant configurable bits in CR1: | |
+ ANFOFF (analog noise filter) | |
+ DNF (digital noise filter bits) | |
+5. Setting STM32_I2C_DEBUG_ENABLE to TRUE logs various events into a short circular buffer, which can then be examined using | |
+the debugger. Alternatively, a call to i2cPrintQ(BaseSequentialStream *chp) prints the buffer. Note that the buffer only | |
+has a write pointer, so once full its necessary to infer the start and end of the data | |
+ | |
+ */ | |
+ | |
/** | |
- * @file I2Cv2/hal_i2c_lld.c | |
+ * @file STM32/I2Cv2/i2c_lld.c | |
* @brief STM32 I2C subsystem low level driver source. | |
* | |
* @addtogroup I2C | |
@@ -80,7 +107,6 @@ | |
/*===========================================================================*/ | |
/* Driver constants. */ | |
/*===========================================================================*/ | |
- | |
#define I2C_ERROR_MASK \ | |
((uint32_t)(I2C_ISR_BERR | I2C_ISR_ARLO | I2C_ISR_OVR | I2C_ISR_PECERR | \ | |
I2C_ISR_TIMEOUT | I2C_ISR_ALERT)) | |
@@ -89,6 +115,12 @@ | |
((uint32_t)(I2C_ISR_TCR | I2C_ISR_TC | I2C_ISR_STOPF | I2C_ISR_NACKF | \ | |
I2C_ISR_ADDR | I2C_ISR_RXNE | I2C_ISR_TXIS)) | |
+/* Mask of interrupt bits cleared automatically - we mustn't clear ADDR else clock stretching doesn't happen */ | |
+#define I2C_INT_CLEAR_MASK \ | |
+ ((uint32_t)(I2C_ISR_TCR | I2C_ISR_TC | I2C_ISR_STOPF | I2C_ISR_NACKF | \ | |
+ I2C_ISR_RXNE | I2C_ISR_TXIS)) | |
+ | |
+ | |
/*===========================================================================*/ | |
/* Driver exported variables. */ | |
/*===========================================================================*/ | |
@@ -117,12 +149,98 @@ I2CDriver I2CD4; | |
/* Driver local variables and types. */ | |
/*===========================================================================*/ | |
+#if STM32_I2C_DEBUG_ENABLE | |
+/* | |
+ * Quick and dirty queue to record event interrupts (useful for debug) | |
+ * | |
+ * Assigned codes (may be others not noted here): | |
+ * 0x02 - NAK received | |
+ * 0x03 - transfer complete received | |
+ * 0x04 - Address match (records current mode before any error recovery etc) | |
+ * 0x05 - STOP received | |
+ * 0x06 - Part transfer complete interrupt | |
+ * 0x07 - Recovery from untidily finished previous transaction | |
+ * 0x08 - Recovery from untidily finished previous transaction | |
+ * 0x09 - Recovery from untidily finished previous transaction | |
+ * 0x10 - error in slave address match - send | |
+ * 0x11 - error in slave address match - receive | |
+ * 0x12 - address match - send | |
+ * 0x13 - address match - receive | |
+ * 0x14 - start slave receive operation | |
+ * 0x15 - lock on pending slave receive operation (no buffer) | |
+ * 0x16 - start slave transmit operation | |
+ * 0x17 - lock on pending slave transmit operation (no buffer available) | |
+ * 0x80 - new reply set | |
+ * 0x81 - reply used immediately | |
+ * 0x82 - new receive buffer set | |
+ * 0x83 - receive buffer used immediately | |
+ * 0xcc - clearing down after transmission - triggered by NAK received (usually valid) | |
+ * 0xd0 - Slave error being signalled | |
+ * 0xd1 - Slave error due to timeout | |
+*/ | |
+#define QEVENTS 32 | |
+ | |
+typedef struct i2cQ_t { | |
+ uint8_t code; | |
+ uint8_t state; // Channel state | |
+ uint8_t mode; // Mode (mostly for slave) | |
+ uint16_t param; // Parameter sometimes used | |
+} i2cQ_t; | |
+i2cQ_t i2cQ[QEVENTS]; | |
+unsigned i2cI = QEVENTS; | |
+#define qEvt(posn,info) {if (++i2cI >= QEVENTS) i2cI = 0; \ | |
+ i2cQ[i2cI].code=(posn); i2cQ[i2cI].state=(i2cp->state); i2cQ[i2cI].mode=(i2cp->mode); i2cQ[i2cI].param=(info); } | |
+ | |
+#include "chprintf.h" | |
+ | |
+void i2cPrintQ(BaseSequentialStream *chp) | |
+{ | |
+ uint8_t i; | |
+ if (QEVENTS == 0) | |
+ { | |
+ chprintf(chp, "I2C Debug queue disabled\r\n"); | |
+ } | |
+ else | |
+ { | |
+ chprintf(chp, "I2C debug ring - write pointer currently %02d\r\n", i2cI); | |
+ } | |
+ for (i = 0; i < QEVENTS; i++) | |
+ { | |
+ chprintf(chp, "%02d: %02x->%02x %02x %04x\r\n", i, i2cQ[i].code, i2cQ[i].state, i2cQ[i].mode, i2cQ[i].param); | |
+ if (i2cQ[i].code == 0) break; // Handle partially filled queue | |
+ } | |
+} | |
+ | |
+#else | |
+#define qEvt(posn,info) | |
+#endif | |
+ | |
+ | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+ | |
+void I2CSlaveDummyCB(I2CDriver *i2cp) | |
+/* | |
+ dummy callback -- placeholder to ignore event | |
+*/ | |
+{(void)i2cp;} | |
+ | |
+ /* lock bus on receive or reply message */ | |
+const I2CSlaveMsg I2CSlaveLockOnMsg = { | |
+ 0, NULL, I2CSlaveDummyCB, I2CSlaveDummyCB, I2CSlaveDummyCB | |
+}; | |
+ | |
+#endif | |
+ | |
/*===========================================================================*/ | |
-/* Driver local functions. */ | |
+/* Driver local functions. */ | |
/*===========================================================================*/ | |
+#if HAL_USE_I2C_MASTER | |
/** | |
- * @brief Slave address setup. | |
+ * @brief Slave (remote) address setup in master mode. | |
* @note The RW bit is set to zero internally. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
@@ -139,9 +257,13 @@ static void i2c_lld_set_address(I2CDriver *i2cp, i2caddr_t addr) { | |
else | |
dp->CR2 = (uint32_t)addr; | |
} | |
+#endif | |
+ | |
+ | |
/** | |
* @brief I2C RX transfer setup. | |
+ * @note Configures transfer count and related flags | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* | |
@@ -152,8 +274,19 @@ static void i2c_lld_setup_rx_transfer(I2CDriver *i2cp) { | |
uint32_t reload; | |
size_t n; | |
- /* The unit can transfer 255 bytes maximum in a single operation.*/ | |
- n = i2c_lld_get_rxbytes(i2cp); | |
+ /* The unit can transfer 255 bytes maximum in a single operation (device constraint). */ | |
+#if STM32_I2C_USE_DMA | |
+ if (i2cp->config->rxchar_cb) | |
+ { | |
+ n = i2cp->rxbytes; // Always interrupt-driven if we have a receive callback | |
+ } | |
+ else | |
+ { | |
+ n = i2c_lld_get_rxbytes(i2cp); // Otherwise get length from DMA or counter as appropriate | |
+ } | |
+#else | |
+ n = i2cp->rxbytes; | |
+#endif | |
if (n > 255U) { | |
n = 255U; | |
reload = I2C_CR2_RELOAD; | |
@@ -163,13 +296,16 @@ static void i2c_lld_setup_rx_transfer(I2CDriver *i2cp) { | |
} | |
/* Configures the CR2 registers with both the calculated and static | |
- settings.*/ | |
- dp->CR2 = (dp->CR2 & ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD)) | i2cp->config->cr2 | | |
- I2C_CR2_RD_WRN | (n << 16U) | reload; | |
+ settings. (Nothing much relevant in static settings - just PEC for SMBUS? */ | |
+ dp->CR2 = (dp->CR2 & ~(I2C_CR2_NBYTES | I2C_CR2_RELOAD)) | i2cp->config->cr2 | I2C_CR2_RD_WRN | | |
+ (n << 16U) | reload; | |
} | |
+ | |
+ | |
/** | |
* @brief I2C TX transfer setup. | |
+ * @note Configures transfer count and related flags | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* | |
@@ -180,8 +316,8 @@ static void i2c_lld_setup_tx_transfer(I2CDriver *i2cp) { | |
uint32_t reload; | |
size_t n; | |
- /* The unit can transfer 255 bytes maximum in a single operation.*/ | |
- n = i2c_lld_get_txbytes(i2cp); | |
+ /* The unit can transfer 255 bytes maximum in a single operation. */ | |
+ n = i2c_lld_get_txbytes(i2cp); // Get transaction size from DMA or buffer as configured | |
if (n > 255U) { | |
n = 255U; | |
reload = I2C_CR2_RELOAD; | |
@@ -196,6 +332,8 @@ static void i2c_lld_setup_tx_transfer(I2CDriver *i2cp) { | |
(n << 16U) | reload; | |
} | |
+ | |
+ | |
/** | |
* @brief Aborts an I2C transaction. | |
* | |
@@ -206,6 +344,7 @@ static void i2c_lld_setup_tx_transfer(I2CDriver *i2cp) { | |
static void i2c_lld_abort_operation(I2CDriver *i2cp) { | |
I2C_TypeDef *dp = i2cp->i2c; | |
+// Note: clearing PE doesn't affect configuration bits (including slave addresses) | |
if (dp->CR1 & I2C_CR1_PE) { | |
/* Stops the I2C peripheral.*/ | |
dp->CR1 &= ~I2C_CR1_PE; | |
@@ -219,154 +358,644 @@ static void i2c_lld_abort_operation(I2CDriver *i2cp) { | |
dmaStreamDisable(i2cp->dmatx); | |
dmaStreamDisable(i2cp->dmarx); | |
#else | |
- dp->CR1 &= ~(I2C_CR1_TXIE | I2C_CR1_RXIE); | |
+ dp->CR1 &= ~(I2C_CR1_TXIE | I2C_CR1_RXIE); // Stop byte-orientated interrupts | |
#endif | |
} | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE || HAL_USE_I2C_LOCK | |
/** | |
- * @brief I2C shared ISR code. | |
+ * @brief stop transaction timeout countdown | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
- * @param[in] isr content of the ISR register to be decoded | |
* | |
* @notapi | |
*/ | |
-static void i2c_lld_serve_interrupt(I2CDriver *i2cp, uint32_t isr) { | |
- I2C_TypeDef *dp = i2cp->i2c; | |
+static inline void stopTimer(I2CDriver *i2cp) | |
+{ | |
+ osalSysLockFromISR(); | |
+ chVTResetI(&i2cp->timer); | |
+ osalSysUnlockFromISR(); | |
+} | |
+#else | |
+#define stopTimer(ignored) {} | |
+#endif | |
+ | |
+ | |
- /* Special case of a received NACK, the transfer is aborted.*/ | |
- if ((isr & I2C_ISR_NACKF) != 0U) { | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+/** | |
+ * @brief report error via slave exception callback | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @note moves back to the idle mode | |
+ * @notapi | |
+ */ | |
+static inline void reportSlaveError(I2CDriver *i2cp) { | |
+ if (i2cp->mode >= i2cIsMaster) return; | |
+ qEvt(0xd0, i2cp->slaveErrors); | |
+ const I2CSlaveMsg *xfer = i2cp->mode >= i2cSlaveReplying ? | |
+ i2cp->slaveReply : i2cp->slaveRx; | |
+ if (xfer->exception) | |
+ xfer->exception(i2cp); | |
+ i2cp->mode = i2cIdle; | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Handling of stalled slave mode I2C transactions - timer handler. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static void slaveTimeExpired(void *i2cv) { | |
+ I2CDriver *i2cp = i2cv; | |
+ | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ i2c_lld_abort_operation(i2cp); | |
+ reportSlaveError(i2cp); | |
+ qEvt(0xd1, 0); | |
+ } | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief start or restart slave mode transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] targetAdr slave address which was matched | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void i2cStartSlaveAction(I2CDriver *i2cp, i2caddr_t targetAdr) | |
+{ | |
+ stopTimer(i2cp); | |
+ i2cp->targetAdr = targetAdr; | |
+ i2cp->slaveBytes = 0; | |
+ i2cp->slaveErrors = 0; | |
+ if (i2cp->slaveTimeout != TIME_INFINITE) | |
+ { | |
+ osalSysLockFromISR(); | |
+ chVTSetI(&i2cp->timer, i2cp->slaveTimeout, slaveTimeExpired, i2cp); | |
+ osalSysUnlockFromISR(); | |
+ } | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief end slave receive message DMA | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void i2cEndSlaveRxDMA(I2CDriver *i2cp) | |
+{ | |
+ size_t bytesRemaining = i2c_lld_get_rxbytes(i2cp); | |
+ if (i2cp->slaveBytes) | |
+ i2cp->slaveBytes += 0xffff - bytesRemaining; | |
+ else | |
+ i2cp->slaveBytes = i2cp->slaveRx->size - bytesRemaining; | |
+#if STM32_I2C_USE_DMA == TRUE | |
+ /* Disabling RX DMA channel.*/ | |
+ dmaStreamDisable(i2cp->dmarx); | |
+#else | |
+ i2cp->i2c->CR1 &= ~(I2C_CR1_RXIE); | |
+#endif | |
+} | |
+ | |
+ | |
+ | |
+/** | |
+ * @brief end slave transmit DMA | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] bytesRemaining bytes lost in output queue | |
+ * | |
+ * @notapi | |
+ */ | |
+static inline void i2cEndSlaveTxDMA(I2CDriver *i2cp, size_t bytesRemaining) | |
+{ | |
+ bytesRemaining += i2c_lld_get_rxbytes(i2cp); | |
+ if (i2cp->slaveBytes) | |
+ i2cp->slaveBytes += 0xffff - bytesRemaining; | |
+ else | |
+ i2cp->slaveBytes = i2cp->slaveReply->size - bytesRemaining; | |
#if STM32_I2C_USE_DMA == TRUE | |
- /* Stops the associated DMA streams.*/ | |
- dmaStreamDisable(i2cp->dmatx); | |
- dmaStreamDisable(i2cp->dmarx); | |
+ /* Disabling TX DMA channel.*/ | |
+ dmaStreamDisable(i2cp->dmatx); | |
+#else | |
+ i2cp->i2c->CR1 &= ~(I2C_CR1_TXIE); | |
+#endif | |
+} | |
+ | |
#endif | |
+ | |
+ | |
+ | |
+/** | |
+ * @brief Start a receive transaction (by enabling DMA, or enabling Rx character interrupts) | |
+ * | |
+ * @note All registers etc must be set up first | |
+ */ | |
+static inline void i2cStartReceive(I2CDriver *i2cp) | |
+{ | |
+#if STM32_I2C_USE_DMA == TRUE | |
+ if (i2cp->config->rxchar_cb) | |
+ { | |
+ // Callback in use - use interrupt-driven transfer | |
+ i2cp->i2c->CR1 |= I2C_CR1_TCIE | I2C_CR1_RXIE; | |
+ } | |
+ else | |
+ { | |
+ /* Enabling RX DMA.*/ | |
+ dmaStreamEnable(i2cp->dmarx); | |
+ | |
+ /* Transfer complete interrupt enabled.*/ | |
+ i2cp->i2c->CR1 |= I2C_CR1_TCIE; | |
+ } | |
+#else | |
+ | |
+ /* Transfer complete and RX interrupts enabled.*/ | |
+ i2cp->i2c->CR1 |= I2C_CR1_TCIE | I2C_CR1_RXIE; | |
+#endif | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Disable transmit data transfer, whether DMA or interrupt-driven | |
+ */ | |
+static inline void i2cDisableTransmitOperation(I2CDriver *i2cp) | |
+{ | |
+#if STM32_I2C_USE_DMA == TRUE | |
+ /* Disabling TX DMA channel.*/ | |
+ dmaStreamDisable(i2cp->dmatx); | |
+#else | |
+ i2cp->i2c->CR1 &= ~(I2C_CR1_TXIE); | |
+#endif | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Disable receive data transfer, whether DMA or interrupt-driven | |
+ */ | |
+static inline void i2cDisableReceiveOperation(I2CDriver *i2cp) | |
+{ | |
+#if STM32_I2C_USE_DMA == TRUE | |
+ /* Disabling RX DMA channel.*/ | |
+ dmaStreamDisable(i2cp->dmarx); | |
+#else | |
+ i2cp->i2c->CR1 &= ~(I2C_CR1_RXIE); | |
+#endif | |
+} | |
+ | |
+ | |
+ | |
+/*===========================================================================*/ | |
+/* Shared ISR Code */ | |
+/*===========================================================================*/ | |
+ | |
+/** | |
+ * @brief I2C shared ISR code - 'Normal' (non-error) interrupts. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] isr content of the ISR register to be decoded (no masking applied) | |
+ * | |
+ * @notapi | |
+ */ | |
+static void i2c_lld_serve_interrupt(I2CDriver *i2cp, uint32_t isr) { | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ | |
+ /* Check for received NACK, the transfer is aborted. Do this in all modes */ | |
+ if ((isr & I2C_ISR_NACKF) != 0U) | |
+ { | |
+ qEvt(2, 0); | |
+ | |
+ i2cDisableReceiveOperation(i2cp); | |
+ i2cDisableTransmitOperation(i2cp); | |
+ #if HAL_USE_I2C_SLAVE | |
+ /* NACK of last byte transmitted in slave response is NORMAL -- not an error! */ | |
+ if (i2cp->mode == i2cSlaveReplying) | |
+ { | |
+ qEvt(0xcc,0); | |
+ i2cEndSlaveTxDMA(i2cp, 1); | |
+ if (i2cp->slaveReply->processMsg) | |
+ i2cp->slaveReply->processMsg(i2cp); | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+ stopTimer(i2cp); | |
+ return; | |
+ } | |
+ #endif | |
+ | |
/* Error flag.*/ | |
i2cp->errors |= I2C_ACK_FAILURE; | |
- /* Transaction finished sending the STOP.*/ | |
+ /* Transaction finished, send the STOP. */ | |
dp->CR2 |= I2C_CR2_STOP; | |
/* Make sure no more interrupts.*/ | |
- dp->CR1 &= ~(I2C_CR1_TCIE | I2C_CR1_TXIE | I2C_CR1_RXIE); | |
+ dp->CR1 &= ~(I2C_CR1_TCIE | I2C_CR1_TXIE | I2C_CR1_RXIE | I2C_CR1_STOPIE); | |
- /* Errors are signaled to the upper layer.*/ | |
- _i2c_wakeup_error_isr(i2cp); | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ else | |
+ { | |
+ /* Master mode - Errors are signalled to the upper layer.*/ | |
+ i2cp->mode = i2cIdle; | |
+ // Must only wake up thread in master mode. | |
+ _i2c_wakeup_isr(i2cp); // This is a normal completion | |
+ } | |
return; | |
} | |
+ | |
#if STM32_I2C_USE_DMA == FALSE | |
- /* Handling of data transfer if the DMA mode is disabled.*/ | |
+ /* Handling of data transfer if the DMA mode is disabled - done character by character. Mode should be irrelevant */ | |
{ | |
uint32_t cr1 = dp->CR1; | |
- if (i2cp->state == I2C_ACTIVE_TX) { | |
+ if ((i2cp->state == I2C_ACTIVE_TX) || (i2cp->mode == i2cSlaveReplying)) | |
+ { | |
/* Transmission phase.*/ | |
if (((cr1 &I2C_CR1_TXIE) != 0U) && ((isr & I2C_ISR_TXIS) != 0U)) { | |
dp->TXDR = (uint32_t)*i2cp->txptr; | |
i2cp->txptr++; | |
i2cp->txbytes--; | |
if (i2cp->txbytes == 0U) { | |
- dp->CR1 &= ~I2C_CR1_TXIE; | |
+ dp->CR1 &= ~I2C_CR1_TXIE; // Last byte sent - stop Tx interrupt | |
} | |
} | |
} | |
- else { | |
+ else | |
+ { | |
/* Receive phase.*/ | |
if (((cr1 & I2C_CR1_RXIE) != 0U) && ((isr & I2C_ISR_RXNE) != 0U)) { | |
- *i2cp->rxptr = (uint8_t)dp->RXDR; | |
+ uint8_t c; | |
+ *i2cp->rxptr = c = (uint8_t)dp->RXDR; | |
i2cp->rxptr++; | |
i2cp->rxbytes--; | |
+ if (i2cp->config->rxchar_cb) | |
+ { | |
+ if (i2cp->config->rxchar_cb(i2cp, c) > 0) | |
+ { | |
+ /* Transaction finished, send the STOP. */ | |
+ dp->CR2 |= I2C_CR2_STOP; | |
+ | |
+ /* Make sure no more interrupts.*/ | |
+ dp->CR1 &= ~(I2C_CR1_TCIE | I2C_CR1_TXIE | I2C_CR1_RXIE | I2C_CR1_STOPIE); | |
+ | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ else | |
+ { | |
+ /* Master mode - Errors are signalled to the upper layer.*/ | |
+ i2cp->mode = i2cIdle; | |
+ // Must only wake up thread in master mode. | |
+ _i2c_wakeup_isr(i2cp); // This is a normal completion | |
+ } | |
+ | |
+ return; | |
+ } | |
+ } | |
if (i2cp->rxbytes == 0U) { | |
- dp->CR1 &= ~I2C_CR1_RXIE; | |
+ dp->CR1 &= ~I2C_CR1_RXIE; // Buffer full - stop reception (TODO: Should we send NAK?? Only possible in slave mode) | |
} | |
} | |
} | |
} | |
+#else | |
+ /* Receive character phase with callback enabled. */ | |
+ if ((i2cp->state == I2C_ACTIVE_RX) || (i2cp->mode == i2cMasterRxing)) | |
+ { | |
+ uint32_t cr1 = dp->CR1; | |
+ if (((cr1 & I2C_CR1_RXIE) != 0U) && ((isr & I2C_ISR_RXNE) != 0U)) { | |
+ uint8_t c; | |
+ c = (uint8_t)dp->RXDR; | |
+ *i2cp->rxptr = c; | |
+ i2cp->rxptr++; | |
+ i2cp->rxbytes--; | |
+ if (i2cp->config->rxchar_cb) | |
+ { | |
+ if (i2cp->config->rxchar_cb(i2cp, c) > 0) | |
+ { | |
+ /* Transaction finished, send the STOP. */ | |
+ dp->CR2 |= I2C_CR2_STOP; | |
+ | |
+ /* Make sure no more interrupts.*/ | |
+ dp->CR1 &= ~(I2C_CR1_TCIE | I2C_CR1_TXIE | I2C_CR1_RXIE | I2C_CR1_STOPIE); | |
+ | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ else | |
+ { | |
+ /* Master mode - Errors are signalled to the upper layer.*/ | |
+ i2cp->mode = i2cIdle; | |
+ // Must only wake up thread in master mode. | |
+ _i2c_wakeup_isr(i2cp); // This is a normal completion | |
+ } | |
+ | |
+ return; | |
+ } | |
+ } | |
+ if (i2cp->rxbytes == 0U) { | |
+ dp->CR1 &= ~I2C_CR1_RXIE; // Buffer full - stop reception (TODO: Should we send NAK?? Only possible in slave mode) | |
+ } | |
+ } | |
+ } | |
#endif | |
- /* Partial transfer handling, restarting the transfer and returning.*/ | |
- if ((isr & I2C_ISR_TCR) != 0U) { | |
- if (i2cp->state == I2C_ACTIVE_TX) { | |
+ | |
+ | |
+ /* Partial transfer handling, restarting the transfer and returning. */ | |
+ if ((isr & I2C_ISR_TCR) != 0U) | |
+ { | |
+ qEvt(0x06, 0); | |
+ if ((i2cp->state == I2C_ACTIVE_TX) || (i2cp->mode == i2cSlaveReplying)) { | |
i2c_lld_setup_tx_transfer(i2cp); | |
} | |
- else { | |
+ if ((i2cp->state == I2C_ACTIVE_RX) || (i2cp->mode == i2cSlaveRxing)) { | |
i2c_lld_setup_rx_transfer(i2cp); | |
} | |
return; | |
} | |
- /* The following condition is true if a transfer phase has been completed.*/ | |
- if ((isr & I2C_ISR_TC) != 0U) { | |
- if (i2cp->state == I2C_ACTIVE_TX) { | |
- /* End of the transmit phase.*/ | |
- | |
-#if STM32_I2C_USE_DMA == TRUE | |
- /* Disabling TX DMA channel.*/ | |
- dmaStreamDisable(i2cp->dmatx); | |
-#endif | |
- /* Starting receive phase if necessary.*/ | |
- if (i2c_lld_get_rxbytes(i2cp) > 0U) { | |
- /* Setting up the peripheral.*/ | |
- i2c_lld_setup_rx_transfer(i2cp); | |
-#if STM32_I2C_USE_DMA == TRUE | |
- /* Enabling RX DMA.*/ | |
- dmaStreamEnable(i2cp->dmarx); | |
-#else | |
- /* RX interrupt enabled.*/ | |
- dp->CR1 |= I2C_CR1_RXIE; | |
+ /* The following condition is true if a transfer phase has been completed. */ | |
+ if ((isr & I2C_ISR_TC) != 0U) | |
+ { | |
+ qEvt(3,i2cp->state); | |
+#if HAL_USE_I2C_SLAVE | |
+ switch (i2cp->mode) | |
+ { | |
+ case i2cLockedRxing : /* stretching clock before receiving message - Rx buffer might be full */ | |
+ case i2cLockedReplying : | |
+ break; // TODO: Two unsupported cases to consider - maybe they can't happen | |
+ case i2cSlaveReplying : // Must have just finished sending a reply in slave mode | |
+ break; // Just go on to send STOP bit and tidy up | |
+ | |
+ case i2cSlaveRxing : // Must have just received a message - process if we can | |
+ i2cEndSlaveRxDMA(i2cp); | |
+ qEvt(0x20, 0); | |
+ if (i2cp->slaveRx->processMsg) | |
+ i2cp->slaveRx->processMsg(i2cp); | |
+ // TODO: Should get a reply message set - if so, start to send it and return | |
+ return; // For now, see what happens if we just return | |
+ break; | |
+ default : // Assume a master mode | |
#endif | |
- | |
- /* Starts the read operation.*/ | |
- dp->CR2 |= I2C_CR2_START; | |
- | |
- /* State change.*/ | |
- i2cp->state = I2C_ACTIVE_RX; | |
- | |
- /* Note, returning because the transaction is not over yet.*/ | |
- return; | |
+ if (i2cp->state == I2C_ACTIVE_TX) { | |
+ /* End of the transmit phase.*/ | |
+ | |
+ i2cDisableTransmitOperation(i2cp); // Disable | |
+ | |
+ /* Starting receive phase if necessary.*/ | |
+ if (i2c_lld_get_rxbytes(i2cp) > 0U) { | |
+ /* Setting up the peripheral.*/ | |
+ i2c_lld_setup_rx_transfer(i2cp); | |
+ | |
+ #if STM32_I2C_USE_DMA == TRUE | |
+ // If receive callback enabled, always transfer using interrupts | |
+ if (i2cp->config->rxchar_cb) | |
+ { | |
+ /* RX interrupt enabled.*/ | |
+ dp->CR1 |= I2C_CR1_RXIE; | |
+ } | |
+ else | |
+ { | |
+ /* Enabling RX DMA.*/ | |
+ dmaStreamEnable(i2cp->dmarx); | |
+ } | |
+ #else | |
+ /* RX interrupt enabled.*/ | |
+ dp->CR1 |= I2C_CR1_RXIE; | |
+ #endif | |
+ | |
+ /* Starts the read operation.*/ | |
+ dp->CR2 |= I2C_CR2_START; | |
+ | |
+ /* State change.*/ | |
+ i2cp->state = I2C_ACTIVE_RX; | |
+ i2cp->mode = i2cMasterRxing; | |
+ | |
+ /* Note, returning because the transaction is not over yet.*/ | |
+ return; | |
+ } | |
} | |
+ else | |
+ { | |
+ /* End of the receive phase.*/ | |
+ i2cDisableReceiveOperation(i2cp); | |
+ } | |
+ | |
+#if HAL_USE_I2C_SLAVE | |
} | |
- else { | |
- /* End of the receive phase.*/ | |
-#if STM32_I2C_USE_DMA == TRUE | |
- /* Disabling RX DMA channel.*/ | |
- dmaStreamDisable(i2cp->dmarx); | |
#endif | |
- } | |
- /* Transaction finished sending the STOP.*/ | |
+ /* Transaction finished sending the STOP. */ | |
dp->CR2 |= I2C_CR2_STOP; | |
- /* Make sure no more 'Transfer Complete' interrupts.*/ | |
+ /* Make sure no more 'Transfer Complete' interrupts. */ | |
dp->CR1 &= ~I2C_CR1_TCIE; | |
/* Normal transaction end.*/ | |
- _i2c_wakeup_isr(i2cp); | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ // Slave mode - just move to idle state | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ else | |
+ { | |
+ i2cp->mode = i2cIdle; | |
+ // Must only wake up thread in master mode | |
+ _i2c_wakeup_isr(i2cp); | |
+ } | |
} | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE | |
+ uint8_t abort = 0; | |
+ /* Check for address match - if so, we're slave */ | |
+ if ((isr & I2C_ISR_ADDR) != 0U) | |
+ { | |
+ i2caddr_t targetAdr = (isr & I2C_ISR_ADDCODE) >> 17; // Identify which slave address used | |
+ uint8_t xferDir = (isr & I2C_ISR_DIR) ? 0 : 1; // Status bit is 0 for receive, 1 for transmit. xferDir inverts sense | |
+ dp->CR1 |= I2C_CR1_STOPIE; // Enable STOP interrupt so we know when slave transaction done | |
+ | |
+ /* First, tidy up from previous transactions as necessary */ | |
+ qEvt(0x04, 0); | |
+ switch (i2cp->mode) { | |
+ case i2cIdle: | |
+ break; | |
+ case i2cSlaveRxing: /* Previous transaction not completed properly, or | |
+ maybe we were sent a message without being asked for a reply */ | |
+ qEvt(0x07, 0); | |
+ i2cEndSlaveRxDMA(i2cp); | |
+ if (i2cp->slaveRx->processMsg) | |
+ i2cp->slaveRx->processMsg(i2cp); // Execute callback if defined | |
+ break; | |
+ case i2cSlaveReplying: /* Master did not NACK last transmitted byte (most likely picked up by NAK handler) */ | |
+ qEvt(0x08, 0); | |
+ if (!xferDir) | |
+ { | |
+ qEvt(0x09, 0); | |
+ i2cEndSlaveTxDMA(i2cp, 2); | |
+ if (i2cp->slaveReply->processMsg) | |
+ i2cp->slaveReply->processMsg(i2cp); | |
+ break; | |
+ } | |
+ // Intentionally don't break if we're addressed to receive | |
+ default: | |
+ // todo: Does this lot happen in the right order? Is an abort appropriate? Or should we just continue? | |
+ qEvt(0x10 + xferDir, 0); | |
+ i2cp->slaveErrors = I2C_UNKNOWN_ERROR + i2cp->mode; | |
+ i2c_lld_abort_operation(i2cp); /* reset and reinit */ | |
+ stopTimer(i2cp); | |
+ reportSlaveError(i2cp); // This clears down to idle | |
+ abort = 1; | |
+ } | |
+ if (!abort) | |
+ { | |
+ qEvt(0x12 + xferDir, (i2cp->slaveNextRx->size)); | |
+ if (xferDir) | |
+ { | |
+ /* Start Receive */ | |
+ /* If receive buffer still full, need to extend clock for timeout. Otherwise set up to receive */ | |
+ dp->CR1 &= ~I2C_CR1_SBC; // Not needed with receive | |
+ i2cStartSlaveAction(i2cp, targetAdr); | |
+ | |
+ const I2CSlaveMsg *rx = i2cp->slaveNextRx; | |
+ if (rx->adrMatched) // Q: Can rx ever be NULL? | |
+ rx->adrMatched(i2cp); // Execute callback on address match if specified | |
+ rx = i2cp->slaveRx = i2cp->slaveNextRx; // Reload message pointer in case callback changed it | |
+ if (rx->body && rx->size) | |
+ { | |
+ /* Receive buffer available - can receive immediately. Set up slave RX DMA */ | |
+ i2c_lld_setup_rx_transfer(i2cp); | |
+ #if STM32_I2C_USE_DMA == TRUE | |
+ /* RX DMA setup.*/ | |
+ dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmarx, rx->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmarx, rx->size); | |
+ #else | |
+ i2cp->rxptr = rx->body; | |
+ i2cp->rxbytes = rx->size; | |
+ #endif | |
+ | |
+ i2cStartReceive(i2cp); | |
+ dp->ICR = I2C_ISR_ADDR; // We can release the clock stretch now | |
+ i2cp->mode = i2cSlaveRxing; | |
+ qEvt(0x14,0); | |
+ } | |
+ else | |
+ { | |
+ /* No reply set up - hold clock low and wait (happens automatically) */ | |
+ qEvt(0x15,0); | |
+ i2cp->mode = i2cLockedRxing; | |
+ } | |
+ } | |
+ else | |
+ { | |
+ /* Start Transmit */ | |
+ i2cStartSlaveAction(i2cp, targetAdr); | |
+ const I2CSlaveMsg *reply = i2cp->slaveNextReply; | |
+ const I2CSlaveMsg *rx = i2cp->slaveNextRx; // Receive control block | |
+ if (rx->adrMatched) // Q: Can rx ever be NULL? | |
+ rx->adrMatched(i2cp); // Execute callback on address match if specified | |
+ reply = i2cp->slaveReply = i2cp->slaveNextReply; // Reload message pointer in case callback changed it | |
+ if (reply->body && reply->size) | |
+ { | |
+ /* Reply message available - can send immediately. Set up slave TX DMA */ | |
+ #if STM32_I2C_USE_DMA == TRUE | |
+ dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmatx, reply->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmatx, reply->size); | |
+ /* Start transmission */ | |
+ i2c_lld_setup_tx_transfer(i2cp); | |
+ | |
+ /* Enabling TX DMA.*/ | |
+ dmaStreamEnable(i2cp->dmatx); | |
+ | |
+ /* Transfer complete interrupt enabled.*/ | |
+ dp->CR1 |= I2C_CR1_TCIE; | |
+ #else | |
+ /* Start transmission */ | |
+ i2cp->txptr = reply->body; | |
+ i2cp->txbytes = reply->size; | |
+ i2c_lld_setup_tx_transfer(i2cp); | |
+ /* Transfer complete and TX character interrupts enabled.*/ | |
+ dp->CR1 |= I2C_CR1_TCIE | I2C_CR1_TXIE; | |
+ #endif | |
+ qEvt(0x16,0); | |
+ i2cp->mode = i2cSlaveReplying; | |
+ dp->CR1 |= I2C_CR1_SBC; // Need this to enable byte counter in transmit mode | |
+ dp->ICR = I2C_ISR_ADDR; // We can release the clock stretch now | |
+ } | |
+ else | |
+ { | |
+ // clock is automatically stretched if we don't clear the status bit | |
+ //dp->CR2 &= (uint16_t)(~I2C_CR2_ITEVTEN); | |
+ qEvt(0x17, 0); | |
+ i2cp->mode = i2cLockedReplying; | |
+ } | |
+ } | |
+ } /* if !abort */ | |
+ } | |
+ if ((isr & I2C_ISR_STOPF) != 0U) { | |
+ /* | |
+ * STOP received: | |
+ * in master mode, if generated by peripheral - can probably just ignore | |
+ * in slave mode, if detected on bus (from any source?) | |
+ * | |
+ * Clear everything down - particularly relevant in slave mode | |
+ */ | |
+ qEvt(0x05, 0); | |
+ dp->CR1 &= ~I2C_CR1_STOPIE; // Disable STOP interrupt | |
+ i2cDisableReceiveOperation(i2cp); // These two may not be necessary | |
+ i2cDisableTransmitOperation(i2cp); | |
+ stopTimer(i2cp); | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+#endif | |
} | |
+ | |
+ | |
/** | |
* @brief I2C error handler. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
- * @param[in] isr content of the ISR register to be decoded | |
+ * @param[in] isr content of the ISR register to be decoded (no masking applied) | |
* | |
* @notapi | |
*/ | |
static void i2c_lld_serve_error_interrupt(I2CDriver *i2cp, uint32_t isr) { | |
-#if STM32_I2C_USE_DMA == TRUE | |
- /* Clears DMA interrupt flags just to be safe.*/ | |
- dmaStreamDisable(i2cp->dmatx); | |
- dmaStreamDisable(i2cp->dmarx); | |
-#else | |
- /* Disabling RX and TX interrupts.*/ | |
- i2cp->i2c->CR1 &= ~(I2C_CR1_TXIE | I2C_CR1_RXIE); | |
+ i2cDisableReceiveOperation(i2cp); | |
+ i2cDisableTransmitOperation(i2cp); | |
+ stopTimer(i2cp); | |
+ | |
+#if HAL_USE_I2C_SLAVE | |
+ // In slave mode, just clock errors and return | |
+ if (i2cp->mode < i2cIsMaster) | |
+ { | |
+ reportSlaveError(i2cp); // This clears down to idle | |
+ return; | |
+ } | |
#endif | |
if (isr & I2C_ISR_BERR) | |
@@ -381,11 +1010,69 @@ static void i2c_lld_serve_error_interrupt(I2CDriver *i2cp, uint32_t isr) { | |
if (isr & I2C_ISR_TIMEOUT) | |
i2cp->errors |= I2C_TIMEOUT; | |
- /* If some error has been identified then sends wakes the waiting thread.*/ | |
+ /* If some error has been identified then wake up the waiting thread.*/ | |
if (i2cp->errors != I2C_NO_ERROR) | |
+ { | |
+ i2cp->mode = i2cIdle; | |
_i2c_wakeup_error_isr(i2cp); | |
+ } | |
} | |
+ | |
+ | |
+ | |
+ | |
+#if HAL_USE_I2C_LOCK /* I2C bus locking support */ | |
+ | |
+/** | |
+ * @brief Handling of expired master bus lock timer | |
+ * | |
+ * @param[in] i2cv pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+static void lockExpired(void *i2cv) { | |
+ I2CDriver *i2cp = i2cv; | |
+ | |
+ if (i2cp->mode == i2cIsMaster && !i2cp->thread) { /* between transactions */ | |
+ i2cp->i2c->CR2 |= I2C_CR2_STOP; | |
+ i2cp->mode = i2cIdle; | |
+ } | |
+ i2cp->lockDuration = TIME_IMMEDIATE; | |
+} | |
+ | |
+ | |
+ | |
+ | |
+/** | |
+ * @brief Lock I2C bus at the beginning of the next message | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] lockDuration max number of ticks to hold bus locked | |
+ * - @a TIME_INFINITE no timeout. | |
+ * - @a TIME_IMMEDIATE unlock the bus immediately | |
+ * . | |
+ * | |
+ * Lock I2C bus at the beginning of the next message sent | |
+ * for a maximum of lockDuration ticks. No other I2C masters will | |
+ * be allowed to interrupt until i2cUnlock() is called. | |
+ * | |
+ * @notapi | |
+ **/ | |
+void i2c_lld_lock(I2CDriver *i2cp, systime_t lockDuration) | |
+{ | |
+ i2cp->lockDuration = lockDuration; | |
+ if (i2cp->mode >= i2cIsMaster) { | |
+ stopTimer(i2cp); | |
+ if (lockDuration == TIME_IMMEDIATE) | |
+ lockExpired(i2cp); | |
+ else if (lockDuration != TIME_INFINITE) | |
+ chVTSetI(&i2cp->timer, lockDuration, lockExpired, i2cp); | |
+ } | |
+} | |
+ | |
+#endif | |
+ | |
/*===========================================================================*/ | |
/* Driver interrupt handlers. */ | |
/*===========================================================================*/ | |
@@ -403,7 +1090,7 @@ OSAL_IRQ_HANDLER(STM32_I2C1_GLOBAL_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD1.i2c->ICR = isr; | |
+ I2CD1.i2c->ICR = isr & ~I2C_ISR_ADDR; | |
if (isr & I2C_ERROR_MASK) | |
i2c_lld_serve_error_interrupt(&I2CD1, isr); | |
@@ -420,7 +1107,7 @@ OSAL_IRQ_HANDLER(STM32_I2C1_EVENT_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD1.i2c->ICR = isr & I2C_INT_MASK; | |
+ I2CD1.i2c->ICR = isr & I2C_INT_CLEAR_MASK; | |
i2c_lld_serve_interrupt(&I2CD1, isr); | |
@@ -458,7 +1145,7 @@ OSAL_IRQ_HANDLER(STM32_I2C2_GLOBAL_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD2.i2c->ICR = isr; | |
+ I2CD2.i2c->ICR = isr & ~I2C_ISR_ADDR; | |
if (isr & I2C_ERROR_MASK) | |
i2c_lld_serve_error_interrupt(&I2CD2, isr); | |
@@ -475,7 +1162,7 @@ OSAL_IRQ_HANDLER(STM32_I2C2_EVENT_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD2.i2c->ICR = isr & I2C_INT_MASK; | |
+ I2CD2.i2c->ICR = isr & I2C_INT_CLEAR_MASK; | |
i2c_lld_serve_interrupt(&I2CD2, isr); | |
@@ -513,7 +1200,7 @@ OSAL_IRQ_HANDLER(STM32_I2C3_GLOBAL_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD3.i2c->ICR = isr; | |
+ I2CD3.i2c->ICR = isr & ~I2C_ISR_ADDR; | |
if (isr & I2C_ERROR_MASK) | |
i2c_lld_serve_error_interrupt(&I2CD3, isr); | |
@@ -530,7 +1217,7 @@ OSAL_IRQ_HANDLER(STM32_I2C3_EVENT_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD3.i2c->ICR = isr & I2C_INT_MASK; | |
+ I2CD3.i2c->ICR = isr & I2C_INT_CLEAR_MASK; | |
i2c_lld_serve_interrupt(&I2CD3, isr); | |
@@ -568,7 +1255,7 @@ OSAL_IRQ_HANDLER(STM32_I2C4_GLOBAL_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD4.i2c->ICR = isr; | |
+ I2CD4.i2c->ICR = isr & ~I2C_ISR_ADDR; | |
if (isr & I2C_ERROR_MASK) | |
i2c_lld_serve_error_interrupt(&I2CD4, isr); | |
@@ -585,7 +1272,7 @@ OSAL_IRQ_HANDLER(STM32_I2C4_EVENT_HANDLER) { | |
OSAL_IRQ_PROLOGUE(); | |
/* Clearing IRQ bits.*/ | |
- I2CD4.i2c->ICR = isr & I2C_INT_MASK; | |
+ I2CD4.i2c->ICR = isr & I2C_INT_CLEAR_MASK; | |
i2c_lld_serve_interrupt(&I2CD4, isr); | |
@@ -672,18 +1359,19 @@ void i2c_lld_init(void) { | |
void i2c_lld_start(I2CDriver *i2cp) { | |
I2C_TypeDef *dp = i2cp->i2c; | |
+#if STM32_I2C_USE_DMA == TRUE | |
+ /* Common DMA modes.*/ | |
+ i2cp->txdmamode = DMAMODE_COMMON | STM32_DMA_CR_DIR_M2P; | |
+ i2cp->rxdmamode = DMAMODE_COMMON | STM32_DMA_CR_DIR_P2M; | |
+#endif | |
+ | |
/* Make sure I2C peripheral is disabled */ | |
dp->CR1 &= ~I2C_CR1_PE; | |
+ i2cp->mode = i2cStopped; | |
/* If in stopped state then enables the I2C and DMA clocks.*/ | |
if (i2cp->state == I2C_STOP) { | |
-#if STM32_I2C_USE_DMA == TRUE | |
- /* Common DMA modes.*/ | |
- i2cp->txdmamode = DMAMODE_COMMON | STM32_DMA_CR_DIR_M2P; | |
- i2cp->rxdmamode = DMAMODE_COMMON | STM32_DMA_CR_DIR_P2M; | |
-#endif | |
- | |
#if STM32_I2C_USE_I2C1 | |
if (&I2CD1 == i2cp) { | |
@@ -841,22 +1529,33 @@ void i2c_lld_start(I2CDriver *i2cp) { | |
/* I2C registers pointed by the DMA.*/ | |
dmaStreamSetPeripheral(i2cp->dmarx, &dp->RXDR); | |
dmaStreamSetPeripheral(i2cp->dmatx, &dp->TXDR); | |
+ /* Reset i2c peripheral, the TCIE bit will be handled separately. */ | |
+ // TODO: Mask out config bits which user mustn't fiddle with | |
+ dp->CR1 = (i2cp->config->cr1 | I2C_CR1_ERRIE | I2C_CR1_NACKIE | | |
+ I2C_CR1_TXDMAEN | I2C_CR1_RXDMAEN); | |
+#else | |
+ /* Reset i2c peripheral, the TCIE bit will be handled separately. No DMA interrupts */ | |
+ dp->CR1 = (i2cp->config->cr1 | I2C_CR1_ERRIE | I2C_CR1_NACKIE); | |
#endif | |
- /* Reset i2c peripheral, the TCIE bit will be handled separately.*/ | |
- dp->CR1 = i2cp->config->cr1 | | |
-#if STM32_I2C_USE_DMA == TRUE | |
- I2C_CR1_TXDMAEN | I2C_CR1_RXDMAEN | /* Enable only if using DMA */ | |
-#endif | |
- I2C_CR1_ERRIE | I2C_CR1_NACKIE; | |
- | |
/* Setup I2C parameters.*/ | |
dp->TIMINGR = i2cp->config->timingr; | |
/* Ready to go.*/ | |
- dp->CR1 |= I2C_CR1_PE; | |
+ i2cp->mode = i2cIdle; | |
+#if HAL_USE_I2C_LOCK | |
+ i2cp->lockDuration = TIME_IMMEDIATE; | |
+#endif | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ i2cp->slaveNextReply = i2cp->slaveNextRx = &I2CSlaveLockOnMsg; | |
+ i2cp->targetAdr = i2cInvalidAdr; | |
+ i2cp->slaveTimeout = TIME_INFINITE; | |
+#endif | |
+ | |
+ dp->CR1 |= I2C_CR1_PE; // Enable peripheral | |
} | |
+ | |
/** | |
* @brief Deactivates the I2C peripheral. | |
* | |
@@ -868,8 +1567,10 @@ void i2c_lld_stop(I2CDriver *i2cp) { | |
/* If not in stopped state then disables the I2C clock.*/ | |
if (i2cp->state != I2C_STOP) { | |
- | |
+ i2cp->mode = i2cStopped; | |
+ | |
/* I2C disable.*/ | |
+ stopTimer(i2cp); | |
i2c_lld_abort_operation(i2cp); | |
#if STM32_I2C_USE_DMA == TRUE | |
dmaStreamRelease(i2cp->dmatx); | |
@@ -938,8 +1639,79 @@ void i2c_lld_stop(I2CDriver *i2cp) { | |
} | |
} | |
+ | |
+ | |
+#if HAL_USE_I2C_MASTER == TRUE | |
+/** | |
+ * Utility routine brings out common code for master timeout | |
+ */ | |
+static msg_t calcMasterTimeout(I2CDriver *i2cp) | |
+{ | |
+ systime_t start, end; | |
+ | |
+ osalDbgAssert((i2cp->mode <= i2cIsMaster), "busy"); | |
+ | |
+ /* Calculating the time window for the timeout on the busy bus condition.*/ | |
+ start = osalOsGetSystemTimeX(); | |
+ end = start + OSAL_MS2ST(STM32_I2C_BUSY_TIMEOUT); | |
+ | |
+ /* Waits until BUSY flag is reset or, alternatively, for a timeout | |
+ condition.*/ | |
+ while (true) { | |
+ osalSysLock(); | |
+ | |
+ /* If the bus is not busy then the operation can continue, note, the | |
+ loop is exited in the locked state.*/ | |
+// if (!(i2cp->i2c->ISR & I2C_ISR_BUSY) && !(i2cp->i2c->CR1 & I2C_CR1_STOP)) | |
+ if (!(i2cp->i2c->ISR & I2C_ISR_BUSY)) | |
+ break; | |
+ | |
+ /* If the system time went outside the allowed window then a timeout | |
+ condition is returned.*/ | |
+ if (!osalOsIsTimeWithinX(osalOsGetSystemTimeX(), start, end)) | |
+ return MSG_TIMEOUT; | |
+ | |
+ osalSysUnlock(); | |
+ // TODO: Should we relinquish thread here for a while? | |
+ chThdSleepMilliseconds(2); | |
+ } | |
+ i2cp->mode = i2cIsMaster; // We can set master mode now | |
+ return MSG_OK; | |
+} | |
+ | |
+ | |
+/** | |
+ * Start a master mode transaction | |
+ * | |
+ * Note: may need adjustment to work in multi-master mode as well - see i2c_lld_safety_timeout() | |
+ */ | |
+static msg_t startMasterAction(I2CDriver *i2cp, systime_t timeout) | |
+{ | |
+#if STM32_I2C_USE_DMA == TRUE | |
+#else | |
+#endif | |
+ msg_t msg; | |
+ | |
+ /* Starts the operation.*/ | |
+ i2cp->i2c->CR2 |= I2C_CR2_START; | |
+ | |
+ /* Waits for the operation completion or a timeout.*/ | |
+ msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); | |
+ | |
+ /* In case of a software timeout a STOP is sent as an extreme attempt | |
+ to release the bus.*/ | |
+ if (msg == MSG_TIMEOUT) { | |
+ i2cp->i2c->CR2 |= I2C_CR2_STOP; | |
+ i2cp->mode = i2cIdle; // TODO: Is this enough? | |
+ } | |
+ return msg; | |
+} | |
+ | |
+ | |
/** | |
* @brief Receives data via the I2C bus as master. | |
+ * @details Number of receiving bytes must be more than 1 on STM32F1x. This is | |
+ * hardware restriction. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* @param[in] addr slave device address | |
@@ -962,9 +1734,9 @@ void i2c_lld_stop(I2CDriver *i2cp) { | |
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout) { | |
- msg_t msg; | |
- I2C_TypeDef *dp = i2cp->i2c; | |
- systime_t start, end; | |
+// I2C_TypeDef *dp = i2cp->i2c; | |
+ | |
+ osalDbgAssert((i2cp->thread==NULL), "#3 - reentry"); | |
/* Resetting error flags for this transfer.*/ | |
i2cp->errors = I2C_NO_ERROR; | |
@@ -972,38 +1744,18 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
/* Releases the lock from high level driver.*/ | |
osalSysUnlock(); | |
+ calcMasterTimeout(i2cp); // This has to be done before we change the state of the connection | |
+ | |
+ | |
#if STM32_I2C_USE_DMA == TRUE | |
/* RX DMA setup.*/ | |
dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
dmaStreamSetMemory0(i2cp->dmarx, rxbuf); | |
dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes); | |
-#else | |
+#endif | |
+ // Always set up the receive buffer in case callbacks enabled | |
i2cp->rxptr = rxbuf; | |
i2cp->rxbytes = rxbytes; | |
-#endif | |
- | |
- /* Calculating the time window for the timeout on the busy bus condition.*/ | |
- start = osalOsGetSystemTimeX(); | |
- end = start + OSAL_MS2ST(STM32_I2C_BUSY_TIMEOUT); | |
- | |
- /* Waits until BUSY flag is reset or, alternatively, for a timeout | |
- condition.*/ | |
- while (true) { | |
- osalSysLock(); | |
- | |
- /* If the bus is not busy then the operation can continue, note, the | |
- loop is exited in the locked state.*/ | |
- if ((dp->ISR & I2C_ISR_BUSY) == 0) | |
- break; | |
- | |
- /* If the system time went outside the allowed window then a timeout | |
- condition is returned.*/ | |
- if (!osalOsIsTimeWithinX(osalOsGetSystemTimeX(), start, end)) { | |
- return MSG_TIMEOUT; | |
- } | |
- | |
- osalSysUnlock(); | |
- } | |
/* Setting up the slave address.*/ | |
i2c_lld_set_address(i2cp, addr); | |
@@ -1011,35 +1763,18 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
/* Setting up the peripheral.*/ | |
i2c_lld_setup_rx_transfer(i2cp); | |
-#if STM32_I2C_USE_DMA == TRUE | |
- /* Enabling RX DMA.*/ | |
- dmaStreamEnable(i2cp->dmarx); | |
- | |
- /* Transfer complete interrupt enabled.*/ | |
- dp->CR1 |= I2C_CR1_TCIE; | |
-#else | |
- | |
- /* Transfer complete and RX interrupts enabled.*/ | |
- dp->CR1 |= I2C_CR1_TCIE | I2C_CR1_RXIE; | |
-#endif | |
+ i2cStartReceive(i2cp); | |
+ i2cp->mode = i2cMasterRxing; | |
+ | |
+ return startMasterAction(i2cp, timeout); | |
+} | |
- /* Starts the operation.*/ | |
- dp->CR2 |= I2C_CR2_START; | |
- /* Waits for the operation completion or a timeout.*/ | |
- msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); | |
- | |
- /* In case of a software timeout a STOP is sent as an extreme attempt | |
- to release the bus.*/ | |
- if (msg == MSG_TIMEOUT) { | |
- dp->CR2 |= I2C_CR2_STOP; | |
- } | |
- | |
- return msg; | |
-} | |
/** | |
* @brief Transmits data via the I2C bus as master. | |
+ * @details Number of receiving bytes must be 0 or more than 1 on STM32F1x. | |
+ * This is hardware restriction. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* @param[in] addr slave device address | |
@@ -1065,9 +1800,9 @@ msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
const uint8_t *txbuf, size_t txbytes, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout) { | |
- msg_t msg; | |
I2C_TypeDef *dp = i2cp->i2c; | |
- systime_t start, end; | |
+ | |
+ osalDbgAssert((i2cp->thread==NULL), "#3 - reentry"); | |
/* Resetting error flags for this transfer.*/ | |
i2cp->errors = I2C_NO_ERROR; | |
@@ -1075,6 +1810,9 @@ msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
/* Releases the lock from high level driver.*/ | |
osalSysUnlock(); | |
+ calcMasterTimeout(i2cp); // This has to be done before we change the state of the connection | |
+ | |
+ | |
#if STM32_I2C_USE_DMA == TRUE | |
/* TX DMA setup.*/ | |
dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
@@ -1088,32 +1826,10 @@ msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
#else | |
i2cp->txptr = txbuf; | |
i2cp->txbytes = txbytes; | |
+#endif | |
+ // Always set up the receive buffer in case callbacks enabled | |
i2cp->rxptr = rxbuf; | |
i2cp->rxbytes = rxbytes; | |
-#endif | |
- | |
- /* Calculating the time window for the timeout on the busy bus condition.*/ | |
- start = osalOsGetSystemTimeX(); | |
- end = start + OSAL_MS2ST(STM32_I2C_BUSY_TIMEOUT); | |
- | |
- /* Waits until BUSY flag is reset or, alternatively, for a timeout | |
- condition.*/ | |
- while (true) { | |
- osalSysLock(); | |
- | |
- /* If the bus is not busy then the operation can continue, note, the | |
- loop is exited in the locked state.*/ | |
- if ((dp->ISR & I2C_ISR_BUSY) == 0) | |
- break; | |
- | |
- /* If the system time went outside the allowed window then a timeout | |
- condition is returned.*/ | |
- if (!osalOsIsTimeWithinX(osalOsGetSystemTimeX(), start, end)) { | |
- return MSG_TIMEOUT; | |
- } | |
- | |
- osalSysUnlock(); | |
- } | |
/* Setting up the slave address.*/ | |
i2c_lld_set_address(i2cp, addr); | |
@@ -1132,21 +1848,218 @@ msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
dp->CR1 |= I2C_CR1_TCIE | I2C_CR1_TXIE; | |
#endif | |
- /* Starts the operation.*/ | |
- dp->CR2 |= I2C_CR2_START; | |
+ i2cp->mode = i2cMasterTxing; | |
- /* Waits for the operation completion or a timeout.*/ | |
- msg = osalThreadSuspendTimeoutS(&i2cp->thread, timeout); | |
+ return startMasterAction(i2cp, timeout); | |
+} | |
+#endif /* #if HAL_USE_I2C_MASTER == TRUE */ | |
- /* In case of a software timeout a STOP is sent as an extreme attempt | |
- to release the bus.*/ | |
- if (msg == MSG_TIMEOUT) { | |
- dp->CR2 |= I2C_CR2_STOP; | |
+ | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE | |
+/************************************************************************/ | |
+/* SLAVE MODE SUPPORT */ | |
+/************************************************************************/ | |
+ | |
+/** | |
+ * @brief Reconfigure I2C channel to respond to passed address | |
+ * in addition to those previously made active | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C network address | |
+ * | |
+ * @return Length of message OR the type of event received | |
+ * @retval I2C_OK Success | |
+ * @retval I2C_ERROR Cannot match address in addition of those already | |
+ * | |
+ * @details MatchAddress calls are cumulative. | |
+ * Specify address zero to match I2C "all call" | |
+ * Does not support 10-bit addressing. | |
+ * Number of supported addresses is chip-dependent - 2+general call on I2CV2 | |
+ * Address masking capabilities of OAR2 not supported | |
+ * | |
+ * @notapi | |
+ **/ | |
+msg_t i2c_lld_matchAddress(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ if (i2cadr == 0) { | |
+ dp->CR1 |= I2C_CR1_GCEN; // Just enable General Call | |
+ dp->CR1 |= I2C_CR1_ADDRIE; // Make sure address match interrupt enabled | |
+ } | |
+ else | |
+ { | |
+ uint32_t adr = i2cadr << 1; | |
+ if ((dp->OAR1 & (0x7f<<1)) == adr) { return I2C_OK; }; // Already matched in OAR1 | |
+ if ((dp->OAR2 & (0x7f<<1)) == adr) { return I2C_OK; }; // Already matched in OAR2 | |
+ | |
+ if (!(dp->OAR1 & I2C_OAR1_OA1EN)) { | |
+ dp->OAR1 = adr | I2C_OAR1_OA1EN; // OAR1 previously unused | |
+ dp->CR1 |= I2C_CR1_ADDRIE; // Make sure address match interrupt enabled | |
+ } | |
+ else | |
+ if (!(dp->OAR2 & I2C_OAR2_OA2EN)) { | |
+ dp->OAR2 = adr | I2C_OAR2_OA2EN; // OAR2 previously unused | |
+ dp->CR1 |= I2C_CR1_ADDRIE; // Make sure address match interrupt enabled | |
+ } | |
+ else | |
+ return I2C_ERROR; /* cannot add this address to set of those matched */ | |
+ } | |
+ return I2C_OK; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Reconfigure I2C channel to no longer match specified address | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C network address | |
+ * | |
+ * @details A message being transferred that has already matched the | |
+ * specified address will continue being processed. | |
+ * Requests to unmatch an address that is not currently being matched | |
+ * are ignored. | |
+ * Does not support 10-bit addressing. | |
+ * | |
+ * @notapi | |
+ **/ | |
+void i2c_lld_unmatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ if (i2cadr == 0) { | |
+ dp->CR1 &= (uint32_t)~I2C_CR1_GCEN; // Disable General Call | |
+ } | |
+ else { | |
+ uint32_t adr = i2cadr << 1; | |
+ if ((dp->OAR1 & (0x7f<<1)) == adr) { | |
+ // Matched in OAR1 | |
+ dp->OAR1 &= ~I2C_OAR1_OA1EN; // Just disable - OAR2 is a bit different | |
+ } | |
+ else | |
+ if ((dp->OAR2 & I2C_OAR2_OA2EN) && (dp->OAR2 & (0x7f<<1)) == adr) | |
+ dp->OAR2 &= ~I2C_OAR2_OA2EN; | |
+ } | |
+ if (!((dp->CR1 & (uint32_t)I2C_CR1_GCEN) || (dp->OAR1 & I2C_OAR1_OA1EN) || (dp->OAR2 & I2C_OAR2_OA2EN))) { | |
+ dp->CR1 &= (uint32_t)~(I2C_CR1_ADDRIE); // Disable Address match interrupts if nothing can generate them (strictly necessary??) | |
+ } | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Reconfigure I2C channel to no longer match any address | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @details Causes all subsequent messages to be ignored. | |
+ * A message being transferred that has already matched a | |
+ * slave address will continue being processed. | |
+ * | |
+ * @notapi | |
+ **/ | |
+void i2c_lld_unmatchAll(I2CDriver *i2cp) | |
+{ | |
+ I2C_TypeDef *dp = i2cp->i2c; | |
+ dp->CR1 &= (uint32_t)~(I2C_CR1_GCEN | I2C_CR1_ADDRIE); // Disable General Call | |
+ dp->OAR1 = 0; | |
+ dp->OAR2 = 0; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers to receive messages | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent received messages | |
+ * | |
+ * @details Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @notapi | |
+ */ | |
+void i2c_lld_slaveReceive(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg) | |
+{ | |
+ osalDbgCheck((rxMsg && rxMsg->size <= 0xffff)); | |
+ qEvt(0x82, rxMsg->size); | |
+ i2cp->slaveNextRx = rxMsg; | |
+ if (i2cp->mode == i2cLockedRxing && rxMsg->body && rxMsg->size) { | |
+ /* We can receive now! */ | |
+ i2cp->slaveRx = rxMsg; | |
+ /* slave RX DMA setup */ | |
+#if STM32_I2C_USE_DMA == TRUE | |
+ /* RX DMA setup.*/ | |
+ dmaStreamSetMode(i2cp->dmarx, i2cp->rxdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmarx, rxMsg->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmarx, rxMsg->size); | |
+#else | |
+ i2cp->rxptr = rxMsg->body; | |
+ i2cp->rxbytes = rxMsg->size; | |
+#endif | |
+ | |
+ i2cp->mode = i2cSlaveRxing; | |
+ i2c_lld_setup_rx_transfer(i2cp); // Set up the transfer | |
+ qEvt(0x83, 0); | |
+ i2cStartReceive(i2cp); | |
+ i2cp->i2c->CR1 &= ~I2C_CR1_SBC; // Not needed with receive | |
+ i2cp->i2c->ICR = I2C_ISR_ADDR; // We can release the clock stretch now | |
} | |
+} | |
+ | |
- return msg; | |
+/** | |
+ * @brief Configure callbacks & buffers for query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @notapi | |
+ */ | |
+void i2c_lld_slaveReply(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg) | |
+{ | |
+ osalDbgCheck((replyMsg && replyMsg->size <= 0xffff)); | |
+ qEvt(0x80, replyMsg->size); | |
+ i2cp->slaveNextReply = replyMsg; | |
+ if (i2cp->mode == i2cLockedReplying && replyMsg->body && replyMsg->size) | |
+ { | |
+ i2cp->slaveReply = replyMsg; | |
+ /* slave TX setup -- we can reply now! */ | |
+ #if STM32_I2C_USE_DMA == TRUE | |
+ dmaStreamSetMode(i2cp->dmatx, i2cp->txdmamode); | |
+ dmaStreamSetMemory0(i2cp->dmatx, replyMsg->body); | |
+ dmaStreamSetTransactionSize(i2cp->dmatx, replyMsg->size); | |
+ i2cp->mode = i2cSlaveReplying; | |
+ /* Start transmission */ | |
+ i2c_lld_setup_tx_transfer(i2cp); | |
+ | |
+ /* Enabling TX DMA.*/ | |
+ dmaStreamEnable(i2cp->dmatx); | |
+ | |
+ /* Transfer complete interrupt enabled.*/ | |
+ i2cp->i2c->CR1 |= I2C_CR1_TCIE; | |
+ #else | |
+ /* Start transmission */ | |
+ i2cp->txptr = replyMsg->body; | |
+ i2cp->txbytes = replyMsg->size; | |
+ i2c_lld_setup_tx_transfer(i2cp); | |
+ /* Transfer complete and TX character interrupts enabled.*/ | |
+ i2cp->i2c->CR1 |= I2C_CR1_TCIE | I2C_CR1_TXIE; | |
+ #endif | |
+ qEvt(0x81, 0); | |
+ i2cp->i2c->CR1 |= I2C_CR1_SBC; // Need this to enable byte counter in transmit mode | |
+ i2cp->i2c->ICR = I2C_ISR_ADDR; // We can release the clock stretch now | |
+ } | |
} | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
+ | |
#endif /* HAL_USE_I2C */ | |
/** @} */ | |
diff --git a/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.h b/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.h | |
index 7255e537d..16cceb65a 100644 | |
--- a/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.h | |
+++ b/os/hal/ports/STM32/LLD/I2Cv2/hal_i2c_lld.h | |
@@ -1,5 +1,5 @@ | |
/* | |
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | |
+ ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
@@ -15,19 +15,20 @@ | |
*/ | |
/* | |
Concepts and parts of this file have been contributed by Uladzimir Pylinsky | |
- aka barthess. | |
+ aka barthess. I2C Slave API for Chibios V2.x V1 I2C originally contributed | |
+ by Brent Roman ([email protected]), ported to Chibios V3, V2 I2C by steved | |
*/ | |
/** | |
- * @file I2Cv2/hal_i2c_lld.h | |
+ * @file STM32/I2Cv2/i2c_lld.h | |
* @brief STM32 I2C subsystem low level driver header. | |
* | |
* @addtogroup I2C | |
* @{ | |
*/ | |
-#ifndef HAL_I2C_LLD_H | |
-#define HAL_I2C_LLD_H | |
+#ifndef _I2C_LLD_H_ | |
+#define _I2C_LLD_H_ | |
#if HAL_USE_I2C || defined(__DOXYGEN__) | |
@@ -51,6 +52,17 @@ | |
#define STM32_TIMINGR_SCLL(n) ((n) << 0) | |
/** @} */ | |
+ | |
+/** | |
+ * Driver clears down tidily after a timeout | |
+ */ | |
+#define I2C_SUPPORT_BUS_CLEAR TRUE | |
+ | |
+/** | |
+ * @brief Invalid I2C bus address | |
+ */ | |
+#define i2cInvalidAdr ((i2caddr_t) -1) | |
+ | |
/*===========================================================================*/ | |
/* Driver pre-compile time settings. */ | |
/*===========================================================================*/ | |
@@ -95,6 +107,23 @@ | |
#define STM32_I2C_USE_I2C4 FALSE | |
#endif | |
+ | |
+/** | |
+ * @brief Enables support for I2C slave mode operation | |
+ */ | |
+#if !defined(HAL_USE_I2C_SLAVE) || defined(__DOXYGEN__) | |
+#define HAL_USE_I2C_SLAVE FALSE | |
+#endif | |
+ | |
+ | |
+/** | |
+ * @brief Turns on some debugging options | |
+ */ | |
+#if !defined(STM32_I2C_DEBUG_ENABLE) || defined(__DOXYGEN__) | |
+#define STM32_I2C_DEBUG_ENABLE FALSE | |
+#endif | |
+ | |
+ | |
/** | |
* @brief I2C timeout on busy condition in milliseconds. | |
*/ | |
@@ -187,6 +216,7 @@ | |
#endif | |
/** @} */ | |
+ | |
/*===========================================================================*/ | |
/* Derived constants and error checks. */ | |
/*===========================================================================*/ | |
@@ -333,12 +363,16 @@ | |
#endif | |
#endif /* STM32_I2C_USE_DMA == TRUE */ | |
+ | |
+ | |
/*===========================================================================*/ | |
/* Driver data structures and types. */ | |
/*===========================================================================*/ | |
/** | |
* @brief Type representing an I2C address. | |
+ * @note For a 7-bit address, this takes values 0..0x7f, which are then | |
+ * shifted left one and the R/W bit added when required | |
*/ | |
typedef uint16_t i2caddr_t; | |
@@ -347,6 +381,40 @@ typedef uint16_t i2caddr_t; | |
*/ | |
typedef uint32_t i2cflags_t; | |
+ | |
+/** | |
+ * @brief Type of a structure representing an I2C driver. | |
+ */ | |
+typedef struct I2CDriver I2CDriver; | |
+ | |
+ | |
+/** | |
+ * @brief Supported modes for the I2C bus. | |
+ * @note Currently not used; retained for future enhancements | |
+ */ | |
+typedef enum { | |
+ OPMODE_I2C = 1, | |
+ OPMODE_SMBUS_DEVICE = 2, | |
+ OPMODE_SMBUS_HOST = 3, | |
+} i2copmode_t; | |
+ | |
+ | |
+ | |
+/** | |
+ * @brief Character received I2C notification callback type. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] c received character | |
+ * | |
+ * @param[out] Return 0 if transfer to continue. 1 if transfer to be stopped | |
+ * | |
+ * @note Use only in master mode, to stop a read transaction | |
+ * once a particular character (or sequence of characters) has been received | |
+ */ | |
+typedef uint8_t (*i2cccb_t)(I2CDriver *i2cp, uint16_t c); | |
+ | |
+ | |
+ | |
/** | |
* @brief Type of I2C driver configuration structure. | |
*/ | |
@@ -364,15 +432,76 @@ typedef struct { | |
uint32_t cr1; | |
/** | |
* @brief CR2 register initialization. | |
- * @note Only the ADD10 bit can eventually be specified here. | |
+ * @note Leave at zero except in special circumstances - most bits controlled via API | |
*/ | |
uint32_t cr2; | |
+ /** | |
+ * @brief Character received callback. Return 0 if transfer to continue. 1 if transfer to be stopped | |
+ * @note Use only in master mode. Set to NULL if not used. | |
+ */ | |
+ i2cccb_t rxchar_cb; | |
} I2CConfig; | |
-/** | |
- * @brief Type of a structure representing an I2C driver. | |
- */ | |
-typedef struct I2CDriver I2CDriver; | |
+ | |
+ | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+typedef struct I2CSlaveMsg I2CSlaveMsg; | |
+ | |
+/* | |
+ returns the current I2C slave message receive configuration | |
+*/ | |
+I2CSlaveMsg *i2cSlaveGetReceiveMsg(I2CDriver *i2cp); | |
+ | |
+ | |
+/* | |
+ returns the current I2C slave message reply configuration | |
+*/ | |
+I2CSlaveMsg *i2cSlaveGetReplyMsg(I2CDriver *i2cp); | |
+ | |
+ | |
+/* | |
+ I2C Slave Message Call Back. | |
+ Invoked from interrupt context just after | |
+ the last byte of the message is transferred or slaveAdr is matched. | |
+ | |
+ Use i2cSlaveReceiveMsg() or i2cSlaveReplyMsg() to access | |
+ the relevant message handling configuration | |
+*/ | |
+typedef void I2CSlaveMsgCB(I2CDriver *i2cp); | |
+ | |
+ | |
+/* | |
+ I2CSlaveMsg message handling configurations are normally | |
+ stored in read-only memory. | |
+ They describe either a buffer to contain incoming messages from | |
+ a bus master and associated callback functions, or one | |
+ preloaded with an outgoing reply to a read request and its callbacks. | |
+*/ | |
+ | |
+struct I2CSlaveMsg { | |
+ size_t size; /* sizeof(body) -- zero if master must wait */ | |
+ uint8_t *body; /* message contents -- or NULL if master must wait */ | |
+ I2CSlaveMsgCB *adrMatched; /* invoked when slave address matches */ | |
+ I2CSlaveMsgCB *processMsg; /* invoked after message is transferred */ | |
+ I2CSlaveMsgCB *exception; /* invoked if error or timeout during transfer */ | |
+}; | |
+ | |
+ | |
+/* | |
+ dummy callback -- placeholder to ignore event | |
+*/ | |
+I2CSlaveMsgCB I2CSlaveDummyCB; | |
+ | |
+ /* lock bus on receive or reply -- force master to wait */ | |
+extern const I2CSlaveMsg I2CSlaveLockOnMsg; | |
+ | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
+ | |
+ | |
/** | |
* @brief Structure representing an I2C driver. | |
@@ -401,6 +530,8 @@ struct I2CDriver { | |
* @brief Thread waiting for I/O completion. | |
*/ | |
thread_reference_t thread; | |
+ | |
+ | |
#if (STM32_I2C_USE_DMA == TRUE) || defined(__DOXYGEN__) | |
/** | |
* @brief RX DMA mode bit mask. | |
@@ -427,6 +558,7 @@ struct I2CDriver { | |
* @brief Number of bytes in TX phase. | |
*/ | |
size_t txbytes; | |
+#endif /* STM32_I2C_USE_DMA == FALSE */ | |
/** | |
* @brief Pointer to the next RX buffer location. | |
*/ | |
@@ -435,11 +567,83 @@ struct I2CDriver { | |
* @brief Number of bytes in RX phase. | |
*/ | |
size_t rxbytes; | |
-#endif /* STM32_I2C_USE_DMA == FALSE */ | |
/** | |
* @brief Pointer to the I2Cx registers block. | |
*/ | |
I2C_TypeDef *i2c; | |
+ | |
+ | |
+ /** | |
+ * @brief low level I2C interface / protocol state | |
+ */ | |
+ enum i2cMode { | |
+ i2cStopped = 0, /* Port not initialised, or not started */ | |
+ i2cIdle=1, /* awaiting address or inactive */ | |
+ i2cSlaveRxing, /* receiving message */ | |
+ i2cLockedRxing, /* stretching clock before receiving message - Rx buffer might be full */ | |
+ i2cSlaveReplying, /* replying to query (transmitting, slave mode) */ | |
+ i2cLockedReplying, /* stretching clock before replying to query (no response available from main code) */ | |
+ | |
+ i2cIsMaster=0x11, /* sent start bit (mastering bus) */ | |
+ i2cMasterStarted, /* repeated start after write */ | |
+ i2cMasterSelecting, /* sending slave address */ | |
+ i2cMasterRxing, /* receiving reply from slave */ | |
+ i2cMasterTxing /* sending message to slave */ | |
+ } mode; | |
+ | |
+#if HAL_USE_I2C_LOCK || HAL_USE_I2C_SLAVE | |
+ /** | |
+ * @brief I2C transaction timer | |
+ * @note USed for slave mode, lock | |
+ */ | |
+ virtual_timer_t timer; | |
+#endif | |
+#if HAL_USE_I2C_LOCK | |
+ /** | |
+ * @brief I2C bus lock duration | |
+ */ | |
+ systime_t lockDuration; | |
+#endif | |
+#if HAL_USE_I2C_SLAVE | |
+ /* additional fields to support I2C slave transactions */ | |
+ | |
+ /** | |
+ * @brief slave address of message being processed | |
+ */ | |
+ i2caddr_t targetAdr; | |
+ /** | |
+ * @brief Error Mask for last slave message | |
+ */ | |
+ i2cflags_t slaveErrors; | |
+ /** | |
+ * @brief Length of most recently transferred slave message | |
+ */ | |
+ size_t slaveBytes; | |
+ /** | |
+ * @brief Maximum # of ticks slave may stretch the I2C clock | |
+ */ | |
+ systime_t slaveTimeout; | |
+ /** | |
+ * @brief Pointer to slave message reception handler | |
+ */ | |
+ const I2CSlaveMsg *slaveRx; | |
+ /** | |
+ * @brief Pointer to slave message Reply (transmit) handler | |
+ * | |
+ * @note This is the currently active/just completed reply | |
+ */ | |
+ const I2CSlaveMsg *slaveReply; | |
+ /** | |
+ * @brief Pointer to handler for next slave received message | |
+ */ | |
+ const I2CSlaveMsg *slaveNextRx; | |
+ /** | |
+ * @brief Pointer to handler for next slave reply (transmit) message | |
+ * | |
+ * @note This is used for a reply if no message received first | |
+ */ | |
+ const I2CSlaveMsg *slaveNextReply; | |
+#endif | |
}; | |
/*===========================================================================*/ | |
@@ -455,6 +659,89 @@ struct I2CDriver { | |
*/ | |
#define i2c_lld_get_errors(i2cp) ((i2cp)->errors) | |
+ | |
+ | |
+#if HAL_USE_I2C_LOCK | |
+/** | |
+ * @brief Unlock I2C bus after the end of the next transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ **/ | |
+#define i2c_lld_unlock(i2cp) (i2cp->lockDuration = TIME_IMMEDIATE) | |
+#endif | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+/** | |
+ * @brief Get slave errors from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveErrors(i2cp) ((i2cp)->slaveErrors) | |
+ | |
+/** | |
+ * @brief Get slave message bytes transferred from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveBytes(i2cp) ((i2cp)->slaveBytes) | |
+ | |
+ | |
+/** | |
+ * @brief Get slave timeout in ticks from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveTimeout(i2cp) ((i2cp)->slaveTimeout) | |
+ | |
+/** | |
+ * @brief Set slave timeout in ticks for I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_set_slaveTimeout(i2cp,ticks) ((i2cp)->slaveTimeout=(ticks)) | |
+ | |
+/** | |
+ * @brief Get slave target address from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveTargetAdr(i2cp) ((i2cp)->targetAdr) | |
+ | |
+/** | |
+ * @brief Get slave receive message descriptor from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveReceive(i2cp) ((i2cp)->slaveNextRx) | |
+ | |
+/** | |
+ * @brief Get slave reply message descriptor from I2C driver. | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @notapi | |
+ */ | |
+#define i2c_lld_get_slaveReply(i2cp) ((i2cp)->slaveNextReply) | |
+ | |
+ | |
+#endif | |
+ | |
+ | |
/*===========================================================================*/ | |
/* External declarations. */ | |
/*===========================================================================*/ | |
@@ -491,12 +778,29 @@ extern "C" { | |
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout); | |
+ | |
+#if HAL_USE_I2C_LOCK /* I2C slave mode support */ | |
+ void i2c_lld_lock(I2CDriver *i2cp, systime_t lockDuration); | |
+#endif | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ msg_t i2c_lld_matchAddress(I2CDriver *i2cp, i2caddr_t i2cadr); | |
+ void i2c_lld_unmatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr); | |
+ void i2c_lld_unmatchAll(I2CDriver *i2cp); | |
+ void i2c_lld_slaveReceive(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg); | |
+ void i2c_lld_slaveReply(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg); | |
+ | |
+#if STM32_I2C_DEBUG_ENABLE | |
+ void i2cPrintQ(BaseSequentialStream *chp); // Debugging routine | |
+#endif /* STM32_I2C_DEBUG_ENABLE */ | |
+ | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif /* HAL_USE_I2C */ | |
-#endif /* HAL_I2C_LLD_H */ | |
+#endif /* _I2C_LLD_H_ */ | |
/** @} */ | |
diff --git a/os/hal/src/hal_i2c.c b/os/hal/src/hal_i2c.c | |
index 54a362f0c..1f98a9d65 100644 | |
--- a/os/hal/src/hal_i2c.c | |
+++ b/os/hal/src/hal_i2c.c | |
@@ -1,5 +1,5 @@ | |
/* | |
- ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio | |
+ ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
@@ -19,7 +19,7 @@ | |
*/ | |
/** | |
- * @file hal_i2c.c | |
+ * @file i2c.c | |
* @brief I2C Driver code. | |
* | |
* @addtogroup I2C | |
@@ -74,8 +74,12 @@ void i2cObjectInit(I2CDriver *i2cp) { | |
i2cp->config = NULL; | |
#if I2C_USE_MUTUAL_EXCLUSION == TRUE | |
+#if CH_CFG_USE_MUTEXES | |
osalMutexObjectInit(&i2cp->mutex); | |
-#endif | |
+#else | |
+ osalSemObjectInit(&i2cp->semaphore, 1); | |
+#endif /* CH_CFG_USE_MUTEXES */ | |
+#endif /* I2C_USE_MUTUAL_EXCLUSION */ | |
#if defined(I2C_DRIVER_EXT_INIT_HOOK) | |
I2C_DRIVER_EXT_INIT_HOOK(i2cp); | |
@@ -113,16 +117,12 @@ void i2cStart(I2CDriver *i2cp, const I2CConfig *config) { | |
void i2cStop(I2CDriver *i2cp) { | |
osalDbgCheck(i2cp != NULL); | |
- | |
- osalSysLock(); | |
- | |
osalDbgAssert((i2cp->state == I2C_STOP) || (i2cp->state == I2C_READY) || | |
(i2cp->state == I2C_LOCKED), "invalid state"); | |
+ osalSysLock(); | |
i2c_lld_stop(i2cp); | |
- i2cp->config = NULL; | |
- i2cp->state = I2C_STOP; | |
- | |
+ i2cp->state = I2C_STOP; | |
osalSysUnlock(); | |
} | |
@@ -246,7 +246,190 @@ msg_t i2cMasterReceiveTimeout(I2CDriver *i2cp, | |
return rdymsg; | |
} | |
-#if (I2C_USE_MUTUAL_EXCLUSION == TRUE) || defined(__DOXYGEN__) | |
+ | |
+#if HAL_USE_I2C_LOCK /* I2C slave mode support */ | |
+ | |
+/** | |
+ * @brief Lock I2C bus at the beginning of the next message sent | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] lockDuration max number of ticks to hold bus locked | |
+ * - @a TIME_INFINITE no timeout. | |
+ * - @a TIME_IMMEDIATE unlock the bus immediately | |
+ * . | |
+ * | |
+ * @api | |
+ */ | |
+void i2cLock(I2CDriver *i2cp, systime_t lockDuration) | |
+{ | |
+ chDbgCheck((i2cp != NULL), "i2cLock"); | |
+ chSysLock(); | |
+ i2c_lld_lock(i2cp, lockDuration); | |
+ chSysUnlock(); | |
+} | |
+ | |
+/** | |
+ * @brief Unlock I2C bus after the end of the next transaction | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @api | |
+ **/ | |
+void i2cUnlock(I2CDriver *i2cp) | |
+{ | |
+ chDbgCheck((i2cp != NULL), "i2cUnlock"); | |
+ chSysLock(); | |
+ i2c_lld_unlock(i2cp); | |
+ chSysUnlock(); | |
+} | |
+#endif | |
+ | |
+ | |
+#if HAL_USE_I2C_SLAVE /* I2C slave mode support */ | |
+ | |
+/** | |
+ * @brief Reconfigure I2C channel to respond to indicated address | |
+ * in addition to those already matched | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C network address | |
+ * | |
+ * @return Length of message OR the type of event received | |
+ * @retval I2C_OK Success | |
+ * @retval I2C_ERROR Cannot match address in addition of those already | |
+ * | |
+ * @details MatchAddress calls are cumulative. | |
+ * Specify address zero to match I2C "all call" | |
+ * Does not support 10-bit addressing. | |
+ * | |
+ * @api | |
+ **/ | |
+msg_t i2cMatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ osalDbgCheck((i2cp != NULL)); | |
+ chSysLock(); | |
+ msg_t result = i2c_lld_matchAddress(i2cp, i2cadr); | |
+ chSysUnlock(); | |
+ return result; | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure to ignore messages directed to the given i2cadr | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] i2cadr I2C bus address | |
+ * - @a 0 matches "all call" | |
+ * . | |
+ * @details A message being transferred that has already matched the | |
+ * specified address will continue being processed. | |
+ * Requests to unmatch an address that is not currently being matched | |
+ * are ignored. | |
+ * | |
+ * @api | |
+ */ | |
+void i2cUnmatchAddress(I2CDriver *i2cp, i2caddr_t i2cadr) | |
+{ | |
+ osalDbgCheck((i2cp != NULL)); | |
+ chSysLock(); | |
+ i2c_lld_unmatchAddress(i2cp, i2cadr); | |
+ chSysUnlock(); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Reconfigure I2C channel to no longer match any address | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * | |
+ * @details Causes all subsequent messages to be ignored. | |
+ * A message being transferred that has already matched a | |
+ * slave address will continue being processed. | |
+ * | |
+ * @api | |
+ **/ | |
+void i2cUnmatchAll(I2CDriver *i2cp) | |
+{ | |
+ osalDbgCheck((i2cp != NULL)); | |
+ chSysLock(); | |
+ i2c_lld_unmatchAll(i2cp); | |
+ chSysUnlock(); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for message reception & query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] rxMsg @p I2CSlaveMsg struct for processing subsequent messages | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Must be called from a thread | |
+ * Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @api | |
+ */ | |
+void i2cSlaveConfigure(I2CDriver *i2cp, | |
+ const I2CSlaveMsg *rxMsg, const I2CSlaveMsg *replyMsg) | |
+{ | |
+ osalDbgCheck((i2cp != NULL)); | |
+ chSysLock(); | |
+ i2c_lld_slaveReceive(i2cp, rxMsg); | |
+ i2c_lld_slaveReply(i2cp, replyMsg); | |
+ chSysUnlock(); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @api | |
+ */ | |
+void i2cSlaveReceive(I2CDriver *i2cp, const I2CSlaveMsg *rxMsg) | |
+{ | |
+ osalDbgCheck((i2cp != NULL && rxMsg != NULL)); | |
+ chSysLock(); | |
+ i2c_lld_slaveReceive(i2cp, rxMsg); | |
+ chSysUnlock(); | |
+} | |
+ | |
+ | |
+/** | |
+ * @brief Configure callbacks & buffers for query reply | |
+ * | |
+ * @param[in] i2cp pointer to the @p I2CDriver object | |
+ * @param[in] replyMsg @p I2CSlaveMsg struct for processing subsequent queries | |
+ * | |
+ * @details Call i2cMatchAddress() after this to start processing | |
+ * Enabling match addresses before installing handler callbacks can | |
+ * result in locking the I2C bus when a master accesses those | |
+ * unconfigured slave addresses | |
+ * | |
+ * @api | |
+ */ | |
+void i2cSlaveReply(I2CDriver *i2cp, const I2CSlaveMsg *replyMsg) | |
+{ | |
+ osalDbgCheck((i2cp != NULL && replyMsg != NULL)); | |
+ chSysLock(); | |
+ i2c_lld_slaveReply(i2cp, replyMsg); | |
+ chSysUnlock(); | |
+} | |
+ | |
+#endif /* HAL_USE_I2C_SLAVE */ | |
+ | |
+ | |
+#if I2C_USE_MUTUAL_EXCLUSION == TRUE || defined(__DOXYGEN__) | |
/** | |
* @brief Gains exclusive access to the I2C bus. | |
* @details This function tries to gain ownership to the I2C bus, if the bus | |
@@ -262,9 +445,14 @@ void i2cAcquireBus(I2CDriver *i2cp) { | |
osalDbgCheck(i2cp != NULL); | |
+#if CH_CFG_USE_MUTEXES | |
osalMutexLock(&i2cp->mutex); | |
+#elif CH_CFG_USE_SEMAPHORES | |
+ osalSemWait(&i2cp->semaphore); | |
+#endif /* CH_CFG_USE_MUTEXES */ | |
} | |
+ | |
/** | |
* @brief Releases exclusive access to the I2C bus. | |
* @pre In order to use this function the option @p I2C_USE_MUTUAL_EXCLUSION | |
@@ -278,8 +466,13 @@ void i2cReleaseBus(I2CDriver *i2cp) { | |
osalDbgCheck(i2cp != NULL); | |
+#if CH_CFG_USE_MUTEXES | |
osalMutexUnlock(&i2cp->mutex); | |
+#elif CH_CFG_USE_SEMAPHORES | |
+ osalSemSignal(&i2cp->semaphore); | |
+#endif /* CH_CFG_USE_MUTEXES */ | |
} | |
+ | |
#endif /* I2C_USE_MUTUAL_EXCLUSION == TRUE */ | |
#endif /* HAL_USE_I2C == TRUE */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment