Sullivan's BLOG

琐碎,定格,在南方

全志t113在主线上的debian,rgb屏幕和触摸驱动

#全志t113在主线上的debian,rgb屏幕和触摸驱动
闲来无事折腾一下linux,对于全志所有芯片的主线支持情况在 sunxi-wiki 上有着详细的记录,可以看出社区在linux6.0之后对D1/D1S/T113系列芯片的支持做的很不错。

##编译主线u-boot,zimage
预装编译环境

1
apt install gcc-arm-linux-gnueabihf debootstrap

先创建一个文件夹,并下载好对应的库

1
2
3
4
5
6
mkdir linux
mkdir bin # 用于存放二进制文件
mkdir mdir # 用于挂载
cd linux
git clone --depth=1 https://github.com/u-boot/u-boot
git clone --depth=1 https://github.com/torvalds/linux

创建一个 boot.cmd 脚本

1
2
3
4
setenv bootargs  mem=128M cma=72M console=ttyS3,115200 root=/dev/mmcblk0p2 rootwait panic=10
fatload mmc 0:1 0x43000000 ${fdtfile} || load mmc 0:1 0x43000000 boot/${fdtfile}
fatload mmc 0:1 0x42000000 zImage || load mmc 0:1 0x42000000 boot/zImage
bootz 0x42000000 - 0x43000000

创建一个 build.sh 脚本,用于方便提取所需文件

1
2
3
4
5
6
7
8
9
10
11
12
13
cd u-boot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- mangopi_mq_r_defconfig
make -j6 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- DEVICE_TREE=sun8i-t113s-mangopi-mq-r-t113
cd ../linux
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- sunxi_defconfig
make -j6 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage dtbs modules
cd ..

cp u-boot/u-boot-sunxi-with-spl.bin bin/
cp linux/arch/arm/boot/zImage bin/
cp linux/arch/arm/boot/dts/allwinner/sun8i-t113s-mangopi-mq-r-t113.dtb bin/

mkimage -C none -A arm -T script -d boot.cmd bin/boot.scr

编译时只需要source build.sh 就可以在bin中可以得到所需文件,使用fdisk对sd卡进行分区,先按d删除所有区,再按n新建一个分区,用来存放zimage dtb之类的文件,偏移输入8192,大小输入+16M,再按n新建一个分区,用来存放linux文件系统,建议大小大于1G,偏移量输入40960,再按t进行分区,第一区为0b,第二区为83,最后按w退出。
格式化所有分区

1
2
sudo mkfs.fat /dev/sda1
sudo mkfs.ext4 /dev/sda2

编写一个writeboot.sh,用于将所image和uboot刷入sd卡

1
2
3
4
5
6
7
sudo dd if=bin/u-boot-sunxi-with-spl.bin of=/dev/sda bs=1024 seek=8
sudo mount /dev/sda1 mdir
sudo cp zImage mdir/
sudo cp sun8i-t113s-mangopi-mq-r-t113.dtb mdir/
sudo cp boot.scr mdir/
sudo cp uboot.env mdir/
sudo umount /dev/sda1 mdir

接下来source writeboot.sh 就可以将sd卡插入t113验证uboot和kernel是否工作,由于还没有文件系统,会在最后卡住。
##debootstrap创建debian文件系统
先创建一个用于挂载的ch-mount.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash

function mnt() {
echo "MOUNTING"
sudo mount -t proc /proc ${2}/proc
sudo mount -t sysfs /sys ${2}/sys
sudo mount -o bind /dev ${2}/dev
sudo mount -o bind /dev/pts ${2}/dev/pts
sudo chroot ${2}
}

function umnt() {
echo "UNMOUNTING"
sudo umount ${2}/proc
sudo umount ${2}/sys
sudo umount ${2}/dev/pts
sudo umount ${2}/dev

}

if [ "$1" == "-m" ] && [ -n "$2" ] ;
then
mnt $1 $2
elif [ "$1" == "-u" ] && [ -n "$2" ];
then
umnt $1 $2
else
echo ""
echo "Either 1'st, 2'nd or both parameters were missing"
echo ""
echo "1'st parameter can be one of these: -m(mount) OR -u(umount)"
echo "2'nd parameter is the full path of rootfs directory(with trailing '/')"
echo ""
echo "For example: ch-mount -m /media/sdcard/"
echo ""
echo 1st parameter : ${1}
echo 2nd parameter : ${2}
fi

使用debootstrap创建文件系统并挂载进sda2

1
2
3
sudo mount /dev/sda2 mdir/
sudo debootstrap --arch=armhf sid mdir https://mirrors.tuna.tsinghua.edu.cn/debian/
chmod 777 ch-mount.sh

接下来就是初始化配置下载一些库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export LC_ALL=en_US.UTF-8
apt-get install locales
dpkg-reconfigure locales
# 选 97 enus
# 选 3

apt install sudo ssh openssh-server net-tools ethtool wireless-tools network-manager iputils-ping \
rsyslog alsa-utils bash-completion gnupg busybox kmod wget curl nano neofetch \
libdrm2 libdrm-common drm-info glmark2-drm libdrm-dev libegl1-mesa-dev libegl-mesa0 libegl1 \
i2c-tools \
# 下载库

cat <<EOF > /etc/hosts #ss
127.0.0.1 localhost
127.0.1.1 $HOST
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
mkdir -p /etc/network
cat >/etc/network/interfaces <<EOF
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
cat >/etc/resolv.conf <<EOF
nameserver 1.1.1.1
nameserver 8.8.8.8
EOF
cat >/etc/fstab <<EOF
#<file system> <mount point> <type> <options> <dump> <pass>
/dev/mmcblk0p1 /boot vfat defaults 0 0
/dev/mmcblk0p2 / ext4 defaults,noatime 0 1
EOF

passwd # 设置你的密码

echo sullivan_project > /etc/hostname
exit
./ch-mount.sh -u mdir

至此,你的debian系统就设置完成了,如国你想保存下来,只需sudo tar -cvf t113-debian.tar mdir/,接下来将sd卡插入t113,就可以进入debian系统使用了,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@sullivanproject:~# neofetch
_,met$$$$$gg. root@sullivanproject
,g$$$$$$$$$$$$$$$P. --------------------
,g$$P" """Y$$.". OS: Debian GNU/Linux trixie/sid armv7l
,$$P' `$$$. Host: MangoPi MQ-R-T113
',$$P ,ggs. `$$b: Kernel: 6.11.0-rc6-g67784a74e258-dirty
`d$$' ,$P"' . $$$ Uptime: 35 mins
$$P d$' , $$P Packages: 478 (dpkg)
$$: $$. - ,d$$' Shell: bash 5.2.32
$$; Y$b._ _,d$P' Terminal: /dev/ttyS3
Y$$. `.`"Y$$$$P"' CPU: Generic DT based system (2)
`$$b "-.__ Memory: 35MiB / 112MiB
`Y$$
`Y$$.
`$$b.
`Y$$b.
`"Y$b._
`"""

##drm驱动和input子系统
t113使用sun4i的drm驱动,已在debian中通过apt下载好,如果在譬如busybox中则需使用mesa源码交叉编译,mesa库编译环境挺麻烦的,建议还是能用包管理器下载就用包管理器下载。
修改位于linux/arch/arm/boot/dts/allwinner/sun8i-t113s-mangopi-mq-r-t113.dts的设备树,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
// Copyright (C) 2022 Arm Ltd.

#include <dt-bindings/interrupt-controller/irq.h>

/dts-v1/;

#include "sun8i-t113s.dtsi"
#include "sunxi-d1s-t113-mangopi-mq-r.dtsi"

/ {
model = "MangoPi MQ-R-T113";
compatible = "widora,mangopi-mq-r-t113", "allwinner,sun8i-t113s";

aliases {
ethernet0 = &rtl8189ftv;
};

panel {
compatible = "bananapi,s070wv20-ct16", "simple-panel";
port {
panel_input: endpoint {
remote-endpoint = <&tcon_lcd0_out_panel>;
};
};
};

wifi_pwrseq: wifi-pwrseq {
compatible = "mmc-pwrseq-simple";
clocks = <&ccu CLK_FANOUT1>;
clock-names = "ext_clock";
reset-gpios = <&pio 6 12 GPIO_ACTIVE_LOW>; /* PG12 */
assigned-clocks = <&ccu CLK_FANOUT1>;
assigned-clock-rates = <32768>;
pinctrl-0 = <&clk_pg11_pin>;
pinctrl-names = "default";
};
};

&de {
status = "okay";
};

&tcon_lcd0 {
pinctrl-names = "default";
pinctrl-0 = <&lcd_rgb666_pins>;
status = "okay";
};

&tcon_lcd0_out {
tcon_lcd0_out_panel: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_input>;
};
};

&pio {
i2c2_pe12_pins: i2c2-pe12-pins {
pins = "PE12", "PE13";
function = "i2c2";
};
};

&i2c2 {
pinctrl-0 = <&i2c2_pe12_pins>;
pinctrl-names = "default";
status = "okay";

gt911@5d {
compatible = "goodix,gt911";
reg = <0x5d>;
interrupt-parent = <&pio>;
interrupts = <1 3 IRQ_TYPE_EDGE_FALLING>;

irq-gpios = <&pio 1 3 GPIO_ACTIVE_HIGH>;
reset-gpios = <&pio 1 2 GPIO_ACTIVE_HIGH>;

VDD-supply = <&reg_3v3>;
status = "okay";
};
};

&usb_otg {
dr_mode = "otg";
status = "okay";
};

&usbphy {
status = "okay";
};

&ehci0 {
status = "disabled";
};

&ohci0 {
status = "disabled";
};

&ehci1 {
status = "okay";
};

&ohci1 {
status = "okay";
};

&cpu0 {
cpu-supply = <&reg_vcc_core>;
};

&cpu1 {
cpu-supply = <&reg_vcc_core>;
};

&mmc1 {
bus-width = <4>;
mmc-pwrseq = <&wifi_pwrseq>;
non-removable;
vmmc-supply = <&reg_3v3>;
vqmmc-supply = <&reg_3v3>;
pinctrl-0 = <&mmc1_pins>;
pinctrl-names = "default";
status = "okay";

rtl8189ftv: wifi@1 {
reg = <1>;
interrupt-parent = <&pio>;
interrupts = <6 10 IRQ_TYPE_LEVEL_LOW>; /* PG10 = WL_WAKE_AP */
interrupt-names = "host-wake";
};
};

panel 驱动使用你实际屏幕的参数,再次修改linux/t113-s3/linux/arch/arm/configs/sunxi_defconfig向其中添加

1
2
CONFIG_EXPERT=y
CONFIG_TOUCHSCREEN_GOODIX=y

重新编译烧录,在命令行中使用glmark2-drm即可看到熟悉的小马
image
当然,这个测试是跑不完的,因为t113s3只有128m内存,lvgl有一个drm驱动直接可以使用drm显示。

zephyr 系统服务(一) ZBUS

zephyr 系统服务(一) ZBUS

前言

最近在做了一个比较复杂的项目,需要一个软件总线作为不同的模块之间的通讯方式,最后采用了Zephyr总线(ZBUS),ZBUS是ephyr中一个自带的系统服务,这个软件总线无论是名字还是文档和例程的详细度质量都远远优于其他内容,一看就是亲儿子。

由于zephyr的历史和背景原因,很多核心内容都是向linux看齐的, 对于linux内核而言,软件总线主要体现在进程间通信,像管道、信号/信号灯、共享内存、消息队列和socket这些比较基础的,又或是xBus,dbus这样重型的总线,难以使用在RTOS上,更何况zephyr内核所含的工作队列和data passing已经足够构建基础的通信机制, 因此ZBUS的诞生就是为了解决RTOS下没有方便使用的软总线的问题。

介绍

如果之前有学习过mqtt的话,会发现ZBUS 的整个模型和mqtt是非常相似的,使用者分为发布者和观察者,观察者又分为三类:

  1. 监听者(Listeners):总线发布消息后立即触发回调函数处理信息。
  2. 订阅者(Subscriber):在自定义的线程里调用函数主动去等待信息的发布,为阻塞行为。
  3. 消息订阅者(Message subscribers):总线发布消息后会将消息的拷贝传递至fifo里,在自定义的线程里调用函数主动去查询fifo内容,为异步非阻塞行为。

使用

首先我们需要定义通讯数据格式结构体,这里我定义一个电机数据结构体motor_data_msg

1
2
3
4
5
6
struct motor_data_msg
{
uint32_t motor_id;
float speed;
float position;
};

使用ZBUS_CHAN_DEFINE定义ZBUS总线

1
2
3
4
5
6
7
ZBUS_CHAN_DEFINE(motor_chan,                                                                /* 总线名字*/
motor_data_msg, /* 总线数据类型 */
motor_data_validator, /* 验证器(在回调函数里处理信息)*/
NULL, /* User data */
ZBUS_OBSERVERS(motor_data_recv), /* 观察者 */
ZBUS_MSG_INIT(.motor_can_id = motor_id::C12B, .speed = 0, .position = 0, ) /* 初始化信息 */
);

验证器函数主要用于处理发布者发布的信息,通过返回true或false过滤无效信息,这里我假设速度大于30的为无效信息,返回false后观察者不会接收到该信息。

1
2
3
4
5
6
7
8
9
bool motor_data_validator(const void *msg, size_t msg_size)
{
motor_data_msg *cm = (motor_data_msg *)msg;
if (cm->speed > 30)
{
return false;
}
return true;
}

一般来说不会使用监听者(Listeners),因为如果需要用回调函数去处理接收数据的话还得思考回调函数与各模块之间的通信问题,相当于根本就没有解决各模块之间的通信问题,这里我们采用订阅者(Subscriber)在一个新线程(模块)里去收集数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ZBUS_SUBSCRIBER_DEFINE(motor_data_recv, 4); // 定义并初始化订阅者,4为消息队列数量
int main()
{
const zbus_channel *chan;
motor_data_msg msg;
zbus_obs_attach_to_thread(&motor_data_recv); //设定该观察者的优先度

while (!zbus_sub_wait(&motor_data_recv, &chan, K_FOREVER)) // 使用zbus_sub_wait()函数等待消息发布
{
if (&motor_chan == chan)
{
zbus_chan_read(&motor_chan, &msg, K_NO_WAIT); // 当消息发布后使用zbus_chan_read()函数读取消息储存在msg中
LOG_INF("recv: id: %d", static_cast<int>(msg.motor_can_id));
LOG_INF("recv: speed: %f", msg.speed);
LOG_INF("recv: position: %f", msg.position);
}
}
}

相比订阅者,消息订阅者(Message subscribers)省去了zbus_chan_read这个函数,当消息发布时就被拷贝到了消息队列中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ZBUS_MSG_SUBSCRIBER_DEFINE(motor_data_recv); // 定义并初始化消息订阅者
int main()
{
const zbus_channel *chan;
motor_data_msg msg;

zbus_obs_attach_to_thread(&motor_data_recv);//设定该观察者的优先度

while (!zbus_sub_wait_msg(&motor_data_recv, &chan, &msg, K_FOREVER))
{
if (&motor_chan == chan)
{
zbus_chan_read(&motor_chan, &msg, K_NO_WAIT); //直接储存在msg里
LOG_INF("recv: speed: %f", (double)msg.speed);
LOG_INF("recv: position: %f", (double)msg.position);
}
}
}

接下来我们定义一个测试线程每隔一秒发布一次数据

1
2
3
4
5
6
7
8
9
10
static void test_thread(void *, void *, void *)
{
while (true)
{
motor_data_msg acc1 = {.motor_can_id = motor_id::C12B, .speed = 1.0, .position = 2.0};
zbus_chan_pub(&motor_chan, &acc1, K_SECONDS(1));
k_sleep(K_SECONDS(1));
}
}
K_THREAD_DEFINE(test_thread_tid, 1024, test_thread, NULL, NULL, NULL, 10, 0, 0);

不要忘记改一下kconfig

1
2
3
4
CONFIG_ZBUS=y
CONFIG_ZBUS_LOG_LEVEL_INF=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_ZBUS_MSG_SUBSCRIBER=y

接下来可以看到输出

1
2
3
4
5
6
7
8
9
10
*** Booting Zephyr OS build v3.7.0-761-g0c7e87714d92 ***
[00:00:00.208,000] <inf> main: recv: id: 0
[00:00:00.208,000] <inf> main: recv: speed: 0.000000
[00:00:00.208,000] <inf> main: recv: position: 0.007888
[00:00:01.208,000] <inf> main: recv: id: 0
[00:00:01.208,000] <inf> main: recv: speed: 0.000000
[00:00:01.208,000] <inf> main: recv: position: 0.007888
[00:00:02.208,000] <inf> main: recv: id: 0
[00:00:02.208,000] <inf> main: recv: speed: 0.000000
[00:00:02.208,000] <inf> main: recv: position: 0.007888

注意事项

使用zbus_sub_wait一定要注意超时时间,多个观察者之间的调度会因为优先级次序出现问题,如果有多个订阅者使用了zbus_obs_attach_to_thread重新定义优先级,使用K_NO_WAIT进行读取很可能会出现其他观察者霸占着ZBUS而出现超时,使用时需仔细考虑。

参考文档.

现代C++:形参包的解包

前言

很多人从传统角度认为,在嵌入式中使用C++模板会导致代码膨胀(特别是STL),在flash紧张的嵌入式系统中是没法使用的,但实际上并非如此,比如像封装底层驱动驱动,零成本的抽象,基于C++的模板对RTOS的进行封装,可变参数模板,还可以进行编译期检查,十分的方便

形参包

模板形参包是接受零个或更多个模板实参(非类型、类型或模板)的模板形参。函数形参包是接受零个或更多个函数实参的函数形参。至少有一个形参包的模板被称作变参模板,对于形参包的解包方法随着C++的发展也有了非常多的变化,下面介绍一下我最近使用的一些套路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <gpio_num_t num> struct z_gpio
{
int real_addr = 1ULL << num;
static inline void set()
{
gpio_set_level(num, 1);
}

static inline void clr()
{
gpio_set_level(num, 0);
}
};
template <gpio_num_t num> using z_pin = z_gpio<num>;
using GP1 = z_pin<GPIO_NUM_1>;

// use
GP1::set();

上面的代码对gpio的操作进行了一个简单的封装,但是在实际工程中初始化时,需要对多个GPIO进行初始化,所以我们需要以可变模板的形式对其进行封装。

1

基于vue3的 web serial 串口调试工具记录

前言

因为参加了全国大学生嵌入式芯片与系统设计竞赛的沁恒赛道,需要做一个用来调试的上位机,在用qt撸的时候突然看到了一个可以直接下载固件到 esp32 的网页,没想到现在前端居然都可以直接控制硬件了,于是我翻了一下w3c和vue的文档,打算自己用 web serial api 开撸上位机,途中遇见了许多的坑这里记录一下

typescripy 支持不全

Clangd在特殊平台上失效的问题

Clangd在特殊平台上失效的问题

前言

Clangd相比cquery和ccls而言,clangd的开发体验好上太多,是目前vscode中最优秀的代码索引工具。由于我目前是嵌入式开发居多,经常会和各种各样的交叉编译器打交道

问题

在配置好了沁恒的clangd之后,会发现不起作用,这是因为使用交叉编译器(clangd或者llvm)时,一般会需要指定cpu target,在riscv gcc中的编译选项为
-march=rv32ec_zicsr,所以只需要修正这个方案就好了

解决方案

修改clangd的编译选项,首先先在更目录下新建一个.clangd文件,然后输入一下内容
CompileFlags:
Remove: -march=rv32ec_zicsr
Add: [-I/root/sdk/bin/xpack-riscv-gcc-13/riscv-none-elf/include]