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层就可以直接调用各芯片厂商对应的驱动即可。