123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- /**
- * Copyright (c) 2009 - 2020, Nordic Semiconductor ASA
- *
- * All rights reserved.
- *
- * 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, except as embedded into a Nordic
- * Semiconductor ASA integrated circuit in a product or a software update for
- * such product, 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. Neither the name of Nordic Semiconductor ASA nor the names of its
- * contributors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
- * 4. This software, with or without modification, must only be used with a
- * Nordic Semiconductor ASA integrated circuit.
- *
- * 5. Any software provided in binary form under this license must not be reverse
- * engineered, decompiled, modified and/or disassembled.
- *
- * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS 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.
- *
- */
- #include <stdbool.h>
- #include <stdint.h>
- #include "twi_master.h"
- #include "nrf_delay.h"
- #include "twi_master_config.h"
- /*lint -e415 -e845 -save "Out of bounds access" */
- #define TWI_SDA_STANDARD0_NODRIVE1() do { \
- NRF_GPIO->PIN_CNF[TWI_MASTER_CONFIG_DATA_PIN_NUMBER] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \
- |(GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) \
- |(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) \
- |(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) \
- |(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos); \
- } while (0) /*!< Configures SDA pin to Standard-0, No-drive 1 */
- #define TWI_SCL_STANDARD0_NODRIVE1() do { \
- NRF_GPIO->PIN_CNF[TWI_MASTER_CONFIG_CLOCK_PIN_NUMBER] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \
- |(GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) \
- |(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) \
- |(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) \
- |(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos); \
- } while (0) /*!< Configures SCL pin to Standard-0, No-drive 1 */
- /*lint -restore */
- #ifndef TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE
- #define TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE (0UL) //!< Unit is number of empty loops. Timeout for SMBus devices is 35 ms. Set to zero to disable slave timeout altogether.
- #endif
- static bool twi_master_clear_bus(void);
- static bool twi_master_issue_startcondition(void);
- static bool twi_master_issue_stopcondition(void);
- static bool twi_master_clock_byte(uint_fast8_t databyte);
- static bool twi_master_clock_byte_in(uint8_t * databyte, bool ack);
- static bool twi_master_wait_while_scl_low(void);
- bool twi_master_init(void)
- {
- // Configure both pins to output Standard 0, No-drive (open-drain) 1
- TWI_SDA_STANDARD0_NODRIVE1(); /*lint !e416 "Creation of out of bounds pointer" */
- TWI_SCL_STANDARD0_NODRIVE1(); /*lint !e416 "Creation of out of bounds pointer" */
- // Configure SCL as output
- TWI_SCL_HIGH();
- TWI_SCL_OUTPUT();
- // Configure SDA as output
- TWI_SDA_HIGH();
- TWI_SDA_OUTPUT();
- return twi_master_clear_bus();
- }
- bool twi_master_transfer(uint8_t address, uint8_t * data, uint8_t data_length, bool issue_stop_condition)
- {
- bool transfer_succeeded = true;
- transfer_succeeded &= twi_master_issue_startcondition();
- transfer_succeeded &= twi_master_clock_byte(address);
- if (address & TWI_READ_BIT)
- {
- /* Transfer direction is from Slave to Master */
- while (data_length-- && transfer_succeeded)
- {
- // To indicate to slave that we've finished transferring last data byte
- // we need to NACK the last transfer.
- if (data_length == 0)
- {
- transfer_succeeded &= twi_master_clock_byte_in(data, (bool)false);
- }
- else
- {
- transfer_succeeded &= twi_master_clock_byte_in(data, (bool)true);
- }
- data++;
- }
- }
- else
- {
- /* Transfer direction is from Master to Slave */
- while (data_length-- && transfer_succeeded)
- {
- transfer_succeeded &= twi_master_clock_byte(*data);
- data++;
- }
- }
- if (issue_stop_condition || !transfer_succeeded)
- {
- transfer_succeeded &= twi_master_issue_stopcondition();
- }
- return transfer_succeeded;
- }
- /**
- * @brief Function for detecting stuck slaves and tries to clear the bus.
- *
- * @return
- * @retval false Bus is stuck.
- * @retval true Bus is clear.
- */
- static bool twi_master_clear_bus(void)
- {
- bool bus_clear;
- TWI_SDA_HIGH();
- TWI_SCL_HIGH();
- TWI_DELAY();
- if (TWI_SDA_READ() == 1 && TWI_SCL_READ() == 1)
- {
- bus_clear = true;
- }
- else if (TWI_SCL_READ() == 1)
- {
- bus_clear = false;
- // Clock max 18 pulses worst case scenario(9 for master to send the rest of command and 9 for slave to respond) to SCL line and wait for SDA come high
- for (uint_fast8_t i = 18; i--;)
- {
- TWI_SCL_LOW();
- TWI_DELAY();
- TWI_SCL_HIGH();
- TWI_DELAY();
- if (TWI_SDA_READ() == 1)
- {
- bus_clear = true;
- break;
- }
- }
- }
- else
- {
- bus_clear = false;
- }
- return bus_clear;
- }
- /**
- * @brief Function for issuing TWI START condition to the bus.
- *
- * START condition is signaled by pulling SDA low while SCL is high. After this function SCL and SDA will be low.
- *
- * @return
- * @retval false Timeout detected
- * @retval true Clocking succeeded
- */
- static bool twi_master_issue_startcondition(void)
- {
- #if 0
- if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 1)
- {
- // Pull SDA low
- TWI_SDA_LOW();
- }
- else if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 0)
- {
- // Issue Stop by pulling SDA high
- TWI_SDA_HIGH();
- TWI_DELAY();
- // Then Start by pulling SDA low
- TWI_SDA_LOW();
- }
- else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 0)
- {
- // First pull SDA high
- TWI_SDA_HIGH();
- // Then SCL high
- if (!twi_master_wait_while_scl_low())
- {
- return false;
- }
- // Then SDA low
- TWI_SDA_LOW();
- }
- else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 1)
- {
- // SCL high
- if (!twi_master_wait_while_scl_low())
- {
- return false;
- }
- // Then SDA low
- TWI_SDA_LOW();
- }
- TWI_DELAY();
- TWI_SCL_LOW();
- #endif
- // Make sure both SDA and SCL are high before pulling SDA low.
- TWI_SDA_HIGH();
- TWI_DELAY();
- if (!twi_master_wait_while_scl_low())
- {
- return false;
- }
- TWI_SDA_LOW();
- TWI_DELAY();
- // Other module function expect SCL to be low
- TWI_SCL_LOW();
- TWI_DELAY();
- return true;
- }
- /**
- * @brief Function for issuing TWI STOP condition to the bus.
- *
- * STOP condition is signaled by pulling SDA high while SCL is high. After this function SDA and SCL will be high.
- *
- * @return
- * @retval false Timeout detected
- * @retval true Clocking succeeded
- */
- static bool twi_master_issue_stopcondition(void)
- {
- #if 0
- if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 1)
- {
- // Issue start, then issue stop
- // Pull SDA low to issue START
- TWI_SDA_LOW();
- TWI_DELAY();
- // Pull SDA high while SCL is high to issue STOP
- TWI_SDA_HIGH();
- }
- else if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 0)
- {
- // Pull SDA high while SCL is high to issue STOP
- TWI_SDA_HIGH();
- }
- else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 0)
- {
- if (!twi_master_wait_while_scl_low())
- {
- return false;
- }
- // Pull SDA high while SCL is high to issue STOP
- TWI_SDA_HIGH();
- }
- else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 1)
- {
- TWI_SDA_LOW();
- TWI_DELAY();
- // SCL high
- if (!twi_master_wait_while_scl_low())
- {
- return false;
- }
- // Pull SDA high while SCL is high to issue STOP
- TWI_SDA_HIGH();
- }
- TWI_DELAY();
- #endif
- TWI_SDA_LOW();
- TWI_DELAY();
- if (!twi_master_wait_while_scl_low())
- {
- return false;
- }
- TWI_SDA_HIGH();
- TWI_DELAY();
- return true;
- }
- /**
- * @brief Function for clocking one data byte out and reads slave acknowledgment.
- *
- * Can handle clock stretching.
- * After calling this function SCL is low and SDA low/high depending on the
- * value of LSB of the data byte.
- * SCL is expected to be output and low when entering this function.
- *
- * @param databyte Data byte to clock out.
- * @return
- * @retval true Slave acknowledged byte.
- * @retval false Timeout or slave didn't acknowledge byte.
- */
- static bool twi_master_clock_byte(uint_fast8_t databyte)
- {
- bool transfer_succeeded = true;
- /** @snippet [TWI SW master write] */
- // Make sure SDA is an output
- TWI_SDA_OUTPUT();
- // MSB first
- for (uint_fast8_t i = 0x80; i != 0; i >>= 1)
- {
- TWI_SCL_LOW();
- TWI_DELAY();
- if (databyte & i)
- {
- TWI_SDA_HIGH();
- }
- else
- {
- TWI_SDA_LOW();
- }
- if (!twi_master_wait_while_scl_low())
- {
- transfer_succeeded = false; // Timeout
- break;
- }
- }
- // Finish last data bit by pulling SCL low
- TWI_SCL_LOW();
- TWI_DELAY();
- /** @snippet [TWI SW master write] */
- // Configure TWI_SDA pin as input for receiving the ACK bit
- TWI_SDA_INPUT();
- // Give some time for the slave to load the ACK bit on the line
- TWI_DELAY();
- // Pull SCL high and wait a moment for SDA line to settle
- // Make sure slave is not stretching the clock
- transfer_succeeded &= twi_master_wait_while_scl_low();
- // Read ACK/NACK. NACK == 1, ACK == 0
- transfer_succeeded &= !(TWI_SDA_READ());
- // Finish ACK/NACK bit clock cycle and give slave a moment to release control
- // of the SDA line
- TWI_SCL_LOW();
- TWI_DELAY();
- // Configure TWI_SDA pin as output as other module functions expect that
- TWI_SDA_OUTPUT();
- return transfer_succeeded;
- }
- /**
- * @brief Function for clocking one data byte in and sends ACK/NACK bit.
- *
- * Can handle clock stretching.
- * SCL is expected to be output and low when entering this function.
- * After calling this function, SCL is high and SDA low/high depending if ACK/NACK was sent.
- *
- * @param databyte Data byte to clock out.
- * @param ack If true, send ACK. Otherwise send NACK.
- * @return
- * @retval true Byte read succesfully
- * @retval false Timeout detected
- */
- static bool twi_master_clock_byte_in(uint8_t *databyte, bool ack)
- {
- uint_fast8_t byte_read = 0;
- bool transfer_succeeded = true;
- /** @snippet [TWI SW master read] */
- // Make sure SDA is an input
- TWI_SDA_INPUT();
- // SCL state is guaranteed to be high here
- // MSB first
- for (uint_fast8_t i = 0x80; i != 0; i >>= 1)
- {
- if (!twi_master_wait_while_scl_low())
- {
- transfer_succeeded = false;
- break;
- }
- if (TWI_SDA_READ())
- {
- byte_read |= i;
- }
- else
- {
- // No need to do anything
- }
- TWI_SCL_LOW();
- TWI_DELAY();
- }
- // Make sure SDA is an output before we exit the function
- TWI_SDA_OUTPUT();
- /** @snippet [TWI SW master read] */
- *databyte = (uint8_t)byte_read;
- // Send ACK bit
- // SDA high == NACK, SDA low == ACK
- if (ack)
- {
- TWI_SDA_LOW();
- }
- else
- {
- TWI_SDA_HIGH();
- }
- // Let SDA line settle for a moment
- TWI_DELAY();
- // Drive SCL high to start ACK/NACK bit transfer
- // Wait until SCL is high, or timeout occurs
- if (!twi_master_wait_while_scl_low())
- {
- transfer_succeeded = false; // Timeout
- }
- // Finish ACK/NACK bit clock cycle and give slave a moment to react
- TWI_SCL_LOW();
- TWI_DELAY();
- return transfer_succeeded;
- }
- /**
- * @brief Function for pulling SCL high and waits until it is high or timeout occurs.
- *
- * SCL is expected to be output before entering this function.
- * @note If TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE is set to zero, timeout functionality is not compiled in.
- * @return
- * @retval true SCL is now high.
- * @retval false Timeout occurred and SCL is still low.
- */
- static bool twi_master_wait_while_scl_low(void)
- {
- #if TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE != 0
- uint32_t volatile timeout_counter = TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE;
- #endif
- // Pull SCL high just in case if something left it low
- TWI_SCL_HIGH();
- TWI_DELAY();
- while (TWI_SCL_READ() == 0)
- {
- // If SCL is low, one of the slaves is busy and we must wait
- #if TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE != 0
- if (timeout_counter-- == 0)
- {
- // If timeout_detected, return false
- return false;
- }
- #endif
- }
- return true;
- }
- /*lint --flb "Leave library region" */
|