diff --git a/firmware/inc/sercom/i2c.h b/firmware/inc/sercom/i2c.h new file mode 100644 index 0000000..93b08c4 --- /dev/null +++ b/firmware/inc/sercom/i2c.h @@ -0,0 +1,110 @@ +/* + * A wrapper around the samd20 i2c functions. Single master only + * Copyright (C) 2015 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef I2C_H +#define I2C_H + +#include "sercom/i2c_master.h" + +/** + * We just declare our single instance here because. Very naughty + */ +struct i2c_master_module i2c_master_instance; + +/** + * I2C Write. + * + * address is the full write address like 0xEE + */ +static void i2c_master_write(uint8_t address, uint8_t* data, uint16_t data_length) +{ + uint32_t timeout = 0; + struct i2c_master_packet packet = { + .address = address >> 1, + .data_length = data_length, + .data = data, + .ten_bit_address = false, + .high_speed = false, + .hs_master_code = 0x0, + }; + + while (i2c_master_write_packet_wait(&i2c_master_instance, &packet) != + STATUS_OK) { + /* Increment timeout counter and check if timed out. */ + if (timeout++ > 1000) { + break; + } + } +} + + +/** + * I2C Read. + * + * address is the full write address like 0xEE + */ +static void i2c_master_read(uint8_t address, uint8_t* data, uint16_t data_length) +{ + uint32_t timeout = 0; + struct i2c_master_packet packet = { + .address = address >> 1, + .data_length = data_length, + .data = data, + .ten_bit_address = false, + .high_speed = false, + .hs_master_code = 0x0, + }; + + while (i2c_master_read_packet_wait(&i2c_master_instance, &packet) != + STATUS_OK) { + /* Increment timeout counter and check if timed out. */ + if (timeout++ > 1000) { + break; + } + } +} + + +/** + * I2C bus master. + */ +static void i2c_init(void) +{ + struct i2c_master_config config_i2c_master; + i2c_master_get_config_defaults(&config_i2c_master); + + /* Config */ + config_i2c_master.buffer_timeout = 10000; + config_i2c_master.baud_rate = 10; + + /* Pinmux */ + config_i2c_master.pinmux_pad0 = PINMUX_PA04D_SERCOM0_PAD0; + config_i2c_master.pinmux_pad1 = PINMUX_PA05D_SERCOM0_PAD1; + + /* Initialize and enable device with config. */ + i2c_master_init(&i2c_master_instance, SERCOM0, &config_i2c_master); + i2c_master_enable(&i2c_master_instance); +} + +#endif /* I2C_H */ diff --git a/firmware/inc/sercom/i2c_common.h b/firmware/inc/sercom/i2c_common.h new file mode 100644 index 0000000..2becddd --- /dev/null +++ b/firmware/inc/sercom/i2c_common.h @@ -0,0 +1,553 @@ +/** + * \file + * + * \brief SAM SERCOM I2C Common Driver + * + * Copyright (C) 2012-2014 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ +#ifndef I2C_COMMON_H_INCLUDED +#define I2C_COMMON_H_INCLUDED + +#include + +/** + * \if (I2C_MASTER_MODE && I2C_SLAVE_MODE) + * \defgroup asfdoc_sam0_sercom_i2c_group SAM D20/D21/R21 I2C Driver (SERCOM I2C) + * \elseif I2C_MASTER_MODE + * \defgroup asfdoc_sam0_sercom_i2c_group SAM D20/D21/R21 I2C Master Mode Driver (SERCOM I2C) + * \elseif I2C_SLAVE_MODE + * \defgroup asfdoc_sam0_sercom_i2c_group SAM D20/D21/R21 I2C Slave Mode Driver (SERCOM I2C) + * \endif + * + * This driver for SAM D20/D21/R21 devices provides an interface for the configuration + * and management of the device's SERCOM I2C module, for the transfer + * of data via an I2C bus. The following driver API modes are covered + * by this manual: + * + * \if I2C_MASTER_MODE + * - Master Mode Polled APIs + * \endif + * \if I2C_MASTER_CALLBACK_MODE + * - Master Mode Callback APIs + * \endif + * \if I2C_SLAVE_MODE + * - Slave Mode Polled APIs + * \endif + * \if I2C_SLAVE_CALLBACK_MODE + * - Slave Mode Callback APIs + * \endif + * + * The following peripheral is used by this module: + * + * - SERCOM (Serial Communication Interface) + * + * The outline of this documentation is as follows: + * - \ref asfdoc_sam0_sercom_i2c_prerequisites + * - \ref asfdoc_sam0_sercom_i2c_overview + * - \ref asfdoc_sam0_sercom_i2c_special_considerations + * - \ref asfdoc_sam0_sercom_i2c_extra + * - \ref asfdoc_sam0_sercom_i2c_examples + * - \ref asfdoc_sam0_sercom_i2c_api_overview + * + * \section asfdoc_sam0_sercom_i2c_prerequisites Prerequisites + * There are no prerequisites. + * + * \section asfdoc_sam0_sercom_i2c_overview Module Overview + * The outline of this section is as follows: + * - \ref asfdoc_sam0_sercom_i2c_module_features + * - \ref asfdoc_sam0_sercom_i2c_functional_desc + * - \ref asfdoc_sam0_sercom_i2c_bus_topology + * - \ref asfdoc_sam0_sercom_i2c_transactions + * - \ref asfdoc_sam0_sercom_i2c_multi_master + * - \ref asfdoc_sam0_sercom_i2c_bus_states + * - \ref asfdoc_sam0_sercom_i2c_timeout + * - \ref asfdoc_sam0_sercom_i2c_sleep_modes + * + * \subsection asfdoc_sam0_sercom_i2c_module_features Driver Feature Macro Definition + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Driver Feature MacroSupported devices
FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEEDSAM D21/R21
FEATURE_I2C_10_BIT_ADDRESSSAM D21/R21
FEATURE_I2C_SCL_STRETCH_MODESAM D21/R21
FEATURE_I2C_SCL_EXTEND_TIMEOUTSAM D21/R21
+ * \note The specific features are only available in the driver when the + * selected device supports those features. + * + * \subsection asfdoc_sam0_sercom_i2c_functional_desc Functional Description + * The I2C provides a simple two-wire bidirectional bus consisting of a + * wired-AND type serial clock line (SCL) and a wired-AND type serial data line + * (SDA). + * + * The I2C bus provides a simple, but efficient method of interconnecting + * multiple master and slave devices. An arbitration mechanism is provided for + * resolving bus ownership between masters, as only one master device may own + * the bus at any given time. The arbitration mechanism relies on the wired-AND + * connections to avoid bus drivers short-circuiting. + * + * A unique address is assigned to all slave devices connected to the bus. A + * device can contain both master and slave logic, and can emulate multiple + * slave devices by responding to more than one address. + * + * \subsection asfdoc_sam0_sercom_i2c_bus_topology Bus Topology + * The I2C bus topology is illustrated in + * \ref asfdoc_sam0_sercom_i2c_bus_topology_figure "the figure below". The pull-up + * resistors (Rs) will provide a high level on the bus lines when none of the + * I2C devices are driving the bus. These are optional, and can be + * replaced with a constant current source. + * + * \anchor asfdoc_sam0_sercom_i2c_bus_topology_figure + * \image html bus_topology.svg "I2C bus topology" width=100% + * + * \subsection asfdoc_sam0_sercom_i2c_transactions Transactions + * The I2C standard defines three fundamental transaction formats: + * - Master Write + * - The master transmits data packets to the slave after addressing it + * - Master Read + * - The slave transmits data packets to the master after being addressed + * - Combined Read/Write + * - A combined transaction consists of several write and read transactions + * + * A data transfer starts with the master issuing a \b Start condition on the + * bus, followed by the address of the slave together with a bit to indicate + * whether the master wants to read from or write to the slave. + * The addressed slave must respond to this by sending an \b ACK back to the + * master. + * + * After this, data packets are sent from the master or slave, according to the + * read/write bit. Each packet must be acknowledged (ACK) or not + * acknowledged (NACK) by the receiver. + * + * If a slave responds with a NACK, the master must assume that the slave + * cannot receive any more data and cancel the write operation. + * + * The master completes a transaction by issuing a \b Stop condition. + * + * A master can issue multiple \b Start conditions during a transaction; this + * is then called a \b Repeated \b Start condition. + * + * \subsubsection asfdoc_sam0_sercom_i2c_address_packets Address Packets + * The slave address consists of seven bits. The 8th bit in the transfer + * determines the data direction (read or write). An address packet always + * succeeds a \b Start or \b Repeated \b Start condition. The 8th bit is handled + * in the driver, and the user will only have to provide the 7 bit address. + * + * \subsubsection asfdoc_sam0_sercom_i2c_data_packets Data Packets + * Data packets are nine bits long, consisting of one 8-bit data byte, and an + * acknowledgement bit. Data packets follow either an address packet or another + * data packet on the bus. + * + * \subsubsection asfdoc_sam0_sercom_i2c_trans_examples Transaction Examples + * The gray bits in the following examples are sent from master to slave, and + * the white bits are sent from slave to master. + * Example of a read transaction is shown in + * \ref asfdoc_sam0_sercom_i2c_trans_examples_i2c_read "the figure below". Here, the + * master first issues a \b Start condition and gets ownership of the bus. An + * address packet with the direction flag set to read is then sent and + * acknowledged by the slave. Then the slave sends one data packet which is + * acknowledged by the master. The slave sends another packet, which is not + * acknowledged by the master and indicates that the master will terminate the + * transaction. In the end, the transaction is terminated by the master issuing + * a \b Stop condition. + * + * \anchor asfdoc_sam0_sercom_i2c_trans_examples_i2c_read + * \image html i2c_read.svg "I2C Packet Read" width=100% + * + * Example of a write transaction is shown in + * \ref asfdoc_sam0_sercom_i2c_trans_examples_i2c_write "the figure below". Here, the + * master first issues a \b Start condition and gets ownership of the bus. An + * address packet with the dir flag set to write is then sent and acknowledged + * by the slave. Then the master sends two data packets, each acknowledged by + * the slave. In the end, the transaction is terminated by the master issuing + * a \b Stop condition. + * + * \anchor asfdoc_sam0_sercom_i2c_trans_examples_i2c_write + * \image html i2c_write.svg "I2C Packet Write" width=100% + * + * \subsubsection asfdoc_sam0_sercom_i2c_packet_timeout Packet Timeout + * When a master sends an I2C packet, there is no way of + * being sure that a slave will acknowledge the packet. To avoid stalling the + * device forever while waiting for an acknowledge, a user selectable timeout + * is provided in the \ref i2c_master_config struct which + * lets the driver exit a read or write operation after the specified time. + * The function will then return the STATUS_ERR_TIMEOUT flag. + * + * This is also the case for the slave when using the functions postfixed + * \c _wait. + * + * The time before the timeout occurs, will be the same as + * for \ref asfdoc_sam0_sercom_i2c_unknown_bus_timeout "unknown bus state" timeout. + * + * \subsubsection asfdoc_sam0_sercom_i2c_repeated_start Repeated Start + * To issue a \b Repeated \b Start, the functions postfixed \c _no_stop must be + * used. + * These functions will not send a \b Stop condition when the transfer is done, + * thus the next transfer will start with a \b Repeated \b Start. To end the + * transaction, the functions without the \c _no_stop postfix must be used + * for the last read/write. + * + * \subsection asfdoc_sam0_sercom_i2c_multi_master Multi Master + * In a multi master environment, arbitration of the bus is important, as only + * one master can own the bus at any point. + * + * \subsubsection asfdoc_sam0_sercom_i2c_arbitration Arbitration + * + * \par Clock stretching + * The serial clock line is always driven by a master device. However, all + * devices connected to the bus are allowed stretch the low period of the clock + * to slow down the overall clock frequency or to insert wait states while + * processing data. + * Both master and slave can randomly stretch the clock, which will force the + * other device into a wait-state until the clock line goes high again. + * + * \par Arbitration on the data line + * If two masters start transmitting at the same time, they will both transmit + * until one master detects that the other master is pulling the data line low. + * When this is detected, the master not pulling the line low, will stop the + * transmission and wait until the bus is idle. + * As it is the master trying to contact the slave with the lowest address that + * will get the bus ownership, this will create an arbitration scheme always + * prioritizing the slaves with the lowest address in case of a bus collision. + * + * \subsubsection asfdoc_sam0_sercom_i2c_clock_sync Clock Synchronization + * In situations where more than one master is trying to control the bus clock + * line at the same time, a clock synchronization algorithm based on the same + * principles used for clock stretching is necessary. + * + * + * \subsection asfdoc_sam0_sercom_i2c_bus_states Bus States + * As the I2C bus is limited to one transaction at the time, + * a master that wants to perform a bus transaction must wait until the bus is + * free. + * Because of this, it is necessary for all masters in a multi-master system to + * know the current status of the bus to be able to avoid conflicts and to + * ensure data integrity. + * \li \b IDLE No activity on the bus (between a \b Stop and a new \b Start + * condition) + * \li \b OWNER If the master initiates a transaction successfully + * \li \b BUSY If another master is driving the bus + * \li \b UNKNOWN If the master has recently been enabled or connected to + * the bus. Is forced to \b IDLE after given + * \ref asfdoc_sam0_sercom_i2c_unknown_bus_timeout "timeout" when + * the master module is enabled. + * + * The bus state diagram can be seen in + * \ref asfdoc_sam0_sercom_i2c_bus_states_figure "the figure below". + * \li S: Start condition + * \li P: Stop condition + * \li Sr: Repeated start condition + * \anchor asfdoc_sam0_sercom_i2c_bus_states_figure + * \image html bus_state_diagram.svg "I2C bus state diagram" width=100% + * + * \subsection asfdoc_sam0_sercom_i2c_timeout Bus Timing + * Inactive bus timeout for the master and SDA hold time is configurable in the + * drivers. + * + * \subsubsection asfdoc_sam0_sercom_i2c_unknown_bus_timeout Unknown Bus State Timeout + * When a master is enabled or connected to the bus, the bus state will be + * unknown until either a given timeout or a stop command has occurred. The + * timeout is configurable in the \ref i2c_master_config struct. + * The timeout time will depend on toolchain and optimization level used, as + * the timeout is a loop incrementing a value until it reaches the specified + * timeout value. + * + * \subsubsection sda_hold SDA Hold Timeout + * When using the I2C in slave mode, it will be important to + * set a SDA hold time which assures that the master will be able to pick up + * the bit sent from the slave. The SDA hold time makes sure that this is the + * case by holding the data line low for a given period after the negative edge + * on the clock. + * + * The SDA hold time is also available for the master driver, but is not a + * necessity. + * + * \subsection asfdoc_sam0_sercom_i2c_sleep_modes Operation in Sleep Modes + * The I2C module can operate in all sleep modes by setting + * the run_in_standby boolean in the \ref i2c_master_config or + * \ref i2c_slave_config struct. + * The operation in slave and master mode is shown in + * \ref asfdoc_sam0_sercom_i2c_sleep_modes_table "the table below". + * + * \anchor asfdoc_sam0_sercom_i2c_sleep_modes_table + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
I2C standby operations
Run in standbySlaveMaster
falseDisabled, all reception is droppedGCLK disabled when master is idle
trueWake on address match when enabledGCLK enabled while in sleep modes
+ * + * + * \section asfdoc_sam0_sercom_i2c_special_considerations Special Considerations + * + * \if (I2C_MASTER_CALLBACK_MODE || I2C_SLAVE_CALLBACK_MODE) + * \subsection asfdoc_sam0_sercom_i2c_common_interrupt Interrupt-Driven Operation + * While an interrupt-driven operation is in progress, subsequent calls to a + * write or read operation will return the STATUS_BUSY flag, indicating that + * only one operation is allowed at any given time. + * + * To check if another transmission can be initiated, the user can either call + * another transfer operation, or use the + * \ref i2c_master_get_job_status/\ref i2c_slave_get_job_status functions + * depending on mode. + * + * If the user would like to get callback from operations while using the + * interrupt-driven driver, the callback must be registered and then enabled + * using the "register_callback" and "enable_callback" functions. + * \else + * There are no special considerations for this driver for the APIs listed in + * this document. + * \endif + * + * \section asfdoc_sam0_sercom_i2c_extra Extra Information + * For extra information see \ref asfdoc_sam0_sercom_i2c_extra_info_page. + * This includes: + * - \ref asfdoc_sam0_sercom_i2c_acronyms + * - \ref asfdoc_sam0_sercom_i2c_extra_dependencies + * - \ref asfdoc_sam0_sercom_i2c_extra_errata + * - \ref asfdoc_sam0_sercom_i2c_extra_history + * + * \section asfdoc_sam0_sercom_i2c_examples Examples + * + * For a list of examples related to this driver, see + * \ref asfdoc_sam0_sercom_i2c_exqsg. + * + * \section asfdoc_sam0_sercom_i2c_api_overview API Overview + * @{ + */ + +/** + * \name Driver feature definition + * Define SERCOME I2C driver features set according to different device family. + * + * \note The high speed mode and 10-bit address feature are not + * supported by the driver now. + * @{ + */ +#if (SAMD21) || (SAMR21) || defined(__DOXYGEN__) +/** Fast mode plus and high speed support */ +# define FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED +/** 10 bit address support */ +# define FEATURE_I2C_10_BIT_ADDRESS +/** SCL stretch mode support */ +# define FEATURE_I2C_SCL_STRETCH_MODE +/** SCL extend timeout support */ +# define FEATURE_I2C_SCL_EXTEND_TIMEOUT +# define FEATURE_I2C_DMA_SUPPORT +#endif +/*@}*/ + +/** \brief Transfer direction + * + * For master: transfer direction or setting direction bit in address. + * For slave: direction of request from master. + */ +enum i2c_transfer_direction { + I2C_TRANSFER_WRITE = 0, + I2C_TRANSFER_READ = 1, +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** + * \page asfdoc_sam0_sercom_i2c_extra_info_page Extra Information for SERCOM I2C Driver + * + * \section asfdoc_sam0_sercom_i2c_acronyms Acronyms + * \ref asfdoc_sam0_sercom_i2c_acronyms_table "Below" is a table listing the acronyms + * used in this module, along with their intended meanings. + * + * \anchor asfdoc_sam0_sercom_i2c_acronyms_table + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Acronyms
AcronymDescription
SDASerial Data Line
SCLSerial Clock Line
SERCOMSerial Communication Interface
DMADirect Memory Access
+ * + * \section asfdoc_sam0_sercom_i2c_extra_dependencies Dependencies + * The I2C driver has the following dependencies: + * \li \ref asfdoc_sam0_system_pinmux_group "System Pin Multiplexer Driver" + * + * + * \section asfdoc_sam0_sercom_i2c_extra_errata Errata + * There are no errata related to this driver. + * + * \section asfdoc_sam0_sercom_i2c_extra_history Module History + * \ref asfdoc_sam0_sercom_i2c_extra_history_table "Below" is an overview of the + * module history, detailing enhancements and fixes made to the module since + * its first release. The current version of this corresponds to the newest + * version listed in + * \ref asfdoc_sam0_sercom_i2c_extra_history_table "the table below". + * + * \anchor asfdoc_sam0_sercom_i2c_extra_history_table + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Module History
Changelog
+ * \li Added 10-bit addressing and high speed support in SAM D21. + * \li Seperate structure i2c_packet into i2c_master_packet and i2c_slave packet. + *
+ * \li Added support for SCL stretch and extended timeout hardware features in SAM D21. + * \li Added fast mode plus support in SAM D21. + *
Fixed incorrect logical mask for determining if a bus error has + * occurred in I2C Slave mode. + *
Initial Release
+ */ + +/** + * \page asfdoc_sam0_sercom_i2c_exqsg Examples for SERCOM I2C Driver + * + * This is a list of the available Quick Start guides (QSGs) and example + * applications for \ref asfdoc_sam0_sercom_i2c_group. QSGs are simple examples with + * step-by-step instructions to configure and use this driver in a selection of + * use cases. Note that QSGs can be compiled as a standalone application or be + * added to the user application. + * + * \if I2C_MASTER_MODE + * - \subpage asfdoc_sam0_sercom_i2c_master_basic_use_case "Quick Start Guide for the I2C Master module - Basic Use Case" + * \endif + * \if I2C_MASTER_CALLBACK_MODE + * - \subpage asfdoc_sam0_sercom_i2c_master_callback_use_case "Quick Start Guide for the I2C Master module - Callback Use Case" + * - \subpage asfdoc_sam0_sercom_i2c_master_dma_use_case "Quick Start Guide for the I2C Master module - DMA Use Case" + * \endif + * \if I2C_SLAVE_MODE + * - \subpage asfdoc_sam0_sercom_i2c_slave_basic_use_case "Quick Start Guide for the I2C Slave module - Basic Use Case" + * \endif + * \if I2C_SLAVE_CALLBACK_MODE + * - \subpage asfdoc_sam0_sercom_i2c_slave_callback_use_case "Quick Start Guide for the I2C Slave module - Callback Use Case" + * - \subpage asfdoc_sam0_sercom_i2c_slave_dma_use_case "Quick Start Guide for the I2C Slave module - DMA Use Case" + * \endif + * + * \page asfdoc_sam0_sercom_i2c_document_revision_history Document Revision History + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Doc. Rev. + * Date + * Comments + *
E03/2014Added SAM R21 support.
D03/2014Added 10-bit addressing and high speed support in SAM D21.
C01/2014Added the SAM D21 to the application note.
B06/2013Corrected documentation typos. Updated I2C Bus State Diagram.
A06/2013Initial release
+ */ + +#endif /* I2C_COMMON_H_INCLUDED */ diff --git a/firmware/inc/sercom/i2c_master.h b/firmware/inc/sercom/i2c_master.h new file mode 100644 index 0000000..beac0e5 --- /dev/null +++ b/firmware/inc/sercom/i2c_master.h @@ -0,0 +1,591 @@ +/** + * \file + * + * \brief SAM SERCOM I2C Master Driver + * + * Copyright (C) 2012-2014 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ + +#ifndef I2C_MASTER_H_INCLUDED +#define I2C_MASTER_H_INCLUDED + +#include "sercom/i2c_common.h" +#include +#include + +#if I2C_MASTER_CALLBACK_MODE == true +# include +#endif + +#ifndef PINMUX_DEFAULT +# define PINMUX_DEFAULT 0 +#endif + +#include +#define Assert assert +/** + * \addtogroup asfdoc_sam0_sercom_i2c_group + * + * @{ + */ + +/** + * \brief I2C master packet for read/write + * + * Structure to be used when transferring I2C master packets. + */ +struct i2c_master_packet { + /** Address to slave device */ + uint16_t address; + /** Length of data array */ + uint16_t data_length; + /** Data array containing all data to be transferred */ + uint8_t *data; + /** Use 10 bit addressing. Set to false if the feature is not supported by the device */ + bool ten_bit_address; + /** Use high speed transfer. Set to false if the feature is not supported by the device */ + bool high_speed; + /** High speed mode master code (0000 1XXX), valid when high_speed is true */ + uint8_t hs_master_code; +}; + +/** \brief Interrupt flags + * + * Flags used when reading or setting interrupt flags. + */ +enum i2c_master_interrupt_flag { + /** Interrupt flag used for write */ + I2C_MASTER_INTERRUPT_WRITE = 0, + /** Interrupt flag used for read */ + I2C_MASTER_INTERRUPT_READ = 1, +}; + +/** + * \brief Values for hold time after start bit. + * + * Values for the possible I2C master mode SDA internal hold times after start + * bit has been sent. + */ +enum i2c_master_start_hold_time { + /** Internal SDA hold time disabled */ + I2C_MASTER_START_HOLD_TIME_DISABLED = SERCOM_I2CM_CTRLA_SDAHOLD(0), + /** Internal SDA hold time 50ns-100ns */ + I2C_MASTER_START_HOLD_TIME_50NS_100NS = SERCOM_I2CM_CTRLA_SDAHOLD(1), + /** Internal SDA hold time 300ns-600ns */ + I2C_MASTER_START_HOLD_TIME_300NS_600NS = SERCOM_I2CM_CTRLA_SDAHOLD(2), + /** Internal SDA hold time 400ns-800ns */ + I2C_MASTER_START_HOLD_TIME_400NS_800NS = SERCOM_I2CM_CTRLA_SDAHOLD(3), +}; + +/** + * \ brief Values for inactive bus time-out. + * + * If the inactive bus time-out is enabled and the bus is inactive for + * longer than the time-out setting, the bus state logic will be set to idle. + */ +enum i2c_master_inactive_timeout { + /** Inactive bus time-out disabled */ + I2C_MASTER_INACTIVE_TIMEOUT_DISABLED = SERCOM_I2CM_CTRLA_INACTOUT(0), + /** Inactive bus time-out 5-6 SCL cycle time-out (50-60us) */ + I2C_MASTER_INACTIVE_TIMEOUT_55US = SERCOM_I2CM_CTRLA_INACTOUT(1), + /** Inactive bus time-out 10-11 SCL cycle time-out (100-110us) */ + I2C_MASTER_INACTIVE_TIMEOUT_105US = SERCOM_I2CM_CTRLA_INACTOUT(2), + /** Inactive bus time-out 20-21 SCL cycle time-out (200-210us) */ + I2C_MASTER_INACTIVE_TIMEOUT_205US = SERCOM_I2CM_CTRLA_INACTOUT(3), +}; + +/** + * \brief I2C frequencies + * + * Values for I2C speeds supported by the module. The driver + * will also support setting any other value, in which case set + * the value in the \ref i2c_master_config at desired value divided by 1000. + * + * Example: If 10kHz operation is required, give baud_rate in the configuration + * structure the value 10. + */ +enum i2c_master_baud_rate { + /** Baud rate at 100kHz (Standard-mode) */ + I2C_MASTER_BAUD_RATE_100KHZ = 100, + /** Baud rate at 400kHz (Fast-mode) */ + I2C_MASTER_BAUD_RATE_400KHZ = 400, +#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED + /** Baud rate at 1MHz (Fast-mode Plus) */ + I2C_MASTER_BAUD_RATE_1000KHZ = 1000, + /** Baud rate at 3.4MHz (High-speed mode) */ + I2C_MASTER_BAUD_RATE_3400KHZ = 3400, +#endif +}; + +#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED +/** + * \brief Enum for the transfer speed + * + * Enum for the transfer speed. + */ +enum i2c_master_transfer_speed { + /** Standard-mode (Sm) up to 100 kHz and Fast-mode (Fm) up to 400 kHz */ + I2C_MASTER_SPEED_STANDARD_AND_FAST = SERCOM_I2CM_CTRLA_SPEED(0), + /** Fast-mode Plus (Fm+) up to 1 MHz */ + I2C_MASTER_SPEED_FAST_MODE_PLUS = SERCOM_I2CM_CTRLA_SPEED(1), + /** High-speed mode (Hs-mode) up to 3.4 MHz */ + I2C_MASTER_SPEED_HIGH_SPEED = SERCOM_I2CM_CTRLA_SPEED(2), +}; +#endif + +#if I2C_MASTER_CALLBACK_MODE == true +/** + * \brief Callback types + * + * The available callback types for the I2C master module. + */ +enum i2c_master_callback { + /** Callback for packet write complete */ + I2C_MASTER_CALLBACK_WRITE_COMPLETE = 0, + /** Callback for packet read complete */ + I2C_MASTER_CALLBACK_READ_COMPLETE = 1, + /** Callback for error */ + I2C_MASTER_CALLBACK_ERROR = 2, +# if !defined(__DOXYGEN__) + /** Total number of callbacks */ + _I2C_MASTER_CALLBACK_N = 3, +# endif +}; + +# if !defined(__DOXYGEN__) +/* Prototype for software module. */ +struct i2c_master_module; + +typedef void (*i2c_master_callback_t)( + struct i2c_master_module *const module); +# endif +#endif + +/** + * \brief SERCOM I2C Master driver software device instance structure. + * + * SERCOM I2C Master driver software instance structure, used to + * retain software state information of an associated hardware module instance. + * + * \note The fields of this structure should not be altered by the user + * application; they are reserved for module-internal use only. + */ +struct i2c_master_module { +#if !defined(__DOXYGEN__) + /** Hardware instance initialized for the struct */ + Sercom *hw; + /** Module lock */ + volatile bool locked; + /** Unknown bus state timeout */ + uint16_t unknown_bus_state_timeout; + /** Buffer write timeout value */ + uint16_t buffer_timeout; + /** If true, stop condition will be sent after a read/write */ + bool send_stop; +# if I2C_MASTER_CALLBACK_MODE == true + /** Pointers to callback functions */ + volatile i2c_master_callback_t callbacks[_I2C_MASTER_CALLBACK_N]; + /** Mask for registered callbacks */ + volatile uint8_t registered_callback; + /** Mask for enabled callbacks */ + volatile uint8_t enabled_callback; + /** The total number of bytes to transfer */ + volatile uint16_t buffer_length; + /** + * Counter used for bytes left to send in write and to count number of + * obtained bytes in read + */ + volatile uint16_t buffer_remaining; + /** Data buffer for packet write and read */ + volatile uint8_t *buffer; + /** Save direction of async request. 1 = read, 0 = write */ + volatile enum i2c_transfer_direction transfer_direction; + /** Status for status read back in error callback */ + volatile enum status_code status; +# endif +#endif +}; + +/** + * \brief Configuration structure for the I2C Master device + * + * This is the configuration structure for the I2C Master device. It + * is used as an argument for \ref i2c_master_init to provide the desired + * configurations for the module. The structure should be initialized using the + * \ref i2c_master_get_config_defaults . + */ +struct i2c_master_config { + /** Baud rate (in KHZ) for I2C operations in + * standard-mode, Fast-mode and Fast-mode Plus Transfers, + * \ref i2c_master_baud_rate */ + uint32_t baud_rate; +#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED + /** Baud rate (in KHz) for I2C operations in + * High-speed mode, \ref i2c_master_baud_rate */ + uint32_t baud_rate_high_speed; + /** Transfer speed mode */ + enum i2c_master_transfer_speed transfer_speed; +#endif + /** GCLK generator to use as clock source */ + enum gclk_generator generator_source; + /** Bus hold time after start signal on data line */ + enum i2c_master_start_hold_time start_hold_time; + /** Unknown bus state \ref asfdoc_sam0_sercom_i2c_unknown_bus_timeout "timeout" */ + uint16_t unknown_bus_state_timeout; + /** Timeout for packet write to wait for slave */ + uint16_t buffer_timeout; + /** Set to keep module active in sleep modes */ + bool run_in_standby; + /** PAD0 (SDA) pinmux */ + uint32_t pinmux_pad0; + /** PAD1 (SCL) pinmux */ + uint32_t pinmux_pad1; + /** Set to enable SCL low time-out */ + bool scl_low_timeout; + /** Inactive bus time out */ + enum i2c_master_inactive_timeout inactive_timeout; +#ifdef FEATURE_I2C_SCL_STRETCH_MODE + /** Set to enable SCL stretch only after ACK bit (required for high speed) */ + bool scl_stretch_only_after_ack_bit; +#endif +#ifdef FEATURE_I2C_SCL_EXTEND_TIMEOUT + /** Set to enable slave SCL low extend time-out */ + bool slave_scl_low_extend_timeout; + /** Set to enable maser SCL low extend time-out */ + bool master_scl_low_extend_timeout; +#endif +}; + +/** + * \name Lock/Unlock + * @{ + */ + +/** + * \brief Attempt to get lock on driver instance + * + * This function checks the instance's lock, which indicates whether or not it + * is currently in use, and sets the lock if it was not already set. + * + * The purpose of this is to enable exclusive access to driver instances, so + * that, e.g., transactions by different services will not interfere with each + * other. + * + * \param[in,out] module Pointer to the driver instance to lock. + * + * \retval STATUS_OK if the module was locked. + * \retval STATUS_BUSY if the module was already locked. + */ +static inline enum status_code i2c_master_lock( + struct i2c_master_module *const module) +{ + enum status_code status; + + system_interrupt_enter_critical_section(); + + if (module->locked) { + status = STATUS_BUSY; + } else { + module->locked = true; + status = STATUS_OK; + } + + system_interrupt_leave_critical_section(); + + return status; +} + +/** + * \brief Unlock driver instance + * + * This function clears the instance lock, indicating that it is available for + * use. + * + * \param[in,out] module Pointer to the driver instance to lock. + * + * \retval STATUS_OK if the module was locked. + * \retval STATUS_BUSY if the module was already locked. + */ +static inline void i2c_master_unlock(struct i2c_master_module *const module) +{ + module->locked = false; +} + +/** @} */ + +/** + * \name Configuration and Initialization + * @{ + */ + +/** + * \brief Returns the synchronization status of the module + * + * Returns the synchronization status of the module. + * + * \param[in] module Pointer to software module structure + * + * \return Status of the synchronization. + * \retval true Module is busy synchronizing + * \retval false Module is not synchronizing + */ +static inline bool i2c_master_is_syncing ( + const struct i2c_master_module *const module) +{ + /* Sanity check. */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_hw = &(module->hw->I2CM); + +#if defined(FEATURE_SERCOM_SYNCBUSY_SCHEME_VERSION_1) + return (i2c_hw->STATUS.reg & SERCOM_I2CM_STATUS_SYNCBUSY); +#elif defined(FEATURE_SERCOM_SYNCBUSY_SCHEME_VERSION_2) + return (i2c_hw->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK); +#else +# error Unknown SERCOM SYNCBUSY scheme! +#endif +} + +#if !defined(__DOXYGEN__) +/** + * \internal + * Wait for hardware module to sync + * + * \param[in] module Pointer to software module structure + */ +static void _i2c_master_wait_for_sync( + const struct i2c_master_module *const module) +{ + /* Sanity check. */ + Assert(module); + + while (i2c_master_is_syncing(module)) { + /* Wait for I2C module to sync. */ + } +} +#endif + +/** + * \brief Gets the I2C master default configurations + * + * Use to initialize the configuration structure to known default values. + * + * The default configuration is as follows: + * - Baudrate 100kHz + * - GCLK generator 0 + * - Do not run in standby + * - Start bit hold time 300ns-600ns + * - Buffer timeout = 65535 + * - Unknown bus status timeout = 65535 + * - Do not run in standby + * - PINMUX_DEFAULT for SERCOM pads + * + * Those default configuration only availale if the device supports it: + * - High speed baudrate 3.4MHz + * - Standard-mode and Fast-mode transfer speed + * - SCL stretch disabled + * - slave SCL low extend time-out disabled + * - maser SCL low extend time-out disabled + * + * \param[out] config Pointer to configuration structure to be initiated + */ +static inline void i2c_master_get_config_defaults( + struct i2c_master_config *const config) +{ + /*Sanity check argument. */ + Assert(config); + config->baud_rate = I2C_MASTER_BAUD_RATE_100KHZ; +#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED + config->baud_rate_high_speed = I2C_MASTER_BAUD_RATE_3400KHZ; + config->transfer_speed = I2C_MASTER_SPEED_STANDARD_AND_FAST; +#endif + config->generator_source = GCLK_GENERATOR_0; + config->run_in_standby = false; + config->start_hold_time = I2C_MASTER_START_HOLD_TIME_300NS_600NS; + config->buffer_timeout = 65535; + config->unknown_bus_state_timeout = 65535; + config->pinmux_pad0 = PINMUX_DEFAULT; + config->pinmux_pad1 = PINMUX_DEFAULT; + config->scl_low_timeout = false; + config->inactive_timeout = I2C_MASTER_INACTIVE_TIMEOUT_DISABLED; +#ifdef FEATURE_I2C_SCL_STRETCH_MODE + config->scl_stretch_only_after_ack_bit = false; +#endif +#ifdef FEATURE_I2C_SCL_EXTEND_TIMEOUT + config->slave_scl_low_extend_timeout = false; + config->master_scl_low_extend_timeout = false; +#endif +} + +enum status_code i2c_master_init( + struct i2c_master_module *const module, + Sercom *const hw, + const struct i2c_master_config *const config); + +/** + * \brief Enables the I2C module + * + * Enables the requested I2C module and set the bus state to IDLE + * after the specified \ref asfdoc_sam0_sercom_i2c_timeout "timeout" period if no + * stop bit is detected. + * + * \param[in] module Pointer to the software module struct + */ +static inline void i2c_master_enable( + const struct i2c_master_module *const module) +{ + /* Sanity check of arguments. */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Timeout counter used to force bus state. */ + uint32_t timeout_counter = 0; + + /* Wait for module to sync. */ + _i2c_master_wait_for_sync(module); + + /* Enable module. */ + i2c_module->CTRLA.reg |= SERCOM_I2CM_CTRLA_ENABLE; + +#if I2C_MASTER_CALLBACK_MODE == true + /* Enable module interrupts */ + system_interrupt_enable(_sercom_get_interrupt_vector(module->hw)); +#endif + /* Start timeout if bus state is unknown. */ + while (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(1))) { + timeout_counter++; + if(timeout_counter >= (module->unknown_bus_state_timeout)) { + /* Timeout, force bus state to idle. */ + i2c_module->STATUS.reg = SERCOM_I2CM_STATUS_BUSSTATE(1); + /* Workaround #1 */ + return; + } + } +} + +/** + * \brief Disable the I2C module + * + * Disables the requested I2C module. + * + * \param[in] module Pointer to the software module struct + */ +static inline void i2c_master_disable( + const struct i2c_master_module *const module) +{ + /* Sanity check of arguments. */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Wait for module to sync. */ + _i2c_master_wait_for_sync(module); + + /* Disable module. */ + i2c_module->CTRLA.reg &= ~SERCOM_I2CM_CTRLA_ENABLE; + +#if I2C_MASTER_CALLBACK_MODE == true + /* Disable module interrupts */ + system_interrupt_disable(_sercom_get_interrupt_vector(module->hw)); +#endif +} + +void i2c_master_reset(struct i2c_master_module *const module); + +/** @} */ + +/** +* \name Read and Write +* @{ +*/ + +enum status_code i2c_master_read_packet_wait( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet); + +enum status_code i2c_master_read_packet_wait_no_stop( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet); + +enum status_code i2c_master_write_packet_wait( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet); + +enum status_code i2c_master_write_packet_wait_no_stop( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet); + +void i2c_master_send_stop(struct i2c_master_module *const module); + +/** @} */ + +#ifdef FEATURE_I2C_DMA_SUPPORT +/** +* \name SERCOM I2C master with DMA interfaces +* @{ +*/ + +/** + * \brief Set I2C for DMA transfer with slave address and transfer size. + * + * This function will set the slave address, transfer size and enable the auto transfer + * mode for DMA. + * + * \param[in,out] module Pointer to the driver instance to lock. + * \param[in] addr I2C slave address + * \param[in] length I2C transfer length with DMA. + * \param[in] direction I2C transfer direction + * + */ +static inline void i2c_master_dma_set_transfer(struct i2c_master_module *const module, + uint16_t addr, uint8_t length, enum i2c_transfer_direction direction) +{ + module->hw->I2CM.ADDR.reg = + SERCOM_I2CM_ADDR_ADDR(addr<<1) | + SERCOM_I2CM_ADDR_LENEN | + SERCOM_I2CM_ADDR_LEN(length) | + direction; +} + +#endif + +#endif /* I2C_MASTER_H_INCLUDED */ diff --git a/firmware/inc/system/events.h b/firmware/inc/system/events.h index 6514ece..4161cfa 100644 --- a/firmware/inc/system/events.h +++ b/firmware/inc/system/events.h @@ -178,7 +178,11 @@ enum status_code { STATUS_BUSY, STATUS_ERR_INVALID_ARG, STATUS_ERR_OVERFLOW, - STATUS_ERR_DENIED + STATUS_ERR_DENIED, + STATUS_ERR_BAUDRATE_UNAVAILABLE, + STATUS_ERR_PACKET_COLLISION, + STATUS_ERR_BAD_ADDRESS, +STATUS_ERR_TIMEOUT }; diff --git a/firmware/src/sercom/i2c_master.c b/firmware/src/sercom/i2c_master.c new file mode 100644 index 0000000..ff8dbdd --- /dev/null +++ b/firmware/src/sercom/i2c_master.c @@ -0,0 +1,739 @@ +/** + * \file + * + * \brief SAM D20 I2C Master Driver + * + * Copyright (C) 2012-2014 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ + +#include "sercom/i2c_master.h" +#include "system/pinmux.h" + +#if I2C_MASTER_CALLBACK_MODE == true +# include "i2c_master_interrupt.h" +#endif + +#if !defined(__DOXYGEN__) + +/** + * \internal Sets configurations to module + * + * \param[out] module Pointer to software module structure. + * \param[in] config Configuration structure with configurations to set. + * + * \return Status of setting configuration. + * \retval STATUS_OK If module was configured correctly + * \retval STATUS_ERR_ALREADY_INITIALIZED If setting other GCLK generator than + * previously set + * \retval STATUS_ERR_BAUDRATE_UNAVAILABLE If given baud rate is not compatible + * with set GCLK frequency + */ +static enum status_code _i2c_master_set_config( + struct i2c_master_module *const module, + const struct i2c_master_config *const config) +{ + /* Sanity check arguments. */ + Assert(module); + Assert(module->hw); + Assert(config); + + /* Temporary variables. */ + uint32_t tmp_ctrla; + int32_t tmp_baud; + enum status_code tmp_status_code = STATUS_OK; + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + Sercom *const sercom_hw = module->hw; + + uint8_t sercom_index = _sercom_get_sercom_inst_index(sercom_hw); + + /* Pin configuration */ + + uint32_t pad0 = config->pinmux_pad0; + uint32_t pad1 = config->pinmux_pad1; + + /* SERCOM PAD0 - SDA */ + if (pad0 == PINMUX_DEFAULT) { + pad0 = _sercom_get_default_pad(sercom_hw, 0); + } + system_pinmux_pin_set_config(pad0 >> 16, + pad0 & 0xFFFF, + SYSTEM_PINMUX_PIN_DIR_OUTPUT_WITH_READBACK, + SYSTEM_PINMUX_PIN_PULL_UP, + false); + + /* SERCOM PAD1 - SCL */ + if (pad1 == PINMUX_DEFAULT) { + pad1 = _sercom_get_default_pad(sercom_hw, 1); + } + system_pinmux_pin_set_config(pad1 >> 16, + pad1 & 0xFFFF, + SYSTEM_PINMUX_PIN_DIR_OUTPUT_WITH_READBACK, + SYSTEM_PINMUX_PIN_PULL_UP, + false); + + + /* Save timeout on unknown bus state in software module. */ + module->unknown_bus_state_timeout = config->unknown_bus_state_timeout; + + /* Save timeout on buffer write. */ + module->buffer_timeout = config->buffer_timeout; + + /* Set whether module should run in standby. */ + if (config->run_in_standby || system_is_debugger_present()) { + tmp_ctrla = SERCOM_I2CM_CTRLA_RUNSTDBY; + } else { + tmp_ctrla = 0; + } + + /* Check and set start data hold timeout. */ + if (config->start_hold_time != I2C_MASTER_START_HOLD_TIME_DISABLED) { + tmp_ctrla |= config->start_hold_time; + } + + /* Check and set SCL low timeout. */ + if (config->scl_low_timeout) { + tmp_ctrla |= SERCOM_I2CM_CTRLA_LOWTOUT; + } + + /* Check and set inactive bus timeout. */ + if (config->inactive_timeout != I2C_MASTER_INACTIVE_TIMEOUT_DISABLED) { + tmp_ctrla |= config->inactive_timeout; + } + + /* Write config to register CTRLA. */ + i2c_module->CTRLA.reg |= tmp_ctrla; + + /* Set configurations in CTRLB. */ + i2c_module->CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; + + /* Find and set baudrate. */ + tmp_baud = (int32_t)(system_gclk_chan_get_hz(SERCOM0_GCLK_ID_CORE + sercom_index) / + (2000*(config->baud_rate)) - 5); + + /* Check that baud rate is supported at current speed. */ + if (tmp_baud > 255 || tmp_baud < 0) { + /* Baud rate not supported. */ + tmp_status_code = STATUS_ERR_BAUDRATE_UNAVAILABLE; + } else { + /* Baud rate acceptable. */ + i2c_module->BAUD.reg = (uint8_t)tmp_baud; + } + + return tmp_status_code; +} +#endif /* __DOXYGEN__ */ + +/** + * \brief Initializes the requested I2C hardware module + * + * Initializes the SERCOM I2C master device requested and sets the provided + * software module struct. Run this function before any further use of + * the driver. + * + * \param[out] module Pointer to software module struct + * \param[in] hw Pointer to the hardware instance + * \param[in] config Pointer to the configuration struct + * + * \return Status of initialization. + * \retval STATUS_OK Module initiated correctly + * \retval STATUS_ERR_DENIED If module is enabled + * \retval STATUS_BUSY If module is busy resetting + * \retval STATUS_ERR_ALREADY_INITIALIZED If setting other GCLK generator than + * previously set + * \retval STATUS_ERR_BAUDRATE_UNAVAILABLE If given baudrate is not compatible + * with set GCLK frequency + * + */ +enum status_code i2c_master_init( + struct i2c_master_module *const module, + Sercom *const hw, + const struct i2c_master_config *const config) +{ + /* Sanity check arguments. */ + Assert(module); + Assert(hw); + Assert(config); + + /* Initialize software module */ + module->hw = hw; + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw); + uint32_t pm_index = sercom_index + PM_APBCMASK_SERCOM0_Pos; + uint32_t gclk_index = sercom_index + SERCOM0_GCLK_ID_CORE; + + /* Turn on module in PM */ + system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index); + + /* Set up the GCLK for the module */ + + + + system_gclk_chan_set_config(gclk_index, config->generator_source); + system_gclk_chan_enable(gclk_index); + _sercom_set_gclk_generator(config->generator_source); + + /* Check if module is enabled. */ + if (i2c_module->CTRLA.reg & SERCOM_I2CM_CTRLA_ENABLE) { + return STATUS_ERR_DENIED; + } + + /* Check if reset is in progress. */ + if (i2c_module->CTRLA.reg & SERCOM_I2CM_CTRLA_SWRST) { + return STATUS_BUSY; + } + +#if I2C_MASTER_CALLBACK_MODE == true + /* Get sercom instance index and register callback. */ + uint8_t instance_index = _sercom_get_sercom_inst_index(module->hw); + _sercom_set_handler(instance_index, _i2c_master_interrupt_handler); + _sercom_instances[instance_index] = module; + + /* Initialize values in module. */ + module->registered_callback = 0; + module->enabled_callback = 0; + module->buffer_length = 0; + module->buffer_remaining = 0; + + module->status = STATUS_OK; + module->buffer = NULL; +#endif + + /* Set sercom module to operate in I2C master mode. */ + i2c_module->CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER; + + /* Set config and return status. */ + return _i2c_master_set_config(module, config); +} + +/** + * \brief Resets the hardware module + * + * Reset the module to hardware defaults. + * + * \param[in,out] module Pointer to software module structure + */ +void i2c_master_reset(struct i2c_master_module *const module) +{ + /* Sanity check arguments */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Wait for sync */ + _i2c_master_wait_for_sync(module); + + /* Disable module */ + i2c_master_disable(module); + +#if I2C_MASTER_CALLBACK_MODE == true + /* Clear all pending interrupts */ + system_interrupt_enter_critical_section(); + system_interrupt_clear_pending(_sercom_get_interrupt_vector(module->hw)); + system_interrupt_leave_critical_section(); +#endif + + /* Wait for sync */ + _i2c_master_wait_for_sync(module); + + /* Reset module */ + i2c_module->CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST; +} + +#if !defined(__DOXYGEN__) +/** + * \internal + * Address response. Called when address is answered or timed out. + * + * \param[in,out] module Pointer to software module structure + * + * \return Status of address response. + * \retval STATUS_OK No error has occurred + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + */ +static enum status_code _i2c_master_address_response( + struct i2c_master_module *const module) +{ + /* Sanity check arguments */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Check for error and ignore bus-error; workaround for BUSSTATE stuck in + * BUSY */ + if (i2c_module->INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB) { + + /* Clear write interrupt flag */ + i2c_module->INTFLAG.reg = SERCOM_I2CM_INTFLAG_SB; + + /* Check arbitration. */ + if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_ARBLOST) { + /* Return packet collision. */ + return STATUS_ERR_PACKET_COLLISION; + } + /* Check that slave responded with ack. */ + } else if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { + /* Slave busy. Issue ack and stop command. */ + i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); + + /* Return bad address value. */ + return STATUS_ERR_BAD_ADDRESS; + } + + return STATUS_OK; +} + +/** + * \internal + * Waits for answer on bus. + * + * \param[in,out] module Pointer to software module structure + * + * \return Status of bus. + * \retval STATUS_OK If given response from slave device + * \retval STATUS_ERR_TIMEOUT If no response was given within specified timeout + * period + */ +static enum status_code _i2c_master_wait_for_bus( + struct i2c_master_module *const module) +{ + /* Sanity check arguments */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Wait for reply. */ + uint16_t timeout_counter = 0; + while (!(i2c_module->INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB) && + !(i2c_module->INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB)) { + + /* Check timeout condition. */ + if (++timeout_counter >= module->buffer_timeout) { + return STATUS_ERR_TIMEOUT; + } + } + return STATUS_OK; +} +#endif /* __DOXYGEN__ */ + +/** + * \internal + * Starts blocking read operation. + * + * \param[in,out] module Pointer to software module struct. + * \param[in,out] packet Pointer to I2C packet to transfer. + * + * \return Status of reading packet. + * \retval STATUS_OK The packet was read successfully + * \retval STATUS_ERR_TIMEOUT If no response was given within + * specified timeout period + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + * + */ +static enum status_code _i2c_master_read_packet( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet) +{ + /* Sanity check arguments */ + Assert(module); + Assert(module->hw); + Assert(packet); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Return value. */ + enum status_code tmp_status; + uint16_t tmp_data_length = packet->data_length; + + /* Written buffer counter. */ + uint16_t counter = 0; + + /* Set address and direction bit. Will send start command on bus. */ + i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_READ; + + /* Wait for response on bus. */ + tmp_status = _i2c_master_wait_for_bus(module); + + /* Set action to ack. */ + i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; + + /* Check for address response error unless previous error is + * detected. */ + if (tmp_status == STATUS_OK) { + tmp_status = _i2c_master_address_response(module); + } + + /* Check that no error has occurred. */ + if (tmp_status == STATUS_OK) { + /* Read data buffer. */ + while (tmp_data_length--) { + /* Check that bus ownership is not lost. */ + if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { + return STATUS_ERR_PACKET_COLLISION; + } + + if (tmp_data_length == 0) { + /* Set action to NACK */ + i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; + } else { + /* Save data to buffer. */ + _i2c_master_wait_for_sync(module); + packet->data[counter++] = i2c_module->DATA.reg; + /* Wait for response. */ + tmp_status = _i2c_master_wait_for_bus(module); + } + + /* Check for error. */ + if (tmp_status != STATUS_OK) { + break; + } + } + + if (module->send_stop) { + /* Send stop command unless arbitration is lost. */ + _i2c_master_wait_for_sync(module); + i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); + } + + /* Save last data to buffer. */ + _i2c_master_wait_for_sync(module); + packet->data[counter] = i2c_module->DATA.reg; + } + + return tmp_status; +} + +/** + * \brief Reads data packet from slave + * + * Reads a data packet from the specified slave address on the I2C + * bus and sends a stop condition when finished. + * + * \note This will stall the device from any other operation. For + * interrupt-driven operation, see \ref i2c_master_read_packet_job. + * + * \param[in,out] module Pointer to software module struct + * \param[in,out] packet Pointer to I2C packet to transfer + * + * \return Status of reading packet. + * \retval STATUS_OK The packet was read successfully + * \retval STATUS_ERR_TIMEOUT If no response was given within + * specified timeout period + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + */ +enum status_code i2c_master_read_packet_wait( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet) +{ + /* Sanity check */ + Assert(module); + Assert(module->hw); + Assert(packet); + +#if I2C_MASTER_CALLBACK_MODE == true + /* Check if the I2C module is busy with a job. */ + if (module->buffer_remaining > 0) { + return STATUS_BUSY; + } +#endif + + module->send_stop = true; + + return _i2c_master_read_packet(module, packet); +} + +/** + * \brief Reads data packet from slave without sending a stop condition when done + * + * Reads a data packet from the specified slave address on the I2C + * bus without sending a stop condition when done, thus retaining ownership of + * the bus when done. To end the transaction, a + * \ref i2c_master_read_packet_wait "read" or + * \ref i2c_master_write_packet_wait "write" with stop condition must be + * performed. + * + * \note This will stall the device from any other operation. For + * interrupt-driven operation, see \ref i2c_master_read_packet_job. + * + * \param[in,out] module Pointer to software module struct + * \param[in,out] packet Pointer to I2C packet to transfer + * + * \return Status of reading packet. + * \retval STATUS_OK The packet was read successfully + * \retval STATUS_ERR_TIMEOUT If no response was given within + * specified timeout period + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + */ +enum status_code i2c_master_read_packet_wait_no_stop( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet) +{ + /* Sanity check */ + Assert(module); + Assert(module->hw); + Assert(packet); + +#if I2C_MASTER_CALLBACK_MODE == true + /* Check if the I2C module is busy with a job. */ + if (module->buffer_remaining > 0) { + return STATUS_BUSY; + } +#endif + + module->send_stop = false; + + return _i2c_master_read_packet(module, packet); +} + +/** + * \internal + * Starts blocking write operation. + * + * \param[in,out] module Pointer to software module struct. + * \param[in,out] packet Pointer to I2C packet to transfer. + * + * \return Status of reading packet. + * \retval STATUS_OK The packet was read successfully + * \retval STATUS_ERR_TIMEOUT If no response was given within + * specified timeout period + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + */ +static enum status_code _i2c_master_write_packet( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet) +{ + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Return value. */ + enum status_code tmp_status; + uint16_t tmp_data_length = packet->data_length; + + _i2c_master_wait_for_sync(module); + + /* Set address and direction bit. Will send start command on bus. */ + i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_WRITE; + + /* Wait for response on bus. */ + tmp_status = _i2c_master_wait_for_bus(module); + + /* Check for address response error unless previous error is + * detected. */ + if (tmp_status == STATUS_OK) { + tmp_status = _i2c_master_address_response(module); + } + + /* Check that no error has occurred. */ + if (tmp_status == STATUS_OK) { + /* Buffer counter. */ + uint16_t buffer_counter = 0; + + /* Write data buffer. */ + while (tmp_data_length--) { + /* Check that bus ownership is not lost. */ + if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { + return STATUS_ERR_PACKET_COLLISION; + } + + /* Write byte to slave. */ + _i2c_master_wait_for_sync(module); + i2c_module->DATA.reg = packet->data[buffer_counter++]; + + /* Wait for response. */ + tmp_status = _i2c_master_wait_for_bus(module); + + /* Check for error. */ + if (tmp_status != STATUS_OK) { + break; + } + + /* Check for NACK from slave. */ + if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { + /* Return bad data value. */ + tmp_status = STATUS_ERR_OVERFLOW; + break; + } + } + + if (module->send_stop) { + /* Stop command */ + _i2c_master_wait_for_sync(module); + i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); + } + } + + return tmp_status; +} + +/** + * \brief Writes data packet to slave + * + * Writes a data packet to the specified slave address on the I2C bus + * and sends a stop condition when finished. + * + * \note This will stall the device from any other operation. For + * interrupt-driven operation, see \ref i2c_master_read_packet_job. + * + * \param[in,out] module Pointer to software module struct + * \param[in,out] packet Pointer to I2C packet to transfer + * + * \return Status of reading packet. + * \retval STATUS_OK If packet was read + * \retval STATUS_BUSY If master module is busy with a job + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + * \retval STATUS_ERR_TIMEOUT If timeout occurred + * \retval STATUS_ERR_OVERFLOW If slave did not acknowledge last sent + * data, indicating that slave does not + * want more data and was not able to read + * last data sent + */ +enum status_code i2c_master_write_packet_wait( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet) +{ + /* Sanity check */ + Assert(module); + Assert(module->hw); + Assert(packet); + +#if I2C_MASTER_CALLBACK_MODE == true + /* Check if the I2C module is busy with a job */ + if (module->buffer_remaining > 0) { + return STATUS_BUSY; + } +#endif + + module->send_stop = true; + + return _i2c_master_write_packet(module, packet); +} + +/** + * \brief Writes data packet to slave without sending a stop condition when done + * + * Writes a data packet to the specified slave address on the I2C bus + * without sending a stop condition, thus retaining ownership of the bus when + * done. To end the transaction, a \ref i2c_master_read_packet_wait "read" or + * \ref i2c_master_write_packet_wait "write" with stop condition or sending a + * stop with the \ref i2c_master_send_stop function must be performed. + * + * \note This will stall the device from any other operation. For + * interrupt-driven operation, see \ref i2c_master_read_packet_job. + * + * \param[in,out] module Pointer to software module struct + * \param[in,out] packet Pointer to I2C packet to transfer + * + * \return Status of reading packet. + * \retval STATUS_OK If packet was read + * \retval STATUS_BUSY If master module is busy + * \retval STATUS_ERR_DENIED If error on bus + * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost + * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave + * acknowledged the address + * \retval STATUS_ERR_TIMEOUT If timeout occurred + * \retval STATUS_ERR_OVERFLOW If slave did not acknowledge last sent + * data, indicating that slave do not want + * more data + */ +enum status_code i2c_master_write_packet_wait_no_stop( + struct i2c_master_module *const module, + struct i2c_master_packet *const packet) +{ + /* Sanity check */ + Assert(module); + Assert(module->hw); + Assert(packet); + +#if I2C_MASTER_CALLBACK_MODE == true + /* Check if the I2C module is busy with a job */ + if (module->buffer_remaining > 0) { + return STATUS_BUSY; + } +#endif + + module->send_stop = false; + + return _i2c_master_write_packet(module, packet); +} + +/** + * \brief Sends stop condition on bus + * + * Sends a stop condition on bus. + * + * \note This function can only be used after the + * \ref i2c_master_write_packet_wait_no_stop function. If a stop condition + * is to be sent after a read, the \ref i2c_master_read_packet_wait + * function must be used. + * + * \param[in] module Pointer to the software instance struct + */ +void i2c_master_send_stop(struct i2c_master_module *const module) +{ + /* Sanity check */ + Assert(module); + Assert(module->hw); + + SercomI2cm *const i2c_module = &(module->hw->I2CM); + + /* Send stop command */ + _i2c_master_wait_for_sync(module); + i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); +}