【CC2530 教程 十二】CC2530 Z-Stack 硬件抽象层

目录

一、硬件抽象层简介:

(1)HAL 硬件抽象层是什么?

(2)通俗易懂的解释:

(3)具体例子:

二、硬件抽象层HAL:

(1)HAL文件目录及工程结构:

(2)工程结构说明:

(3)HAL文件使用:


一、硬件抽象层简介:

(1)HAL 硬件抽象层是什么?

        硬件抽象层(Hardware Abstraction Layer,简称 HAL) 是一种软件设计模式,它的目的是将硬件的具体实现细节与软件的其他部分隔离开来。简单来说,HAL 就像是一个中间层,它站在软件和硬件之间,让软件可以通过统一的接口来操作不同的硬件,而不需要关心硬件的具体实现细节。

(2)通俗易懂的解释:

        想象一下,你是一个厨师,你需要用不同的厨具来烹饪食物。如果你每次都要直接操作各种各样的锅、碗、瓢、盆,可能会很麻烦,尤其是当厨具的种类和品牌不同时。于是,你决定用一套统一的工具来操作这些厨具,比如用一个通用的锅铲来翻炒,用一个通用的勺子来搅拌。这样,你就不需要记住每种厨具的具体操作方法,只需要掌握通用工具的使用方法即可。

        在嵌入式系统中,HAL 就像是这套通用工具。它提供了一组统一的函数接口,让软件开发者可以通过这些接口来操作硬件,而不需要直接处理硬件的复杂细节。比如,你要控制一个 LED 灯亮起,你可以通过 HAL 提供的函数来实现,而不需要直接操作硬件寄存器。

(3)具体例子:

        假设你有一个开发板,上面有多种硬件设备,比如 LED 灯、按键、串口等。如果没有 HAL,你可能需要直接操作每个硬件设备的寄存器来控制它们,这会非常复杂,尤其是当硬件设备的类型和型号不同时。有了 HAL,你可以通过调用 HAL 提供的函数来控制这些设备,比如:

// 通过 HAL 函数点亮 LED 灯
HAL_LED_On(LED1);

// 通过 HAL 函数读取按键状态
uint8_t keyState = HAL_KEY_Read(KEY1);

        这些函数内部已经封装了对硬件寄存器的操作,你只需要调用这些函数即可,不需要关心硬件的具体实现。

二、硬件抽象层HAL:

(1)HAL文件目录及工程结构:

HAL的文件位于 C:\Texas Instruments\Z-Stack 3.0.2\Components\hal 文件夹中。

进入 HAL 文件,如下:

其中:

组名称 说明
common 存放公共文件。
include 存放驱动程序接口文件。
target 存放各种类型主板的驱动程序源文件。

打开这三个文件夹,有:

文件名 说明
hal_assert.c 实现条件合法性判断功能的源代码文件。
hal_drivers.c 硬件抽象层 HAL 任务初始化及事件处理函数所在文件,是 HAL 的入口源文件。

文件名 说明
hal_adc.h ADC(模拟数字转换)驱动程序头文件。
hal_assert.h 条件合法性判断功能的头文件。
hal_board.h 各种类型主板的硬件资源配置头文件。
hal_defs.h 通用定义头文件。
hal_drivers.h HAL任务初始化及事件处理函数的头文件。
hal_flash.h FLASH(存储器)驱动程序头文件。
hal_key.h 按键驱动程序头文件。
hal_lcd.h 显示屏驱动程序头文件。
hal_led.h LED驱动程序头文件。
hal_rpc.h RPC(Remote Procedure Call,远程过程调用)驱动程序头文件。
hal_sleep.h 休眠功能驱动程序头文件。
hal_timer.h 定时器驱动程序头文件。
hal_uart.h 串口驱动程序头文件。

