本篇文章来自极术社区与聆思科技组织的CSK6 视觉AI开发套件活动,更多开发板试用活动请关注极术社区网站。作者:oxlm
背景
在访问极术社区时,偶然发现聆思科技的CSK6开发板的评估活动,看CSK6的硬件配置和技术规格,300M的M33核 + 300M的HIFI4 + 128TOPS的NPU,完全有机会在我们公司内部的音视频产品上用上,因此申请了该方案的测评,以便更详细的了解该方案,也期待后续的音频模块,以便集中测试方案的音频部分以及音视频结合部分,确认是否可以满足我们的需求。
很荣幸,在第一批试用名单中被选中。
初步想法为: AI方式控风扇转速和舵机人物跟随,即风扇随人转动,且人靠近时降低转速,离远时提高转速,超出距离关闭风扇。由于目前暂时无自己训练接口,因此暂时先实现手势控风扇转速的功能。即OK手势开启风扇,停止手势关闭风扇,YES手势提高转速,like手势降低转速。
实现原理
风扇实现原理比较简单,本质上为一直流无刷(有刷)电机,通过调节电压大小调节转速,因此只需要使用一个供电切换开关加电容,使用PWM控制便可实现调速,因此在实现上,需要打通PWM接口。并将回调事件给到PWM接口上。
在不接外部电机的情况下,PWM效果可以使用板载LED灯观察,因此使用板载LED灯做功能初步实现。待外设就位后,再将LED灯接到外设看实际使用效果。
AI效果初探
参考:https://docs.listenai.com/chips/600X/ai_usage/hsgd/user_guide
拉取代码
lisa zep create --from-git https://cloud.listenai.com/zephyr/applications/app_algo_hsd_sample_for_csk6.git
打开webusb
在proj.conf中,将宏 CONFIG_WEBUSB改为y
编译代码
lisa zep build -b csk6011a_nano
此处出现过一次编译后的固件电脑报摄像头错误,此时通过运行以下命令重新编译后烧录修复:
lisa zep update
lisa zep build -b csk6001a_nano -p
烧录固件
lisa zep flash
烧录资源文件
板卡在我电脑上识别到的是串口6,因此命令中使用的是COM6
lisa zep exec cskburn -s \\.\COM6 -C 6 0x400000 .\resource\cp.bin -b 748800
lisa zep exec cskburn -s \\.\COM6 -C 6 0x500000 .\resource\res.bin -b 748800
电脑下载摄像头查看工具
git clone https://cloud.listenai.com/zephyr/applications/csk_view_finder_spd.git
安装驱动
由于摄像头查看软件使用的时libusb实现的hid接口,在windows下需使用zadig更换驱动,驱动更换方式如下:
效果验证
初步效果验证,发现误识别率偏高,遂按照文档建议0~3m参数调试
hsd_set_params(hsd, HSD_PARAM_HEAD_SHOULDER_DETECT_THRES, 0.35f);
hsd_set_params(hsd, HSD_PARAM_HEAD_SHOULDER_DETECT_PIXESIZE, 50.0f);
经过验证,此参数在1m左右识别准确度较高,可用于AI调试PWM
代码实现
1. 由于此时不需要再查看webusb,因此在proj.conf中将CONFIG\_WEBUSB置成n,之后编码实现
2. 在proj.conf中添加 CONFIG_PWM=y
3. 编写测试代码
/*
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <csk_malloc.h>
#include <device.h>
#include <drivers/video.h>
#include <licak/licak.h>
#include <zephyr.h>
#define LOG_LEVEL 4
#include <logging/log.h>
LOG_MODULE_REGISTER(main);
#include "bitmap.h"
#include <zephyr/drivers/pwm.h>
#define VIDEO_DEV DT_LABEL(DT_NODELABEL(dvp))
static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));
#define MAX_PERIOD PWM_MSEC(1U)
// 240p
// #define IMAGE_HEIGHT 240
// #define IMAGE_WIDTH 320
// 480p
#define IMAGE_HEIGHT 480
#define IMAGE_WIDTH 640
#define IMAGE_SIZE (IMAGE_WIDTH * IMAGE_HEIGHT * 3)
#define MOCK_DATA (0)
#if MOCK_DATA
#include "input_640x480.h"
#endif
#define MSGQ_NUMBER 10
typedef struct {
hsd_head_shoulder_detect result;
head_shoulder_detect hsd;
uint32_t data_len;
hsd_event event;
} msg_data_t;
const struct device *video;
bool is_usb_cfg;
struct k_msgq msg;
int64_t time;
uint32_t max_period = MAX_PERIOD;
int32_t percent = 1u;
void on_receive_hsd_result(hsd_t *hsd, hsd_event event, void *data, void *user_data)
{
if (event == HSD_EVENT_GESTURE_RECOGNIZE) {
head_shoulder_detect *result = (head_shoulder_detect *)data;
// LOG_INF("gesture result id: %d ,state: %d", result->id, result->gesture_state);
msg_data_t msg_data = {.event = event};
memcpy(&msg_data.hsd, result, sizeof(msg_data.hsd));
k_msgq_put(&msg, &msg_data, K_NO_WAIT);
}
}
void pwm_pulse_set(int32_t per)
{
int ret;
if (per <= 1) {
per = 1;
} else if (per >= 100) {
per = 99;
}
ret = pwm_set_dt(&pwm_led0, max_period, max_period / 100 * per);
if (ret) {
printk("Error %d: failed to set pulse width %d\n", ret, percent / 2U);
return;
}
}
void main(void)
{
if (0 != licak_init()) {
printk("LICAK init failed,exit.\n");
return;
}
video = device_get_binding(VIDEO_DEV);
if (video == NULL) {
LOG_ERR("Video device %s not found, "
"fallback to software generator.",
VIDEO_DEV);
return;
}
struct video_format fmt;
fmt.pixelformat = VIDEO_PIX_FMT_VYUY;
fmt.width = IMAGE_WIDTH;
fmt.height = IMAGE_HEIGHT;
fmt.pitch = fmt.width * 2;
if (video_set_format(video, VIDEO_EP_OUT, &fmt)) {
LOG_ERR("Unable to set video format");
return;
}
hsd_t *hsd = hsd_create(HSD_FLAG_HEAD_SHOULDER | HSD_FLAG_GESTURE_RECOGNIZE);
if (hsd == NULL) {
LOG_ERR("Create HSD instance failed.");
return;
}
// hsd_event_register(hsd, HSD_EVENT_HEAD_SHOULDER, on_receive_hsd_result, NULL);
hsd_event_register(hsd, HSD_EVENT_GESTURE_RECOGNIZE, on_receive_hsd_result, NULL);
printk("- Device name: %s\n", VIDEO_DEV);
static char buffer[sizeof(msg_data_t) * MSGQ_NUMBER];
msg_data_t msg_data;
k_msgq_init(&msg, buffer, sizeof(msg_data_t), MSGQ_NUMBER);
hsd_set_params(hsd, HSD_PARAM_HEAD_SHOULDER_DETECT_THRES, 0.35f);
hsd_set_params(hsd, HSD_PARAM_HEAD_SHOULDER_DETECT_PIXESIZE, 50.0f);
// float value;
// hsd_get_params(hsd, HSD_PARAM_HEAD_SHOULDER_DETECT_THRES, &value);
// LOG_INF("GET_PARAMS: %d %f", HSD_PARAM_HEAD_SHOULDER_DETECT_THRES, value);
// hsd_get_params(hsd, HSD_PARAM_HEAD_SHOULDER_DETECT_PIXESIZE, &value);
// LOG_INF("GET_PARAMS: %d %f", HSD_PARAM_HEAD_SHOULDER_DETECT_PIXESIZE, value);
hsd_start(hsd, video);
if (!device_is_ready(pwm_led0.dev)) {
printk("Error: PWM device %s is not ready\n", pwm_led0.dev->name);
return;
}
while (1) {
int ret = k_msgq_get(&msg, &msg_data, K_FOREVER);
if (ret != 0) {
LOG_WRN("Get video buffer timeout.");
continue;
}
switch (msg_data.event) {
case HSD_EVENT_GESTURE_RECOGNIZE:
switch (msg_data.hsd.gesture_state) {
case GESTURE_LIKE:
percent -= 10;
if (percent < 10) {
percent = 10;
}
pwm_pulse_set(percent);
LOG_INF("Speed down %d\n", percent);
break;
case GESTURE_OK:
pwm_pulse_set(percent);
LOG_INF("On %d\n", percent);
break;
case GESTURE_STOP:
pwm_pulse_set(0);
LOG_INF("Off");
break;
case GESTURE_YES:
percent += 10;
if (percent >= 100) {
percent = 99;
}
pwm_pulse_set(percent);
LOG_INF("Speed up %d\n", percent);
break;
case GESTURE_OTHER:
case GESTURE_SIX:
default:
break;
}
break;
default:
break;
}
}
hsd_stop(hsd);
hsd_destroy(hsd);
LOG_DBG("AP EXEC END\n");
}
实现效果
stop手势时,绿灯亮
OK手势时,绿灯灭
like手势时,绿灯变亮
yes手势时,绿灯变暗
进一步效果需等PWM后级做好后直接接上验证
遇到问题
- 装有企业版360杀毒软件的电脑无法安装
由于关闭权限在IT,无法关闭,暂时改用个人电脑编码 - pwm不能设置成100%占空比和0%占空比
待继续查看底层实现,确定原因
建议
通过proj.conf方式配置参数开关的方式,其实不是那么方便,比如我需要开PWM,我自己得往里面写一个 CONFIG_PWM=y才行,也就是说,我得知道有这么个宏才能打开PWM。
个人建议要么proj.conf将代码中可用于配置的宏全部添加,不用置为n。要么学RTT和linux驱动方式的配置,通过Kconfig来做功能模块的开关,否则只能通过查看官方文档才能实现功能模块的添加。
从文档上看,该条建议已经实现,之前学时使用时未看到该部分