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 接口的對接與發送類似。