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

  1. 云栖社区>
  2. AliOS Things>
  3. 博客>
  4. 正文

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

泉墨170385 2018-08-09 15:28:25 浏览1655

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 设备发送。

调用这个接口时需要注意两点:

  1. 需要对返回值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 接口的对接与发送类似。