在龙芯1c上使用RT-Thread统一标准的i2c接口

本文首先介绍几个常用的RTT统一的标准的I2C接口,然后以I2C接口的EEPROM芯片AT24C02为例,演示如何在龙芯1C上用RTT统一的I2C接口读取EEPROM芯片AT24C02。
在本文之前,已经写过几篇关于龙芯1C上,模拟I2C和硬件I2C,在裸机程序中和RTT中使用的文章,如下
【龙芯1c库】封装硬件I2C接口和使用示例
http://blog.csdn.net/caogos/article/details/77891546
在RT-Thread上使用龙芯1c库中的硬件I2C接口
http://blog.csdn.net/caogos/article/details/77892951
【龙芯1c库】封装模拟I2C接口和使用示例
http://blog.csdn.net/caogos/article/details/73089406
这几篇文章都是以“龙芯1C库”为背景的,而本文的不同之处在于重点讨论RTT统一标准的I2C接口,在龙芯1C上的移植和使用。

RTT统一的标准的I2C接口简介

初始化I2C

函数原型

int ls1c_i2c_init(void);

本函数的作用是,向RTT添加注册一个I2C总线。本函数为龙芯1C定制的函数,具体的实现细节放到后面移植部分再讨论。

使用示例

ls1c_i2c_init();

直接调用一下就行。
有的BSP里面是使用
INIT_DEVICE_EXPORT(ls1c_i2c_init);
的形式调用的,但在龙芯1C上没有这样采用。原因是每个人使用I2C时,可能使用不同的引脚。

Find设备

函数原型

/**
 * This function finds a device driver by specified name.
 *
 * @param name the device driver's name
 *
 * @return the registered device driver on successful, or RT_NULL on failure.
 */
rt_device_t rt_device_find(const char *name)
函数rt_device_find()为RTT中通用的,find设备的函数。其它一些地方也可能会见到。入参为I2C总线的名字。I2C总线的名字在函数ls1c_i2c_init()中,调用函数rt_i2c_bit_add_bus()时注册的I2C总线名字。

使用示例

#define AT24C02_I2C_BUS_NAME                ("i2c2")
struct rt_i2c_bus_device *at24c02_i2c_bus = RT_NULL;

// find设备
at24c02_i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(AT24C02_I2C_BUS_NAME);
if (RT_NULL == at24c02_i2c_bus)
{
    rt_kprintf("[%s] no i2c device -- am2320!\n", __FUNCTION__);
    return ;
}

收发I2C信息

函数原型

rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
                          struct rt_i2c_msg         msgs[],
                          rt_uint32_t               num)
本函数是I2C收发的核心函数。收发都是调用本函数。具体是收,还是发,第二个入参中有个flags来控制。第一个入参为I2C总线(前面已经使用函数rt_device_find()得到的I2C总线),第三个入参为msg的个数,注意要与第二个参数中的len区别。
下面来详细看看第二个参数的结构体定义

struct rt_i2c_msg
{
    rt_uint16_t addr;
    rt_uint16_t flags;
    rt_uint16_t len;
    rt_uint8_t  *buf;
};
addr为I2C从机的地址,flags为此msg的一些标志,比如用来标记该msg是向从机读数据,还是写数据。len为此msg中收发数据的长度,buf为此msg收发的具体数据。
rt_i2c_msg为RTT为I2C封装的抽象概念。一次发送或接收,单独用一个msg来表示。
比如要读取AT24C02上某个地址的数据,先要把该数据的地址“写入”(发送)给AT24C02,然后再读取数据。这里面包含了两个msg,首先是一个写的msg,然后是一个读的msg。

使用示例

向AT24C02指定地址写入指定数据

/*
 * 在指定地址写入一个字节的数据
 * @write_addr 地址
 * @data 待写入的数据
 */
