将温湿度传感器AM2320接在一个硬件I2C引脚上,使用本文封装好的接口与AM2320通信,读取温湿度信息。以此验证硬件I2C接口是否正常工作,其后在详细讲解是如何封装硬件I2C接口的。
龙芯1c库中硬件I2C接口使用示例
硬件I2C接口简介
I2C初始化i2c_init()
函数原型
/*
* 初始化指定i2c模块
* @i2c_info_p 某个i2c模块的信息
*/
void i2c_init(ls1c_i2c_info_t *i2c_info_p);
风格和STM32库类似,看到函数原型应该大概能猜到了。重点来看下入参“ls1c_i2c_info_t *i2c_info_p”,变量类型ls1c_i2c_info_t的定义如下
// 硬件I2C信息
typedef struct
{
ls1c_i2c_t I2Cx; // i2c模块编号
unsigned long clock; // i2c时钟频率,单位hz
}ls1c_i2c_info_t;
// I2C模块编号
typedef enum
{
LS1C_I2C_0 = 0,
LS1C_I2C_1,
LS1C_I2C_2
}ls1c_i2c_t;
代码已经很清楚了,初始化函数i2c_init()的入参中包含I2C编号(I2C0,I2C1或I2C2)和I2C时钟频率。
除了I2C0有默认引脚(即可以不复用)外,I2C1和I2C2都需要复用,并且不止一种复用,具体复用到那个引脚根据自己喜好。需要注意的是,初始化函数中没有设置引脚复用的代码,需要另外单独调用引脚复用的函数(pin_set_remap())进行设置。
使用示例
比如,使用I2C0,时钟为50k,可以如下使用
ls1c_i2c_info_t i2c_info;
i2c_info.clock = 50*1000; // 50kb/s
i2c_info.I2Cx = LS1C_I2C_0;
i2c_init(&i2c_info);
引脚复用pin_set_remap()
函数原型
/*
* 设置指定pin为第n复用
* @gpio gpio编号
* @remap 第n复用
*/
void pin_set_remap(unsigned int gpio, pin_remap_t remap)
// 引脚复用
typedef enum
{
PIN_REMAP_FIRST = 0, // 第一复用
PIN_REMAP_SECOND, // 第二复用
PIN_REMAP_THIRD, // 第三复用
PIN_REMAP_FOURTH, // 第四复用
PIN_REMAP_FIFTH, // 第五复用
}pin_remap_t;
引脚复用的这个函数其实早已封装好的,函数pin_set_remap()的第一个入参为GPIO编号,第二个参数为引脚的第n复用,想知道那些引脚可以复用为I2C,需要查询《龙芯1C处理器数据手册》。比如
由上图可知,GPIO54和GPIO55可以复用为I2C1,GPIO56和GPIO57可以复用为I2C2,都是第四复用。
使用示例
前面分析的I2C1和I2C2,可以通过下面的调用实现复用
// I2C1,引脚CAMDATA4(GPIO54)和CAMDATA5(GPIO55)的第四复用
#define LS1C_I2C_SDA1_GPIO54 (54)
#define LS1C_I2C_SCL1_GPIO55 (55)
// I2C2,引脚CAMDATA6(GPIO56)和CAMDATA7(GPIO57)的第四复用
#define LS1C_I2C_SDA2_GPIO56 (56)
#define LS1C_I2C_SCL2_GPIO57 (57)
// 使用I2C1,引脚CAMDATA4(GPIO54)和CAMDATA5(GPIO55)的第四复用
pin_set_remap(LS1C_I2C_SDA1_GPIO54, PIN_REMAP_FOURTH);
pin_set_remap(LS1C_I2C_SCL1_GPIO55, PIN_REMAP_FOURTH);
// I2C2,引脚CAMDATA6(GPIO56)和CAMDATA7(GPIO57)的第四复用
pin_set_remap(LS1C_I2C_SDA2_GPIO56, PIN_REMAP_FOURTH);
pin_set_remap(LS1C_I2C_SCL2_GPIO57, PIN_REMAP_FOURTH);
注意,还有其它引脚也可以复用为I2C1或I2C2.
另外,单独写了一篇引脚复用的博文,有兴趣的可以移步到《【龙芯1c库】封装引脚复用接口和使用示例》 http://blog.csdn.net/caogos/article/details/72529394
发送START信号和地址i2c_send_start_and_addr()
龙芯1c的I2C硬件本身决定了,没有单独发送地址的命令,即发送开始信号后,硬件自动将数据寄存器中的值作为地址发送出去。所以I2C的开始信号和地址封装成了一个函数。
函数原型
/*
* 发送START信号和地址
* @i2c_info_p i2c模块信息
* @slave_addr 从机地址
* @direction 数据传输方向(读、写)
*/
ls1c_i2c_ret_t i2c_send_start_and_addr(ls1c_i2c_info_t *i2c_info_p,
unsigned char slave_addr,
ls1c_i2c_direction_t direction)
// I2C数据传输方向
typedef enum
{
LS1C_I2C_DIRECTION_WRITE = 0, // 主机向从机写信息
LS1C_I2C_DIRECTION_READ, // 主机向从机读信息
}ls1c_i2c_direction_t;
注意,函数i2c_send_start_and_addr()的第二个参数slave_addr不是I2C开始信号后发出去的8字节数据。开始信号后的8字节数据=(slave_addr << 1) | ((LS1C_I2C_DIRECTION_READ == direction) ? 1 : 0),所以第二个参数——从机地址是7位的数,不是8位的,在具体应用时要特别注意。
使用示例
比如,温湿度传感器AM2320手册中说AM2320的地址为0xB8,如下图
注意,AM2320手册中说的地址为0xB8是不能直接作为函数i2c_send_start_and_addr()的第二个参数的,需要右移一位,即slave_addr = 0xb8 >> 1。可能大家会怀疑,那么接着来看AM2320的手册。
在读取温湿度信息时,地址为0xB9.
函数i2c_send_start_and_addr()完整的示例为
ls1c_i2c_info_t i2c_info;
int slave_addr = 0xb8 >> 1;
i2c_info.clock = 50*1000; // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_send_start_and_addr(&i2c_info, slave_addr, LS1C_I2C_DIRECTION_WRITE);
发送STOP信号i2c_send_stop()
函数i2c_send_stop()发送I2C协议中的STOP信号。
函数原型
/*
* 发送STOP信号
* @i2c_info_p i2c模块信息
*/
void i2c_send_stop(ls1c_i2c_info_t *i2c_info_p)
这个函数很简单,不用解释吧。
使用示例
ls1c_i2c_info_t i2c_info;
i2c_info.clock = 50*1000; // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_send_stop(&i2c_info);
接收从机的ACK信号i2c_receive_ack()
I2C协议规定,在向从机发送一个字节信息后,从机需要回应一个ACK信号。函数i2c_receive_ack()就是用于接收这个ACK信号的。
函数原型
/*
* (再发送一个字节数据之后)接收从机发送的ACK信号
* @i2c_info_p i2c模块信息
* @ret LS1C_I2C_ACK or LS1C_I2C_NACK
*/
ls1c_i2c_ack_t i2c_receive_ack(ls1c_i2c_info_t *i2c_info_p)
// I2C应答
typedef enum
{
LS1C_I2C_ACK = 0, // 收到应答
LS1C_I2C_NACK = 1, // 没收到应答
}ls1c_i2c_ack_t;
入参i2c_info_p指示在哪个I2C总线上读取ACK信号,函数返回是否收到ACK信号。
使用示例
ls1c_i2c_info_t i2c_info;
i2c_info.clock = 50*1000; // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_receive_ack(&i2c_info);
这里没有判断返回值,有需要的自己判断一下。
发送数据i2c_send_data()
注意,这里说的数据是指除I2C从机地址,START信号和STOP信号外的其它信息。对于具体的某个I2C传感器来说,这里的数据包括渎写传感器的命令。比如,温湿度传感器AM2320的读命令,就是用函数i2c_send_data()发送的,即AM2320的读命令就是这里说的数据。
函数原型
/*
* 发送数据
* @i2c_info_p i2c模块信息
* @data 待发送的数据
* @len 待发送数据的长度
*/
ls1c_i2c_ret_t i2c_send_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *data, int len)
// 函数返回值
typedef enum
{
LS1C_I2C_RET_OK = 0, // OK
LS1C_I2C_RET_TIMEOUT, // 超时
}ls1c_i2c_ret_t;
使用示例
比如,向AM2320发送读命令
ls1c_i2c_info_t i2c_info;
unsigned char send_buff[] = {0x03, 0x00, 0x04};
i2c_info.clock = 50*1000; // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_send_data(&i2c_info, send_buff, sizeof(send_buff));
接收数据i2c_receive_data()
函数原型
/*
* 接收数据
* @i2c_info_p i2c模块信息
* @buf 数据缓存
* @len 待接收数据的长度
*/
ls1c_i2c_ret_t i2c_receive_dat