言京 · 2022年12月11日 · 上海市

【聆思CSK6 视觉AI开发套件试用】CSK6011a物联网初体验

开发板基本介绍

官方文档介绍非常详细,AI功能支持头肩检测和人脸检测。系统依托于开源嵌入式系统zephyr。
 title=
从SDK可以看出,聆思科技是在zephyr的sdk基础上整合了开发板和设备树信息,并为开发板各组件开发了专门的设备驱动,并优化了原本west命令的复杂使用,再次封装了一个快捷编译命令lisa。zephyr有着完善的编译系统,通过Kconfig可以非常快速的布置编译选项,加入新的feature。设备树延续linux的方案,方便linux工程师快速上手设备驱动。

AI功能

根据官方文档的指引编译镜像和导入模型文件等,在PC端可以看到测试结果:
 title=
串口的信息:

*** Booting Zephyr OS build e32aeccd52a1  ***                                                                                                         
- Device name: DVPI                                                                                                                                   
[00:00:03.164,000] <inf> hsd: Setup resource [head_thinker] which in <0x18500060,0xa6ce0>                                                             
[00:00:03.164,000] <inf> hsd: Setup resource [gesture_thinker] which in <0x185a6d40,0x135d40>                                                         
[00:00:03.321,000] <inf> hsd: fmt: [VYUY] width [640] height [480]                                                                                    
[00:00:03.321,000] <inf> hsd: Alloc video buffer: 921600                                                                                              
[00:00:03.322,000] <inf> hsd: Alloc video buffer: 921600                                                                                              
[00:00:03.322,000] <inf> hsd: Alloc video buffer: 921600

移植TF-M

TF-M,trustedfirmware-M是arm研发的基于M核的一个可信开源安全系统,聆思科技的芯片是基于ARM China的IP核,支持v8-M架构,理论上是可以移植tf-m的。

翻看zephyr的源码可以看出目前已经支持tf-m 1.6.0的模组了,官方提供了对an521的支持。即在Non-secure的环境中运行zephyr的镜像,而在secure的环境中运行tfm secure镜像。an521是arm的公版架构的一块板子,因此移植需要加入tf-m对csk6011a芯片的支持。

我尝试移植了一段时间,发现自己对tf-m仍然学艺不精,工作量很大。我是打算将model放在secure partition的protected storage里面,这样AI的model就不会被篡改,如果csk支持TF-M,将对产品的安全有很大的意义。不仅仅是上电前模型数据不会被测信道攻击所篡改,在运行时,ram处于secure world时,也是对外安全的。

等我有空继续研究,恰好我在团队里也打算扩展TF-M在不同领域的应用。

OLED显示

放弃了上面的方向,于是我打算将评测搞简单点,将测试结果显示在一个0.96寸的OLED的显示屏上,在设备树下挂载设备:

&csk6011a_nano_pinctrl{
                /* I2C alternate function */
                pinctrl_i2c0_scl_default: i2c0_scl_default{
                        pinctrls = <I2C0_SCL_GPIOA_04>;
                };
                
                pinctrl_i2c0_sda_default: i2c0_sda_default{
                        pinctrls = <I2C0_SDA_GPIOA_05>;
                };
};

&i2c0 {
        status = "okay";
        pinctrl-0 = <&pinctrl_i2c0_scl_default &pinctrl_i2c0_sda_default>; 
        pinctrl-names = "default";
        lcd: lcd@21{
        compatible = "test,lcd";
        reg = <0x21>;
                status = "okay";
    };
};

编译后发现通过csk的i2c驱动写不进去,暂时没找到问题所在,不过在sdk提供的例程中,发现slave接收有问题,顺便报了一个bug。

wifi连接

6011a_nano在sdk示例中并没有直接提供代码,需要移植一下,阅读wifi_sta的例程,可以看出esp32走的是spi接口,我们先在设备树上挂上设备:

        ...
        pinctrl_spi1_sclk_default: spi1_sclk_default{
                pinctrls = <SPI1_CLK_GPIOA_20>;
        };

        pinctrl_spi1_mosi_default: spi1_mosi_default{
                pinctrls = <SPI1_MOSI_GPIOA_19>;
        };

        pinctrl_spi1_miso_default: spi1_miso_default{
                pinctrls = <SPI1_MISO_GPIOA_18>;
        };

        pinctrl_spi1_cs_default: spi1_cs_default{
                pinctrls = <SPI1_CS_N_GPIOA_17>;
        };
        
        ...

&spi1 {
        pinctrl-0 = <&pinctrl_spi1_sclk_default &pinctrl_spi1_mosi_default &pinctrl_spi1_miso_default &pinctrl_spi1_cs_default>;
        pinctrl-names = "default";
        status = "okay";
    wifi_module: esp32c3@0 {
                compatible = "espressif,esp-hosted";
                label = "WiFi module";
        spi-max-frequency = <25000000>;
        reg = <0>;
                dataready-gpios = <&gpioa 11 0>;
                handshake-gpios = <&gpioa 16 0>;
                reset-gpios = <&gpioa 10 0>;
                status = "okay";
        };
};

