19

oxlm · 2022年11月05日 · 广东

【聆思CSK6 视觉AI开发套件试用】AI控制直流电机转速接口打通

背景

在访问极术社区时,偶然发现聆思科技的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
 title=

编译代码

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更换驱动,驱动更换方式如下:
 title=

效果验证

初步效果验证,发现误识别率偏高,遂按照文档建议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来做功能模块的开关,否则只能通过查看官方文档才能实现功能模块的添加。
从文档上看,该条建议已经实现,之前学时使用时未看到该部分

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