2023寒假一起练 - 基于 Sipeed M1s Dock 实现网络相机
一、项目描述
Sipeed M1s Dock 基于 BL808 芯片组,同时集成了显示屏和摄像头。BL808 是一款高度集成的 AIoT 芯片组,具有 Wi-Fi/BT/BLE/Zigbee 等无线互联单元,包含多个 CPU 以及音频编码译码器、视频编码译码器和 AI 硬件加速器,适用于各种高性能和低功耗应用领域。
BL808 系列芯片主要包含无线和多媒体两个子系统:
- 无线子系统包含一颗 RISC-V 32-bit 高性能 CPU - 内核 E907,集成 Wi-Fi/BT/Zigbee 无线子系统,可以实现多种无线连接和数据传输,提供多样化的连接与传输体验。
- 多媒体子系统包含一颗 RISC-V 64-bit 超高性能 CPU - 内核 C906,集成 DVP/CSI/ H264/NPU 等视频处理模块,可以广泛应用于视频监控/智能音箱等多种 AI 领域。
在本项目中,使用内核 E907 实现:
- WIFI 网络连接
- 摄像头捕获图像并发送到远端服务器
使用内核 C906 实现任务控制。
二、设计思路
BL808 内部集成了两颗 RSIC-V CPU和NPU,本项目不需要使用 NPU,两颗 CPU 之间通过 XRAM 进行命令交互。具体实现思路如下:
- 控制命令 - 使用内核 C906 发送控制命令给内核 E907,包括“WIFI 连接”、“开始相机数据 TCP 串流(支持时间周期)"、“停止相机数据TCP串流”、“开始相机数据 UDP 串流"和“停止相机数据UDP串流”
- WiFi 连接、串流任务 - 在 E907 在执行
- 在 PC 上使用 C# 实现一个可以接收 TCP/UDP 串流数据的服务程序
框图如下:
三、开发环境的搭建
BL808 的 CPU 是基于平头哥的 RSIC-V 内核设开发的,平头哥官方提供了 Linux 和 Windows 平台的工具链,选择 Linux 平台开发会更方便一些。我选择的是 Linux 开发环境 - WSL2 Ubuntu20.04。固件升级工具可以跨平台,因为我的 WSL2 Ubuntu20.04 没有配置图形界面,所以使用了 Windows 版本的升级工具。
开发环境配置方法如下:
- 创建BL808开发目录:
mkdir ~/bl808
- 下载 SDK 和 示例代码:
cd ~/bl808 git clone https://gitee.com/Sipeed/M1s_BL808_SDK.git git clone https://gitee.com/Sipeed/M1s_BL808_example.git cd M1s_BL808_example && ln -s ../M1s_BL808_SDK ./
- 下载平头哥工具链:
https://dl.sipeed.com/shareURL/others/toolchain
下载 Xuantie-900-gcc-elf-newlib-x86_64-V2.2.4-20211227.tar.gz
- 安装工具链:
# 创建工具链目录 cd ~/bl808 && mkdir M1s_BL808_SDK/toolchain # 拷贝工具链到开发目录并解压到 toolchain 目录 mv Xuantie-900-gcc-elf-newlib-x86_64-V2.2.4-20220715.tar.gz ~/bl808 tar -zxvf Xuantie-900-gcc-elf-newlib-x86_64-V2.2.4-20220715.tar.gz -C M1s_BL808_SDK/toolchain/ # 工具链目录重命名 cd M1s_BL808_SDK/toolchain && mv Xuantie-900-gcc-elf-newlib-x86_64-V2.2.4/ Linux_x86_64 && cd -
- 编译示例工程:
# 编译在 C906 上运行的程序 cd ~/bl808/M1s_BL808_example/c906_app/ # 配置 SDK 路径环境变量 export BL_SDK_PATH=$(pwd)/../M1s_BL808_SDK # 编译 hello_world 项目,编译成功后可以在 build_out 目录找到 hello_world.bin ./build.sh hello_world # 编译在 E907 上运行的程序 cd ~/bl808/M1s_BL808_example/e907_app/ # 编译 firmware,编译成功后可以在 build_out 目录找到 firmware.bin ./build.sh firmware
四、开发之前的填坑
Sipeed M1s Dock 出厂时固化的 E907 内核程序有 bug,会导致系统使用固定IP地址,解决方法有两个:
- 从 Sipeed 官方下载最新的 E907 固件
- 使用最新的 SDK 仓库版本,编译可供 E907 使用的 firmware.bin。
这里提供直接去官方下载固件更新的方法:
https://dl.sipeed.com/shareURL/MAIX/M1s/M1s_Dock/7_Firmware/factory
下载 firmware_20230227.bin
https://dl.sipeed.com/shareURL/MAIX/M1s/M1s_Dock/7_Firmware/partition
下载 partition_cfg_16M_m1sdock.toml
https://dev.bouffalolab.com/download
下载 Bouffalo Lab Dev Cube,使用 BLDevCube.exe
关于固件升级的详细描述,可以参考 Sipeed 的官方 Wiki:
https://wiki.sipeed.com/hardware/zh/maix/m1s/other/start.html#%E4%B8%B2%E5%8F%A3%E7%83%A7%E5%BD%95
- 板卡有两个USB接口:OTG和UART:
- 连接 UART 后,PC上会出现两个串口,小端口号对应的是 C906,大端口号对应的是 E907,串口波特率均为 2000000 bps;
- 连接 OTG 后,保持按住板卡两侧按钮,然后按 RST,PC上会出现虚拟出来的U盘,复制固件程序到 U盘后,会自动对 C906进 行升级。
五、代码和功能展示
- C906 固件
- 参考 M1s_BL808_example/c906_app/cli_demo/main.c 的命令行程序实现
- 代码如下:
// wifi 连接命令 static void wifi_connect_cmd(char *buf, int len, int argc, char **argv) { getopt_env_t getopt_env; if (3 > argc) { goto _ERROUT; } utils_getopt_init(&getopt_env, 0); if (getopt_env.optind >= argc || argc - getopt_env.optind < 1) { printf("Expected ssid and password\r\n"); goto _ERROUT; } m1s_xram_wifi_init(); m1s_xram_wifi_connect(argv[getopt_env.optind], argv[getopt_env.optind+1]); printf("connect wifi ssid:%s, psk:%s\r\n", argv[getopt_env.optind], argv[getopt_env.optind+1]); return; _ERROUT: printf("[USAGE]: %s <ssid> <password>\r\n", argv[0]); return; } // 其他命令代码见代码附件 // 命令列表 const static struct cli_command cmds_user[] STATIC_CLI_CMD_ATTRIBUTE = { { "wifi_connect", "wifi station connect", wifi_connect_cmd}, { "start_tcp_upload_stream", "start wifi upload stream (tcp)", wifi_upload_stream_tcp_start_cmd}, { "stop_tcp_upload_stream", "stop wifi upload stream (tcp)", wifi_upload_stream_tcp_stop_cmd}, { "start_udp_upload_stream", "start wifi upload stream (udp)", wifi_upload_stream_udp_start_cmd}, { "stop_udp_upload_stream", "stop wifi upload stream (udp)", wifi_upload_stream_udp_stop_cmd}, };
- E907 固件
- 基于 Sipeed 提供的 xram_wifi 程序进行修改,直接修改 M1s_BL808_SDK 中的相关代码,修改的文件如下:
topgear@ubuntu2004:~/bl808/M1s_BL808_example/M1s_BL808_SDK$ git status On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: components/sipeed/c906/m1s_c906_xram/include/m1s_c906_xram_wifi.h modified: components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c modified: components/sipeed/e907/m1s_e907_xram/src/m1s_e907_xram_wifi.c modified: components/sipeed/m1s_common_xram/m1s_common_xram_wifi.h no changes added to commit (use "git add" and/or "git commit -a")
- 修改 M1s_BL808_SDK/components/sipeed/m1s_common_xram/m1s_common_xram_wifi.h,更新交互命令相关的枚举变量类型
diff --git a/components/sipeed/m1s_common_xram/m1s_common_xram_wifi.h b/components/sipeed/m1s_common_xram/m1s_common_xram_wifi.h index 5a26257..a5d1b27 100644 --- a/components/sipeed/m1s_common_xram/m1s_common_xram_wifi.h +++ b/components/sipeed/m1s_common_xram/m1s_common_xram_wifi.h @@ -13,7 +13,10 @@ enum wifi_operation { XRAM_WIFI_DEINIT, XRAM_WIFI_CONNECT, XRAM_WIFI_DISCONNECT, - XRAM_WIFI_UPLOAD_STREAM, + XRAM_WIFI_UPLOAD_STREAM_TCP_START, + XRAM_WIFI_UPLOAD_STREAM_TCP_STOP, + XRAM_WIFI_UPLOAD_STREAM_UDP_START, + XRAM_WIFI_UPLOAD_STREAM_UDP_STOP, }; struct m1s_xram_wifi { @@ -26,6 +29,7 @@ struct m1s_xram_wifi { struct { uint32_t port; + uint32_t delay_ms; char ip[16]; } __attribute__((packed)) upload_stream; };
- 修改 M1s_BL808_SDK/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c,修改 TCP 串流命令,增加停止命令和 UDP 串流开始/停止命令
diff --git a/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c b/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c index aad9c6f..c781c48 100644 --- a/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c +++ b/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c @@ -69,13 +69,35 @@ int m1s_xram_wifi_disconnect(void) return m1s_xram_wifi_operation(&op, XRAM_WIFI_DISCONNECT); } -int m1s_xram_wifi_upload_stream(char *ip, uint32_t port) +int m1s_xram_wifi_upload_stream(char *ip, uint32_t port, uint32_t delay_ms) { m1s_xram_wifi_t op = {0}; strncpy(op.upload_stream.ip, ip, sizeof(op.upload_stream.ip)); op.upload_stream.port = port; - return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM); + op.upload_stream.delay_ms = delay_ms; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_TCP_START); } + +int m1s_xram_wifi_upload_stream_stop() +{ + m1s_xram_wifi_t op = {0}; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_TCP_STOP); +} + +int m1s_xram_wifi_upload_stream_udp(char *ip, uint32_t port) +{ + m1s_xram_wifi_t op = {0}; + strncpy(op.upload_stream.ip, ip, sizeof(op.upload_stream.ip)); + op.upload_stream.port = port; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_UDP_START); +} + +int m1s_xram_wifi_upload_stream_stop_udp() +{ + m1s_xram_wifi_t op = {0}; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_UDP_STOP); +} + diff --git a/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c b/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c index aad9c6f..c781c48 100644 --- a/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c +++ b/components/sipeed/c906/m1s_c906_xram/src/m1s_c906_xram_wifi.c @@ -69,13 +69,35 @@ int m1s_xram_wifi_disconnect(void) return m1s_xram_wifi_operation(&op, XRAM_WIFI_DISCONNECT); } -int m1s_xram_wifi_upload_stream(char *ip, uint32_t port) +int m1s_xram_wifi_upload_stream(char *ip, uint32_t port, uint32_t delay_ms) { m1s_xram_wifi_t op = {0}; strncpy(op.upload_stream.ip, ip, sizeof(op.upload_stream.ip)); op.upload_stream.port = port; - return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM); + op.upload_stream.delay_ms = delay_ms; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_TCP_START); } + +int m1s_xram_wifi_upload_stream_stop() +{ + m1s_xram_wifi_t op = {0}; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_TCP_STOP); +} + +int m1s_xram_wifi_upload_stream_udp(char *ip, uint32_t port) +{ + m1s_xram_wifi_t op = {0}; + strncpy(op.upload_stream.ip, ip, sizeof(op.upload_stream.ip)); + op.upload_stream.port = port; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_UDP_START); +} + +int m1s_xram_wifi_upload_stream_stop_udp() +{ + m1s_xram_wifi_t op = {0}; + return m1s_xram_wifi_operation(&op, XRAM_WIFI_UPLOAD_STREAM_UDP_STOP); +} +
- 修改 M1s_BL808_SDK/components/sipeed/e907/m1s_e907_xram/src/m1s_e907_xram_wifi.c,增加 UDP 串流命令等,具体实现见代码附件
- 基于 Sipeed 提供的 xram_wifi 程序进行修改,直接修改 M1s_BL808_SDK 中的相关代码,修改的文件如下:
- 发送图片的流程:
- C# 程序
- 实现如下功能:
- 获取本机 IP 地址
- 启动/停止 TCP 接收服务
- 启动/停止 UDP 接收服务
- 实现如下功能:
六、功能展示
七、未来计划
目前 TCP/UDP 长时间串流会出现卡顿,未来计划优化串流服务的程序,并且增加语音事实传输的功能,并且结合显示屏做视频的同步展示。
八、参考连接
- M1s 相关资料下载 https://dl.sipeed.com/shareURL/MAIX/M1s
- M1s Dock Wiki https://wiki.sipeed.com/hardware/zh/maix/m1s/m1s_dock.html