470 lines
16 KiB
C
470 lines
16 KiB
C
/** library to communicate using I²C as master
|
|
* @file
|
|
* @author King Kévin <kingkevin@cuvoodoo.info>
|
|
* @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 <stdint.h> // standard integer types
|
|
#include <stdbool.h> // boolean types
|
|
#include <stdlib.h> // 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
|
|
}
|