/** library to communicate using I²C as master * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2017-2021 * @note the I²C peripheral is not well specified and does not cover all cases. The following complexity is the best I could do to cope with it */ /* standard libraries */ #include // standard integer types #include // boolean types #include // general utilities /* own libraries */ #include "stm8s.h" // STM8S definitions #include "i2c_master.h" // I²C header and definitions #include "main.h" bool i2c_master_setup(bool fast) { // configure I²C peripheral if (!i2c_master_check_signals()) { // check the signal lines return false; } I2C_FREQR = 16; // the peripheral frequency (must match CPU frequency) if (fast) { I2C_CCRL = 0x2; // set SCL at 320 kHz (for less error) I2C_CCRH = (I2C_CCRH_FS | I2C_CCRH_DUTY); // set fast speed mode I2C_CCRH = (1U << 7); // set fast speed mode } else { I2C_CCRL = 0x50; // set SCL at 100 kHz I2C_CCRH = 0; // set standard speed mode } I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral return true; } void i2c_master_release(void) { I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral } bool i2c_master_check_signals(void) { i2c_master_release(); // ensure PB4/PB5 are not used as alternate function GPIO_PB->CR1.reg &= ~(PB4 | PB5); // operate in open-drain mode GPIO_PB->DDR.reg |= (PB4 | PB5); // set SCL/SDA as output to test pull-up GPIO_PB->ODR.reg |= PB4; // ensure SCL is high GPIO_PB->ODR.reg &= ~PB5; // set SDA low (start condition) for (volatile uint8_t t = 0; t < 10; t++); // wait 10 us GPIO_PB->ODR.reg |= PB5; // set SDA high (stop condition) for (volatile uint8_t t = 0; t < 10; t++); // wait 10 us for pull-up to take effect GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral return ((GPIO_PB->IDR.reg & PB4) && (GPIO_PB->IDR.reg & PB5)); // test if both lines are up } void i2c_master_reset(void) { I2C_CR2 |= I2C_CR2_STOP; // release lines // don't check if BUSY is cleared since its state might be erroneous // rewriting I2C_CR2 before I2C_CR2_STOP is cleared might cause a second STOP, but at this point we don't care I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit while ((0 == (GPIO_PB->IDR.reg & PB4) && (0 == (GPIO_PB->IDR.reg & PB5)))); // wait for SDA/SCL line to be released I2C_CR2 &= ~I2C_CR2_SWRST; // release reset I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral to clear some bits I2C_CR1 |= I2C_CR1_PE; // re-enable I²C peripheral (must be done before any other register is written) } enum i2c_master_rc i2c_master_start(void) { // send (re-)start condition if (I2C_CR2 & (I2C_CR2_START | I2C_CR2_STOP)) { // ensure start or stop operations are not in progress return I2C_MASTER_RC_START_STOP_IN_PROGESS; } I2C_CR2 |= I2C_CR2_START; // sent start condition while ((I2C_CR2 & I2C_CR2_START) && !(I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until start condition has been accepted and cleared if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (!(I2C_SR1 & I2C_SR1_SB)) { // the start bit has not been set return I2C_MASTER_RC_BUS_ERROR; } if (!(I2C_SR3 & I2C_SR3_MSL)) { // verify if in master mode return I2C_MASTER_RC_NOT_MASTER; } return I2C_MASTER_RC_NONE; } /** wait until stop is sent and bus is released * @return I²C return code */ static enum i2c_master_rc i2c_master_wait_stop(void) { while ((I2C_CR2 & I2C_CR2_STOP) && !(I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO))); // wait until stop condition is accepted and cleared if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } // this time we can't use I2C_CR2_STOP to check for timeout if (I2C_SR3 & I2C_SR3_MSL) { // ensure we are not in master mode anymore return I2C_MASTER_RC_BUS_ERROR; } if (I2C_SR3 & I2C_SR3_BUSY) { // ensure bus is released return I2C_MASTER_RC_BUS_ERROR; } if (!i2c_master_check_signals()) { // ensure lines are released return I2C_MASTER_RC_BUS_ERROR; } return I2C_MASTER_RC_NONE; } enum i2c_master_rc i2c_master_stop(void) { // sanity check if (!(I2C_SR3 & I2C_SR3_BUSY)) { // ensure bus is not already released return I2C_MASTER_RC_NONE; // bus has probably already been released } if (I2C_CR2 & (I2C_CR2_START | I2C_CR2_STOP)) { // ensure start or stop operations are not in progress return I2C_MASTER_RC_START_STOP_IN_PROGESS; } I2C_CR2 |= I2C_CR2_STOP; // send stop to release bus return i2c_master_wait_stop(); } enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write) { if (!(I2C_SR1 & I2C_SR1_SB)) { // start condition has not been sent yet enum i2c_master_rc rc = i2c_master_start(); // send start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } } if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode return I2C_MASTER_RC_NOT_MASTER; } // select slave I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure if (!address_10bit) { // 7-bit address I2C_DR = (slave << 1) | (write ? 0 : 1); // select slave, with read/write flag while (!(I2C_SR1 & I2C_SR1_ADDR) && !(I2C_SR2 & (I2C_SR2_AF | I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until address is transmitted (or error) if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged return I2C_MASTER_RC_NAK; } } else { // 10-bit address // send first part of address I2C_DR = 11110000 | (((slave >> 8 ) & 0x3) << 1); // send first header (11110xx0, where xx are 2 MSb of slave address) while (!(I2C_SR1 & I2C_SR1_ADD10) && !(I2C_SR2 & (I2C_SR2_AF | I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until address is transmitted (or error) if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged return I2C_MASTER_RC_NAK; } // send second part of address I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure I2C_DR = (slave & 0xff); // send remaining of address while (!(I2C_SR1 & I2C_SR1_ADDR) && !(I2C_SR2 & (I2C_SR2_AF | I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until address is transmitted (or error) if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged return I2C_MASTER_RC_NAK; } // go into receive mode if necessary if (!write) { enum i2c_master_rc rc = i2c_master_start(); // send start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } // send first part of address with receive flag I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure I2C_DR = 11110001 | (((slave >> 8) & 0x3) << 1); // send header (11110xx1, where xx are 2 MSb of slave address) while (!(I2C_SR1 & I2C_SR1_ADDR) && !(I2C_SR2 & (I2C_SR2_AF | I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until address is transmitted (or error) if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged return I2C_MASTER_RC_NAK; } } } // verify if we are in the right mode if (write && !(I2C_SR3 & I2C_SR3_TRA)) { return I2C_MASTER_RC_NOT_TRANSMIT; } else if (!write && (I2C_SR3 & I2C_SR3_TRA)) { return I2C_MASTER_RC_NOT_RECEIVE; } return I2C_MASTER_RC_NONE; } enum i2c_master_rc i2c_master_read(uint8_t* data, uint16_t data_size) { if (NULL == data || 0 == data_size) { // no data to read return I2C_MASTER_RC_OTHER; // we indicate an error because we don't send a stop } if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode return I2C_MASTER_RC_NOT_MASTER; } // we can't check if the address phase it over since ADDR has been cleared when checking for mode if (I2C_SR3 & I2C_SR3_TRA) { // ensure we are in receive mode return I2C_MASTER_RC_NOT_RECEIVE; } // read data for (uint16_t i = 0; i < data_size; i++) { // read bytes // set (N)ACK (EV6_3, EV6_1) if (1 == data_size - i) { // prepare to sent NACK for last byte I2C_CR2 &= ~(I2C_CR2_ACK); // disable ACK I2C_CR2 |= I2C_CR2_STOP; // prepare to send the stop } else { I2C_CR2 |= I2C_CR2_ACK; // enable ACK } while (!(I2C_SR1 & I2C_SR1_RXNE) && !(I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO))); // wait until data is received (or error) if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } data[i] = I2C_DR; // read the received byte } return i2c_master_wait_stop(); } enum i2c_master_rc i2c_master_write(const uint8_t* data, uint16_t data_size) { if (NULL == data || 0 == data_size) { // no data to read return I2C_MASTER_RC_NONE; // we don't indicate an error because the stop is done seperatly } if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode return I2C_MASTER_RC_NOT_MASTER; } // we can't check if the address phase it over since ADDR has been cleared when checking for mode if (!(I2C_SR3 & I2C_SR3_TRA)) { // ensure we are in transmit mode return I2C_MASTER_RC_NOT_TRANSMIT; } // write data for (uint16_t i = 0; i < data_size; i++) { // write bytes I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure I2C_DR = data[i]; // send byte while (!(I2C_SR1 & I2C_SR1_TXE) && !(I2C_SR2 & (I2C_SR2_AF | I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until byte has been transmitted if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (I2C_SR2 & I2C_SR2_AF) { // data has not been acknowledged return I2C_MASTER_RC_NAK; } } while (!(I2C_SR1 & I2C_SR1_BTF) && !(I2C_SR2 & (I2C_SR2_AF | I2C_SR2_BERR | I2C_SR2_ARLO)) && !(I2C_CR2 & I2C_CR2_STOP)); // wait until last byte has been transmitted if (I2C_SR2 & (I2C_SR2_BERR | I2C_SR2_ARLO)) { return I2C_MASTER_RC_BUS_ERROR; } if (I2C_CR2 & I2C_CR2_STOP) { return I2C_MASTER_RC_TIMEOUT; } if (I2C_SR2 & I2C_SR2_AF) { // data has not been acknowledged return I2C_MASTER_RC_NAK; } return I2C_MASTER_RC_NONE; } enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, uint16_t data_size) { enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes rc = i2c_master_start(); // send (re-)start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read if (I2C_MASTER_RC_NONE != rc) { goto error; } if (NULL != data && data_size > 0) { // only read data if needed rc = i2c_master_read(data, data_size); // read data (includes stop) if (I2C_MASTER_RC_NONE != rc) { goto error; } } else { i2c_master_stop(); // sent stop condition } rc = I2C_MASTER_RC_NONE; // all went well error: if (I2C_MASTER_RC_NONE != rc) { i2c_master_stop(); // sent stop condition } return rc; } enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, uint16_t data_size) { enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes rc = i2c_master_start(); // send (re-)start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write if (I2C_MASTER_RC_NONE != rc) { goto error; } if (NULL != data && data_size > 0) { // write data only is some is available rc = i2c_master_write(data, data_size); // write data if (I2C_MASTER_RC_NONE != rc) { goto error; } } rc = I2C_MASTER_RC_NONE; // all went well error: i2c_master_stop(); // sent stop condition return rc; } enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size) { enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes rc = i2c_master_start(); // send (re-)start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write if (I2C_MASTER_RC_NONE != rc) { goto error; } // write address if (NULL != address && address_size > 0) { rc = i2c_master_write(address, address_size); // send memory address if (I2C_MASTER_RC_NONE != rc) { goto error; } } // read data if (NULL != data && data_size > 0) { rc = i2c_master_start(); // send re-start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read if (I2C_MASTER_RC_NONE != rc) { goto error; } rc = i2c_master_read(data, data_size); // read memory (includes stop) if (I2C_MASTER_RC_NONE != rc) { goto error; } } else { i2c_master_stop(); // sent stop condition } rc = I2C_MASTER_RC_NONE; error: if (I2C_MASTER_RC_NONE != rc) { // only send stop on error i2c_master_stop(); // sent stop condition } return rc; } enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size) { if (UINT16_MAX - address_size < data_size) { // prevent integer overflow return I2C_MASTER_RC_OTHER; } if (address_size > 0 && NULL == address) { return I2C_MASTER_RC_OTHER; } if (data_size > 0 && NULL == data) { return I2C_MASTER_RC_OTHER; } enum i2c_master_rc rc; // to store I²C return codes rc = i2c_master_start(); // send (re-)start condition if (I2C_MASTER_RC_NONE != rc) { return rc; } rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write if (I2C_MASTER_RC_NONE != rc) { goto error; } if (address_size && address) { rc = i2c_master_write(address, address_size); // send memory address if (I2C_MASTER_RC_NONE != rc) { goto error; } } if (data_size && data) { rc = i2c_master_write(data, data_size); // send memory data if (I2C_MASTER_RC_NONE != rc) { goto error; } } rc = I2C_MASTER_RC_NONE; // all went fine error: if (I2C_MASTER_RC_NONE != rc) { i2c_master_stop(); } else { rc = i2c_master_stop(); // sent stop condition } return rc; }