AliOS Things 硬件抽象层(HAL)对接系列3 — I2C driver porting

HAL层(Hardware abstraction layer) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS ThingsHAL层与不同芯片平台对接的poring要点,并举例说明。

Hal porting系列 3 —— I2C driver porting

一. 接口定义说明

I2C 对外接口定义在 include/hal/soc下面,接口函数主要有以下几个:
int32_t hal_i2c_init(i2c_dev_t *spi);
int32_t hal_i2c_master_send(i2c_dev_t *i2c, uint16_t dev_addr, const uint8_t *data,
                            uint16_t size, uint32_t timeout);
int32_t hal_i2c_master_recv(i2c_dev_t *i2c, uint16_t dev_addr, uint8_t *data,
                            uint16_t size, uint32_t timeout);
int32_t hal_i2c_slave_send(i2c_dev_t *i2c, const uint8_t *data, uint16_t size, uint32_t timeout);
int32_t hal_i2c_slave_recv(i2c_dev_t *i2c, uint8_t *data, uint16_t size, uint32_t timeout);
int32_t hal_i2c_mem_write(i2c_dev_t *i2c, uint16_t dev_addr, uint16_t mem_addr,
                          uint16_t mem_addr_size, const uint8_t *data, uint16_t size,
                          uint32_t timeout);
int32_t hal_i2c_mem_read(i2c_dev_t *i2c, uint16_t dev_addr, uint16_t mem_addr,
                         uint16_t mem_addr_size, uint8_t *data, uint16_t size,
                         uint32_t timeout);
int32_t hal_i2c_finalize(i2c_dev_t *i2c);

其中,结构体 i2c_dev_t 定义为:
typedef struct {
    uint8_t      port;    /* i2c port */
    i2c_config_t config;  /* i2c config */
    void        *priv;    /* priv data */
} i2c_dev_t;

typedef struct {
    uint32_t address_width;
    uint32_t freq;
    uint8_t  mode;
    uint16_t dev_addr;
} i2c_config_t;

port 指i2c的端口号,在一个系统中,可能会有不止一对的i2c主从设备,此时可以通过port值来区分是哪个i2c设备,如i2c0、i2c1等等;
config是用户需要指定的配置,这里预留了较为常见的配置数据。分别是:
address_width --- 传输位宽,不同芯片平台支持的I2C 传输位宽不同,7BIT、8BIT、10BIT、16BIT都有;
mode --- 模式 master or slave;
freq --- 传输频率,也是根据芯片来定,常见10K、100K、400K;
dev_addr ---- 设备地址,每个master主设备可连多个slave从设备,每个从设备都有一个设备地址;

二. 接口使用说明

初始化 I2C 设备:
需要定义i2c_dev_t 的变量,举例说明:

i2c_dev_t i2c_0 = {.port   = 0,
                   .config = {I2C_MIDTH, I2C_MODE, I2C_FREQ,  I2C_DEV_ADDR},
                   .priv   = 0};
hal_spi_init(i2c_0);

master发送数据:

ret = hal_i2c_master_send(i2c_0 , i2c_0 ->config.dev_addr,  data, nbytes, timeout);

master接收数据:

ret = hal_i2c_master_recv( i2c_0 ,  i2c_0 ->config.dev_addr,  data, nbytes, timeout);

slave的发送和接收数据接口与master类似;

master 通过I2C总线直接直接将数据写入某设备的寄存器reg中
(这个接口较为常见,因为往往将数据需要写入不同寄存器中)

ret =  hal_i2c_mem_write(i2c_0, i2c_0->config.dev_addr,  reg,  len, data, nbytes,  timeout);

同理,master读出某个设备的寄存器reg的值:

ret =  hal_i2c_mem_read( i2c_0 ,  i2c_0 ->config.dev_addr,  reg,  len, data,  nbytes ,  timeout );

三. HAL层对接要点

以 STM32L4 系列为例介绍hal层具体porting步骤:

HAL层接口函数位于/include/hal/soc目录下,I2C的HAL层接口函数定义在对应i2c.h中

hal层定义的接口为:

int32_t hal_i2c_init(i2c_dev_t *i2c)

STM32L4的初始化接口为:

HAL_StatusTypeDef  HAL_I2C_Init(I2C_HandleTypeDef *hi2c)

