mirror of https://github.com/ARMmbed/mbed-os.git
485 lines
20 KiB
C++
485 lines
20 KiB
C++
/* mbed Microcontroller Library
|
|
* Copyright (c) 2006-2019 ARM Limited
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#ifndef MBED_I2C_H
|
|
#define MBED_I2C_H
|
|
|
|
#include "platform/platform.h"
|
|
#include "hal/gpio_api.h"
|
|
|
|
#if DEVICE_I2C || defined(DOXYGEN_ONLY)
|
|
|
|
#include "hal/i2c_api.h"
|
|
#include "platform/SingletonPtr.h"
|
|
#include "rtos/Mutex.h"
|
|
#include "platform/NonCopyable.h"
|
|
|
|
#if DEVICE_I2C_ASYNCH
|
|
#include "platform/CThunk.h"
|
|
#include "hal/dma_api.h"
|
|
#include "platform/Callback.h"
|
|
#endif
|
|
|
|
namespace mbed {
|
|
/** \defgroup drivers-public-api-i2c I2C
|
|
* \ingroup drivers-public-api
|
|
*/
|
|
|
|
/**
|
|
* \defgroup drivers_I2C I2C class
|
|
* \ingroup drivers-public-api-i2c
|
|
* @{
|
|
*/
|
|
|
|
// Note: In the below comments, Doxygen wants to auto-link the word "I2C" to the class name. A percent sign is used to
|
|
// suppress this behavior.
|
|
|
|
/** An %I2C Master, used for communicating with %I2C slave devices
|
|
*
|
|
* There are three different forms of the %I2C API usable via this class:
|
|
* <ul>
|
|
* <li>Transaction-based %I2C</li>
|
|
* <li>Single-byte %I2C</li>
|
|
* <li>Asynchronous %I2C</li>
|
|
* </ul>
|
|
*
|
|
* All three of these APIs let you execute %I2C operations, but they work differently.
|
|
*
|
|
* <h1>Transaction-Based API</h1>
|
|
*
|
|
* The simplest API, which should be appropriate for most use cases, is the transaction-based API, which is
|
|
* accessed through the \link I2C::read(int address, char *data, int length, bool repeated) read() \endlink and the
|
|
* \link write(int address, const char *data, int length, bool repeated) write() \endlink functions. These functions
|
|
* execute an entire %I2C transaction (the start condition, address, data bytes, and stop condition) in a single
|
|
* function call.
|
|
*
|
|
* The bytes to be read/written are passed in through an array, which requires that you can predict the
|
|
* size of the data ahead of time. If this information is not known, you may want to use the single-byte API instead
|
|
* (see below).
|
|
*
|
|
* Example of using the transaction-based API to read the temperature from an LM75BD:
|
|
* @code
|
|
* #include "mbed.h"
|
|
* I2C i2c(I2C_SDA , I2C_SCL);
|
|
* const int addr7bit = 0x48; // 7-bit I2C address
|
|
* const int addr8bit = 0x48 << 1; // 8-bit I2C address, 0x90
|
|
*
|
|
* int main() {
|
|
* char cmd[2];
|
|
* while (1) {
|
|
* cmd[0] = 0x01;
|
|
* cmd[1] = 0x00;
|
|
*
|
|
* // read and write takes the 8-bit version of the address.
|
|
* // set up configuration register (at 0x01)
|
|
* I2C::Result result = i2c.write(addr8bit, cmd, 2);
|
|
*
|
|
* if(result != I2C::ACK)
|
|
* {
|
|
* // Chip not accessible, handle error....
|
|
* }
|
|
*
|
|
* ThisThread::sleep_for(500);
|
|
*
|
|
* // read temperature register
|
|
* cmd[0] = 0x00;
|
|
* i2c.write(addr8bit, cmd, 1, true); // Set repeated to true so that we don't give up the bus after this transaction
|
|
* i2c.read(addr8bit | 1, cmd, 2);
|
|
*
|
|
* float tmp = (float((cmd[0]<<8)|cmd[1]) / 256.0);
|
|
* printf("Temp = %.2f\n", tmp);
|
|
* }
|
|
* }
|
|
* @endcode
|
|
*
|
|
*
|
|
*
|
|
* <h1>Single-Byte API</h1>
|
|
*
|
|
* The single-byte API consists of the \link I2C::start() start() \endlink, \link I2C::write_byte() write_byte()
|
|
* \endlink, \link I2C::read_byte() read_byte() \endlink, and \link I2C::stop() stop() \endlink functions.
|
|
* With the single-byte API, you have manual control over each condition and data byte put onto the I2C bus.
|
|
* This is useful for dealing with devices which can return variable amounts of data in one I2C operation,
|
|
* or when you don't want to create buffers to store the data. However, this API is more verbose than the
|
|
* transaction-based API and will have a bit more overhead since there's more code executing per byte.
|
|
*
|
|
* The following is an example that accomplishes the same thing as the above code, but using the single-byte API.
|
|
* @code
|
|
* #include "mbed.h"
|
|
* I2C i2c(I2C_SDA , I2C_SCL);
|
|
* const int addr7bit = 0x48; // 7-bit I2C address
|
|
* const int addr8bit = 0x48 << 1; // 8-bit I2C address, 0x90
|
|
*
|
|
* int main() {
|
|
* while (1) {
|
|
* // read and write takes the 8-bit version of the address.
|
|
* // set up configuration register (at 0x01)
|
|
* i2c.lock();
|
|
* i2c.start();
|
|
* I2C::Result result = i2c.write_byte(addr8bit); // Write address, LSBit low to indicate write
|
|
* i2c.write_byte(0x01);
|
|
* i2c.write_byte(0x00);
|
|
* i2c.stop();
|
|
* i2c.unlock();
|
|
*
|
|
* if(result != I2C::ACK)
|
|
* {
|
|
* // Chip not accessible, handle error....
|
|
* }
|
|
*
|
|
* ThisThread::sleep_for(500);
|
|
*
|
|
* // Set register to read
|
|
* i2c.lock();
|
|
* i2c.start();
|
|
* i2c.write_byte(addr8bit); // Write address
|
|
* i2c.write_byte(0x00);
|
|
* // To create a repeated start condition, we do not call stop() here
|
|
*
|
|
* i2c.start();
|
|
* i2c.write_byte(addr8bit | 1); // Write address, LSBit high to indicate read
|
|
*
|
|
* // Read the two byte temperature word
|
|
* uint16_t temperatureBinary = 0;
|
|
* temperatureBinary |= static_cast<uint16_t>(i2c.read_byte(true)) << 8;
|
|
* temperatureBinary |= static_cast<uint16_t>(i2c.read_byte(false)); // send NACK to indicate last byte
|
|
* i2c.stop();
|
|
* i2c.unlock();
|
|
*
|
|
* float tmp = (float(temperatureBinary) / 256.0);
|
|
* printf("Temp = %.2f\n", tmp);
|
|
* }
|
|
* }
|
|
* @endcode
|
|
*
|
|
* \attention If a single I2C object is being shared among multiple threads, you should surround usage of the
|
|
* single-byte API with \link I2C::lock() lock() \endlink and \link I2C::unlock() unlock() \endlink. This
|
|
* ensures that a transaction by one thread is not interrupted by another. It may also improve performance
|
|
* because the backing mutex will not need to be locked for each byte.
|
|
*
|
|
* <h1>Asynchronous API</h1>
|
|
*
|
|
* The asynchronous API allows you to run %I2C operations in the background. This API is only
|
|
* available if your device has the I2C_ASYNCH feature. To use this API, use \link I2C::transfer() transfer() \endlink
|
|
* to start an operation and \link I2C::abort_transfer() abort_transfer() \endlink to stop it. Alternately, use the
|
|
* \link I2C::transfer_and_wait() transfer_and_wait() \endlink function to block the current thread until
|
|
* the transfer finishes.
|
|
*
|
|
* Some devices implement these features using DMA, others use interrupts, so be mindful that there may still be
|
|
* significant CPU usage if you have multiple and/or high-rate transfers going on.
|
|
*
|
|
* <h1>A Note about Addressing</h1>
|
|
* Most %I2C devices make use of 7-bit addresses (see <a href="https://www.i2c-bus.org/addressing/">here</a> for details).
|
|
* Mbed OS, however, works with addresses in 8-bit format, where the least significant bit specifies if the transaction
|
|
* is a read (1) or a write (0). Due to this, you will generally need to use bitshifts and bitwise ORs when passing
|
|
* addresses to I2C functions. See the documentation on each function for details.
|
|
*
|
|
* %I2C also has a <a href="https://www.i2c-bus.org/addressing/10-bit-addressing/">10-bit addressing mode</a>, where
|
|
* the address is sent in two physical bytes on the bus. Some, but not all, Mbed targets support this mode -- refer
|
|
* to your MCU datasheet and your target's HAL code for details. For 10-bit addresses, use the same format to
|
|
* pass them to I2C functions -- shift them left by one and set the LSBit to indicate the read/write direction.
|
|
* On MCUs that do not natively support 10-bit addressing, you can emulate support by using the single-byte API
|
|
* to send two address bytes; see the linked page above for details.
|
|
*
|
|
* <h1>Other Info</h1>
|
|
*
|
|
* The I2C class is thread-safe, and uses a mutex to prevent multiple threads from using it at the same time.
|
|
*
|
|
* \warning Mbed OS requires that you only create one instance of the I2C class per physical %I2C bus on your chip.
|
|
* This means that if you have multiple sensors connected together on a bus, you must create one I2C object at the
|
|
* top level and pass it in to the drivers for each sensor. Violating this directive will cause undefined
|
|
* behavior in your code.
|
|
*
|
|
* \attention Due to how %I2C works, if multiple devices are sharing a bus which support different %I2C speeds, you cannot
|
|
* go faster than the maximum bus speed of any of the devices. Otherwise, slower devices may misinterpret messages
|
|
* that are too fast for them and cause interference on the bus. For example, if you have two 400kHz devices and one
|
|
* 100kHz device on a bus, you must run the entire bus at 100kHz!
|
|
*/
|
|
class I2C : private NonCopyable<I2C> {
|
|
|
|
public:
|
|
|
|
/**
|
|
* Result code for I2C operations
|
|
*/
|
|
enum Result : int {
|
|
/// ACK was received
|
|
ACK = 0,
|
|
/// NACK was received
|
|
NACK,
|
|
/// Timeout waiting for I2C hardware
|
|
TIMEOUT,
|
|
/// Other error in I2C operation
|
|
OTHER_ERROR
|
|
};
|
|
|
|
|
|
/** Create an I2C Master interface, connected to the specified pins.
|
|
* The new object defaults to 100kHz speed.
|
|
*
|
|
* @param sda I2C data line pin
|
|
* @param scl I2C clock line pin
|
|
*/
|
|
I2C(PinName sda, PinName scl);
|
|
|
|
/** Create an I2C Master interface, connected to the specified pins.
|
|
* The new object defaults to 100kHz speed.
|
|
*
|
|
* @param static_pinmap reference to structure which holds static pinmap.
|
|
*/
|
|
I2C(const i2c_pinmap_t &static_pinmap);
|
|
I2C(const i2c_pinmap_t &&) = delete; // prevent passing of temporary objects
|
|
|
|
/** Set the frequency of the I2C interface.
|
|
* If you do not call this function, the I2C will run at 100kHz speed.
|
|
*
|
|
* Note: Some underlying HALs only support a very limited set of common I2C frequencies, such as 100kHz and
|
|
* 400kHz. Other implementations support all frequencies. If the frequency you set is not supported, you will get
|
|
* an assertion failure after calling this function.
|
|
*
|
|
* @param hz The bus frequency in hertz
|
|
*/
|
|
void frequency(int hz);
|
|
|
|
/** Read from an %I2C slave
|
|
*
|
|
* Performs a complete read transaction. The least significant bit of
|
|
* the address must be 1 to indicate a read.
|
|
*
|
|
* @param address 8/11-bit I2C slave address [ (7 or 10 bit addr << 1) | 1 ]
|
|
* @param data Pointer to the byte-array to read data in to
|
|
* @param length Number of bytes to read
|
|
* @param repeated Set up for a repeated start. If true, the Mbed processor does not relinquish the bus after
|
|
* this read operation. You may then call write(), read(), or start() again to start another operation.
|
|
*
|
|
* @returns Result enum describing whether the I2C transaction succeeded or failed
|
|
*/
|
|
Result read(int address, char *data, int length, bool repeated = false);
|
|
|
|
/** Write to an %I2C slave
|
|
*
|
|
* Performs a complete write transaction. The least significant bit of
|
|
* the address must be 0 to indicate a write.
|
|
*
|
|
* @param address 8/11-bit I2C slave address [ (7 or 10 bit addr << 1) | 0 ]
|
|
* @param data Pointer to the byte-array data to send
|
|
* @param length Number of bytes to send
|
|
* @param repeated Set up for a repeated start. If true, the Mbed processor does not relinquish the bus after
|
|
* this write operation. You may then call write(), read(), or start() again to start another operation.
|
|
*
|
|
* @returns Result enum describing whether the I2C transaction succeeded or failed
|
|
*/
|
|
Result write(int address, const char *data, int length, bool repeated = false);
|
|
|
|
/** Creates a start condition on the %I2C bus. After calling this function, you should call
|
|
* \link write_byte() \endlink to send the %I2C address.
|
|
*/
|
|
void start(void);
|
|
|
|
/** Read a single byte from the %I2C bus.
|
|
*
|
|
* After calling this function, you may call it again to read another byte from the slave. Alternately,
|
|
* you may call \link stop() \endlink to stop the current transaction, or \link start() \endlink to
|
|
* start a new transaction.
|
|
*
|
|
* Note: Reads are not acknowledged by the slave device in I2C, which is why this function does not
|
|
* return an ACK/NACK result.
|
|
*
|
|
* @param ack indicates if the byte is to be acknowledged (true = acknowledge). Use false to indicate to
|
|
* the slave that you don't want to read any more data.
|
|
*
|
|
* @returns
|
|
* the byte read, or -1 on error.
|
|
*/
|
|
int read_byte(bool ack);
|
|
|
|
/** Read a single byte from the %I2C bus. This function is a legacy alias for \link read_byte() \endlink
|
|
*
|
|
* After calling this function, you may call it again to read another byte from the slave. Alternately,
|
|
* you may call \link stop() \endlink to stop the current transaction, or \link start() \endlink to
|
|
* start a new transaction.
|
|
*
|
|
* Note: Reads are not acknowledged by the slave device in I2C, which is why this function does not
|
|
* return an ACK/NACK result.
|
|
*
|
|
* @param ack indicates if the byte is to be acknowledged (1 = acknowledge)
|
|
*
|
|
* @returns
|
|
* the byte read
|
|
*/
|
|
int read(int ack)
|
|
{
|
|
return read_byte(ack);
|
|
}
|
|
|
|
/** Write a single byte out on the %I2C bus. The very first write_byte() call after calling start()
|
|
* is used to set up the slave address.
|
|
*
|
|
* After calling this function, you may call \link write_byte() \endlink again to write bytes in a write operation,
|
|
* or \link read_byte() \endlink to read bytes in a read operation. Once done, call \link stop() \endlink
|
|
* to stop the current transaction or \link start() \endlink to start a new transaction.
|
|
*
|
|
* @param data data to write out on bus. Note: This is an int, not a uint8_t, to support addressing modes
|
|
* with more than 7 bits.
|
|
*
|
|
* @returns Result enum describing whether the I2C byte was acknowledged or not
|
|
*/
|
|
Result write_byte(int data);
|
|
|
|
/** Write a single byte out on the %I2C bus. Deprecated version of \link write_byte() \endlink, with a legacy
|
|
* return code format.
|
|
*
|
|
* @param data data to write out on bus
|
|
*
|
|
* @returns
|
|
* '0' - NAK was received
|
|
* '1' - ACK was received,
|
|
* '2' - timeout
|
|
*/
|
|
MBED_DEPRECATED_SINCE("mbed-ce", "Use I2C::write_byte() instead for better readability and return codes")
|
|
int write(int data);
|
|
|
|
/**
|
|
* Creates a stop condition on the %I2C bus. This puts the bus back into an idle state where new transactions can be
|
|
* initiated by this device or others.
|
|
*/
|
|
void stop(void);
|
|
|
|
/** Acquire exclusive access to this %I2C bus
|
|
*/
|
|
virtual void lock(void);
|
|
|
|
/** Release exclusive access to this %I2C bus
|
|
*/
|
|
virtual void unlock(void);
|
|
|
|
virtual ~I2C()
|
|
{
|
|
// Do nothing
|
|
}
|
|
|
|
#if DEVICE_I2C_ASYNCH
|
|
|
|
/** Start nonblocking %I2C transfer.
|
|
*
|
|
* The %I2C peripheral will begin a transmit and/or receive operation in the background. If only a transmit
|
|
* or receive buffer is specified, only a transmit or receive will be done. If both buffers are specified,
|
|
* first the transmission is done to the given slave address, then the specified number of bytes are received.
|
|
*
|
|
* If you wish to find out when the transfer is done, pass a callback function to the callback argument
|
|
* and set the event argument to the events you wish to receive.
|
|
* This callback will be called when the transfer completes or errors out. Be careful: if you
|
|
* only request the I2C_EVENT_TRANSFER_COMPLETE event, and the transfer errors, the callback will never be called.
|
|
*
|
|
* Internally, the chip vendor may implement this function using either DMA or interrupts.
|
|
*
|
|
* This function locks the deep sleep until any event has occurred.
|
|
*
|
|
* You may not call any other functions on this class instance until the transfer is complete, has errored,
|
|
* or is aborted. Trying to start multiple transfers at once will return an error.
|
|
*
|
|
* @param address 8/11 bit %I2C slave address
|
|
* @param tx_buffer The TX buffer with data to be transferred. May be nullptr if tx_length is 0.
|
|
* @param tx_length The length of TX buffer in bytes. If 0, no transmission is done.
|
|
* @param rx_buffer The RX buffer, which is used for received data. May be nullptr if tx_length is 0.
|
|
* @param rx_length The length of RX buffer in bytes If 0, no reception is done.
|
|
* @param event The logical OR of events to modify. May be I2C_EVENT_ALL, or some combination
|
|
* of the flags I2C_EVENT_ERROR, I2C_EVENT_ERROR_NO_SLAVE, I2C_EVENT_TRANSFER_COMPLETE, or I2C_EVENT_TRANSFER_EARLY_NACK
|
|
* @param callback The event callback function
|
|
* @param repeated Set up for a repeated start. If true, the Mbed processor does not relinquish the bus after
|
|
* this write operation. You may then call write(), read(), or start() again to start another operation.
|
|
*
|
|
* @returns Zero if the transfer has started, or -1 if I2C peripheral is busy
|
|
*/
|
|
int transfer(int address, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, const event_callback_t &callback, int event = I2C_EVENT_TRANSFER_COMPLETE, bool repeated = false);
|
|
|
|
/** Abort the ongoing I2C transfer
|
|
*/
|
|
void abort_transfer();
|
|
|
|
/** Start %I2C transfer and wait until it is complete. Like the transactional API this blocks the current thread,
|
|
* however all work is done in the background and other threads may execute.
|
|
*
|
|
* The %I2C peripheral will begin a transmit and/or receive operation in the background. If only a transmit
|
|
* or receive buffer is specified, only a transmit or receive will be done. If both buffers are specified,
|
|
* first the transmission is done to the given slave address, then the specified number of bytes are received.
|
|
*
|
|
* Internally, the chip vendor may implement this function using either DMA or interrupts.
|
|
*
|
|
* This function locks the deep sleep until it returns.
|
|
*
|
|
* @param address 8/11 bit %I2C slave address
|
|
* @param tx_buffer The TX buffer with data to be transferred. May be nullptr if tx_length is 0.
|
|
* @param tx_length The length of TX buffer in bytes. If 0, no transmission is done.
|
|
* @param rx_buffer The RX buffer, which is used for received data. May be nullptr if tx_length is 0.
|
|
* @param rx_length The length of RX buffer in bytes If 0, no reception is done.
|
|
* @param timeout timeout value. Use @link rtos::Kernel::wait_for_u32_forever to wait forever (the default).
|
|
* @param repeated Set up for a repeated start. If true, the Mbed processor does not relinquish the bus after
|
|
* this operation. You may then call write(), read(), or start() again to start another operation.
|
|
*
|
|
* @returns Result code describing whether the transfer succeeded or not.
|
|
*/
|
|
Result transfer_and_wait(int address, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, rtos::Kernel::Clock::duration_u32 timeout = rtos::Kernel::wait_for_u32_forever, bool repeated = false);
|
|
|
|
#if !defined(DOXYGEN_ONLY)
|
|
protected:
|
|
/** Lock deep sleep only if it is not yet locked */
|
|
void lock_deep_sleep();
|
|
|
|
/** Unlock deep sleep only if it has been locked */
|
|
void unlock_deep_sleep();
|
|
|
|
void irq_handler_asynch(void);
|
|
event_callback_t _callback;
|
|
CThunk<I2C> _irq;
|
|
DMAUsage _usage;
|
|
bool _deep_sleep_locked;
|
|
#endif
|
|
#endif
|
|
|
|
#if !defined(DOXYGEN_ONLY)
|
|
protected:
|
|
|
|
i2c_t _i2c;
|
|
int _hz;
|
|
SingletonPtr<rtos::Mutex> _mutex;
|
|
PinName _sda;
|
|
PinName _scl;
|
|
|
|
private:
|
|
/** Recover I2C bus, when stuck with SDA low
|
|
* @note : Initialization of I2C bus is required after this API.
|
|
*
|
|
* @param sda I2C data line pin
|
|
* @param scl I2C clock line pin
|
|
*
|
|
* @returns
|
|
* '0' - Successfully recovered
|
|
* 'I2C_ERROR_BUS_BUSY' - In case of failure
|
|
*
|
|
*/
|
|
int recover(PinName sda, PinName scl);
|
|
#endif
|
|
};
|
|
|
|
/** @}*/
|
|
|
|
} // namespace mbed
|
|
|
|
#endif
|
|
|
|
#endif
|