开发板基本介绍
官方文档介绍非常详细,AI功能支持头肩检测和人脸检测。系统依托于开源嵌入式系统zephyr。
从SDK可以看出,聆思科技是在zephyr的sdk基础上整合了开发板和设备树信息,并为开发板各组件开发了专门的设备驱动,并优化了原本west
命令的复杂使用,再次封装了一个快捷编译命令lisa
。zephyr有着完善的编译系统,通过Kconfig可以非常快速的布置编译选项,加入新的feature。设备树延续linux的方案,方便linux工程师快速上手设备驱动。
AI功能
根据官方文档的指引编译镜像和导入模型文件等,在PC端可以看到测试结果:
串口的信息:
*** 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};