@海石生风
那部分zig代码是手写的还是工具生成的?
哪部分代码?model和view-model都是模型,都是业务代码,当然是手写的。
Zig这边的模型都是Zig结构体,而非AWTK的tk_object_t对象;这里的“封装”是将view-model结构体转换为tk_object_t对象,封装手段是泛型反射;可以理解为Zig的view-model在编译时转换成了tk_object_t对象。
在MVVM框架下,model是纯数据模型、view-mode封装了model并添加UI所需的属性和命令用于跟view交互。
而awtk-mvvm其实没有model而只有view-model,而view-model的实现方式是使用代码生成工具将C的结构体转换为tk_object_t对象。
上述示例工程的界面效果如下:
其中“性别平衡”的显示比较体现MVVM作为声明式框架的优势,其控件的UI描述是这样的:
<label w="30%" v-data:text="{isGenderBalance ? tr('是') : tr('否')}" />
其模型代码是这样的:
// 虚拟property: isGenderBalance
pub fn getIsGenderBalance(self: Self, _: []const u8, v: *awtk.Value) c.ret_t {
var male_count: u32 = 0;
var female_count: u32 = 0;
for (self.users.models.items) |item| {
if (item.gender == .Male) {
male_count += 1;
} else {
female_count += 1;
}
}
_ = c.value_set_bool(v, male_count == female_count);
return c.RET_OK;
}
代码就这么多,再没其它的了。这是个虚拟属性,当列表内容增加、减少或列表元素的性别发生变化,其值都会更新。
如果在命令式UI上实现此功能,想必需要添加比较多的事件响应函数,而且整个逻辑代码分散在多个地方,繁琐且容易出BUG.
基于Zig语言对AWTK的MVVM框架在泛型/反射特性之上进行了封装,实现了类似WPF那样方便使用的MVVM框架。因为由Zig编写,故支持64MB内存级别的嵌入式Linux UI开发。AWTK的MVVM框架使用C语言编写,但因为C语言的特性太少,直接使用这个框架的话会非常不方便,比如:要在头文件添加特定格式的注释、属性不支持多层等。使用Zig封装之后,使用的便利性基本就跟WPF的MVVM差不多了。
比如,UI声明大致如下:
<window v-model="UsersVM" >
<label v-data:text="{name}" />
<label v-data:text="{cfg.age}" />
<label v-data:text="{tr(cfg.gender.str)}" />
<button tr_text="大一岁" v-on:click="{addAge}" />
<button tr_text="小一岁" v-on:click="{subAge}" />
<button tr_text="添加" v-on:click="{append}" />
</window>
对应的模型代码大致如下:实现起来是比较直观而且容易的
pub const UsersVM = struct {
name: awtk.String = undefined,
cfg: struct {
age: u8,
gender: user_model.Gender,
} = .{
.age = 0,
.gender = .Male,
},
const Self = @This();
pub fn addAge(self: *Self) !awtk.Notify {
self.cfg.age += 1;
return .PropertyChanged;
}
pub fn canAddAge(self: Self) bool {
return self.cfg.age < 5;
}
pub fn subAge(self: *Self) !awtk.Notify {
self.cfg.age -= 1;
return .PropertyChanged;
}
pub fn canSubAge(self: Self) bool {
return self.cfg.age > 0;
}
pub fn append(self: *Self) !awtk.Notify {
// 逻辑代码实现
return .ItemsChanged;
}
pub fn canAppend(self: Self) bool {
return self.name.len() > 0;
}
};
项目地址在这里:https://gitee.com/ufbycd/awtk-mvvm-zig-example
目前嵌入式Linux已经在D21x平台的D213ECV-DEMO-V4开发板上验证。 另外,Zig的交叉编译也非常方便,上述项目已经内置D21x平台编译支持,编译命令为:
zig build -Dd21x
该命令等效于:
zig build -Dtarget=riscv64-linux-gnu -Dcpu=generic_rv64+i+m+a+f+d+c
可前往项目地址了解更多详情。
linux SDK的构建系统的自动检测ncurses是否安装功能不准确。
Manjaro Linux系统,ncurses被一大堆软件包依赖,肯定是已经安装了的
$ pacman -Q ncurses
ncurses 6.5-4
$ make menuconfig
*** Unable to find the ncurses libraries or the
*** required header files.
*** 'make menuconfig' requires the ncurses libraries.
***
*** Install ncurses (ncurses-devel or libncurses-dev
*** depending on your distribution) and try again.
***
make[1]: *** [Makefile:253:/home/chenss/git/ArtInChip/d211/output/d211_D213ecvDemoV4/build/luban-config/dochecklxdialog] 错误 1
make: *** [package/Makefile.sdk:706:/home/chenss/git/ArtInChip/d211/output/d211_D213ecvDemoV4/build/luban-config/mconf] 错误 2
bootloader主程序在 application/baremetal/bootloader/main.c的main函数内,通过调用console_set_bootcmd函数来执行不同介质的启动命令,这些命令在application/baremetal/bootloader/cmd文件夹内。
SDK文档的“SDK 编译”章节对Eclipse和VS Code均有描述
芯片ID读取可参考/bsp/artinchip/drv/efuse/drv_efuse.c(或bsp/artinchip/drv_bare/efuse/efuse.c)的drv_efuse_read_chip_id函数
方法二改法不太妥当。新版本下,Env.get函数返回的是deque、group.get函数返回的是list,再者不能用加号来组合。最好按下述修改:将deque转为list
diff --git a/kernel/rt-thread/tools/building.py b/kernel/rt-thread/tools/building.py
index 24bcf5cc..cf329981 100644
--- a/kernel/rt-thread/tools/building.py
+++ b/kernel/rt-thread/tools/building.py
@@ -779,8 +779,8 @@ def DoBuilding(target, objects):
CFLAGS = Env.get('CFLAGS', '') + group.get('LOCAL_CFLAGS', '')
CCFLAGS = Env.get('CCFLAGS', '') + group.get('LOCAL_CCFLAGS', '')
CXXFLAGS = Env.get('CXXFLAGS', '') + group.get('LOCAL_CXXFLAGS', '')
- CPPPATH = Env.get('CPPPATH', ['']) + group.get('LOCAL_CPPPATH', [''])
- CPPDEFINES = Env.get('CPPDEFINES', ['']) + group.get('LOCAL_CPPDEFINES', [''])
+ CPPPATH = list(Env.get('CPPPATH', [''])) + group.get('LOCAL_CPPPATH', [''])
+ CPPDEFINES = list(Env.get('CPPDEFINES', [''])) + group.get('LOCAL_CPPDEFINES', [''])
ASFLAGS = Env.get('ASFLAGS', '') + group.get('LOCAL_ASFLAGS', '')
for source in group['src']:
我之前试过是可以的,需要修改Flash设备类型及额外一些配置,参见这里:
今年年初起的这个项目至今都有任何进展,是因为还有很多细节一直没有搞定。并且LVGL的API一直都不稳定,单单是搞其API绑定层都要不少精力。
因为Zig基本上可以直接调用C,故放弃API绑定层的思路,直接在框架内调用UI库的C接口,并将框架转为MVVM。
MVVM框架本身就规定好了View跟Model的绑定规则,非常适合声明式UI框架。
目前已经基本在awtk-mvvm上添加实现了zig语言支持:https://gitee.com/ufbycd/awtk-mvvm-zig-example,在zig上实现Model要比C/C++方便很多,便捷性几乎跟javascript的差不多。
下一步,将用zig元组替换xml来实现View声明,即实现一个zig的MVVM框架:zig-mvvm
再下一步,将zig-mvvm框架推广到其它UI库,如LVGL等。
因为业余自由时间不是很多,一天至多只能抽出两三个小时,也是随缘更新。
我用AWTK有一两年了,只要在awtk.zlg.cn网站保持登录就会持续发放LIC。
不过我不怎么用AWStudio,实际项目都是手写UI描述文件XML,特别是用了MVVM的情形。即使是手写XML肯定也比LVGL好用,LVGL现在才开始搞UI描述文件。
如果对MVVM感兴趣,可以关注我这几天的研究成果,用zig写MVVM应用,利用zig的泛型/反射特性直接从Model构建出View-Model:
https://gitee.com/ufbycd/awtk-mvvm-zig-example
找到问题了!D21x是64位机,C++的初始化函数表.init_array应该是8字节对齐的,而SDK的链接脚本里弄成4字节对齐了。
按以下patch将链接脚本里的改为8字节对齐即可:
diff --git a/bsp/artinchip/sys/d21x/link_script/gcc_aic.ld.S b/bsp/artinchip/sys/d21x/link_script/gcc_aic.ld.S
index 7600a467..561a79ed 100644
--- a/bsp/artinchip/sys/d21x/link_script/gcc_aic.ld.S
+++ b/bsp/artinchip/sys/d21x/link_script/gcc_aic.ld.S
@@ -149,7 +149,7 @@ SECTIONS
*(.rodata.*)
*(.srodata*)
*(.rodata.str1.4)
- . = ALIGN(0x4) ;
+ . = ALIGN(0x8) ;
PROVIDE(__ctors_start__ = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
试过将main线程栈开到4MB都一样。C++代码如下,非常简单,且只是加入编译没有在其它地方调用C++源码的函数。
发现只要用了C++的标准库就出问题,不用C++标准库就没有问题;即使能宏ENABLE_CPP_STD就会出问题
#include <stdio.h>
#define ENABLE_CPP_STD 1
#if ENABLE_CPP_STD
#include <vector>
#endif
class MyOutputStream {
public:
MyOutputStream(){}
MyOutputStream& operator<<(int v) {
printf("%d", v);
return *this;
}
MyOutputStream& operator<<(unsigned int v) {
printf("%u", v);
return *this;
}
MyOutputStream& operator<<(long int v) {
printf("%ld", v);
return *this;
}
MyOutputStream& operator<<(unsigned long int v) {
printf("%lu", v);
return *this;
}
MyOutputStream& operator<<(char v) {
printf("%c", v);
return *this;
}
MyOutputStream& operator<<(const char* v) {
printf("%s", v);
return *this;
}
MyOutputStream& operator<<(char* v) {
printf("%s", v);
return *this;
}
};
class A {
public:
A();
~A();
private:
#if ENABLE_CPP_STD
std::vector<int> vi;
#endif
MyOutputStream cout;
};
A::A()
{
cout << "A init\n";
#if ENABLE_CPP_STD
vi = {1, 2, 3};
vi.push_back(4);
vi.push_back(5);
cout << "is size:" << vi.size() << '\n';
for(auto &i: vi) {
cout << i << ", ";
}
cout << '\n';
#endif
}
A::~A()
{
cout << "A deinit\n";
}
void a_test()
{
A a;
}
经测试AWTK和C++源码只要不是同时使能,系统都能正常运行。但两都同时使能编译在一起运行就会进入不了main函数并出现Exception如下:
CPU Exception: NO.1
x1(ra) : 00000000400361c6 x2(sp) : 00000000402de558 x3(gp) : 000000004026e530f
x5(t0) : 000000004026fef8 x6(t1) : 0000000000000002 x7(t2) : 00000000000000724
x9(s1) : 0000000040269670 x10(a0) : 0000000000000000 x11(a1) : 00000000000000008
x13(a3) : 00000000402a35a0 x14(a4) : 0000000066f25211 x15(a5) : 401f1830000000008
x17(a7) : 0000000000000064 x18(s2) : 00000000deadbeef x19(s3) : 00000000deadbeeff
x21(s5) : 00000000deadbeef x22(s6) : 00000000deadbeef x23(s7) : 00000000deadbeeff
x25(s9) : 00000000deadbeef x26(s10) : 00000000deadbeef x27(s11) : 00000000deadbeefa
x29(t4) : 0000000000000190 x30(t5) : 000000000000002d x31(t6) : 0000000000000000
mcause : 0000000000000001
mtval : 0000003000000000
mepc : 0000003000000000
mstatus : 8000000a00007880
完整启动日志如下:
tinySPL [Built on Sep 11 2024 16:34:02]
[W] usbh_is_connected()105 usb 1 port change wait failed.
[E] main()172 Not find udisk.
qspi0 freq (input): 91636363Hz
qspi0 freq ( bus ): 91636363Hz
nftl vol: data, size 0
Selecting default config 'Luban-lite firmware'
spl read: 2549040 byte, 348034 us -> 7152 KB/s
Boot time:
108099 : Enter main
109631 : Clock and pinmux done
110143 : Console UART ready
111287 : Heap init done
114980 : Banner shown
204155 : UDISK checked
571345 : Run APP
Welcome to ArtInChip Luban-Lite 1.0.5 [D21x Inside]
Built on Sep 24 2024 13:43:04
09-24 13:45:53 I/PWM main: ArtInChip PWM loaded
09-24 13:45:53 I/touch main: rt_touch init success
09-24 13:45:53 I/gt911 main: touch device gt911 init success
[I] aic_find_panel()83 find panel driver : panel-lvds
[I] aicfb_probe()978 fb0 allocated at 0x42000040
[I] hal_ge_init()1620 dither line phys: 0x424B0100
[I] pcm1803a_init()22 pcm1803a init
09-24 13:45:53 I/PSADC main: ArtInChip PSADC loaded
[I] aic_sdmc_clk_init()548 SDMC1 sclk: 50400 KHz, parent clk 1008000 KHz
09-24 13:45:53 I/SDMC main: SDMC1 BW 1, sclk 50400 KHz, clk 400 KHz(406 KHz), div 2-62
[I] aic_sdmc_probe()665 SDMC1 driver loaded
qspi0 freq (input): 91636363Hz
qspi0 freq ( bus ): 91636363Hz
[I] spinand_info_read()473 find raw ID efaa2200
[I] spinand_flash_init()524 Enabled BUF, HWECC. Unprotected.
nftl vol: data, size 0
09-24 13:45:53 I/sensor main: rt_sensor[temp_tsen_cpu] init success
09-24 13:45:53 I/WDT main: ArtInChip WDT loaded
CPU Exception: NO.1
x1(ra) : 00000000400361c6 x2(sp) : 00000000402de558 x3(gp) : 000000004026e530f
x5(t0) : 000000004026fef8 x6(t1) : 0000000000000002 x7(t2) : 00000000000000724
x9(s1) : 0000000040269670 x10(a0) : 0000000000000000 x11(a1) : 00000000000000008
x13(a3) : 00000000402a35a0 x14(a4) : 0000000066f25211 x15(a5) : 401f1830000000008
x17(a7) : 0000000000000064 x18(s2) : 00000000deadbeef x19(s3) : 00000000deadbeeff
x21(s5) : 00000000deadbeef x22(s6) : 00000000deadbeef x23(s7) : 00000000deadbeeff
x25(s9) : 00000000deadbeef x26(s10) : 00000000deadbeef x27(s11) : 00000000deadbeefa
x29(t4) : 0000000000000190 x30(t5) : 000000000000002d x31(t6) : 0000000000000000
mcause : 0000000000000001
mtval : 0000003000000000
mepc : 0000003000000000
mstatus : 8000000a00007880
其实就一个问题:烧录工具软件只支持Windows。可能是官方错认为大家都是在Windows下用虚拟机Linux做开发,实则在实体Linux下做开发的应该不在少数,MACOS也算类Linux环境。
RTOS SDK那边有Linux下的命令行烧录工具(upgcmd),但不稳定,且不支持指定分区烧录。
Linux SDK那边还没怎么研究,似乎没有Linux烧录工具。不过Linux下如果板子有网口很少在开发时直接烧录,而是通过网络共享的方式应用新固件或App,但没看到有文档介绍。板子没网口的话,也只能通过烧录来验证调试。
本人的开发环境是实体ManjaroLinux,目前用RTOS SDK做开发,SDK依赖库都能装上、源码编译无问题,唯独要时常开个Windows虚拟机来做烧录,肯定是不便利的。
目前手上有个带7寸显示屏和步进电机的新项目,用的是D21x单主控方案。步进电机只需控制AB点往返,但需要加减速控制,而Linux的PWM不支持细粒度控制,所以选了RTOS SDK进行开发,UI用AWTK。也是第一次用RT-Thread,但发现其代码质量和功能都要弱于Linux,后续还是打算切换到Linux,这就需要搞定Linux下的步进电机的加减速控制。
之前已经研究过播放wav音频 + 音频功放驱动步进电机是可行的,参见这里:
https://www.bilibili.com/video/BV1nj411D754
然而D21x的音频输出信号不是模拟信号而是PWM信号,是否可以参考上述方案用这个PWM实现step/dir接口的步进电机驱动?或者是否有其它更好的单主控驱动方案?
再次测试chart demo,可以肯定,是移植有问题:打开宏ENABLE_PERFORMANCE_PROFILE后,在demo主界面只有顶部app bar的时间在按秒更新,其它元素静止时,时间每更新一次就会有以下打印信息:
packages/third-party/awtk-ui/awtk/src/base/lcd_profile.c:322
-------------------------------------
total_cost=490
draw_image_cost=4 times=6
draw_text_cost=0 times=26
fill_cost=3 times=4
stroke_cost=0 times=0
end_frame_cost=4
-------------------------------------
其中 total_cost 指的是界面刷新耗时毫秒数,这里要490ms,太离谱!
上述调试说明参见这里: https://gitee.com/zlgopen/awtk/blob/master/docs/optimation.md#%E4%BA%8C%E5%B7%A5%E5%85%B7
根据上述信息可以得出UI性能弱鸡原因:
- 局部text更新触发整屏刷新
- 整屏刷新耗时半秒
每一条都是逆天般的存在!
再细看源码,LCD显存只开了双buffer没有开三buffer,AWTK官方说三buffer可以大大提高帧率。
像D21x这样的MPU HMI芯片只支持LVGL是不够的,LVGL的中文输入都是个问题,这个问题我另外发帖说明。
按上述改动后不知为何每次编译都更新compile_commands文件,导致编译时间变长好多。于是按下述修改,改为只有添加命令行参数时才更新:scons --cdb
# compilation database
AddOption('--cdb', dest='cdb', action='store_true', default=False, help='generate compilation database')
if GetOption('cdb'):
env.Tool('compilation_db')
compilation_db_file_name = PRJ_KERNEL + '_compile_commands.json'
env.CompilationDatabase(compilation_db_file_name)
又有新发现,scons本身就可以生成compile_commands.json文件。按下述diff修改SDK根目录上的SConstruct文件:
diff --git a/SConstruct b/SConstruct
index 9750ab30..a51b0846 100644
--- a/SConstruct
+++ b/SConstruct
@@ -102,6 +102,11 @@ env['ASCOM'] = env['ASPPCOM']
# signature database
env.SConsignFile(PRJ_OUT_DIR + ".sconsign.dblite")
+# compilation database
+env.Tool('compilation_db')
+compilation_db_file_name = PRJ_KERNEL + '_compile_commands.json'
+env.CompilationDatabase(compilation_db_file_name)
+
Export('RTT_ROOT')
Export('rtconfig')
- 编译boot时会生成baremetal_compile_commands.json
- 编译rt-thread时会生成rt-thread_compile_commands.json
那么搞两个项目Configuration分别叫boot和rt-thread分别各自使用上面的json db文件就可以灵活地对boot和rtos码字时进行完满的代码补全和阅读跳转了。
发现RTOS SDK里有这个API:aic_get_time_us,其实现就是读time寄存器,于是找到上面问题的原因了。
我用的芯片是D21x,其CSR寄存器time是64位的,所以此芯片应该没有timeh寄存器,只需读time寄存器即可。
那么,基本上只需两条指令就能获取分辨率为1us的时基,不错!
最后D21x上读取内核时基的函数如下,D13x等32位MCU就没那么便利了:要读取寄存器2次且需处理读取过程中的进位情况
static inline uint64_t _read_csr_time(void)
{
uint64_t value;
__asm__ __volatile__ ("csrr %0, time\n\t"
: "=r" (value) :
: "memory");
return value;
}
PS:RTOS SDK里d13x、d12x等读取系统时基的函数 aic_get_ticks,没有处理读取过程中可能出现的进位情况(读取时低32位寄存器发生32位进位),是否是一个BUG?!
u64 aic_get_ticks(void)
{
return (((u64)csi_coret_get_valueh() << 32U) | csi_coret_get_value());
}
@xdlkliang
感谢解答。我上面的需求说法有误,我要的是分辨率为1us,精度是us级(10us左右)的时基计时。
我测试了第二种方式,发现读取timeh寄存器时会再现cpu异常。寄存器读取函数如下:
static inline uint32_t _read_csr_time_lo(void)
{
uint32_t value;
__asm__ __volatile__ ("csrr %0, time\n\t"
: "=r" (value) :
: "memory");
return value;
}
static inline uint32_t _read_csr_time_hi(void)
{
uint32_t value;
__asm__ __volatile__ ("csrr %0, timeh\n\t"
: "=r" (value) :
: "memory");
return value;
}
RTOS SDK 1.0.5下经测试读寄存器time没有问题,读寄存器timeh时出现cpu异常:
CPU Exception: NO.2
x1(ra) : 00000000400338b8 x2(sp) : 000000004022d2c8 x3(gp) : 00000000401ba318 x4(tp) : 00000000deadbeef
x5(t0) : 00000000401bbef8 x6(t1) : 0000000000000001 x7(t2) : 00000000deadbeef x8(s0/fp): 0000000000000006
x9(s1) : 00000000400bb7e0 x10(a0) : 0000000000000001 x11(a1) : 000000004022d2e8 x12(a2) : ffffffff00000000
x13(a3) : 0000000040223228 x14(a4) : 0000000000000000 x15(a5) : 0000000000000000 x16(a7) : 0000000000000009
x17(a7) : 0000000040223228 x18(s2) : 0000000040223222 x19(s3) : 0000000040150570 x20(s4) : 00000000401829f8
x21(s5) : 0000000040150900 x22(s6) : 000000004015bd28 x23(s7) : 00000000401c2400 x24(s8) : 000000000000000d
x25(s9) : 0000000040223222 x26(s10) : 0000000040222dd0 x27(s11) : 00000000deadbeef x28(t3) : 0000000000000022
x29(t4) : 000000000000005c x30(t5) : 000000000000000a x31(t6) : 00000000deadbeef
mcause : 0000000000000002
mtval : 00000000c8102773
mepc : 00000000400bb7f0
mstatus : 8000000a00007880
什么问题?
按照10.1. Dynamic Module 使用指南生成的aic-dm-apps默认编译及运行测试都没问题。
在尝试测试C++编译,添加C++编译选项时编译失败:
编译配置文件 aic-dm-apps/hello/SConscript 添加 CXXFLAGS
from building import *
src = Glob('*.c') + Glob('*.cpp')
cwd = GetCurrentDir()
CPPPATH = [cwd]
CXXFLAGS = ' -std=c++11'
group = DefineGroup('', src, depend = [''], CPPPATH=CPPPATH, CXXFLAGS=CXXFLAGS)
Return('group')
编译输出:
$ scons --app=hello
scons: Reading SConscript files ...
args.outfile: /home/chenss/projects/test/source/luban-lite/partition_table.h
scons: done reading SConscript files.
scons: Building targets ...
CXX hello/cpp_test.o
riscv-none-embed-g++: error: -std=c++11: No such file or directory
scons: *** [hello/cpp_test.o] Error 1
scons: building terminated because of errors.
海石生风 说:PMOS开关电路,很常见呀。不过要注意VGS数值不要超过12V
https://whycan.com/files/members/1798/屏幕截图_20240722_122744.png这个电路有问题,这个GS上并一个这么大的电容,这个PMOS大负载的时候分分钟烧掉
GS并联电容是为了减小驱动容性负载导通时的瞬时电流,使其不超过MOS管的最大漏极电流,起到保护作用。实在想不出哪里会烧掉。
海石生风 说:PMOS开关电路,很常见呀。不过要注意VGS数值不要超过12V
https://whycan.com/files/members/1798/屏幕截图_20240722_122744.png这个电路有问题,这个GS上并一个这么大的电容,这个PMOS大负载的时候分分钟烧掉
什么原因烧的?
目前SDK里有命令行下载工具:upgcmd,此工具也可以通过构建系统来调用(scons --aicupg)。但我在D21x上测试发现很不稳定,只下载成功过一次。
Fatfs data 分区是支持读写的,需要一个nftl 做中间层,配置那里不要用自动计算
加了nftl中间层 "nftl": { "data": { "size": "-" } }后Fatfs data 分区确实是可读写的
将data分区选择为uffs格式在编译时会出现以下编译错误
是因为分区太小。你可以改大试试
我把uffs分区大小改为100MB后依然出现同样的编译错误:
page_2k_block_128k_oob_64_data.fs file_size: 0x6720000 is over much than part_size: 0x6400000
scons: *** [output/d21x_d213ecv-demo-v4_rt-thread_helloworld/images/d21x.elf] Error 1
另外,littlefs是否优于fatfs和uffs?是否应该着重支持该格式?
分区表为:
"spi-nand": { // Device, The name should be the same with string in image:info:media:type
"size": "128m", // Size of SPI NAND
"partitions": {
"spl": { "size": "1m" },
"env": { "size": "256k" },
"env_r": { "size": "256k" },
"os": { "size": "8m" },
"rodata": { "size": "16m" },
"data": { "size": "16m" }
}
}
将data分区选择为littlefs格式在烧录时会出现以下错误:
Firmware Component:
name: image.target.data
partition: data
attr: mtd;optional
Media: SPI_NAND(2)
[E] mtd_spinand_block_isbad()124 Offset: 0x1000000 is out of mtd size: 0x1000000.
[E] nand_fwc_mtd_write()348 Write block is bad, skip it.
[E] nand_fwc_mtd_write()320 Not enough space to write mtd data
并且RTOS内mount失败。
将data分区选择为uffs格式在编译时会出现以下编译错误:
page_2k_block_128k_oob_64_data.fs file_size: 0x1080000 is over much than part_size: 0x1000000
scons: *** [output/d21x_d213ecv-demo-v4_rt-thread_helloworld/images/d21x.elf] Error 1
data分区只有在格式为fat32时才能成功挂载,此格式在意外关机时容易出现错误,不能用于商用环境中的可读写的文件系统。
开发环境基于Eclipse或VS Code都没有啥问题,这是当前开源且功能丰富的工具的两大主流选择。问题是PlatformIO主要面向萌新,有点能力的老鸟用这个就很不搭调。
这问题其实跟MounRiver差不多,起初MounRiver为了迎合Keil/IAR开发者的口味,对Eclispe大肆裁减,企图将Ecipse改为Keil,这引起Eclipse开发者的不满。
于是就有非社区版和社区版之分,非社区版裁掉了很多Ecipse特色的实用功能就是个开源版的Keil,而社区版则没有功能裁减只是增加了WCH家的插件。
我个人认为,作为一个面向各种资历的开发者的工具,没必要为了迎合新人而降低工具的天花板,把新人的入门引导做好即可。
参照上面SEGGER那个非常有问题的测试对比文章,我用Zig写了类似的测试源码,其中两个测试结果:
$ zig build size --release -Dtarget=arm-freestanding-gnueabi -Dcpu=cortex_m23
text data bss dec hex filename
28828 16 0 28844 70ac /home/chenss/workspace/zig/code_size/zig-out/bin/code_size
$ zig build size --release -Dtarget=riscv32-freestanding-gnueabi -Dcpu=sifive_e34
text data bss dec hex filename
31326 232 0 31558 7b46 /home/chenss/workspace/zig/code_size/zig-out/bin/code_size
跑了多个测试发现RISC-V跟ARM的代码密码其实差不多(Zig对RISC-V32的支持要弱于ARM32)
详情参见这里:https://gitee.com/ufbycd/code_size
这个测试其实有问题,测试代码全部是调用math.h库函数。而不同编译器所用库是不同的,即本质上各个测试用例的测试代码并不全部相同。
不过上述方法有个大问题,因为很多编译相关的宏没有设置,导致工程里的很多符号没有被eclipse识别,这样写代码时很多东西不能自动补全,极不方便。
另一方面,按理来说,重复执行 scons --target=eclipse_sdk 后eclipse应该可以自动更新工程,并且应该不会的编译问题才对。
另一种工程管理方法:用VS Code配合bear(https://github.com/rizsotto/Bear)工具就可以检索复杂工程,用法如下:
* 先安装好bear工具,ArchLinux系统可以直接安装:sudo pacman -S bear
* 配合bear构建工程来生成clang的检索文件compile_commands.json:bear -- scons -j16
* 然后使用VS Code打开工程的文件夹即可,此时所有编译时用到的文件内的符号都能自动补全
* 工程因menuconfig更新后,需要重新执行第2部的bear构建来更新检索文件
海石生风 说:这个是有的,D13x显控一体。定时器多达26个。
如果是伺服驱动器应用,建议选择M6800系列,SDK是分开的。
那期待工业控制相关外设的文档及SDK能早日开放,我做的产品大多就是都包含屏显和电机控制的。
脚本:/etc/init.d/S20network
#!/bin/sh
#
# Start the network....
#
case "$1" in
start)
printf "Start dhcpc: "
/sbin/ifconfig lo up
/sbin/ifconfig eth0 up
udhcpc &
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
static)
printf "Set network IP: "
/sbin/ifconfig lo up
/sbin/ifconfig eth0 192.168.1.127 netmask 255.255.255.0 up
/sbin/route add default gw 192.168.1.1
echo -e "nameserver 202.96.134.133\nnameserver 202.96.128.166" > /etc/resolv.conf
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
stop)
printf "Bring down network: "
killall udhcpc
ifconfig lo down
ifconfig eth0 down
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
restart|reload)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
参考这个帖子 https://whycan.com/t_10597.html,解决DNS污染。实测github的clone速率为170.00 KiB/s左右,还可以接受。
@海石生风
元组是在编译时确定的,那怎么实现的状态diff和更新呢?还有UI元素的增减怎么实现的?比如根据条件显示一个label与否
zig只在编译时支持泛型和反射,所以只能在编译时确定声明。控件一般来说对用户是隔离的,用户通常只操作模型;控件状态的更新来源于与其绑定的模型。
需要UI元素增减的场景是List和TableView控件吧, 这种控件会绑定一个模型数组,用户对表内的UI元素进行声明,UI框架会在编译时依据声明来生成一个用于构建UI元素的函数,模型有变化时就调用这个构建函数。
目前这还是一个构思,这种操作能否实现,还有待研究;毕竟zig这个编译时特性在众多编程言语中是绝无仅有的。
这三个项目整理好了:将lvgl的C源码直接放入到zlvgl项目内,zlvgl和zdec两个项目分开管理而不使用git submodule。工程已经分别上传到gitee和github:
https://gitee.com/ufbycd/zlvgl
https://gitee.com/ufbycd/zdec
https://github.com/ufbycd/zlvgl
https://github.com/ufbycd/zdec
lvgl的意思是lv_obj_add_event_cb就是列表里加个函数指针 根本不会 fail 不过列表如果静态的会不会占好大地方 如果动态满了怎么办
lv_obj_add_event_cb内部会有动态内存分配,只有一个fail原因,那就是内存分配失败。
后面看了不少C代码,lvgl对于内存分配失败都是只用LV_ASSERT_MALLOC触发断言而没有在返回值上体现来处理的,跟我之前所用的UI库处理习惯上有点不同。
而zig这边因为有完善的错误处理机制,一个函数返回OOM(Out Of Memory)错误是很常见的。zig提倡由调用者决定什么时候处理错误,这就可以让软件更健壮,不会一触发OOM就因ASSERT断言而死掉。
海石生风 说:现在都流行提问说一半不说一半让大家猜谜吗?
atof("000000003.1")的结果为0
你单个写个C源码测试,结果是正确的吧。是你实际调用有问题,不是atof("000000003.1")问题。
提问最好要把怎么使用的细节列出来。不要一开始就怀疑一个很多人在用且用了很多年的C库有问题。
#include <stdio.h>
#include <stdlib.h>
int main()
{
double d = atof("000000003.1");
printf("d = %f\n", d);
return 0;
}
PS:标准的C库atof返回的是一个double,printf的"%f"接受的是一个double,其他C库特别是MCU平台的C库要另行确认是否有不同。
借鉴Flutter,不使用编程语言之外的标记语言而是使用编程语言本身来实现UI描述。这就要求编程语言支持泛型和反射特性。目前支持这两个特性又可以用于MCU平台的语言就只有Rust跟Zig了,但Rust太复杂了用在MCU平台大材小用,故选择Zig。Zig惟一的问题是目前还远没达到1.0版本,但当前v0.11版本的实用性也不错了。
项目命名为"zdec",“z”取自Zig,“dec”取自英文“声明”的前三个字母;而在汇编语言上一般用“dec”指令表示减法,所以"dec"也有在UI实现上做减法从而方便使用的意思。
目前已初步实现大体框架,实现了控件的创建/属性初始化和命令/属性的绑定。
UI构建方式如下:
const main_ui = .{
.{
d.Id.Button,
d.Size{ .width = 160, .height = 48 },
d.Align{ .lv_align = .Center, .y_ofs = -100 },
d.Text{ .text = "button" },
struct {
user_data: *Model,
pub fn onClicked(event: anytype) void {
const the_model = event.userData();
const step = 10;
std.debug.print("{s}: add Model.count by {d}\n", .{ @typeName(@TypeOf(event.target())), step });
the_model.add(step);
}
}{ .user_data = &_model },
},
.{
d.Id.Slider,
d.Size{ .width = 240, .height = 16 },
d.Align{ .lv_align = .Center, .y_ofs = 100 },
d.Range{ .min = 0, .max = 200 },
d.Bind(d.BindType.Value, @TypeOf(_model.count)){ .property = &_model.count },
},
};
var widget = try d.buildUI(lv.Screen.active(), main_ui);
效果是这样的:点击button,Slider的游标就会变化
上述例子的完整代码在这里:https://gitee.com/ufbycd/zdec/blob/dev/zdec/example.zig
项目主页在这里:https://gitee.com/ufbycd/zdec
目前只支持Linux,并使用SDL2作为后端来显示窗口。
海石生风 说:我以为你是说按Tab键滑动,理解错了。
让Table控件接收KEY事件,在事件处理函数里调用API滑动就行了。没理解是什么意思
事件处理函数就是丢给lv_obj_add_event_cb的那个回调函数,你的用法不太对,改成如下:
lv_obj_add_event_cb(DataStream_table_1, DataStream_table_1_event_handler, LV_EVENT_KEY, DataStream_table_1);
// 那么DataStream_table_1就存储在lv_event_t的user_data里,就可以在回调函数里取出来使用:
void DataStream_table_1_event_handler(lv_event_t * e) {
lv_table_t *table = e->user_data;
// 使用table来调用Table的API来滑动
}
我最近启动了一个项目:实现一个应用于嵌入式的声明式UI框架: https://gitee.com/ufbycd/zdec
用Zig语言编写,底层基于LVGL进行绘图。我用过emWin、AWTK、QT(widget/qml/pyqt)、 wxWidgets,但我之前对LVGL只是耳闻并在PC上跑了下Demo,实际项目从来没用过。
在实现命令绑定时发现LVGL的事件处理函数的添加和删除的API好丑呀。
lv_obj_add_event_cb的返回值在V8版本已经标记为遗弃。而在V9版本则没有返回值了,API调用到底是成功还是失败,用户不知道!太不严谨了。
并且不能通过lv_obj_add_event_cb返回的东西来删除处理函数了。这对于实现一个声明式UI框架很不友好!
AWTK相应的API是这样的:
uint32_t widget_on(widget_t* widget, uint32_t type, event_func_t on_event, void* ctx);
ret_t widget_off(widget_t* widget, uint32_t id);
用法是这样的:
// 添加
uint32_t event_id = widget_on(widget, EVT_CLICK, callback, ctx);
if(event_id == TK_INVALID_ID) {
// 失败处理
}
// 删除
widget_off(widget, event_id);
LVGL的版本号都这么大了,大家都没有意见吗?我打算到github上提下意见。
另外,将来空闲时也会写点这个Zig项目的心得。
RT-Thread团队在Smart内核上对接了Linux的DRM (Direct Rendering Manager)并移植了wayland从而可以在RT-Thread通过DRM后端来跑weston合成器(显示服务器)。将来在RT-Thread上移植GUI将非常简单容易了!
详情参见官方新闻: https://www.rt-thread.org/newsDetail.html?id=cb5491b3c0088c80
@EE
起初是裸机,由于对可靠性要求极高,于是最起码要对不同任务进行内存地址隔离。隔离后,不同任务间要通信,就是要做通信组件;还要监控不同任务是否正常运行,如果异常就要做最小损失处理。还要对整个任务系统进行备份(我国当前的航天器已经做3套备份了)。
这些东西做下来,就不由自主地变成一个OS了。
顺便一提,经常飞越星际的VxWorks已经支持“容器”这个虚拟系统的概念了,将实时性要求不高又容易出错的任务跑在容器里,以隔离其对整个系统的影响。
航空航天的可靠性要求不是民用的能比的,而航空航天系统又越来越复杂,肯定要对硬件资源进行管理,这一管理就变成一个OS了。
这不是为了OS而上OS,而是无奈地成为了OS。
在vectos_stm32f103xb.c里有定义向量表__isr_ectors[],在sections.ld里把向量表定义在flash 0x08000000位置,stm32上电后从这里运行即可。startup.s是st库里常用写法,但不是唯一方法
本质不单单是中断向量表,而是启动代码,即执行main函数之前的初始化.DATA段和.BSS段的代码是用C语言写的。
之所以能用C语言实现启动代码,是因为cortex-m的中断向量表的前4个字节的值为栈指针位置。MCU启动时先从中断向量表获取栈指针位置,确定了栈位置C语言的运行环境就初始化OK了,就可以调用C函数了。
至于中断向量表的定位是通过链接脚本指定的:
先在C语言里指定中断向量表这个数组所处的段(section)
__attribute__ ((section(".isr_vector"),used))
pHandler __isr_vectors[] =
{
(pHandler) &_estack, // The initial stack pointer
Reset_Handler, // The reset handler
...
};
再在链接脚本里指明这个段的链接位置:
MEMORY
{
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
}
SECTIONS
{
/*
* For Cortex-M devices, the beginning of the startup code is stored in
* the .isr_vector section, which goes to FLASH.
*/
.isr_vector : ALIGN(4)
{
FILL(0xFF)
KEEP(*(.isr_vector)) /* Interrupt vectors */
} >FLASH
链接脚本里的内容是按出现的先后顺序排列的,isr_vector 写在最前所以就链接在FLASH起始位置。
@lfs911
见 https://whycan.com/t_10374.html 这里有运行日志
Startup time: 0.456 sec
这是跑的rtthread的demo
程序是是在哪跑的,Flash、SRAM还是PSRAM?
http://science.china.com.cn/2023-11/24/content_42608233.htm
今天看到这个消息,来转发一下。
hp5301, 8元,300M,没can,16位adc,开发板40元。看了下sdk,gitee上有,rtos开发。
顶一下,HPM5300系列芯片有针对电机控制的运动控制系统,非常强大。如:
旋变解调器:根据电机的两路线圈的电流信号评估转子位置
支持串行口编码器;如 磁编码器MT6701的SPI接口,配置好寄存器后无需CPU干预就能定时获取编码器数值
运动处理单元:根据输入的电机位置值来预测电机的位置、速度、加速度
其它还有强点还有:双精度FPU、64位定时计数器、纳秒计数器、32位编码器计数器、编码器输出、CAN FD、运算放大器OPAMP、两个16位ADC等就不一一列举了,感觉兴趣的同学可以看芯片手册深入了解。
海石生风 说:tomyqg 说:@海石生风
听说c906的浮点也是个残废啊哪里道听途说的,RVV + FPU 的浮点性能怕是跟ARM9比得差两个量级
我看了下,大意是说C910的FPU少了个寄存器标志位,没有完全兼容IEE-754。
但说它“残废”,就等于说它没什么用处,就是谣言了。
音频功放驱动实现步进电机的加减速及正反转控制,视频请移步B站:https://www.bilibili.com/video/BV1nj411D754/
生成wav波形文件的python脚本如下:
#!/usr/bin/env python
import numpy as np
import scipy.signal as signal
import wave
freq_min = 10
freq_max = 50
shifting_seconds = 0.5 # 加速或减速时长
con_seconds = 1.5 # 匀速时长
SAMPLE_RATE = 48000 # 采样率
def genwave(f0, f1, seconds, phase_diff=90):
t = np.arange(0, seconds, 1.0/SAMPLE_RATE)
vleft = signal.chirp(t, f0=f0, f1=f1, t1=seconds, method='linear')
vright = signal.chirp(t, f0=f0, f1=f1, t1=seconds, method='linear', phi=phase_diff)
left_scale = vleft * 32000
right_scale = vright * 32000
left_data = left_scale.astype(np.int16)
right_data = right_scale.astype(np.int16)
cat_data = np.array([left_data, right_data])
frame_data = np.transpose(cat_data)
return frame_data
def genaction(f_min, f_max, shifting_t, con_t, is_forward=True):
phase = 90 if is_forward else -90
acc = genwave(f_min, f_max, shifting_t, phase)
con = genwave(f_max, f_max, con_t, phase)
dec = genwave(f_max, f_min, shifting_t, phase)
action = np.concatenate((acc, con, dec))
return action
def genround(f_min, f_max, shifting_t, con_t):
forward = genaction(f_min, f_max, shifting_t, con_t, True)
backward = genaction(f_min, f_max, shifting_t, con_t, False)
return np.concatenate((forward, backward))
# frames = genaction(freq_min, freq_max, shifting_seconds, con_seconds, False)
frames = genround(freq_min, freq_max, shifting_seconds, con_seconds)
seconds = shifting_seconds*2 + con_seconds
fname = "{}~{}Hz-{}s.wav".format(freq_min, freq_max, seconds)
wf = wave.open(fname, "wb")
wf.setnchannels(2) # 2声道
wf.setsampwidth(2) # 采样宽(字节)
wf.setframerate(SAMPLE_RATE)
wf.setcomptype('NONE','not compressed') # 设置采样格式:无压缩
wf.writeframes(frames.tobytes())
wf.close()
print("generated: {}".format(fname))
优化双声道数据的合并
#!/usr/bin/env python
import numpy as np
import scipy.signal as signal
import wave
freq0 = 10 # 信号起始频率(Hz)
freq1 = 50 # 信号终止频率(Hz)
seconds = 20 # 音频时长(秒)
sample_rate = 4800 # 采样率
t = np.arange(0, seconds, 1.0/sample_rate)
vleft = signal.chirp(t, f0=freq0, f1=freq1, t1=seconds, method='linear')
vright = signal.chirp(t, f0=freq0, f1=freq1, t1=seconds, method='linear', phi=90)
# import matplotlib.pyplot as plt
# plt.plot(t, vleft)
# plt.plot(t, vright)
# plt.show()
# exit()
left_scale = vleft * 32000
right_scale = vright * 32000
left_data = left_scale.astype(np.int16)
right_data = right_scale.astype(np.int16)
cat_data = np.array([left_data, right_data])
frame_data = np.transpose(cat_data)
fname = "{}~{}Hz-{}s.wav".format(freq0, freq1, seconds)
wf = wave.open(fname, "wb")
wf.setnchannels(2) # 2声道
wf.setsampwidth(2) # 采样宽(字节)
wf.setframerate(sample_rate)
wf.setcomptype('NONE','not compressed') # 设置采样格式:无压缩
wf.writeframes(frame_data.tobytes())
wf.close()
print("generated: {}".format(fname))
突发奇想,D类音频功放是用两个H桥来驱动两线圈,电磁驱动原理跟步进电机驱动是一样的。那么能否用D类音频功放直接驱动步进电机呢?
只要在左右声道分别输出相位差是90度的正弦波应该就可以驱动电机了。于是说干就干,立马淘了TPA3116D2功放板来实验。
音频功放确实能够驱动步进电机。但经测试发现驱动特性比较一般,抖动挺大,信号频率到100Hz左右电机就转不动。
视频可移步B站:https://www.bilibili.com/video/BV1MW4y1Q7kP/
用于生成驱动信号的wav文件的python如下,感兴趣的同学也可以尝试下。
#!/usr/bin/env python
import numpy as np
import scipy.signal as signal
import wave
from array import array
freq0 = 20 # 信号起始频率(Hz)
freq1 = 20 # 信号终止频率(Hz)
seconds = 20 # 音频时长(秒)
sample_rate = 4800 # 采样率
t = np.arange(0, seconds, 1.0/sample_rate)
vleft = signal.chirp(t, f0=freq0, f1=freq1, t1=seconds, method='linear')
vright = signal.chirp(t, f0=freq0, f1=freq1, t1=seconds, method='linear', phi=90)
# import matplotlib.pyplot as plt
# plt.plot(t, vleft)
# plt.plot(t, vright)
# plt.show()
# exit()
left_scale = vleft * 32000
right_scale = vright * 32000
left_data = left_scale.astype(np.int16)
right_data = right_scale.astype(np.int16)
frame_data = array('h')
for i in range(len(left_data)):
frame_data.append(left_data[i])
frame_data.append(right_data[i])
fname = "{}~{}Hz-{}s.wav".format(freq0, freq1, seconds)
wf = wave.open(fname, "wb")
wf.setnchannels(2) # 2声道
wf.setsampwidth(2) # 采样宽(字节)
wf.setframerate(sample_rate)
wf.setcomptype('NONE','not compressed') # 设置采样格式:无压缩
wf.writeframes(frame_data.tobytes())
wf.close()
print("generated: {}".format(fname))
海石生风 说:发现 灵动 的方案还有带预驱和MOSFET管的,集成5v LDO最高16V供电,完全单芯片解决方案,电路极其简洁!一个MCU加几个阻容器件搞定BLDC FOC!
有没有型号呢 我去看看
https://mindmotion.com.cn/products/mm32mcu/mm32spin/mm32spin_driver_mcu/
三个型号都带MOSFET管和高压LDO,只有MM32SPIN422C带运放。
slint发展得挺快的,昨天即4月3号就在官网“announce version 1.0”。个人比较看好这个UI库,基于rust还是很有前途的:C太简单,C++又太复杂。但愿嵌入式领域能有个趁手GUI。
发布详情见这里: https://slint-ui.com/blog/announcing-slint-1.0.html
120通道逻辑分析仪
https://github.com/gusmanb/logicanalyzer
树莓派单片机24通道100Msps逻辑分析仪提升至支持120通道。
支持了菊花链方式级联五个设备,从而允许捕获120个通道。包括渲染引擎的性能改进和可见性改进、屏幕中的更多样本、自动选择捕获模式、编辑功能等。
摘自:硬汉嵌入式 https://www.bilibili.com/read/cv22245829?spm_id_from=333.999.0.0 出处:bilibili
有满足我需求的型号,感谢回复。