以下作品由安信可社区用户小浪先生制作
产品开箱
首先谢谢安信可官方送的开发板,安信可AiPi-Eyes-R2包含清单如下:
AiPi-Eyes-R2开发板 | |
腔体喇叭-2pin间距1.25mm黄色超薄插头 | 2 |
1.25mm4pin转1.25mm端子反向连接线-200mm(连接摄像头) | 1 |
转接线-1.25mm转2.54mm6p | 1 |
摄像头模组-酷视-CV-031C50-1.25mm4pin接口-130W像素 | 1 |
显示器-4.0寸-RGB接口40寸-像素480x480 | 1 |
咪头-2pin-1.25mm间距-交叉绞线100mm | 2 |
具体开箱流程照片如下:
环境搭建
因为之前笔者已经用过Windows开发小安派-Eyes-S1了,这次使用Linux进行开发。
安装VMware
笔记本电脑中已经安装过VMware了,可以参考安信可官方给出的安装教程,具体链接如下:安信可官方教程链接
安装Ubuntu22.04
为什么用22.04版本,不用18.04版本呢??
因为ubuntu官方推出了24.04LST版本,之前的版本应该比较稳定了,这次尝试用新版本。也可选用安信可官方推荐的18.04版本,旧版本用得久了网上资源也比较多。
下载ios镜像文件
登录ubuntu官网链接,点击如下图所示位置“check out our alternative downloads”。
往下滑找到“All past releases ?”。
往下滑找到“22.04.05”。
往下滑找到“ubuntu-22.04.5-desktop-amd64.iso”,点击进行下载。
创建虚拟机
打开VMware,点击【创建新的虚拟机】,选择【典型】,点击下一步。
选择【稍后安装操作系统】,点击下一步。
选择客服及操作系统为Linux,版本为Ubuntu64位。
命名虚拟机,并选择合适的保存路径。
指定磁盘容量大小,最少20G,建议40G。
完成虚拟机创建。
安装虚拟机
设置CD/DVD类型,内存至少为4G,笔者的电脑是32G的,所以设置为8G。双击下图框选处,打开设置。
设置内存、选择下载的ISO镜像文件,点击确定。
点击【开启此虚拟机】。
选择第一项install ubuntu,回车。
选择中文简体,点击【安装Ubuntu】,这里看着窗口比较小,不要担心,后面会恢复的。
键盘布局默认即可,选择继续。
选择最小安装,并取消勾选“安装Ubuntu时更新”。
选择现在安装。
点击继续。
时区默认shanghai即可,点击继续。
设置计算机名及密码,然后点击继续,等待安装即可。
安装好后,直接用密码登录即可。
安装小安派开发环境
Ctrl+Alt+T快捷键打开终端,输入以下质量+回车,安装依赖:
sudo apt-get install make gcc vim cmake git ninja-build -y
mkdir创建新的文件夹,cd进入文件夹,克隆SDK
git clone -b master https://gitee.com/Ai-Thinker-Open/AiPi-Open-Kits.git
显示隐藏文件,并将文件中的github改为gitee,保存。
更新子模块,以防部分文件夹为空,依次执行以下命令
cd AiPi-Open-Kitsgit submodule init git submodule update
进入SDK文件夹,执行脚本文件
cd aithinker_Ai-M6X_SDK/. install.sh
. export.sh
至此,环境就按照完成了。
误用VSCode远程访问Ubuntu
VSCode我Windows本地已经安装好了,VSCode是一个免费的软件,这里就不再介绍如何安装,比较简单,下面介绍一下如何用本地VSCode远程访问Ubuntu。
安装VSCode插件Remote-SSH
在VSCode扩展中搜索,Remote-SSH,进行安装。
在ubuntu中终端输入以下命令,获取ubuntu的ip地址。
ifconfig
在VSCode左侧的“远程资源管理器”中,按下图操作,在框中输入ubuntu的ip地址+回车,选择选项的第一个C:Usersxxx.sshconfig。
按下图操作可以打开配置文件,Host可以任意配置,但是User必须是Ubuntu的用户名,如果不知道自己的用户名可以查看终端命令行前面的“@”前面的内容就是用户名,可以直接复制。
选择在当前窗口建立远程连接(->)或者点击右侧的按钮,在新窗口建立连接。
选择远程平台的系统,此处为Linux。
输入系统用户的密码+回车,即可控制系统了。
编译程序
首先在Wiindows下的VSCode打开文件夹,点击确定。
从../AiPi-Open-Kits/aithinker_Ai-M6X_SDK/examples/helloworld文件夹中复制helloword文件夹到AiPi-Open-Kits目录下,我这里将文件名修改成了Smart_dvc,修改SDK路径为图中所示。
此时还不能编译,需要将小安派的配置同步至SDK内部,进入aithinker_Ai-M6X_SDK文件夹,分步执行以下命令:
. update_sdk.sh.
export.sh
然后进入到Smart_dvc文件夹,进行编译,发现会报错:fatal error: lwip/dns.h: No such file or directory,解决方法是注释board.c中的#include "lwip/dns.h" 和 ip_addr_t dns_addr 变量定义这两行代码。
再次编译,显示编译成功!
此种方法并没有一劳永逸的解决环境变量问题,需要每次打开终端都需要执行一次.install.sh和. export.sh,具体操作参照我刚发的一篇文章:一劳永逸解决编译问题。
下载程序
下载程序就USB转TTL的TX接板子的RX、RX接板子的TX;原则上需要共地,我是通过电脑TypeC对小安派Eyes-R2供电,所以就不用共地了,在终端执行以下命令即可完成程序的下载。
make flash
到这里看串口就会发现,程序执行不了。那是因为AiPI-Eyes-R1/R2 出厂时做了加密处理,所以在运行的时候,需要指定特定的 boot2及验证固件,否则程序可能无法正常运行。将工程中的flash_prog_cfg.ini增加以下代码:
# 配置boot2固件,否则无法使用复位烧录功能(Configure boot2 firmware, otherwise the reset burn function cannot be used) [boot2] filedir = ./build/build_out/Rx_boot2*.bin address = 0x000000 [edata] filedir = ./build/build_out/edata.bin address = 0x3e0000 # 配置partition固件,这是必要的(Configuring partition firmware is necessary) [partition] filedir = ./build/build_out/partition*.bin address = 0xE000
将两个文件复制到 ./build/build_out/文件夹下,boot2在以下目录中:
AiPi-Open-Kitsaithinker_Ai-M6X_SDKbspboardbl616dkconfig
edata.bin在以下目录:
AiPi-Open-KitsAiPi-Eyes-Rxboardconfig
性能测试
下载AiPi-Eyes-Rx工程编译后的文件,可以看到屏幕帧率8FPS、CPU使用率40%。
DIY项目
利用GUI Guider设计的界面和心知天气API获取近三天的天气。在工程中使用了FreeRTOS。自己建的工程,自己一点一点搭起来的,程序框架还算可以(还有进步的空间,后续优化好了再开源。屏幕刷新率杠杠的)。
效果展示:
工程搭建
工程搭建我遇到的问题,估计大家都会遇到,在我往期的帖子里可以看到。我是利用helloword工程一点点搭建的,对理解工程框架很有帮助,有问题可以留言探讨。以下是我往期的帖子:
环境搭建
查找AiPI-Eyes-R1/R2 特定的 boot2及验证固件
工程框架
天气时钟工程大致可以分为三部分:
1.连接wifi
2.请求api,获取天气
3.GUI界面显示
连接WiFi
连接wifi和http请求部分是参考的这篇文章【教程贴】M61-32S系列连接Wifi 且发送HTTP请求。
连接wifi部分不需要改动,http请求部分,确实会像这位博主所说的,会有卡死的的几率,查阅资料发现,使用的recv函数是死等待,请求没有返回时,程序会跑飞。
笔者在recv前面加入了下面几行代码设置了超时时间,算是暂时解决了这个问题,跑程序一两个小时没有卡死,不过还需进一步验证测试。
// 设置recv超时时间 struct timeval tv_out; tv_out.tv_sec = 5; tv_out.tv_usec = 0;
获取天气
天气api是采用的新知天气api获取近3天的天气,前面http请求获取的数据是json数据,利用开源的cJSON库进行解析。cJSON库下载链接。
网上很多人说,在cJSON库解析过程中,很容易卡死,那是因为没有及时删除cJSON对象。每次解析完都要删除,不然解析次数多了就会卡死。
cJSON_Delete(root); /*每次调用cJSON_Parse函数后,都要释放内存*/ 解析代码如下: int weather_info_parse(char *weather_data_buf) { // 对接收到的数据作相应的处理 uint8_t i, j; uint8_t result_array_size = 0; uint8_t daily_array_size = 0; cJSON *root = NULL; cJSON *item = NULL; cJSON *results_root = NULL; cJSON *daily_root = NULL; root = cJSON_Parse(weather_data_buf); if (!root) { // ESP_LOGI(TAG, "Error before: [%s]n", cJSON_GetErrorPtr()); LOG_I("Error before: [%s]rn", cJSON_GetErrorPtr()); cJSON_Delete(root); /*每次调用cJSON_Parse函数后,都要释放内存*/ return -1; } // ESP_LOGI(TAG, "%srn", cJSON_Print(root)); /*将完整的数据以JSON格式打印出来*/ cJSON *Presult = cJSON_GetObjectItem(root, "results"); /*results 的键值对为数组,*/ result_array_size = cJSON_GetArraySize(Presult); /*求results键值对数组中有多少个元素*/ // ESP_LOGI(TAG, "Presult array size is %dn",result_array_size); for (i = 0; i < result_array_size; i++) { cJSON *item_results = cJSON_GetArrayItem(Presult, i); char *sresults = cJSON_PrintUnformatted(item_results); results_root = cJSON_Parse(sresults); if (!results_root) { // ESP_LOGI(TAG, "Error before: [%s]n", cJSON_GetErrorPtr()); LOG_I("Error before: [%s]rn", cJSON_GetErrorPtr()); cJSON_Delete(root); /*每次调用cJSON_Parse函数后,都要释放内存*/ return -1; } /*-------------------------------------------------------------------*/ cJSON *Plocation = cJSON_GetObjectItem(results_root, "location"); item = cJSON_GetObjectItem(Plocation, "id"); user_sen_config.id = cJSON_Print(item); // ESP_LOGI(TAG, "id:%sn", user_sen_config.id); /*逐个打印*/ item = cJSON_GetObjectItem(Plocation, "name"); user_sen_config.name = cJSON_Print(item); // ESP_LOGI(TAG, "name:%sn", cJSON_Print(item)); item = cJSON_GetObjectItem(Plocation, "country"); user_sen_config.country = cJSON_Print(item); // ESP_LOGI(TAG, "country:%sn", cJSON_Print(item)); item = cJSON_GetObjectItem(Plocation, "path"); user_sen_config.path = cJSON_Print(item); // ESP_LOGI(TAG, "path:%sn", cJSON_Print(item)); item = cJSON_GetObjectItem(Plocation, "timezone"); user_sen_config.timezone = cJSON_Print(item); // ESP_LOGI(TAG, "timezone:%sn", cJSON_Print(item)); item = cJSON_GetObjectItem(Plocation, "timezone_offset"); user_sen_config.timezone_offset = cJSON_Print(item); // ESP_LOGI(TAG, "timezone_offset:%sn", cJSON_Print(item)); /*-------------------------------------------------------------------*/ cJSON *Pdaily = cJSON_GetObjectItem(results_root, "daily"); daily_array_size = cJSON_GetArraySize(Pdaily); // ESP_LOGI(TAG, "Pdaily array size is %dn",daily_array_size); for (j = 0; j < daily_array_size; j++) { cJSON *item_daily = cJSON_GetArrayItem(Pdaily, j); char *sdaily = cJSON_PrintUnformatted(item_daily); daily_root = cJSON_Parse(sdaily); if (!daily_root) { LOG_I("Error before: [%s]rn", cJSON_GetErrorPtr()); return -1; } item = cJSON_GetObjectItem(daily_root, "date"); user_sen_config.day_config[j].date = item-?>valuestring; // cJSON_Print(item) item = cJSON_GetObjectItem(daily_root, "text_day"); user_sen_config.day_config[j].text_day = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "code_day"); user_sen_config.day_config[j].code_day = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "text_night"); user_sen_config.day_config[j].text_night = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "code_night"); user_sen_config.day_config[j].code_night = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "high"); user_sen_config.day_config[j].high = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "low"); user_sen_config.day_config[j].low = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "rainfall"); user_sen_config.day_config[j].rainfall = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "precip"); user_sen_config.day_config[j].precip = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "wind_direction"); user_sen_config.day_config[j].wind_direction = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "wind_direction_degree"); user_sen_config.day_config[j].wind_direction_degree = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "wind_speed"); user_sen_config.day_config[j].wind_speed = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "wind_scale"); user_sen_config.day_config[j].wind_scale = cJSON_Print(item); item = cJSON_GetObjectItem(daily_root, "humidity"); user_sen_config.day_config[j].humidity = cJSON_Print(item); cJSON_Delete(daily_root); /*每次调用cJSON_Parse函数后,都要释放内存*/ } /*-------------------------------------------------------------------*/ item = cJSON_GetObjectItem(results_root, "last_update"); user_sen_config.last_update = cJSON_Print(item); cJSON_Delete(results_root); /*每次调用cJSON_Parse函数后,都要释放内存*/ } cJSON_Delete(root); /*每次调用cJSON_Parse函数后,都要释放内存*/ return 0; }
GUI界面设计
在配置完工程所需的LVGL配置后可能会发现,自己配置的LVGL程序并不能执行,而且..AiPi-Open-Kitsaithinker_Ai-M6X_SDKexamples下的lvgl示例也不能正确执行,不要怀疑,配置可能是没问题的。
具体解决方法参考小安派R2工程移植LVGL后不运行【暂时解决】这篇帖子。
GUI设计是利用NXP的GUI Guider进行设计的,这里介绍一下怎么移植文件,当程序配置好LVGL组件并能正常执行demo后,将GUI工程中的custom和generated文件夹放入自己的工程中,在Cmake.List文件中配置好路径,可能generated文件夹中的images文件夹中的生成的图片可能会报错,只需要将lgvllvgl.h改为lvgl.h即可。
在mian.c中加入头文件
// lvgl #include "gui_guider.h" #include "events_init.h"
在mian函数中实例化对象
setup_ui(&guider_ui); events_init(&guider_ui);
就可以正常显示通过GUI Guider设计的界面了,我这里是用的FreeRTOS的任务调用的lvgl处理函数:
void lvgl_task(void *pvParameters) { while (1) { // LOG_I("lvgl_task is runing...rn"); lv_task_handler(); vTaskDelay(1); // bflb_mtimer_delay_ms(1); } }
下面是用GUI Guider设计,很简单的一个界面。
产品不足和建议
不足
不知道大家在使用过程中有没有发现模块屏幕的接口和USB供电接口放在了一侧,这使得插上屏幕后,Type-C很难插上,即使插上Type-C了,屏幕也会翘起来。可能会设想设计的初衷是想让开发板折在屏幕的背后,但是这样做,会看不见屏幕或者本身插好了下载口,屏幕正对人,会很难看到按键等。
建议
修改一下Type-C口和屏幕口布局,不方便做单片机开发板利用。
最后,欢迎大家来安信可论坛,笔者发布的原贴下一起交流讨论:
原贴地址
【小安派R2测评】安信可小安派R2开箱、环境搭建、性能测试、天气时钟、不足与建议
【小安派R2测评】安信可小安派R2 天气时钟
审核编辑 黄宇
-
Linux
+关注
关注
88文章
11535浏览量
214845 -
开源硬件
+关注
关注
8文章
220浏览量
30686 -
开发板
+关注
关注
25文章
5766浏览量
106338
发布评论请先 登录
零基础开发小安派-Eyes-S1 进阶篇 ——通过屏幕输入连接 Wi-Fi

零基础开发AiPi-Eyes-S1——通过屏幕输入连接Wi-Fi

用小安派 DSL做一个天气站

零基础开发小安派-Eyes-S1【进阶篇】——初识 LVGL 并搭建最小工程

零基础开发小安派-Eyes-S1——初识LVGL并搭建最小工程
零基础开发小安派-Eyes-S1 外设篇——DAC

零基础开发小安派-Eyes-S1外设篇——I2S

电子DIY作品 小安派R1做个86盒中控

零基础开发小安派-Eyes-S1【外设篇】——FLASH

零基础开发小安派-Eyes-S1 外设篇 ——I2C

零基础开发小安派-Eyes-S1【外设篇】——PWM

零基础开发小安派-Eyes-S1外设篇——GPIO中断编程

零基础开发小安派-Eyes-S1外设篇——GPIO 输入输出

评论