第二届 RISC-V中国峰会将于2022年8月24日至26日举办,持续三天,峰会将分成两个线上分会场同时进行。
本届峰会将会包含产业界最新技术及产品发布、学术界前沿成果展示等,共收录技术报告、成果展示、短演讲80余个。
全志科技产品研发中心总经理黄少锐也将以“RISC-V音视频计算芯片的应用与探索”为题,在8月24日周三的下午,于分会场A进行线上主题演讲,欢迎感兴趣的朋友前来交流探讨。
以下为峰会的完整议程时间表:
第二届 RISC-V中国峰会将于2022年8月24日至26日举办,持续三天,峰会将分成两个线上分会场同时进行。
本届峰会将会包含产业界最新技术及产品发布、学术界前沿成果展示等,共收录技术报告、成果展示、短演讲80余个。
全志科技产品研发中心总经理黄少锐也将以“RISC-V音视频计算芯片的应用与探索”为题,在8月24日周三的下午,于分会场A进行线上主题演讲,欢迎感兴趣的朋友前来交流探讨。
以下为峰会的完整议程时间表:
近期,极术社区联合矽速科技组织了搭载周易AIPU处理器的R329开发板申领活动,很多工程师进行了评测,但是如何能够更有效得利用周易AIPU来进行AI应用的部署开发呢?请关注本期由矽速科技CTO吴长泽带来的公开课。
分享内容
全志科技在2020年发布了搭载安谋科技“周易”AIPU的AI语音专用芯片R329,它主攻智能语音市场,其高算力、低功耗的特性引起了行业内的广泛关注。 本期课程将分享在矽速科技的MaixSense R329开发板上利用“周易”AIPU快速进行实时语音识别功能的全流程实现过程,让大家了解“周易”AIPU Compass SDK的强大,同时大家可以在开发板上DIY属于自己的智能语音助理。
以下是内容大纲
矽速MaixSense R329开发板软硬件介绍
“周易”AIPU Compass SDK简介及使用技巧
R329开发板上的视觉算法部署简介
端侧LVCSR(大词汇量连续语音识别)算法实现
视频及PPT
PPT部分预览(文末提供下载)
Q&A精选
如果大家有对周易 Z2 AIPU/R329的问题,欢迎在此提问 https://aijishu.com/t/aipu/questions
嘉宾简介
吴才泽 Caesar Wu
Sipeed 矽速科技 CTO
Caesar 毕业于浙江大学,拥有多年嵌入式软硬件设计经验,熟悉端侧AIoT算法优化部署。他开发的矽速MAIX开源AIoT产品系列深受国内外工程师及创客欢迎,目前他组织的MaixPy开源项目在github star已超过1K 。
本次视频分享为周易"AIPU"系列第五期,前四期请点击下方链接。
第一期|Arm中国周易“AIPU”初探
第二期|快速贯通全志智能语音技术,R329专用处理器大剖析
第三期|全志Tina-Linux软件平台及R329应用开发介绍(视频+PPT)
第四期|Arm中国周易Z2 AIPU概述(视频+PPT)
欢迎关注周易"AIPU"专栏获取周易"AIPU"更多技术干货等。
(文章转载自:极术社区 极术小姐姐)
(原文链接:https://aijishu.com/a/1060000000230794 )
问题背景
硬件:R系列芯片
软件:Tina
问题简述
说明:该FAQ旨在记录wpa_supplicant和wpa_cli的一些基础知识,方便在Tina系统上做网络联通性的排查。
问题分析
wpa_supplicant是一个连接、配置WiFi的工具,它主要包含wpa_supplicant与wpa_cli两个程序。 可以通过wpa_cli来进行WiFi的配置与连接,前提要保证wpa_supplicant正常启动。
相当于wpa_supplicant 是服务端,wpa_cli 是客户端。
1.wpa_supplicant
启动wpa_supplicant应用
wpa_supplicant -i wlan0 -Dnl80211 -c/etc/wifi/wpa_supplicant.conf -O /etc/wifi/sockets -B
wpa_supplicant -Dnl80211 -iwlan0 -c/etc/wifi/wpa_supplicant.conf -B
wpa_supplicant -B -iwlan0 -c/etc/wifi/wpa_supplicant.conf -Dwext
-D 驱动程序名称(可以是多个驱动程序:nl80211,wext)
-i 接口名称
-c 配置文件
-B 在后台运行守护进程
配置文件 /etc/wpa_supplicant.conf文件里,添加下面代码:
ctrl_interface=/var/run/wpa_supplicant //
update_config=1 // 强制更新覆盖配置
ctrl_interface指向的是一个目录,在这个目录中默认会生成一个文件/var/run/wpa_supplicant/wlan0,这是local socket address,相当于UNIX Domain Socket,程序和后台程序wpa_supplicant进行通信(其实是wpa_supplicant作为后台服务程序是通过本地socket和客户端进行通信的)
update_config = 1时会在(客户端发送SAVE_CONFIG命令)更新这个配置文件。
Tina上wpa_supplicant的启动是放在/etc/init.d/wpa_supplicant自启动脚本中的。
2.wpa_cli
wpa_cli 是客户端,用来进行WiFi的配置与连接。
启动wpa_cli应用
wpa_cli 有命令和交互的方式进行操作
wpa_cli -i wlan0 scan //搜索附件wifi热点
wpa_cli -i wlan0 scan_result //显示搜索wifi热点
wpa_cli -i wlan0 status //当前WPA/EAPOL/EAP通讯状态
wpa_cli -i wlan0 ping //pings wpa_supplicant
添加新的连接
wpa_cli -i wlan0 add_network //添加一个网络连接,会返回
wpa_cli set_network ssid ‘“name”’ //ssid名称
wpa_cli set_network psk ‘“psk”’ //密码
wpa_cli set_network scan_ssid 1
wpa_cli set_network priority 1 //优先级
保存连接
wpa_cli -i wlan0 save_config //信息保存到默认的配置文件中,前面提到的/etc/wpa_supplicant.conf
断开连接
wpa_cli -i wlan0 disable_network
连接已有连接
wpa_cli -i wlan0 list_network //列举保存过得连接
wpa_cli -i wlan0 select_network //连接指定的ssid
wpa_cli -i wlan0 enable_network //使能制定的ssid
网络连接成功的配置文件示例
ctrl_interface=/var/run/wpa_supplicant/
ap_scan=1
network={
scan_ssid=1
ssid="xxxx"
psk="xxxx"
bssid=
priority=2
}
例如:
wpa_cli -i wlan0 scan;
wpa_cli -i wlan0 scan_result;
wpa_cli -i wlan0 add_network;
wpa_cli -i wlan0 set_network 0 ssid '"AW-PDC-PD2-fly2.4g"';
wpa_cli -i wlan0 set_network 0 key_mgmt WPA-PSK;
wpa_cli -i wlan0 set_network 0 psk '"xxxxxxxx"';
wpa_cli -i wlan0 save_config;
wpa_cli -i wlan0 select_network 0;
wpa_cli -i wlan0 enable_network 0;
wpa_cli -i wlan0 list_network;
wpa_cli -i wlan0 status;
问题记录
原来在命令中sudo wpa_supplicant -B -D n180211,wext -i wlo1 -c /home/wpa_supplicant.cfg
错把nl80211写成n180211,注意是数字1与字母l的区别。
错误2
wpa supplicant: No network configuration found for current AP
说明配置文件写的不对,尤其从网上复制过来时常有看不见的tab字符等。
解决办法:从Archlinux文档上复制了一份格式正确的配置,再改一改就OK了
错误3
ctrl_iface exists and seems to be in use - cannot override it
Delete ‘/var/run/wpa_supplicant/wlo1’ manually if it is not used anymore
Failed to initialize control interface ‘/var/run/wpa_supplicant’.
You may have another wpa_supplicant process already running or the file was
left by an unclean termination of wpa_supplicant in which case you will need
to manually remove this file before starting wpa_supplicant again.
系统已经存在打开的多个wpa_supplicant实例,执行一下sudo killall wpa_supplicant杀死所有wpa_supplicant即可。
错误4
Successfully initialized wpa_supplicant
[ 2093.037373] ieee80211_do_open: vif_type=2, p2p=0, ch=3, addr=34:0f:51:ac:5f:97
[ 2093.045662] [STA] !!!xradio_vif_setup: id=0, type=2, p2p=0, addr=34:0f:51:ac:5f:97
[ 2093.055088] [AP_WRN] BSS_CHANGED_ASSOC but driver is unjoined.
nl80211: deinit ifname=wlan0 disabled_11b_rates=0
[ 2093.088882] [STA_WRN] !!! xradio_remove_interface: vif_id=0
wlan0: Failed to initialize driver interface
00后开源创客 @YuzukiTsuru 基于全志D1s芯片设计并制作了一款RISC-V开发板,目前该开发板已经获得了阿里等多家知名科技公司的批量采购订单,将会被用于嵌入式系统的研究和IoT产品的研发。
值得一提的是,本开发板部分小件如电容电阻等是嘉立创机贴的,但D1s主控、XR829等大件物料为心灵手巧的作者本人手贴。
大佬手贴D1s实录
该开发板基于全志D1s芯片(阿里平头哥C906 RISC-V核)设计,可用于方案评估、方案预研和个人DIY,可应用于游戏机、智能商显、智能中控等产品形态。作者将开源开发板的全部设计资料。
开发板将提供的资料有:
以上所有资料获取:https://bbs.aw-ol.com/topic/1257/
贴片图
实物图
规格介绍:
D1s开发板拓展应用-桌面小电脑
开发板作者 @YuzukiTsuru 是一位00后的大二学生,目前就国内某院校物联网专业。他高中时就搭建了自己的个人博客,上大学后开始玩嵌入式,恰逢全志在线成立并进行资料开源,就开始围绕全志芯片进行项目开发,目前已经基于全志的芯片做了十几个不同型号的开发板并开源到立创开源硬件平台等社区。
立创开源硬件平台个人主页
Github个人主页
全志在线个人主页:https://bbs.aw-ol.com/user/yuzukitsuru
本开发板由原作者委托哇酷科技代售并开具发票,所有收益将由哇酷转交给原作者@YuzukiTsuru,有兴趣的可以扫码到淘宝店购买。
下午两点全志科技线上分享地址:
B站:https://live.bilibili.com/10339607
电子发烧友:https://t.elecfans.com/live/2096.html
小时候,我们总是充满想象力和创造力。
在那个年龄,我们没有真正的手表,但我们总是喜欢在纸上画出自己的手表,仿佛它真的能告诉我们时间。
为了弥补童年的遗憾,作者找到了一个智能手表的开源项目——NWatch,并把他移植到了R128开发板上。
本项目基于ZakKemble的开源项目NWatch,与原作者的NWatch不一样的是,作者将其移植到DShanMCU-R128s2-DevKit开发板的同时相比于原作者添加了一些功能,比如优化屏幕刷新,使显示更流畅,以及添加蜂鸣器、旋转编码器、DHT11温湿度传感器。
本项目的基础用意是提供一个综合的示例进行学习参考,所以没有将所有硬件集成到一小块开发板上,而是采用面包板来实现手表功能效果,所需要用到的硬件有以下几个:
模块 | 接线 | 对应引脚 |
---|---|---|
IIC OLED | SCK | PB00 |
SDA | PB01 | |
EC11 | S1 | PA24 |
S2 | PA25 | |
KEY | PA29 | |
蜂鸣器 | BEEP DATA | PA26 |
红外接收 | IR DATA | PA10 |
DHT11 | DHT11 DATA | PA6 |
手表上的所有功能都伴有动画效果。
原作者对软件系统的代码花费了大量时间进行优化渲染,优化的内容就包括了将位图图像从闪存复制到RAM中的帧缓冲区,并通过 SPI 将帧缓冲区发送到 OLED,最终使得手表能够在几乎所有区域的显示中可以保持100+FPS。
一些主要的动画内容:
1.进入和退出睡眠模式时的 CRT 动画(类似于某些 Android 智能手机具有的 CRT 动画)。
2.主要时间数字切换时具有动画效果。
3.菜单有一个向左/向右滚动的动画,选择一个选项将会有当前菜单从屏幕上掉下来的动画效果。
打开以下文件进行修改:
R128-S2-SDK/board/r128s2/pro/configs/sys_config.fex
IIC引脚配置:
[twi1]
twi1_sck = port:PB00<3><1><default><default>
twi1_sda = port:PB01<3><1><default><default>
PWM配置:
[pwm6]
pwm_used = 1
pwm_positive = port:PA26<4><0><2><default>
接下来添加100ask_r128_demos,首先clone仓库或者下载仓库压缩包到本地,并将仓库目录放在sdk的这个目录下面:
R128-S2-SDK/lichee/rtos-components/thirdparty/100ask_r128_demos
打开文件进行编辑
R128-S2-SDK/lichee/rtos-components/thirdparty/Makefile
在文件的最后面或最前一行加入下面的内容:
obj-$(CONFIG_COMPONENTS_100ASK_R128_DEMOS) += 100ask_r128_demos/
打开文件进行编辑
R128-S2-SDK/lichee/rtos-components/thirdparty/Kconfig
在文件的最后或最前一行加入下面的内容:
source components/common/thirdparty/100ask_r128_demos/Kconfig
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
R128开发板购买链接:https://m.tb.cn/h.5T4uATe?tk=S079W0vCt6v
Gitee源码获取链接:
https://gitee.com/weidongshan/100ask_r128_demos/tree/master/nwatch
GitHub源码获取链接:
https://github.com/100askTeam/100ask_r128_demos/tree/master/nwatch
如果你不想自己编译或者不需要二次开发,那么可以从下方获取固件:
Gitee处Releases获取:
https://gitee.com/weidongshan/100ask_r128_demos/releases/tag/v0.0.1
GitHub处Releases获取:
https://github.com/100askTeam/100ask_r128_demos/releases/tag/v0.0.1
烧写固件到DShanMCU-R128s2-DevKit在新窗口打开后开机会自动启动NWatch任务,如果没有自动启动在串口终端输入命令然后按回车即可nwatch_100ask 3。
更多关于项目的详情可以前往百问网R128综合项目开发案例界面了解。
1、现象
目前D1使用vdecoder SDK,其中调用完初始化完解码器函数后,在未送入视频帧数据让该解码器工作之前,cpu会被占满;
2、问题原因
目前代码 vdecoder.c 中 ENABLE_SBM_FRAME_INTERFACE 默认值为1,使用帧接口,此时解码H264会使用软件搜头,CPU占用率会比较高。
3、解决方法
将 ENABLE_SBM_FRAME_INTERFACE 改为 0 即可,此时使用硬件搜头。
单核且CPU频率不高的情况,建议 ENABLE_SBM_FRAME_INTERFACE 置为 0 。
作者@zhang1gong
原文链接:https://occ.t-head.cn/community/post/detail?spm=a2cl5.14300636.0.0.7f1f180fHnTGVq&id=4039809713964126208
1 核心板LED点亮
LicheeRV教程提供了核心板上的LED点亮教程。LED点亮或闪烁往往是广泛应用于自动控制的嵌入式系统运行的第一个试验程序,如同一般在桌面系统上学习编程语言运行的第一个程序:“Hello, world!”。我是第一次在Linux系统下运行点灯程序,感觉和在裸机上用c或汇编编程完全不同:基本上不用关心硬件,完全是对文件操作,充分提现了Linux系统“万物皆文件”的理念。核心板上驱动LED的GPIO口(PC1)与底板上的其他应用冲突,因此必须把核心板从底板上拆下,才能做这个点灯试验。但是,核心板上的USB口并不具备模拟串口功能,对此教程似乎并没有明确说明。几位已经做了这个试验的测试者都提到了:要用ADB。虽然大概早晚都会在我的桌面系统中装ADB以搭建交叉编译环境,我还是想先看看有没有其他办法。将核心板从底板拆下后,发现在USB口旁边有4个预留的焊盘,PCB板背面在焊盘旁边标出了“T R G 5V”,不禁使人想到:这难道是个串口?查了一下原理图,果然如此!正好我手头有不止一个串口转USB的小板(某宝上几块钱一个还包邮),为什么不用这个串口呢?忽然想到开发套件中有个小口袋装了4脚插针,应该就是干这个用的。顺利地将4脚插针焊上,但马上发现:如果将核心板插回底板,由于在串口插针下是底板上的复位按键,需要对焊上的插针修剪,否则插针的焊接端就会顶到复位按键上。焊接、修剪过程其实十分简单,但我差点儿在阴沟里翻船。经历了核心板不能工作、终于又恢复的过程(此处略去具体翻船现场和恢复过程200字),总算有惊无险!
通过核心板上的串口,经小板转换成USB连到桌面系统的模拟终端,按照教程给出的命令行指令逐条执行,点灯过程很顺利。(教程给出的命令行指令有一处小错:“cd /sys/class/gpio/export/gpio65”)
2 引脚扩展,RGB三色LED闪烁
底板上预留的扩展引脚区给人以无限遐想,总觉得如不把它们引出来,似乎对不起设计者的初衷。将引脚引出的主要障碍是需要把显示屏与底板分离。分离本身其实并不困难,难的是下决心去做这种带一点儿破坏性的事情(恳请厂家在出厂时就把双排插座焊上吧)。用刀片将显示屏和底板之间的连接分离,小心地在底板上焊上插座,可以方便地用杜邦线连接扩展引脚了。
根据底板上扩展引脚的标号,对照原理图,在感觉没有被占用的引脚中选择B2、B3、B4来用,分别用来驱动三色LEB中的R、G、B。参照教程,先用
“cat /sys/kernel/debug/pinctrl/2000000.pinctrl/pinmux-pins”
命令查询管脚标号对应的数字编号:
然后参照教程写了两个脚本,并放到“/mnt/SDCARD”目录下,这样掉电文件也不会丢失:
\
# rgb_config.sh
echo 34 > /sys/class/gpio/export
echo 35 > /sys/class/gpio/export
echo 36 > /sys/class/gpio/export
cd /sys/class/gpio/gpio34
echo out>direction
cd /sys/class/gpio/gpio35
echo out>direction
cd /sys/class/gpio/gpio36
echo out>direction
\
# rgb_blink.sh
for a in $(seq 1 5)
do
cd /sys/class/gpio/gpio34
echo 1 > value
sleep 0.5
cd /sys/class/gpio/gpio35
echo 1 > value
sleep 0.5
cd /sys/class/gpio/gpio36
echo 1 > value
sleep 1
cd /sys/class/gpio/gpio34
echo 0 > value
sleep 0.5
cd /sys/class/gpio/gpio35
echo 0 > value
sleep 0.5
cd /sys/class/gpio/gpio36
echo 0 > value
sleep 1
done
脚本“rgb_config.sh”用来初始化管脚,“rgb_blink.sh”控制三色LED的闪烁。
Linux系统下万物皆文件,还要进一步好好体会。
执行ifconfig -a/ifconfig eth0/ifconfig eth0 up
命令,找不到eth0设备,有如下类似打印:
root@TinaLinux:/# ifconfig -a
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:6480 errors:0 dropped:0 overruns:0 frame:0
TX packets:6480 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:505440 (493.5 KiB) TX bytes:505440 (493.5 KiB)
root@TinaLinux:/#
root@TinaLinux:/# ifconfig eth0
ifconfig: eth0: error fetching interface information: Device not found
root@TinaLinux:/#
root@TinaLinux:/# ifconfig eth0 up
ifconfig: SIOCGIFFLAGS: No such device
以太网模块配置未生效或存在GPIO冲突
步骤1:抓取内核启动log,搜索"gmac"关键字段,检查gmac驱动是否probe成功;
步骤2:若内核启动log显示probe失败,常见原因是GPIO资源冲突导致,有如下类似打印:
sun50iw10p1-pinctrl pio: pin PH0 already requested by twi0; cannot claim for gmac0
sun50iw10p1-pinctrl pio: pin-224 (gmac0) status -22
sun50iw10p1-pinctrl pio: could not request pin 224 (PH0) from group PH0 on device pio
sunxi-gmac gmac0: Error applying setting, reverse things back
sunxi-gmac: probe of gmac0 failed with error -22
4.1 GPIO冲突
(1) 首先,结合内核启动log定位与哪个模块存在GPIO冲突,有如下类似打印:
sun50iw10p1-pinctrl pio: pin PH0 already requested by twi0; cannot claim for gmac0
(2) 然后,确认该模块GPIO配置是否有误或者是否可以关闭该模块。
4.2 以太网配置未生效
(1) 首先,确认内核menuconfig以太网模块配置是否打开,路径及截图如下:
menuconfig -> Device Drivers -> Network device support -> Ethernet driver support
(2) 然后,确认board.dts/sys_config.fex中GMAC模块是否打开,board.dts配置示例如下:
gmac0: eth@05020000{
phy-mode = "rgmii";
use_ephy25m = <1>;
tx-delay = <7>;
rx-delay = <0>;
status = "okay";
};
注:
status = “okay"代表打开该模块,status = “disabled"代表关闭该模块;
确保PHY与GMAC之间物理接口与软件配置相匹配,对于RGMII接口phy-mode配置为"rgmii”,RMII接口phy-mode配置为"rmii”;
use_ephy25m=1代表PHY使用SOC内部EPHY_25M时钟,use_ephy25m=0或者不配置该参数,代表PHY不使用SOC内部EPHY_25M时钟,
需外挂25M晶振为PHY提供时钟;
问题背景
硬件:R329
软件:Tina
内核:Linux-4.9
问题描述
使用Audiocodec进行录音,格式S24_LE,录制的.wav波形在某些软件中异常
arecord -D hw:audiocodec -f S24_LE -r 16000 -c 2 -d10 /tmp/test3_S24_LE.wav
需要放大很多倍才能看到声音波形
问题分析
1.R329的Audiocodec用于录音的ADC只支持16bit和20bit的采样精度。采样后的数字信号会存放到RX_FIFO中,RX_FIFO的大小为256*20-bit,其他平台可以在User Manual确认支持的采样精度,从而判断是否会有这个问题产生
2.RX_DATA是一个32位的寄存器,保存的是从RX_FIFO获取的一个channel的样本数据,当使用arecord进行录音时,RX_DATA中的值会经DMA搬至内存,最后保存到.wav中
其中RX_DATA有四种模式去获取RX_FIFO的数据,S24_LE和S32_LE均采用20-bit mode0
当设置了20bit采样精度时,对应的两种模式如下图所示:
3.先说明一下S24_LE和S32_LE这两种格式的区别
S24_LE指有符号整型,范围是-2^23 ~ ((2^23) - 1),有效数据在低24位
S32_LE指有符号整型,范围是-2^31 ~ ((2^31) - 1),有效数据在高24位
LSB MSB
1st byte 2nd byte 3rd byte 4th byte alignment
S32_LE: 00000000 xxxxxxxx xxxxxxxx xxxxxxxx 32 bits
S24_LE: xxxxxxxx xxxxxxxx xxxxxxxx 00000000 32 bits
S24_3LE: xxxxxxxx xxxxxxxx xxxxxxxx 24 bits
4.在驱动程序中,S24_LE和S32_LE虽然都支持,但他们两者都是使用20-bit的mode0,这导致这两种格式保存到文件中的数据排布是一致的,但生成的wav头信息中的采样位数则不一样,从下图可以看出两者的差异
S32_LE的wav文件信息:
若软件以S32_LE进行解析,以上红框的数据变为0x0f80f0,依然可以保留全部有效数据
S24_LE的wav文件信息:
若软件以S24_LE进行解析,以上红框的数据变为0x55f000,便会丢失一部分数据
解决方案
总结原因就是audiocodec的采样精度只支持16和20bit,因此PCM格式中S24_LE虽然也支持,但硬件的特性使驱动并不能做到很好的适配,若软件以标准S24_LE格式进行分析,则会丢失高位的有效数据,这取决于软件如何对数据进行分析,解决方法有以下三种
arecord -D hw:-f S32_LE -r 16000 -c 2 -d10 /tmp/test32.wav
Linux还真是逐步熟悉中,现在才了解到Linux即没有原生的GUI,也没有应用层协议栈,所以要实现HTTP应用,必须利用TCP然后自己封装HTTP数据包。本篇即记录封装HTTP数据包,到心知天气请求天气信息的案例实现过程。
1、心知天气API说明
心知天气应该是当下国内使用很普遍的一个天气数据站点。相关注册和使用过程,这里就不再啰嗦了,不清楚的朋友可以自己到官网上查看(https://www.seniverse.com/)。
本例仅测试实时天气数据获取,天气相关数据只有“状态(晴朗之类)”和“气温”,请求接口地址如下:
可以看到请求地址给的是域名,TCP连接需要直接给IP地址,所以用ping来获取其IP为“116.62.81.138”,端口自然是80。
得到IP地址后,先不着急编程,通过网络助手实验一把,具体过程是:选择TCP Client,连接对方IP和端口(116.62.81.138:80),然后将请求地址前加上方法字串“GET”,结尾还要有两个回车换行“\r\n\r\n”。初次测试时,忘记了回车换行符没有成功,加上后就好了。
封装好的数据包是:“GET https://api.thinkpage.cn/v3/weather/now.json?key=yourkey&location=tianjin&language=en&unit=c\r\n\r\n”。
2、JSON分析
请求到的数据是JSON格式,贴到Json.cn(https://www.json.cn/)的在线工具里,可以更清晰的看到其结构。
可以看到请求实时数据(now.json),得到一个JSON对象,包含一个“results”引导的JSON数组,且数组只有一个元素,元素中又包含“location”、“now”和“last_update”三个JSON对象,内部还有键值对。
既然是开发Linux API的C程序,当然利用cJSON库来帮助进行数据解析了。本人使用的库是从网上搜到的一个百度网盘分享。
链接:https://pan.baidu.com/s/1DQynsdlNyIvsVXmf4W5b8Q
提取码:ww4z
3、请求天气案例
具体思路就是建立TCP Client连接心知天气的Server,然后发送请求包,得到响应包,解析并打印出结果,案例比较简单做成单次的——开启即运行到底,代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "cJSON.h"
#define SERVER_IP "116.62.81.138"
#define SERVER_PORT 80
#define NOW "now.json"
#define DAILY "daily.json"
#define API_KEY "SK0LJ8FI2TP0L-IsQ"
#define CITY "tianjin"
#define REQ_PACK "GET https://api.thinkpage.cn/v3/weather/%s?key=%s&location=%s&language=en&unit=c\r\n\r\n"
#define N 1024
#define errlog(errmsg) do{ perror(errmsg);\
printf("----%s----%s----%d----\n", __FILE__, __func__, __LINE__);\
return -1;\
} while(0)
//struct for weather data
typedef struct {
char id[16];
char name[32];
char country[16];
char path[64];
char timezone[32];
char tz_offset[16];
char text[16];
char code[4];
char temp[8];
char last_update[32];
} weather_t;
//parse function & print weather_t data function
void aita_ParseJsonNow(char *json, weather_t *w);
void aita_PrintWeather(weather_t *w);
int main(int argc, const char *argv[]) {
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
char sendbuf[N] = "";
char recvbuf[N] = "";
weather_t weather = {0};
//create socket
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
errlog("socket error");
}
//connect to server of seniverse.com
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serveraddr.sin_port = htons(SERVER_PORT);
if((connect(sockfd, (struct sockaddr*)&serveraddr, addrlen)) < 0) {
errlog("connect error");
}
//build & send request package
sprintf(sendbuf, REQ_PACK, NOW, API_KEY, CITY);
if(send(sockfd, sendbuf, N, 0) < 0) {
errlog("send error");
}
//waiting server response
if(recv(sockfd, recvbuf, N, 0) < 0) {
errlog("recv error");
}
printf("recv: %s\n", recvbuf);
//parse & print data
aita_ParseJsonNow(recvbuf, &weather);
aita_PrintWeather(&weather);
close(sockfd);
return 0;
}
void aita_ParseJsonNow(char *msg, weather_t *w) {
cJSON *json, *ja, *jo, *josub, *item;
json = cJSON_Parse(msg); //parse string to cJSON type
if(json == NULL) {
printf("json type cast error: %s", cJSON_GetErrorPtr());
return;
} else {
printf("parse now pack\n");
if((ja=cJSON_GetObjectItem(json, "results")) != NULL) { //get results array
if((jo=cJSON_GetArrayItem(ja, 0)) != NULL) { //get array[0](the only item)
//get location object
if((josub=cJSON_GetObjectItem(jo, "location")) != NULL) {
if((item=cJSON_GetObjectItem(josub, "id")) != NULL) {
memcpy(w->id, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "name")) != NULL) {
memcpy(w->name, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "country")) != NULL) {
memcpy(w->country, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "path")) != NULL) {
memcpy(w->path, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "timezone")) != NULL) {
memcpy(w->timezone, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "timezone_offset")) != NULL) {
memcpy(w->tz_offset, item->valuestring, strlen(item->valuestring));
}
}
//get now object
if((josub=cJSON_GetObjectItem(jo, "now")) != NULL) {
if((item=cJSON_GetObjectItem(josub, "text")) != NULL) {
memcpy(w->text, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "code")) != NULL) {
memcpy(w->code, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "temperature")) != NULL) {
memcpy(w->temp, item->valuestring, strlen(item->valuestring));
}
}
//get last_update object
if((josub=cJSON_GetObjectItem(jo, "last_update")) != NULL) {
memcpy(w->last_update, josub->valuestring, strlen(josub->valuestring));
}
}
}
}
//delete original json pack free memory
cJSON_Delete(json);
return;
}
void aita_PrintWeather(weather_t *w) {
printf("id: %s\n", w->id);
printf("name: %s\n", w->name);
printf("country: %s\n", w->country);
printf("path: %s\n", w->path);
printf("timezone: %s\n", w->timezone);
printf("timezone_offset: %s\n", w->tz_offset);
printf("text: %s\n", w->text);
printf("code: %s\n", w->code);
printf("temperature: %s\n", w->temp);
printf("last_update: %s\n", w->last_update);
}
项目路径中建立了源文件main.c,编写上述代码,并导入cJSON.c和cJSON.h,编译命令为:“riscv64-unknown-linux-gnu-gcc main.c cJSON.c -o weather -lm”。因为cJSON会用到math库,而它需要“-lm”来动态链接。
1、lvgl的图片显示
lvgl框架中图片可以是一个文件也可以是一个变量(数组形式的图片码),当然文件还需要初始化lvgl对文件系统的接口,本例暂以变量形式提供。
应用要显示图片,则需要引入一个图片控件,然后设置它的数据源——使用“lv_img_set_src()”函数。示例如下:
lv_obj_t * icon = lv_img_create(lv_scr_act(), NULL);
/*From variable*/
lv_img_set_src(icon, &my_icon_dsc);
上述代码中“icon”是一个lvgl对象指针,通过“lv_img_create()”实例化,则对应图片控件。设置数据源时传入参数“my_icon_dsc”是lvgl中的图片描述符数据结构“lv_img_dsc_t”——本身是一个结构体类型,其定义源码如下:
//in “../lvgl/src/draw/lv_img_buf.h”
typedef struct {
uint32_t cf : 5; /*Color format: See `lv_img_color_format_t`*/
uint32_t always_zero : 3; /*It the upper bits of the first byte. Always zero to look like a
non-printable character*/
uint32_t reserved : 2; /*Reserved to be used later*/
uint32_t w : 11; /*Width of the image map*/
uint32_t h : 11; /*Height of the image map*/
} lv_img_header_t;
typedef struct {
lv_img_header_t header; /**< A header describing the basics of the image*/
uint32_t data_size; /**< Size of the image in bytes*/
const uint8_t * data; /**< Pointer to the data of the image*/
} lv_img_dsc_t;
示例代码中,图片描述符变量的定义过程如下代码:
uint8_t my_icon_data[] = {0x00, 0x01, 0x02, ...};
static lv_img_dsc_t my_icon_dsc = {
.header.always_zero = 0,
.header.w = 80,
.header.h = 60,
.data_size = 80 * 60 * LV_COLOR_DEPTH / 8,
.header.cf = LV_IMG_CF_TRUE_COLOR, /*Set the color format*/
.data = my_icon_data,
};
其中,枚举“LV_IMG_CF_TRUE_COLOR”是色彩格式定义,表示RGB格式。
宏“LV_COLOR_DEPTH”则定义色彩深度,它位于“lv_conf.h”,用户可以自定义。本例中设置为32,即4字节的ARGB8888格式。
2、时间获取
86板的Tina Linux可以通过C time库轻松地获得本地时间等数据。本例使用的API有:time()、localtime()、strftime()以及time_t、struct tm。
3、图片和时间显示案例
本例继续使用线程管理lvgl刷新,创建1s周期的lvgl定时器,在定时器回调中获取本地时间并格式化输出。另外,系统初始时显示一个“天津”的Logo,而且初始即做一次时间获取和输出(如果不做,初始刹那label会显示默认“text”字样)。
图片码通过软件“Img2Lcd”获取,软件配置方式如下图所示。图片生成的数组有72008个字节,被放置到头文件“aita_logo.h”。
/* Includes ------------------------------------------------------- */
#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "aita_logo.h"
/* Private macro -------------------------------------------------- */
#define AITA_DISP_BUF_SIZE (128 * 1024)
#define AITA_SCREEN_WIDTH 480
#define AITA_SCREEN_HEIGHT 480
#define AITA_TITLE_STRING "AITA Weather for LicheeRV with LVGL"
#define SEND_PERIOD 1000
#define errlog(errmsg) do{ perror(errmsg);\
printf("----%s----%s----%d----\n", __FILE__, __func__, __LINE__);\
return;\
} while(0)
/* Global variables ----------------------------------------------- */
lv_indev_t *aita_indev; //pointer of indev
lv_obj_t *sys_scr; //pointer of system screen instance
lv_obj_t *head_label; //pointer of title label instance
lv_obj_t *main_label; //pointer of main label instance
char main_label_text[32]; //main label text string for datetime
lv_obj_t *logo_img; //pointer of city logo image instance
lv_timer_t *sec_timer; //pointer of timer instance for tcp polling
pthread_t lvgl_tid; //lvgl thread id
pthread_t tcprecv_tid; //tcp receive thread id
pthread_mutex_t lvgl_mutex; //mutex for lvgl tick
//image descriptor for logo_img
//ARGB8888 image 180*100 which code array is 'tj_logo'
lv_img_dsc_t img_dsc_city = {
.header.always_zero = 0,
.header.w = 180,
.header.h = 100,
.data_size = 18000 * LV_COLOR_SIZE / 8,
.header.cf = LV_IMG_CF_TRUE_COLOR,
.data = tj_logo,
};
/* Private function prototypes ------------------------------------ */
void aita_InitLVGL(void);
void aita_CreateMainUI(void);
void *thread_lvgl(void *arg);
void sec_timer_cb(lv_timer_t *timer);
void aita_InitTimer(void);
void aita_GetTime(void);
/* Private functions ---------------------------------------------- */
int main(void) {
void *retval;
//by author. initialize lvgl including displaybuffer, device for disp & input
aita_InitLVGL();
//by author. initialize and register event device
//these code must be in main(), otherwise the touch will fail.
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER; //by author. choice touchpad
indev_drv.read_cb = evdev_read; //by author. input callback
aita_indev = lv_indev_drv_register(&indev_drv);
//by author. create the main view when the demo starts up
aita_CreateMainUI();
//by author. create a timer
aita_InitTimer();
//by author. create mutex for lvgl
if(pthread_mutex_init(&lvgl_mutex, NULL) != 0) {
errlog("initialize mutex error");
}
//by author. create lvgl thread
if(pthread_create(&lvgl_tid, NULL, thread_lvgl, (void *)0) != 0) {
errlog("create lvgl thread error");
}
//by author. wait for thread exit, this demo should never be here.
pthread_join(lvgl_tid, &retval);
printf("lvgl thread exit, return value: %s\n", (char *)retval);
pthread_mutex_destroy(&lvgl_mutex);
return 0;
}
/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
static uint64_t start_ms = 0;
if(start_ms == 0) {
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
}
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
uint64_t now_ms;
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
uint32_t time_ms = now_ms - start_ms;
return time_ms;
}
void aita_InitLVGL(void) {
/*LittlevGL init*/
lv_init();
/*Linux frame buffer device init*/
fbdev_init(); //by author. initialize framebuffer device for display
evdev_init(); //by author. initialize event device for touchpad
/*A small buffer for LittlevGL to draw the screen's content*/
static lv_color_t buf[AITA_DISP_BUF_SIZE];
/*Initialize a descriptor for the buffer*/
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf, NULL, AITA_DISP_BUF_SIZE);
/*Initialize and register a display driver*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = fbdev_flush;
disp_drv.hor_res = 480;
disp_drv.ver_res = 480;
lv_disp_drv_register(&disp_drv);
}
void aita_CreateMainUI(void) {
//by author. create system screen which is basic graphic level
sys_scr = lv_obj_create(lv_scr_act());
lv_obj_set_size(sys_scr, AITA_SCREEN_WIDTH, AITA_SCREEN_HEIGHT);
//by author. create the main title which is just a label
head_label = lv_label_create(sys_scr);
lv_label_set_text(head_label, AITA_TITLE_STRING);
lv_obj_align(head_label, LV_ALIGN_TOP_MID, 0, 10);
//by author. create the city logo image
logo_img = lv_img_create(sys_scr);
lv_img_set_src(logo_img, &img_dsc_city);
lv_obj_align(logo_img, LV_ALIGN_TOP_LEFT, 10, 40);
//by author. get local time and show string
aita_GetTime();
main_label = lv_label_create(sys_scr);
lv_label_set_text(main_label, main_label_text);
lv_obj_align(main_label, LV_ALIGN_TOP_LEFT, 200, 40);
lv_obj_set_style_text_font(main_label, &lv_font_montserrat_20, 0);
}
//by author. lvgl core thread function
void *thread_lvgl(void *arg) {
while(1) {
pthread_mutex_lock(&lvgl_mutex);
lv_task_handler();
pthread_mutex_unlock(&lvgl_mutex);
usleep(5000); /* sleep for 5 ms */
}
}
//by author. sec_timer callback which refresh date string
void sec_timer_cb(lv_timer_t *timer) {
aita_GetTime();
lv_label_set_text(main_label, main_label_text);
}
//by author. initialize timer for 1s timing
void aita_InitTimer(void) {
sec_timer = lv_timer_create(sec_timer_cb, 1000, NULL);
lv_timer_set_repeat_count(sec_timer, -1);
}
//by author. get local time string
void aita_GetTime(void) {
time_t tsec;
struct tm *tlocal;
tsec = time(NULL);
tlocal = localtime(&tsec);
memset(main_label_text, 0, 32);
strftime(main_label_text, 32, "%Y-%m-%d %a %H:%M:%S", tlocal);
}
本篇结合本人前两篇的HTTP请求天气数据(通过“心知天气”网站)和lvgl显示图片及时间,在案例主界面上增加了日历显示和实时天气显示,先直接上图。
1、lvgl日历控件
calendar是lvgl提供的“Extra widgets”组件之一,需要注意的是8.0版本后有几个API的传参发生了变化,本例使用8.3版本,设置日期是需要同时传递“年、月、日”三个参数。
本例使用的API有:lv_calendar_create()、lv_canlendar_set_today_date()、lv_calendar_set_showed_date()和lv_calendar_header_arrow_create()。
lv_calendar_create()函数用于实例化calendar控件,传参是控件的父容器指针,本例使用“lv_scr_act()”即系统屏幕。
lv_canlendar_set_today_date()函数用于设置当前日期,本人使用发现lvgl是附带万年历功能的,只要设置好当天的年月日,就可以自动生成正确的日历排布。函数传参分别是控件指针和年月日数据。
关于年月日参数有两点注意事项。一是v7版本中,传参通过lv_calendar_date_t结构体,其包含年月日三个成员。二是如果使用了C time库的struct tm,注意其中年份需要加上“1900”,而月份则需要加“1”。
lv_calendar_set_showed_date()函数用于设置日历当前显示页,也就是设置当前月份。本人实验的效果是当天日期框会自动高亮,如果想设置多个高亮日期,可以使用函数lv_calendar_set_highlighted_dates()。
lv_calendar_header_arrow_create()函数用于向日历控件顶部增加“左、右箭头”两个按钮用于日历翻页(一页是一月)。此外,还有函数lv_calendar_header_dropdown_create()则是设置两个下拉列表分别用于选择年份和月份。这两个函数都只用传递日历控件指针一个参数,且是8.1版本新增API。
2、日历和天气显示案例
本案例的思路是:1)在应用启动时,获取当前时间(上篇中已经实现),然后将时间保存在全局量“struct tm today”中,并利用变量“today”来初始化日历控件的日期数据。2)上篇实现的时间显示案例,通过lvgl定时器,每秒获取本地数据,此处在定时器回调中再增加一个每到正分钟发送“Linux条件变量”。3)同时,应用启动时建立两个线程——lvgl线程和请求天气线程,请求天气线程等待条件变量到来,开启一次天气数据请求过程。
本例代码结合文章上半部分已经给出的案例,这里只给出改变部分。
/* Includes ------------------------------------------------------- */
// 增加头文件,cJSON用于解析JSON格式的天气数据
#include "cJSON.h"
/* Private macro -------------------------------------------------- */
// 增加请求天气数据相关的宏定义
#define HTTP_IP "116.62.81.138"
#define HTTP_PORT 80
#define NOW "now.json"
#define API_KEY "SK0LJ8FI2TP0L-IsQ"
#define CITY "tianjin"
#define REQ_PACK "GET https://api.thinkpage.cn/v3/weather/%s?key=%s&location=%s&language=en&unit=c\r\n\r\n"
#define N 1024
// struct for weather data 建立结构体存储解析后的天气数据
typedef struct {
char id[16];
char name[32];
char country[16];
char path[64];
char timezone[32];
char tz_offset[16];
char text[16];
char code[4];
char temp[8];
char last_update[32];
} weather_t;
/* Global variables ----------------------------------------------- */
// 增加显示天气的标签控件定义
lv_obj_t *weather_label; //pointer of weather label instance
// 增加日历控件定义
lv_obj_t *calendar; //pointer of calendar instance
// 定义today变量存储当前日期,用于设置日历
struct tm today; //
// 请求天气的线程ID
pthread_t reqweather_tid; //request weather thread id
// 请求天气线程等待的条件变量(min_cond)
// Linux中需要互斥量包含条件变量的使用,所以定义cond_mutex
pthread_mutex_t cond_mutex; //mutex for 1-min cond
pthread_cond_t min_cond; //1-min cond
/* Private functions ---------------------------------------------- */
int main(void) {
// other code from previous demo
// main()函数中创建互斥量、条件变量、请求天气线程
//by author. create mutex for 1-min cond
if(pthread_mutex_init(&cond_mutex, NULL) != 0) {
errlog("initialize cond mutex error");
}
//by author. create condition for 1-min
if(pthread_cond_init(&min_cond, NULL) != 0) {
errlog("initialize 1 minute condition error");
}
//by author. create request weather thread
if(pthread_create(&reqweather_tid, NULL, thread_reqweather, (void *)0) != 0) {
errlog("create request weather thread error");
}
//by author. wait for thread exit, this demo should never be here.
pthread_join(lvgl_tid, &retval);
printf("lvgl thread exit, return value: %s\n", (char *)retval);
pthread_join(reqweather_tid, &retval);
printf("request weather thread exit, return value: %s\n", (char *)retval);
pthread_mutex_destroy(&lvgl_mutex);
pthread_mutex_destroy(&cond_mutex);
pthread_cond_destroy(&min_cond);
return 0;
}
void aita_CreateMainUI(void) {
// other code from previous demo
// aita_CreateMainUI()被main()函数调用,初始化主界面。
//by author. create the weather label
weather_label = lv_label_create(sys_scr);
lv_label_set_text(weather_label, " ");
lv_obj_align(weather_label, LV_ALIGN_TOP_LEFT, 200, 120);
//by author. create the calendar
calendar = lv_calendar_create(sys_scr);
lv_obj_set_size(calendar, 235, 235);
lv_obj_align(calendar, LV_ALIGN_BOTTOM_LEFT, 10, -50);
lv_calendar_set_today_date(calendar, today.tm_year+1900, today.tm_mon+1, today.tm_mday);
lv_calendar_set_showed_date(calendar, today.tm_year+1900, today.tm_mon+1);
lv_calendar_header_arrow_create(calendar);
}
// 增加正分钟发送条件变量
void sec_timer_cb(lv_timer_t *timer) {
aita_GetTime();
lv_label_set_text(main_label, main_label_text);
if(today.tm_sec == 0) {
//by author. send condition signal per whole minute
pthread_cond_signal(&min_cond);
}
}
// 增加对today的赋值
void aita_GetTime(void) {
time_t tsec;
struct tm *tlocal;
tsec = time(NULL);
tlocal = localtime(&tsec);
today = *tlocal;
memset(main_label_text, 0, 32);
strftime(main_label_text, 32, "%Y-%m-%d %a %H:%M:%S", tlocal);
}
// 请求天气线程业务逻辑
void *thread_reqweather(void *arg) {
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
char sendbuf[N] = "";
char recvbuf[N] = "";
weather_t weather = {0};
char w_string[64] = "";
while(1) {
pthread_mutex_lock(&cond_mutex);
pthread_cond_wait(&min_cond, &cond_mutex);
pthread_mutex_unlock(&cond_mutex);
//create socket
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
errlog("socket error");
}
//connect to server of seniverse.com
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(HTTP_IP);
serveraddr.sin_port = htons(HTTP_PORT);
if((connect(sockfd, (struct sockaddr*)&serveraddr, addrlen)) < 0) {
errlog("connect error");
}
//build & send request package
memset(sendbuf, 0, N);
sprintf(sendbuf, REQ_PACK, NOW, API_KEY, CITY);
if(send(sockfd, sendbuf, N, 0) < 0) {
errlog("send error");
}
//waiting server response
if(recv(sockfd, recvbuf, N, 0) < 0) {
errlog("recv error");
}
printf("recv: %s\n", recvbuf);
//parse & print data,下面两个函数来自于“十三”案例
aita_ParseJsonNow(recvbuf, &weather);
aita_PrintWeather(&weather);
close(sockfd);
memset(recvbuf, 0, N);
//show weather string
memset(w_string, 0, 64);
sprintf(w_string, "weather:%s temperatur:%s", weather.text, weather.temp);
pthread_mutex_lock(&lvgl_mutex);
lv_label_set_text(weather_label, w_string);
pthread_mutex_unlock(&lvgl_mutex);
}
}
另外,本例在lvgl工程中增加了cJSON.c和cJSON.h文件,Makefile也做出了调整,具体如下所示。
原文链接:https://occ.t-head.cn/community/post/detail?spm=a2cl5.25411629.0.0.6597180fVP7giT&id=4039525135236603904
作者 @ firr
https://oshwhub.com/activities/spark2023/fp#P1
https://oshwhub.com/activities/spark2023/fp/detail?demand_uuid=6bdf5f66bf064607b3fb371299c9bd22
嘉立创旗下的开源硬件平台,特推【星火计划】活动,给想干一个大项目的电子人提供弹药,也提供展示的平台。
星火计划,是立创开源硬件平台面向全球电子爱好者发出的硬件开源助力活动。专门陪跑那种比较费钱的开源设计!
星火计划项目在2022年正式开启,每年举办一次,每次举办一年。
目前,星火计划分为两个赛道,分别是自由赛道和外包赛道。
自由赛道项目不限主题,不限参与人群,只要你有想法都可以来报名,该赛道下单个项目最高可提供一万元的耗材经费支持!
外包赛道为星火计划二期的全新赛道,有不同类型的外包项目需求,每个外包项目均提供定额项目奖金,外包项目仅对接一人,成功结项百分百获奖!
纠结症患者在参赛时一般会想:我要做什么项目?我能做什么项目?我的项目要实现什么功能才能脱颖而出?做好了能不能评上奖?.......
这也是嘉立创通过对2022期参赛者的调研发现的普遍问题,大家把前因后果全都考虑了一遍才发现自己还是连一点项目头绪都没有,但又不想错过这个千载难逢的好机会!毕竟,这种史诗级羊毛不薅?这说不过去!
基于此类“通病”,星火计划外包赛道就为专门治愈这类“纠结症患者”而生!不同应用领域的需求让你应接不暇,总有一个适合你!
一套流程行云流水,每个外包项目均提供定额的项目奖金,且只对接一位参赛者,成功结项将百分百获奖!
贡献创意
如果 这里面没有你想要参加的项目,你也可以“贡献创意”,把你觉得还不错的项目创意提给我们,为星火计划推波助澜,成就更多开源好项目。
【贡献创意】的需求一经采纳,贡献者奖100元无门槛元器件券,无上限!
活动奖励
活动流程
Vlog记录奖励
在外包赛道中有全志H616电脑的悬赏项目,当然,有更好想法的开发者也可以选择自由赛道,无限发挥自己的脑洞,你的每一个创意都可以在星火计划中获得全力的支持。
H616电脑设计需求
外设接口
1、HDMI接口1(4k显示);
2、USB2.03
3、USB2.0_OTG1;
4、3.5mm音频接口1;
5、USB-Type-C供电*1;
6、TF卡槽;
内置:
1、板载WiFi、蓝牙5.0;
2、运行存储:2GB;
3、内存:32GB;
4、电池充放电电路;
5、10.1寸触摸屏;
设计说明
1、使用嘉立创EDA设计;
2、整体项目需设计合适的3D外壳,材料不限(公版模型也可以);
3、外壳模型必须有着对应的接口说明;
4、支持加载Android镜像载入;
验收说明
1、电路部分需完全使用嘉立创EDA专业版进行设计,设计前需与工作人员确认方案的可行性后再进行;
2、原理图电路正确,PCB布局走线要求整理美观,无明显缺陷,设计合理,整体外形协调美观;
3、要求录制完整的演示视频,提供项目硬件、软件源文件、器件清单,填写完善开源项目描述。
4、中标后即可进行设计,设计过程中所需耗材与器件需自行准备,项目完成后发放奖金,规定时间内未完成将重新启动项目招标,原项目作废,且不报销已购买物料。
5、嘉立创EDA拥有完整的项目解释权,但会保留作者的著作权。
BLIKVM 是一款开源 KVM over IP 软件,可帮助您远程管理服务器或工作站,无论目标系统的健康状况如何,目前可与 Raspberry Pi CM4 硬件、Raspberry Pi HAT 或 PCIe 板配合使用。
还有一种更紧凑、可能更便宜的型号正在开发中,该型号会适配Mango Pi 的最小模组mCore-H616。
1.问题背景
问题平台:XR806 + RTOS
2.问题描述
XR806(M33内核)适配新的RTOS时,沿用M4F的启动代码后出现了系统奔溃,但是expection显示的PC地址和LR地址都被修改,无法准确判断哪条语句导致的错误。
3.问题分析
//.c文件中插入
void AsmPrint(void)
{
printf("var = %#x\n",PrintMagic);
}
#汇编代码中插入以下代码查看R0的值
LDR R8,=PrintMagic
str R0,[R8]
LDR R8,=AsmPrint
bx R8
4.解决方法
使用__set_PSPLIM可以设置PSPlimit地址,在不确定PSP限制时,可以__set_PSPLIM(0)取消这个功能,MSP也是相同道理。
问题背景
Tina环境下当前支持两个版本的optee,一个是2.5.0,一个是3.7.0。
由于Tina下平台众多,客户不是很清楚哪些平台支持哪个版本的optee。
问题分析
optee版本主要涉及三个部分optee-client,optee-os-dev-kit,optee.bin。由于2.5与3.7版本跨度太大,所以这三者必须版本匹配,系统才能正常工作,Tina release版本SDK是通过make menuconfig中的OPTEE_VERSION_2_5与OPTEE_VERSION_3_7来统一配置版本信息。
可以参考《TinaLinux_安全使用指南》第1.2章节的适用范围来,确认平台的optee版本,旧芯片支持2.5版本,新芯片使用3.7版本。这里汇总如下:
optee-2.5.0: R18、MR133、R311、R328…
optee-3.7.0: R329、MR813、R818、R528…
理论上来说,所有平台都可以支持optee的3.7.0版本。有些客户希望对旧芯片,如R328,升级到3.7.0,此时,系统中就会存在两个版本optee,如果没有统一管理,就容易造成混乱。
问题解决
optee-client
查看tina/package/security/optee-client*/Makefile中的PKG_VERSION来确认版本信息。
PKG_VERSION:=2.5.0
optee-os-dev-kit
查看tina/package/security/optee-os-dev-kit/dev_kit/arm-plat-xxx/export-
ta_arm32/host_include/conf.mk中的CFG_OPTEE_REVISION_MAJOR与CFG_OPTEE_REVISION_MINOR
CFG_OPTEE_REVISION_MAJOR=2
CFG_OPTEE_REVISION_MINOR=5
optee.bin
optee.bin头部包含版本信息,可以通过hexdump进行查看。
$ hexdump -C optee_xxx.bin -n 64
00000000 fd 03 00 ea 6f 70 74 65 65 00 00 00 00 00 00 00 |....optee.......|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 33 2e 37 00 |............3.7.|
Ubuntu发行商Canonical近日官方发布了适配全志D1-H哪吒开发板的Ubuntu镜像,旨在为开发者提供稳定的底层框架,让开发者可以专注于核心应用程序的开发。
官方原文:https://canonical.com/blog/canonical-enables-ubuntu-on-allwinners-nezha-risc-v-boards
开发者可以直接到Ubuntu官方网站获取镜像文件及安装指南。
下载链接:https://ubuntu.com/download/risc-v
此次发布的镜像是Ubuntu Severt 22.04.1版本,该版本的移植适配工作由 Canonical、阿里平头哥以及全志共同完成。
D1-H哪吒运行Ubuntu Server
Ubuntu是最受欢迎的开源操作系统之一,其稳定且可靠的特性吸引大部分开发者选用其作为开发时的首选OS。RISC-V架构在近些年的时间里飞速发展,从低端微控制器到高端服务器级处理器皆可发现用RISC-V架构进行部署的应用。开源软件搭配上开源硬件,让免费且可扩展软硬件的自由度迈上新的台阶。
D1-H哪吒开发板距发布至今已有超过一年的时间,全志以“开放BSP,融入OS”为核心,建立起了丰富的Linux开发生态,目前开发板已经成功适配了全志自研Tina Linux、Ubuntu、RT-Thread Smart、Debian、Fedora......等数款系统,全志也将携手各厂商、社区、工程师、开源创客等业内伙伴,在开源软硬件适配方向持续深耕,完善开发生态,为开发者提供多元化的选择。
本项目是基于全志F1C200S设计的开源屏幕开发板,设计的目标是提供一个低成本、超迷你且适合Linux开发的平台,特别是针对屏幕接口的支持。
开发板板载16M nor flash,主控芯片采用F1C200S,内置64M DRAM。同时附带USB Host接口以及USB type-c口,以及CH340串口转USB芯片,用于开发调试使用。
由于芯片功能繁多,开发板设计也相对复杂,为了教会大家自己DIY开发板,作者将开发板设计的硬件部分按功能拆分为了10个不同的知识点,来对开发板整体设计进行全面介绍和详细讲解电路原理。
全志F1C200s是一款高度集成、低功耗的移动应用处理器,支持高清视频解码,包括H.264、H.263、MPEG 1/2/4等格式,同时还具备音频编解码器和I2S/PCM接口,适用于多媒体音视频设备。
芯片基于ARM 9架构,并SiP了DDR,这样的配置使其外围电路在设计时会非常简单,非常适合作为入门级的Linux开发板。该部分原理图如下图所示:
SDMMC接口用于接入Micro SD卡,系统启动时,可以从SD卡中加载U-Boot,内核,RootFS,实现Linux启动。
如上图所示,相关线路说明如下所示:
此电路用于用户连接系统调试中断使用,其功能为将TTL串口转换为USB接口,使得用户可以在电脑中连接该串口进行调试。
需要注意的是,由于F1C200S的UART0接口(PE0/PE1引脚)被触摸的I2C接口占用,所以本开发板将CH340的串口连接到了F1C200S的UART1(PA2/PA3引脚)上,后续编译U-Boot和内核时我们需要相应的修改代码。
如上图所示,该部分除了串口转USB外,还兼顾了系统的供电。
该部分主要为主控芯片提供供电,采用SY8089A1AAC,单路最大输出电流2A。
在该模块中,我们使用了2520电感,与普通的电感相比,体积更小,但是2520电感在DCR(即直流电阻)参数上,会比普通的电感大一点,电感值的计算公式可以参考下方:
如下图所示,本开发板电感值直接参考SY8089数据手册文档,折中后取1.5Uh:
芯片的反馈电阻控制着芯片的输出电压,可以参考下方公式计算:
该部分用于AVCC 3V供电,使用XC6206 3V LDO,位号为U10,由于较为简单,此处不在详细说明。
Nor Flash为F1C200S芯片提供了第二种启动方式。
此处SPI Nor Flash可以同时兼容Nand Flash,不过目前裸机资料基本上都是以SPI Nor Flash为基础,所以此处焊接了W25Q128JVEIQ 128Mbit(16Mbyte)SPI Nor Flash。
此处引出了未使用的IO,用户可连接其他设备,C35为滤波电容,用于保证电源质量,该部分引脚功能可以参考下图(来源:芯片数据手册14/15页):
该部分连接到了芯片的DP/DM引脚,为芯片的USB接口。
USB Type-C用于USB Fel模式烧录系统,无供电输入/输出能力。
USB OTG处可用于连接其他USB设备,带5V输出,当然也可以接双头USB Type-A线缆用于USB Fel模式。
需要注意的是,开发板中没有连接ID线(ID线用于识别USB模式),所以在编写设备树时,我们需要强制指定USB模式为主机或从机。
该部分用于驱动RGB屏幕背光,标准40Pin RGB屏幕基本采用串联背光,由于本身开发板供电只有5V,所以我们需要使用背光驱动芯片升压到合适的电压,来驱动屏幕背光。
同时,背光驱动芯片采用恒流控制,可以避免电流过大导致背光LED烧毁,该部分原理图如下所示:
R5为芯片的反馈电阻,用于调节输出的电流,计算公式可参考下方:
此处我们选择20ma,所以R1=0.25/0.020(Ω) = 12.5Ω,就近取12Ω。
如上图,下方说明了LED为2并5串,额定电流为40ma,我们为了保险,选择了20ma,亮度会有所损失。
此处参考屏幕数据手册即可,由于F1C200S只支持RGB565,RGB666,此处使用RGB666,屏蔽了RGB三色的低2位,这样最终色彩影响比较小,同时,F1C200S内置色彩抖动,可以更加接近RGB888效果。
其中需要注意的是,CTP_SDA/CTP_SCL最好加上拉电阻,此处选用了内部上拉,所以并没有加电阻,该部分原理图如下所示:
使用VSCode的DeviceTree插件,我们可以实现设备树文件的代码高亮,编辑c语言代码。
安装VSCode后,我们开始安装设备树插件,再商店中搜索DeviceTree插件,点击安装安装即可:
同理,推荐读者同时安装中文汉化,搜索CN,参考下图安装即可,安装后按照要求重启VSCode即可使用。
打开安装好的Ubuntu 18.04虚拟机,将需要分区的SD卡插入电脑USB口,并右键点击VMware右下角的USB存储器图标,点击连接,将SD卡连入虚拟机。具体操作过程如下图所示:
点击桌面左下角图标,进入所有应用,然后搜索GPartd,可参考下图:
此时需要输入密码,输入用户密码,提权到root用户,如下图所示:
接着在右上角选择我们需要格式化的SD卡,默认为/dev/sda,这个是我们虚拟机的系统盘,我们需要切换到SD卡,此处一定要小心,sdb不一定是我们的sd卡。
完成切换后,右键点击如图所示位置,点击“卸载”,接着点击“删除”按钮删除SD卡中原有分区,最后点击确定,确认删除,具体过程可以参考下图。
接着开始创建分区,首先创建boot分区,用于u-boot读取设备树、内核等文件,我们需要在分区前方空出一定的空间,用于u-boot以及SPL程序存放,如下图所示,首先点击左上角按钮,创建新分区,然后按照下图创建boot分区。
此处为U-Boot以及SPL预留了1Mib的空间,完全足够存放这些程序。
接着创建rootfs分区,我们将剩下的空间全部作为rootfs,文件系统选择ext4,如下图所示:
最后点击保存,确认后生效,拔出SD卡备用,操作可参考下图:
作者适配的U-Boot目前使用了master分支的U-Boot并给出了移植指南。由于后续master分支代码可能会存在更新,所以移植指南使用了最近的一个U-Boot版本来指导复刻打开发者进行修改和配置,编译出自己的U-Boot。
本项目所有资料均已开源,软硬件都开源了,其中软件开源了:UBoot、Kernel、Buildroot:测试镜像下载地址等……想获取资料自己DIY学习的伙伴可以前往获取
1、问题背景
硬件:R系列芯片,XR806
软件:Tina
2、问题简述
说明:该FAQ旨在列出和分析低功耗蓝牙BLE常见的断开连接原因,方便排查连接过程、连接之后和绑定过程中的断开连接问题。
3、问题分析和解决办法
蓝牙低功耗BLE连接时和连接后和绑定过程中可能会有不同原因造成蓝牙断开,下面是比较常见的几种断开错误码和原因分析。
问题背景
XR806的文件管理系统是littlefs或spifs,不像fatfs可以直接进行文件传输,有客户放映不清楚如何通过文件管理系统调用音频文件。
问题描述
XR806SDK中的audio_demo找不到本地mp3等音频文件。
问题分析
解决方法
打包音频文件
新建一个文件夹,如data(名称随意),并把目标音频文件存放仅该目录下,值得注意的是因为audio_demo中默认播放的的是music文件夹下的音频文件,所以音频文件也必须放在music文件夹下。
.
├── data
│ └── music
│ └── 1.mp3
└── mklittlefs
打包该文件夹使用如下命令
./mklittlefs -c data/ -d 0 -b 4096 -p 256 -s 524288 lfs.bin
-c后接目标路径。
-d后接debug等级,默认为0,不用修改。
-b后接block的大小,littlefs默认为4096,一般情况下不用修改。
-p后接page大小,默认为256,不用修改。
-s后接littlefs镜像大小,和在make menuconfig中的配置必须一致。
lfs.bin是生成的镜像文件名。名称随意,但一般是.bin后缀。
make menuconfig配置
进入图形化界面配置,并选中filesystem support后选项配置如下。推荐勾选上flash filesystem image pack support,编译代码后会自动把lfs.bin打包到镜像,否则只能在phoenixMC的调试界面中擦除flash地址1572864(0x18000)后的内容,并手动把lfs.bin写进flash。其中步骤1所说的镜像大小524288就是由2048*1024-1572864而来。
--- filesystem support
[*] flash filesystem image pack support
FileSystem Type Select (LittleFS) --->
(1572864) little filesystem start address
(4096) little filesystem block size
(128) little filesystem block count
修改工程cfg文件配置
把前面打包好的lfs.bin复制到project/demo/audio_demo/image/xr806目录下,并修改目录下的image.cfg。
{
"magic" : "AWIH",
"version" : "0.5",
"image" : {"max_size": "1532K"},
"section" :[
{"id": "0xa5ff5a00", "bin" :"boot_40M.bin", "cert": "null", "flash_offs": "0K", "sram_offs": "0x00230000", "ep": "0x00230101", "attr":"0x1"},
{"id": "0xa5fe5a01", "bin" :"app.bin", "cert": "null", "flash_offs": "71K", "sram_offs": "0x00201000", "ep": "0x00201101", "attr":"0x1"},
{"id": "0xa5fd5a02", "bin" :"app_xip.bin", "cert": "null", "flash_offs": "104K", "sram_offs": "0xffffffff", "ep": "0xffffffff", "attr":"0x2"},
{"id": "0xa5fa5a05", "bin" :"wlan_bl.bin", "cert": "null", "flash_offs": "1075K", "sram_offs": "0xffffffff", "ep": "0xffffffff", "attr":"0x1"},
{"id": "0xa5f95a06", "bin" :"wlan_fw.bin", "cert": "null", "flash_offs": "1078K", "sram_offs": "0xffffffff", "ep": "0xffffffff", "attr":"0x1"},
{"id": "0xa5f85a07", "bin" :"sys_sdd_40M.bin", "cert": "null", "flash_offs": "1103K", "sram_offs": "0xffffffff", "ep": "0xffffffff", "attr":"0x1"},
{}
],
"raw_bin" :[
{"bin" :"lfs.bin", "flash_offs": "1536K"},
{}
]
}
编译完成后编译烧录即可。
问题背景
客户在使用audiocodec进行声音播放时,发现调用snd_pcm_open所用的时间达400多ms,客户希望能减少耗时
开发环境
硬件:R328
软件:Tina
内核: Linux-4.9
问题分析
客户调用snd_pcm_open时,传入的PCM NAME是使用了dmix插件的,dmix插件的功能是可以让多个应用同时访问音频通道,实现声音同时输出的效果。
当不使用任何插件时(-D hw:audiocodec,0),调用snd_pcm_open会马上获取对应的pcm句柄,获取成功后再配置音频通路和寄存器,而当使用dmix插件时,调用snd_pcm_open会先解析插件中的参数,再根据参数配置音频通路和寄存器,通过dapm打开功放和操作DAC寄存器,之后再获取对应的pcm句柄,执行的逻辑更多。
当打开功放和操作DAC寄存器时,为了防止pop音的产生,驱动中加上了160ms + 2*30ms的sleep(乘以2是因为有左右声道),这里的sleep就是耗时产生的地方
解决方案
snd_pcm_open,2646,s:0.439500
Used Time:0.439527 ----> 调整前
snd_pcm_open,2646,s:0.258538
Used Time:0.258548 ----> 调整后
FunnyPi-T113是一款基于全志T113-S3/D1S处理器的完全开源多功能开发板,设计FunnyPi最初的目的是想满足日常学习,结合T113高效能和低功耗的特点,来满足做语音助手,智能家居屏幕、桌面摆件屏、博客服务器等嵌入式应用的开发需求。
FunnyPi开发板内置了丰富的外设接口,包括USB、GPIO、I2C、SPI、UART等,方便用户连接各种传感器、执行器和其他外围设备,同时板载了标准RGB接口、WiFi模组、PMIC芯片、串口转USB芯片等,方便开发者进行DIY开发。
此外,这款开发板适配全志Tina-Linux,支持快启,最重要的一点是,本开发板所有阻容使用0603封装且将绝大部分元件放在了正面,非常方便手焊!
本项目是基于全志T113-S3制作的无线串口调试器,上位机由Qt制作的串口助手客户端、下位机数据采集转发装置组成,配有无线WiFi蓝牙模组,可以对下位机采集到的数据显示以及对于数据绘制波形。
上位机信号的转换使用的是CH343P,对串口3输出TTL信号的调试信息转换成USB差分信号,当然也可以不焊CH343P,用TTL转USB工具直接于电脑连接。电源管理方面使用的是IP5306来进行锂电池充放电管理,并由单节4.2V锂电池进行供电。屏幕则是使用比较常见的十寸RGB565,同样支持RGB666,触摸IC使用GT911。
上位机由Awboot+Linux+Buildroot组成,作者本人开源了交叉编译器、Awboot源码、串口助手qt界面源码等内容,并详细介绍了开发环境搭建及qt界面配置等教程。
如上,你所见到的这款圆滚滚的小玩意,就是魔改了一款在前几期的文章中我们所介绍过的,一款方形的T113-S3智能家居86盒,原项目因其验证过的原理图和PCB(√),可初始化的屏幕(√),详尽的SDK和保姆级的部署教程(√),从而受到了不少开发者的关注,其中也包括一些爱整活的小伙伴。
萨纳兰的黄昏在86盒的原作者FanHuaCloud大佬加持下,又给86盒挖了个新坑,为了解决之前所驱动的圆屏只能播放MJPEG并且帧率较低的尴尬问题,集圆屏加一体化驱动板+外壳+炫酷LVGL UI于一身的圆形86盒横空出世,并命名其为——T113太极派。
目前的版本是插电使用的超薄版本 ,后续可能会增加带扩展版的支持电池的充电版本,其最主要的特点是作者为这款圆形的太极派专门给它建模做了一个极其轻薄的CNC外壳,触摸屏直径是71.8mm,外壳直径为74mm。树脂版外壳厚度为13mm,CNC版外壳厚度为10mm。
此核心板尺寸小巧,板载资源极其丰富,元件均在一面,背面可直接与大显3寸MIPI液晶屏通过FPC排线相连。板载CH340,MPU6050,HUB,NANDFlash,麦克风,XR829-WIFI。仅用一根Typc线便可调试此核心板,非常方便。调试串口,ADB接口,音频接口及USB,GPIO均已用引出。扩展性极强。板载资源均已调试通过。
产品包装清单
包括:
V853主板 *1
7寸屏幕及转接板 *1
双摄像头模组 *1
亚克力支架 *1
Type-C USB数据线 *1
串口线 *1
电源适配器 *1
小喇叭 *1
包装盒 *1
【问题背景】
硬件:R328+ Wi-Fi模组(XR829)
软件:Tina
说明:该FAQ旨在记录在Tina系统上XR829如何进入monitor模式
【问题简述】
我们知道xradio的xconfig配网方式,会自动切到monitor模式,那么驱动如何手动设置进入monitor模式呢?
【问题分析】
1.既然xconfig配网可以进入monitor模式,那么直接分析代码就可以找到对应的操作。
2.可以通过添加打印来跟踪xconfig的调用流程。
echo 0xff > /sys/kernel/debug/xradio_host_dbg/dbg_common
echo 0xff > /sys/kernel/debug/xradio_host_dbg/dbg_sta
【解决方法】
手动设置进入monitor模式:
insmod /system/vendor/modules/xradio_wlan.ko //加载xr829.ko
ifconfig wlan0 down //可能系统起来会默认进入sta模式,所以先down掉
iw wlan0 set type monitor //最关键的,设置进入monitor模式
iw wlan0 info //获取wlan0网卡信息
ifconfig wlan0 up //up wlan0
iw wlan0 set channel 6 //设置信道
echo 0xffff,0xffff > /sys/kernel/debug/ieee80211/phy0/xradio/parse_flags //打开收发帧调试,可以分析monitor模式
在市面上开源的热成像作品中,有一部分颜值高,但分辨率太低;也有一部分把分辨率提高了,但使用起来却不太流畅。
基于此,作者本人结合二者的优势,设计了一款热成像相机——LiThermal,成本算下来只要400出头,还具备了万全的功能。
这款热成像相机不仅拥有高分辨率及丝滑流畅的拍摄,在主控全志T113的加持下,UI界面的几乎所有动画都能达到90Hz刷新率,并支持随意的动画打断,最重要的是,作者将热成像相机的软硬件全部开源了出来!
热成像相机开机动画演示
PCB温度检测演示
远距相机检测演示
相机不仅支持高清拍照功能,而且在拍照的同时能够即时捕捉并显示全屏范围内的温度数据,为用户提供直观的温度分布视图,拍摄后的照片和视频都可以在相册中查看。
此外,该设备还提供了温度数据的最大值、最小值以及中心值的查看功能,帮助快速识别温度异常区域,为了更直观地展示温度变化趋势,设备还贴心配备了温度统计图功能。
为了满足不同用户的需求,设备还支持自定义调色板,根据个人偏好或特定应用场景调整色彩显示方案,重定向后台管理页面的功能,可以通过电脑访问后台管理系统,实现更高级的设置和数据管理操作。
作者直接为相机在全志Tina Linux系统上基于LVGL8设计一套全新的UI界面,并顺利的在2.4寸的320x240分辨率TFT LCD屏幕上以最高94.3 Hz的刷新率流畅运行。
在热成像方面,该设备表现尤为突出,刷新率达到了25Hz,能够实时捕捉温度变化,测温范围也覆盖到0-106.4 ℃,零下的条件下作者未进行测试,但问题不大,基本满足了多种应用场景的需求。传感器方面,设备采用了160*120分辨率的传感器,确保了温度数据的精确捕捉。
本文与热成像相机相关的所有内容均转载自原作者本人立创开源硬件平台的工程页面,软硬件资料均开源,感兴趣的小伙伴可以复制下方链接或者戳文末的“阅读原文”阅读了解。
项目作者:小李电子实验室
项目名称:热成像相机
热成像相机软硬件开源:https://oshwhub.com/lxu0423/lithermal-thermal-imaging-camera
Github开源链接:https://github.com/diylxy/LiThermal
视频版介绍:https://www.bilibili.com/video/BV1e4xeeCEGL
以上链接中包含硬件设计文件,程序源代码和编译工具链,可以根据这些资料完整复刻本作品,达到视频中的效果。
复刻注意事项:本作品难度较大,想要完整复刻需要能够焊接0402元件和0.3毫米间距的QFP引脚,并且需要有一定Linux系统使用经验和计算机网络基础,请做好心理准备。
问题背景
硬件:R328+ Wi-Fi模组(XRADIO)
软件:Tina3.0及以上
说明:该FAQ旨在记录XRADIO驱动常见的调试Tips。与具体驱动相关,具有一般性。
问题简述
客户需求,如何获取和设置地区代码?
问题分析
地区码:表示现有的国家、独立机构或特殊地理政治区域的名称代码。
区别于
移动网络代码:是与移动设备国家代码(Mobile Country Code,MCC)(也称为“MCC / MNC”)相结合,以用来表示唯一一个的移动设备的网络运营商。这些运营商可以是使用的GSM/LTE、CDMA、iDEN、TETRA和通用移动通讯系统的公共陆基移动网亦或是卫星网络。
当前xr829驱动并没有透出接口用来获取和设置地区代码。
解决方法
借用wpa_cli工具来获取和设置:
root@TinaLinux:/# wpa_cli -i wlan0 -p /etc/wifi/sockets set country US
OK
root@TinaLinux:/# wpa_cli -i wlan0 -p /etc/wifi/sockets get country
US
本文是我基于官方文档整理的最新XR806开发环境搭建步骤,所有步骤都合并为几段shell脚本,可以直接复制粘贴执行。
我的开发环境是基于Ubuntu 20.04的,目前XR806的OpenHarmony代码版本是1.0.1_release;
每段脚本下方的注都是容易遇到坑的地方,请大家遇到问题的时候多注意。
[ "$(id -u)" == "0" ] && alias sudo=
sudo apt update
sudo apt -y install curl git python3 python3-pip
ln -s /usr/bin/python3 /usr/bin/python
# 设置pip源为阿里镜像站
pip config set global.index-url http://mirrors.aliyun.com/pypi/simple/
pip config set global.timeout 120
pip config set global.trusted-host mirrors.aliyun.com
# 下载repo工具
[ -e ~/bin/ ] || mkdir ~/bin/
curl -s https://gitee.com/oschina/repo/raw/fork_flow/repo-py3 > ~/bin/repo
chmod +x ~/bin/repo
pip install requests # 码云的 repo 依赖这个pip包
echo 'export PATH=$PATH:~/bin' | tee -a ~/.bashrc
注:
repo命令行工具本身是一个python脚本,所以需要先安装python3;
这里的repo为码云提供的版本,REPO_URL的值他们已经修改好了,不需要再修改;
他们修改的代码依赖了requests包,因此步骤中有配置pip源和下载requests包;
# 下载 gn 压缩包
GN_TARBALL=gn-linux-x86-1717.tar.gz
GN_URL_PREFIX=https://repo.huaweicloud.com/harmonyos/compiler/gn/1717/linux
curl $GN_URL_PREFIX/$GN_TARBALL > $GN_TARBALL
# 下载 ninja 压缩包
NINJA_TARBALL=ninja.1.9.0.tar
NINJA_URL_PREFIX=https://repo.huaweicloud.com/harmonyos/compiler/ninja/1.9.0/linux
curl $NINJA_URL_PREFIX/$NINJA_TARBALL > $NINJA_TARBALL
# 下载clang 9.0 压缩包
CLANG_TARBALL=llvm-linux-9.0.0-36191.tar
CLANG_URL_PREFIX=https://repo.huaweicloud.com/harmonyos/compiler/clang/9.0.0-36191/linux
curl $CLANG_URL_PREFIX/$CLANG_TARBALL > $CLANG_TARBALL
# 下载 gcc-arm-none-eabi-10-2020-q4-major 压缩包
GCC_TARBALL=gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2
GCC_URL_PREFIX=https://armkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/10-2020q4
curl $GCC_URL_PREFIX/$GCC_TARBALL > $GCC_TARBALL
[ -e ~/tools/ ] || mkdir ~/tools/
# 解压gn和ninja压缩包
tar -C ~/tools/ -xvf $GN_TARBALL
tar -C ~/tools/ -xvf $NINJA_TARBALL
echo 'export PATH=$PATH:~/tools:~/tools/ninja' | tee -a ~/.bashrc
# 解压clang压缩包
tar -C ~/tools/ -xvf $CLANG_TARBALL
echo 'export PATH=$PATH:~/tools/llvm/bin' | tee -a ~/.bashrc
# 解压gcc压缩包
tar -C ~/tools/ -xvf $GCC_TARBALL
echo 'export PATH=$PATH:~/tools/gcc-arm-none-eabi-10-2020-q4-major/bin' | tee -a ~/.bashrc
source ~/.bashrc # 生效环境变量
注:
全志在线的步骤描述里面没有写需要下载gn/ninja/clang,但是后续的hb build命令会依赖这几个命令行工具;
# 配置你的git作者信息
git config --global user.email "yourname@example.com"
git config --global user.name "Your Name"
# 创建目录
[ -e ~/xr806_openharmony ] || mkdir ~/xr806_openharmony
cd ~/xr806_openharmony
# 初始化清单仓
repo init -u https://gitee.com/openharmony/manifest.git -b OpenHarmony_1.0.1_release --no-repo-verify
# 同步所有代码仓到本地
repo sync -c
repo forall -c 'git lfs pull'
# 下载 xr806 的 device和vendor 仓到 device/xradio和vendor/xradio目录
git clone https://gitee.com/XR806/devboard_device_allwinner_xr806.git device/xradio/
git clone https://gitee.com/XR806/devboard_vendor_allwinner_xr806.git vendor/xradio/
注:
最后的两个仓是我从uncleli克隆的两个代码仓,因为全职在线文档中openharmony-sig组织下的代码仓暂设置访问权限为私有了,组织外部人员暂时无法访问;
配置项目
第一次编译前需要执行“配置项目”的步骤。主要用于生成部分Kconfig配置和Makefile代码片段,后续编译不再需要执行这里的步骤。
sudo apt install -y libncurses5-dev # menuconfig 依赖的ncurses库
# 进入SDK目录。
cd device/xradio/xr806/xr_skylark/
# 复制配置文件。
cp project/demo/audio_demo/gcc/defconfig .config
# 使用图形化界面确认配置。
make menuconfig
# 执行make menuconfig后,按方向键选择save保存后,选择exist退出即可。
# 清除过程文件。
make build_clean
# 生成静态库已经自动生成头文件。
make lib -j
# 返回根目录。
cd -
安装hb命令
第一次编译前,需要安装hb命令。后续的编译不再需要执行这里的命令。
cd ~/xr806_openharmony
cd build/lite
pip install prompt_toolkit==1.0.14
python setup.py install --user
echo 'export PATH=$PATH:~/.local/bin' | tee -a ~/.bashrc # 将hb命令所在目录加到PATH环境变量
source ~/.bashrc # 生效环境变量
编译代码
hb set #回车,并选择wifi_skylark,第一次编译需要执行这个命令,执行完成后会生成ohos_config.json文件
hb build -f # 编译代码
运行device\xradio\xr806\xr_skylark\tools目录下的phoenixMC_v3.1.21014b.exe;
编译完的镜像文件在device\xradio\xr806\xr_skylark\out目录下;
烧录软件的使用参考全志在线的文档:固件烧录 - XR806 (aw-ol.com)
烧录需要注意:记得勾选“硬件复位烧写模式”,否则进度到92%就会报错。
XR806 OpenHarmony默认的串口配置为:波特率115200,无校验,8位数据位,1位停止位。
使用PuTTY查看启动日志,需要在的Terminal配置中,勾选“Implicit CR in every LF”和“Implicit LF in every CR”这两个选项。
参考了以下链接:
全志在线XR806的文档:https://xr806.docs.aw-ol.com/ (aw-ol.com)
码云临时代码仓:uncleli/devboard_device_allwinner_XR806 (gitee.com)
文章转自极术社区:https://aijishu.com/a/1060000000291606
作者:xusiwei1236
@xieminghao sudo git config --global credential.helper store
做SDK对接过程作,应用开发有可能需要自己制作升级固件(OTA,烧录)。这就需要从SDK中找出编译好的各个分区镜像文件。本FAQ提供一个脚本,将各个分区已经编译好的镜像分区给拷贝到一个固定地方。
将附件中内容按照目录替换到sdk的相应目录当中。
目录:scripts/.hooks (如.hooks 目录不存在,在需要创建)
scripts/.hooks
|_post-pack
|_pack_script
|_aw_pack.sh
安装完成后,直接按正常使用那样,执行pack 脚本进行打包,打包成功后回有打印:
/home/v833-sdk/out/v831_pro_ipc/image/…/aw_pack_src
├── aw_pack.sh #执行该脚本即可再进行打包,并输出在当前目录的out目录
├── config #打包的配置文件
├── image #各种分区镜像文件,可以替换,请勿改名
│ ├── boot0_nand.fex #boot0 的nand镜像
│ ├── boot0_sdcard.fex #boot0 的SD卡镜像
│ ├── boot0_spinor.fex #boot0 的spinor镜像
│ ├── boot.fex #内核镜像
│ ├── boot_package.fex # uboot打包镜像
│ ├── boot_package_nor.fe # uboot 在spinor方案上的打包镜像
│ ├── env_nor.fex #env环境变量镜像
│ ├── rootfs.fex #rootfs 镜像
│ ├── sunxi.fex #GTP分区表
│ └── other.fex #其他分区镜像
├── other #打包所需的其他文件
├── out #执行aw_pack.sh脚本后输出的目录
├── tmp #临时目录,用于打包时使用
└── tools #打包时需要的linux 工具
脚本文件:aw_pack_src.tar.gz
狗子身上有12个舵机,一条腿接3个舵机,连接到一个pca9685舵机驱动上,舵机驱动通过I2C与XR806通信,XR806有两个I2C接口,我主要是用第1个(B14, B15),通过控制PCA9685上的寄存器来简介控制舵机。
pca9685参数:
引脚图示:
首先要进行i2c初始化:
i2c_init(i2c_id);
导入相关库:
#include "driver/chip/hal_i2c.h"
#define i2c_id 1 # 使用第1个i2c
i2c初始化函数:
void i2c_init(unsigned int id){
I2C_InitParam initParam;
initParam.addrMode = I2C_ADDR_MODE_7BIT;
initParam.clockFreq = 40000;
if (HAL_I2C_Init(id, &initParam) != HAL_OK) {
printf("i2c init fail!\n");
while(1);
} else {
printf("i2c init success!\n");
}
}
主要是调用HAL_I2C_Init函数对第1个i2c接口进行初始化。
初始化pca9685:
pca9685_init(60);
pca9685_init函数:
void pca9685_init(float hz) {
pca_write(pca_mode1, 0x0);
pca_setfreq(hz);
OS_MSleep(500);
}
1. pca9685写寄存器
void pca_write(uint8_t reg_addr, uint8_t data) {
uint8_t buf[1];
buf[0] = data;
HAL_I2C_Master_Transmit_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1);
}
2. pca9685读寄存器
uint8_t pca_read(uint8_t reg_addr) {
uint8_t buf[1];
HAL_I2C_Master_Receive_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1);
return buf[0];
}
3. pca9685设置频率
void pca_setfreq(float freq) {
uint8_t prescale,old_mode,new_mode;
double prescaleval;
freq *= 0.92;
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale =(uint8_t)(prescaleval + 0.5f);
old_mode = pca_read(pca_mode1);
new_mode = (old_mode & 0x7F) | 0x10;
pca_write(pca_mode1, new_mode);
pca_write(pca_pre, prescale);
pca_write(pca_mode1, old_mode);
OS_MSleep(2);
pca_write(pca_mode1, old_mode | 0xa1);
}
这里的代码不像读函数和写函数一样好理解,主要是一般情况下,在用pca9685内置晶振,为25MHZ,通过配置PRE_SCALE寄存器进行配置。如果在舵机控制中,采用内置晶振,取osc_clock=25000000,update_rate=50(舵机控制频率50Hz)。
而且在写PRESCALE寄存器的时候,要先设置为Sleep模式,也就是将mode1寄存器的SLEEP标志位设置为1,具体可参考我上面写的代码。
void pca_setpwm(uint8_t num, uint32_t on, uint32_t off) {
pca_write(LED0_ON_L+4*num,on);
pca_write(LED0_ON_H+4*num,on>>8);
pca_write(LED0_OFF_L+4*num,off);
pca_write(LED0_OFF_H+4*num,off>>8);
}
pwm通道寄存器如下图:
由图可知,对于每一个通道,有4个寄存器,在设置PWM占空比的时候,首先配置舵机的示例如下图所示(ON < OFF的情况):
狗子运行图:
核心代码:
#include <stdio.h>
#include "ohos_init.h"
#include "kernel/os/os.h"
// #include "iot_i2c.h"
#include "driver/chip/hal_i2c.h"
#include "iot_errno.h"
#include "math.h"
#define i2c_id 1
#define pca_adrr 0x40 // pca9685设备地址
#define pca_mode1 0x0
#define pca_pre 0xFE
#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9
static OS_Thread_t g_main_thread;
void pca_write(uint8_t reg_addr, uint8_t data) {
uint8_t buf[1];
buf[0] = data;
HAL_I2C_Master_Transmit_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1);
}
uint8_t pca_read(uint8_t reg_addr) {
uint8_t buf[1];
HAL_I2C_Master_Receive_Mem_IT(i2c_id, pca_adrr, reg_addr, I2C_MEMADDR_SIZE_8BIT, buf, 1);
return buf[0];
}
void pca_setfreq(float freq) {
uint8_t prescale,old_mode,new_mode;
double prescaleval;
freq *= 0.92;
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale =(uint8_t)(prescaleval + 0.5f);
old_mode = pca_read(pca_mode1);
new_mode = (old_mode & 0x7F) | 0x10;
pca_write(pca_mode1, new_mode);
pca_write(pca_pre, prescale);
pca_write(pca_mode1, old_mode);
OS_MSleep(2);
pca_write(pca_mode1, old_mode | 0xa1);
}
void pca_setpwm(uint8_t num, uint32_t on, uint32_t off) {
pca_write(LED0_ON_L+4*num,on);
pca_write(LED0_ON_H+4*num,on>>8);
pca_write(LED0_OFF_L+4*num,off);
pca_write(LED0_OFF_H+4*num,off>>8);
}
void pca9685_init(float hz) {
pca_write(pca_mode1, 0x0);
pca_setfreq(hz);
OS_MSleep(500);
}
void pca_rotate(uint8_t num,uint8_t angle) {
uint32_t off=0;
off=floor(angle * 2 + angle / 5 + 158);
pca_setpwm(num, 0, off);
}
void i2c_init(unsigned int id){
I2C_InitParam initParam;
initParam.addrMode = I2C_ADDR_MODE_7BIT;
initParam.clockFreq = 40000;
if (HAL_I2C_Init(id, &initParam) != HAL_OK) {
printf("i2c init fail!\n");
while(1);
} else {
printf("i2c init success!\n");
}
}
static void MainThread(void *arg) {
uint8_t i = 0;
i2c_init(i2c_id);
pca9685_init(60);
printf("i2c and pca9685 init done.\n");
while(1){
pca_rotate(0, 0);
OS_MSleep(1000);
pca_rotate(0, 180);
OS_MSleep(1000);
}
}
void PCAMain(void) {
printf("PCA9685 Motor Start.\n");
if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL,
OS_THREAD_PRIO_APP, 10 * 1024) != OS_OK) {
printf("[ERR] Create MainThread Failed\n");
}
}
SYS_RUN(PCAMain);
狗子的相关步态算法还在调试,使用的舵机是MG90S,走得还不是很流畅,狗腿子会抖,后面需要继续调试,而且供电也是个大问题,不过已经焊了个供电模块稍微解决了,后面还要给狗子加个壳。
文章转自极术社区:https://aijishu.com/a/1060000000292068
作者:堇花还没开吗
【调试信息】
cat /sys/kernel/debug/mpp/ve
********Channal[0]: H265Enc Ver.07388090580d7ad94c897633057ff0806abf4340 F119********
MainProfile, Level:186, BitRate:1048576, FrameRate:20
Input:1920x1080, Output:2304x1296, RotAng:0
Crop: Left:0, Top:0, Width:0, Height:0
IDRPeriod:40, GopSize:2, NormalP, VBR, IpcCase, Colour
InitQp:30, IQp[10~50], PQp[10~50]
CurPQp:29, TargetBits:23424, RealBits:25920, BitRatio:110.66%
Scene:1, Move:0, MovingLevel:0, BinImgRatio:0.32%, MovingTh:20
AvgBitRate:3, RealBitRate:763, AvgFrmRate:0, RealFrmRate:21
LBC:1, Lossy2X, y&yc_stride:1152,1728
VbvSize:622592, Unused:619328, Valid:3264, ValidFrmNum:1
Intra4x4:1, IntraInP:1, 3DNR:1
Quality:10, IBitsCoef:10, PBitsCoef:10
Mad[][10]:96.21 1.55 0.42 0.23 0.17 0.10 0.07 0.03 0.00 0.00 0.00 0.00
Mad[0][]:3.83 4.90 39.77 30.91 10.06 3.28 1.81 0.83 0.45 0.36
ClassifyTh[]:0 0 0 0 0 0 0 0 0 0 0 0
Lambda:0.00, LambdaC:0.00, LambdaSqrt:0.00
Intra: Coef{31 31 31}, Th{0 0 0}
PredTend: Inter:16, Skip:16, Merge:20
********End Channal[0] H265Enc********
********Channal[1]: H264Enc Ver.07388090580d7ad94c897633057ff0806abf4340 F119********
MainProfile, Level:51, BitRate:512000, FrameRate:20
Input:640x360, Output:640x360, RotAng:0
Crop: Left:0, Top:0, Width:0, Height:0
IDRPeriod:60, NormalP, VBR, IpcCase, Colour
InitQp:30, IQp[10~45], PQp[10~45]
CurPQp:20, TargetBits:20894, RealBits:25648, BitRatio:122.75%
Scene:0, Move:1, MovingLevel:1, BinImgRatio:1.55%, MovingTh:20
AvgBitRate:585, RealBitRate:374, AvgFrmRate:21, RealFrmRate:21
LBC:1, Lossy2X, y&yc_stride:384,576
VbvSize:304128, Unused:300864, Valid:3264, ValidFrmNum:1
Intra4x4:1, IntraInP:1, 3DNR:1
Quality:10, IBitsCoef:10, PBitsCoef:10
Mad[][10]:46.81 7.12 1.50 0.44 0.38 0.56 0.19 0.19 0.25 0.06 0.00 0.00
Mad[0][]:1.31 5.94 7.38 3.81 4.75 5.50 7.31 4.81 3.62 2.38
ClassifyTh[]:0 0 0 0 0 0 0 0 0 0 0 0
********End Channal[1] H264Enc********
【参数说明】
参数 | 描述 |
---|---|
Channal | VE通道号,0/1/2/3 |
EncType | 编码类型,H264:H264Enc;H265:H265Enc |
Ver | 编码库版本,对应的commit ID |
FrameCnt | 编码帧的帧数,例如F119表示第119帧 |
Profile | 编码输出视频的档次,档次越高,允许使用的编码技术越丰富。H264低于High不能使用8x8转换,质量变差,如客户没具体要求,默认H264使用Hihg,H265使用Main。H264:Base/ Main/ High,H265:Main/ Main10/ MainStill |
Level | 编码输出视频的等级,按照标准规定的分辨率和帧率而定。过往有些客户的机器出现解码高Level码流性能不足的情况,所以建议由编码库内部自适应配置。H264:定义在enum H264_LEVEL_E,H265:定义在enum H265_LEVEL_E |
BitRate | 码率,单位bps |
FrameRate | 帧率,单位fps |
Input | 编码输入分辨率 |
Output | 编码输出分辨率 |
RotAng | 旋转角度 |
Crop | 裁剪坐标 |
IDRPeriod | I帧周期 |
GopSize | 编码参考集大小,该参数不影响码率控制,H265的该参数会影响某些解码器的参考帧内存预分配,建议统一配置成2。 |
FixQp | 固定I/P帧Qp,仅在RcMode选择FixQp时生效 |
InitQp | RcMode为非FixQp下,首个I帧Qp |
IQp | I帧Qp取值范围 |
PQp | P帧Qp取值范围 |
TargetBits | 当前帧目标bit数 |
RealBits | 当前帧实际bit数 |
BitRatio | bit数比例 = 100 * RealBits / TargetBits |
Scene | 纹理复杂程度的等级,[0,2] |
Move | 运动复杂程度的等级,[0,4] |
MovingLevel | 场景变化剧烈程度等级,[0,3] |
BinImgRatio | SAD超过MovingTh的8x8块数量比例,[0%,100%] |
MovingTh | 8x8块SAD阈值,[10,31] |
AvrBitRate | 到目前为止的平均码率,按编码器拿到的VIBuffer的时间戳每隔一秒更新一次 |
RealBitRate | 实际最近一秒内的瞬时码率,按编码器拿到的VIBuffer的时间戳每隔一秒更新一次 |
AvgFrmRate | 到目前为止的平均帧率,按编码器拿到的VIBuffer的时间戳每隔一秒更新一次 |
RealFrmRate | 实际最近一秒内的瞬时帧率,按编码器拿到的VIBuffer的时间戳每隔一秒更新一次 |
AFBC | VI使用LBC的开关,[0,1] |
LBC | VI使用LBC的开关,[0,1] |
VbvSize | 编码器输出Buffer长度 |
Unused | VbvBuffer剩余可用空间 |
Valid | VbvBuffer已占用空间 |
ValidFrmNum | 占用VbvBuffer未归还的帧数量 |
Intra4x4 | 帧内4x4块预测开关,[0,1] |
IntraInP | P帧使用帧内预测开关,[0,1] |
3DNR | 编码器3D滤波开关,[0,1] |
Quality | 静止场景下的P帧bit数比重,按10为100%计算,[0,20] |
IBitsCoef | I帧bit数比重,按10为100%计算,[1,20] |
PBitsCoef | 运动场景下P帧bit数比重,按10为100%计算,[1,50] |
Mad[][10] | 纹理MAD直方图比例分布,以10为步长 |
Mad[0][] | 纹理MAD在10以内的直方图比例分布,以1为步长 |
ClassifyTh | MB级码控分类阈值 |
Lambda | H265编码器Lambda值,H264:无效,V853的H265无效 |
LambdaC | H265编码器LambdaC值,H264:无效,V853的H265无效 |
LambdaSqrt | H265编码器LambdaSqrt值,H264:无效,V853的H265无效 |
IntraCoef | 帧内分块决策系数,三个系数分别对应32x32、16x16、8x8块,系数越大,越倾向于分小块,H264:无效,H265:[0,31] |
IntraTh | 帧内分块决策MAD阈值,三个阈值分别对应32x32、16x16、8x8块,阈值越小,越倾向于分小块,H264:无效,H265:[0,127] |
PredTendInter | Inter预测模式倾向系数,系数越小,越倾向于Inter预测,H264:无效,H265:[0,63] |
PredTendSkip | Skip预测模式倾向系数,系数越小,越倾向于Skip预测,H264:无效,H265:[0,63] |
PredTendMerge | Merge预测模式倾向系数,系数越小,越倾向于Merge预测,H264:无效,H265:[0,36] |
【调试方法】
app运行结束后,支持查看VE debugfs调试信息功能的开启方法。
默认情况下,app运行结束后,通过 cat /sys/kernel/debug/mpp/ve 无法继续查看ve的debugfs调试信息。
在app运行前执行命令 echo 1 > /sys/kernel/debug/mpp/ve 可开启该功能。
平台重启后,修改会自动失效,需要重新打开。
执行命令 echo 0 > /sys/kernel/debug/mpp/ve 可恢复默认情况。
H264
使用Elescard StreamEye 4.6 工具(只支持H264),按如下截图配置后,开启P帧帧内刷新后,图像看到一个橙色的竖状矩形条,同时逐帧往后查看时矩形条会从左往右移动;关闭P帧帧内刷新后,则无此矩形条。
H265
使用YUView 工具(需要配置ffmpeg动态库)可分析H265文件,勾选Pred Mode后,可显示一个蓝色的竖状矩形条,同时逐帧往后查看时矩形条会从左往右移动;关闭P帧帧内刷新后,则无此矩形条。
注:
YUView 工具配置ffmpeg动态库
为使YUView 工具分析能力更强,需要配置ffmpeg动态库。配置方法如下:
@shenhao618 市面上有很多Pi都是基于H616开发的哦,我们欢迎大家到全志在线讨论相关的内容,SDK的话可以联系你购买商家的客服或者代理获取
OTA升级失败出现"Found installer for stream recovery ubivol"以及"cannot write 16384 bytes: Operation not permitted"报错
软件:Tina
OTA升级失败,通过命令 cat /mnt/UDISK/swupdate.log查看升级失败log,出现 “cannot write 16384 bytes: Operation not permitted” 或 “Found installer for stream recovery ubivol” 错误。
通过两条报错可以猜测,不允许操作,不能写入,以及报ubivol等问题。
可以在设备端确认当前介质,执行ll dev/by-name确认(例:当前为emmc介质,为mmc*,如果为nand ubi介质为ubi* 或 nand*)
升级包的中文件的介质与当前板子的介质不对应,不能操作分区。
[KERNEL_DIR]/arch/arm/boot/dts/sun8iw21p1.dtsi
npu: npu@03050000 {
compatible = "allwinner,npu";
reg = <0x0 0x03050000 0x0 0x1000>;
device_type = "npu";
dev_name = "npu";
interrupts = <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_npu>,
<&clk_pll_npux4>;
clock-frequency = <504000000>;
interrupt-names = "npu";
iommus = <&mmu_aw 6 1>;
status = "okay";
power-domains = <&pd V853_PD_NPU>;
};
一:如何使能电源控制以及电源状态确认
在dts确认配置power-domains后,电源默认打开,如果NPU模块电源需要关闭,需要内核支持CONFIG_SUNXI_POWER_DOMAINS=y;不用的时候应用层软件会默认将其关闭;
root@TinaLinux:/# echo 0x070010a0,0x070010ac > /sys/class/sunxi_dump/dump && cat
/sys/class/sunxi_dump/dump
0x070010a0: 0x00000002 0x00020000 0x00000000 0x00000000
如上为电源处理关闭状态
echo 0x070010a0,0x070010ac > /sys/class/sunxi_dump/dump && cat
/sys/class/sunxi_dump/dump
0x070010a0: 0x00000001 0x00010000 0x00000000 0x00000000
如上电源处于开启状态
二:如何配置支持的频率
clock-frequency = <504000000>;代表现在设置的频率为504M;客户请严格按照原厂建议配置,或直接默认配置,勿随意更改其频点;
clocks = <&clk_npu>,<&clk_pll_npux4>;前面为模块时钟,后面为父时钟;
在[KERNEL_DIR]/drivers/clk/sunxi/clk-sun8iw21_tbl.c中有如下table代表目前所支持的频点;
/* PLL_NPU(n, d1, freq) F_N8X8_D1V1X1 */
struct sunxi_clk_factor_freq factor_pllnpux4_tbl[] = {
PLLNPU(24, 1, 300000000U),
PLLNPU(40, 1, 492000000U),
PLLNPU(20, 0, 504000000U),
PLLNPU(25, 0, 624000000U),
PLLNPU(28, 0, 696000000U),
PLLNPU(41, 0, 1008000000U),
PLLNPU(57, 0, 1392000000U),
};
配置规则为:
npu4x的计算公式为:24M * (N+1) / (M+1)N:0-254M:0-1
在clk-sun8iw21_tbl.c中截图部分添加新的频率组即可
如:492PLLNPU(40, 1, 492000000U),24*(40+1)/(1+1)= 492
通过clk_summary我们可以看到配置是否生效
root@TinaLinux:/# cat /sys/kernel/debug/clk/clk_summary
pll_npux4 0 2 504000000 0 0
npu 0 1 504000000 0 0
当然我们也可以从probe的打印看到时钟的相关配置
[ 843.717406] Want set pclk rate(504000000) support(504000000) real(504000000)
[ 843.725445] Want set mclk rate(504000000) support(504000000) real(504000000)
三:怎么用命令修改时钟的频率
reboot uboot
#等待其处于uboot中后:
fdt list /soc/npu
fdt set /soc/npu clock-frequency <696000000>
fdt list /soc/npu
save
boot
@shenhao618 市面上有很多Pi都是基于H616开发的哦,我们欢迎大家到全志在线讨论相关的内容,SDK的话可以联系你购买商家的客服或者代理获取
全志V853处理器,是专为智慧视觉领域设计的AI处理器,配备了高效的NPU算力和丰富的外设接口,能够提供稳定的AI边缘计算支持;同时,基于V853处理器这一性能优势,全志还为客户提供了一整套完整的智能辅助驾驶算法,包括:
可为 智能座舱、行车记录仪、智能后视镜、倒车影像、360°行车监控等智慧车载场景及产品提供稳定可靠的视觉技术解决方案。
ADAS是通过车载摄像头、雷达等传感器对行车时道路环境信息进行实时采集和处理的算法系统。结合 全志V853 内置的 NPU 加速模型进行系统的推理运算与分析,可以快速辅助机动车或驾驶员做出相应判断和预警,从而提高行车的舒适性与安全性。ADAS算法中包括:
前车启停监测(FVSA)
车道偏离警告(LDW)
前方碰撞警告(FCW)
交通标志牌识别(TSR)
信号灯识别(TLR)
BSD 算法通常需要多个摄像头实时监测车辆两侧和后方的盲区,全志 V853 支持多路摄像头接口,可以很好地同时处理来自多个摄像头的数据。通过 NPU 高效执行图像处理和目标检测算法,实时对盲区内的机动车、行人等关键目标进行精准识别,对驾驶员行为做出预警。
DMS算法是行车记录仪中至关重要的一个应用。它通过对驾驶员的面部特征、眼睛运动、头部姿态等进行实时分析,能够判断驾驶员的疲劳程度、分心状态等,并及时发出预警,提醒驾驶员注意行车安全。
全志技术团队结合CPU和NPU进行了深度性能优化,可以实现毫秒级算法快速冷启动和低算力占用。部分场景算力资源占用情况:
同时,算法使用了大批量的素材对算法进行深度训练,并进行了长时间的道路实测。其中仅亚裔人种的DMS算法人脸训练素材就超过了128000份,令算法更适合亚洲道路行车场景。
目前,全志V853智慧视觉专用处理器凭借优秀的AI智能算法支持和性能表现,为众多客户完成了产品落地;未来,随着全志V系列芯片的不断升级完善,将进一步夯实在智慧车载视觉领域的技术实力和市场地位,全志也会持续以创新的技术和产品,为全球客户提供核心算力支撑,助力更多智慧车载产品的量产提速,让人们更高效便捷的享受智慧出行美好体验。
1.主题
如何动态打开蓝牙kernel部分的log
2.问题背景
产品:扫描笔等Tina产品
硬件:V853 + XR829
软件:Tina linux4.9
目的是为了分析问题,抓取kerne里面/net/bluetooth/、driver/bluetooth/目录下的BT_DBG打印。
3.解决办法
menuconfig选上CONFIG_DEBUG_FS、CONFIG_DYNAMIC_DEBUG
1. echo 8 > /proc/sys/kernel/printk 调整printk打印等级为7以上
2. cat /sys/kernel/debug/dynamic_debug/control | grep bluetooth 查看目前能控制的打印
3. echo 'file hci_core.c +p' > /sys/kernel/debug/dynamic_debug/control 指将hci_core.c文件的打印打开
4. echo 'file hci_core.c -p' > /sys/kernel/debug/dynamic_debug/control 指将hci_core.c文件的打印关闭
5. echo "file net/bluetooth/rfcomm/core.c line 1603 +p" > /sys/kernel/debug/dynamic_debug/control 指将文件net/bluetooth/rfcomm/core.c的第1603行的打印打开
6. echo 'module $mod_name +p' > /sys/kernel/debug/dynaminc_debug/control 指将某个模块的打印打开。
按照上面的方法配置好后,打开蓝牙调试就可以了,默认会输出到终端上或dmesg方式查看。
问题背景
硬件:R329
软件:Tina
内核:Linux-4.9
问题描述
使用Audiocodec进行录音,格式S24_LE,录制的.wav波形在某些软件中异常
arecord -D hw:audiocodec -f S24_LE -r 16000 -c 2 -d10 /tmp/test3_S24_LE.wav
需要放大很多倍才能看到声音波形
问题分析
1.R329的Audiocodec用于录音的ADC只支持16bit和20bit的采样精度。采样后的数字信号会存放到RX_FIFO中,RX_FIFO的大小为256*20-bit,其他平台可以在User Manual确认支持的采样精度,从而判断是否会有这个问题产生
2.RX_DATA是一个32位的寄存器,保存的是从RX_FIFO获取的一个channel的样本数据,当使用arecord进行录音时,RX_DATA中的值会经DMA搬至内存,最后保存到.wav中
其中RX_DATA有四种模式去获取RX_FIFO的数据,S24_LE和S32_LE均采用20-bit mode0
当设置了20bit采样精度时,对应的两种模式如下图所示:
3.先说明一下S24_LE和S32_LE这两种格式的区别
S24_LE指有符号整型,范围是-2^23 ~ ((2^23) - 1),有效数据在低24位
S32_LE指有符号整型,范围是-2^31 ~ ((2^31) - 1),有效数据在高24位
LSB MSB
1st byte 2nd byte 3rd byte 4th byte alignment
S32_LE: 00000000 xxxxxxxx xxxxxxxx xxxxxxxx 32 bits
S24_LE: xxxxxxxx xxxxxxxx xxxxxxxx 00000000 32 bits
S24_3LE: xxxxxxxx xxxxxxxx xxxxxxxx 24 bits
4.在驱动程序中,S24_LE和S32_LE虽然都支持,但他们两者都是使用20-bit的mode0,这导致这两种格式保存到文件中的数据排布是一致的,但生成的wav头信息中的采样位数则不一样,从下图可以看出两者的差异
S32_LE的wav文件信息:
若软件以S32_LE进行解析,以上红框的数据变为0x0f80f0,依然可以保留全部有效数据
S24_LE的wav文件信息:
若软件以S24_LE进行解析,以上红框的数据变为0x55f000,便会丢失一部分数据
解决方案
总结原因就是audiocodec的采样精度只支持16和20bit,因此PCM格式中S24_LE虽然也支持,但硬件的特性使驱动并不能做到很好的适配,若软件以标准S24_LE格式进行分析,则会丢失高位的有效数据,这取决于软件如何对数据进行分析,解决方法有以下三种
arecord -D hw:-f S32_LE -r 16000 -c 2 -d10 /tmp/test32.wav
前言
有时我们需要在 Linux 内核中预留一部分内存空间用作特殊用途(给安全模块使用,给其它处理器使用,或是给特定的驱动程序使用等),在 Device Tree 中有提供两种方法对预留内存进行配置:memreserve 和 reserved-memory。
memreserve
memreserve 的使用方法比较简单,如下所示,会将从地址 0x40000000 开始共 1MB 的内存空间预留出来:
/memreserve/ 0x40000000 0x00100000;
使用 memreserve 预留出来的内存一般无法再被 Linux 系统使用(当然,也可以通过特殊方法让代码固定访问该地址,但这种并非标准用法,在此不展开描述)。
reserved-memory
reserved-memory 框架提供了更多样的使用方法,并且与内核的 DMA API 和 CMA 框架紧密联系。
推荐先阅读一下内核自带的文档 Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt,里面有其详细的语法说明和注意事项(例如 reserved-memory 节点中的 #address-cells 和 #size-cells 的值需要与根节点的保持一致)。
下面对几种常见的使用方法进行举例说明:
通过 memremap/ioremap 来使用
在 Device Tree 配置如下,然后通过“memory-region”参数可将该预留内存分配给特定的设备驱动使用:
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
foobar_reserved: foobar@70000000 {
no-map;
reg = <0x0 0x70000000 0x0 0x10000000>;
};
};
foobar_driver: foobar_driver@0 {
memory-region = <&foobar_reserved>;
};
在设备驱动程序中,可解析 Device Tree 节点获得预留内存的物理地址和大小,然后通过 memremap/ioremap 映射这片内存空间来使用:
/* Get reserved memory region from Device-tree */
np = of_parse_phandle(dev->of_node, "memory-region", 0);
if (!np) {
dev_err(dev, "No %s specified\n", "memory-region");
goto error1;
}
rc = of_address_to_resource(np, 0, &r);
if (rc) {
dev_err(dev, "No memory address assigned to the region\n");
goto error1;
}
lp->paddr = r.start;
lp->vaddr = memremap(r.start, resource_size(&r), MEMREMAP_WB);
dev_info(dev, "Allocated reserved memory, vaddr: 0x%0llX, paddr: 0x%0llX\n", (u64)lp->vaddr, lp->paddr);
通过 DMA API 来使用
设置“shared-dma-pool”属性后,可让设备驱动通过 DMA API 来使用预留内存:
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
foobar_reserved: foobar@70000000 {
compatible = "shared-dma-pool";
no-map;
reg = <0x0 0x70000000 0x0 0x10000000>;
};
};
foobar_driver: foobar_driver@0 {
memory-region = <&foobar_reserved>;
};
设备驱动程序中可类似常规地使用 DMA API,它申请的内存不是来源于默认的 CMA 内存池,而是来源于该预留内存:
/* Initialize reserved memory resources */
rc = of_reserved_mem_device_init(dev);
if(rc) {
dev_err(dev, "Could not get reserved memory\n");
goto error1;
}
/* Allocate memory */
dma_set_coherent_mask(dev, 0xFFFFFFFF);
lp->vaddr = dma_alloc_coherent(dev, ALLOC_SIZE, &lp->paddr, GFP_KERNEL);
dev_info(dev, "Allocated coherent memory, vaddr: 0x%0llX, paddr: 0x%0llX\n", (u64)lp->vaddr, lp->paddr);
给 CMA 预留内存
有时我们不需要将预留内存分配给特定的设备驱动,而只是想给默认 CMA 内存池分配一片固定的内存区域,这时我们可配置上“reusable”和“linux,cma-default”:
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
reg = <0x0 0x70000000 0x0 0x10000000>;
linux,cma-default;
};
};
由此可见,不同于 memreserve,通过 reserved-memory 预留的内存有可能进入系统 CMA,这需要满足以下几个条件:
问题背景
硬件: R818
软件: Tina2.5及以上
操作: bootchart工具打开后无法开机。
说明: 客户程序较复杂,系统应用起来时间有25秒,需要优化。bootchart无使用说明。
问题简述
Tina整体启动时间中rootfs占用的比重很大,但是rootfs启动过程中log很多,在自启动脚本中也添加不了打印,造成启动时间统计非常不便。
Tina SDK有集成busybox bootchart工具,可使用该工具分析rootfs启动过程各个进程的启动时刻以及耗费时间等信息,可以让用户分析启动过程并进行针对性优化。
解决办法
该方法支持Tina所有方案。
Tina上开启bootchartd方法:
① make menuconfig,打开CONFIG_BUSYBOX_CONFIG_BOOTCHARTD
② 修改文件env-x.x.cfg,将“init=/sbin/init”修改为“init=/sbin/bootchartd”
③ 编译,烧写固件,设备启动后,等待一段时间(1min左右),直到出现/var/log/bootlog.tgz,使用adb pull将该文件拉到PC端。
④ 在PC端解压附件工具bootchart.tar.xz,运行命令“./bootchart/pybootchartgui.py bootlog.tgz”,生成bootchart.png图片,打开可得系统各进程的启动时间。
bootchart.png图片分析
bootchart.png效果如下图所示,图中横坐标是时间,纵坐标是各进程信息。图上方两条是CPU和I/O的使用情况;下方是各个进程的运行状态,包含各个进程开始执行时间与结束时间,进程条上有颜色信息,表示对CPU、I/O的占用情况。
优化思路:
问题背景
很多Wi-Fi/BT模组默认出厂是不带MAC地址的,整机厂需要根据需求,烧写特定的MAC地址。
MAC地址通路
Linux-4.9后,全志平台模组MAC地址定制流程如下
系统启动后,引导程序会加载env中定义的key,并传递给cmdline和内核dts。 如果安全存储中没有mac/wifi_mac/bt_mac这几个key,或者值解析失败,我们将尝试从私有分区加载并解析这些key。在内核空间中,addr_mgt驱动程序读取cmdline或dts中与Mac相关的键,对其进行解析并导出到其他驱动程序以使用。 为了让用户空间可以访问这些地址值,创建了sysfs来保存地址值。
配置
uboot env
请确保env.cfg中有如下配置项存在:
dts
dts配置参考如下
其中,type_addr_xx表示mac地址的来源,值含义如下
烧写
使用全志烧号工具DragonSN或DragonKey烧写mac/wifi_mac/bt_mac到私有分区或secure storge中。合法的mac地址格式为xx:xx:xx:xx:xx:xx, x 是16禁止值,0-9,a-f。
使用
内核空间
Linux-4.9
int get_wifi_custom_mac_address(char *addr_str)
int get_bt_custom_mac_address(char *addr_str)
int get_eth_custom_mac_address(char *addr_str)
Linux-5.4
int get_custom_mac_address(int fmt, char *name, char *addr)
fmt: 0为str,1为16进制值
name: “wifi”、“bt”、“eth”
用户空间
可以通过sysfs文件节点访问对应值,linux-4.9下主要节点如下:
root@venus-a1:/sys/class/addr_mgt# ls -l
total 0
-rw-r--r-- 1 root root 4096 2019-01-15 17:22 addr_bt
-rw-r--r-- 1 root root 4096 2019-01-15 17:22 addr_eth
-r--r--r-- 1 root root 4096 2019-01-15 17:22 addr_type
-rw-r--r-- 1 root root 4096 2019-01-15 17:22 addr_wifi
linux-5.4下主要节点如下:
console:/ # ls -l /sys/class/addr_mgt/
total 0
-rw-r--r-- 1 bluetooth net_bt_admin 4096 2020-12-22 19:33 addr_bt
-rw-r--r-- 1 root root 4096 2020-12-23 13:10 addr_eth
-rw-r--r-- 1 root root 4096 2020-12-23 13:10 addr_wifi
-r--r--r-- 1 root root 4096 2020-12-23 13:10 summary
问题背景
硬件:R528+ Wi-Fi模组(XR829)
软件:Tina3.0及以上
说明:该FAQ旨在介绍DragonSN烧写mac地址的过程。
问题简述
如何从私有分区获取mac地址?
问题分析
从私有分区获取mac地址,需要利用到DragonSN工具烧写mac地址到私有分区,然后借助uboot以comdline的形式传递到内核,内核addr_mgt驱动解析。
这里简要描述一下上述过程:
1.lichee/brandy-2.0/u-boot-2018/board/sunxi/sunxi_bootargs.c
+int update_user_data_ali(void)
+{
+ int data_len;
+ int ret;
+ char output[SST_WIFI_MAC_ADDR_LEN+1];
+ char *sst_info = NULL;
+ int sec_inited = !sunxi_secure_storage_init(); //分区初始化
+ memset(output, 0, SST_WIFI_MAC_ADDR_LEN+1); //获取存放mac地址的地址空间并初始化
+ if (sec_inited) {
+ sst_info = (char *)malloc(SST_INFO_KEY_VALUE_ACTUAL_LEN); //分配存放mac地址的空间并赋值给sst_info结构体
+ if (get_boot_work_mode() != WORK_MODE_BOOT) {
+ return 0;
+ }
+
+ ret = sunxi_secure_object_read(SST_INFO_KEY_NAME, sst_info, SST_INFO_KEY_VALUE_ACTUAL_LEN, &data_len); //从flash secure storage读出
+ if (ret)
+ pr_msg("=========%s->%d sunxi_secure_object_read failed==============\n", __func__, __LINE__);
+ memcpy(output, sst_info + SST_WIFI_MAC_ADDR_OFFSET, SST_WIFI_MAC_ADDR_LEN);
+ env_set("wifi_mac", output);
+ pr_msg("=========%s->%d fly test wifi_mac:%s==============\n", __func__, __LINE__, output);
+ }else{
+ pr_msg("=========%s->%d sunxi_secure_storage_init failed==============\n", __func__, __LINE__);
+ }
+
+ free(sst_info);
+ return 0;
+}
通过sunxi_secure_object_read()接口根据偏移量读取分区中已经烧好的mac地址,赋值给wifi_mac。在通过env_set()接口将获取到的mac地址传递到env。
2.device/config/chips/r818/configs/ailabs_dictpen/linux/env.cfg
27 #set kernel cmdline if boot.img or recovery.img has no cmdline we will use this
28 setargs_nand=setenv bootargs console=${console}...mac_addr=${mac} wifi_mac=${wifi_mac} bt_mac=${bt_mac}
29 setargs_mmc=setenv bootargs console=${console}...mac_addr=${mac} wifi_mac=${wifi_mac} bt_mac=${bt_mac}
内核读取env.cfg时再把wifi_mac取出来,通过addr-mgt驱动获取。
3.lichee/linux-4.9/drivers/misc/sunxi-addr-mgt/sunxi-addr-mgt.c
83 int get_wifi_custom_mac_address(char *addr_str)
84 {
85 if (IS_TYPE_INVALID(info.type_cur_wifi) ||
86 addr_parse(info.addr_wifi, 1))
87 return -1;
88
89 strcpy(addr_str, info.addr_wifi); //直接拷贝info结构体的mac地址属性,最原始的就是uboot阶段的sst_info
90 return info.type_cur_wifi;
91 }
4.xr829驱动获取lichee/linux-4.9/drivers/net/wireless/xr829/wlan/main.c
478 extern int get_wifi_custom_mac_address(char *addr_str);
479
480 #define MAC_FROM_CHIPID
481 static void xradio_get_mac_addrs(u8 *macaddr)
482 {
483 int ret = 0;
484 char addr_str[20];
485 SYS_BUG(!macaddr);
486 /* Check mac addrs param, if exsist, use it first.*/
487 #ifdef XRADIO_MACPARAM_HEX
488 memcpy(macaddr, xradio_macaddr_param, ETH_ALEN);
489 #else
490 if (xradio_macaddr_param) {
491 ret = xradio_macaddr_char2val(macaddr, xradio_macaddr_param);
492 }
493 #endif
494 if (ret < 0 || !MACADDR_VAILID(macaddr)) {
495 ret = get_wifi_custom_mac_address(addr_str); //直接调用addr_mgt驱动的接口,即从分区读的方式获取mac地址。
496 if (ret != -1) {
497 sscanf(addr_str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
498 &macaddr[0], &macaddr[1], &macaddr[2],
499 &macaddr[3], &macaddr[4], &macaddr[5]);
500 }
501 }
502
503 /* Use random value to set mac addr for the first time,
504 * and save it in wifi config file. TODO: read from product ID*/
505 if (ret < 0 || !MACADDR_VAILID(macaddr)) {
506 #ifdef XRADIO_MACPARAM_HEX
507 ret = access_file(WIFI_CONF_PATH, macaddr, ETH_ALEN, 1); //自定义mac地址写入文件后,从文件获取mac地址
508 #else
509 char c_mac[XRADIO_MAC_CHARLEN+2] = {0};
510 ret = access_file(WIFI_CONF_PATH, c_mac, XRADIO_MAC_CHARLEN, 1);
511 if (ret >= 0) {
512 ret = xradio_macaddr_char2val(macaddr, c_mac);
513 }
514 #endif
515
516 if (ret < 0 || !MACADDR_VAILID(macaddr)) {
517 #if defined(MAC_FROM_CHIPID)
518 u32 databuf[4] = {0};
519
520 sunxi_get_soc_chipid((u8 *)databuf); //获取chipid后定制mac地址的方式
521 macaddr[0] = (((databuf[1] >> 28) & 0x0f) | ((databuf[2] & 0x0f) << 4)) & 0xff;
522 macaddr[1] = (databuf[2] >> 4) & 0xff;
523 macaddr[2] = (databuf[2] >> 12) & 0xff;
524 macaddr[3] = (databuf[3] >> 6) & 0xff;
525 macaddr[4] = (databuf[3] >> 16) & 0xff;
526 macaddr[5] = (databuf[3] >> 26) & 0xff;
527 #else
528 get_random_bytes(macaddr, 6); //随机生成mac地址的方式
529 #endif
530 macaddr[0] &= 0xFC;
531 #ifdef XRADIO_MACPARAM_HEX
532 ret = access_file(WIFI_CONF_PATH, macaddr, ETH_ALEN, 0);
533 #else
534 ret = xradio_macaddr_val2char(c_mac, macaddr);
535 ret = access_file(WIFI_CONF_PATH, c_mac, ret, 0);
536 #endif
537 if (ret < 0)
538 xradio_dbg(XRADIO_DBG_ERROR, "Access_file failed, path:%s!\n",
539 WIFI_CONF_PATH);
540 if (!MACADDR_VAILID(macaddr)) {
541 xradio_dbg(XRADIO_DBG_WARN, "Use default Mac addr!\n"); //驱动默认可以写死一个mac地址。
542 macaddr[0] = 0xDC;
543 macaddr[1] = 0x44;
544 macaddr[2] = 0x6D;
545 } else {
546 xradio_dbg(XRADIO_DBG_NIY, "Use random Mac addr!\n");
547 }
548 } else {
549 xradio_dbg(XRADIO_DBG_NIY, "Use Mac addr in file!\n");
550 }
551 }
552 xradio_dbg(XRADIO_DBG_NIY, "MACADDR=%02x:%02x:%02x:%02x:%02x:%02x\n",
553 macaddr[0], macaddr[1], macaddr[2],
554 macaddr[3], macaddr[4], macaddr[5]);
555 }
解决方法
1.工作获取
从全志一号通入口获取
https://one.allwinnertech.com/#/devtool?menuID=37
2.烧写流程
前提条件:软件支持从private分区获取mac地址。
2.1配置添加类型
2.2设置key类型
2.3开始烧写
2.4烧写成功
3.确认操作
可以直接通过系统起来后ifconfig命令查看mac地址是否与烧写的一致
问题背景
系统:Tina
平台:R818、V833 扫描笔产品
蓝牙功能:a2dp source
问题概述
(1)客户有一个蓝牙音箱和一个蓝牙耳机,并且这两个设备之前都已经跟扫描笔连接配对过了。
(2)客户主动让扫描笔连接上蓝牙音箱。
(3)打开蓝牙耳机,此时蓝牙耳机回连上扫描笔。
但是客户不想要这个场景存在,希望只有一个连接存在。
问题分析
蓝牙耳机打开后回连这个动作,我们无法阻止它,只能想办法拒绝他。
如果在应用层处理,连上了再把它断开,这个会影响状态的管理,也不是最好的解决方法。
所以我们考虑再收到连接请求事件时,就拒绝了它。linux的蓝牙驱动层会处理HCI上报的
事件,因此我们可以在驱动完成这个逻辑。
解决方法
代码路径:
lichee/linux-4.9/net/bluetooth/hci_event.c
连接请求处理函数:hci_conn_request_evt 中增加如下代码:
首先判断连接类型是否是ACL_LINK,然后获取当前连接数,如果当前已经有连接了,就拒绝本次的连接请求。
if (ev->link_type == ACL_LINK) {
if ((hci_conn_num(hdev, ACL_LINK) != 0) && (hdev->dev_type == HCI_PRIMARY)) {
BT_INFO("already exist acl link, reject new! %s, %d", __func__, __LINE__);
hci_reject_conn(hdev, &ev->bdaddr);
return;
}
}
修改过之后的代码如下:
static void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb)
{
struct hci_ev_conn_request *ev = (void *) skb->data;
int mask = hdev->link_mode;
struct inquiry_entry *ie;
struct hci_conn *conn;
__u8 flags = 0;
BT_DBG("%s bdaddr %pMR type 0x%x", hdev->name, &ev->bdaddr,
ev->link_type);
mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, ev->link_type,
&flags);
if (!(mask & HCI_LM_ACCEPT)) {
hci_reject_conn(hdev, &ev->bdaddr);
return;
}
if (ev->link_type == ACL_LINK) {
if ((hci_conn_num(hdev, ACL_LINK) != 0) && (hdev->dev_type == HCI_PRIMARY)) {
BT_INFO("already exist acl link, reject new! %s, %d", __func__, __LINE__);
hci_reject_conn(hdev, &ev->bdaddr);
return;
}
}
if (hci_bdaddr_list_lookup(&hdev->blacklist, &ev->bdaddr,
BDADDR_BREDR)) {
hci_reject_conn(hdev, &ev->bdaddr);
return;
}