【龙芯1c库】封装硬件I2C接口和使用示例

本文详述如何在龙芯1c库中封装硬件I2C接口,提供初始化、发送START/STOP信号、读写数据等操作的示例,并通过测试温湿度传感器AM2320验证接口的正确性。同时介绍了龙芯1c的硬件I2C寄存器功能。
摘要由CSDN通过智能技术生成
龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库。完整源码请移步到https://gitee.com/caogos/OpenLoongsonLib1c

将温湿度传感器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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值