2.3 硬件抽象层(HAL)

Mynewt中的硬件抽象层为底层、基础外设抽象。硬件抽象层提供了一组为Mynewt所支持的MCU而设计的服务。设备驱动程序通常用于初始化硬件、并为高层软件提供管理硬件访问的软件库。在Mynewt操作系统中,层可以按照以下方式描述。

  1. +———————————————————————————+
  2. | app |
  3. +———————————————————————————+
  4. | (n)drivers |
  5. +———————————————————————————+
  6. | HAL | BSP |
  7. +—————————————+—————————————+
  • 板级支持包抽象平台的特定配置,如CPU频率、输入电压、LED引脚、片上Flash映射等
  • 硬件抽象层抽象特定架构的功能。它初始化和启用主处理器中的组件。它被设计成可以在Mynewt中所支持的各种MCU之间移植(如Nodic的nRF51、nRF52,NXP的MK64F12等)。它包括了用于初始化和管理板卡上的组件访问(例如I2C,PCI,PCMCIA等),片外存储器和片外I/O(以太网、RS232,显示,鼠标等)。
  • 设备驱动在HAL和BSP之上,它抽象了通过标准接口连接到处理的每个外围设备的通用操作模式。对于特定的外围设备,可能有不同复杂性的多个驱动程序实现。驱动程序注册到内核的power管理API,并管理和关闭外设和外部芯片组等。

通用设计准则

  • HAL设计的API需要尽可能简单。应该尽可能容易的为硬件设计。一个简单的HAL API可以使得快速启动新的MCU变得十分容易。
  • HAL API应该可以移植到Mynewt中支持的各种MCU上。

依赖

要在项目中包含HAL,最简单的方式就是在包依赖中添加hal即可:

  1. pkd.deps:
  2. ...
  3. hw/hal

平台支持

并不是所有的平台都支持HAL设备。需要查阅MCU或BSP文档,查看硬件是否对感兴趣的外设支持。如果支持,就可以在mcu/src/hal_xxxx.c中查看是否已经实现,然后在构建项目时确保所有外部依赖问题都已经解决。

2.3.1 定时器(Timer)

硬件独立定时器结构以及用于配置、初始化和运行定时器的API。

描述

HAL定时器结构如下所示。用户可以根据需求声明任意数量的定时器结构。当用户调用hal_timer_starthal_timer_start_at函数时,将会在特定的硬件定时器上排队。用户在启动定时器之前需要调用hal_timer_set_cb

注意:用户不应该修改这个结构中的内容,且必须使用HAL的应用程序接口。

  1. struct hal_timer
  2. {
  3. void *bsp_timer; /* Internal platform specific pointer */
  4. hal_timer_cb cb_func; /* Callback function */
  5. void *cb_arg; /* Callback argument */
  6. uint32_t expiry; /* Tick at which timer should expire */
  7. TAILQ_ENTRY(hal_timer) link; /* Queue linked list structure */
  8. };

API

硬件抽象层定时器回调函数:

  1. typedef void(* hal_timer_cb)(void *arg)

硬件抽象层定时器初始化:

  1. int hal_timer_init(int timer_num, void *cfg)

参数:

  • timer_num:需要初始化的硬件定时器
  • cfg:特定硬件定时器的配置。从BSP直接到MCU特定驱动。
  1. int hal_timer_deinit(int timer_num)

2.3.2 通用输入输出口(GPIO)

Mynewt系统中用于配置、读取GPIO的基础操作。

独立的GPIO在API中作为pins引用。然而,在这个接口中pins为抽线引脚。MCU头文件将将这些抽象的pins映射到物理GPIO端口和引脚。

通常,BSP代码为这些虚拟引脚命名,以描述连接到物理引脚的设备。

以STM32F4XX处理器为例,该处理包含N个端口,每个端口包含16个引脚。MCU的hal_gpio驱动映射到一组虚拟引脚0-N,端口A映射到0-15,端口B映射到16-31,端口C映射到32-47等等。

所有,如果想开启端口B的引脚3,需要使用虚拟引脚:16*1+3 = 19。(物理引脚从0开始)

在写BSP时,通常会给个名称用于关联引脚,通常在bsp.h头文件中定义。

  1. #define SYSTEM_LED (37)
  2. #define FLASH_SPI_CHIP_SELECT (3)

抽象引脚37对应端口C的pin 5,抽象引脚3为端口A的pin 3。

2.3.3 通用串行接口(UART)

