4.7. pika_hal 设备抽象层

pika_hal 是一个设备抽象层,其设计初衷是简化设备控制和提高跨平台开发的效率。它将硬件设备(例如 GPIO、IIC、PWM 等)的操作抽象成统一的 API 接口,使得在不同的硬件平台(如 STM32、ESP32、BL602 等)上可以使用相同的代码来进行设备控制。

4.7.1. 安装

pika_hal 内置在 PikaStdDevice 模块内,安装步骤如下:

  • 在 requestment.txt 中加入 PikaStdDevice 的依赖。

PikaStdDevice
  • 运行 pikaPackage.exe

4.7.2. 概述

适配 pika_hal 的硬件平台可以直接使用 PikaPython 提供的 Python 模块,例如 PikaStdDevice 模块和一些传感器、电机等其他模块。这些模块使用的是 pika_hal 的统一 API,因此,一旦硬件平台适配了 pika_hal,就可以直接使用这些 Python 模块,无需再为每个模块进行平台适配。

同时,pika_hal 兼顾了跨平台、脚本开发和性能:

  • 跨平台:pika_hal 的设计思想是提供统一的设备操作API,因此,适配了 pika_hal 的硬件平台可以使用统一的设备操作代码,极大地提高了代码的可移植性和复用性。

  • 脚本开发:通过使用 PikaPython 提供的 Python 模块,开发者可以使用 Python 这种高级语言进行脚本开发,大大简化了设备控制的复杂度,并且可以快速地进行原型设计和迭代开发。

  • 性能:pika_hal 是用纯C语言编写的,因此,它在执行效率上比使用解释型语言(如 Python)编写的代码更高。此外,pika_hal 在设计时考虑了内部环节的精简,尽可能地减少了执行过程中的资源消耗,提高了运行性能。

总之,pika_hal 是一个旨在简化设备控制、提高跨平台开发效率、支持脚本开发,并且兼顾运行性能的设备抽象层。

4.7.3. 讲解视频

【Pika Python 设备抽象层】pika_hal

4.7.4. 设计理念

  • 高效。纯 C 语言实现,内部环节精简。

  • 标准。采用类 linux 的设计,所有类型的设备操作有且仅有类似于文件的 5 个标准 API: open()close()write()read()ioctl()

4.7.5. 编程模型

_images/image-20221207132547828.png

所有设备均遵循类 linux 文件的编程模型,所有类型的设备均使用 pika_dev 结构体来作为设备句柄,所有类型的设备均有且只有以下五个控制 API:

4.7.6. open()

  • 概述

    open() 函数用于打开一个设备,最先调用。

  • 函数原型

    pika_dev* pika_hal_open(PIKA_HAL_DEV_TYPE dev_type, char* name);
    
  • 参数

参数 类型 功能 备注
dev_type PIKA_HAL_DEV_TYPE 设备类型 如 PIKA_HAL_GPIO 为 GPIO 设备,PIKA_HAL_SPI 为 SPI 设备。
name char* 设备名 如 PA0 ,SPI2 等
(return) pika_dev 设备句柄 如果成功打开设备,将会返回设备句柄 pika_dev 的指针,如果打开失败会返回 NULL。

4.7.7. close()

  • 概述

    close() 函数用于关闭一个设备,最后调用,关闭设备时需要调用 pika_hal_close() 避免出现内存泄漏。

  • 函数原型

    int pika_hal_close(pika_dev* dev);
    
  • 参数

参数 类型 功能 备注
dev pika_dev* 设备句柄 要操作的设备句柄。
(return) int 错误值 错误值为 0 表示操作成功,其他返回值表示操作失败,返回值为错误码。

4.7.8. ioctl()

  • 概述

    ioctl() 函数用于对设备进行控制,包括:

    • 配置 - config

    • 使能 - enable

    • 失能 - disable

  • 函数原型

    int pika_hal_ioctl(pika_dev* dev, PIKA_HAL_IOCTL_CMD cmd, ...);
    
  • 参数

参数 类型 功能 备注
dev pika_dev* 设备句柄 要操作的设备句柄。
cmd PIKA_HAL_IOCTL_CMD 控制命令 可填 PIKA_HAL_IOCTL_ENABLE,PIKA_HAL_IOCTL_DISABLE,PIKA_HAL_IOCTL_CONFIG 三个命名,分别对应使能、失能和配置。
... (None)/pika_hal_config_XXXX * 控制参数 该参数可填可不填,根据 cmd 的取值而定。当 cmd 为 PIKA_HAL_IOCTL_ENABLE、PIKA_HAL_IOCTL_DISABLE 时,该参数不填。当 cmd 为 PIKA_HAL_IOCTL_CONFIG 时,该参数为 pika_hal_config_XXXX *cfg,其中 XXXX 是设备的类型,如 pika_hal_config_GPIO 、pika_hal_config_SPI 等,应和 pika_hal_open() 中使用的设备的类型相同。
(return) int 错误值 错误值为 0 表示操作成功,其他返回值表示操作失败,返回值为错误码。

