#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_TypeDef} *I2Cx * @return {*} * @note */ static void i2c_reset(I2C_TypeDef *I2Cx) { // Disable the I2C peripheral LL_I2C_Disable(I2Cx); // Re-enable the I2C peripheral LL_I2C_Enable(I2Cx); } /** * @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); if (LL_I2C_IsActiveFlag_BUSY(handle->i2c) == 1) { i2c_reset(handle->i2c); // xsh:重置I2C,修复一段时间后无法读写的问题 } uint16_t count = 2000; 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 非阻塞模式下使用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->i2c); // xsh:重置I2C,修复一段时间后无法读写的问题 } uint16_t count = 5000; 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 = 5000; while (!handle->tx_dma_ok) { if (count-- == 0) { handle->tx_dma_ok = TRUE; return FALSE; } } count = 5000; 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 = 10000; 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 创建一个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结构体 return handle; } /** * @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 设置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); } } /** * @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); } } /*下面是内部实现方法*/ /** * @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); }