包含了UART发送与接收数据的基础操作。也包含了UART配置速度、奇偶校验位等的API接口。UART在重配前需要关闭。

  1. enum hal_uart_parity {
  2. /** No Parity */
  3. HAL_UART_PARITY_NONE = 0,
  4. /** Odd parity */
  5. HAL_UART_PARITY_ODD = 1,
  6. /** Even parity */
  7. HAL_UART_PARITY_EVEN = 2
  8. };
  9. enum hal_uart_flow_ctl {
  10. /** No Flow Control */
  11. HAL_UART_FLOW_CTL_NONE = 0,
  12. /** RTS/CTS */
  13. HAL_UART_FLOW_CTL_RTS_CTS = 1
  14. };
  1. /**
  2. * 函数原型,用于UART驱动查看是否需要发送更多数据
  3. * 若没有使能中断,驱动必须调用此接口
  4. **/
  5. typedef int(* hal_uart_tx_char)(void *arg)
  1. /**
  2. * UART驱动报告发送完成的函数原型,当发送最后一个字节时需要调用
  3. * 若没有使能中断,驱动必须调用此接口
  4. **/
  5. typedef int(* hal_uart_tx_done)(void *arg)

2.3.4 SPI

SPI是一个四线同步接口,通常用于连接嵌入式系统中的组件。

Mynewt HAL接口支持SPI的master功能的阻塞或非阻塞接口。SPI的slave功能的非阻塞模式。

SPI包含4个信号:MISO,MOSI,CLK和SS。SS信号为slave select的简写,低允许SPI从设备,主设备可以通过片选信号来区分多个从设备。

Mynewt的SPI硬件抽象层接口支持阻塞和非阻塞传输。阻塞意味着传输的接口函数将会等到传输完成才会返回。阻塞形式仅可用于SPI的主机模式。而非阻塞意味着API调用后,函数返回,在完成传输后执行回调函数(若有设置),非阻塞可用于主、从模式。

hal_spi_config方法用于配置SPI设备参数以及工作的模式:主设备或从设备。必须在spi初始化之后调用,即hal_spi_init之后,且spi并没有使能(hal_spi_disable)。当然也可以用于重新配置一个已经初始化了的SPI。

  1. int hal_spi_config(int spi_num, struct hal_spi_settings *psettings)

SPI的配置信息包括:

  1. struct hal_spi_settings {
  2. uint8_t data_mode;
  3. uint8_t data_order;
  4. uint8_t word_size;
  5. uint32_t baudrate; /* baudrate in kHz */
  6. };

Mynewt的API硬件抽象层并没有内置SS信号,通常需要应用中操作GPIO进行控制。

2.3.5 I2C

I2C总线(I2C,Inter-Integrated Circuit的简写)是一个多主机、多从机串行接口,用于连接电路板上的组件和外设。I2C为两线协议,通过SDA、SCL两线在设备之间传输数据。

I2C事务通常包括获得总线,发送或接收数据,释放总线。总线获得非常重要,因为通常总线是多主机的,从而可能有其他设备尝试读写同一个外设。

HAL_I2C实现了一个I2C总线的主机接口。典型使用按照以下步骤执行:

  • 调用hal_i2c_init函数初始化I2C设备
  • 要执行I2C事务,可以调用hal_i2c_master_writer或hal_i2c_master_read

操作时,这些函数将发送一个START信号,紧跟一个7-bit的I2C地址,接着发送或接收数据。为了在适当的时候发出停止条件,可以在任意函数中将last_op字段设置为1。

HAL_I2C中数据结构:

  1. struct hal_i2c_master_data {
  2. uint8_t address; /* 目的地址 */
  3. uint16_t len; /* 发送或接收的数据量 */
  4. uint8_t *buffer; /* 发送或接收的数据缓存 */
  5. };

I2C总线的地址为7-bit,将在LSB加上1-bit的读写指示。

B7 B6 B5 B4 B3 B2 B1 B0
1 0 0 0 1 1 0 R/W
MSB LSB

2.3.6 Flash

应用程序使用的闪存无关的硬件接口。

通过API函数提供了闪存的初始化、读、写、擦除、扇区擦除和其他操作。

  1. int hal_flash_ioctl(uint8_t flash_id, uint32_t cmd, void * args)
  2. int hal_flash_read(uint8_t flash_id, uint32_t address, void * dst, uint32_t num_bytes)
  3. int hal_flash_write(uint8_t flash_id, uint32_t address, const void * src, uint32_t num_bytes)
  4. int hal_flash_erase_sector(uint8_t flash_id, uint32_t sector_address)
  5. int hal_flash_erase(uint8_t flash_id, uint32_t address, uint32_t num_bytes)
  6. int hal_flash_isempty(uint8_t flash_id, uint32_t address, uint32_t num_bytes)
  7. uint8_t hal_flash_align(uint8_t flash_id)
  8. int hal_flash_init(void)

2.3.7 看门狗(Watchdog)

hal_watchdog_init接口可用于设置一个可重复运行的不超过expire_msecs看门狗计时器。

  1. int hal_watchdog_init(uint32_t expire_msecs)

看门狗通过调用hal_watchdog_enable()开启,看门狗需要一个小于设置周期的时间内不断喂狗(hal_watchdog_tickle()),否则将触发看门狗,导致看门狗超时、复位。

参考资料:

http://mynewt.apache.org/latest/os/modules/hal/hal.html