void at24c02_write_byte(unsigned char write_addr, unsigned char data)
{
    struct rt_i2c_msg msg[1] = {0};
    unsigned char buf[2] = {0};

    buf[0] = write_addr;
    buf[1] = data;
    
    msg[0].addr    = at24c02_addr;
    msg[0].flags   = RT_I2C_WR;
    msg[0].buf     = buf;
    msg[0].len     = 2;
    rt_i2c_transfer(at24c02_i2c_bus, msg, 1);

    return ;
}

从AT24C02指定地址读出数据

/*
 * 从指定地址读出一个字节
 * @read_addr 地址
 */
unsigned char at24c02_read_byte(unsigned char read_addr)
{
    struct rt_i2c_msg msgs[2];
    unsigned char data = 0;

    msgs[0].addr    = at24c02_addr;
    msgs[0].flags   = RT_I2C_WR;
    msgs[0].buf     = &read_addr;
    msgs[0].len     = 1;

    msgs[1].addr    = at24c02_addr;
    msgs[1].flags   = RT_I2C_RD;
    msgs[1].buf     = &data;
    msgs[1].len     = 1;
    rt_i2c_transfer(at24c02_i2c_bus, msgs, 2);

    return data;
}

综合应用示例——在龙芯1C上接I2C接口的EEPROM芯片AT24C02

测试的思路是,在at24c02内,地址为1的地方保存复位次数。每次上电后,先读取当前次数,并打印,然后加一,并保存到at24c02中。为了验证保存正确,在读取一次,并将读取的结果打印出来。

实物图

电路连接为
AT24C02                     龙芯1c
VCC   ------------------- 3.3V
GND  ------------------- GND
SCL   ------------------- GPIO57
SDA   ------------------ GPIO56

串口打印

测试源码清单

在前面介绍函数rt_i2c_transfer()时,已经把读写eeprom的函数介绍了,读写eeprom是本测试用例中的核心代码,这里就直接把完整的测试代码贴出来(I2C移植的部分代码稍后再讨论)。

application.c

bsp\ls1cdev\applications\application.c

/*
 * File      : application.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006-2012, RT-Thread Develop Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date                Author         Notes
 * 2010-06-25          Bernard        first version
 * 2011-08-08          lgnq           modified for Loongson LS1B
 * 2015-07-06          chinesebear    modified for Loongson LS1C
 */

#include <rtthread.h>
#include "net/synopGMAC.h"
#include <lwip/api.h>
#include <drivers/i2c.h>
#include "../drivers/drv_i2c.h"


// 测试用的线程  
#define THREAD_TEST_PRIORITY                    (25)  
#define THREAD_TEST_STACK_SIZE                  (4*1024)        // 4k  
#define THREAD_TEST_TIMESLICE                   (10)  


#define AT24C02_I2C_BUS_NAME                ("i2c2")        // 注意与i2c bus初始化函数中的bus name保持一致
struct rt_i2c_bus_device *at24c02_i2c_bus = RT_NULL;
int at24c02_addr = 0xA0 >> 1;               // 地址前7位
  
struct rt_thread thread_test;  
ALIGN(8) rt_uint8_t thread_test_stack[THREAD_TEST_STACK_SIZE];  


/*
 * 从指定地址读出一个字节
 * @read_addr 地址
 */
unsigned char at24c02_read_byte(unsigned char read_addr)
{
    struct rt_i2c_msg msgs[2];
    unsigned char data = 0;

    msgs[0].addr    = at24c02_addr;
    msgs[0].flags   = RT_I2C_WR;
    msgs[0].buf     = &read_addr;
    msgs[0].len     = 1;

    msgs[1].addr    = at24c02_addr;
    msgs[1].flags   = RT_I2C_RD;
    msgs[1].buf     = &data;
    msgs[1].len     = 1;
    rt_i2c_transfer(at24c02_i2c_bus, msgs, 2);

    return data;
}


/*
 * 在指定地址写入一个字节的数据
 * @write_addr 地址
 * @data 待写入的数据
 */