在配置文件里打开net的功能:

CONFIG_WIFI=y
CONFIG_CSK_WIFI_STATION=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_RX_STACK_SIZE=1024
CONFIG_NET_TX_STACK_SIZE=2048
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=64
CONFIG_NET_BUF_TX_COUNT=64
CONFIG_NET_CONTEXT_NET_PKT_POOL=y

CONFIG_NEWLIB_LIBC=y

CONFIG_NETWORKING=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_IPV4=y
CONFIG_NET_SOCKETS=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_TCP=y

再在Kconfig里面加入控制驱动的开关,这里选择的是esp32:

if WIFI

choice CSK_WIFI_OFFLOAD_MODULE
   prompt "WiFi Offload Module"
   default CSK_WIFI_ESP32C3

    config CSK_WIFI_ESP32C3
      bool "ESP32C3"

    config CSK_WIFI_XR819S
      bool "XR819S"

endchoice

endif # WIFI

然后写wifi的相关驱动,根据文档提供的接口,撰写连接代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/printk.h>
#include <sys/sys_heap.h>

#include <net/net_if.h>
#include <net/net_core.h>
#include <net/net_context.h>
#include <net/net_mgmt.h>
#include "csk6/csk_wifi.h"
#include <string.h>
#include <net/net_ip.h>
#include <net/socket.h>
#include <stdbool.h>

/// \brief Wi-Fi模块的返回状态
typedef enum {
    WIFI_OK,
    WIFI_ERROR
}wifi_status_t;

/// \brief Wi-Fi模块的当前模式
typedef enum {
    WIFI_NONE_MODE      = 0,
    WIFI_STA_MODE       = 1,
    WIFI_AP_MODE        = 2,
    WIFI_AP_STA_MODE    = 3,
}wifi_mode_t;

/**
 * @brief
 *  封装了STA相关的接口,后续增加AP和其他相关接口功能
 */
typedef struct {
    char mac[24];           /*!< MAC地址 */
    char ip[16];            /*!< 设备IP */
    char netmask[16];       /*!< 子网掩码 */
    char gw[16];            /*!< 网关 */
    char ssid[32];          /*!< 连接热点名称 */
    char passwd[32];        /*!< 连接热点密码 */

    /**
    * @brief 初始化wifi模块
    *
    * @return wifi_status_t 返回状态
    */
    wifi_status_t (*init)(void);

    /**
    * @brief 反初始化wifi模块
    *
    * @return wifi_status_t 返回状态
    */
    wifi_status_t (*uninit)(void);

    /**
    * @brief 连接热点
    *
    * @return wifi_status_t 返回状态
    */
    wifi_status_t (*connect_ap)(const char* ssid, const char* passwd);

    /**
     * @brief: 断开热点
     * @return wifi_status_t 返回状态
     */    
    wifi_status_t (*disconnect_ap)(void);

    // 
    /**
     * @brief: 扫描可用热点
     * @return wifi_status_t 返回状态
     */    
    // wifi_status_t (*scan_ap)(void);

    /**
     * @brief: 检查Wi-Fi模块是否工作正常
     * @return wifi_status_t 返回状态
     */
    bool (*is_connect)(void);
    /*!< Wi-Fi当前连接状态 */
    bool connect_status;
    
}wifi_t;


void wifi_test(void);
void udp_test();

/// 日志分等级管理
typedef enum {
    EVERBOSE,            ///< 冗长日志
    EDEBUG,                ///< 调试日志
    EINFO,                ///< 信息日志
    EWARN,                ///< 警告日志
    EERROR,                ///< 错误日志
    LOG_LEVEL_MAX        ///< 日志分级最大值
}log_level_t;

/// 记录当前日志等级的静态全局变量
static log_level_t g_log_level = EDEBUG;

/// 日志等级设置
static inline void set_log_level(log_level_t level)
{
    g_log_level = level;
}

/// 去除文件路径,Keil工程的路径是'\'反斜杠,由于单个'\'是转义字符,因此需要通过'\\'把反斜杠改为普通字符
/// 其他系统中,如linux系统,需要把'\\'改为'/'
#define LOG_FILE_NAME_STRIP(name) strrchr(name, '/')?(strrchr(name, '/')+1):name