其中 I2C_HandleTypeDef 是ST系列自定义的结构体定义,可参考ST驱动源码。

由于STM32L4的驱动函数和hal层定义的接口并非完全一致,我们需要在STM32L4驱动上封装一层,以对接hal层。

我们需要新建两个文件hal_i2c_stm32l4.c和hal_i2c_stm32l4.h,将封装层代码放到这两个文件中。

在hal_i2c_stm32l4.c中,首先定义相应的STM32L4的i2c句柄:

/ handle for i2c /
I2C_HandleTypeDef i2c_handle;

ST有单独的i2c初始化流程,直接调用即可:

代码示例如下:

void I2C1_Init(void)
{
    if (HAL_I2C_GetState(&I2c1Handle) == HAL_I2C_STATE_RESET) {
#ifdef I2C1_INSTANCE
        I2c1Handle.Instance              = I2C1_INSTANCE;
        I2c1Handle.Init.Timing           = I2C1_TIMING;
        I2c1Handle.Init.OwnAddress1      = I2C1_OWN_ADDRESS1;
        I2c1Handle.Init.AddressingMode   = I2C1_ADDRESSING_MODE;
        I2c1Handle.Init.DualAddressMode  = I2C1_DUAL_ADDRESS_MODE;
        I2c1Handle.Init.OwnAddress2      = I2C1_OWNADDRESS2;
        I2c1Handle.Init.GeneralCallMode  = I2C1_GENERAL_CALL_MODE;
        I2c1Handle.Init.NoStretchMode    = I2C1_NO_STRETCH_MODE;
#endif
        /* Init the I2C */
        HAL_I2C_Init(&I2c1Handle);
    }
}

int32_t hal_i2c_init(i2c_dev_t *i2c)
{
    int32_t ret = -1;

    if (i2c == NULL) {
       return -1;
    }

    switch (i2c->port) {
        case AOS_PORT_I2C1:
            I2C1_Init();
            i2c->priv = &I2c1Handle;
            ret = 0;
            break;
        case AOS_PORT_I2C2:
            I2C2_Init();
            i2c->priv = &I2c2Handle;
            ret = 0;
            break;
        case AOS_PORT_I2C3:
            I2C3_Init();
            i2c->priv = &I2c3Handle;
            ret = 0;
            break;
        case AOS_PORT_I2C4:
            I2C4_Init();
            i2c->priv = &I2c4Handle;
            ret = 0;
            break;
        default:- 
            break;
    }
    return ret;
}

以 Nordic NRF52xxx系列为例(可以看出每个芯片驱动都有自己的组织风格,驱动之间的结构体定义基本一致)
NRF的i2c init驱动定义如下(Nordic将i2c称为twi):

ret_code_t nrf_drv_twi_init(nrf_drv_twi_t const *        p_instance,
                            nrf_drv_twi_config_t const * p_config,
                            nrf_drv_twi_evt_handler_t    event_handler,
                            void *                       p_context)

与spi驱动对接类似:
我们在新建的hal_i2c_nrf52xxx.h中可以将 上述接口中使用的函数入参统一到一个新的结构体中,并命名为
TWI_HandleTypeDef:

typedef struct __TWI_HandleTypeDef
{
    nrf_drv_twi_t twi_dev;
    nrf_drv_twi_config_t twi_config;
    nrf_drv_twi_evt_handler_t twi_handler;
    void * p_context;
} TWI_HandleTypeDef;

在 hal_i2c_nrf52xxx.c中 定义相应的NRF52的spi句柄:
/* handle for i2c */

TWI_HandleTypeDef twi1_handle =

 {NRF_DRV_TWI_INSTANCE(AOS_PORT_TWI1), NRF_DRV_TWI_DEFAULT_CONFIG, NULL, NULL};

static uint32_t twi1_init()
{
    uint32_t err_code;
    err_code = nrf_drv_twi_init(&twi1_handle.twi_dev, &twi1_handle.twi_config, twi1_handle.twi_handler, NULL);
    if (err_code)
        return err_code;

    nrf_drv_twi_enable(&twi1_handle.twi_dev);
    return err_code;
}

初始化完成后,数据的发送与接收,HAL层就可以直接调用各芯片厂商对应的驱动即可。

上一篇:Linux 下安装 Oracle9i


下一篇:JLink + GDB 调试方法