HAL层(Hardware abstraction layer 硬件抽象层) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS ThingsHAL层与不同芯片平台对接的poring要点,并举例说明。
Hal 对接系列1 —— Gpio driver porting
一. 接口定义说明
gpio 对外接口定义在 include/hal/soc下面,接口函数主要有以下几个:
int32_t hal_gpio_init(gpio_dev_t *gpio)
int32_t hal_gpio_output_high(gpio_dev_t *gpio);
int32_t hal_gpio_output_low(gpio_dev_t *gpio);
int32_t hal_gpio_output_toggle(gpio_dev_t *gpio);
int32_t hal_gpio_input_get(gpio_dev_t *gpio, uint32_t *value);
int32_t hal_gpio_enable_irq(gpio_dev_t *gpio, gpio_irq_trigger_t trigger, gpio_irq_handler_t handler, void *arg);
int32_t hal_gpio_disable_irq(gpio_dev_t *gpio);
int32_t hal_gpio_clear_irq(gpio_dev_t *gpio);
int32_t hal_gpio_finalize(gpio_dev_t *gpio)
其中,结构体gpio_dev_t 定义为:
typedef struct {
uint8_t port; /* gpio port */
gpio_config_t config; /* gpio config */
void *priv; /* priv data */
} gpio_dev_t;
接口函数的含义清晰明了,这里用户有两种方式可以调用驱动:
- 直接调用接口函数,常用于对gpio驱动的二次封装,如在lcd驱动中调用gpio进行相关操作;
- 通过vfs虚拟文件系统进行操作,即使用open, read, write, ioctl等标准接口进行。
二. 接口使用说明
二者调用方式在hal层没有显著区别。以第一种为例:
调用gpio驱动时,首先定义
gpio_dev_t gpio_1 = {KEY_1, OUTPUT, &value}
即使用gpio 的输出功能,可以使用
hal_gpio_output_high(&gpio_1);
hal_gpio_output_low(& gpio_1);
hal_gpio_output_toggle(& gpio_1);
等接口函数操作gpio输出高、低电平或者反转电平。
gpio_dev_t gpio_2 = {KEY_2, INPUT, NULL}
即使用gpio的输入功能,可以使用
hal_gpio_input_get(&gpio_2 , uint32_t *value)
等接口函数操作读取gpio电平值;
gpio_dev_t gpio_3 = {KEY_3, IRQ_MODE, &value}
即使用gpio的输入中断功能,value值为上升沿、下降沿和边沿触发三种模式,
可以使用
hal_gpio_enable_irq(&gpio_3, gpio_irq_trigger_t trigger,
gpio_irq_handler_t handler, void *arg);
进行中断初始化,用户还需要自行提供中断处理函数handler,定义如下:
typedef void (gpio_irq_handler_t)(void arg);
具体的操作示例,可以参考aos/example/peri_deiver_test_developerkit/peri_test.c
三. HAL层对接要点
HAL层需要严格按照位于include/hal/soc/gpio.h的实现进行对接, 新建两个文件hal_gpio_xxx.c和hal_gpio_xxx.h,将封装层代码放到这两个文件中,实现上面列出的接口函数。
hal_gpio_init是gpio初始化,不同系列芯片的初始化方式差异较大,我们往往无法直接调用芯片厂商的初始化函数,此时需要对接口进行一些转换以对接芯片厂商的驱动。举例说明。
ST系列:
我们最终需要的gpio初始化接口即:int32_t hal_gpio_init(gpio_dev_t *gpio)
ST系列芯片驱动的gpio初始化接口为:void HAL_GPIO_Init(GPIO_TypeDef GPIOx, GPIO_InitTypeDef GPIO_Init)
其中的结构体GPIO_TypeDef 、GPIO_InitTypeDef 为ST芯片专用的驱动结构体定义,
对接ST系列的gpio驱动时,需要手动对上述结构体进行定义,以满足继续调用ST的驱动的要求:
定义
GPIO_TypeDef *GPIOx;
GPIO_InitTypeDef GPIO_InitStruct;
/伪代码举例/
get_gpio_group(gpio, &GPIOx) 来获取GPIOx
gpio_para_transform(gpio, &GPIO_InitStruct); 来获取 GPIO_InitStruct
(get_gpio_group 和 gpio_para_transform 等函数的实现要点:根据GPIO_TypeDef 和GPIO_InitTypeDef 结构体的定义将 gpio_dev_t 实现转换 ,具体可参考ST驱动源码)
有了GPIOx 和 GPIO_InitStruct,进一步调用ST提供gpio初始化函数 HAL_GPIO_Init
再以NRF系列芯片为例:
NRF系列芯片驱动的gpio初始化接口为:ret_code_t renrf_drv_gpiote_init(void)
对接该系列hal层时,可以直接调用底层驱动,需要注意的是,NRF的gpio初始化分
输入gpio的初始化和输出gpio的初始化,即:
ret_code_t nrf_drv_gpiote_out_init(nrf_drv_gpiote_pin_t pin,
nrf_drv_gpiote_out_config_t const * p_config);
ret_code_t nrf_drv_gpiote_in_init(nrf_drv_gpiote_pin_t pin,
nrf_drv_gpiote_in_config_t const * p_config,
nrf_drv_gpiote_evt_handler_t evt_handler);
这里的 nrf_drv_gpiote_pin_t 、nrf_drv_gpiote_out_config_t 、nrf_drv_gpiote_in_config_t 等结构体定义也是NRF系列专用,对接该系列的gpio驱动时,需要手动对上述结构体进行定义,如:
static nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_SIMPLE(false);
static nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
int32_t hal_gpio_init(gpio_dev_t *gpio)
{
int32_t ret = -1;
if (!nrf_drv_gpiote_is_init()) {
ret = nrf_drv_gpiote_init();
if (ret != NRF_SUCCESS)
return NRF_ERROR_INTERNAL;
}
switch (gpio->config) {
case OUTPUT_PUSH_PULL:
case OUTPUT_OPEN_DRAIN_NO_PULL:
case OUTPUT_OPEN_DRAIN_PULL_UP:
ret = nrf_drv_gpiote_out_init(gpio->port, &out_config);
break;
case IRQ_MODE:
in_config.pull = NRF_GPIO_PIN_PULLUP;
ret = nrf_drv_gpiote_in_init(gpio->port, &in_config, gpio->priv);
default:
ret = -1;
break;
}
return ret;
}
hal_gpio_output_high、
hal_gpio_output_low、
hal_gpio_output_toggle 、
hal_gpio_input_get
等函数的HAL层对接比较简单,一般在gpio初始化后直接调用相应芯片的驱动即可,
如ST系列: HAL_GPIO_WritePin、HAL_GPIO_ReadPin 进行gpio输入输出操作;
NRF系列:nrf_drv_gpiote_out_set、nrf_drv_gpiote_out_clear、nrf_drv_gpiote_in_is_set进行gpio输入输出操作。
对于gpio的输入中断的对接,ST系列使用了
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)进行gpio输入中断的响应,并在
int32_t hal_gpio_enable_irq(gpio_dev_t *gpio, gpio_irq_trigger_t trigger, gpio_irq_handler_t handler, void *arg)
中定义中断响应的handler 和传入的参数arg,如:
ret = hal_gpio_enable_irq(&gpio1, IRQ_TRIGGER_RISING_EDGE, key_handle, NULL);
if (ret != 0) {
printf("hal_gpio_enable_irq key return failed.\n");
}
void key_handle(void *arg)
{
/*key interupt相关操作*/
}
与ST不同,NRF系列在使用
ret_code_t nrf_drv_gpiote_in_init(nrf_drv_gpiote_pin_t pin,
nrf_drv_gpiote_in_config_t const * p_config,
nrf_drv_gpiote_evt_handler_t evt_handler);
最后一个参数evt_handler来表示中断响应函数,nrf 底层驱动会对这个参数进行判断,如果传入NULL,则表示不使用gpio输入中断功能。
综上所述, hal层porting需要根据不同芯片厂商的驱动的接口来进行。