文件夹名称 说明
CC2530EB 针对芯片主控为CC2530的评估板相关的驱动程序。
CC2530USB 针对芯片主控为CC2530的带USB转串口评估板的驱动程序。
CC2530ZNP 针对芯片主控为CC2530的ZNP(ZigBee And Processor)评估板的驱动程序。
CC2538 针对芯片主控为CC2538的评估板的驱动程序。
CC2538ZNP 针对芯片主控为CC2538的ZNP(ZigBee And Processor)评估板的驱动程序。

 以第一个为例,打开CC2530EB有:

对应类型主板的接口源文件和程序驱动源文件。

文件名 作用说明
hal_uart_dma.c 实现串口通信的直接内存访问(DMA)功能,用于高效的数据传输。
hal_uart_isr.c 包含串口通信的中断服务例程(ISR),处理串口的中断事件。
hal_adc.c 提供模数转换(ADC)功能的实现,用于采集模拟信号并转换为数字数据。
hal_aes.c 提供高级加密标准(AES)加密算法的实现,用于数据加密和解密操作。
hal_board_cfg.h 包含评估板硬件配置的宏定义和配置参数,用于适配具体的硬件平台。
hal_ccm.h 提供CCM(Counter with CBC-MAC)加密模式的相关定义和函数声明。
hal_dma.c 实现直接内存访问(DMA)功能,用于在不同内存区域或外设之间直接传输数据。
hal_dma.h 提供DMA功能的函数声明和相关宏定义,用于配置和控制DMA操作。
hal_flash.c 提供对存储器(FLASH)的操作函数,包括读取、写入和擦除等操作。
hal_key.c 实现按键输入的相关功能,包括按键扫描和状态检测。
hal_lcd.c 提供液晶显示屏(LCD)的驱动函数,用于显示字符或图形。
hal_led.c 实现LED控制功能,包括点亮、熄灭和闪烁等操作。
hal_led_cfg.h 包含LED配置的宏定义,如LED引脚定义和初始状态设置。
hal_mac_cfg.h 包含MAC层配置的宏定义,用于设置无线通信的相关参数。
hal_mcu.h 提供微控制器(MCU)相关的函数声明和宏定义,如时钟配置和复位操作。
hal_oad.c 实现无线固件更新(Over-the-Air Download,OAD)功能的源代码。
hal_oad.h 提供OAD功能的函数声明和相关宏定义,用于无线固件更新操作。
hal_ota.c 提供OTA(Over-the-Air)升级功能的实现,用于远程更新设备固件。
hal_ota.h 包含OTA功能的函数声明和相关定义,用于支持远程固件升级。
hal_sleep.c 实现低功耗睡眠模式的相关功能,用于节能操作。
hal_startup.c 包含系统启动相关的代码,如初始化和主函数入口等。
hal_timer.c 提供定时器功能的实现,用于精确的时间控制和延迟操作。
hal_types.h 定义常用的类型和数据结构,为其他模块提供统一的数据类型声明。
hal_uart.c 实现串口通信的基本功能,包括数据发送和接收操作。

(2)工程结构说明:

打开 Z-Stack 中的一个示例工程:

 打开HAL目录,分别展开Common、Include和Target目录如下:

文件夹路径 作用说明
HAL/Common 存放公共文件,包含通用函数实现和工具代码,可在不同项目和硬件平台上复用。
HAL/Include 存放各种驱动程序的接口文件,定义函数原型、数据结构和宏等,供其他模块调用和配置。
HAL/Target 存放针对特定类型主板的驱动程序源文件和配置文件,每个子文件夹对应不同的硬件平台。
HAL/Target/CC2530EB 针对主控芯片为CC2530的评估板,包含该平台下的驱动实现和配置文件。
HAL/Target/CC2530USB 针对带USB转串口的CC2530评估板,包含该平台下的驱动实现和配置文件。
HAL/Target/CC2530ZNP 针对CC2530的ZNP评估板,包含该平台下的驱动实现和配置文件。
HAL/Target/CC2538 针对主控芯片为CC2538的评估板,包含该平台下的驱动实现和配置文件。
HAL/Target/CC2538ZNP 针对CC2538的ZNP评估板,包含该平台下的驱动实现和配置文件。

