/**********************************************************************
|
* Copyright: (C)2024 LingYun IoT System Studio
|
* Author: GuoWenxue<guowenxue@gmail.com>
|
*
|
* Description: The purpose of this code is to provide a simple C library,
|
* which providing software bit-bang of the I2C protocol on
|
* any GPIO pins for ISKBoard.
|
*
|
* ChangeLog:
|
* Version Date Author Description
|
* V1.0.0 2024.08.29 GuoWenxue Release initial version
|
*
|
***********************************************************************/
|
|
#include <stdbool.h>
|
#include <stdio.h>
|
#include "miscdev.h" /* udelay() */
|
#include "i2c_bitbang.h"
|
|
/* I2C1 bus instance for SHT20, RTC, OLED on board */
|
i2c_bus_t i2c1_bus =
|
{
|
.lock = ATOMIC_VAR_INIT(false),
|
.addr = 0x00,
|
.scl = {GPIOB, GPIO_PIN_6},
|
.sda = {GPIOB, GPIO_PIN_7},
|
};
|
|
/* I2C2 bus instance on MikroBUS */
|
i2c_bus_t i2c2_bus =
|
{
|
.lock = ATOMIC_VAR_INIT(false),
|
.addr = 0x00,
|
.scl = {GPIOB, GPIO_PIN_10},
|
.sda = {GPIOB, GPIO_PIN_11},
|
};
|
|
/*
|
*+---------------------------------+
|
*| I2C GPIO port Level functions |
|
*+---------------------------------+
|
*/
|
|
/**
|
* @brief Initialize I2C GPIO pins
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_gpio_init(i2c_bus_t *bus)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
/* Enable GPIO clocks */
|
gpio_clk_enable(bus->scl.group);
|
gpio_clk_enable(bus->sda.group);
|
|
/* Configure SCL pin */
|
GPIO_InitStruct.Pin = bus->scl.pin;
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; /* Open-drain output */
|
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
HAL_GPIO_Init(bus->scl.group, &GPIO_InitStruct);
|
|
/* Configure SDA pin */
|
GPIO_InitStruct.Pin = bus->sda.pin;
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; /* Open-drain output */
|
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
HAL_GPIO_Init(bus->sda.group, &GPIO_InitStruct);
|
|
/* Set both pins to HIGH (idle state) */
|
HAL_GPIO_WritePin(bus->scl.group, bus->scl.pin, GPIO_PIN_SET);
|
HAL_GPIO_WritePin(bus->sda.group, bus->sda.pin, GPIO_PIN_SET);
|
}
|
|
/**
|
* @brief Set SCL pin level
|
* @param bus: pointer to I2C bus structure
|
* @param level: LOW or HIGH
|
* @retval None
|
*/
|
static inline void i2c_scl_set(i2c_bus_t *bus, uint8_t level)
|
{
|
HAL_GPIO_WritePin(bus->scl.group, bus->scl.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
|
}
|
|
/**
|
* @brief Set SDA pin level
|
* @param bus: pointer to I2C bus structure
|
* @param level: LOW or HIGH
|
* @retval None
|
*/
|
static inline void i2c_sda_set(i2c_bus_t *bus, uint8_t level)
|
{
|
HAL_GPIO_WritePin(bus->sda.group, bus->sda.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
|
}
|
|
/**
|
* @brief Read SDA pin level
|
* @param bus: pointer to I2C bus structure
|
* @retval pin level (0 or 1)
|
*/
|
static uint8_t i2c_sda_read(i2c_bus_t *bus)
|
{
|
return (HAL_GPIO_ReadPin(bus->sda.group, bus->sda.pin) == GPIO_PIN_SET) ? 1 : 0;
|
}
|
|
|
/*
|
*+---------------------------------+
|
*| I2C bus control function |
|
*+---------------------------------+
|
*/
|
|
/**
|
* @brief 工业级模拟 I2C 总线初始化 (带硬件死锁九脉冲自愈)
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_init(i2c_bus_t *bus)
|
{
|
if (!bus)
|
{
|
return;
|
}
|
|
/* 1. 初始化 I2C GPIO 口 */
|
i2c_gpio_init(bus);
|
|
/* 2. 连续发送 9 个 SCL 时钟脉冲 */
|
i2c_sda_set(bus, HIGH);
|
for (int i = 0; i < 9; i++)
|
{
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
}
|
|
/* 3. 补发一个结束信号,强迫所有从机回到空闲态 */
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
i2c_sda_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
i2c_sda_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
}
|
|
/**
|
* @brief Deinitialize I2C GPIO pins
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_deinit(i2c_bus_t *bus)
|
{
|
HAL_GPIO_DeInit(bus->scl.group, bus->scl.pin);
|
HAL_GPIO_DeInit(bus->sda.group, bus->sda.pin);
|
atomic_store(&bus->lock, false);
|
}
|
|
/**
|
* @brief 绑定设备及地址到 I2C 总线上
|
*/
|
int i2c_bind_dev(i2c_dev_t *dev, i2c_bus_t *bus, uint8_t dev_addr)
|
{
|
if (!dev || !bus)
|
{
|
return -1;
|
}
|
|
dev->bus = bus;
|
dev->dev_addr = dev_addr;
|
|
return 0;
|
}
|
|
/**
|
* @brief 将I2C总线加锁让设备独占总线
|
*/
|
int i2c_lock_dev(i2c_dev_t *dev)
|
{
|
if( !dev || !dev->bus )
|
{
|
return -1;
|
}
|
|
/* 安全上锁,若总线被占用则在此阻塞等待 */
|
if( I2C_OK != i2c_lock(dev->bus) )
|
{
|
return -2;
|
}
|
|
return 0;
|
}
|
|
/**
|
* @brief 释放设备的 I2C 总线锁
|
*/
|
void i2c_free_dev(i2c_dev_t *dev)
|
{
|
if (!dev || !dev->bus)
|
{
|
return;
|
}
|
|
/* 1. 释放总线锁,让其他外设可以竞争总线 */
|
i2c_unlock(dev->bus);
|
}
|
|
/**
|
* @brief Lock I2C bus for exclusive access (bare metal mutex)
|
* @param bus: pointer to I2C bus structure
|
* @retval I2C_OK if success, I2C_BUSY if bus is locked
|
*/
|
i2c_status_t i2c_lock(i2c_bus_t *bus)
|
{
|
uint32_t timeout = 10000;
|
|
/* atomic_compare_exchange_weak 会原子性地检查 bus->lock 是否为 false。
|
* 如果是 false,就把它设置为 true,并返回 true;
|
* 如果已经是 true,则返回 false,进入循环等待。
|
*/
|
while (timeout > 0)
|
{
|
_Bool expected = false;
|
if (atomic_compare_exchange_weak(&bus->lock, &expected, true))
|
{
|
return I2C_OK;
|
}
|
udelay(10);
|
timeout--;
|
}
|
|
return I2C_BUSY;
|
}
|
|
/**
|
* @brief Unlock I2C bus
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_unlock(i2c_bus_t *bus)
|
{
|
atomic_store(&bus->lock, false);
|
}
|
|
/**
|
* @brief Scan I2C bus for connected devices
|
* @param bus: pointer to I2C bus structure
|
* @retval none
|
*/
|
void i2c_scan(i2c_bus_t *bus)
|
{
|
uint8_t addr;
|
int found = 0;
|
|
if (i2c_lock(bus) != I2C_OK) {
|
printf("I2C bus lock failed!\r\n");
|
return;
|
}
|
|
printf("Scanning I2C bus...\r\n");
|
|
/*10-bit 地址前缀保留区(0x70 ~ 0x77)*/
|
for (addr = 0x03; addr < 0x70; addr++) {
|
i2c_start(bus);
|
i2c_write_byte(bus, (addr << 1) | 0x01); // read mode
|
|
if (i2c_wait_ack(bus) == I2C_ACK) {
|
printf("Found device at 0x%02X\r\n", addr);
|
found = 1;
|
}
|
|
i2c_stop(bus);
|
udelay(100);
|
}
|
|
i2c_unlock(bus);
|
|
if (!found) {
|
printf("No I2C devices found.\r\n");
|
}
|
}
|
|
|
/*
|
*+---------------------------------+
|
*| I2C timing sequence function |
|
*+---------------------------------+
|
*/
|
|
/**
|
* @brief Generate I2C start condition
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_start(i2c_bus_t *bus)
|
{
|
/* Start Condition Timing Sequence:
|
______
|
SCL: |___
|
_____
|
SDA: |_____
|
*/
|
|
/* 无论前序是空闲还是刚结束 ACK,先确保 SCL 为低时 SDA 释放为高 */
|
i2c_sda_set(bus, HIGH);
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
|
/* 此时拉高 SCL,SDA 必然处于稳定的高电平 */
|
i2c_scl_set(bus, HIGH);
|
i2c_sda_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
|
/* 在 SCL 为高期间拉低 SDA,制造标准的 START / RESTART 沿 */
|
i2c_sda_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
}
|
|
/**
|
* @brief Generate I2C stop condition
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_stop(i2c_bus_t *bus)
|
{
|
/* SCL Stop Condition Timing Sequence:
|
_______
|
SCL: ___|
|
_____
|
SDA: _____|
|
*/
|
|
i2c_scl_set(bus, LOW);
|
i2c_sda_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
|
i2c_sda_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
}
|
|
/**
|
* @brief Wait for ACK from slave
|
* @param bus: pointer to I2C bus structure
|
* @retval I2C_ACK or I2C_NACK
|
*/
|
i2c_ack_t i2c_wait_ack(i2c_bus_t *bus)
|
{
|
uint16_t timeout = 0;
|
|
i2c_sda_set(bus, HIGH); /* 释放SDA线允许从机控制 */
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US); /* 确保SCL高电平波形稳定 */
|
|
while (i2c_sda_read(bus))
|
{
|
timeout++;
|
if (timeout > I2C_TIMEOUT_COUNT)
|
{
|
break; /* 超时退出,返回NACK */
|
}
|
}
|
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
|
return (timeout > I2C_TIMEOUT_COUNT) ? I2C_NACK : I2C_ACK;
|
}
|
|
/**
|
* @brief Send ACK to slave
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_send_ack(i2c_bus_t *bus)
|
{
|
i2c_scl_set(bus, LOW);
|
i2c_sda_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
}
|
|
/**
|
* @brief Send NACK to slave
|
* @param bus: pointer to I2C bus structure
|
* @retval None
|
*/
|
void i2c_send_nack(i2c_bus_t *bus)
|
{
|
i2c_scl_set(bus, LOW);
|
i2c_sda_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US);
|
}
|
|
/**
|
* @brief Write one byte to I2C bus
|
* @param bus: pointer to I2C bus structure
|
* @param byte: byte to write
|
* @retval None
|
*/
|
void i2c_write_byte(i2c_bus_t *bus, uint8_t byte)
|
{
|
uint8_t i;
|
|
i2c_scl_set(bus, LOW);
|
|
for (i = 0; i < 8; i++)
|
{
|
i2c_sda_set(bus, (byte & 0x80) ? HIGH : LOW);
|
udelay(I2C_DELAY_US); /* 数据建立时间 Setup Time */
|
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US); /* 保持高电平采样 */
|
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US); /* 确保时钟低电平持续时间 */
|
|
byte <<= 1;
|
}
|
}
|
|
/**
|
* @brief Read one byte from I2C bus
|
* @param bus: pointer to I2C bus structure
|
* @retval received byte
|
*/
|
uint8_t i2c_read_byte(i2c_bus_t *bus)
|
{
|
uint8_t i;
|
uint8_t byte = 0;
|
|
i2c_sda_set(bus, HIGH);
|
udelay(I2C_DELAY_US);
|
|
for (i = 0; i < 8; i++)
|
{
|
byte <<= 1;
|
|
i2c_scl_set(bus, HIGH);
|
udelay(I2C_DELAY_US); /* 延时确保 SCL 电平平稳后采样 */
|
|
if (i2c_sda_read(bus))
|
{
|
byte |= 0x01;
|
}
|
|
i2c_scl_set(bus, LOW);
|
udelay(I2C_DELAY_US); /* 确保时钟低电平持续时间 */
|
}
|
|
return byte;
|
}
|
|
|
/*
|
*+-----------------------------------+
|
*| I2C read/write function |
|
*+-----------------------------------+
|
*/
|
|
/**
|
* @brief Write data to I2C slave device
|
* @param bus: pointer to I2C bus structure
|
* @param addr: 7-bit slave address
|
* @param reg: register address
|
* @param data: pointer to data buffer
|
* @param len: data length
|
* @retval I2C status
|
*/
|
i2c_status_t i2c_write(i2c_bus_t *bus, uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len)
|
{
|
uint16_t i;
|
int rv = I2C_OK;
|
|
i2c_start(bus);
|
|
/* Send slave address + write bit */
|
i2c_write_byte(bus, (addr << 1) | 0x00);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* Send register address */
|
i2c_write_byte(bus, reg);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* Send data */
|
for (i = 0; i < len; i++)
|
{
|
i2c_write_byte(bus, data[i]);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
}
|
|
cleanup:
|
i2c_stop(bus);
|
return rv;
|
}
|
|
/**
|
* @brief Read data from I2C slave device
|
* @param bus: pointer to I2C bus structure
|
* @param addr: 7-bit slave address
|
* @param reg: register address
|
* @param data: pointer to data buffer
|
* @param len: data length
|
* @retval I2C status
|
*/
|
i2c_status_t i2c_read(i2c_bus_t *bus, uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len)
|
{
|
uint16_t i;
|
int rv = I2C_OK;
|
|
/* Write register address */
|
i2c_start(bus);
|
i2c_write_byte(bus, (addr << 1) | 0x00);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
i2c_write_byte(bus, reg);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* Restart and read data */
|
i2c_start(bus);
|
i2c_write_byte(bus, (addr << 1) | 0x01);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
for (i = 0; i < len; i++)
|
{
|
data[i] = i2c_read_byte(bus);
|
|
if (i < len - 1)
|
{
|
i2c_send_ack(bus);
|
}
|
else
|
{
|
i2c_send_nack(bus);
|
}
|
}
|
|
cleanup:
|
i2c_stop(bus);
|
return rv;
|
}
|
|
/**
|
* @brief Write data to I2C slave device without register address
|
* @param bus: pointer to I2C bus structure
|
* @param addr: 7-bit slave address
|
* @param data: pointer to data buffer
|
* @param len: data length
|
* @retval I2C status
|
*/
|
i2c_status_t i2c_write_no_reg(i2c_bus_t *bus, uint8_t addr, uint8_t *data, uint16_t len)
|
{
|
uint16_t i;
|
int rv = I2C_OK;
|
|
i2c_start(bus);
|
|
/* Send slave address + write bit */
|
i2c_write_byte(bus, (addr << 1) | 0x00);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* Send data */
|
for (i = 0; i < len; i++)
|
{
|
i2c_write_byte(bus, data[i]);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
}
|
|
cleanup:
|
i2c_stop(bus);
|
return rv;
|
}
|
|
/**
|
* @brief Read data from I2C slave device without register address
|
* @param bus: pointer to I2C bus structure
|
* @param addr: 7-bit slave address
|
* @param data: pointer to data buffer
|
* @param len: data length
|
* @retval I2C status
|
*/
|
i2c_status_t i2c_read_no_reg(i2c_bus_t *bus, uint8_t addr, uint8_t *data, uint16_t len)
|
{
|
uint16_t i;
|
int rv = I2C_OK;
|
|
i2c_start(bus);
|
|
/* Send slave address + read bit */
|
i2c_write_byte(bus, (addr << 1) | 0x01);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
rv = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* Read data */
|
for (i = 0; i < len; i++)
|
{
|
data[i] = i2c_read_byte(bus);
|
|
if (i < len - 1)
|
{
|
i2c_send_ack(bus);
|
}
|
else
|
{
|
i2c_send_nack(bus);
|
}
|
}
|
|
cleanup:
|
i2c_stop(bus);
|
return rv;
|
}
|
|
|
/*
|
*+-----------------------------------+
|
*| Linux I2C API–compatible function |
|
*+-----------------------------------+
|
*/
|
|
/* Functions compatible with the Linux I2C adapter API */
|
i2c_status_t i2c_transfer(i2c_bus_t *bus, i2c_msg_t *msgs, int num)
|
{
|
i2c_status_t status = I2C_OK;
|
int i, j;
|
|
for (i = 0; i < num; i++)
|
{
|
i2c_msg_t *msg = &msgs[i];
|
|
/* 1. 每条消息都以 Start (或 Repeated Start) 开始 */
|
i2c_start(bus);
|
|
/* 2. 发送从机地址 + 读写位 */
|
uint8_t dir = (msg->flags & I2C_M_RD) ? 0x01 : 0x00;
|
i2c_write_byte(bus, (msg->addr << 1) | dir);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
status = I2C_ERROR;
|
goto cleanup;
|
}
|
|
/* 3. 根据读写标志传输主体数据 */
|
if (msg->flags & I2C_M_RD)
|
{
|
/* 读模式 */
|
for (j = 0; j < msg->len; j++)
|
{
|
msg->buf[j] = i2c_read_byte(bus);
|
/* 只要不是当前消息的最后一个字节,就回复 ACK */
|
if (j < msg->len - 1)
|
i2c_send_ack(bus);
|
else
|
i2c_send_nack(bus);
|
}
|
}
|
else
|
{
|
/* 写模式 */
|
for (j = 0; j < msg->len; j++)
|
{
|
i2c_write_byte(bus, msg->buf[j]);
|
if (i2c_wait_ack(bus) != I2C_ACK)
|
{
|
status = I2C_ERROR;
|
goto cleanup;
|
}
|
}
|
}
|
}
|
|
cleanup:
|
/* 4. 只有当所有消息都传输完毕,或者中途出错时,才发送 Stop 释放总线 */
|
i2c_stop(bus);
|
|
return status;
|
}
|