/// 输出日志,大于等于当前日志等级的会输出,并显示输出的当前文件、函数和行号信息方便查询 
#define LOG(level, format, ...) \
    { \
        if (level >= g_log_level) { \
            printk("[%s %s:%d]""["#level"]"format"\n", \
                LOG_FILE_NAME_STRIP(__FILE__),__FUNCTION__,__LINE__, ##__VA_ARGS__); \
        } \
    }


__weak wifi_status_t esp32_c3_wifi_init(void);
__weak wifi_status_t esp32_c3_wifi_uninit(void);
__weak wifi_status_t esp32_c3_wifi_connect_ap(const char* ssid, 
                                            const char* passwd);
__weak wifi_status_t esp32_c3_wifi_disconnect_ap(void);
// __weak wifi_status_t esp32_c3_wifi_scan_ap(void);
__weak bool esp32_c3_wifi_is_connect(void);

///< wifi单例
static wifi_t wifi = {
    .init           = esp32_c3_wifi_init,
    .uninit         = esp32_c3_wifi_uninit,
    .connect_ap     = esp32_c3_wifi_connect_ap,
    .disconnect_ap  = esp32_c3_wifi_disconnect_ap,
    // .scan_ap        = esp32_c3_wifi_scan_ap,
    .is_connect     = esp32_c3_wifi_is_connect,
    .connect_status = false,
};


static csk_wifi_event_cb_t wifi_event_cb;
static csk_wifi_result_t wifi_result;
static struct net_mgmt_event_callback dhcp_cb;

static void handler_cb(struct net_mgmt_event_callback *cb, 
                    uint32_t mgmt_event, struct net_if *iface)
{
    if (mgmt_event != NET_EVENT_IPV4_DHCP_BOUND) {
        return;
    }

    char buf[NET_IPV4_ADDR_LEN];

    snprintf(wifi.ip, sizeof(wifi.ip), "%s", 
        net_addr_ntop(AF_INET, &iface->config.dhcpv4.requested_ip, 
                    buf, sizeof(buf)));
    snprintf(wifi.netmask, sizeof(wifi.netmask), "%s", 
        net_addr_ntop(AF_INET, &iface->config.ip.ipv4->netmask, 
                    buf, sizeof(buf)));
    snprintf(wifi.gw, sizeof(wifi.gw), "%s", 
        net_addr_ntop(AF_INET, &iface->config.ip.ipv4->gw, 
                    buf, sizeof(buf)));

    LOG(EDEBUG, "Your address: %s,Subnet: %s,Router: %s", 
        wifi.ip, wifi.netmask, wifi.gw);
    
    wifi.connect_status = true;
}


static void wifi_event_handler(csk_wifi_event_t events, void *event_data,
                                uint32_t data_len, void *arg)
{
    if (events & CSK_WIFI_EVT_STA_CONNECTED) {
        // wifi.connect_status = true;
        LOG(EDEBUG, "[WiFi sta] connected");
    } else if (events & CSK_WIFI_EVT_STA_DISCONNECTED) {
        wifi.connect_status = false;
        LOG(EDEBUG, "[WiFi sta] disconnected");
    } else {
        abort();
    }
}


__weak wifi_status_t esp32_c3_wifi_init(void)
{
    uint8_t mac_addr[6] = {0};

    /* CSK WiFi 驱动初始化 */
    int rc = csk_wifi_init();
    if (rc != 0) {
        LOG(EERROR, "wifi get mac failed, ret: %d\n", rc);
        return WIFI_ERROR;
    }
    rc = csk_wifi_get_mac(CSK_WIFI_MODE_STA, mac_addr);
    if (rc != 0) {
        LOG(EERROR, "wifi get mac failed, ret: %d\n", rc);
        return WIFI_ERROR;
    }
    snprintf(wifi.mac, sizeof(wifi.mac), "%x:%x:%x:%x:%x:%x", 
            mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3],
            mac_addr[4], mac_addr[5]);
    LOG(EDEBUG, "mac address:%s", wifi.mac);

    return (rc == 0)?WIFI_OK:WIFI_ERROR;
}


__weak wifi_status_t esp32_c3_wifi_uninit(void)
{
    /* CSK WiFi 驱动初始化 */
    int rc = csk_wifi_deinit();

    return (rc == 0)?WIFI_OK:WIFI_ERROR;
}