(3)HAL文件使用:

初始化函数:

HAL中的初始化函数位于hal_drivers.c文件为Hal_Init(),如下:

/**************************************************************************************************
 * @fn      Hal_Init
 *
 * @brief   Hal初始化函数。
 *
 * @param   task_id - Hal TaskId
 *
 * @return  None
 **************************************************************************************************/
void Hal_Init( uint8 task_id )
{
  /*注册任务ID */
  Hal_TaskID = task_id;

#ifdef CC2591_COMPRESSION_WORKAROUND
  osal_start_reload_timer( Hal_TaskID, PERIOD_RSSI_RESET_EVT, PERIOD_RSSI_RESET_TIMEOUT );
#endif
}

驱动程序初始化:

HAL中的驱动程序初始化函数位于hal_drivers.c文件为HalDriverInit(),如下:

/**************************************************************************************************
 * @fn      Hal_DriverInit
 *
 * @brief   初始化硬件(HW)- 这些需要在任何人之前初始化。
 *          该函数负责按正确的顺序初始化各个硬件模块,确保系统正常运行。
 *
 * @param   task_id - Hal TaskId(任务ID),用于标识HAL相关的任务或事件。
 *
 * @return  None
 **************************************************************************************************/
void HalDriverInit(void)
{
  /* TIMER(定时器)*/
#if (defined HAL_TIMER) && (HAL_TIMER == TRUE)
  // 如果定义了HAL_TIMER且其值为TRUE,则初始化定时器模块
  // 定时器模块用于提供精确的时间控制和延迟功能
#endif

  /* ADC(模数转换)*/
#if (defined HAL_ADC) && (HAL_ADC == TRUE)
  // 如果定义了HAL_ADC且其值为TRUE,则初始化ADC模块
  // ADC模块用于将模拟信号转换为数字信号
  HalAdcInit();
#endif

  /* DMA(直接内存访问)*/
#if (defined HAL_DMA) && (HAL_DMA == TRUE)
  // 如果定义了HAL_DMA且其值为TRUE,则初始化DMA模块
  // DMA模块允许在外设和内存之间直接传输数据,而无需CPU干预,提高效率
  // 必须在调用任何使用DMA模块的初始化函数之前调用此函数
  HalDmaInit();
#endif

  /* AES(高级加密标准)*/
#if (defined HAL_AES) && (HAL_AES == TRUE)
  // 如果定义了HAL_AES且其值为TRUE,则初始化AES模块
  // AES模块用于数据加密和解密操作
  HalAesInit();
#endif

  /* LCD(液晶显示屏)*/
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
  // 如果定义了HAL_LCD且其值为TRUE,则初始化LCD模块
  // LCD模块用于显示字符或图形
  HalLcdInit();
#endif

  /* LED(发光二极管)*/
#if (defined HAL_LED) && (HAL_LED == TRUE)
  // 如果定义了HAL_LED且其值为TRUE,则初始化LED模块
  // LED模块用于控制LED的点亮、熄灭和闪烁等操作
  HalLedInit();
#endif

  /* UART(通用异步收发传输器)*/
#if (defined HAL_UART) && (HAL_UART == TRUE)
  // 如果定义了HAL_UART且其值为TRUE,则初始化UART模块
  // UART模块用于串行通信,实现数据的发送和接收
  HalUARTInit();
#endif

  /* KEY(按键)*/
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
  // 如果定义了HAL_KEY且其值为TRUE,则初始化按键模块
  // 按键模块用于检测按键输入和处理按键事件
  HalKeyInit();
#endif

  /* SPI(串行外设接口)*/
#if (defined HAL_SPI) && (HAL_SPI == TRUE)
  // 如果定义了HAL_SPI且其值为TRUE,则初始化SPI模块
  // SPI模块用于与外部设备进行高速串行通信
  HalSpiInit();
#endif

  /* HID(人机接口设备)*/
#if (defined HAL_HID) && (HAL_HID == TRUE)
  // 如果定义了HAL_HID且其值为TRUE,则初始化HID模块
  // HID模块用于与人机接口设备(如键盘、鼠标等)进行通信
  usbHidInit();
#endif
}

        在开发过程中可以需求来使用指定的外设,例如设置 HAL_TIMER = TRUE、HAL_ADC = TRUE、HAL_DMA = TRUE、HAL_AES = TRUE、HAL_LCD = TRUE、HAL_LED = TRUE、HAL_UART = TRUE 等等,指定对应的外设对应的宏。如果不用用到,那么就不用定义。在定义了对应的宏后就会执行对应的初始化。如果需要增加这里没有的外设,可以按照协议栈的这个架构新增外设。

