CN / EN
文档反馈
感谢关注汇顶文档,期待您的宝贵建议!
感谢您的反馈,祝您愉快!
无匹配项 共计114个匹配页面
文档中心 > GR551x HAL及LL驱动用户手册/ HAL驱动/ 简介/ 如何使用HAL驱动 Copy URL

如何使用HAL驱动

GR551x的HAL层驱动的调用流程如图 3所示。

图 3 HAL驱动调用流程
说明:
  • 图中 浅蓝框内的函数为HAL驱动实现的函数。
  • 图中 深蓝框内的操作为开发者需在用户程序中实现的代码。
  • 图中 橙色框内的函数为在用户程序中实现的msp(MCU Specific Package)函数。
  • 图中 紫色框线内的函数为在中断处理函数中调用的函数。
具体的调用流程描述如下:
  1. 开发者在用户程序文件(main.cgr55xx_msp.c等)中重写外设PPP的msp函数hal_ppp_msp_init()及hal_ppp_msp_deinit()。
  2. 如果需要使用中断方式的API,则还需重写相应的回调函数,如hal_ppp_process_cplt_callback()。
  3. 开发者在用户程序文件中定义外设PPP的句柄(handle),并配置相关参数。
  4. 开发者调用hal_ppp_init()初始化外设PPP,其中hal_ppp_init()由PPP的驱动文件实现,初始化过程中调用开发者重写的hal_ppp_msp_init() 以初始化PPP使用的GPIO引脚、NVIC中断、DMA通道。
  5. 对于轮询、中断、DMA三种IO操作方式,各自的调用流程如下:
    • 轮询方式下,开发者调用驱动文件中的hal_ppp_process()进行IO操作,操作完成后才会退出当前函数。
    • 中断方式下,开发者调用驱动文件中的hal_ppp_process_it()进行IO操作,中断使能后退出当前函数,IO操作会在PPP的中断处理函数PPP_IRQHandler中进行,操作完成时调用开发者重写的回调函数,从而进行事件完成通知。
    • DMA方式下,开发者调用驱动文件中的hal_ppp_process_dma()进行IO操作,传输开始后退出当前函数,IO操作由DMA外设实现,操作完成时DMA的中断处理函数DMA_IRQHandler()会调用开发者重写的回调函数,从而进行事件完成通知。
  6. 外设PPP使用完成时,开发者可调用PPP驱动文件中的hal_ppp_deinit()反初始化外设PPP,恢复相应的寄存器为默认值。反初始化过程中会调用开发者重写的hal_ppp_msp_deinit()以反初始化PPP使用的GPIO引脚、NVIC中断及DMA通道。

下文将对HAL驱动调用流程中的初始化、IO操作、超时检测及错误检查进行详细说明。

HAL驱动初始化

HAL全局初始化

gr55xx_hal.c中提供了若干API接口,可对HAL驱动所使用的SysTick进行初始化和反初始化,从而实现外设驱动轮询方式数据收发的超时检测。

  • hal_init():系统启动后可调用该函数实现以下功能:

    调用hal_msp_init()初始化时钟、GPIO、中断、DMA等。

  • hal_deinit():调用hal_msp_deinit()反初始化时钟、GPIO、中断、DMA等。

外设MSP初始化

在初始化外设PPP的过程中,hal_ppp_init()会调用hal_ppp_msp_init()函数对该外设所使用的GPIO、中断、DMA进行初始化。在反初始化的过程中,则会调用hal_ppp_msp_deinit()。hal_ppp_msp_init()及hal_ppp_msp_deinit()为weak类型的空函数,在实际使用过程中,开发者需根据具体应用重写,这两个函数在驱动中的weak类型的空函数如下:

__weak void hal_ppp_msp_init(ppp_handle_t *p_ppp)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(p_ppp);
}

__weak void hal_ppp_msp_deinit(ppp_handle_t *p_ppp)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(p_ppp);
}

HAL驱动IO操作

HAL驱动对外设的IO读写操作包括三种读写方式:轮询、中断、DMA。

