开发环境基于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?