事件处理:

        HAL的事件处理函数为位于hal_drivers.c文件中的Hal_ProcessEvent(),它的主要作用是处理HAL层的事件,位置如下:

/**************************************************************************************************
 * @fn      Hal_ProcessEvent
 *
 * @brief   处理 HAL 相关的事件。
 *          该函数根据传入的事件标志,调用相应的处理函数或执行特定的操作。
 *
 * @param   task_id - Hal TaskId(任务ID),用于标识HAL相关的任务。
 *          events - 需要处理的事件标志,每个标志代表一种特定的事件。
 *
 * @return  返回未处理的事件标志。
 **************************************************************************************************/
uint16 Hal_ProcessEvent(uint8 task_id, uint16 events)
{
    uint8 *msgPtr;  // 用于接收消息的指针

    (void)task_id;  // 有意未引用的参数,避免编译器警告

    // 检查是否有系统消息事件
    if (events & SYS_EVENT_MSG)
    {
        msgPtr = osal_msg_receive(Hal_TaskID);  // 接收消息

        while (msgPtr)
        {
            /* 在这里做些事情 - 目前,只是释放消息并继续 */

            /* 释放消息内存 */
            osal_msg_deallocate(msgPtr);
            /* 接收下一条消息 */
            msgPtr = osal_msg_receive(Hal_TaskID);
        }

        // 清除 SYS_EVENT_MSG 事件标志,表示该事件已处理
        return events ^ SYS_EVENT_MSG;
    }

#if (defined HAL_BUZZER) && (HAL_BUZZER == TRUE)
    // 检查是否有蜂鸣器事件
    if (events & HAL_BUZZER_EVENT)
    {
        HalBuzzerStop();  // 停止蜂鸣器
        // 清除 HAL_BUZZER_EVENT 事件标志,表示该事件已处理
        return events ^ HAL_BUZZER_EVENT;
    }
#endif

#ifdef CC2591_COMPRESSION_WORKAROUND
    // 检查是否有周期性 RSSI 重置事件
    if (events & PERIOD_RSSI_RESET_EVT)
    {
        macRxResetRssi();  // 重置 RSSI
        // 清除 PERIOD_RSSI_RESET_EVT 事件标志,表示该事件已处理
        return (events ^ PERIOD_RSSI_RESET_EVT);
    }
#endif

    // 检查是否有 LED 闪烁事件
    if (events & HAL_LED_BLINK_EVENT)
    {
#if (defined (BLINK_LEDS)) && (HAL_LED == TRUE)
        HalLedUpdate();  // 更新 LED 状态
#endif /* BLINK_LEDS && HAL_LED */
        // 清除 HAL_LED_BLINK_EVENT 事件标志,表示该事件已处理
        return events ^ HAL_LED_BLINK_EVENT;
    }

    // 检查是否有按键事件
    if (events & HAL_KEY_EVENT)
    {
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
        /* 检查按键状态 */
        HalKeyPoll();

        /* 如果中断已禁用,则安排下一次轮询 */
        if (!Hal_KeyIntEnable)
        {
            osal_start_timerEx(Hal_TaskID, HAL_KEY_EVENT, 100);
        }
#endif
        // 清除 HAL_KEY_EVENT 事件标志,表示该事件已处理
        return events ^ HAL_KEY_EVENT;
    }

#if defined POWER_SAVING
    // 检查是否有睡眠定时器事件
    if (events & HAL_SLEEP_TIMER_EVENT)
    {
        halRestoreSleepLevel();  // 恢复睡眠级别
        // 清除 HAL_SLEEP_TIMER_EVENT 事件标志,表示该事件已处理
        return events ^ HAL_SLEEP_TIMER_EVENT;
    }

    // 检查是否有电源管理保持事件
    if (events & HAL_PWRMGR_HOLD_EVENT)
    {
        (void)osal_pwrmgr_task_state(Hal_TaskID, PWRMGR_HOLD);

        (void)osal_stop_timerEx(Hal_TaskID, HAL_PWRMGR_CONSERVE_EVENT);
        (void)osal_clear_event(Hal_TaskID, HAL_PWRMGR_CONSERVE_EVENT);

        // 清除 HAL_PWRMGR_HOLD_EVENT 和 HAL_PWRMGR_CONSERVE_EVENT 事件标志
        return (events & ~(HAL_PWRMGR_HOLD_EVENT | HAL_PWRMGR_CONSERVE_EVENT));
    }

    // 检查是否有电源管理节能事件
    if (events & HAL_PWRMGR_CONSERVE_EVENT)
    {
        (void)osal_pwrmgr_task_state(Hal_TaskID, PWRMGR_CONSERVE);
        // 清除 HAL_PWRMGR_CONSERVE_EVENT 事件标志,表示该事件已处理
        return events ^ HAL_PWRMGR_CONSERVE_EVENT;
    }
#endif

    // 如果没有处理任何事件,返回 0
    return 0;
}

