driver/bsp/i2cs.c

670 lines
21 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "i2cs.h"
#include "main.h"
static inline void delay(i2c_t *handle); // 延时函数
static inline void _ack(i2c_t *handle); // 应答
static inline void _nack(i2c_t *handle); // 非应答
/**
* @brief 启动I2C总线
* @param {i2c_t} *handle - I2C总线句柄
* @note: 用于启动I2C总线的操作。在发送或接收数据之前需要先启动总线。
*/
static void _start(i2c_t *handle)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
// 获取gpios指针
i2c_gpio_group_t *gpios = &handle->gpios;
// 获取scl指针
gpio_t *scl = gpios->scl;
// 获取sda指针
gpio_t *sda = gpios->sda;
// 设置sda
gpios->sda->set(*sda);
// 设置scl
gpios->scl->set(*scl);
// 延时
delay(handle);
// 重置sda
gpios->sda->reset(*sda);
// 延时
delay(handle);
// 重置scl
gpios->scl->reset(*scl);
// 延时
delay(handle);
}
/**
* @brief 停止I2C总线
* @param {i2c_t} *handle - I2C总线句柄
* @note: 用于停止I2C总线的操作。在发送或接收数据之后需要先停止总线。
*/
static void _stop(i2c_t *handle)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
i2c_gpio_group_t *gpios = &handle->gpios;
gpio_t *scl = gpios->scl;
gpio_t *sda = gpios->sda;
gpios->scl->reset(*scl);
gpios->sda->reset(*sda);
delay(handle);
gpios->scl->set(*scl);
gpios->sda->set(*sda);
delay(handle);
}
/**
* @brief 等待应答信号
* @param {i2c_t} *handle - I2C总线句柄
* @return {BOOL} - 等待成功返回TRUE否则返回FALSE
* @note: 用于等待I2C总线上发送的应答信号。
*/
static BOOL _wait_ack(i2c_t *handle)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
uint8_t count = 0;
i2c_gpio_group_t *gpios = &handle->gpios;
gpio_t *scl = gpios->scl;
gpio_t *sda = gpios->sda;
gpios->sda->set(*sda);
gpios->scl->set(*scl);
delay(handle);
while (gpios->sda->read(*sda))
{
count++;
if (count > 250)
{
_stop(handle);
return FALSE;
}
}
gpios->scl->reset(*scl);
delay(handle);
return TRUE;
}
/**
* @brief 读取一个字节
* @param {i2c_t} *handle - I2C总线句柄
* @param {BOOL} ack - 应答信号标志
* @return {uint8_t} - 读取到的字节
* @note: 用于从I2C总线上读取一个字节。在读取一个字节后需要发送应答信号。
*/
static uint8_t _read_byte(i2c_t *handle, BOOL ack)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
uint8_t i = 0, receive = 0;
i2c_gpio_group_t *gpios = &handle->gpios;
gpio_t *scl = gpios->scl;
gpio_t *sda = gpios->sda;
for (i = 0; i < 8; i++)
{
gpios->sda->set(*sda);
gpios->scl->set(*scl);
receive <<= 1;
delay(handle);
if (gpios->sda->read(*sda))
receive++;
gpios->scl->reset(*scl);
delay(handle);
}
if (TRUE == ack)
{
_ack(handle);
}
else
{
_nack(handle);
}
return receive;
}
/**
* @brief 发送一个字节的数据到I2C总线上
* @param {i2c_t} *handle I2C总线的句柄
* @param {uint8_t} data 要发送的字节数据
* @return {*} 无
* @note: 该函数用于在I2C总线上发送一个字节的数据。它首先定义了一个循环用于遍历要发送的字节中的每一位。在循环中首先检查当前位是否为1如果是则设置SDA为1否则设置SDA为0。然后设置SCL为1延时1ms。接着将数据右移一位然后设置SCL为0延时1ms。当位遍历完后最后设置SDA为1以确保正确的结束传输。
*/
static void _write_byte(i2c_t *handle, uint8_t data)
{
// 定义变量i
uint8_t i = 0;
// 断言参数handle不为空
DBG_ASSERT(handle != NULL __DBG_LINE);
// 定义变量gpios
i2c_gpio_group_t *gpios = &handle->gpios;
// 定义变量scl
gpio_t *scl = gpios->scl;
// 定义变量sda
gpio_t *sda = gpios->sda;
// 遍历每一位
for (i = 0; i < 8; i++)
{
// 如果data的最低位为1
if (data & 0x80)
{
// 设置sda的状态为1
gpios->sda->set(*sda);
}
// 否则设置sda的状态为0
else
{
// 设置sda的状态为0
gpios->sda->reset(*sda);
}
// 设置scl的状态为1
gpios->scl->set(*scl);
// 延时1ms
delay(handle);
// 将data右移1位
data <<= 1;
// 设置scl的状态为0
gpios->scl->reset(*scl);
// 延时1ms
delay(handle);
// 如果i等于7
if (i == 7)
{
// 设置sda的状态为1
gpios->sda->set(*sda);
}
}
}
/**
* @brief 发送一个字节的数据到I2C总线上
* @param {i2c_t} *handle I2C总线的句柄
* @param {uint16_t} data 要发送的字节数据
* @return {*} 无
* @note: 该函数用于在I2C总线上发送一个字节的数据。它首先定义了一个循环用于遍历要发送的字节中的每一位。在循环中首先检查当前位是否为1如果是则设置SDA为1否则设置SDA为0。然后设置SCL为1延时1ms。接着将数据右移一位然后设置SCL为0延时1ms。当位遍历完后最后设置SDA为1以确保正确的结束传输。
*/
static void _write_word(i2c_t *handle, uint16_t data)
{
// 循环写入2个字节
for (uint8_t i = 0; i < 2; i++)
{
// 将data的第i个字节写入i2c接口
_write_byte(handle, (uint8_t)(data >> (8 * i)));
// 等待ACK
_wait_ack(handle);
}
}
/**
* @brief 创建一个I2C总线设备
* @param {i2c_gpio_group_t} gpios I2C总线的GPIO配置
* @param {uint16_t} delay_ticks I2C总线的延时参数
* @return {i2c_t *} 创建的I2C总线设备句柄
* @note: 该函数用于创建一个I2C总线设备。它首先创建一个i2c_t结构体并将gpios和delay_ticks的内存地址复制到handle结构体中。然后它为handle结构体定义了start、stop、wait_ack、read_byte、write_byte和write_word函数。最后它返回handle结构体。
*/
i2c_t *i2c_create(i2c_gpio_group_t gpios, uint16_t delay_ticks)
{
// 创建一个i2c_t结构体
i2c_t *handle = (i2c_t *)osel_mem_alloc(sizeof(i2c_t));
// 将gpios的内存地址复制到handle结构体中
osel_memcpy((uint8_t *)&handle->gpios, (uint8_t *)&gpios, sizeof(i2c_gpio_group_t));
// 将delay_ticks的内存地址复制到handle结构体中
handle->delay_ticks = delay_ticks;
// 创建一个start函数
handle->interface.start = _start;
// 创建一个stop函数
handle->interface.stop = _stop;
// 创建一个wait_ack函数
handle->interface.wait_ack = _wait_ack;
// 创建一个read_byte函数
handle->interface.read_byte = _read_byte;
// 创建一个write_byte函数
handle->interface.write_byte = _write_byte;
// 创建一个write_word函数
handle->interface.write_word = _write_word;
handle->dead_count = 0;
// 返回handle结构体
return handle;
}
/**
* @brief 设置I2C器件地址
* @param {i2c_t} *handle
* @param {uint8_t} w_address 写地址
* @param {uint8_t} r_address 读地址
* @return {*}
* @note
*/
void i2c_dma_set_address(i2c_t *handle, uint8_t w_address, uint8_t r_address)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
handle->w_address = w_address;
handle->r_address = r_address;
}
/**
* @brief 释放I2C总线设备
* @param {i2c_t} *handle 需要释放的I2C总线设备句柄
* @return {*} 无
* @note: 该函数用于释放一个I2C总线设备。它首先检查handle是否为空如果不为空则释放所有的GPIO。最后释放handle的内存。
*/
void i2c_free(i2c_t *handle)
{
// 如果handle不为空则释放所有的gpio
if (NULL != handle)
{
gpio_free(handle->gpios.scl);
gpio_free(handle->gpios.sda);
osel_mem_free(handle);
}
}
/**
* @brief I2C DMA TX回调函数
* @param {i2c_t} handle
* @return {*}
* @note
*/
void i2c_dma_callback(i2c_t *handle)
{
// 检查输入参数是否为空
DBG_ASSERT(handle != NULL __DBG_LINE);
// 清除传输完成标志位
if (handle->dma_tx_cb != NULL)
{
handle->dma_tx_cb(handle);
}
if (handle->dma_rx_cb != NULL)
{
handle->dma_rx_cb(handle);
}
}
#if defined(STM32L4xx_LL_I2C_H)
static void i2c_reset(i2c_t *handle);
static BOOL _read_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size);
static BOOL _write_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size);
/**
* @brief 创建一个I2C总线设备 DMA
* @param {I2C_TypeDef} *i2c
* @param {DMA_TypeDef} *dma
* @param {uint16_t} rxsize
* @param {uint32_t} dma_rx_channel
* @param {uint16_t} txsize
* @param {uint32_t} dma_tx_channel
* @return {*}
* @note
*/
i2c_t *i2c_create_dma(I2C_TypeDef *i2c, DMA_TypeDef *dma, uint16_t rxsize, uint32_t dma_rx_channel,
i2cs_dma_callback *dma_rx_cb, uint16_t txsize, uint32_t dma_tx_channel, i2cs_dma_callback *dma_tx_cb)
{
i2c_t *handle = (i2c_t *)osel_mem_alloc(sizeof(i2c_t));
handle->i2c = i2c;
handle->dma = dma;
handle->dma_rx_channel = dma_rx_channel;
handle->dma_tx_channel = dma_tx_channel;
handle->rxbuf = (uint8_t *)osel_mem_alloc(rxsize);
handle->txbuf = (uint8_t *)osel_mem_alloc(txsize);
handle->rxsize = rxsize;
handle->txsize = txsize;
handle->tx_dma_ok = TRUE;
handle->interface.write_mem_dma = _write_mem_dma;
handle->interface.read_mem_dma = _read_mem_dma;
if (dma_rx_cb != NULL)
{
handle->dma_rx_cb = dma_rx_cb;
}
if (dma_tx_cb != NULL)
{
handle->dma_tx_cb = dma_tx_cb;
}
LL_DMA_DisableChannel(dma, dma_tx_channel);
LL_DMA_DisableChannel(dma, dma_rx_channel);
// TX
uint8_t *pTransmitBuffer = handle->txbuf;
LL_DMA_ConfigTransfer(dma, dma_tx_channel, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_PRIORITY_HIGH | LL_DMA_MODE_NORMAL | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | LL_DMA_MDATAALIGN_BYTE);
LL_DMA_ConfigAddresses(dma, dma_tx_channel, (uint32_t)pTransmitBuffer, (uint32_t)LL_I2C_DMA_GetRegAddr(i2c, LL_I2C_DMA_REG_DATA_TRANSMIT), LL_DMA_GetDataTransferDirection(dma, dma_tx_channel));
LL_DMA_SetPeriphRequest(dma, dma_tx_channel, LL_DMA_REQUEST_3);
// RX
uint8_t *pReceiveBuffer = handle->rxbuf;
LL_DMA_ConfigTransfer(dma, dma_rx_channel, LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_PRIORITY_HIGH | LL_DMA_MODE_NORMAL | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | LL_DMA_MDATAALIGN_BYTE);
LL_DMA_ConfigAddresses(dma, dma_rx_channel, (uint32_t)LL_I2C_DMA_GetRegAddr(i2c, LL_I2C_DMA_REG_DATA_RECEIVE), (uint32_t)pReceiveBuffer, LL_DMA_GetDataTransferDirection(dma, dma_rx_channel));
LL_DMA_SetPeriphRequest(dma, dma_rx_channel, LL_DMA_REQUEST_3);
LL_DMA_EnableIT_TC(dma, dma_tx_channel);
LL_DMA_EnableIT_TE(dma, dma_tx_channel);
LL_DMA_EnableIT_TC(dma, dma_rx_channel);
LL_DMA_EnableIT_TE(dma, dma_rx_channel);
LL_I2C_EnableDMAReq_TX(i2c);
LL_I2C_EnableDMAReq_RX(i2c);
LL_I2C_Enable(i2c);
return handle;
}
/**
* @brief 非阻塞模式下使用DMA从特定内存地址读取数据
* @param {i2c_t} *handle 指向一个i2c_t结构体其中包含指定I2C的配置信息
* @param {uint16_t} dev_address 目标设备地址在数据手册中设备7位地址值需要向左移动以调用接口
* @param {uint16_t} mem_address 内部内存地址
* @param {uint16_t} mem_addsize 内部内存地址大小
* @param {uint8_t} *data 数据缓冲区的指针
* @param {uint16_t} size 要发送的数据量
* @return {*}
* @note
*/
static BOOL _read_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
DBG_ASSERT(data != NULL __DBG_LINE);
if (LL_I2C_IsActiveFlag_BUSY(handle->i2c) == 1)
{
i2c_reset(handle); // xsh:重置I2C,修复一段时间后无法读写的问题
return FALSE;
}
uint16_t count = 2000;
handle->txsize = 0;
handle->tx_dma_ok = FALSE;
handle->rx_dma_ok = FALSE;
for (uint8_t i = mem_addsize; i > 0; i--)
{
handle->txbuf[handle->txsize++] = (uint8_t)(mem_address >> (8 * (i - 1)));
}
LL_DMA_SetDataLength(handle->dma, handle->dma_tx_channel, handle->txsize);
LL_DMA_EnableChannel(handle->dma, handle->dma_tx_channel);
LL_I2C_HandleTransfer(handle->i2c, handle->w_address, LL_I2C_ADDRSLAVE_7BIT, handle->txsize, LL_I2C_MODE_SOFTEND, LL_I2C_GENERATE_START_WRITE);
count = 2000;
while (!handle->tx_dma_ok)
{
if (count-- == 0)
{
handle->tx_dma_ok = TRUE;
return FALSE;
}
}
count = 2000;
while (LL_I2C_IsActiveFlag_TC(handle->i2c) != 1)
{
if (count-- == 0)
{
handle->tx_dma_ok = TRUE;
return FALSE;
}
}
handle->tx_dma_ok = FALSE;
handle->rx_dma_ok = FALSE;
handle->rxsize = size;
osel_memset(handle->rxbuf, 0, handle->rxsize);
LL_DMA_DisableChannel(handle->dma, handle->dma_tx_channel);
LL_DMA_SetDataLength(handle->dma, handle->dma_rx_channel, handle->rxsize);
LL_DMA_EnableChannel(handle->dma, handle->dma_rx_channel);
LL_I2C_HandleTransfer(handle->i2c, handle->r_address, LL_I2C_ADDRSLAVE_7BIT, handle->rxsize, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_RESTART_7BIT_READ);
count = 2000;
while (!LL_I2C_IsActiveFlag_STOP(handle->i2c))
{
if (count-- == 0)
{
handle->tx_dma_ok = TRUE;
return FALSE;
}
}
LL_DMA_DisableChannel(handle->dma, handle->dma_rx_channel);
LL_I2C_ClearFlag_STOP(handle->i2c);
osel_memcpy(data, handle->rxbuf, handle->rxsize);
return TRUE;
}
/**
* @brief 非阻塞模式下使用DMA将数据写入特定内存地址
* @param {i2c_t} *handle 指向一个i2c_t结构体其中包含指定I2C的配置信息
* @param {uint16_t} dev_address 目标设备地址在数据手册中设备7位地址值需要向左移动以调用接口
* @param {uint16_t} mem_address 内部内存地址
* @param {uint16_t} mem_addsize 内部内存地址大小
* @param {uint8_t} *data 数据缓冲区的指针
* @param {uint16_t} size 要发送的数据量
* @return {*}
* @note
*/
static BOOL _write_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
DBG_ASSERT(data != NULL __DBG_LINE);
uint16_t count = 2000;
if (LL_I2C_IsActiveFlag_BUSY(handle->i2c) == 1)
{
i2c_reset(handle); // xsh:重置I2C,修复一段时间后无法读写的问题
return FALSE;
}
handle->txsize = 0;
handle->tx_dma_ok = FALSE;
for (uint8_t i = mem_addsize; i > 0; i--)
{
handle->txbuf[handle->txsize++] = (uint8_t)(mem_address >> (8 * (i - 1)));
}
osel_memcpy(&handle->txbuf[handle->txsize], data, size);
handle->txsize += size;
LL_DMA_SetDataLength(handle->dma, handle->dma_tx_channel, handle->txsize);
LL_DMA_EnableChannel(handle->dma, handle->dma_tx_channel);
LL_I2C_HandleTransfer(handle->i2c, handle->w_address, LL_I2C_ADDRSLAVE_7BIT, handle->txsize, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);
count = 2000;
while (!handle->tx_dma_ok)
{
if (count-- == 0)
{
handle->tx_dma_ok = TRUE;
return FALSE;
}
}
count = 2000;
while (!LL_I2C_IsActiveFlag_STOP(handle->i2c))
{
if (count-- == 0)
{
handle->tx_dma_ok = TRUE;
return FALSE;
}
}
LL_DMA_DisableChannel(handle->dma, handle->dma_tx_channel);
LL_I2C_ClearFlag_STOP(handle->i2c);
return TRUE;
}
/**
* @brief I2C重置
* @param {I2C_TypeDef} *I2Cx
* @return {*}
* @note 解决I2C总线死锁问题
*/
static void i2c_reset(i2c_t *handle)
{
LL_I2C_Disable(handle->i2c);
LL_I2C_Enable(handle->i2c);
handle->dead_count++;
}
/**
* @brief I2C 中断回调函数
* @param {i2c_t} *handle
* @return {*}
* @note
*/
void i2c_ev_callback(i2c_t *handle)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
if (LL_I2C_IsActiveFlag_ADDR(handle->i2c))
{
/* Verify the Address Match with the OWN Slave address */
if (LL_I2C_GetAddressMatchCode(handle->i2c) == handle->w_address || LL_I2C_GetAddressMatchCode(handle->i2c) == handle->r_address)
{
/* Verify the transfer direction, a write direction, Slave enters receiver mode */
if (LL_I2C_GetTransferDirection(handle->i2c) == LL_I2C_DIRECTION_WRITE)
{
/* Clear ADDR flag value in ISR register */
LL_I2C_ClearFlag_ADDR(handle->i2c);
/* Enable Receive Interrupt */
LL_I2C_EnableIT_RX(handle->i2c);
}
else if (LL_I2C_GetTransferDirection(handle->i2c) == LL_I2C_DIRECTION_READ)
{
/* Clear ADDR flag value in ISR register */
LL_I2C_ClearFlag_ADDR(handle->i2c);
/* Enable Transmit Interrupt */
LL_I2C_EnableIT_TX(handle->i2c);
}
}
else
{
/* Clear ADDR flag value in ISR register */
LL_I2C_ClearFlag_ADDR(handle->i2c);
/* Call Error function */
DBG_ASSERT(FALSE __DBG_LINE);
}
}
/* Check NACK flag value in ISR register */
else if (LL_I2C_IsActiveFlag_NACK(handle->i2c))
{
/* End of Transfer */
LL_I2C_ClearFlag_NACK(handle->i2c);
}
/* Check RXNE flag value in ISR register */
else if (LL_I2C_IsActiveFlag_RXNE(handle->i2c))
{
/* Call function Slave Reception Callback */
}
/* Check TXIS flag value in ISR register */
else if (LL_I2C_IsActiveFlag_TXIS(handle->i2c))
{
/* Call function Slave Ready to Transmit Callback */
}
/* Check STOP flag value in ISR register */
else if (LL_I2C_IsActiveFlag_STOP(handle->i2c))
{
/* End of Transfer */
LL_I2C_ClearFlag_STOP(handle->i2c);
/* Check TXE flag value in ISR register */
if (!LL_I2C_IsActiveFlag_TXE(handle->i2c))
{
/* Flush TX buffer */
LL_I2C_ClearFlag_TXE(handle->i2c);
}
/* Call function Slave Complete Callback */
}
/* Check TXE flag value in ISR register */
else if (!LL_I2C_IsActiveFlag_TXE(handle->i2c))
{
/* Do nothing */
/* This Flag will be set by hardware when the TXDR register is empty */
/* If needed, use LL_I2C_ClearFlag_TXE() interface to flush the TXDR register */
}
else
{
/* Call Error function */
DBG_ASSERT(FALSE __DBG_LINE);
}
}
#endif
/*下面是内部实现方法*/
/**
* @brief 此方法是一个简单的延迟函数它使用循环来执行指定数量的NOP无操作指令。这个延迟函数的目的是在程序的执行中引入延迟这在需要精确定时的某些应用中很有用。延迟时间由输入参数“count”的值决定该参数指定要执行的NOP指令的数量。延迟时间通常以微秒或毫秒为单位测量具体取决于使用延迟的环境。
* @param {uint16_t} count NOP指令的数量
* @return {*}
*/
static inline void delay(i2c_t *handle)
{
// 断言参数handle不为空
DBG_ASSERT(handle != NULL __DBG_LINE);
// 定义循环计数变量count
uint16_t count = 0;
// 设置循环计数变量count的值为handle->delay_ticks
count = handle->delay_ticks;
// 循环计数变量count的值直到count的值为0
while (count--)
{
// 每次循环调用__NOP()函数
__NOP();
}
}
/**
* @brief 发送ACK信号
* @param {i2c_t} *handle I2C总线的句柄
* @return {*} 无
* @note: 该函数用于在I2C总线上发送ACK信号。它首先断言handle不为空然后获取gpios和scl、sda的指针。接着重置sda延时1ms设置scl为1延时1ms重置scl确保正确的结束传输。
*/
static inline void _ack(i2c_t *handle)
{
// 断言handle不为空
DBG_ASSERT(handle != NULL __DBG_LINE);
// 获取gpios指针
i2c_gpio_group_t *gpios = &handle->gpios;
// 获取scl指针
gpio_t *scl = gpios->scl;
// 获取sda指针
gpio_t *sda = gpios->sda;
// 重置sda
gpios->sda->reset(*sda);
// 延时
delay(handle);
// 设置scl
gpios->scl->set(*scl);
// 延时
delay(handle);
// 重置scl
gpios->scl->reset(*scl);
}
/**
* @brief 发送NACK信号
* @param {i2c_t} *handle I2C总线的句柄
* @return {*} 无
* @note: 该函数用于在I2C总线上发送NACK信号。它首先断言handle不为空然后获取gpios和scl、sda的指针。接着设置sda为1延时1ms设置scl为1延时1ms重置scl确保正确的结束传输。
*/
static inline void _nack(i2c_t *handle)
{
DBG_ASSERT(handle != NULL __DBG_LINE);
// 获取gpios指针
i2c_gpio_group_t *gpios = &handle->gpios;
// 获取scl指针
gpio_t *scl = gpios->scl;
// 获取sda指针
gpio_t *sda = gpios->sda;
// 设置sda引脚
gpios->sda->set(*sda);
// 等待延时
delay(handle);
// 设置scl引脚
gpios->scl->set(*scl);
// 等待延时
delay(handle);
// 重置scl引脚
gpios->scl->reset(*scl);
}