本文目录
Maxi Go试用初体验
最近鱼很闲(当然不是闲鱼),想试一下AIoT和嵌入式CV和Micropython的开发,于是看中了Maix Go这款开发板。话不多say,先来看一看官网的介绍。
Seeed Studio Sipeed MAix Go套件是一款RISC-V 64开发平台,专为AI + IoT应用而设计,基于支持Wi-Fi®的MAix-1模块。Sipeed MAix Go套件包括紧凑的88mm x 60mm开发板、DVP(数字视频端口)摄像头、2.8"触摸显示屏、外壳和Wi-Fi天线。借助板载JTAG和UART控制器,开发人员无需单独的调试器即可调试MAix-1模块。电路板上还设有电源管理、USB-C、I2S MEMS麦克风、扬声器、RGB LED、麦克风阵列连接器、指轮开关和microSDTF卡插槽。
随附的Seeed Studio支持Wi-Fi的Sipeed MAix-1模块采用紧凑的1"方形 (25mm x 25mm) 封装,非常适合空间受限的物联网应用。
Features:
- CPU:带FPU的RISC-V双核64位处理器,主频400MHz(可超频至800MHz)
- QVGA@60FPS/VGA@30FPS图像识别
- 板载全向、底部端口、I2S数字输出MSM261S4030H0 MEMS麦克风
- 支持带有FPC10连接器和2个3W扬声器的Sipeed R6+1麦克风阵列板
- 板载MSA300数字三轴加速度计,用户可选范围:±2g、±4g、±8g、±16g
- 板载32.768k晶振与STM32F103连接串口下载器
- 带有电阻式触摸屏的MAIX-LCD板直接连接到引脚接头
- 内置ESP8285 WiFi芯片,支持2.4G 802.11.b/g/n
- 支持自弹式TF卡支架
- 三向拨盘开关和一个复位按钮
- 所有GPIO均连接到2×20 2.54mm接头
- 充电电流高达2.5A;集成动态路径管理
- 支持FreeRTOS和Micro Python
- 基于卷积神经网络的机器视觉
- 音频输出:
- PT8211:音频DAC芯片,16位动态范围;低谐波失真
- PAM8403:音频功放芯片,双通道
- 内置1W小喇叭
- 音频输入: MEMS麦克风,全方位、I2S输入输出
这是Maix Go的板载资源示意图:
这是组装好的图:
Seeed,Waveshare等代理商都有卖。我在tb买的,245R。。对于这种开发套件类产品来说,性价比还算可以啦
K210和Maix系列的关系
刚开始我半天没搞懂K210和Maix系列的关系…(一头雾水.jpg)
看了一堆文档才明白,我大致讲讲。。
K210是kendryte推出的一款RISC-V架构的双核Soc处理器(system-on-chip,片上系统)。
以下是它的架构图,截自 K210的Datasheet
可以看到,它内部集成了KPU和APU两个做 人工智能 常用的协处理器,对于做一些机器学习视觉识别的应用来说确实是个可以考虑的选择。FPIOA(Field programmable Input/Output Array)使得K210的IO口可以通过软件配置来任意分配,这是个很强大很方便的功能。
同时它也带有传统soc,mcu上常见的硬件通信协议接口,如UART,SPI,I2C,I2S,JTAG等。
而Maix系列,则是使用K210和ESP系列的WIFI模组设计的一系列开发板,可以将其视作一个Arduino类似的平台。
想要详细了解,请 点击此查看Sipeed的官方文档
此次介绍的Maix Go,是其中外设和功能比较完善且强大的一款,自带锂电池使得其可以离线工作(虽然是个容量才600mAh的弟弟…),Type-C接口也非常方便~
当然K210也有别的开发板,并不全是Sipeed家的Maix系列,Sipeed对其做了一个较为完善的封装,并且开发出了一整套较为完善的SDK和HDK等工具链,相较于K210的原生C语言开发,更加简单且快捷。我们来分析一下它是怎么做到的。
The Answer is!
MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments.
简单的说,就是在微处理器平台上运行的Python语言。
Micropython -> MaixPy
我们都知道,Python是一种解释性语言,而我们过去在给MCU编程时使用的C语言是一种编译型语言,它们之间最大的区别就是:
编译型语言在编译过程中生成目标平台的指令,解释型语言在运行过程中才生成目标平台的指令。
在知乎上看到一个神比喻:
编译相当于做好一桌子菜再开吃
解释就是吃火锅,边吃还要边煮,(所以效率低一些)
世上没有万全的解决方案~
如果追求效率可能就要牺牲可读性(上手难易度),如果要追求简洁易懂,就可能需要牺牲执行效率。
而python就是典型的追求简洁易懂的编程语言之一。在嵌入式领域,C语言需要手敲几千行代码的工程,如果使用Micropython调库,可能真的不用100行。
目前Micropython已经支持了如下几种硬件平台:
- ESP32
- PyBoard(好像是比较官方的支持,俺也不太清楚)
- ESP8266
- STM32系列MCU也有Micropython固件,典型的如Nucleo系列评估板
- 以Arduino为代表的AVR系列单片机
- OpenMV开发平台
- 还有多少俺也不知道…
现在,Sipeed将一整套Micropython解释环境移植到了K210平台上,就诞生了他们独特的开发环境——MaixPy。只要给K210烧录MaixPy的固件,就可以体验快速开发(嫖代码)的快感了!(官方文档请点我)
有了Maixpy的强大支持,K210这个soc的开发瞬间似乎变得简单了起来。。。比如我们来试试寻找I2C总线上的设备,只需要用如下代码即可实现:
# Search I2C Bus for device
# Send back their addresses via UART
from machine import I2C
i2c = I2C(I2C.I2C0, freq=100000, scl=28, sda=29)
devices = i2c.scan()
print(devices)
是不是感觉很爽?
另外,Sipeed还开发了Maixduino,也就是支持Arduino环境开发的接口。(官方文档请点我 )
另外,Maixduino还支持了Platform IO开发平台,不过国内不推荐使用这个东西,它的资源都在外网,不科学上网的话非常难用。
以下是使用Arduino IDE开发Maix系列开发板的一个实例,串口输出Hello world。
#include
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.println("Hello world");
delay(2000);
}
是不是有内味了。
当然,目前不太推荐使用这种方式来开发,因为我瞄了一眼,他们官网的库还没开发完,文档都没写完。。。(严重怀疑他们的工程师摸鱼23333)
MaixPy IDE
- Hello world demo.py
官方文档(也是下载链接) 中写到:
首先需要弄清:MaixPy使用Micropython脚本语法,所以不像 C语言 一样需要编译,其实不用IDE也能愉快使用:使用串口终端工具,后面会介绍
使用 IDE 则会方便在电脑上实时编辑脚本并上传到开发板以及直接在开发板上执行脚本,以及在电脑上实时查看摄像头图像、保存文件到开发板等
当然, 使用 IDE 因为压缩、传输需要耗费一部分资源,所以性能会有所降低,但对调试来说影响不大
据我的使用经历来看,这个MaixPy IDE就是套了个OpenMV IDE的壳。。。不过做的优化却不是那么好,bug挺多,正常使用是没有问题的。(鉴于我也没用过OpenMV IDE,虽然我下了,但我没有板子,没用过,我也不知道OpenMV IDE的稳定性怎么样,不过都是开源软件,稳定性你懂的嘿嘿嘿)
用起来还是挺方便的,崩溃不太多,可以接受。
当然不用MaixPy IDE 也是可以开发的,详见文档中的介绍,当然如果你没有自虐症我不建议你这么做。
你不是说体验么,体验呢?
就Maix Go这块开发板来说,开发体验还是不错的。
之前我玩了一些Opencv的demo,在我的电脑上跑,CPU直接起飞了。。在Maix Go上跑了一些openMV的demo,感觉还不错,以QVGA(320×240),16bit彩色显示(这个算法需要用灰度图,所以你看到的是黑白)的图像格式跑一些算法比如拉普拉斯变换-边缘检测,能达到实时显示20FPS+,我是比较满意的。
我还跑了个好玩的算法,用颜色鲜艳程度来表示图像中高光和阴影程度。
可以看到红色那一圈,旁边是个吊灯,所以显示为红色, 而我的衣服上光强很弱,所以显示为冷色调的青蓝色。
顺便贴个代码吧~
import sensor
import image
import lcd
import time
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
#sensor.set_windowing((240, 240))
sensor.run(1)
sensor.skip_frames(30)
clock = time.clock()
while True:
clock.tick()
img = sensor.snapshot()
fps =clock.fps()
img.draw_string(2,2, ("%2.1ffps" %(fps)), color=(0,128,0), scale=2)
lcd.display(img.to_rainbow())
'''
这些是我用来测试用的,不用看orz
'''
#img = img.to_rainbow()
#img.gaussian(1)
#img = img.histeq(adaptive = True)
#img = img.mean(3, threshold = True)
#img = img.laplacian(3, sharpen = True, threshold=True)
#img.to_grayscale()
#img.find_edges(100, 200)
体验就是,还不错的样子。
附录,官方文档,Github
在这里整理一下相关的官方文档,便于各位查找资料。
- MaixPy官方文档
- MaixDuino官方文档
- MicroPython官方文档
- Sipeed Wiki对各种产品的介绍
- 嵌入式图形库Littlevgl官方文档
- Kendryte-Standalone-SDK文档
- Kendryte-FreeRTOS-SDK文档
- OpenMV官方文档-Micropython
以下是相关Github Repo:
- Sipeed MaixPy ENV源码
- Maixduino for K210
- Kendryte-Standalone-SDK开发环境包
- Kendryte-Standalone-SDK示例
- Kendryte-FreeRTOS-SDK开发环境包
- Kendryte-FreeRTOS-SDK示例
- Kendryte-K210 IDE(新人勿用)
- Littlevgl-Micropython
- OpenMV开源机器视觉平台
另,新人不推荐使用C语言去开发(也就是你看到本文里提到的Kendryte相关的东西,都不用看),下面我来辣一下大家的眼睛。
这是一段用C语言编写的,运行在K210 soc上的FFT示例代码。如果用python,它可以缩短至少2倍的…(它还调了库)
/* Copyright 2018 Canaan Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include
#include
#include
#include
#include
#include
#include
#define FFT_N 512U
#define FFT_FORWARD_SHIFT 0x0U
#define FFT_BACKWARD_SHIFT 0x1ffU
#define PI 3.14159265358979323846
int16_t real[FFT_N];
int16_t imag[FFT_N];
float power[FFT_N];
float angel[FFT_N];
uint64_t fft_out_data[FFT_N / 2];
uint64_t buffer_input[FFT_N];
uint64_t buffer_output[FFT_N];
uint64_t cycle[FFT_DIR_MAX];
uint16_t get_bit1_num(uint32_t data)
{
uint16_t num;
for(num = 0; data; num++)
data &= data - 1;
return num;
}
int main(void)
{
int32_t i;
float tempf1[3];
fft_data_t *output_data;
fft_data_t *input_data;
uint16_t bit1_num = get_bit1_num(FFT_FORWARD_SHIFT);
complex_hard_t data[FFT_N] = {0};
for(i = 0; i < FFT_N; i++)
{
tempf1[0] = 0.3 * cosf(2 * PI * i / FFT_N + PI / 3) * 256;
tempf1[1] = 0.1 * cosf(16 * 2 * PI * i / FFT_N - PI / 9) * 256;
tempf1[2] = 0.5 * cosf((19 * 2 * PI * i / FFT_N) + PI / 6) * 256;
data[i].real = (int16_t)(tempf1[0] + tempf1[1] + tempf1[2] + 10);
data[i].imag = (int16_t)0;
}
for(int i = 0; i < FFT_N / 2; ++i)
{
input_data = (fft_data_t *)&buffer_input[i];
input_data->R1 = data[2 * i].real;
input_data->I1 = data[2 * i].imag;
input_data->R2 = data[2 * i + 1].real;
input_data->I2 = data[2 * i + 1].imag;
}
cycle[FFT_DIR_FORWARD] = read_cycle();
fft_complex_uint16_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, FFT_FORWARD_SHIFT, FFT_DIR_FORWARD, buffer_input, FFT_N, buffer_output);
cycle[FFT_DIR_FORWARD] = read_cycle() - cycle[FFT_DIR_FORWARD];
for(i = 0; i < FFT_N / 2; i++)
{
output_data = (fft_data_t *)&buffer_output[i];
data[2 * i].imag = output_data->I1;
data[2 * i].real = output_data->R1;
data[2 * i + 1].imag = output_data->I2;
data[2 * i + 1].real = output_data->R2;
}
for(i = 0; i < FFT_N; i++)
{
power[i] = sqrt(data[i].real * data[i].real + data[i].imag * data[i].imag) * 2;
}
printf("\n[fft real][fft imag]\n");
for(i = 0; i < FFT_N / 2; i++)
printf("%3d: %7d %7d\n",
i, data[i].real, data[i].imag);
printf("\nphase:\n");
for(i = 0; i < FFT_N / 2; i++)
{
angel[i] = atan2(data[i].imag, data[i].real);
printf("%3d : %f\n", i, angel[i] * 180 / PI);
}
for(int i = 0; i < FFT_N / 2; ++i)
{
input_data = (fft_data_t *)&buffer_input[i];
input_data->R1 = data[2 * i].real;
input_data->I1 = data[2 * i].imag;
input_data->R2 = data[2 * i + 1].real;
input_data->I2 = data[2 * i + 1].imag;
}
cycle[FFT_DIR_BACKWARD] = read_cycle();
fft_complex_uint16_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, FFT_BACKWARD_SHIFT, FFT_DIR_BACKWARD, buffer_input, FFT_N, buffer_output);
cycle[FFT_DIR_BACKWARD] = read_cycle() - cycle[FFT_DIR_BACKWARD];
for(i = 0; i < FFT_N / 2; i++)
{
output_data = (fft_data_t *)&buffer_output[i];
data[2 * i].imag = output_data->I1;
data[2 * i].real = output_data->R1;
data[2 * i + 1].imag = output_data->I2;
data[2 * i + 1].real = output_data->R2;
}
printf("[fft test] [%d bytes] forward time = %ld us, backward time = %ld us\n",
FFT_N,
cycle[FFT_DIR_FORWARD] / (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 1000000),
cycle[FFT_DIR_BACKWARD] / (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 1000000));
while(1)
;
return 0;
}
所以说,新人就别作死啦(锤
Hello!
Is there anybody in there?