4.7.9. read()

  • 概述

    read() 函数用于从设备中读取数据。

  • 函数原型

    int pika_hal_read(pika_dev* dev, void* buf, size_t len);
    
  • 参数

参数 类型 功能 备注
dev pika_dev* 设备句柄 要操作的设备句柄。
buf void* 读取缓冲区 对于 GPIO、ADC 这样只能读取单个数据的设备,缓冲区使用 uint32_t。
len size_t 读取的字节数 对于 GPIO、ADC 这样只能读取单个数据的设备,长度为 sizeof(uint32_t)。
(return) int 错误值 错误值为 0 表示操作成功,其他返回值表示操作失败,返回值为错误码。

4.7.10. write()

  • 概述

    write() 函数用于向设备写入数据。

  • 函数原型

    int pika_hal_write(pika_dev* dev, void* buf, size_t len);
    
  • 参数

参数 类型 功能 备注
dev pika_dev* 设备句柄 要操作的设备句柄。
buf void* 写入缓冲区 对于 GPIO、DAC 这样只能写入单个数据的设备,缓冲区使用 uint32_t。
len size_t 写入的字节数 对于 GPIO、DAC 这样只能读取单个数据的设备,长度为 sizeof(uint32_t)。
(return) int 错误值 错误值为 0 表示操作成功,其他返回值表示操作失败,返回值为错误码。

4.7.11. ioctl config

pika_hal_ioctl()cmd 参数填入 PIKA_HAL_IOCTL_CONFIG 时对设备进行配置,这一部分在设备驱动中是最关键的,因此单独说明。

cmdPIKA_HAL_IOCTL_CONFIG 时,pika_hal_ioctl() 的第三个参数为配置结构体的指针 pika_hal_config_XXXX *cfg,其中 XXXX 是设备的类型,如 pika_hal_config_GPIOpika_hal_config_SPI 等。

4.7.11.1. 配置结构体

创建配置结构体时,应确保结构体被清零,避免未定义行为。推荐写法:

pika_hal_config_XXXX cfg = {0};

配置结构体在 pika_hal.h 中定义,如 GPIO 的配置结构体:

typedef struct {
    PIKA_HAL_GPIO_DIR dir;
    PIKA_HAL_GPIO_PULL pull;
    PIKA_HAL_GPIO_SPEED speed;
    void (*event_callback_rising)(pika_dev* dev);
    void (*event_callback_falling)(pika_dev* dev);
} pika_hal_GPIO_config;

4.7.11.2. 配置项

配置结构体中的配置项的取值通常由 enum 定义,如 PIKA_HAL_GPIO_DIR 代表的 GPIO 的方向,其取值由以下的 enum 来决定:

typedef enum {
    _PIKA_HAL_GPIO_DIR_UNUSED = 0,
    PIKA_HAL_GPIO_DIR_IN,
    PIKA_HAL_GPIO_DIR_OUT,
} PIKA_HAL_GPIO_DIR;

PIKA_HAL_GPIO_DIR_INPIKA_HAL_GPIO_DIR_OUT 表示 GPIO 的方向为输入或者输出,是常规的取值。而 _PIKA_HAL_GPIO_DIR_UNUSED 是一个特殊的取值,这个取值表示不在配置结构体中使用 PIKA_HAL_GPIO_DIR 这个配置项。

_PIKA_HAL_GPIO_DIR_UNUSED 被指定时,实际的配置操作会进行下面的处理:

  • 如果没有配置过该配置项,那么该配置项会被自动赋予默认值。(默认配置)

  • 如果已经配置过该配置项,那么该配置项会维持原先的状态。(配置差分修改)

4.7.11.3. 默认配置

这样设计的好处在于,在使用 ioctl config 对设备进行初次配置时,不必填写所有的配置项,只需要填写实际关心的配置项即可,这降低了填写配置项的心智负担。

比如,如果在应用中不关心 GPIO 的翻转速度,那么可以这样填写配置结构体:

pika_hal_config_GPIO cfg = {0};
cfg.dir = PIKA_HAL_GPIO_DIR_OUT;
cfg.pull = PIKA_HAL_GPIO_PULL_UP;

这时 cfg.speed 取值为 _PIKA_HAL_GPIO_SPEED_UNUSED (创建清零的结构体后,所有配置项均为 0,而 _PIKA_HAL_XXXX_UNUSED 的数值总是 0)

在运行 ioct config 后,实际设备接收到的配置值如下:

cfg.dir = PIKA_HAL_GPIO_DIR_OUT;
cfg.pull = PIKA_HAL_GPIO_PULL_UP;
cfg.speed = PIKA_HAL_GPIO_SPEED_10M;
cfg.event_callback_rising = NULL;
cfg.event_callback_falling = NULL;