void at24c02_write_byte(unsigned char write_addr, unsigned char data)
{
    struct rt_i2c_msg msg[1] = {0};
    unsigned char buf[2] = {0};

    buf[0] = write_addr;
    buf[1] = data;
    
    msg[0].addr    = at24c02_addr;
    msg[0].flags   = RT_I2C_WR;
    msg[0].buf     = buf;
    msg[0].len     = 2;
    rt_i2c_transfer(at24c02_i2c_bus, msg, 1);

    return ;
}


// 测试用的线程的入口  
void thread_test_entry(void *parameter)  
{
    unsigned char read_addr = 1;    // 地址
    unsigned char count = 0;        // 用于计数的变量

    // 初始化(添加i2c总线--i2c2)
    ls1c_i2c_init();

    // find设备
    at24c02_i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(AT24C02_I2C_BUS_NAME);
    if (RT_NULL == at24c02_i2c_bus)
    {
        rt_kprintf("[%s] no i2c device -- am2320!\n", __FUNCTION__);
        return ;
    }

    // 读
    count = at24c02_read_byte(read_addr);
    rt_kprintf("[%s] last's count=%u\n", __FUNCTION__, count);

    // 加一,然后写
    count++;
    at24c02_write_byte(read_addr, count);
    rt_thread_delay(6);     // 一定要延时5ms以上

    // 读
    count = at24c02_read_byte(read_addr);
    rt_kprintf("[%s] current count=%d\n", __FUNCTION__, count);
    
    while (1)  
    {
        // 间隔3s
        rt_thread_delay(3 * RT_TICK_PER_SECOND);  
    }  
}  



void rt_init_thread_entry(void *parameter)
{
	/* initialization RT-Thread Components */
	rt_components_init();

    // 网口EMAC初始化
	rt_hw_eth_init();
}

int rt_application_init(void)
{
	rt_thread_t tid;
    rt_err_t result;

	/* create initialization thread */
	tid = rt_thread_create("init",
							rt_init_thread_entry, RT_NULL,
							4096, RT_THREAD_PRIORITY_MAX/3, 20);
	if (tid != RT_NULL)
		rt_thread_startup(tid);

  
    // 初始化测试用的线程  
    result = rt_thread_init(&thread_test,   
                            "thread_test",  
                            thread_test_entry,  
                            RT_NULL,  
                            &thread_test_stack[0],  
                            sizeof(thread_test_stack),  
                            THREAD_TEST_PRIORITY,  
                            THREAD_TEST_TIMESLICE);  
    if (RT_EOK == result)  
    {  
        rt_thread_startup(&thread_test);  
    }  
    else  
    {  
        return -1;  
    }  

	return 0;
}

把RTT统一的标准的I2C接口移植到龙芯1C上

移植要点

RTT支持硬件I2C和模拟I2C,这里使用的是模拟I2C。大家都知道,其实模拟I2C本身就不难,而RTT又把其中一些非硬件相关的,通用的部分提出来了,那么留给我们移植的工作量就更少了。简单来说就只需要实现读取和设置scl和sda的函数就可以了。对应代码为

static const struct rt_i2c_bit_ops bit_ops = {
    .data       = RT_NULL,
    .set_sda    = ls1c_set_sda,
    .set_scl    = ls1c_set_scl,
    .get_sda    = ls1c_get_sda,
    .get_scl    = ls1c_get_scl,

    .udelay     = ls1c_udelay,

    .delay_us   = 20,       // 此值为周期(us)
    .timeout    = 10,       // 单位为tick
};
这个结构体中的函数都实现好后,然后调用函数rt_i2c_bit_add_bus()把bit_ops注册一下就可以了。具体代码为

int ls1c_i2c_init(void)
{
    static struct rt_i2c_bus_device bus = {0};

    bus.priv = (void *)&bit_ops;

    ls1c_i2c_gpio_init();

    rt_i2c_bit_add_bus(&bus, "i2c2");

    return RT_EOK;
}

这是一个I2C的驱动,假设想再增加一个呢?道理类似,依葫芦画瓢,把scl和sda的引脚改一下,把I2C总线的名字改一下,基本就可以了。
最后在特别说明一下,目前龙芯的gpio没有开漏输出模式,所以在读取sda值的时候需要先设为输入模式。如下

