Skip to content

File syn_soft_i2c.c

File List > drivers > syn_soft_i2c.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_SOFT_I2C) || SYN_USE_SOFT_I2C

#include "../util/syn_assert.h"
#include "syn_gpio.h"
#include "syn_soft_i2c.h"

static void i2c_delay(const SYN_SoftI2C *i2c)
{
    for (volatile uint32_t i = 0; i < i2c->delay_loops; i++) {
        // NOP loop
    }
}

static void sda_high(const SYN_SoftI2C *i2c)
{
    if (i2c == NULL)
        return;
    syn_gpio_write(i2c->sda, SYN_GPIO_HIGH);
    // Alternatively: syn_gpio_init(i2c->sda, SYN_GPIO_INPUT) for pseudo open-drain
}

static void sda_low(const SYN_SoftI2C *i2c)
{
    if (i2c == NULL)
        return;
    // syn_gpio_init(i2c->sda, SYN_GPIO_OUTPUT);
    syn_gpio_write(i2c->sda, SYN_GPIO_LOW);
}

static void scl_high(const SYN_SoftI2C *i2c)
{
    if (i2c == NULL)
        return;
    syn_gpio_write(i2c->scl, SYN_GPIO_HIGH);

    // Clock stretching support: wait while SCL is held low by a slave
    // (Requires SCL to be configured as Open-Drain input)
    uint32_t timeout = 10000;
    while (syn_gpio_read(i2c->scl) == SYN_GPIO_LOW && timeout > 0) {
        timeout--;
    }
}

static void scl_low(const SYN_SoftI2C *i2c)
{
    syn_gpio_write(i2c->scl, SYN_GPIO_LOW);
}

void syn_soft_i2c_init(SYN_SoftI2C *i2c, SYN_GPIO_Pin scl, SYN_GPIO_Pin sda, uint32_t delay_loops)
{
    SYN_ASSERT(i2c != NULL);
    i2c->scl = scl;
    i2c->sda = sda;
    i2c->delay_loops = delay_loops;

    // We assume the HAL supports Open-Drain mode
    syn_gpio_init(i2c->scl, SYN_GPIO_OUTPUT_OD);
    syn_gpio_init(i2c->sda, SYN_GPIO_OUTPUT_OD);

    sda_high(i2c);
    scl_high(i2c);
}

void syn_soft_i2c_start(const SYN_SoftI2C *i2c)
{
    if (!i2c)
        return;
    sda_high(i2c);
    scl_high(i2c);
    i2c_delay(i2c);
    sda_low(i2c);
    i2c_delay(i2c);
    scl_low(i2c);
    i2c_delay(i2c);
}

void syn_soft_i2c_stop(const SYN_SoftI2C *i2c)
{
    if (!i2c)
        return;
    scl_low(i2c);
    i2c_delay(i2c);
    sda_low(i2c);
    i2c_delay(i2c);
    scl_high(i2c);
    i2c_delay(i2c);
    sda_high(i2c);
    i2c_delay(i2c);
}

bool syn_soft_i2c_write(const SYN_SoftI2C *i2c, uint8_t data)
{
    if (!i2c)
        return false;
    for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
        if (data & mask) {
            sda_high(i2c);
        } else {
            sda_low(i2c);
        }
        i2c_delay(i2c);
        scl_high(i2c);
        i2c_delay(i2c);
        scl_low(i2c);
    }

    // Read ACK
    sda_high(i2c); // Release SDA
    i2c_delay(i2c);
    scl_high(i2c);
    i2c_delay(i2c);
    bool ack = (syn_gpio_read(i2c->sda) == SYN_GPIO_LOW);
    scl_low(i2c);
    i2c_delay(i2c);

    return ack;
}

uint8_t syn_soft_i2c_read(const SYN_SoftI2C *i2c, bool ack)
{
    if (!i2c)
        return 0;
    uint8_t data = 0;

    sda_high(i2c); // Release SDA
    for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
        i2c_delay(i2c);
        scl_high(i2c);
        i2c_delay(i2c);
        if (syn_gpio_read(i2c->sda) == SYN_GPIO_HIGH) {
            data |= mask;
        }
        scl_low(i2c);
    }

    // Send ACK/NACK
    if (ack) {
        sda_low(i2c);
    } else {
        sda_high(i2c);
    }
    i2c_delay(i2c);
    scl_high(i2c);
    i2c_delay(i2c);
    scl_low(i2c);
    sda_high(i2c);

    return data;
}

bool syn_soft_i2c_write_read(SYN_SoftI2C *i2c, uint8_t dev_addr, const uint8_t *tx_data,
                             size_t tx_len, uint8_t *rx_data, size_t rx_len)
{
    SYN_ASSERT(i2c != NULL);

    /* Write phase */
    if (tx_len > 0) {
        syn_soft_i2c_start(i2c);
        if (!syn_soft_i2c_write(i2c, (uint8_t)(dev_addr << 1))) {
            syn_soft_i2c_stop(i2c);
            return false; /* NACK on address */
        }
        for (size_t i = 0; i < tx_len; i++) {
            if (!syn_soft_i2c_write(i2c, tx_data[i])) {
                syn_soft_i2c_stop(i2c);
                return false; /* NACK on data */
            }
        }
    }

    /* Read phase */
    if (rx_len > 0) {
        syn_soft_i2c_start(i2c); /* Repeated start (or first start if tx_len==0) */
        if (!syn_soft_i2c_write(i2c, (uint8_t)((dev_addr << 1) | 1))) {
            syn_soft_i2c_stop(i2c);
            return false; /* NACK on address */
        }
        for (size_t i = 0; i < rx_len; i++) {
            rx_data[i] = syn_soft_i2c_read(i2c, (i < rx_len - 1));
        }
    }

    syn_soft_i2c_stop(i2c);
    return true;
}

#endif /* SYN_USE_SOFT_I2C */