HAL层(Hardware abstraction layer) 的目的是为了屏蔽底层不同芯片平台的差异,从而使驱动层上面的软件不会随芯片平台而改变。AliOS Things定义了全面的HAL抽象层,这个系列主要介绍AliOS ThingsHAL层与不同芯片平台对接的poring要点,并举例说明。
Hal porting系列2 —— SPI driver porting
一. 接口定义说明
SPI 对外接口定义在 include/hal/soc下面,接口函数主要有以下几个:
int32_t hal_spi_init(spi_dev_t *spi);
int32_t hal_spi_send(spi_dev_t *spi, const uint8_t *data, uint16_t size, uint32_t timeout);
int32_t hal_spi_recv(spi_dev_t *spi, uint8_t *data, uint16_t size, uint32_t timeout);
int32_t hal_spi_send_recv(spi_dev_t *spi, uint8_t *tx_data, uint8_t *rx_data, uint16_t size, uint32_t timeout);
int32_t hal_spi_finalize(spi_dev_t *spi);
其中,结构体 spi_dev_t 定义为:
typedef struct {
uint8_t port; /* spi port */
spi_config_t config; /* spi config */
void *priv; /* priv data */
} spi_dev_t;
结构体 spi_config_t 定义为:
typedef struct {
uint32_t mode; /* spi communication mode */
uint32_t freq; /* communication frequency Hz */
} spi_config_t;
port 指spi的端口号,在一个系统中,可能会有不止一对的spi主从设备,此时可以通过port值来区分是哪个spi设备,如spi0、spi1等等;
config是用户需要指定的配置,这里给出了2个较为常见的配置数据。分别是:
mode --- 模式 master or slave
freq --- 传输频率,不用硬件支持的频率不同,一般可选从125K到8M。
若用户还有其他需要指定的数据,可以通过priv来传入。
二. 接口使用说明
初始化 spi 设备:
需要定义spi_dev_t 的变量,举例说明:
spi_dev_t spi_0 = {.port = 0,
.config = {SPI_MODE, SPI_FREQ_8M},
.priv = 0};
初始化:
hal_spi_init(spi_0);
发送数据:
ret = hal_spi_send(spi_0 , buf, nbytes, timeout);
接收数据:
ret = hal_spi_recv(spi_0, buf, nbytes, timeout)
master发送数据同时接收slvae发来的数据:
ret = hal_spi_send_recv( spi_0, buf_tx, buf_rx, nbytes , timeout)
三. hal层对接要点
以 STM32L4 系列为例介绍hal层具体porting步骤:
HAL层接口函数位于/include/hal/soc目录下,SPI 的HAL层接口函数定义在对应的spi.h中
hal层定义的接口为:
int32_t hal_spi_init(spi_dev_t *spi)
STM32L4的初始化接口为:
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi)
其中 SPI_HandleTypeDef 是ST系列自定义的结构体定义,可参考ST驱动源码。
由于STM32L4的驱动函数和hal层定义的接口并非完全一致,我们需要在STM32L4驱动上封装一层,以对接hal层。
我们需要新建两个文件hal_spi_stm32l4.c和hal_spi_stm32l4.h,将封装层代码放到这两个文件中。
在hal_spi_stm32l4.c中,首先定义相应的STM32L4的spi句柄:
/ handle for spi /
SPI_HandleTypeDef spi1_handle;
然后自定义如下函数,将用户指定的mode和freq传入 spi1_handle
int32_t spi_mode_transform(uint32_t mode_hal, uint32_t *mode_stm32l4);
int32_t spi_freq_transform(uint32_t freq_hal, uint32_t *BaudRatePrescaler_stm32l4_stm32l4);
代码示例如下:
int32_t spi1_init(spi_dev_t *spi)
{
int32_t ret = 0;
spi1_handle.Instance = SPI1;
ret = spi_mode_transform(spi->config.mode, &spi1_handle.Init.Mode);
ret = spi_freq_transform(spi->config.freq, &spi1_handle.Init.BaudRatePrescaler);
if (ret != 0) {
return -1;
}
/* init spi */
ret = HAL_SPI_Init(&spi1_handle);
return ret;
}
int32_t hal_spi_init(spi_dev_t *spi)
{
int32_t ret = -1;
if (spi == NULL) {
return -1;
}
/*init spi handle*/
memset(&spi1_handle, 0, sizeof(spi1_handle));
switch (spi->port) {
case PORT_SPI1:
spi->priv = &spi1_handle;
ret = spi1_init(spi);
break;
/* if ohter spi exist add init code here */
default:
break;
}
return ret;
}
以 Nordic NRF52xxx系列为例:
NRF的spi init驱动定义如下:
ret_code_t nrf_drv_spi_init(nrf_drv_spi_t const * const p_instance,
nrf_drv_spi_config_t const * p_config,
nrf_drv_spi_evt_handler_t handler,
void * p_context)
所以,要对接NRF系列的HAL层,需要仔细研究驱动的定义,下面给出示例:
我们在新建的hal_spi_nrf52xxx.h中可以将 上述接口中使用的函数入参统一到一个新的结构体中,并命名为SPI_HandleTypeDef:
typedef struct __SPI_HandleTypeDef
{
nrf_drv_spi_t spi_dev;
nrf_drv_spi_config_t spi_config;
nrf_drv_spi_evt_handler_t spi_handler;
void * p_context;
} SPI_HandleTypeDef;
在 hal_spi_nrf52xxx.c中 定义相应的NRF52的spi句柄:
/* handle for spi */
SPI_HandleTypeDef spi0_handle = {NRF_DRV_SPI_INSTANCE(AOS_PORT_SPI0), NRF_DRV_SPI_DEFAULT_CONFIG, 0, NULL};
(其中的 NRF_DRV_SPI_INSTANCE 和 NRF_DRV_SPI_DEFAULT_CONFIG定义参考NRF驱动源码)
static int32_t spi0_init(spi_dev_t *spi)
{
int32_t ret1 = 0, ret2 = 0;
spi0_handle.spi_config.ss_pin = SPIM0_SS_PIN;
spi0_handle.spi_config.miso_pin = SPIM0_MISO_PIN;
spi0_handle.spi_config.mosi_pin = SPIM0_MOSI_PIN;
spi0_handle.spi_config.sck_pin = SPIM0_SCK_PIN;
ret1 = spi_mode_transform(spi->config.mode, &spi0_handle.spi_config.mode);
ret2 = spi_freq_transform(spi->config.freq, &spi0_handle.spi_config.frequency);
if ((ret1 != 0) || (ret2 != 0))
return -1;
return nrf_drv_spi_init(&spi0_handle.spi_dev, &spi0_handle.spi_config, NULL, NULL);
}
static int32_t spi_mode_transform(uint32_t mode_hal, uint32_t *mode_nrf52xxx)
{
nrf_drv_spi_mode_t mode = 0;
int32_t ret = 0;
switch (mode_hal)
{
case SPI_MODE_0:
mode = NRF_DRV_SPI_MODE_0;
break;
case SPI_MODE_1:
mode = NRF_DRV_SPI_MODE_1;
break;
case SPI_MODE_2:
mode = NRF_DRV_SPI_MODE_2;
break;
case SPI_MODE_3:
mode = NRF_DRV_SPI_MODE_3;
break;
default:
ret = -1;
}
if(ret == 0)
*mode_nrf52xxx = (uint32_t)mode;
return ret;
}
static int32_t spi_freq_transform(uint32_t freq_hal, uint32_t *freq_nrf52xxx)
{
nrf_drv_spi_frequency_t freq = 0;
int32_t ret = 0;
switch (freq_hal)
{
case SPI_FREQ_125K:
freq = NRF_SPI_FREQ_125K;
break;
case SPI_FREQ_250K:
freq = NRF_SPI_FREQ_250K;
break;
case SPI_FREQ_500K:
freq = NRF_SPI_FREQ_500K;
break;
case SPI_FREQ_1M:
freq = NRF_SPI_FREQ_1M;
break;
case SPI_FREQ_2M:
freq = NRF_SPI_FREQ_2M;
break;
case SPI_FREQ_4M:
freq = NRF_SPI_FREQ_4M;
break;
case SPI_FREQ_8M:
freq = NRF_SPI_FREQ_8M;
break;
default:
ret = -1;
}
if(ret == 0)
*freq_nrf52xxx = (uint32_t)freq;
return ret;
}
发送数据:
ret = hal_spi_send(spi_0 , buf, nbytes, timeout);
表示在timeout时间范围内,将buf开始的大小为nbytes字节的数据通过spi_0 设备发送。
调用这个接口时需要注意两点:
- 需要对返回值ret进行判断,不用芯片平台驱动的返回值不同:
ST系列:
typedef enum
{
HAL_OK = 0x00,
HAL_ERROR = 0x01,
HAL_BUSY = 0x02,
HAL_TIMEOUT = 0x03
} HAL_StatusTypeDef;
NRF系列:
要根据不同返回值的定义判断驱动此时的状态。
2.不同芯片平台驱动中,对timeout的理解不同:
ST系列,底层发送驱动中会对 timeout进行判断,若timeout时间到仍未发送完成,则返回 HAL_TIMEOUT ;
NRF系列,若不使用中断,则发送驱动中采用的是while死等的操作方式,此时参数 timeout将不起作用。
数据接收 hal_spi_recv 接口的对接与发送类似。