__weak wifi_status_t esp32_c3_wifi_connect_ap(const char* ssid, 
                                                const char* passwd)
{
    /* 配置WiFi回调事件参数 */
    wifi_event_cb.handler = &wifi_event_handler;
    wifi_event_cb.events = 
        CSK_WIFI_EVT_STA_CONNECTED | CSK_WIFI_EVT_STA_DISCONNECTED;
    wifi_event_cb.arg = NULL;

    /* 注册WiFi回调事件 */
    csk_wifi_add_callback(&wifi_event_cb);

    /* WiFi参数配置 */
    csk_wifi_sta_config_t sta_config = {0};
    snprintf(sta_config.ssid, sizeof(sta_config.ssid), "%s", ssid);
    snprintf(sta_config.pwd, sizeof(sta_config.pwd), "%s", passwd);
    sta_config.encryption_mode = CSK_WIFI_AUTH_WPA2_PSK;

    int retry_count = 0;
    do {
        LOG(EDEBUG, "connecting to wifi: %s ...", sta_config.ssid);
        /* 连接WiFi */
        int ret = csk_wifi_sta_connect(&sta_config, &wifi_result, K_FOREVER);
        if (ret == 0) {
            snprintf(wifi.ssid, sizeof(wifi.ssid), "%s", ssid);
            snprintf(wifi.passwd, sizeof(wifi.passwd), "%s", passwd);
            break;
        } else {
            if (wifi_result == CSK_WIFI_ERR_STA_FAILED) {
                retry_count++;
                LOG(EERROR, "retry to connecting wifi ... %d", retry_count);
            } else {
                LOG(EERROR, "AP not found or invalid password");
                return WIFI_ERROR;
            }
        }
    } while (retry_count < 10);

    if (retry_count >= 10) return WIFI_ERROR;
    
    /* 打印已连接WiFi信息 */
    LOG(EDEBUG, "--------------------------Current AP info-------------------------------");
    LOG(EDEBUG, "ssid: %s  pwd: %s  bssid: %s  channel: %d  rssi: %d\n",
        sta_config.ssid, sta_config.pwd, sta_config.bssid, sta_config.channel,
        sta_config.rssi);
    LOG(EDEBUG, "------------------------------------------------------------------------");
    /* 初始化并注册 DHCP BOUND 事件,设备获取 ipv4 地址后产生回调 */
    net_mgmt_init_event_callback(&dhcp_cb, handler_cb, NET_EVENT_IPV4_DHCP_BOUND);
    net_mgmt_add_event_callback(&dhcp_cb);
    struct net_if *iface = net_if_get_default();
    if (!iface) {
        LOG(EDEBUG, "wifi interface not available");
        return WIFI_ERROR;
    }
    /* 开启dhcp client,DHCP 用来分配 IP */
    net_dhcpv4_start(iface);

    return WIFI_OK;
}


__weak wifi_status_t esp32_c3_wifi_disconnect_ap(void)
{
    int rc = csk_wifi_sta_disconnect(&wifi_result, K_FOREVER);

    return (rc == 0)?WIFI_OK:WIFI_ERROR;
}


__weak bool esp32_c3_wifi_is_connect(void)
{
    return wifi.connect_status;
}

void wifi_test(void)
{
    // 初始化Wi-Fi模块
    wifi.init();

    // 连接AP热点
    wifi.connect_ap("jianliang", "95898063");

    LOG(EINFO, "wifi connected\n");
}

连接wifi后的log打印:

[wifi_core.c esp32_c3_wifi_init:237][EDEBUG]mac address:34:b4:72:ef:a4:7c                                                                             
[wifi_core.c esp32_c3_wifi_connect_ap:272][EDEBUG]connecting to wifi: jianliang ...                                                                   
[wifi_core.c wifi_event_handler:209][EDEBUG][WiFi sta] connected                                                                                      
[wifi_core.c esp32_c3_wifi_connect_ap:293][EDEBUG]--------------------------Current AP info-------------------------------                            
[wifi_core.c esp32_c3_wifi_connect_ap:294][EDEBUG]ssid: jianliang  pwd: 95898063  bssid: 64:64:4a:e3:af:67  channel: 10  rssi: -44                    
                                                                                                                                                      
[wifi_core.c esp32_c3_wifi_connect_ap:297][EDEBUG]------------------------------------------------------------------------                            
[wifi_core.c wifi_test:334][EINFO]wifi connected

测试udp连接和传输:

void udp_test()
{
    const char* send_msg = "this is a udp test message.";
    const char* server_ip = "192.168.70.1";
    const short server_port = 8080;
    struct sockaddr_in addr = {0};

    while(!wifi.connect_status) { delay_ms(100); }

    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        LOG(EERROR, "socket init failed");
        return ;
    }

    LOG(EDEBUG, "udp init successed");
}

通过UDP就可以把AI识别测试结果通过网络发送到其他设备上:

gesture_t gesture = {
                    .x = msg_data.hsd.rect.x,
                    .y = msg_data.hsd.rect.y,
                    .w = msg_data.hsd.rect.w,
                    .h = msg_data.hsd.rect.h,
                    .gesture = msg_data.hsd.gesture_state,
                    .score =
                        msg_data.hsd
                            .gesture_scores[msg_data.hsd.gesture_state],
                    .status =
                        msg_data.hsd
                            .gesture_status[msg_data.hsd.gesture_state],
                    .id = msg_data.hsd.id};

image.png

推荐阅读
关注数
5165
内容数
99
聆思科技官方专栏,专注AIOT芯片,持续分享有趣的解决方案。商务合作微信:listenai-csk 技术交流QQ群:825206462
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息