自从用了 cursor 之类的AI软件,基本不些写代码了,有bug也不看代码,直接把bug描述出来,让AI继续改。
要是勤快一点,一天烧个100刀随随便便。
https://whycan.com/files/members/7106/ScreenShot_2026-03-31_211058_600.png
一天100刀,那岂不是等于给AI打工了?!
rtos启动速度200ms应该是非XIP启动的耗时,不过这个XIP启动我也没有深究。
D13x的SDK是全公开的,在这里: https://gitee.com/artinchip/luban-lite
@海石生风
那部分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] 错误 2bootloader主程序在 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管的最大漏极电流,起到保护作用。实在想不出哪里会烧掉。