这时 cfg.speed 会自动被设置为默认值 PIKA_HAL_GPIO_SPEED_10M

4.7.11.4. 配置差分修改

在对设备进行再次配置时,通常只想要对设备的某几个值进行修改,而不是修改全部值,因此不想修改的值可以填写为 _PIKA_HAL_XXXX_UNUSED

例如:

pika_hal_config_GPIO cfg2 = {0};
cfg2.dir = PIKA_HAL_GPIO_DIR_IN;

在运行 ioct config 后,实际设备接收到的配置值如下:

cfg.dir = PIKA_HAL_GPIO_DIR_IN;
cfg.pull = PIKA_HAL_GPIO_PULL_UP;
cfg.speed = PIKA_HAL_GPIO_SPEED_10M;
cfg.event_callback_rising = NULL;
cfg.event_callback_falling = NULL;

只有被指定了的 dir 配置项被修改了,其他的配置项均维持原状。

4.7.11.5. 配置合并策略

配置的默认值和合并策略(新的配置结构体如何合并进旧的配置值)在 pika_hal.c 中的 pika_hal_XXXX_ioctl_merge_config() 函数可以看到,例如 GPIO 的配置合并策略:

int pika_hal_GPIO_ioctl_merge_config(pika_hal_GPIO_config* dst,
                                     pika_hal_GPIO_config* src) {
    _IOCTL_CONFIG_USE_DEFAULT(dir, PIKA_HAL_GPIO_DIR_IN);
    _IOCTL_CONFIG_USE_DEFAULT(pull, PIKA_HAL_GPIO_PULL_NONE);
    _IOCTL_CONFIG_USE_DEFAULT(speed, PIKA_HAL_GPIO_SPEED_10M);
    _IOCTL_CONFIG_USE_DEFAULT(event_callback_rising, NULL);
    _IOCTL_CONFIG_USE_DEFAULT(event_callback_falling, NULL);
    return 0;
}

_IOCTL_CONFIG_USE_DEFAULT 宏指定了配置项的默认值,例如 speed 配置项的 _PIKA_HAL_XXXX_UNUSED 默认值为 PIKA_HAL_GPIO_SPEED_10M

也有例外的合并策略,例如 PWM 的配置中 duty 配置项的合并:

int pika_hal_PWM_ioctl_merge_config(pika_hal_PWM_config* dst,
                                    pika_hal_PWM_config* src) {
    _IOCTL_CONFIG_USE_DEFAULT(period, PIKA_HAL_PWM_PERIOD_1MS * 10);
    dst->duty = src->duty;
    return 0;
}

其中 duty 配置项就是直接使用新配置结构体的配置值,而没有使用默认值(因为 duty 为 0 时也是有意义的,不能认为是 _PIKA_HAL_XXXX_UNUSED)。

4.7.12. 驱动适配

为平台适配 pika_hal,就是为设备重写以下的 pika_hal_platform_XXXX 为前缀的 WEAK 函数,其中XXXX 为设备类型名,如 GPIOPWM 等。

PIKA_WEAK int pika_hal_platform_XXXX_open(pika_dev* dev, char* name);
PIKA_WEAK int pika_hal_platform_XXXX_close(pika_dev* dev);
PIKA_WEAK int pika_hal_platform_XXXX_read(pika_dev* dev, void* buf, size_t count);
PIKA_WEAK int pika_hal_platform_XXXX_write(pika_dev* dev, void* buf, size_t count);
PIKA_WEAK int pika_hal_platform_XXXX_ioctl_enable(pika_dev* dev);
PIKA_WEAK int pika_hal_platform_XXXX_ioctl_disable(pika_dev* dev);
PIKA_WEAK int pika_hal_platform_XXXX_ioctl_config(pika_dev* dev, pika_hal_XXXX_config* cfg);

示例适配代码:

博流智能 BL_IOT_SDK:https://gitee.com/Lyon1998/pikapython/tree/master/package/BLIOT

博流智能 buffalo_sdk:https://gitee.com/Lyon1998/pikapython/tree/master/package/bflb

STM32G0:https://gitee.com/Lyon1998/pikapython/tree/master/package/STM32G0

ESP32:https://gitee.com/Lyon1998/pikapython/tree/master/package/ESP32

4.7.13. 案例教程

4.7.13.1. 案例教程1 - STM32G0 上对 GPIO 设备的适配

源码链接

4.7.13.1.1. 数据结构定义

首先,你需要定义一个数据结构来保存与你的GPIO设备相关的所有数据。在我们的例子中,这个结构是platform_data_GPIO

typedef struct platform_data_GPIO {
    GPIO_TypeDef* gpioPort;
    uint16_t gpioPin;
    uint32_t pinMode;
    uint32_t gpioPull;
} platform_data_GPIO;

这个结构中保存了GPIO的端口(gpioPort)、引脚编号(gpioPin)、模式(pinMode)以及上拉/下拉状态(gpioPull)。

4.7.13.1.2. 打开设备

pika_hal_platform_GPIO_open函数用于打开GPIO设备。首先,它尝试获取GPIO端口和引脚信息。如果失败,它将返回-1。如果成功,它将创建一个新的platform_data_GPIO实例,填充相关的数据,并将其附加到设备实例中。

int pika_hal_platform_GPIO_open(pika_dev* dev, char* name) {
    __platform_printf("Open: %s \r\n", name);
    GPIO_TypeDef* gpioPort = GPIO_get_Group(name);
    if (NULL == gpioPort) {
        __platform_printf("Error: get GPIO group failed.\r\n");
        return -1;
    }
    uint16_t gpioPin = GPIO_get_pin(name);
    if (0 == gpioPin) {
        __platform_printf("Error: get GPIO Pin failed.\r\n");
        return -1;
    }
    platform_data_GPIO* data = pikaMalloc(sizeof(platform_data_GPIO));
    data->gpioPin = gpioPin;
    data->gpioPort = gpioPort;
    dev->platform_data = data;
    return 0;
}

4.7.13.1.3. 关闭设备

pika_hal_platform_GPIO_close函数用于关闭GPIO设备。它简单地释放之前分配的platform_data_GPIO实例。

int pika_hal_platform_GPIO_close(pika_dev* dev) {
    if (NULL != dev->platform_data) {
        pikaFree(dev->platform_data, sizeof(platform_data_GPIO));
        dev->platform_data = NULL;
    }
    return 0;
}

4.7.13.1.4. 读取数据

pika_hal_platform_GPIO_read函数用于从GPIO设备中读取数据。它首先获取设备的状态,然后复制状态到提供的缓冲区中。

int pika_hal_platform_GPIO_read(pika_dev* dev, void* buf, size_t count) {
    platform_data_GPIO* data = dev->platform_data;
    uint32_t level = LL_GPIO_IsInputPinSet(data->gpioPort, data->gpioPin);
    if (level != 1 && level != 0) {
        return -1;
    }
    memcpy(buf, &level, count);
    return 0;
}

4.7.13.1.5. 写入数据

pika_hal_platform_GPIO_write函数用于向GPIO设备写入数据。它将缓冲区的内容解析为GPIO的新状态,然后设置该状态。

int pika_hal_platform_GPIO_write(pika_dev* dev, void* buf, size_t count) {
    platform_data_GPIO* data = dev->platform_data;
    uint32_t level = 0;
    memcpy(&level, buf, count);
    if (level == 0) {
        LL_GPIO_ResetOutputPin(data->gpioPort, data->gpioPin);
        return 0;
    }
    if (level == 1) {
        LL_GPIO_SetOutputPin(data->gpioPort, data->gpioPin);
        return 0;
    }
    return -1;
}

4.7.13.1.6. 启用设备

pika_hal_platform_GPIO_ioctl_enable函数用于启用GPIO设备。它启用GPIO的时钟,然后将GPIO的状态设置为低电平。然后,它根据platform_data_GPIO中的数据配置GPIO。

int pika_hal_platform_GPIO_ioctl_enable(pika_dev* dev) {
    platform_data_GPIO* data = dev->platform_data;

    if (0 != _enable_gpio_clk(data->gpioPort)) {
        return -1;
    }

    /*Configure GPIO pin Output Level */
    LL_GPIO_ResetOutputPin(data->gpioPort, data->gpioPin);

    LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
    /*Configure GPIO*/
    GPIO_InitStruct.Pin = data->gpioPin;
    GPIO_InitStruct.Mode = data->pinMode;
    GPIO_InitStruct.Pull = data->gpioPull;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    LL_GPIO_Init(data->gpioPort, &GPIO_InitStruct);
    return 0;
}

4.7.13.1.7. 禁用设备

pika_hal_platform_GPIO_ioctl_disable函数用于禁用GPIO设备。它简单地停用了GPIO。

int pika_hal_platform_GPIO_ioctl_disable(pika_dev* dev) {
    platform_data_GPIO* data = dev->platform_data;
    LL_GPIO_DeInit(data->gpioPort);
    return 0;
}

4.7.13.1.8. 配置设备

最后,pika_hal_platform_GPIO_ioctl_config函数用于配置GPIO设备。它根据提供的配置信息更新platform_data_GPIO中的数据,并重新配置GPIO。

int pika_hal_platform_GPIO_ioctl_config(pika_dev* dev,
                                        pika_hal_GPIO_config* cfg) {
    platform_data_GPIO* data = dev->platform_data;
    switch (cfg->dir) {
        case PIKA_HAL_GPIO_DIR_IN:
            data->pinMode = LL_GPIO_MODE_INPUT;
            break;
        case PIKA_HAL_GPIO_DIR_OUT:
            data->pinMode = LL_GPIO_MODE_OUTPUT;
            break;
        default:
            data->pinMode = LL_GPIO_MODE_OUTPUT;
    }
    switch (cfg->pull) {
        case PIKA_HAL_GPIO_PULL_UP:
            data->gpioPull = LL_GPIO_PULL_UP;
            break;
        case PIKA_HAL_GPIO_PULL_DOWN:
            data->gpioPull = LL_GPIO_PULL_DOWN;
            break;
        default:
            data->gpioPull = LL_GPIO_PULL_NO;
    }
    LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
    /*Configure GPIO*/
    GPIO_InitStruct.Pin = data->gpioPin;
    GPIO_InitStruct.Mode = data->pinMode;
    GPIO_InitStruct.Pull = data->gpioPull;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    LL_GPIO_Init(data->gpioPort, &GPIO_InitStruct);
    return 0;
}

4.7.13.2. 案例教程2 - ESP32 上对 UART 设备的适配

源码链接

4.7.13.2.1. 首先,我们需要包含必要的头文件

#include <stdint.h>
#include "BaseObj.h"
#include "dataStrs.h"
#include "driver/uart.h"
#include "pika_hal.h"
#include "pika_hal_ESP32_common.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

4.7.13.2.2. 定义UART平台相关的数据结构

typedef struct platform_data_UART {
    uart_port_t uartPort;
    uart_config_t uart_conf;
    QueueHandle_t uart_queue;
    gpio_num_t tx_port;
    gpio_num_t rx_port;
    gpio_num_t rts_port;
    gpio_num_t cts_port;
    PIKA_BOOL event_thread_started;
} platform_data_UART;

此结构用于保存平台相关的UART设备配置和状态信息。包括uart端口,配置参数,接收数据的队列以及四个GPIO端口(发送、接收、RTS和CTS)。

4.7.13.2.3. 开启和关闭设备的函数

int pika_hal_platform_UART_open(pika_dev* dev, char* name) {
    /* UARTX */
    if (name[0] != 'U' || name[1] != 'A' || name[2] != 'R' || name[3] != 'T') {
        return -1;
    }
    platform_data_UART* uart = pikaMalloc(sizeof(platform_data_UART));
    memset(uart, 0, sizeof(platform_data_UART));

    uart->uartPort = fast_atoi(name + 4);
    if (uart->uartPort < 0 || uart->uartPort >= UART_NUM_MAX) {
        pikaFree(uart, sizeof(platform_data_UART));
        return -1;
    }
    dev->platform_data = uart;
    return 0;
}

int pika_hal_platform_UART_close(pika_dev* dev) {
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    if (NULL == uart) {
        return -1;
    }
    pikaFree(uart, sizeof(platform_data_UART));
    dev->platform_data = NULL;
    return 0;
}

函数 pika_hal_platform_UART_open()用于打开UART设备并为其分配内存。传入的设备名称应以”UART”开头,后面跟着端口号。

函数 pika_hal_platform_UART_close()用于关闭UART设备并释放其内存。

4.7.13.2.4. 读写设备的函数

int pika_hal_platform_UART_read(pika_dev* dev, void* buf, size_t count) {
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    if (NULL == uart) {
        return -1;
    }
    return uart_read_bytes(uart->uartPort, buf, count, 100);
}

int pika_hal_platform_UART_write(pika_dev* dev, void* buf, size_t count) {
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    if (NULL == uart) {
        return -1;
    }
    return uart_write_bytes(uart->uartPort, buf, count);
}

函数 pika_hal_platform_UART_read()从UART设备读取数据。

函数 pika_hal_platform_UART_write()向UART设备写入数据。

4.7.13.2.5. UART事件处理函数

static void uart_event_task(void* pvParameters) {
    pika_debug("uart_event_task start: pvParameters: %p", pvParameters);
    pika_dev* dev = (pika_dev*)pvParameters;
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    pika_hal_UART_config* cfg = (pika_hal_UART_config*)dev->ioctl_config;
    if (NULL == uart) {
        pika_platform_printf("Error: uart_event_task: uart is NULL\r\n");
        vTaskDelete(NULL);
        return;
    }
    uart_event_t event;
    for (;;) {
        // Waiting for UART event.
        if (xQueueReceive(uart->uart_queue, (void*)&event,
                          (TickType_t)portMAX_DELAY)) {
            pika_debug("UART%d: event.type: %d", uart->uartPort,
                       event.type);
            switch (event.type) {
                // Event of UART receving data
                /*We'd better handler data event fast, there would be much more
                data events than other types of events. If we take too much time
                on data event, the queue might be full.*/
                case UART_DATA:
                    pika_assert(g_event_lock != NULL);
                    xSemaphoreTake(g_event_lock, portMAX_DELAY);
                    cfg->event_callback(dev, event.type);
                    xSemaphoreGive(g_event_lock);
                    break;
                // Others
                default:
                    pika_platform_printf("[INFO] uart event type: %d\r\n", event.type);
                    break;
            }
        }
    }
    g_PikaMemInfo.heapUsed -= EVENT_TRHEAD_STACK_SIZE;
    vTaskDelete(NULL);
}

