2.3 硬件抽象层(HAL)
Mynewt中的硬件抽象层为底层、基础外设抽象。硬件抽象层提供了一组为Mynewt所支持的MCU而设计的服务。设备驱动程序通常用于初始化硬件、并为高层软件提供管理硬件访问的软件库。在Mynewt操作系统中,层可以按照以下方式描述。
+———————————————————————————+
| app |
+———————————————————————————+
| (n)drivers |
+———————————————————————————+
| HAL | BSP |
+—————————————+—————————————+
- 板级支持包抽象平台的特定配置,如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即可:
pkd.deps:
...
hw/hal
平台支持
并不是所有的平台都支持HAL设备。需要查阅MCU或BSP文档,查看硬件是否对感兴趣的外设支持。如果支持,就可以在mcu/src/hal_xxxx.c中查看是否已经实现,然后在构建项目时确保所有外部依赖问题都已经解决。
2.3.1 定时器(Timer)
硬件独立定时器结构以及用于配置、初始化和运行定时器的API。
描述
HAL定时器结构如下所示。用户可以根据需求声明任意数量的定时器结构。当用户调用hal_timer_start或hal_timer_start_at函数时,将会在特定的硬件定时器上排队。用户在启动定时器之前需要调用hal_timer_set_cb。
注意:用户不应该修改这个结构中的内容,且必须使用HAL的应用程序接口。
struct hal_timer
{
void *bsp_timer; /* Internal platform specific pointer */
hal_timer_cb cb_func; /* Callback function */
void *cb_arg; /* Callback argument */
uint32_t expiry; /* Tick at which timer should expire */
TAILQ_ENTRY(hal_timer) link; /* Queue linked list structure */
};
API
硬件抽象层定时器回调函数:
typedef void(* hal_timer_cb)(void *arg)
硬件抽象层定时器初始化:
int hal_timer_init(int timer_num, void *cfg)
参数:
- timer_num:需要初始化的硬件定时器
- cfg:特定硬件定时器的配置。从BSP直接到MCU特定驱动。
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头文件中定义。
#define SYSTEM_LED (37)
#define FLASH_SPI_CHIP_SELECT (3)
抽象引脚37对应端口C的pin 5,抽象引脚3为端口A的pin 3。
2.3.3 通用串行接口(UART)
包含了UART发送与接收数据的基础操作。也包含了UART配置速度、奇偶校验位等的API接口。UART在重配前需要关闭。
enum hal_uart_parity {
/** No Parity */
HAL_UART_PARITY_NONE = 0,
/** Odd parity */
HAL_UART_PARITY_ODD = 1,
/** Even parity */
HAL_UART_PARITY_EVEN = 2
};
enum hal_uart_flow_ctl {
/** No Flow Control */
HAL_UART_FLOW_CTL_NONE = 0,
/** RTS/CTS */
HAL_UART_FLOW_CTL_RTS_CTS = 1
};
/**
* 函数原型,用于UART驱动查看是否需要发送更多数据
* 若没有使能中断,驱动必须调用此接口
**/
typedef int(* hal_uart_tx_char)(void *arg)
/**
* UART驱动报告发送完成的函数原型,当发送最后一个字节时需要调用
* 若没有使能中断,驱动必须调用此接口
**/
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。
int hal_spi_config(int spi_num, struct hal_spi_settings *psettings)
SPI的配置信息包括:
struct hal_spi_settings {
uint8_t data_mode;
uint8_t data_order;
uint8_t word_size;
uint32_t baudrate; /* baudrate in kHz */
};
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中数据结构:
struct hal_i2c_master_data {
uint8_t address; /* 目的地址 */
uint16_t len; /* 发送或接收的数据量 */
uint8_t *buffer; /* 发送或接收的数据缓存 */
};
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函数提供了闪存的初始化、读、写、擦除、扇区擦除和其他操作。
int hal_flash_ioctl(uint8_t flash_id, uint32_t cmd, void * args)
int hal_flash_read(uint8_t flash_id, uint32_t address, void * dst, uint32_t num_bytes)
int hal_flash_write(uint8_t flash_id, uint32_t address, const void * src, uint32_t num_bytes)
int hal_flash_erase_sector(uint8_t flash_id, uint32_t sector_address)
int hal_flash_erase(uint8_t flash_id, uint32_t address, uint32_t num_bytes)
int hal_flash_isempty(uint8_t flash_id, uint32_t address, uint32_t num_bytes)
uint8_t hal_flash_align(uint8_t flash_id)
int hal_flash_init(void)
2.3.7 看门狗(Watchdog)
hal_watchdog_init接口可用于设置一个可重复运行的不超过expire_msecs看门狗计时器。
int hal_watchdog_init(uint32_t expire_msecs)
看门狗通过调用hal_watchdog_enable()开启,看门狗需要一个小于设置周期的时间内不断喂狗(hal_watchdog_tickle()),否则将触发看门狗,导致看门狗超时、复位。
参考资料: