海石生风 说:这个是有的,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))