轮询方式

在轮询方式中,数据的读写操作是轮询式的,即读写API会在数据的读写操作完成时才退出。读写成功完成时,读写API返回HAL_OK状态,否则返回HAL_ERROR或HAL_TIMEOUT状态。通过hal_ppp_get_state()及hal_ppp_get_error()可获取具体的状态及错误码。在轮询方式的读写API中,为了避免程序陷入死循环,HAL驱动采用了超时检测机制,可使用API中的timeout参数指定超时时间。

轮询方式读写API的典型定义如下:

hal_status_t hal_ppp_transmit(ppp_handle_t *p_ppp, uint8_t *p_data, uint16_t size, uint32_t timeout)
{
    if ((NULL == p_data) || (0U == size))
    {
        return  HAL_ERROR;
    }
    (…)
    while (data processing is running)
   {
       if (timeout reached)
      {
           return HAL_TIMEOUT;
       }
    }
(…)
return HAL_OK;
}

中断方式

在中断方式中,数据的读写操作是非轮询式的,即读写API会在使能读写中断后退出,读写操作将在外设中断处理函数中进行。读写完成时,程序将调用开发者实现的回调函数来通知APP。开发者可以通过hal_ppp_get_state()接口来获取当前的读写状态。

在中断方式中,驱动中主要定义了如下接口函数:

  • hal_ppp_process_it():中断方式的读写API。
  • hal_ppp_irq_handler():外设PPP的中断处理函数。
  • _weak hal_ppp_process_cplt_callback():操作完成时的回调函数,由开发者实现。
  • _weak hal_ppp_process_error_callback():操作出错时的回调函数,由开发者实现。

使用中断方式的API接口时,开发者首先需要在gr55xx_it.c中注册中断处理函数hal_ppp_irq_handler(),然后调用hal_ppp_process_it()进行数据读写。

回调函数在外设的HAL驱动中被定义为weak类型,即开发者需自己实现要使用的回调函数,从而在数据读写完成时进行缓冲区内存的释放等操作。

例如,通过中断方式进行UART0读写操作的接口使用示例代码如下:

main.c文件中:

uart_handle_t uart_handle;
int main(void)
{   
    uart_handle.p_instance         = UART0; 
    uart_handle.init.baud_rate     = 115200;
    uart_handle.init.data_bits     = UART_DATABITS_8;
    uart_handle.init.stop_bits     = UART_STOPBITS_1;
    uart_handle.init.parity        = UART_PARITY_NONE;
    uart_handle.init.hw_flow_ctrl  = UART_HWCONTROL_NONE;
    hal_uart_init(&uart_handle);

    char *p_tx_buff = “Hello World!\r\n”;
    hal_uart_transmit_it(&uart_handle,p_tx_buff,strlen((char*)p_tx_buff));
    while (hal_uart_get_state(&uart_handle) != HAL_UART_STATE_READY);

    hal_uart_deinit(&uart_handle);
}

void hal_uart_tx_cplt_callback(uart_handle_t *p_uart)
{
    (…)

}
void hul_uart_error_callback(uart_handle_t *p_uart)
{
    (…)
}

gr55xx_it.c文件中:

void UART0_IRQHandler(void)
{
    Hal_uart_irq_handler(&uart_handle);
}
说明:

UART0_IRQHandler()也可直接放在main.c中。

DMA方式

在DMA方式中,数据的读写操作也是非轮询式的。在数据读写完成时,中断处理函数调用相应的回调函数通知APP。开发者还可通过hal_ppp_get_state()接口来读取当前读写操作的状态。

在DMA方式中,驱动中主要定义了如下API接口:

  • hal_ppp_process_dma():DMA方式的读写API。
  • hal_ppp_irq_handler():外设PPP的中断处理函数。
  • _weak hal_ppp_process_cplt_callback():操作完成时的回调函数,由开发者实现。
  • _weak hal_ppp_process_error_callback():操作出错时的回调函数,由开发者实现。