这个任务用于处理来自UART设备的事件。它会根据事件类型进行不同的处理。例如,当收到UART数据时,它会触发回调函数处理数据。

4.7.13.2.6. 设备控制函数

int pika_hal_platform_UART_ioctl_enable(pika_dev* dev) {
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    pika_debug("UART%d_enable: platform_data: %p", uart->uartPort, uart);
    pika_hal_UART_config* cfg = (pika_hal_UART_config*)dev->ioctl_config;
    if (NULL == uart || NULL == cfg) {
        return -1;
    }
    uart_driver_install(uart->uartPort, 1024, 1024, 20, &uart->uart_queue, 0);
    pika_debug(
        "UART%d: baudrate:%d, data_bits:%d, parity:%d, stop_bits: %d",
        uart->uartPort, uart->uart_conf.baud_rate, uart->uart_conf.data_bits,
        uart->uart_conf.parity, uart->uart_conf.stop_bits);
    uart_param_config(uart->uartPort, &uart->uart_conf);
    pika_debug("UART%d: tx:%d, rx:%d, rts:%d, cts:%d", uart->uartPort,
               uart->tx_port, uart->rx_port, uart->rts_port, uart->cts_port);
    uart_set_pin(uart->uartPort, uart->tx_port, uart->rx_port, uart->rts_port,
                 uart->cts_port);
    return 0;
}

int pika_hal_platform_UART_ioctl_disable(pika_dev* dev) {
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    if (NULL == uart) {
        return -1;
    }
    uart_driver_delete(uart->uartPort);
    return 0;
}

这些函数用于控制UART设备的开启和关闭。

pika_hal_platform_UART_ioctl_enable()函数用于安装UART驱动,配置UART参数,并设置UART的GPIO。

pika_hal_platform_UART_ioctl_disable()函数用于删除UART驱动。

