diff --git a/eeprom_blockprog.c b/eeprom_blockprog.c deleted file mode 100644 index f9fea72..0000000 --- a/eeprom_blockprog.c +++ /dev/null @@ -1,69 +0,0 @@ -/** library to program EEPROM using block programming - * @file - * @author King Kévin - * @copyright SPDX-License-Identifier: GPL-3.0-or-later - * @date 2021 - * @warning functions need to be put in and run from RAM (because block programming is used) - */ - -// RAM code-putting from https://lujji.github.io/blog/executing-code-from-ram-on-stm8/ - -/* standard libraries */ -#include // standard integer types -#include // boolean types - -#include "stm8s.h" // STM8S definitions -#include "eeprom_blockprog.h" // own definitions - -// start address of EEPROM -#define EEPROM_ADDR 0x4000 -// block size from low-density devices (including STM8S103) -#define DATA_BLOCK_SIZE 64U - -#pragma codeseg RAM_SEG -bool eeprom_blockprog(const uint8_t* data, uint16_t length) -{ - if (0 == length) { - return true; // nothing to do - } - if (!data) { - return false; // data missing - } - if (0 != (length % DATA_BLOCK_SIZE)) { - return false; // we can only program whole blocks - } - - // disable DATA (e.g. EEPROM) write protection - // don't check if it is locked this it does not save that much time and uses memory) - FLASH_DUKR = FLASH_DUKR_KEY1; - FLASH_DUKR = FLASH_DUKR_KEY2; -// don't verify if unlock succeeded to save memory -// if (!(FLASH_IAPSR & FLASH_IAPSR_DUL)) { // un-protecting failed -// return false; -// } - - - // program data - uint8_t* eeprom = (uint8_t*)(EEPROM_ADDR); - while (length) { - // enable standard block programming - FLASH_CR2 |= FLASH_CR2_PRG; - FLASH_NCR2 &= ~FLASH_NCR2_NPRG; - // program block - for (uint8_t i = 0; i < DATA_BLOCK_SIZE; i++) { - *(eeprom++) = *(data++); - } - length -= DATA_BLOCK_SIZE; - // wait until program completed - while (FLASH_CR2 & FLASH_CR2_PRG); - // check if programming failed - // we don't check for WR_PG_DIS (while checking EOP) because EEPROM isn't (and can't be) write protected - if (!(FLASH_IAPSR & FLASH_IAPSR_EOP)) { - FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection - return false; - } - } - - FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection - return true; -} diff --git a/eeprom_blockprog.h b/eeprom_blockprog.h deleted file mode 100644 index 8b0c3d4..0000000 --- a/eeprom_blockprog.h +++ /dev/null @@ -1,14 +0,0 @@ -/** library to program EEPROM using block programming - * @file - * @author King Kévin - * @copyright SPDX-License-Identifier: GPL-3.0-or-later - * @date 2021 - * @warning functions need to be put in and run from RAM (because block programming is used) - */ - -/** program EEPROM using block programming - * @param[in] data data to be programmed - * @param[in] length length of data to be programmed (must be a multiple of the block length) - * @return if program succeeded - */ -bool eeprom_blockprog(const uint8_t* data, uint16_t length); diff --git a/i2c_master.c b/i2c_master.c deleted file mode 100644 index 8fc17c5..0000000 --- a/i2c_master.c +++ /dev/null @@ -1,469 +0,0 @@ -/** 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 - -bool i2c_master_setup(uint16_t freq_khz) -{ - // configure I²C peripheral - I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral to configure it -/* - if (!i2c_master_check_signals()) { // check the signal lines - return false; - } -*/ - I2C_FREQR = 16; // the peripheral frequency (must match CPU frequency) - if (freq_khz > 100) { - uint16_t ccr = (I2C_FREQR * 1000) / (3 * freq_khz); - if (ccr > 0x0fff) { - ccr = 0x0fff; - } - I2C_CCRL = (ccr & 0xff); // set SCL at 320 kHz (for less error) - I2C_CCRH = ((ccr >> 8) & 0x0f) | (I2C_CCRH_FS); // set fast speed mode - I2C_TRISER = ((I2C_FREQR * 3 / 10) + 1); // set rise time - } else { - uint16_t ccr = (I2C_FREQR * 1000) / (2 * freq_khz); - if (ccr > 0x0fff) { - ccr = 0x0fff; - } - I2C_CCRL = (ccr & 0xff); // set SCL at 320 kHz (for less error) - I2C_CCRH = ((ccr >> 8) & 0x0f); // set fast speed mode - I2C_TRISER = (I2C_FREQR + 1); // set rise time - } - 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 a bit to be sure signal is low - GPIO_PB->ODR.reg |= PB5; // set SDA high (stop condition) - GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral - for (volatile uint8_t t = 0; t < 50; t++); // wait 10 us for pull-up to take effect - - 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 - // be sure a watchdog is present as this can take forever - 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 -} - -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; - } - - // don't check BUSY flag as this might be for a re-start - I2C_CR2 |= I2C_CR2_START; // sent start condition - - I2C_SR2 = 0; // clear error flags - rim(); // enable interrupts - while ((I2C_CR2 & I2C_CR2_START) || !(I2C_SR1 & I2C_SR1_SB) || !(I2C_SR3 & I2C_SR3_MSL)) { // wait until start condition has been accepted, send, and we are in aster mode - if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - if (I2C_CR2 & I2C_CR2_STOP) { - return I2C_MASTER_RC_TIMEOUT; - } - I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - - 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) -{ - I2C_SR2 = 0; // clear error flags - while (I2C_CR2 & I2C_CR2_STOP) { // wait until stop condition is accepted and cleared - if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - // there is no interrupt flag we can use here - } - // 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 - I2C_SR2 = 0; // clear error flags - rim(); // enable interrupts - while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or 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 if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - } 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) - I2C_SR2 = 0; // clear error flags - rim(); // enable interrupts - while (!(I2C_SR1 & I2C_SR1_ADD10)) { // wait until address is transmitted (or 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 if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - // send second part of address - I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure - I2C_DR = (slave & 0xff); // send remaining of address - I2C_SR2 = 0; // clear error flags - rim(); // enable interrupts - while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or 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 if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - // 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) - I2C_SR2 = 0; // clear error flags - while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or 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 if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - } - } - // I2C_SR3_TRA should be set after I2C_SR1_ADDR is cleared (end of address transmission), but this is not the case and the TRM/errata does not provide more info - // verify if we are in the right mode - // final check - 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 - I2C_CR2 |= I2C_CR2_ACK; // enable ACK by default - I2C_SR2 = 0; // clear error flags - rim(); // enable interrupts - for (uint16_t i = 0; i < data_size; i++) { // read bytes - IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog - // 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 - } - rim(); // enable interrupts - while (!(I2C_SR1 & I2C_SR1_RXNE)) { // wait until data is received (or error) - if (I2C_SR2) { // an error occurred - return I2C_MASTER_RC_BUS_ERROR; - } - I2C_ITR = (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable all I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - 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 separately - } - 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 - (void)(I2C_SR1 & I2C_SR1_BTF); // clear BTF (when followed by write) in case the clock is stretched because there was no data to send on the next transmission slot - I2C_DR = data[i]; // send byte - I2C_SR2 = 0; // clear error flags - rim(); // enable interrupts - while (!(I2C_SR1 & I2C_SR1_TXE)) { // wait until byte has been transmitted - IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog - 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; - } else if (I2C_SR2) { - return I2C_MASTER_RC_BUS_ERROR; - } - I2C_ITR = (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable all I²C interrupts - wfi(); // got to sleep to prevent EMI causing glitches - } - } - - 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_stop(); // sent stop condition - if (I2C_MASTER_RC_NONE != rc) { - goto error; - } - - rc = I2C_MASTER_RC_NONE; // all went fine -error: - return rc; -} - -void i2c_master_isr(void) __interrupt(IRQ_I2C) // I²C event or error happened -{ - I2C_ITR = 0; // disable all interrupt sources to stop looping in ISR and let current loop check the right status flags -} diff --git a/i2c_master.h b/i2c_master.h deleted file mode 100644 index 82fc9a1..0000000 --- a/i2c_master.h +++ /dev/null @@ -1,117 +0,0 @@ -/** 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 - * @warning the I²C peripheral is very glitchy (sending random clock pulses), thus prefer the software implementation alternative, which is simpler, more flexible, smaller, and very stable (it just draws more energy) - */ -#pragma once - -/** I²C return codes */ -enum i2c_master_rc { - I2C_MASTER_RC_NONE = 0, /**< no error */ - I2C_MASTER_RC_START_STOP_IN_PROGESS, /**< a start or stop condition is already in progress */ - I2C_MASTER_RC_NOT_MASTER, /**< not in master mode */ - I2C_MASTER_RC_NOT_TRANSMIT, /**< not in transmit mode */ - I2C_MASTER_RC_NOT_RECEIVE, /**< not in receive mode */ - I2C_MASTER_RC_NOT_READY, /**< slave is not read (previous operations has been NACKed) */ - I2C_MASTER_RC_NAK, /**< not acknowledge received */ - I2C_MASTER_RC_BUS_ERROR, /**< an error on the I²C bus occurred */ - I2C_MASTER_RC_TIMEOUT, /**< a timeout has occurred because an operation has not completed in the expected time */ - I2C_MASTER_RC_OTHER, /** any other error (does not have to be I²C related) */ -}; - -/** setup I²C peripheral - * @param[in] freq_khz desired clock frequency, in kHz - * @return if I²C bus is ready to be used (same as i2c_master_check_signals) - */ -bool i2c_master_setup(uint16_t freq_khz); -/** release I²C peripheral */ -void i2c_master_release(void); -/** reset I²C peripheral, fixing any locked state - * @warning the I²C peripheral needs to be re-setup - * @note to be used after failed start or stop, and bus error - */ -void i2c_master_reset(void); -/** check if SDA and SCL signals are pulled high - * @return if SDA and SCL signals are pulled high - */ -bool i2c_master_check_signals(void); -/** send start condition - * @return I2C return code - */ -enum i2c_master_rc i2c_master_start(void); -/** select I²C slave device - * @warning a start condition should be sent before this operation - * @param[in] slave I²C address of slave device to select - * @param[in] address_10bit if the I²C slave address is 10 bits wide - * @param[in] write this transaction will be followed by a read (false) or write (true) operation - * @return I²C return code - */ -enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write); -/** read data over I²C - * @param[out] data array to store bytes read - * @param[in] data_size number of bytes to read - * @return I²C return code - * @warning the slave device must be selected before this operation - * @note a stop condition will be sent at the end (I²C does not permit multiple reads, and this is necessary for 1-byte transfer) - */ -enum i2c_master_rc i2c_master_read(uint8_t* data, uint16_t data_size); -/** write data over I²C - * @param[in] data array of byte to write to slave - * @param[in] data_size number of bytes to write - * @return I²C return code - * @warning the slave device must be selected before this operation - * @note no stop condition is sent at the end, allowing multiple writes - */ -enum i2c_master_rc i2c_master_write(const uint8_t* data, uint16_t data_size); -/** sent stop condition - * @param[in] i2c I²C base address - * @return I²C return code - */ -enum i2c_master_rc i2c_master_stop(void); -/** read data from slave device - * @param[in] slave I²C address of slave device to select - * @param[in] address_10bit if the I²C slave address is 10 bits wide - * @param[out] data array to store bytes read - * @param[in] data_size number of bytes to read - * @return I²C return code - * @note start and stop conditions are included - */ -enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, uint16_t data_size); -/** write data to slave device - * @param[in] slave I²C address of slave device to select - * @param[in] address_10bit if the I²C slave address is 10 bits wide - * @param[in] data array of byte to write to slave - * @param[in] data_size number of bytes to write - * @return I²C return code - * @note start and stop conditions are included - */ -enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, uint16_t data_size); -/** read data at specific address from an I²C memory slave - * @param[in] slave I²C address of slave device to select - * @param[in] address_10bit if the I²C slave address is 10 bits wide - * @param[in] address memory address of slave to read from - * @param[in] address_size address size in bytes - * @param[out] data array to store bytes read - * @param[in] data_size number of bytes to read - * @return I²C return code - * @note start and stop conditions are included - */ -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); -/** write data at specific address on an I²C memory slave - * @param[in] slave I²C address of slave device to select - * @param[in] address_10bit if the I²C slave address is 10 bits wide - * @param[in] address memory address of slave to write to - * @param[in] address_size address size in bytes - * @param[in] data array of byte to write to slave - * @param[in] data_size number of bytes to write - * @return I²C return code - * @note start and stop conditions are included - */ -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); -/** interrupt service routine used to wake up - * @note not sure why the declaration need to be in main for it to work - */ -void i2c_master_isr(void) __interrupt(IRQ_I2C); - diff --git a/softi2c_master.c b/softi2c_master.c deleted file mode 100644 index 24d85f3..0000000 --- a/softi2c_master.c +++ /dev/null @@ -1,412 +0,0 @@ -/** library to communicate using I²C as master, implemented in software - * @file - * @author King Kévin - * @copyright SPDX-License-Identifier: GPL-3.0-or-later - * @date 2021 - * @note I implemented I²C in software because the hardware peripheral is hard to use, and buggy (I was not able to get rid of clock glitches corrupting the communication, undetected) - * @note some methods copied from Wikipedia https://en.wikipedia.org/wiki/I%C2%B2C - */ - -/* standard libraries */ -#include // standard integer types -#include // boolean types -#include // general utilities - -/* own libraries */ -#include "stm8s.h" // STM8S definitions -#include "softi2c_master.h" // software I²C header and definitions - -// half period to wait for I²C clock -static uint16_t period = 0; - -// port for data line -#define SDA_PORT GPIO_PB -// pin for data line -#define SDA_PIN PB5 -// port for clock line -#define SCL_PORT GPIO_PB -// pin for clock line -#define SCL_PIN PB4 -// operation timeout (in half period) -#define TIMEOUT 10U - -// delay for half a period -static void I2C_delay(void) -{ - for (volatile uint16_t i = 0; i < period; i++); -} - -// Return current level of SCL line, 0 or 1 -static inline bool read_SCL(void) -{ - return (SCL_PORT->IDR.reg & SCL_PIN); -} - -// Return current level of SDA line, 0 or 1 -static inline bool read_SDA(void) -{ - return (SDA_PORT->IDR.reg & SDA_PIN); -} - -// Do not drive SCL (set pin high-impedance) -static inline void set_SCL(void) -{ - SCL_PORT->ODR.reg |= SCL_PIN; -} - -// Actively drive SCL signal low -static inline void clear_SCL(void) -{ - SCL_PORT->ODR.reg &= ~SCL_PIN; -} - -// Do not drive SDA (set pin high-impedance) -static inline void set_SDA(void) -{ - SDA_PORT->ODR.reg |= SDA_PIN; -} - -// Actively drive SDA signal low -static inline void clear_SDA(void) -{ - SDA_PORT->ODR.reg &= ~SDA_PIN; -} - -bool softi2c_master_setup(uint16_t freq_khz) -{ - // enforce minimal frequency - if (0 == freq_khz) { - freq_khz = 1; - } - // calculated period from frequency (hand tuned value using 16 MHz clock) - period = 589 / (1 << CLK->CKDIVR.fields.HSIDIV) / (1 << CLK->CKDIVR.fields.CPUDIV) / freq_khz; - - // switch pins to open drain - SCL_PORT->ODR.reg |= SCL_PIN; // ensure clock is high - SCL_PORT->DDR.reg |= SCL_PIN; // switch pin to output - SCL_PORT->CR1.reg &= ~SCL_PIN; // use in open-drain mode - SDA_PORT->ODR.reg |= SDA_PIN; // ensure data is high - SDA_PORT->DDR.reg |= SDA_PIN; // switch pin to output - SDA_PORT->CR1.reg &= ~SDA_PIN; // use in open-drain mode - - I2C_delay(); // give time to get high - return (read_SCL() && read_SDA()); // line is ready when the two lines are high -} - -void softi2c_master_release(void) -{ - SCL_PORT->DDR.reg &= ~SCL_PIN; // switch pin to input - SDA_PORT->DDR.reg &= ~SDA_PIN; // switch pin to input -} - -// if transaction has already started -static bool started = false; - -bool softi2c_master_start(void) -{ - if (started) { - // if started, do a restart condition - // set SDA to 1 - set_SDA(); - I2C_delay(); - set_SCL(); - uint8_t timeout = TIMEOUT; - while (read_SCL() == 0 && timeout) { // Clock stretching - I2C_delay(); - timeout--; - } - if (0 == timeout) { - return false; - } - - // Repeated start setup time, minimum 4.7us - I2C_delay(); - } - - if (read_SDA() == 0) { - return false; - } - - // SCL is high, set SDA from 1 to 0. - clear_SDA(); - I2C_delay(); - clear_SCL(); - started = true; - - return true; -} - -bool softi2c_master_stop(void) -{ - // set SDA to 0 - clear_SDA(); - I2C_delay(); - - set_SCL(); - uint8_t timeout = TIMEOUT; - while (read_SCL() == 0 && timeout) { // Clock stretching - I2C_delay(); - timeout--; - } - if (0 == timeout) { - return false; - } - - I2C_delay(); // Stop bit setup time, minimum 4us - - // SCL is high, set SDA from 0 to 1 - set_SDA(); - I2C_delay(); - - if (read_SDA() == 0) { - return false; - } - - started = false; - - return true; -} - -// Write a bit to I²C bus -static bool softi2c_master_write_bit(bool bit) { - // set data bit - if (bit) { - set_SDA(); - } else { - clear_SDA(); - } - I2C_delay(); // SDA change propagation delay - set_SCL(); // Set SCL high to indicate a new valid SDA value is available - I2C_delay(); // Wait for SDA value to be read by slave, minimum of 4us for standard mode - uint8_t timeout = TIMEOUT; - while (read_SCL() == 0 && timeout) { // Clock stretching - I2C_delay(); - timeout--; - } - if (0 == timeout) { - return false; - } - - // SCL is high, now data is valid - if (bit && (read_SDA() == 0)) { // If SDA is high, check that nobody else is driving SDA - return false; - } - clear_SCL(); // Clear the SCL to low in preparation for next change - - return true; -} - -// Read a bit from I²C bus -static bool softi2c_master_read_bit(void) { - set_SDA(); // Let the slave drive data - I2C_delay(); // Wait for SDA value to be written by slave, minimum of 4us for standard mode - set_SCL(); // Set SCL high to indicate a new valid SDA value is available - uint8_t timeout = TIMEOUT; - while (read_SCL() == 0 && timeout) { // Clock stretching - I2C_delay(); - timeout--; - } - if (0 == timeout) { - return false; - } - I2C_delay(); // Wait for SDA value to be written by slave, minimum of 4us for standard mode - const bool bit = read_SDA(); // SCL is high, read out bit - clear_SCL(); // Set SCL low in preparation for next operation - - return bit; -} - -// Write a byte to I2C bus. Return true if ACK by the slave. -static bool softi2c_master_write_byte(uint8_t byte) -{ - for (uint8_t bit = 0; bit < 8; ++bit) { - softi2c_master_write_bit((byte & 0x80) != 0); - byte <<= 1; - } - - const bool nack = softi2c_master_read_bit(); - - return !nack; -} - -// Read a byte from I²C bus -static uint8_t softi2c_master_read_byte(bool nack) -{ - uint8_t byte = 0; - for (uint8_t bit = 0; bit < 8; ++bit) { - byte = (byte << 1) | softi2c_master_read_bit(); - } - - softi2c_master_write_bit(nack); - - return byte; -} - -bool softi2c_master_select_slave(uint8_t slave, bool write) -{ - if (!softi2c_master_start()) { // send (re-)start condition - return false; - } - const uint8_t byte = (slave << 1) | (write ? 0 : 1); // select slave, with read/write flag - return softi2c_master_write_byte(byte); // select slave -} - -bool softi2c_master_read(uint8_t* data, uint16_t data_size) -{ - if (NULL == data || 0 == data_size) { // no data to read - return false; - } - - for (uint16_t i = 0; i < data_size; i++) { // read bytes - IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog - if (1 == (data_size - i)) { // last byte - data[i] = softi2c_master_read_byte(true); // NACK after reading byte - } else { - data[i] = softi2c_master_read_byte(false); // ACK after reading byte - } - } - - return softi2c_master_stop(); -} - -bool softi2c_master_write(const uint8_t* data, uint16_t data_size) -{ - if (NULL == data || 0 == data_size) { // no data to read - return true; // we don't indicate an error because the stop is done separately - } - - // write data - for (uint16_t i = 0; i < data_size; i++) { // write bytes - if (!softi2c_master_write_byte(data[i])) { // write byte - return false; - } - } - - return true; -} - -bool softi2c_master_slave_read(uint8_t slave, uint8_t* data, uint16_t data_size) -{ - if (NULL == data && data_size > 0) { // no data to read - return false; - } - - if (!softi2c_master_select_slave(slave, false)) { // select slave to read - softi2c_master_stop(); - return false; - } - if (NULL != data && data_size > 0) { // only read data if needed - if (!softi2c_master_read(data, data_size)) { // read data (includes stop) - return false; - } - } - - return true; -} - -bool softi2c_master_slave_write(uint8_t slave, const uint8_t* data, uint16_t data_size) -{ - if (NULL == data && data_size > 0) { // no data to read - return false; - } - - bool rc = false; - if (!softi2c_master_select_slave(slave, true)) { // select slave to write - goto error; - } - if (NULL != data && data_size > 0) { // write data only is some is available - if (!softi2c_master_write(data, data_size)) { // write data - goto error; - } - } - - rc = true; // all went well -error: - rc = softi2c_master_stop() && rc; // sent stop condition - return rc; -} - -bool softi2c_master_address_read(uint8_t slave, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size) -{ - if (address_size > 0 && NULL == address) { - return false; - } - if (data_size > 0 && NULL == data) { - return false; - } - - bool rc = false; - rc = softi2c_master_select_slave(slave, true); // select slave to write - if (!rc) { - goto error; - } - - // write address - if (NULL != address && address_size > 0) { - rc = softi2c_master_write(address, address_size); // send memory address - if (!rc) { - goto error; - } - } - // read data - if (NULL != data && data_size > 0) { - rc = softi2c_master_select_slave(slave, false); // re-select slave to read - if (!rc) { - goto error; - } - rc = softi2c_master_read(data, data_size); // read memory (includes stop) - if (!rc) { - goto error; - } - } else { - softi2c_master_stop(); // sent stop condition - } - - rc = true; -error: - if (!rc) { // only send stop on error - softi2c_master_stop(); // sent stop condition - } - return rc; -} - -bool softi2c_master_address_write(uint8_t slave, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size) -{ - if (address_size > 0 && NULL == address) { - return false; - } - if (data_size > 0 && NULL == data) { - return false; - } - - bool rc = false; - rc = softi2c_master_select_slave(slave, true); // select slave to write - if (!rc) { - goto error; - } - - if (address_size && address) { - rc = softi2c_master_write(address, address_size); // send memory address - if (!rc) { - goto error; - } - } - if (data_size && data) { - rc = softi2c_master_write(data, data_size); // send memory data - if (!rc) { - goto error; - } - } - - rc = softi2c_master_stop(); // sent stop condition - if (!rc) { - return false; - } - - rc = true; // all went fine -error: - if (!rc) { - softi2c_master_stop(); // send stop on error - } - return rc; -} diff --git a/softi2c_master.h b/softi2c_master.h deleted file mode 100644 index 0cd8c27..0000000 --- a/softi2c_master.h +++ /dev/null @@ -1,83 +0,0 @@ -/** library to communicate using I²C as master, implemented in software - * @file - * @author King Kévin - * @copyright SPDX-License-Identifier: GPL-3.0-or-later - * @date 2021 - */ -#pragma once - -/** setup I²C peripheral - * @param[in] freq_khz desired clock frequency, in kHz - * @return if I²C bus is ready - */ -bool softi2c_master_setup(uint16_t freq_khz); -/** release I²C peripheral */ -void softi2c_master_release(void); -/** send start condition - * @return if start sent (else arbitration lost) - */ -bool softi2c_master_start(void); -/** sent stop condition - * @param[in] i2c I²C base address - * @return if stop sent (else arbitration lost) - */ -bool softi2c_master_stop(void); -/** select I²C slave device - * @param[in] slave I²C address of slave device to select - * @param[in] write this transaction will be followed by a read (false) or write (true) operation - * @return if slave ACKed - * @note includes (re-)start condition - */ -bool softi2c_master_select_slave(uint8_t slave, bool write); -/** read data over I²C - * @param[out] data array to store bytes read - * @param[in] data_size number of bytes to read - * @return if read succeeded (else arbitration lost) - * @warning the slave device must be selected before this operation - * @note includes sending stop (after having NACKed last received byte) - */ -bool softi2c_master_read(uint8_t* data, uint16_t data_size); -/** write data over I²C - * @param[in] data array of byte to write to slave - * @param[in] data_size number of bytes to write - * @return if write succeeded (else data has been NACKed) - * @warning the slave device must be selected before this operation - * @note no stop condition is sent at the end, allowing multiple writes - */ -bool softi2c_master_write(const uint8_t* data, uint16_t data_size); -/** read data from slave device - * @param[in] slave I²C address of slave device to select - * @param[out] data array to store bytes read - * @param[in] data_size number of bytes to read - * @return if read succeeded (else arbitration has been lost) - * @note start and stop conditions are included - */ -bool softi2c_master_slave_read(uint8_t slave, uint8_t* data, uint16_t data_size); -/** write data to slave device - * @param[in] slave I²C address of slave device to select - * @param[in] data array of byte to write to slave - * @param[in] data_size number of bytes to write - * @return if write succeeded - * @note start and stop conditions are included - */ -bool softi2c_master_slave_write(uint8_t slave, const uint8_t* data, uint16_t data_size); -/** read data at specific address from an I²C memory slave - * @param[in] slave I²C address of slave device to select - * @param[in] address memory address of slave to read from - * @param[in] address_size address size in bytes - * @param[out] data array to store bytes read - * @param[in] data_size number of bytes to read - * @return if read succeeded - * @note start and stop conditions are included - */ -bool softi2c_master_address_read(uint8_t slave, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size); -/** write data at specific address on an I²C memory slave - * @param[in] slave I²C address of slave device to select - * @param[in] address memory address of slave to write to - * @param[in] address_size address size in bytes - * @param[in] data array of byte to write to slave - * @param[in] data_size number of bytes to write - * @return if write succeeded - * @note start and stop conditions are included - */ -bool softi2c_master_address_write(uint8_t slave, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size);