static rt_int32_t ls1c_get_sda(void *data)
{
#ifdef LS1C_SET_GPIO_MODE
    gpio_init(LS1C_I2C_SDA, gpio_mode_input);
    ls1c_udelay(5);
#endif
    return gpio_get(LS1C_I2C_SDA);
}

static void ls1c_set_sda(void *data, rt_int32_t state)
{
#ifdef LS1C_SET_GPIO_MODE
    gpio_init(LS1C_I2C_SDA, gpio_mode_output);
    ls1c_udelay(5);
#endif
    gpio_set(LS1C_I2C_SDA, state);
    return ;
}
其中,对GPIO的操作还是使用的龙芯1C库中的gpio函数(bsp\ls1cdev\libraries\ls1c_gpio.h中)。

源码清单

drv_i2c.c

bsp\ls1cdev\drivers\drv_i2c.c

/*
 * File      : drv_i2c.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2012, RT-Thread Development Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2017-11-14     勤为本       first version
 */

#include <rtthread.h>
#include <drivers/i2c.h>
#include <drivers/i2c-bit-ops.h>
#include "drv_i2c.h"
#include "../libraries/ls1c_gpio.h"
#include "../libraries/ls1c_delay.h"


#define LS1C_I2C_SCL                (57)    // gpio57
#define LS1C_I2C_SDA                (56)    // gpio56

#define LS1C_SET_GPIO_MODE

static void ls1c_i2c_gpio_init(void)
{
    gpio_init(LS1C_I2C_SCL, gpio_mode_output);
    gpio_set(LS1C_I2C_SCL, gpio_level_high);

    gpio_init(LS1C_I2C_SDA, gpio_mode_output);
    gpio_set(LS1C_I2C_SDA, gpio_level_high);

    return ;
}


static void ls1c_udelay(rt_uint32_t us)
{
    delay_us((int)us);
}


static void ls1c_set_sda(void *data, rt_int32_t state)
{
#ifdef LS1C_SET_GPIO_MODE
    gpio_init(LS1C_I2C_SDA, gpio_mode_output);
    ls1c_udelay(5);
#endif
    gpio_set(LS1C_I2C_SDA, state);
    return ;
}


static void ls1c_set_scl(void *data, rt_int32_t state)
{
#ifdef LS1C_SET_GPIO_MODE
    gpio_init(LS1C_I2C_SCL, gpio_mode_output);
    ls1c_udelay(5);
#endif
    gpio_set(LS1C_I2C_SCL, state);
    return ;
}


static rt_int32_t ls1c_get_sda(void *data)
{
#ifdef LS1C_SET_GPIO_MODE
    gpio_init(LS1C_I2C_SDA, gpio_mode_input);
    ls1c_udelay(5);
#endif
    return gpio_get(LS1C_I2C_SDA);
}


static rt_int32_t ls1c_get_scl(void *data)
{
#ifdef LS1C_SET_GPIO_MODE
    gpio_init(LS1C_I2C_SCL, gpio_mode_input);
    ls1c_udelay(5);
#endif
    return gpio_get(LS1C_I2C_SCL);
}


static const struct rt_i2c_bit_ops bit_ops = {
    .data       = RT_NULL,
    .set_sda    = ls1c_set_sda,
    .set_scl    = ls1c_set_scl,
    .get_sda    = ls1c_get_sda,
    .get_scl    = ls1c_get_scl,

    .udelay     = ls1c_udelay,

    .delay_us   = 20,       // 此值为周期(us)
    .timeout    = 10,       // 单位为tick
};



int ls1c_i2c_init(void)
{
    static struct rt_i2c_bus_device bus = {0};

    bus.priv = (void *)&bit_ops;

    ls1c_i2c_gpio_init();

    rt_i2c_bit_add_bus(&bus, "i2c2");

    return RT_EOK;
}








感谢阅读!


猜你喜欢

转载自blog.csdn.net/caogos/article/details/78617597