int pika_hal_platform_UART_ioctl_config(pika_dev* dev,
                                        pika_hal_UART_config* cfg) {
    platform_data_UART* uart = (platform_data_UART*)dev->platform_data;
    pika_debug("UART%d_config: platform_data: %p", uart->uartPort, uart);
    if (NULL == uart || NULL == cfg) {
        pika_platform_printf("Error: uart config error, uart:%p, cfg:%p\r\n",
                             uart, cfg);
        return -1;
    }
    uart->uart_conf.baud_rate = cfg->baudrate;
    pika_debug("UART%d: set baudrate to %d", uart->uartPort,
               uart->uart_conf.baud_rate);
    switch (cfg->data_bits) {
        case PIKA_HAL_UART_DATA_BITS_5:
            uart->uart_conf.data_bits = UART_DATA_5_BITS;
            break;
        case PIKA_HAL_UART_DATA_BITS_6:
            uart->uart_conf.data_bits = UART_DATA_6_BITS;
            break;
        case PIKA_HAL_UART_DATA_BITS_7:
            uart->uart_conf.data_bits = UART_DATA_7_BITS;
            break;
        case PIKA_HAL_UART_DATA_BITS_8:
            uart->uart_conf.data_bits = UART_DATA_8_BITS;
            break;
        default:
            uart->uart_conf.data_bits = UART_DATA_8_BITS;
            break;
    }
    pika_debug("UART%d: set data_bites to %d", uart->uartPort,
               uart->uart_conf.data_bits);
    switch (cfg->parity) {
        case PIKA_HAL_UART_PARITY_NONE:
            uart->uart_conf.parity = UART_PARITY_DISABLE;
            break;
        case PIKA_HAL_UART_PARITY_ODD:
            uart->uart_conf.parity = UART_PARITY_ODD;
            break;
        case PIKA_HAL_UART_PARITY_EVEN:
            uart->uart_conf.parity = UART_PARITY_EVEN;
            break;
        default:
            uart->uart_conf.parity = UART_PARITY_DISABLE;
            break;
    }
    pika_debug("UART%d: set parity to %d", uart->uartPort,
               uart->uart_conf.parity);
    switch (cfg->stop_bits) {
        case PIKA_HAL_UART_STOP_BITS_1:
            uart->uart_conf.stop_bits = UART_STOP_BITS_1;
            break;
        case PIKA_HAL_UART_STOP_BITS_1_5:
            uart->uart_conf.stop_bits = UART_STOP_BITS_1_5;
            break;
        case PIKA_HAL_UART_STOP_BITS_2:
            uart->uart_conf.stop_bits = UART_STOP_BITS_2;
            break;
        default:
            uart->uart_conf.stop_bits = UART_STOP_BITS_1;
            break;
    }
    pika_debug("UART%d: set stop_bits to %d", uart->uartPort,
               uart->uart_conf.stop_bits);
    switch (cfg->flow_control) {
        case PIKA_HAL_UART_FLOW_CONTROL_NONE:
            uart->uart_conf.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
            break;
        case PIKA_HAL_UART_FLOW_CONTROL_RTS:
            uart->uart_conf.flow_ctrl = UART_HW_FLOWCTRL_RTS;
            break;
        case PIKA_HAL_UART_FLOW_CONTROL_CTS:
            uart->uart_conf.flow_ctrl = UART_HW_FLOWCTRL_CTS;
            break;
        case PIKA_HAL_UART_FLOW_CONTROL_RTS_CTS:
            uart->uart_conf.flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS;
            break;
        default:
            uart->uart_conf.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
            break;
    }
    uart->uart_conf.source_clk = UART_SCLK_DEFAULT;
    pika_debug("UART%d: set flow_control to %d", uart->uartPort,
               uart->uart_conf.flow_ctrl);

    if (NULL == cfg->TX || NULL == cfg->RX) {
        pika_platform_printf("Error: uart config error, TX:%p, RX:%p\r\n",
                             cfg->TX, cfg->RX);
        return -1;
    }
    uart->tx_port = ((platform_data_GPIO*)(cfg->TX->platform_data))->gpioPort;
    uart->rx_port = ((platform_data_GPIO*)(cfg->RX->platform_data))->gpioPort;
    if (NULL != cfg->RTS && NULL != cfg->CTS) {
        uart->rts_port =
            ((platform_data_GPIO*)(cfg->RTS->platform_data))->gpioPort;
        uart->cts_port =
            ((platform_data_GPIO*)(cfg->CTS->platform_data))->gpioPort;
    } else {
        uart->rts_port = UART_PIN_NO_CHANGE;
        uart->cts_port = UART_PIN_NO_CHANGE;
    }

    if (dev->is_enabled){
        pika_debug("UART%d: uart is enabled, reconfig", uart->uartPort);
        uart_param_config(uart->uartPort, &uart->uart_conf);
    }

    /* support event callback */
    if (dev->is_enabled == PIKA_TRUE && NULL != cfg->event_callback &&
        PIKA_HAL_EVENT_CALLBACK_ENA_ENABLE == cfg->event_callback_ena) {
        // hosal_uart_ioctl(platform_uart, HOSAL_UART_MODE_SET,
        //                  (void*)HOSAL_UART_MODE_INT);
        switch (cfg->event_callback_filter) {
            /* Configure UART to interrupt mode */
            case PIKA_HAL_UART_EVENT_SIGNAL_RX:
                pika_debug("Setting UART_RX callback");
                uart_enable_rx_intr(uart->uartPort);
                break;
            case PIKA_HAL_UART_EVENT_SIGNAL_TX:
                pika_debug("Setting UART_TX callback");
                uart_enable_tx_intr(uart->uartPort, 1, 0);
                break;
            default:
                __platform_printf(
                    "Error: not supported event callback filter %d\r\n",
                    cfg->event_callback_filter);
                return -1;
        }
        if (uart->event_thread_started == PIKA_FALSE){
            /* start irq task thread */
            pika_debug("Starting uart event task:%p", dev);
            if (NULL == g_event_lock){
                g_event_lock = xSemaphoreCreateMutex();
            }
            g_PikaMemInfo.heapUsed += EVENT_TRHEAD_STACK_SIZE;
            xTaskCreate(uart_event_task, "uart_event_task", EVENT_TRHEAD_STACK_SIZE, dev, 12, NULL);
            uart->event_thread_started = PIKA_TRUE;
        }
    }
    return 0;
}

pika_hal_platform_UART_ioctl_config()该函数用于配置 UART(通用异步接收/发送)设备的各项参数。函数的参数包括一个 pika_dev 类型的指针 dev,该指针指向一个代表特定设备的数据结构,以及一个 pika_hal_UART_config 类型的指针 cfg,该指针指向一个存储 UART 配置信息的数据结构。

函数开始时,它首先从设备数据结构 dev 中获取平台特定的数据,然后进行一系列的错误检查。如果 uartcfgNULL,则输出错误信息并返回 -1

然后,函数设置 UART 设备的各种参数,包括波特率(baud rate)、数据位(data bits)、奇偶校验位(parity bits)、停止位(stop bits)和流控制(flow control)。这些参数都来源于 cfg 数据结构。每个参数设置后,都会输出一条调试信息,显示已经设置的参数值。

接着,函数进行进一步的错误检查。如果 cfg 数据结构中的 TXRXNULL,则输出错误信息并返回 -1。否则,从 TXRXRTSCTS 对应的 GPIO 端口中获取 UART 的发送(TX)、接收(RX)、请求发送(RTS)和清除发送(CTS)端口。

如果 UART 设备已启用,函数将重新配置 UART 参数。此外,如果启用了事件回调,并且配置了支持的回调事件过滤器,函数将设置 UART 中断并可能启动一个事件处理任务。

如果所有配置和检查都成功,函数最后返回 0,表示成功完成配置。

