/********************************************************************** * Copyright: (C)2024 LingYun IoT System Studio * Author: GuoWenxue * * 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 #include #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; }