375 lines
11 KiB
C
375 lines
11 KiB
C
#include "i2cs.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总线上发送的应答信号。
|
||
*/
|
||
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总线上读取一个字节。在读取一个字节后,需要发送应答信号。
|
||
*/
|
||
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,以确保正确的结束传输。
|
||
*/
|
||
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,以确保正确的结束传输。
|
||
*/
|
||
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);
|
||
}
|
||
}
|
||
|
||
void _write_12bit(i2c_t *handle, uint16_t data)
|
||
{
|
||
uint8_t hi = ((data >> 8) << 4) + ((uint8_t)data >> 4);
|
||
uint8_t lo = data << 4;
|
||
// 定义变量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;
|
||
// 低位只写4bit
|
||
for (i = 0; i < 8; i++)
|
||
{
|
||
// 如果data的最低位为1
|
||
if (lo & 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位
|
||
lo <<= 1;
|
||
// 设置scl的状态为0
|
||
gpios->scl->reset(*scl);
|
||
// 延时1ms
|
||
delay(handle);
|
||
}
|
||
// 等待ACK
|
||
_wait_ack(handle);
|
||
_write_byte(handle, hi);
|
||
// 等待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->interface.write_12bit = _write_12bit;
|
||
|
||
// 返回handle结构体
|
||
return handle;
|
||
}
|
||
|
||
/**
|
||
* @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 此方法是一个简单的延迟函数,它使用循环来执行指定数量的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);
|
||
}
|