后面会发布针对 dd 等普通烧录工具的 img
liangdi
@liangdi
Best posts made by liangdi
-
[Happy Hacking Nezha Board] 开始裸奔吧,少年
每一个程序员都有一个 OS 梦! - Liangdi
前言
我属于提前批拿到哪吒开发板的,兴奋之余开始研究如何去运行自己的裸机程序,美其名曰:操作系统.
和 mcu 不一样, sbc 级别的 cpu 跑起来要复杂的多,不过好在系统级别的领域,不同的软件分工明确, 我们的裸机程序作为 kernel 部分,等着被引导就好.
尽管 sbc 的系统很复杂, 不过要跑起我们的小小的代码,我们刚开始关心的东西不必要很多.
走出第一步,才能看到后面的广阔天空.
由于没有自己的 OS , 这里用 rt-thread 的 rt-smart 来作为实验验证对象.
uboot
和我们接触的第一个对象就是 uboot , uboot 是哪吒开发板的 bootloader,所以我们要和他搞好关系,了解他,才能让他帮我们完成 kernel 的引导.
哪吒开发板的引导路径大致是这样: BROOM -> spl -> uboot -> [nand | mmc]
通过简单的把玩,发现以下规律, BROOM 中的一级 bootloader 会检测 mmc 和 nand 设备, 如果存在 mmc 设备就会 load mmc boot 分区中的 spl 继续工作, nand 同理.
开发板上有 256MB 的 nand flash, 可以有足够的空间存放我们的程序了, 所以就不考虑 mmc 了.
哪吒的 uboot 和 nand
开发板,接上串口工具,上电,串口中就可以看到系统启动信息了, 如果你什么都不操作就会进入 tina 环境了, 所以开机的时候,连按 s 键盘(和 PC 开机按 F2或者 F10 一样吧) 就可以进入 uboot 环境
如下界面:
先用 mtdparts 查看 nand 信息
mdtparts default mtdparts # 输出如下: device nand0 <nand>, # parts = 4 #: name size offset mask_flags 0: boot0 0x00100000 0x00000000 1 1: uboot 0x00300000 0x00100000 1 2: secure_storage 0x00100000 0x00400000 1 3: sys 0x0fb00000 0x00500000 0 active partition: nand0,0 - (boot0) 0x00100000 @ 0x00000000 defaults: mtdids : nand0=nand mtdparts: mtdparts=nand:[email protected](boot0)ro,[email protected](uboot)ro,[email protected](secure_storage)ro,-(sys)
从上面可以看到, nand 有四个分区, 前面两个 bootloader , 第三 secure_storage 和我们也没有什么关系, 第四个分区 sys 就是保存用户 os 的地方, 目前就是 tina linux 系统.
查看一下 sys 中的信息
ubi part sys ubi info l # 输出如下: Volume information dump: vol_id 0 reserved_pebs 1 alignment 1 data_pad 0 vol_type 4 name_len 3 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name mbr Volume information dump: vol_id 1 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 13 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name boot-resource Volume information dump: vol_id 2 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 3 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name env Volume information dump: vol_id 3 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 10 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name env-redund Volume information dump: vol_id 4 reserved_pebs 29 alignment 1 data_pad 0 vol_type 3 name_len 4 usable_leb_size 258048 used_ebs 29 used_bytes 7483392 last_eb_bytes 258048 corrupted 0 upd_marker 0 name boot ...
我们看到了一些熟悉的信息,系统镜像的分区表, 就是 tina sdk 打包出来的产物.
那么 uboot 如何引导 nand 中的系统的呢?
使用 printenv 查看一下 uboot 的环境变量,下面列出重要的部分:boot_normal=sunxi_flash read 45000000 ${boot_partition};bootm 45000000 boot_partition=boot bootcmd=run setargs_nand_ubi boot_normal [email protected]_0:[email protected]_1:[email protected]_2:[email protected]_3:[email protected]_4:[email protected]_5:[email protected]_6:[email protected]_7:[email protected]_8: root_partition=rootfs setargs_nand_ubi=setenv bootargs ubi.mtd=${mtd_name} ubi.block=0,${root_partition} earlyprintk=${earlyprintk} clk_ignore_unused initcall_debug=${initcall_debug} console=${console} loglevel=${loglevel} root=${nand_root} rootfstype=${rootfstype} init=${init} partitions=${partitions} cma=${cma} snum=${snum} mac_addr=${mac} wifi_mac=${wifi_mac} bt_mac=${bt_mac} specialstr=${specialstr} gpt=1 ubi_attach_mtdnum=3
就以上这几行就可以了, 对我们来说关键作用的只有前 3 行.
bootcmd
这个是 uboot 启动时候执行的变量, 内容是run setargs_nand_ubi
和boot_normal
其中
setargs_nand_ubi
是设置bootargs
的, 是 Linux 关心的东西.
我们主要看boot_normal
boot_normal
大致含义是 flash 工具读取${boot_partition}
(解析后是boot
) 位置的数据到内存0x45000000
的位置, 然后bootm
引导0x45000000
位置的内核.所以,简单的方法就是我们把我们自己的 OS 程序,写入到 nand 中 boot 分区的位置,理论上就可以了.
构建 nand 和引导自己的系统
起初本来想用 xboot 的 xfel 工具将数据写入 nand, 然后发现没有实现,所以先跳过, 等后续支持了就会更方便了.
tina sdk 中
device/config/chips/d1/configs/nezha_min/sys_partition.fex
这个文件是pack
的配置信息 , 根据文件知道pack
命令会把 boot.img 打包到 nand 的 boot 分区, 这个就是我们所要的,所以最简单的方法就是把我们自己的 bin 文件替换调 boot.img , 然后 tina sdk 中执行pack
,生成的产物tina_d1-nezha_min_uart0.img
中就包含了我们的代码了.然后用全志的工具,将
tina_d1-nezha_min_uart0.img
烧录到哪吒主板上.第一步就完成了.这样就可以正常引导了么? 答案是否定的.
在前面 uboot 的引导指令用的是
bootm 45000000
, bootm 是引导 linux kernel 的,包含了引导协议的一些东西, 我们作为一个裸机程序,我们可以使用 uboot 的go
命令之间跳转到0x45000000
处运行, 将 boot_normal 改为sunxi_flash read 45000000 ${boot_partition};go 45000000
即可, 但是目前 tina 默认的 uboot 没有编译go
指令, 进入lichee/brandy-2.0/u-boot-2018
目录, 执行make menuconfig
, 然后在 Command line interface --> Boot commands 中选中 go 指令,保存后,重新编译, 在打包一次就可以了.tina uboot 默认的环境变量信息在文件
device/config/chips/d1/configs/nezha/env.cfg
里面,可以将 boot_normal 改好后再编译,就不用在 uboot 交互界面中修改环境变量了.上电
bingo!少年, 下一步就开始在哪吒上运行你的 Dreeam OS 吧!
最后的补充注意事项: RISC-V 芯片运行在 SBI 环境, S Mode 下,所以如果裸机程序 M 模式的代码是无法正常运行的.
-
用 Rust 探索 RISC-V 主板 D1 之 GPIO
gpio 是单片机或者单板机和外部硬件沟通的桥梁,通过它可以控制外部硬件,可以建立通讯,可以获取传感器数据等
D1 开发板和树莓派一样,对外引出了 40pin 引脚, 这些引脚包含3.3v,5v供电, GND , 以及几个未使用(NC)引脚, 然后就是我们要讲到的 GPIO 引脚.
辅助利器
开发 gpio 应用离不开几个利器
1. 原理图
全志已经开放原理图下载, 下载地址: https://developer.allwinnertech.com/downloads/resources/24
根据原理图,我们可以看到 40pin 引脚与之对应的芯片端口
这里要说明 D1 板子用 PCF8574 扩展了 8 个 IO 分别是 PP0-PP7 ,其他引出的 IO 来至 D1 这颗芯片, 并且由于 IO 端口不足, 40 Pin 里面物理 32pin 和 38pin 为未启用(NC), 树莓派中是两个 GPIO 端口
2. debugfs
第二个利器就是 debugfs Wiki: Debugfs
debugfs 传承了 Linux 一切皆文件的理念,把内核更多信息通过文件系统展现给开发者,这里当然就包括了我们要的 gpio 信息
debugfs 默认挂载在
/sys/kernel/debug
, 如果没有挂载,可以执行mount -t debugfs none /sys/kernel/debug
挂载在 /sys/kernel/debug 目录下有个 gpio 文件
gpiochip0: GPIOs 0-223, parent: platform/2000000.pinctrl, 2000000.pinctrl: gpio-115 ( |usb1-vbus ) out lo gpio-116 ( |otg_det ) in lo gpio-117 ( |otg_id ) in hi gpio-144 ( |phy-rst ) out hi gpio-166 ( |cd ) in hi IRQ gpio-202 ( |wlan_hostwake ) in hi gpio-204 ( |wlan_regon ) out lo gpio-208 ( |bt_wake ) out lo gpio-209 ( |bt_hostwake ) in hi gpio-210 ( |bt_rst ) out lo gpiochip1: GPIOs 2020-2027, parent: i2c/2-0038, pcf8574, can sleep:
里面内容显示了,板子上有两部分 gpio 组成,第一部分(gpiochip0)有 0-223 ,共224 个 gpio 端口,来自 D1 芯片, 第二部分(gpiochip1) 2020-2027 , 共8个 gpio 端口来自 PCF8574, 并且 8574 是通过 i2c 连接的 , 同时又显示了gpiochip0 中已经做了配置的 GPIO 接口
D1 芯片中的 gpio 信息可以在
/sys/kernel/debug/pinctrl/2000000.pinctrl/pins
找到编号和芯片引脚名称(PA1,PB2等)的对应关系registered pins: 88 pin 32 (PB0) pin 33 (PB1) pin 34 (PB2) pin 35 (PB3) pin 36 (PB4) pin 37 (PB5) pin 38 (PB6) pin 39 (PB7) pin 40 (PB8) pin 41 (PB9) pin 42 (PB10) pin 43 (PB11) pin 44 (PB12) pin 64 (PC0) pin 65 (PC1) pin 66 (PC2) pin 67 (PC3) ...
3. sysfs
sysfs 和 debugfs 一样,通过文件系统,用户不仅可以查看信息,还可以操作硬件.
在
/sys/class/gpio
目录下有四个文件,分别是 export,gpiochip0 ,gpiochip2020,unexport其中 gpiochip0,gpiochip2020 链接的是芯片两个gpio主控
export 和 unexport 用来控制 gpio 的开启与关闭
# 启用 2020 号 gpio 端口, 根据上面的信息,可以知道 2020 对应扩展 IO PP0 , 也就是 40pin 引脚中的 GPIO8 echo 2020 > export # 执行完 echo 2020 > export 后, 会在 /sys/class/gpio 中创建一个目录 /sys/class/gpio/gpio202 , 在这个目录里面就可以设置 gpio 的in 和 out 以及读取或者输出高低电平 cd /sys/class/gpio/gpio2020 # 设置为输出 echo out > direction # 设置高电平 echo 1 > value # 设置低电平 echo 0 > value # 执行代码后, 如果接了 LED 灯, 灯就会亮了又灭了
Rust 在 gpio 方面的支持情况
rust 有以下一些 crate
1. linux-embedded-hal
Implementation of the embedded-hal traits for Linux devices
2. gpio-cdev
基于 GPIO character device ABI 的库
3. sysfs-gpio
基于 sysfs 操作 gpio 的库, 原理如上面手动操作 sysfs 是一样的
4. gpio-utils
操作 gpio 的小工具程序, 基于
sysfs_gpio
Rust Demo (使用
cdev-gpio
)- list gpios
extern crate gpio_cdev; use gpio_cdev::*; fn main() { let chip_iterator = match chips() { Ok(chips) => chips, Err(e) => { println!("Failed to get chip iterator: {:?}", e); return; } }; for chip in chip_iterator { let chip = match chip { Ok(chip) => chip, Err(err) => panic!("Failed to open the chip: {:?}", err) }; println!( "GPIO chip: {}, \"{}\", \"{}\", {} GPIO Lines", chip.path().to_string_lossy(), chip.name(), chip.label(), chip.num_lines() ); for line in chip.lines() { match line.info() { Ok(info) => { let mut flags = vec![]; if info.is_kernel() { flags.push("kernel"); } if info.direction() == LineDirection::Out { flags.push("output"); } if info.is_active_low() { flags.push("active-low"); } if info.is_open_drain() { flags.push("open-drain"); } if info.is_open_source() { flags.push("open-source"); } let usage = if !flags.is_empty() { format!("[{}]", flags.join(" ")) } else { "".to_owned() }; println!( "\tline {lineno:>3}: {name} {consumer} {usage}", lineno = info.line().offset(), name = info.name().unwrap_or("unused"), consumer = info.consumer().unwrap_or("unused"), usage = usage, ); } Err(e) => println!("\tError getting line info: {:?}", e), } } println!(); } }
cdev-gpio 的接口中, 通过 chips() 获取 gpio控制器列表, D1 中的/dev/gpiochip0 和 /dev/gpiochip1
每个 chip 中有 lines 列表,就是控制器下的 gpio 列表 line 就是 gpio 对象, 可以 进行 set_value , get_value ,以及设定输入输出等操作
总体来说, Linux 对 gpio 封装已经很简单, rust 在这方面支持也比较完善. 后续文章还有 i2c, spi , uart 等方面的操作,欢迎关注,欢迎拍砖.
-
[Happy Hacking Nezha Board] 小孩子才做选择,我全都要 BOOT
乘胜追击
前面完成引导 rt-smart 后, 开始继续其他功能的研究.
常规我们使用 raspberry pi 以及其他 Linux 系统的时候, 一般我们的 kernel 等信息都是放在 /boot 目录下的,大多数会格式化成独立的一个分区.
那么我们就照着这个方向去改造哪吒板子, 目前已经初见成效了, 我做了一个 demo 的 image , 供大家测试, 后续 RVBoards 会发布正式的 Debian 版本.
- 下载地址: d1-multi-kernel
说明
镜像前面几个 1-3 分区是全志的预留的几个分区,占用空间很间,这里不去动他.
第 4 分区是 vfat 格式的 boot 分区, kernel 和 dtbo 等文件存放在这里面
demo 镜像 boot 分区文件说明
overlay
: 存放 dtb overlay 文件boot_debian.img
debian 内核boot_tina.img
tina 内核config.txt
引导配置文件rt-smart
rt-smart 执行程序
config.txt 配置说明
配置示例
# mode # 0 boot bare metal bin # 1 boot linux kernel mode=1 bin=rt-smart kernel=boot_debian.img # dtb overlay # load overlay/${dtoverlay}.dtbo dtoverlay=test-overlay # uboot vars # for debian mmc_root=/dev/mmcblk0p6 # for tina #mmc_root=/dev/mmcblk0p5
详细说明
-
mode
:配置引导模式 0 为引导二进制程序 1 为引导linux 内核 (目前这个版本的 内核文件需要使用
mkbootimage
打包生成,就是 tina sdk 中pack
命令生成的boot.img
文化) -
bin
mode=0 的时候引导的文件
-
kernel
mode=1 的时候引导的文件
-
dtoverlay
dtb overlay 配置, 将会加载 overlay/${dtoverlay}.dtbo 这个文件,后续将会支持多个文件加载
-
mmc_root
这个是作为 bootargs 中 root 参数传递给内核的,告诉内核 root 在什么分区,默认是
/dev/mmcblk0p5
, demo 镜像中有多个内核,多个 rootfs ,所以需要配置一下. -
注意
- 配置项"="两边不能有空格
- config.txt 是作为 uboot 的环境变量加载的,可以配置其他变量覆盖 uboot 内部的变量
原理
主要就是利用 uboot 的 fatload 这个指令,从 mmc 中 vfat 文件系统中加载指定文件到内存中使用.
用到相关的指令
fatload
,env import
,fdt
等核心配置
# demo 镜像中的 bootcmd=run boot_check setargs_mmc boot_mmc # 其中 setargs_mmc 是全志默认的,设置 mmc 加载的 bootrags 的指令 # boot_check 检测 mmc 是否启用,然后加载 config.txt 文件,再加载 dtbo 文件 boot_check=run card_init;mmcinfo;mmc part;fatload mmc ${mmc_dev}:${mmc_boot_part} 47000000 config.txt;env import -t 47000000 ${filesize}; test -n "$dtoverlay" && fatload mmc ${mmc_dev}:${mmc_boot_part} 48000000 overlay/${dtoverlay}.dtbo; fdt apply 48000000 # boot_mmc 就是根据 mode 引导不同系统了 boot_mmc=if test ${mode} -eq 0; then fatload mmc ${mmc_dev}:${mmc_boot_part} 45000000 ${bin}; go 45000000; else fatload mmc ${mmc_dev}:${mmc_boot_part} 45000000 ${kernel}; bootm 45000000; fi
原理和实现其实很简单, 后续还可以继续改进,支持多个 dtbo 加载, tftp 加载(方便快速调试) 等等.
后记
完成多系统引导就这么简单了, 后续文章我会再写一个 dtb overlay 的 demo.
注意事项: demo 镜像中, debain 的 rootfs 大小太小,更大空间,需要自行处理一下. -
「RVBoards-哪吒」开启 SSH 和 VNC 远程访问,摆脱烦人的鼠标键盘显示器
单板机,上手比较烦人的就是要准备配套的鼠标键盘以及显示器,通过 SSH 或者 VNC 就可以在自己电脑上远程进行操作,更加方便.
准备材料
- 哪吒开发板 (RVBoards Debian 系统)
- 串口调试线
- 网络已经联通(联网不在这里讨论,可以另外写一篇文章了)
开启 SSH
系统默认配置禁用了 root 远程 ssh 登陆, 如果是普通权限用户没有这个问题.
-
开启 root ssh 远程登陆
编辑 /etc/ssh/sshd_config
将
#PermitRootLogin without-password
修改为PermitRootLogin yes
systemctl restart sshd
重启 ssh 服务即可 -
ssh 访问
使用
ssh [email protected]
就可以登陆访问了,默认密码是rvboards
使用
ssh-copy-id [email protected]
可以设置公钥访问,省掉密码输入
开启 VNC 服务
Linux 上有很多 vnc 服务程序,这里我们选择 tigervnc
- 安装软件
apt update apt install tigervnc-standalone-server -y
-
tigervnc server 常规使用方法
启动服务:
vncserver -localhost no -display :1
上述命令启动 vncserver 并且使用 :1 编号的显示器, :0 默认被启动的 xserver使用了, -localhost no 表示可以远程访问
第一次启用的时候会提示输入密码, 建议使用和 root 一样的密码,便于记忆, 同时可以配置使用 linux 系统认证, 这个哪吒玩家可以自己去查看相关资料.
查看服务:
vncserver -list
TigerVNC server sessions: X DISPLAY # RFB PORT # PROCESS ID SERVER :1 5901 647 Xtigervnc
停止服务
vncserver -kill
vncserver -kill :1 # 结束 :1 display 的 vnc 服务
配置分辨率, 使用
-geometry 1280x800
参数目前哪吒支持的分辨率
1920x1080 60.00 1600x1200 60.00 1680x1050 60.00 1400x1050 60.00 1360x768 60.00 1280x1024 60.00 1280x960 60.00 1280x720 60.00 1024x768 60.00 800x600 60.00 640x480 60.00
-
配置 VNC server 开机启动
开机启动最简单的方式是在 /etc/rc.local 中加入启动脚本,以下是示例
echo "start vnc server" export HOME=/root /usr/bin/vncserver -localhost no -display :1 -geometry 1280x800 echo "vnc server started" # 这里需要先配置 HOME 环境变量, vncserver 需要
-
VNC 远程连接
VNC 有很多客户端, ReadVNC 的 VNC Viewer 推荐一下,并且有 Chrome 的插件, 输入ip和端口号就可以连接了,密码就是初次启动 vncserver 配置的密码
总结
linux 生态下, 远程访问是比较容易的, SBC 级别的设备,大多比较精简,需要自己去安装配置,借此文抛砖引玉,欢迎一起交流.
吐槽一下目前系统层面对 D1 的显示驱动优化的比较差, 性能弱,使用 VNC 操作 gui 大大提升用户体验.
Latest posts made by liangdi
-
[Happy Hacking Nezha Board] 掌握 Device Tree Oerlay 的魔法
Device Tree 是目前嵌入式 Linux 系统最常用的设备解耦工具, 所以要玩转嵌入式 Linux , 这个东西必须掌握.
DTB, DTS , DTSI?
在 tina sdk 代码中, 有 board.dts , sun20iw1p1.dtsi 这些文件, 这些就是 device tree 的源文件,或者说描述文件.
dts 通过 dtc 这个编译器可以编译成 dtb 以及后面我们要用到的 dtbo(dtb overlay) , 它们是二进制文件, Linux 和 uboot 可以使用.
DTC
Device Tree Compiler , dts 的编译工具, Linux 下面可以使用包管理工具按照
# redora sudo dnf install dtc # ubuntu sudo apt install device-tree-compiler
windows 参考(未验证):
https://github.com/lbmeng/dtcdtb overlay 示例代码
在之前给出的 demo 镜像中, pwm7 这个设备的驱动部分在 dts 中被注释了, 所以我们尝试用 dtb overlay 给他弄回来.
dtbo 的编译只要 dtc 就可以, 但是我们会看到 dtsi 中有 c 语言的 #include 宏,所以还会用到 c编译根据展开宏,如果没有 #include ,就不需要.
使用 #include 宏展开示例, include 里面都是 dt-bindings 目录下的文件,该目录在 tina 中的位置是: lichee/linux-5.4/include , 你要展开需要将 lichee/linux-5.4/include/dt-bindings 拷贝到 dts 目录中来
cpp -nostdinc -I. -undef -x assembler-with-cpp board.dts > board-with-include.dts
执行后, board-with-include.dts 就是 include 展开后的代码.
pwm7-overlay.dts 示例代码
/dts-v1/; /plugin/; / { [email protected] { target-path = "/soc/[email protected]"; __overlay__ { pwm7_pin_a: [email protected] { pins = "PD22"; function = "pwm7"; drive-strength = <10>; bias-pull-up; }; pwm7_pin_b: [email protected] { pins = "PD22"; function = "gpio_in"; }; }; }; [email protected] { target-path = "/soc/[email protected]"; __overlay__ { pinctrl-names = "active", "sleep"; pinctrl-0 = <&pwm7_pin_a>; pinctrl-1 = <&pwm7_pin_b>; status = "okay"; }; }; };
头部是作为一个 overlay 的描述, 和 dts 相比多了 /plugin/; 这个节点
/dts-v1/; /plugin/;
后面同样有个 / 根节点, 然后需要覆盖的地方用 [email protected] 作为节点名称
fragment 里面 target-path 为需要覆盖的节点路径, 也可以用 target 使用的是引用.
后面 overlay 里面就是需要覆盖的属性.
编译
dtc -I dts -O dtb -o ./pwm7-overlay.dtbo ./pwm7-overlay.dts
这样得到的 pwm7-overlay.dtbo 文件就是我们所要的. 可以用
fdtdump ./pwm7-overlay.dtbo
查看结果测试
一个测试程序 (pwm.sh, 来自 RVBoards) 和蜂鸣器扩展版
#!/bin/bash #pwm 节点 pwmnode=pwm$1 #输出提示pwm$1 通道 echo /sys/class/pwm/pwmchip0/${pwmnode} #申请pwm$1 通道 echo $1 > /sys/class/pwm/pwmchip0/export #指定pwm$1 通道的频率 echo $2 > /sys/class/pwm/pwmchip0/${pwmnode}/period #指定pwm$1 通道的占空比 echo $3 > /sys/class/pwm/pwmchip0/${pwmnode}/duty_cycle #使能pwm$1 通道 echo 1 > /sys/class/pwm/pwmchip0/${pwmnode}/enable #使能pwm$1 通道 工作时间10s sleep 10 #失能pwm$1 通道 echo 0 > /sys/class/pwm/pwmchip0/${pwmnode}/enable #等待pwm$1 通道 关闭 sleep 1 #注销pwm$1 通道 echo $1 > /sys/class/pwm/pwmchip0/unexport
在没有配置 dtb overlay 的系统中执行
# 设置 7 号 pwm 引脚数据 ./pwm.sh 7 300000 150000
会出现错误
然后将
pwm7-overlay.dtbo
复制到 boot 分区的 overlay 目录下, 在编辑 config.txt 添加配置
dtoverlay=pwm7-overlay
, 配置中不需要加.dtbo
, 系统会自动添加.重启后, 在执行
./pwm.sh 7 300000 150000
, 蜂鸣器就会响了.如果没有蜂鸣器扩展版,可以查看
/sys/class/pwm/
里面的内容,进行前后对比,也会发现 dtb overlay 生效了.后记
dtb 以及相关技术需要去掌握,掌握了就可以把开发板玩的更溜了, 如果你去使用各种大的扩展板, 基本都会涉及到 dtb overlay.
-
[Happy Hacking Nezha Board] 小孩子才做选择,我全都要 BOOT
乘胜追击
前面完成引导 rt-smart 后, 开始继续其他功能的研究.
常规我们使用 raspberry pi 以及其他 Linux 系统的时候, 一般我们的 kernel 等信息都是放在 /boot 目录下的,大多数会格式化成独立的一个分区.
那么我们就照着这个方向去改造哪吒板子, 目前已经初见成效了, 我做了一个 demo 的 image , 供大家测试, 后续 RVBoards 会发布正式的 Debian 版本.
- 下载地址: d1-multi-kernel
说明
镜像前面几个 1-3 分区是全志的预留的几个分区,占用空间很间,这里不去动他.
第 4 分区是 vfat 格式的 boot 分区, kernel 和 dtbo 等文件存放在这里面
demo 镜像 boot 分区文件说明
overlay
: 存放 dtb overlay 文件boot_debian.img
debian 内核boot_tina.img
tina 内核config.txt
引导配置文件rt-smart
rt-smart 执行程序
config.txt 配置说明
配置示例
# mode # 0 boot bare metal bin # 1 boot linux kernel mode=1 bin=rt-smart kernel=boot_debian.img # dtb overlay # load overlay/${dtoverlay}.dtbo dtoverlay=test-overlay # uboot vars # for debian mmc_root=/dev/mmcblk0p6 # for tina #mmc_root=/dev/mmcblk0p5
详细说明
-
mode
:配置引导模式 0 为引导二进制程序 1 为引导linux 内核 (目前这个版本的 内核文件需要使用
mkbootimage
打包生成,就是 tina sdk 中pack
命令生成的boot.img
文化) -
bin
mode=0 的时候引导的文件
-
kernel
mode=1 的时候引导的文件
-
dtoverlay
dtb overlay 配置, 将会加载 overlay/${dtoverlay}.dtbo 这个文件,后续将会支持多个文件加载
-
mmc_root
这个是作为 bootargs 中 root 参数传递给内核的,告诉内核 root 在什么分区,默认是
/dev/mmcblk0p5
, demo 镜像中有多个内核,多个 rootfs ,所以需要配置一下. -
注意
- 配置项"="两边不能有空格
- config.txt 是作为 uboot 的环境变量加载的,可以配置其他变量覆盖 uboot 内部的变量
原理
主要就是利用 uboot 的 fatload 这个指令,从 mmc 中 vfat 文件系统中加载指定文件到内存中使用.
用到相关的指令
fatload
,env import
,fdt
等核心配置
# demo 镜像中的 bootcmd=run boot_check setargs_mmc boot_mmc # 其中 setargs_mmc 是全志默认的,设置 mmc 加载的 bootrags 的指令 # boot_check 检测 mmc 是否启用,然后加载 config.txt 文件,再加载 dtbo 文件 boot_check=run card_init;mmcinfo;mmc part;fatload mmc ${mmc_dev}:${mmc_boot_part} 47000000 config.txt;env import -t 47000000 ${filesize}; test -n "$dtoverlay" && fatload mmc ${mmc_dev}:${mmc_boot_part} 48000000 overlay/${dtoverlay}.dtbo; fdt apply 48000000 # boot_mmc 就是根据 mode 引导不同系统了 boot_mmc=if test ${mode} -eq 0; then fatload mmc ${mmc_dev}:${mmc_boot_part} 45000000 ${bin}; go 45000000; else fatload mmc ${mmc_dev}:${mmc_boot_part} 45000000 ${kernel}; bootm 45000000; fi
原理和实现其实很简单, 后续还可以继续改进,支持多个 dtbo 加载, tftp 加载(方便快速调试) 等等.
后记
完成多系统引导就这么简单了, 后续文章我会再写一个 dtb overlay 的 demo.
注意事项: demo 镜像中, debain 的 rootfs 大小太小,更大空间,需要自行处理一下. -
RE: [Happy Hacking Nezha Board] 开始裸奔吧,少年
rt-smart 代码: https://gitlab.eduxiji.net/lizhirui/project325618-83178.git
用 riscv64-unknown-elf 的工具链就可以 -
[Happy Hacking Nezha Board] 开始裸奔吧,少年
每一个程序员都有一个 OS 梦! - Liangdi
前言
我属于提前批拿到哪吒开发板的,兴奋之余开始研究如何去运行自己的裸机程序,美其名曰:操作系统.
和 mcu 不一样, sbc 级别的 cpu 跑起来要复杂的多,不过好在系统级别的领域,不同的软件分工明确, 我们的裸机程序作为 kernel 部分,等着被引导就好.
尽管 sbc 的系统很复杂, 不过要跑起我们的小小的代码,我们刚开始关心的东西不必要很多.
走出第一步,才能看到后面的广阔天空.
由于没有自己的 OS , 这里用 rt-thread 的 rt-smart 来作为实验验证对象.
uboot
和我们接触的第一个对象就是 uboot , uboot 是哪吒开发板的 bootloader,所以我们要和他搞好关系,了解他,才能让他帮我们完成 kernel 的引导.
哪吒开发板的引导路径大致是这样: BROOM -> spl -> uboot -> [nand | mmc]
通过简单的把玩,发现以下规律, BROOM 中的一级 bootloader 会检测 mmc 和 nand 设备, 如果存在 mmc 设备就会 load mmc boot 分区中的 spl 继续工作, nand 同理.
开发板上有 256MB 的 nand flash, 可以有足够的空间存放我们的程序了, 所以就不考虑 mmc 了.
哪吒的 uboot 和 nand
开发板,接上串口工具,上电,串口中就可以看到系统启动信息了, 如果你什么都不操作就会进入 tina 环境了, 所以开机的时候,连按 s 键盘(和 PC 开机按 F2或者 F10 一样吧) 就可以进入 uboot 环境
如下界面:
先用 mtdparts 查看 nand 信息
mdtparts default mtdparts # 输出如下: device nand0 <nand>, # parts = 4 #: name size offset mask_flags 0: boot0 0x00100000 0x00000000 1 1: uboot 0x00300000 0x00100000 1 2: secure_storage 0x00100000 0x00400000 1 3: sys 0x0fb00000 0x00500000 0 active partition: nand0,0 - (boot0) 0x00100000 @ 0x00000000 defaults: mtdids : nand0=nand mtdparts: mtdparts=nand:[email protected](boot0)ro,[email protected](uboot)ro,[email protected](secure_storage)ro,-(sys)
从上面可以看到, nand 有四个分区, 前面两个 bootloader , 第三 secure_storage 和我们也没有什么关系, 第四个分区 sys 就是保存用户 os 的地方, 目前就是 tina linux 系统.
查看一下 sys 中的信息
ubi part sys ubi info l # 输出如下: Volume information dump: vol_id 0 reserved_pebs 1 alignment 1 data_pad 0 vol_type 4 name_len 3 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name mbr Volume information dump: vol_id 1 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 13 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name boot-resource Volume information dump: vol_id 2 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 3 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name env Volume information dump: vol_id 3 reserved_pebs 1 alignment 1 data_pad 0 vol_type 3 name_len 10 usable_leb_size 258048 used_ebs 1 used_bytes 258048 last_eb_bytes 258048 corrupted 0 upd_marker 0 name env-redund Volume information dump: vol_id 4 reserved_pebs 29 alignment 1 data_pad 0 vol_type 3 name_len 4 usable_leb_size 258048 used_ebs 29 used_bytes 7483392 last_eb_bytes 258048 corrupted 0 upd_marker 0 name boot ...
我们看到了一些熟悉的信息,系统镜像的分区表, 就是 tina sdk 打包出来的产物.
那么 uboot 如何引导 nand 中的系统的呢?
使用 printenv 查看一下 uboot 的环境变量,下面列出重要的部分:boot_normal=sunxi_flash read 45000000 ${boot_partition};bootm 45000000 boot_partition=boot bootcmd=run setargs_nand_ubi boot_normal [email protected]_0:[email protected]_1:[email protected]_2:[email protected]_3:[email protected]_4:[email protected]_5:[email protected]_6:[email protected]_7:[email protected]_8: root_partition=rootfs setargs_nand_ubi=setenv bootargs ubi.mtd=${mtd_name} ubi.block=0,${root_partition} earlyprintk=${earlyprintk} clk_ignore_unused initcall_debug=${initcall_debug} console=${console} loglevel=${loglevel} root=${nand_root} rootfstype=${rootfstype} init=${init} partitions=${partitions} cma=${cma} snum=${snum} mac_addr=${mac} wifi_mac=${wifi_mac} bt_mac=${bt_mac} specialstr=${specialstr} gpt=1 ubi_attach_mtdnum=3
就以上这几行就可以了, 对我们来说关键作用的只有前 3 行.
bootcmd
这个是 uboot 启动时候执行的变量, 内容是run setargs_nand_ubi
和boot_normal
其中
setargs_nand_ubi
是设置bootargs
的, 是 Linux 关心的东西.
我们主要看boot_normal
boot_normal
大致含义是 flash 工具读取${boot_partition}
(解析后是boot
) 位置的数据到内存0x45000000
的位置, 然后bootm
引导0x45000000
位置的内核.所以,简单的方法就是我们把我们自己的 OS 程序,写入到 nand 中 boot 分区的位置,理论上就可以了.
构建 nand 和引导自己的系统
起初本来想用 xboot 的 xfel 工具将数据写入 nand, 然后发现没有实现,所以先跳过, 等后续支持了就会更方便了.
tina sdk 中
device/config/chips/d1/configs/nezha_min/sys_partition.fex
这个文件是pack
的配置信息 , 根据文件知道pack
命令会把 boot.img 打包到 nand 的 boot 分区, 这个就是我们所要的,所以最简单的方法就是把我们自己的 bin 文件替换调 boot.img , 然后 tina sdk 中执行pack
,生成的产物tina_d1-nezha_min_uart0.img
中就包含了我们的代码了.然后用全志的工具,将
tina_d1-nezha_min_uart0.img
烧录到哪吒主板上.第一步就完成了.这样就可以正常引导了么? 答案是否定的.
在前面 uboot 的引导指令用的是
bootm 45000000
, bootm 是引导 linux kernel 的,包含了引导协议的一些东西, 我们作为一个裸机程序,我们可以使用 uboot 的go
命令之间跳转到0x45000000
处运行, 将 boot_normal 改为sunxi_flash read 45000000 ${boot_partition};go 45000000
即可, 但是目前 tina 默认的 uboot 没有编译go
指令, 进入lichee/brandy-2.0/u-boot-2018
目录, 执行make menuconfig
, 然后在 Command line interface --> Boot commands 中选中 go 指令,保存后,重新编译, 在打包一次就可以了.tina uboot 默认的环境变量信息在文件
device/config/chips/d1/configs/nezha/env.cfg
里面,可以将 boot_normal 改好后再编译,就不用在 uboot 交互界面中修改环境变量了.上电
bingo!少年, 下一步就开始在哪吒上运行你的 Dreeam OS 吧!
最后的补充注意事项: RISC-V 芯片运行在 SBI 环境, S Mode 下,所以如果裸机程序 M 模式的代码是无法正常运行的.
-
D1 tina Docker 编译环境制作和使用
d1 的 tina 环境由于工具链比较老旧, 很多开发者的机器上的环境没法正常使用
所以,我写一篇使用 Docker 编译 tina 的文章,继续抛砖引玉,欢迎交流.
PS: 还是希望全志官方升级一下工具链版本准备工作
- 安装好 docker , 执行
docker info
可以查看是否安装正常 - 下载好 tina sdk 的代码, 参考: tina sdk 下载
使用 Docker 编译
# 假设下载的 tina-sdk 目录是 /opt/tina-sdk # 执行如下 docker 命令 docker run -v /opt/tina-sdk:/sdk -it liangdi/d1-tina-build-env /bin/bash # 第一次执行的时候,需要下载镜像,会耗时比较久一点 # 执行完上面 docker 命令后, 会将本机 /opt/tina-sdk 映射到 docker 中的 /sdk 下,就可以在 docker 中编译 tina 了 cd /sdk source ./build/envsetup.sh # 选择编译内容 lunch # 需要设置 FORCE_UNSAFE_CONFIGURE 变量解决 root 检测, 后面 -jx x为配置编译线程数量,可以根据 cpu 数量来配置 make FORCE_UNSAFE_CONFIGURE=1 -j8 # 备注: 第一次编译,会编译一批 host 的依赖比较慢,但是编译一次后,下次重新编译就会跳过了,速度会快很多 # 打包 pack
以上就可以正常编译 d1 的 tina 了, 编译完成后, 在本机 tina-sdk 目录下的 out 目录中就可以看到编译产物了, 采用路径映射的方式,本机更新代码后,再运行 docker , docker 里面就是最新的代码
附上 Dockerfile
# D1 tina build env FROM ubuntu:14.04 RUN apt update RUN apt install build-essential subversion git-core libncurses5-dev zlib1g-dev gawk flex quilt libssl-dev xsltproc libxml-parser-perl mercurial bzr ecj cvs unzip lib32z1 lib32z1-dev lib32stdc++6 libstdc++6 -y RUN apt install bc busybox wget -y
可以自行修改构建, 完善这个 docker
- 安装好 docker , 执行
-
「RVBoards-哪吒」开启 SSH 和 VNC 远程访问,摆脱烦人的鼠标键盘显示器
单板机,上手比较烦人的就是要准备配套的鼠标键盘以及显示器,通过 SSH 或者 VNC 就可以在自己电脑上远程进行操作,更加方便.
准备材料
- 哪吒开发板 (RVBoards Debian 系统)
- 串口调试线
- 网络已经联通(联网不在这里讨论,可以另外写一篇文章了)
开启 SSH
系统默认配置禁用了 root 远程 ssh 登陆, 如果是普通权限用户没有这个问题.
-
开启 root ssh 远程登陆
编辑 /etc/ssh/sshd_config
将
#PermitRootLogin without-password
修改为PermitRootLogin yes
systemctl restart sshd
重启 ssh 服务即可 -
ssh 访问
使用
ssh [email protected]
就可以登陆访问了,默认密码是rvboards
使用
ssh-copy-id [email protected]
可以设置公钥访问,省掉密码输入
开启 VNC 服务
Linux 上有很多 vnc 服务程序,这里我们选择 tigervnc
- 安装软件
apt update apt install tigervnc-standalone-server -y
-
tigervnc server 常规使用方法
启动服务:
vncserver -localhost no -display :1
上述命令启动 vncserver 并且使用 :1 编号的显示器, :0 默认被启动的 xserver使用了, -localhost no 表示可以远程访问
第一次启用的时候会提示输入密码, 建议使用和 root 一样的密码,便于记忆, 同时可以配置使用 linux 系统认证, 这个哪吒玩家可以自己去查看相关资料.
查看服务:
vncserver -list
TigerVNC server sessions: X DISPLAY # RFB PORT # PROCESS ID SERVER :1 5901 647 Xtigervnc
停止服务
vncserver -kill
vncserver -kill :1 # 结束 :1 display 的 vnc 服务
配置分辨率, 使用
-geometry 1280x800
参数目前哪吒支持的分辨率
1920x1080 60.00 1600x1200 60.00 1680x1050 60.00 1400x1050 60.00 1360x768 60.00 1280x1024 60.00 1280x960 60.00 1280x720 60.00 1024x768 60.00 800x600 60.00 640x480 60.00
-
配置 VNC server 开机启动
开机启动最简单的方式是在 /etc/rc.local 中加入启动脚本,以下是示例
echo "start vnc server" export HOME=/root /usr/bin/vncserver -localhost no -display :1 -geometry 1280x800 echo "vnc server started" # 这里需要先配置 HOME 环境变量, vncserver 需要
-
VNC 远程连接
VNC 有很多客户端, ReadVNC 的 VNC Viewer 推荐一下,并且有 Chrome 的插件, 输入ip和端口号就可以连接了,密码就是初次启动 vncserver 配置的密码
总结
linux 生态下, 远程访问是比较容易的, SBC 级别的设备,大多比较精简,需要自己去安装配置,借此文抛砖引玉,欢迎一起交流.
吐槽一下目前系统层面对 D1 的显示驱动优化的比较差, 性能弱,使用 VNC 操作 gui 大大提升用户体验.
-
用 Rust 探索 RISC-V 主板 D1 之 GPIO
gpio 是单片机或者单板机和外部硬件沟通的桥梁,通过它可以控制外部硬件,可以建立通讯,可以获取传感器数据等
D1 开发板和树莓派一样,对外引出了 40pin 引脚, 这些引脚包含3.3v,5v供电, GND , 以及几个未使用(NC)引脚, 然后就是我们要讲到的 GPIO 引脚.
辅助利器
开发 gpio 应用离不开几个利器
1. 原理图
全志已经开放原理图下载, 下载地址: https://developer.allwinnertech.com/downloads/resources/24
根据原理图,我们可以看到 40pin 引脚与之对应的芯片端口
这里要说明 D1 板子用 PCF8574 扩展了 8 个 IO 分别是 PP0-PP7 ,其他引出的 IO 来至 D1 这颗芯片, 并且由于 IO 端口不足, 40 Pin 里面物理 32pin 和 38pin 为未启用(NC), 树莓派中是两个 GPIO 端口
2. debugfs
第二个利器就是 debugfs Wiki: Debugfs
debugfs 传承了 Linux 一切皆文件的理念,把内核更多信息通过文件系统展现给开发者,这里当然就包括了我们要的 gpio 信息
debugfs 默认挂载在
/sys/kernel/debug
, 如果没有挂载,可以执行mount -t debugfs none /sys/kernel/debug
挂载在 /sys/kernel/debug 目录下有个 gpio 文件
gpiochip0: GPIOs 0-223, parent: platform/2000000.pinctrl, 2000000.pinctrl: gpio-115 ( |usb1-vbus ) out lo gpio-116 ( |otg_det ) in lo gpio-117 ( |otg_id ) in hi gpio-144 ( |phy-rst ) out hi gpio-166 ( |cd ) in hi IRQ gpio-202 ( |wlan_hostwake ) in hi gpio-204 ( |wlan_regon ) out lo gpio-208 ( |bt_wake ) out lo gpio-209 ( |bt_hostwake ) in hi gpio-210 ( |bt_rst ) out lo gpiochip1: GPIOs 2020-2027, parent: i2c/2-0038, pcf8574, can sleep:
里面内容显示了,板子上有两部分 gpio 组成,第一部分(gpiochip0)有 0-223 ,共224 个 gpio 端口,来自 D1 芯片, 第二部分(gpiochip1) 2020-2027 , 共8个 gpio 端口来自 PCF8574, 并且 8574 是通过 i2c 连接的 , 同时又显示了gpiochip0 中已经做了配置的 GPIO 接口
D1 芯片中的 gpio 信息可以在
/sys/kernel/debug/pinctrl/2000000.pinctrl/pins
找到编号和芯片引脚名称(PA1,PB2等)的对应关系registered pins: 88 pin 32 (PB0) pin 33 (PB1) pin 34 (PB2) pin 35 (PB3) pin 36 (PB4) pin 37 (PB5) pin 38 (PB6) pin 39 (PB7) pin 40 (PB8) pin 41 (PB9) pin 42 (PB10) pin 43 (PB11) pin 44 (PB12) pin 64 (PC0) pin 65 (PC1) pin 66 (PC2) pin 67 (PC3) ...
3. sysfs
sysfs 和 debugfs 一样,通过文件系统,用户不仅可以查看信息,还可以操作硬件.
在
/sys/class/gpio
目录下有四个文件,分别是 export,gpiochip0 ,gpiochip2020,unexport其中 gpiochip0,gpiochip2020 链接的是芯片两个gpio主控
export 和 unexport 用来控制 gpio 的开启与关闭
# 启用 2020 号 gpio 端口, 根据上面的信息,可以知道 2020 对应扩展 IO PP0 , 也就是 40pin 引脚中的 GPIO8 echo 2020 > export # 执行完 echo 2020 > export 后, 会在 /sys/class/gpio 中创建一个目录 /sys/class/gpio/gpio202 , 在这个目录里面就可以设置 gpio 的in 和 out 以及读取或者输出高低电平 cd /sys/class/gpio/gpio2020 # 设置为输出 echo out > direction # 设置高电平 echo 1 > value # 设置低电平 echo 0 > value # 执行代码后, 如果接了 LED 灯, 灯就会亮了又灭了
Rust 在 gpio 方面的支持情况
rust 有以下一些 crate
1. linux-embedded-hal
Implementation of the embedded-hal traits for Linux devices
2. gpio-cdev
基于 GPIO character device ABI 的库
3. sysfs-gpio
基于 sysfs 操作 gpio 的库, 原理如上面手动操作 sysfs 是一样的
4. gpio-utils
操作 gpio 的小工具程序, 基于
sysfs_gpio
Rust Demo (使用
cdev-gpio
)- list gpios
extern crate gpio_cdev; use gpio_cdev::*; fn main() { let chip_iterator = match chips() { Ok(chips) => chips, Err(e) => { println!("Failed to get chip iterator: {:?}", e); return; } }; for chip in chip_iterator { let chip = match chip { Ok(chip) => chip, Err(err) => panic!("Failed to open the chip: {:?}", err) }; println!( "GPIO chip: {}, \"{}\", \"{}\", {} GPIO Lines", chip.path().to_string_lossy(), chip.name(), chip.label(), chip.num_lines() ); for line in chip.lines() { match line.info() { Ok(info) => { let mut flags = vec![]; if info.is_kernel() { flags.push("kernel"); } if info.direction() == LineDirection::Out { flags.push("output"); } if info.is_active_low() { flags.push("active-low"); } if info.is_open_drain() { flags.push("open-drain"); } if info.is_open_source() { flags.push("open-source"); } let usage = if !flags.is_empty() { format!("[{}]", flags.join(" ")) } else { "".to_owned() }; println!( "\tline {lineno:>3}: {name} {consumer} {usage}", lineno = info.line().offset(), name = info.name().unwrap_or("unused"), consumer = info.consumer().unwrap_or("unused"), usage = usage, ); } Err(e) => println!("\tError getting line info: {:?}", e), } } println!(); } }
cdev-gpio 的接口中, 通过 chips() 获取 gpio控制器列表, D1 中的/dev/gpiochip0 和 /dev/gpiochip1
每个 chip 中有 lines 列表,就是控制器下的 gpio 列表 line 就是 gpio 对象, 可以 进行 set_value , get_value ,以及设定输入输出等操作
总体来说, Linux 对 gpio 封装已经很简单, rust 在这方面支持也比较完善. 后续文章还有 i2c, spi , uart 等方面的操作,欢迎关注,欢迎拍砖.
-
RE: 「RVBoards-哪吒」D1 Debian系统镜像和安装方法
@王哥 在 「RVBoards-哪吒」Allwinner D1 Debian系统镜像和安装方法 中说:
07e82d95f363afa8079047a197620511
是的 我已经下载 :blush:
-
RE: 「RVBoards-哪吒」D1 Debian系统镜像和安装方法
目前第一轮测试下来反馈一下:
测试环境无网络.- 无 lsusb 命令, 这个对硬件使用来说挺重要的
- WIFI 设备无法使用
- 无 iw* 相关iang命令
- connmanctl 无法正常操作 wifi 设备
- 蓝牙无法正常使用
- hcitool dev 无法找到设备
- 烧录系统的改进(这个应该在官方计划中)