4.7.13.3. 案例教程3 - ESP32 上对 WIFI 设备的适配

源码链接

首先,我们需要包含一些必要的头文件,如 pika_hal.h、esp_wifi.h、esp_event.h 等。这些头文件提供了 pika_hal 和 esp32 的相关定义和函数。

#include "../pikascript-lib/pikastddevice/pika_hal.h"
#include "esp_event.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "freertos/freertos.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "nvs_flash.h"

然后,我们定义了一些全局变量和常量,用于记录 WIFI 的状态和配置信息。例如,wifi_started 表示 WIFI 是否已经启动,wifi_sta_connect_requested 表示是否请求连接到某个 WIFI 热点,wifi_sta_disconn_reason 表示连接失败的原因等。

static volatile pika_bool wifi_started = pika_false;
static volatile pika_bool wifi_sta_connect_requested = pika_false;
static volatile pika_bool wifi_sta_connected = pika_false;
static volatile pika_hal_wifi_status wifi_sta_disconn_reason =
    pika_hal_wifi_status_idle;
static eventgrouphandle_t wifi_event_group;
static esp_netif_t* sta_netif = null;
static esp_netif_t* ap_netif = null;

接下来,我们定义了一个辅助函数 _ip_str2u32 ,用于将字符串形式的 IP 地址转换为 uint32_t 类型的数值。这个函数会遍历字符串中的每个数字,并将其存储到一个 uint8_t 类型的数组中,然后返回这个数组所代表的 uint32_t 值。

uint32_t _ip_str2u32(char* ip_str) {
    uint32_t ip = 0;
    uint8_t* ip_u8 = (uint8_t*)&ip;
    char* p = ip_str;
    for (int i = 0; i < 4; i++) {
        ip_u8[i] = atoi(p);
        p = strchr(p, '.');
        if (p == null) {
            break;
        }
        p++;
    }
    return ip;
}

紧接着,我们定义了一个事件处理函数 event_handler ,用于响应不同类型和 ID 的事件,并根据事件数据进行相应的操作。例如,在 WIFI_EVENT_STA_START 事件中,如果请求连接到某个热点,则调用 esp_wifi_connect 函数;在 IP_EVENT_STA_GOT_IP 事件中,则设置 wifi_sta_connected 为 PIKA_TRUE 并设置 wifi_sta_disconn_reason 为 PIKA_HAL_WIFI_STATUS_GOT_IP 等。

static void event_handler(void* event_handler_arg,
                          esp_event_base_t event_base,
                          int32_t event_id,
                          void* event_data) {
    // ...
}

然后,我们实现了几个主要的设备操作函数,分别对应于打开、关闭、配置和控制 WIFI 设备。这些函数都需要传入一个指向设备对象(pika_dev)的指针,并根据不同情况返回相应的结果或错误码。

  • pika_hal_platform_WIFI_open 函数用于初始化 NVS(非易失性存储)、网络接口和事件循环,并创建一个事件组(event group)。

  • pika_hal_platform_WIFI_close 函数用于反初始化 NVS、网络接口和事件循环,并删除事件组。

  • pika_hal_platform_WIFI_ioctl_config 函数用于根据设备对象中的 ioctl_config 字段(pika_hal_WIFI_config 类型)来配置 WIFI 的模式、热点信息等。如果是 STA 模式,则不支持配置;如果是 AP 模式,则调用 esp_wifi_set_config 函数来设置热点的 SSID、密码、信道、认证模式和最大连接数等。

  • pika_hal_platform_WIFI_ioctl_enable 函数用于启动或停止 WIFI。首先,根据 ioctl_config 字段中的 mode 字段来确定 WIFI 的模式,然后调用 esp_wifi_set_mode 函数来设置模式。如果 WIFI 还没有启动,则还需要注册事件处理函数,创建默认的网络接口,以及调用 esp_wifi_start 函数来启动 WIFI,并设置 wifi_started 为 PIKA_TRUE;否则,只需要设置模式即可。

  • pika_hal_platform_WIFI_ioctl_disable 函数用于停止或反初始化 WIFI。如果 WIFI 已经启动,则调用 esp_wifi_stop 和 esp_wifi_deinit 函数来停止和反初始化 WIFI,并设置 wifi_started 为 PIKA_FALSE;否则,返回 -1 表示错误。

  • pika_hal_platform_WIFI_ioctl_others 函数用于处理其他类型的控制命令,如获取 WIFI 的状态、是否激活、扫描附近的热点等。这些命令都通过 cmd 参数来指定,并通过 arg 参数来传递或返回数据。例如,在 PIKA_HAL_IOCTL_WIFI_GET_STATUS 命令中,根据 wifi_sta_connect_requested 和 wifi_sta_connected 等变量来判断当前的连接状态,并将其赋值给 arg 指向的 pika_hal_wifi_status 类型变量。

4.7.14. 参与贡献

请参考 参与社区贡献->贡献模块 部分的文档发布你编写的模块。