使用DMA方式的API接口时,开发者首先需注册中断处理函数hal_dma_irq_handler(),对于部分外设还需要注册hal_ppp_irq_handler(),如I2C。然后在hal_ppp_msp_init()中初始化需要使用的DMA通道,最后再调用hal_ppp_process_dma()进行数据读写。

例如,通过DMA方式进行UART0的读写操作,接口使用的示例代码如下:

  • main.c文件
    uart_handle_t uart_handle;
    int main(void)
    {   
        uart_handle.p_instance          = UART0;
        uart_handle.init.baud_rate     = 115200;
        uart_handle.init.data_bits     = UART_DATABITS_8;
        uart_handle.init.stop_bits     = UART_STOPBITS_1;
        uart_handle.init.parity         = UART_PARITY_NONE;
        uart_handle.init.hw_flow_ctrl  = UART_HWCONTROL_NONE;
        hal_uart_init(&uart_handle);
    
        char *p_tx_buff = “Hello World!\r\n”;
        hal_uart_transmit_dma(&uart_handle, p_tx_buff, strlen((char*)p_tx_buff));
        while (hal_uart_get_state(&uart_handle) != HAL_UART_STATE_READY);
        hal_uart_deinit(&uart_handle);
    }
    
    void hal_uart_tx_cplt_callback(uart_handle_t *p_uart)
    {
        (…)
    }
    void hul_uart_error_callback(uart_handle_t *p_uart)
    {
        (…)
    }
  • gr55xx_hal_msp.c文件
    void hal_uart_msp_init (uart_handle_t *p_uart)
    {  
        static dma_handle_t hdma_tx;
        static dma_handle_t hdma_rx;
        (…)
        hal_dma_init(&hdma_tx);
        hal_dma_init(&hdma_rx);
    
        __HAL_LINKDMA(p_uart, p_dmatx, hdma_tx);
        __HAL_LINKDMA(p_uart, p_dmarx, hdma_rx);
        (…)
    }
  • gr55xx_it.c文件
    void UART0_IRQHandler(void)
    {
        hal_uart_irq_handler(&uart_handle);
    }
    void DMA_IRQHandler(void)
    {
        Hal_uart_irq_handler(uart_handle.p_dmatx);
    }
说明:

hal_uart_msp_init()和UART0_IRQHandler()均可直接放在main.c中。

HAL驱动超时检测及错误检查

超时检测

在轮询方式的API接口中,HAL驱动通过超时检测来避免程序因为错误状态而进入死循环中。如下为SPI轮询方式发送数据的API接口:

hal_status_t hal_spi_transmit(spi_handle_t *p_spi, uint8_t *p_data, uint32_t length, 
                              uint32_t timeout)

接口中的输入参数timeout为发送数据所需要的最大超时时间。一旦超过指定的超时时间,API接口将返回HAL_TIMEOUT。

HAL驱动中的超时时间在文件gr55xx_hal_def.h中定义,取值范围为0 ~ HAL_MAX_DELAY,其中HAL_MAX_DELAY = 0xFFFFFFFF。具体的超时时间取值及描述如表 10所示:

表 10 超时的取值
超时时间值 描述

0

无超时,不等待,若标志检测条件不满足则立即退出循环。

1 ~ (HAL_MAX_DELAY - 1)

超时时间,单位ms。

HAL_MAX_DELAY

一直循环,直到操作成功完成才退出。

此外,在某些情况下,固定长度的超时时间会在某些外设的HAL驱动中使用,例如I2C的busy检测超时时间为25 ms。与API接口输入参数中的超时时间不同,固定长度的超时时间在API内部以宏的方式进行使用,并且不能被修改。

错误检查