HAL轮询:

  HAL的HAL轮询函数为位于hal_drivers.c文件中的Hal_ProcessPoll(),它的主要作用为轮询那些不支持中断或者需要定期检查状态的硬件模块,位置如下:

/**************************************************************************************************
 * @fn      Hal_ProcessPoll
 *
 * @brief   该例程将被OSAL调用来轮询UART、TIMER等硬件模块的状态。
 *          主要用于轮询那些不支持中断或者需要定期检查状态的硬件模块。
 *
 * @param   task_id - Hal TaskId(任务ID),用于标识HAL相关的任务。
 *
 * @return  None
 **************************************************************************************************/
void Hal_ProcessPoll()
{
#if defined(POWER_SAVING)
    /* 允许在下一个OSAL事件循环之前进入睡眠模式 */
    /* 这个宏可能用于节能模式,允许系统在空闲时进入低功耗状态 */
    ALLOW_SLEEP_MODE();
#endif

    /* UART轮询 */
#if (defined HAL_UART) && (HAL_UART == TRUE)
    /* 如果定义了HAL_UART且其值为TRUE,则调用HalUARTPoll函数轮询UART状态 */
    /* UART轮询用于检查串口通信的状态,如是否有数据可读取或发送缓冲区是否为空 */
    HalUARTPoll();
#endif

    /* SPI轮询 */
#if (defined HAL_SPI) && (HAL_SPI == TRUE)
    /* 如果定义了HAL_SPI且其值为TRUE,则调用HalSpiPoll函数轮询SPI状态 */
    /* SPI轮询用于检查SPI通信的状态,如是否有数据传输完成或是否有错误发生 */
    HalSpiPoll();
#endif

    /* HID轮询 */
#if (defined HAL_HID) && (HAL_HID == TRUE)
    /* 如果定义了HAL_HID且其值为TRUE,则调用usbHidProcessEvents函数处理HID事件 */
    /* HID轮询用于处理人机接口设备(如键盘、鼠标等)的事件,如按键按下或移动事件 */
    usbHidProcessEvents();
#endif
}