为了提高驱动程序的健壮性,避免出现不可预期的错误,HAL驱动中实现了错误检查机制。

  • 输入参数有效性检查

    在给开发者提供的API函数中,作为输入参数的变量需确保是已定义的且有效的,否则会导致程序崩溃或者进入未定义的状态,因此在HAL驱动中实现了输入参数的有效性检查。

    例如,在API函数hal_uart_transmit_it()中会检查输入参数的缓冲区指针和长度的有效性,代码如下:

    hal_status_t hal_uart_transmit_it(uart_handle_t *p_uart, uint8_t *p_data, uint16_t size)
    {
        /* Check that a Tx process is not already ongoing */
        if(HAL_UART_STATE_READY == p_uart->g_state)
        {
            if((NULL == p_data ) || (0U == size))
            {
                return HAL_ERROR;
            }
            (…)
        }
    }
    
  • 句柄有效性检查

    外设句柄是外设HAL驱动中最重要的参数,其存储着外设驱动的配置参数以及各种运行时变量等,因此在外设的初始化函数hal_ppp_init()中实现外设句柄有效性检查。

    例如,在UART的初始化函数hal_uart_init中检查句柄的有效性,代码如下:

    hal_status_t hal_uart_init(uart_handle_t *p_uart)
    {
        /* Check the UART handle allocation */
        if(NULL == p_uart)
        {
            return HAL_ERROR;
        }
       (…)
    }
  • 超时错误检查

    在轮询方式的API函数中,需要对操作时间进行检查。一旦超时,需返回超时状态。

    例如,UART在轮询方式下接收数据时,检查是否超时,代码如下:

    hal_status_t hal_uart_receive(uart_handle_t *p_uart, uint8_t *p_data, uint16_t size, 
                                  uint32_t timeout)
    {
    (…)
        /* as long as data have to be received */
        while(0U < p_uart->rx_xfer_count)
        {
            if(HAL_OK != uart_wait_fifo_flag_until_timeout(p_uart, UART_FLAG_FIFO_RFNE, RESET, 
                                                           tickstart, timeout)
            {
                return HAL_TIMEOUT;
            }
           (…)
        }
    (…)
    }

    在外设句柄中定义了error_code这一全局变量,用于存储API操作过程中的错误码。因此,在API函数返回HAL_ERROR后,开发者可调用hal_ppp_get_error()函数获取具体的错误类型,例如:

    uint32_t hal_uart_get_error(uart_handle_t *p_uart)
    {
        return p_uart -> error_code;
    }

运行时参数检查

HAL驱动提供了输入参数的运行时检查,用于判断输入的参数是否在允许的取值范围内。运行时检查是通过gr_assert_param宏实现的,该宏定义在gr55xx_hal_conf.h头文件中,可通过USE_FULL_ASSERT宏来启用/禁用运行时检查功能。

例如,在I2C的初始化函数中对I2C实例、速度、本机设备地址、设备地址模式等参数进行判断,代码如下:

hal_status_t hal_i2c_init(i2c_handle_t *p_i2c)
{
    (…)
    /* Check the parameters */
    gr_assert_param(IS_I2C_ALL_INSTANCE(p_i2c->instance));
    gr_assert_param(IS_I2C_SPEED(p_i2c->init.speed));
    gr_assert_param(IS_I2C_OWN_ADDRESS(p_i2c->init.own_address));
    gr_assert_param(IS_I2C_ADDRESSING_MODE(p_i2c->init.addressing_mode));
    gr_assert_param(IS_I2C_GENERAL_CALL(p_i2c->init.general_call_mode));
    (…)
}

如果gr_assert_param宏的判断结果是false,则assert_failed()函数将会被调用,并返回判断出错的文件名及行号,其中的assert_failed()函数需要由开发者进行实现。gr_assert_param宏的定义及实现的代码如下:

#ifdef  USE_FULL_ASSERT
/**
  * @brief The gr_assert_param macro is used for function's parameterscheck.
  * @param  expr If expr is false, it calls assert_failed function
  *        which reports the name of the source file and the source
  *        line number of the call that failed. 
  *        If expr is true, it returns no value.
  * @retval None
  */
#define gr_assert_param(expr) ((expr) ? (void)0U : assert_failed((char  *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
  void assert_failed(char* file, uint32_t line);
#else
  #define gr_assert_param(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */

扫描关注

打开微信,使用“扫一扫”即可关注。