LiCheePi_Zero底层开发
LiCheePi Zero底层开发
各种文件
烧录工具:sunxi-tools-spi-rebase.zip
根文件系统:buildroot-2019.08.tar.bz2
交叉编译器:gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf.tar.xz
Uboot:u-boot-3s-current.zip
- spi nor flash启动:u-boot-3s-spi-experimental.zip
主线Linux内核:linux-zero-4.10.y.zip、linux-zero-4.13.y.zip
BSP内核:v3s_lichee.zip( camdriod ,需要从中剥离出内核)
一、编译下载镜像
1. 编译及相关配置
1.1编译uboot
1 | #480x272LCD:LicheePi_Zero_480x272LCD_defconfig |
编译完成后,在当前目录下生成了u-boot-sunxi-with-spl.bin
自定义uboot配置
该部分属于自定义部分,可以直接采用默认配置
- Architecture select架构选择:ARM架构
- ARM architecture
- DDR相关配置
- LCD相关配置
- Boot images
- CPU clock frequency:时钟频率配置
- delay in seconds before automatically booting:开机等待时间的秒数
- SPL/TPL
- MMC raw mode: by sector 按扇区
- Address on the MMC to load U-Boot from mmc加载uboot的地址
- Support GPIO 支持GPIO
- Support I2C 支持I2C
- Support common libraries 支持通用lib
- Support disk paritions 支持分区
- Support generic libraries 支持一般lib库
- Support MMC 支持MMC
- Support power drivers 支持电源驱动
- Support serial 支持串口
修改代码支持SD卡
源码:u-boot-3s-current
增加uboot源码中include/configs/sun8i.h
:
1 |
修改代码支持SPI FLASH
源码:u-boot-3s-spi-experimental
如果flash大于16M,需要修改uboot配置,勾选flash bank支持选项( CONFIG_SPI_FLASH_BAR )
增加uboot源码中include/configs/sun8i.h
:
1 |
|
1.2编译Linux内核
1.2.1 主线内核
1 | #bsp内核中用到是arm-linux-gnueabi-,工具链为自带的4.6.3 |
编译完成后,zImage在arch/arm/boot/
下,驱动模块在INSTALL_MOD_PATH指定的目录下
使用SPI的uboot时需要配置
1 | Device Drivers |
设备树中添加spi flash节点信息
1 | &spi0 { |
内核代码drivers/mtd/devices/m25p80.c
中添加flash型号
1 | static const struct spi_device_id m25p_ids[] = { |
内核代码drivers/mtd/spi-nor/spi-nor.c
中添加flash型号
1 | static const struct flash_info spi_nor_ids[] = { |
- mkfs.jffs2 使用的最小擦除尺寸是8KB,而spi flash的扇区大小是4KB,所以按照扇区擦除的话,会无法使用,所以必须使用块擦除。 所以这里应该去掉SECT_4K
wifi使能配置
linux-zero-4.10.y.zip 内核才有rtl8723bs
wifi的驱动(默认支持,编译成模块)
1 | -> Device Drivers |
使能之后还需要rtl8723bs_nic.bin
wifi固件,放到 /lib/firmware/rtlwifi/
中,没有的话要自己建立。
1.2.2 BSP内核
-
解压v3s_lichee.zip, BSP内核源码在lichee/linux-3.4下
-
采用WhyCan上大佬的配置文件lichee_BSP_config :
cp lichee_BSP_config .config
-
配置内核
make ARCH=arm menuconfig
(下面这些均基于默认配置)-
使能USB摄像头驱动(已经使能):
1
2
3
4
5-> Device Drivers
-> Multimedia support (MEDIA_SUPPORT [=y])
-> Video capture adapters (VIDEO_CAPTURE_DRIVERS [=y])
-> V4L USB devices (V4L_USB_DRIVERS [=y])
-><M> USB Video Class (UVC) CONFIG_USB_VIDEO_CLASS -
使能DVP/MIPI摄像头(需选上ov2640):
1
2
3
4
5
6
7
8
9
10-> Device Drivers
-> Multimedia support (MEDIA_SUPPORT [=y])
-> Video capture adapters (VIDEO_CAPTURE_DRIVERS [=y])
-> V4L platform devices (V4L_PLATFORM_DRIVERS [=y])
-> SoC camera support (SOC_CAMERA [=y])
<M> ov2640 camera support(默认没有选上)
<M> ov5642 camera support
<M> sunxi video front end (camera and etc)driver
<M> v4l2 driver for SUNXI
<*> sunxi video encoder and decoder support -
由于camdriod原始的内核配置是为了在spi nor flash上运行而配置的,没有ext4支持,所以需要额外添加ext4支持(已经使能):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17File systems --->
<*> The Extended 4 (ext4) filesystem
[*] Use ext4 for ext2/ext3 file systems (NEW)
[*] Ext4 extended attributes (NEW)
[ ] Ext4 POSIX Access Control Lists (NEW)
[ ] Ext4 Security Labels (NEW)
[ ] EXT4 debugging support (NEW)
[ ] JBD2 (ext4) debugging support (NEW)
#支持大文件,以便于挂载文件系统
[*] Enable the block layer --->
[*] Support for large (2TB+) block devices and files
#加上CGROUPS支持
-> General setup
[*] Control Group support --->
#开启SWAP
-> General setup
[*] Support for paging of anonymous memory (swap) -
开启 FHANDLE 特性( 否则debian下会出现报错)(已经使能):
1
2-> General setup
[*] open by fhandle syscalls (EXPORTFS [=y]) -
开启wifi功能(AW_RF_PM功能未使能)
1
2
3
4
5
6
7
8
9#开启RTL8723BS的支持
-> Device Drivers
-> Network device support (NETDEVICES [=y])
-> Wireless LAN (WLAN [=y])
<M> Realtek 8723B SDIO WiFi
#开启AW_RF_PM功能
-> Device Drivers
-> Misc devices
[*] Allwinner rf module pm driver
-
-
-
编译并提取bsp内核
-
解压buildroot/dl/gcc-linaro.tar.bz2到lichee/out/sun8iw8p1/linux/common/buildroot/external-toolchain
1
2
3
4
5
6
7#在lichee目录下
mkdir -p out/sun8iw8p1/linux/common/buildroot/external-toolchain
tar --strip-components=1 -jxf buildroot/dl/gcc-linaro.tar.bz2 -C out/sun8iw8p1/linux/common/buildroot/external-toolchain
touch out/sun8iw8p1/linux/common/buildroot/external-toolchain/.installed
mv out/sun8iw8p1/linux/common/buildroot/external-toolchain/gcc-linaro/* out/sun8iw8p1/linux/common/buildroot/external-toolchain/
rm -rf out/sun8iw8p1/linux/common/buildroot/external-toolchain/gcc-linaro/ -
安装
lib32z1
解决64位的操作系统中没有32位的类库的问题
1
2sudo apt-get update
sudo apt-get install lib32z1- 在linux-3.4目录下执行:
1
2
3
4
5
6
7
8
9
10
11
12
13#使用解压的工具链
export PATH=../out/sun8iw8p1/linux/common/buildroot/external-toolchain/bin:../tools/pack/pctools/linux/android:$PATH
#清除上次编译
./scripts/build_tiger-cdr.sh clean
#直接编译
#后面的选项根据lichee\linux-3.4\arch\arm\configs\sun8iw8p1smp_defconfig来配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j16
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j16 modules
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j16 INSTALL_MOD_PATH=/home/null/Code/LiCheePi_Zero/Image/cam_image/rootfs modules_install
#LICHEE_JLEVEL:-j${LICHEE_JLEVEL}
#LICHEE_JLEVEL=8 ./scripts/build_tiger-cdr.sh
#./scripts/build_sun8iw8p1.sh all -
1.2.3 单独编译设备树
1 | make ARCH=arm licheepi_zero_defconfig |
如果设备树中有include包含关系,是不能够直接用dtc编译的,会报include相关的错。
DTC本身不支持#include语法,其正确语法为/include/
。
对于以下稍微复杂一点(包含#include,宏,*.h等)的设备树,以上的方法不免有些笨拙。
由于“#include”“宏”等都是C的特征,因此可以使用CPP(C Preprocessor)命令对dts源文件进行处理,完成文件包含与宏置换的工作。
用下面的脚本dts2dtb.sh可以实现目的:
1 | #/bin/bash |
1.2.4 反编译设备树
1 | ./scripts/dtc/dtc -I dtb -O dts -o ../sun8i-v3s-licheepi-zero.dts ./arch/arm/boot/dts/sun8i-v3s-licheepi-zero.dtb |
1.3buildroot编译根文件系统
1 | make menuconfig |
配置
1 | target options |
编译
1 | sudo make -j8 |
编译完成后会在 buildroot/output/images 下生成根文件系统
修改控制台颜色
编辑/etc/profile
文件,修改如下语句
1 | if [ "$PS1" ]; then |
使能配置
1 | source /etc/profile |
构建完后,想要取消登录密码
(在buildroot的menuconfig中可以配置取消密码,这里是假设是配置完毕后想要取消)
- 方法一(参考官方手册)
修改/etc/inittab:
1 | ttyS0::respawn:/root/logintest -L ttyS0 115200 vt100 |
新建logintest:
1 |
|
自启动任务在/etc/init.d/rcS中加入即可
export 相关环境变量在/etc/profile中加入。
- 方法二
找到 /etc/inittab
文件的
1 | console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL |
修改为:
1 | console::respawn:-/bin/sh |
flash启动
flash启动时需要jffs2格式的文件系统, 所以需要使用此rootfs制作jffs2文件系统镜像
下载jffs2文件系统制作工具
1 | apt-get install mtd-utils |
解压 rootfs.tar
1 | mkdir rootfs |
安装内核模块
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16 modules |
计算好jffs的大小,总空间是16M-1M-64K-4M=0xAF0000
1 | #页大小0x100 256字节 |
删除rootfs文件夹
1 | rm -rf rootfs/ |
或者重新打包回rootfs.tar
1 | tar -cvf rootfs.tar ./rootfs |
添加wifi工具库(这里有好几个库,推荐用buildroot )
单独编译wireless tools工具集
修改Makefile中的 CC、 AR 和 RANLIB 这三个变量:
1 | ## Compiler to use (modify this for cross compile). |
-
清理:make clean
-
编译:make
编译完成以后就会在当前目录下生成 iwlist、 iwconfig、 iwspy、 iwpriv、 ifrename 这 5 个工
具,另外还有很重要的 libiw.so.29 这个库文件。将这 5 个工具拷贝到开发板根文件系统下的
/usr/bin 目录中 ,将 libiw.so.29 这个库文件拷贝到开发板根文件系统下的/usr/lib 目录中
1 | sudo cp iwlist iwconfig iwspy iwpriv ifrename /home/null/Code/LiCheePi_Zero/Image/spi_image_wifi/rootfs/usr/bin -f |
编译。。。。工具集(以后再写)
buildroot 编译
配置使能
1 | -> make menuconfig |
2. 下载
2.1 SD卡
2.1.1 修改uboot源码设置启动命令
参考1.1节修改uboot源码后可以直接启动,无需boot.scr和script.bin这两个文件
2.1.2 利用boot.scr和script.bin
在不修改uboot源码的情况下,可以通过两个启动参数文件:boot.scr
和script.bin
来配置
boot.src是什么
根据资料描述 https://github.com/linux-sunxi/u-boot-sunxi/wiki#bootscr-support,u-boot在启动的时候会在第一个分区(FAT/extX格式)寻找/boot.scr或者/boot/boot.scr文件,boot.scr中可以包含用于载入script.bin,kernel,initrd(可选)以及设置内核启动参数的uboot命令。 (相当于uboot的bootcmd启动命令)
boot.src如何生成
在工作目录新建 boot.cmd
文件,添加以下内容(即uboot启动命令):
1 | setenv bootargs console=ttyS0,115200 panic=5 rootwait root=/dev/mmcblk0p2 earlyprintk rw vt.global_cursor_default=0 |
详细解释:
1.上述第一行设置uboot的bootargs启动参数,格式为 参数=值,不同参数使用空格分开,其中
- console=ttyS0,115200 含义为使用特定的串口ttyS0,波特率为 115200
- noinitrd 含义为不使用ramdisk(内存磁盘)
- init=/init 含义为内核启起来后,进入系统中运行的第一个脚本
- root=/dev/mmcblk0p2 含义为指定rootfs的位置为TF卡第二个分区
- rootfstype=ext4 含义为根文件系统类型
- rootwait 含义为等待设备/dev/mmcblk0p2就绪后才尝试挂载rootfs
- panic=5 传递内核参数,当遇到panic(内核严重错误)时等待5秒后重启
更多的参数可以通过查看Linux内核源码目录下Documentation/kernel-parameters.txt
文件了解
2.第二行和第三行为将script.bin
和内核uImage
加载到指定内存地址。fatload是U-Boot中装载linux kernel 到内存的指令(这里用到oad)。
基本用法:fatload <interface> <dev[:part]> <addr> <filename> <bytes>
- interface:所用到接口,如:MMC、USB
- dev [:part]: 文件存放的设备 如:ide 0:1
- addr: 装载到内存的开始地址。
- filename: 装载的文件名称。
- bytes: copy的字节数.
3.第四行bootm 用于将内核映像加载到指定的地址
保存文件后,执行以下命令生成boot.scr:
1 | mkimage -C none -A arm -T script -d boot.cmd boot.scr |
script.bin是什么
script.bin是被全志SOC内核驱动或LiveSuit使用的针对特定目标板的二进制配置文件,包含如何设置基于芯片的各种外设,端口,I/O针脚信息。 (相当于设备树,有该信息后就不需要设备树文件了,具体由script.bin还是设备树指定由boot.src中的命令决定)
其对应的可读文本文件格式为FEX,可以利用 Sunxi-tools在二进制和文本文件之间进行转换。更多关于FEX配置的信息可以参考 http://linux-sunxi.org/Fex_Guide
script.bin如何生成
首先需要编译sunxi-tools,得到fex2bin
、bin2fex
等文件,其中fex2bin
能把 *.fex 文件生成 *.bin文件。反之bin2fex
可以将得到的*.bin文件生成可读的*.fex文件。
1 | fex2bin sys_config.fex script.bin |
2.1.3 分区及下载
ubuntu上商店中下载 gparted
,利用该软件对SD卡进行格式化并分区
如果SD卡上已经有分区需要先umount卸载文件系统,再格式整个SD卡后才能确保操作无误
-
将uboot写入到sd卡8k偏移处:
1
sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdb bs=1024 seek=8
-
建立第一个分区,大小32M(可以随意填写),格式FAT16,把zImage,sun8i-v3s-licheepi-zero.dtb, boot.src(如果有),script.bin(如果有) 拷贝到这个分区
-
建立第二个分区,用尽剩余空间,格式ext4,把buildroot产生的rootfs.tar解压到该分区根目录
1
2
3sudo tar -xvf output/images/rootfs.tar -C /挂载的tf卡第二个分区目录
#如:
sudo tar -xvf output/images/rootfs.tar -C /media/null/rootfs/
2.2 SPI FLASH
分区情况
分区序号 | 起始地址 | 长度 | 镜像名字 | 内容 |
---|---|---|---|---|
mtd0 | 0x0 | 0x0100000(1M) | u-boot-sunxi-with-spl.bin | uboot |
mtd1 | 0x0100000 | 0x0010000(64k) | sun8i-v3s-licheepi-zero.dtb | dtb |
mtd2 | 0x0110000 | 0x0400000(4M) | zImage | kernel |
mtd3 | 0x0510000 | 0x100 0000-0x0510000=0xAF 0000剩余 | jffs2.img | rootfs |
生成img镜像
1 | # !/bin/sh |
执行完毕后生成镜像文件 flashimg.bin
烧写镜像
安装sunxiflash烧写工具
-
下载
1
git clone -b spi-rebase https://github.com/Icenowy/sunxi-tools.git
-
编译安装
1
2
3#安装 libusb-1.0-0-dev 以防止报错
sudo apt-get install libusb-1.0-0-dev
make && sudo make install
进入下载(fel)模式
Zero有一个usb下载模式称为fel模式,进入fel模式有下面几种方式:
- TF卡和spi flash 同时没有可启动镜像;
- TF卡中有进入fel模式的特殊固件 fel-sdboot.sunxi
- 如果spiflash已经有了启动镜像,那么需要在TF卡中烧入一个sunxi提供的启动工具 (
dd if=fel-sdboot.sunxi of=/dev/mmcblk0 bs=1024 seek=8
), 插入TF卡后就会启动会进入fel模式;
- 如果spiflash已经有了启动镜像,那么需要在TF卡中烧入一个sunxi提供的启动工具 (
- 上电时SPI_MISO拉低到地(记得松开,否则一直检测不到flash)
下载
1 | sudo sunxi-fel version #查看连接的cpu信息 |
dd命令详解
dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
if
=文件名:输入文件名,缺省为标准输入。即指定源文件。<if=inputfile>of
=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >- ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。
obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。
bs
=bytes:同时设置读入/输出的块大小为bytes个字节。 - cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。
- skip=blocks:从输入文件开头跳过blocks个块后再开始复制。
seek
=blocks:从输出文件开头跳过blocks个块后再开始复制。
(注意:通常只用当输出文件是磁盘或磁带时才有效,即备份到磁盘或磁带时才有效。count
=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。- conv=conversion:用指定的参数转换文件。
- ascii:转换ebcdic为ascii
- ebcdic:转换ascii为ebcdic
- ibm:转换ascii为alternateebcdic
- block:把每一行转换为长度为cbs,不足部分用空格填充
- unblock:使每一行的长度都为cbs,不足部分用空格填充
- lcase:把大写字符转换为小写字符
- ucase:把小写字符转换为大写字符
- swab:交换输入的每对字节
- noerror:出错时不停止
- notrunc:不截短输出文件
- sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。
/dev/null和/dev/zero的区别
/dev/null
,外号叫无底洞,你可以向它输出任何数据,它通吃,并且不会撑着!/dev/zero
,是一个输入设备,你可你用它来初始化文件。该设备无穷尽地提供0,可以使用任何你需要的数目——设备提供的要多的多。他可以用于向设备或文件写入字符串0。
二、开发环境搭建
Root用户:
1 | 用户名: root |
存储空间查看
1 | df -h |
rtl8723bs无线网卡搭建
前提条件:
rtl8723bs_nic.bin
wifi固件- wifi工具库
r8723bs.ko
驱动(4.13主线内核自带,默认以模块方式安装)
步骤:
-
ifconfig -a
命令查看当前系统中的全部网卡 -
安装驱动后执行
ifconfig wlan0 up
启动网卡 -
修改或创建配置文件
/etc/wpa_supplicant.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=0
ap_scan=1
network={
#wifi名字
ssid="debugdump"
scan_ssid=1
key_mgmt=WPA-EAP WPA-PSK IEEE8021X NONE
pairwise=TKIP CCMP
group=CCMP TKIP WEP104 WEP40
#wifi秘密
psk="13800138000"
priority=5
} -
使能配置
wpa_supplicant -B -d -i wlan0 -c /etc/wpa_supplicant.conf
-
开启wifi
udhcpc -i wlan0
三、移植OPENCV(3.4.1)
默认使用的是6.3.1的gcc
下载烧录V3s_linux_4_2_0_ov2640_debugdump.bin镜像
1 | sudo dd of=/dev/sdX if=V3s_linux_4_2_0_ov2640_debugdump.bin |
用户名root,密码root
该镜像中rootfs只有90M左右,需要用到linux主机下gparted 软件将rootfs进行扩展
测试相机
1 | #ov2640可用 |
配置编译OPENCV
-
linux主机下打开cmake的gui
1
sudo cmake-gui
-
填写配置信息
- 取消
WITH_GTK
- 取消
WITH_TIFF
- 修改安装路径
CMAKE_INSTALL_PREFIX
为/home/null/Code/OpenCV/install_opencv - 配置
CMAKE_EXE_LINKER_FLAGS
为 -lpthread -lrt -ldl - 其他详见https://blog.csdn.net/Guet_Kite/article/details/78667175
- 取消
-
sudo make
编译 -
make install
将opencv编译出的库文件安装到CMAKE_INSTALL_PREFIX设置的目录下 -
将安装目录下的/lib/下的所有lib文件拷贝至开发板/lib下
1
2
3
4
5cp -a /home/null/Code/OpenCV/nfs_code/opencvlib/* /home/null/Code/LiCheePi_Zero/Image/cam_image/rootfs/lib
#-a :相当于 -pdr 的意思(参数pdr分别为:保留权限,复制软链接本身,递归复制);
#-p :连同档案的属性一起复制过去,而非使用预设属性;
#-d :若来源文件为连结文件的属性(link file),则复制连结文件属性而非档案本身;
#-r :递归持续复制,用于目录的复制行为;
OPENCV开发
makefile
1 | CC = arm-linux-gnueabihf-g++ |
测试文件
1 |
|
- 由于编译的时候没有加入
WITH_GTK
选项,因而在板卡上执行的程序无法使用函数imshow
、waitKey
等函数。
上面例子中的girl.jpg:
fly.jpg:
合成后的图片:
四、驱动编写
暂时采用官方镜像,镜像名字为nanopi-neo-core_sd_friendlycore-xenial_4.14_armhf_20190823.img
,该镜像中采用的rootfs为UbuntuCore 18.04
写在前面
内核版本查看方法
1 | cat /proc/version |
挂载pc上文件系统
1 | mount -t nfs -o nolock 192.168.2.101:/home/null/Code/NanoPi_NEO_Core/nfs /mnt |
挂载报错尝试安装nfs-common
报错:
1
2
3
4
5
6
7 mount: wrong fs type, bad option, bad superblock on 192.168.2.101:/home/null/nfs_root,
missing codepage or helper program, or other error
(for several filesystems (e.g. nfs, cifs) you might
need a /sbin/mount.<type> helper program)
In some cases useful info is found in syslog - try
dmesg | tail or so.安装nfs-common:
1 sudo apt-get install nfs-common
pc端检查是否配置成功(自己挂接自己)(绝对路径)
1 | sudo mount -t nfs -o nolock localhost://home/null/Code/NanoPi_NEO_Core/nfs /mnt/nfs#挂接 |
一键挂载脚本
1 |
|
记得chmod +x
卸载脚本
1 |
|
记得chmod +x
更换源
参考: http://mirrors.ustc.edu.cn/help/ubuntu-ports.html
修改 /etc/apt/sources.list
文件中镜像源服务器地址 http://ports.ubuntu.com/ 为 http://mirrors.ustc.edu.cn/ubuntu-ports/
1 | deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse |
更新源:
1 | sudo apt update |
1.按键驱动程序
1.1修改设备树
编译设备树
1 | make dtbs ARCH=arm CROSS_COMPILE=arm-linux- -j8 |
查看系统中是否存在对应节点
1 | ls /proc/device-tree/ |
设备树中的GPIO节点
假设PA6和PA7上接入两个LED,通过编写一个GPIO的驱动程序来控制LED的亮灭,在设备树文件sun8i-h3-nanopi-m1.dts的根节点增加一个myleds子节点 :
1 | myleds { |
节点中的led-gpios属性,引用了pinctrl信息,sunxi-h3-h5.dtsi中有关于该控制器的描述,
pio: pinctrl@01c20800 {
/* compatible is in per SoC .dtsi file */
reg = <0x01c20800 0x400>;
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
#gpio-cells = <3>;
interrupt-controller;
#interrupt-cells = <3>;
......
};
myleds子节点描述了两个GPIO信息,具有多个gpio口信息的属性值: <&gpio控制器节点名 具体gpio口的标识符>,<&gpio控制器节点名 具体gpio口的标识符> …;
具体gpio口的标识符是由多个数字组成, 数字的个数由所用的gpio控制器节点里的#gpio-cells属性值指定.。全志H3 共有七组GPIO ,也即七个bank,分别为PA (PA0-21),PC(PC0-16) PD (PD0-17), PE(PE0-15),PF(PF0-6) PG(PG0-13) ,PL(PL0-PL11),在内核中PA-PG是一个pinctrl控制器,而PL是另一个pinctrl控制器 。
由#gpio-cells = <3>可以推出GPIO属性需要用3个u32的数据描述,第一个参数代表该GPIO位于哪个bank,第二个参数代表该bank下的序号,第三个参数代表默认电平,myleds节点下的GPIO_ACTIVE_HIGH这个宏就定义于include/dt-bindings/gpio/gpio.h
1.2编写驱动
1.2.1检查注册的驱动
-
驱动安装好后,会在
/dev/
目录下创建同名目录 -
或者通过
cat /proc/devices
命令查看多出来的驱动
1.2.2gpiolib及gpio操作
经典接口
gpio_request
:驱动中要想使用某一个gpio,就必须先调用gpio_request接口来向内核申请,得到允许后才可以去使用这个gpiogpio_free
: 对应gpio_request,用来释放申请后用完了的gpiogpiochip_is_requested
: 接口用来判断某一个gpio是否已经被申请了gpio_direction_input
/gpio_direction_output
: 接口用来设置GPIO为输入/输出模式(不推荐直接设置寄存器)
- 一般来说,gpio的资源申请和释放操作应该放在驱动模块的加载与卸载函数内
- gpio_request的第一个参数是需要申请的gpio号。第二个参数是我们给该gpio起个名字
- 申请完了之后对这个gpio进行模式设置,我们这里用gpio_direction_output 设置成输出模式,并且默认输出1让led灭
新接口
devm_gpio_request_one
: 自带初始化电平功能,并且会在模块卸载时自动释放gpio,十分简便
读写gpio
gpio_get_value
:读gpiogpio_set_value
:写gpio
控制台中查看当前gpio占用情况的方法
内核中提供了虚拟文件系统debugfs,里面有一个gpio文件,提供了gpio的使用信息
- 使用
mount -t debugfs debugfs /tmp
把debugfs挂接到/tmp下,再重新进入/tmp后就能看到一个名为gpio的文件 cat /tmp/gpio
即可得到gpio的所有信息,使用完后umount /tmp
卸载掉debugfs
1.3调试
printk
-
查看
printk
输出的日志:通过
dmesg
和more
、tail
、less
、grep
的结合查看日志1
2
3
4
5
6
7
8
9
10#输出全部信息
dmesg
#输出前20行
dmesg | head -20
#输出后20行
dmesg | tail -20
#输出包含usb的信息(-i:忽略大小写)
dmesg | grep -i usb
#清空dmesg环形缓冲区中的日志
dmesg -c -
或者直接设置日志的输出的等级:
1
2
3echo $level > /proc/sys/kernel/printk#默认为4 4 1 7
#如:
echo 8 4 1 7 > /proc/sys/kernel/printk#所有级别日志均可打印输出/proc/sys/kernel/printk中对应的四个数分别对应于:
- ①控制台日志级别:优先级高于该值的消息将被打印至控制台。
- ②缺省的消息日志级别:将用该值来打印没有优先级的消息。
- ③最低的控制台日志级别:控制台日志级别可能被设置的最小值。
- ④缺省的控制台:控制台日志级别的缺省值。
日志缓冲区的每一行文本开头具有级别标记, 级别值越小则优先级越高.
系统定义了8个消息级别, 级别号从0到7分别为:
-
致命级(KERN_EMESG),
-
警戒级(KERN_ALERT),
-
临界级(KERN_CRIT),
-
错误级(KERN_ERR),
-
告警级(KERN_WARN),
-
注意级(KERN_NOTICE),
-
通知级(KERN_INFO),
-
调试级(KERN_DEBUG).
后台运行
-
&
当在前台运行某个作业时,终端被该作业占据;可以在命令后面加上& 实现后台运行。例如:sh test.sh &
当成功地提交进程以后,就会显示出一个进程号,可以用它来监控该进程,或杀死它。(
ps -ef | grep 进程号
或者kill -9 进程号
) -
nohup
使用&命令后,作业被提交到后台运行,当前控制台没有被占用,但是一但把当前控制台关掉(退出帐户时),作业就会停止运行。nohup命令可以在你退出帐户之后继续运行相应的进程。nohup就是不挂起的意思( no hang up)。该命令的一般形式为:
1
nohup command &
如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件:
1
nohup command > myout.file 2>&1 &
使用了nohup之后,很多人就这样不管了,其实这样有可能在当前账户非正常退出或者结束的时候,命令还是自己结束了。所以在使用nohup命令后台运行命令之后,需要使用exit正常退出当前账户,这样才能保证命令一直在后台运行。
-
ctrl + z
可以将一个正在前台执行的命令放到后台,并且处于暂停状态。
-
jobs
查看当前有多少在后台运行的命令。
jobs -l
选项可显示所有任务的PID,jobs的状态可以是running, stopped, Terminated。但是如果任务被终止了(kill),shell 从当前的shell环境已知的列表中删除任务的进程标识。
2.内核定时器
3.中断
上半部和下半部
-
软中断
软中断必须在编译的时候静态注册
- open_sotfirq:打开软中断
- raise_sotfirq:触发软中断
-
tasklet:利用软中断来实现的另一种下半部机制,在软中断和tasklet之间建议选tasklet
- tasklet_init:初始化tasklet_struct结构体,或直接使用宏DECLARE_TASKLET定义+初始化
- tasklet_schedule:一般在上半部中调用,使要调度的tasklet在合适的时候运行
-
工作队列:在进程上下文执行,允许睡眠或重新调度(下半部可以睡眠就可以交给工作队列),要执行的工作会交给一个内核工作者线程去执行
- 宏INIT_WORK:初始化工作
- 宏DECLARE_WORK:定义+初始化工作
- schedule_work:类似tasklet_schedule,调度传入的工作
3.1修改设备树
参考内核绑定信息:Documentation/devicetree/bindings/arm/gic.txt
对于一般的ARM处理器来说,#interrupt-cells=<3>
对应的三个cell分别为:
- 中断类型
- 0:SPI中断
- 1:PPI中断
- 中断号
- SPI:0~987
- PPI:0~15
- 标志
- bit[3:0]:中断触发类型
- 1:上升沿
- 2:下降沿
- 4:高电平
- 8:低电平
- bit[15:8]:PPI中断的CPU掩码
- bit[3:0]:中断触发类型
相关函数
- irq_of_parse_and_map:从设备树中中断节点中的interrupts属性提取对应的中断号
- gpio_to_irq:获取gpio对应的中断号
编译设备树
1 | make dtbs ARCH=arm CROSS_COMPILE=arm-linux- -j8 |
查看系统中是否存在对应节点
1 | ls /proc/device-tree/ |
设备树中的中断节点
- interrupt-parent = <&pio>;//属于pio中断控制器
- interrupts = <0 7 IRQ_TYPE_EDGE_BOTH>;
- 第一个参数:第几个GPIO(这里是GPIOA)
- 第二个参数:第几个外部中断
- 第三个参数:触发沿
3.2编写驱动
3.3调试
中断注册成功后可以通过以下命令来查看是否注册到系统中:
1 | cat /proc/interrupts |
4.input子系统
4.1修改设备树
如果需要使用内核自带的gpio_keys.c
驱动,可以参考内核中Documentation/devicetree/bindings/input/gpio-keys.txt
,需要设备树中的节点满足以下要求:
-
节点名字为
“gpio-keys”
-
gpio-keys节点的compatible属性必须为
“gpio-keys”
-
所有的按键都是gpio-keys的子节点,并可以使用如下属性描述自己:
- gpios:按键连接的gpio信息
- interrupts:按键使用的中断信息(可选)
- lable:按键名字
- linux,code:要模拟的按键值
-
如果要支持连按的话需要加入
autorepeat
属性例子:
1
2
3
4
5
6
7
8
9
10gpio_keys {
compatible = "gpio-keys";
autorepeat;//支持连按
k1 {
label = "k1";//按键名称
linux,code = <KEY_POWER>;//要模拟的按键值
gpios = <&pio 0 7 GPIO_ACTIVE_LOW>;//按键连接的gpio信息
interrupts = <0 7 IRQ_TYPE_EDGE_BOTH>;//中断信息 PA7 --> EINT7
};
};
4.2编写驱动
Linux中已经实现了input输入子系统框架,因此无需再次注册设备,系统中input框架中的所有设备的主设备号均为13
- input设备结构体
input_dev
- evbit成员:输入事件类型,可选事件见下文
- keybit成员:按键值
- relbit成员:相对坐标
- absbit成员:绝对坐标
- …
设置input_dev结构体中成员的方法:
- __set_bit
- BIT_MASK宏
- input_set_capability:仅仅设置keybit成员
相关函数
- input_allocate_device:申请一个input_dev结构体
- input_free_device:释放申请到的input_dev结构体
- input_register_device:向内核注册input_dev结构体
- input_unregister_device:注销input_dev结构体
- input_event:上报指定事件及对应的值
- input_report_key:封装一层,专门上报按键事件
- input_report_rel
- input_report_abs
- input_report_ff_status
- input_report_switch
- input_mt_sync
- input_sync:同步事件
input_dev的相关事件
- EV_SYN:同步
- EV_KEY:按键
- EV_REL:相对坐标
- EV_ABS:绝对坐标
- EV_MSC:杂项(其他)
- EV_SW:开关
- EV_LED:LED
- EV_SND:声音(sound)
- EV_REP:重复事件
- EV_FF:压力
- EV_PWR:电源
- EV_FF_STATUS:压力状态
4.3调试
input设备注册成功后可以通过以下命令来查看是否注册到系统中:
1 | ls /dev/input/ -l |
通过以下命令查看是否有效:
1 | busybox hexdump /dev/input/event1 |
5.IIC框架驱动
5.1设备树
clock-frequency
属性:iic总线的速度(默认100000,即100k)- status:是否使能该总线
- okay
- disabled
- iic设备节点需要写到iic总线节点中,作为总线节点的子节点
- 总线节点需要利用
pinctrl-names = "default";
和pinctrl-0
指定iic引脚(已经设置好) - iic控制器节点
compatible
属性必须为allwinner,sun6i-a31-i2c或allwinner,sun4i-a10-i2c,这里在sunxi-h3-h5.dtsi中已经设置好,无需再次设置
设备树修改成功后可以通过以下命令来查看是否注册到系统中:
1 | #查看设备节点,该目录下放着所有的i2c设备 |
5.2驱动
基本数据结构
- IIC总线结构体
i2c_bus_type
- match函数:驱动和设备匹配是调用该函数来决定是否匹配
- IIC适配器(控制器)驱动结构体
i2c_adapter
- algo:总线访问算法成员
- master_xfer:IIC适配器的传输函数
- smbus_xfer:SMBUS总线传输函数
- algo:总线访问算法成员
- IIC设备结构体
i2c_client
- addr:芯片地址,存在低7位
- name:名字
- adapter:对应的IIC适配器
- dev:设备结构体
- irq:中断
- IIC设备驱动结构体
i2c_driver
- probe函数:IIC设备和驱动匹配后调用,初始化用
- remove函数:同理,卸载用
- id_table:传统方式匹配ID列表
- driver:
- owner:所有者,一般为宏THIS_MODULE
- name:名字
- of_match_table:设备树匹配列表
- IIC发送数据结构体
i2c_msg
- addr:从机地址
- flags:标志
- len:消息长度
- buf:消息数据
相关函数
- IIC适配器(控制器)
- i2c_add_adapter:向系统注册i2c_adapter成员,使用动态总线号
- i2c_add_numbered_adapter:向系统注册i2c_adapter成员,使用静态总线号
- i2c_del_adapter:删除IIC适配器
- SPI设备
- i2c_register_driver / i2c_add_driver:注册i2c_driver
- i2c_del_driver:卸载i2c_driver
- 传输数据
- i2c_transfer:传输i2c_msg类型数据
- i2c_master_send:IIC发送缓存中的数据,最后会调用i2c_transfer
- i2c_master_recv:IIC接收数据到缓存中,最后会调用i2c_transfer
5.3调试
i2c-tools工具
得益于ubuntucore,直接运行sudo apt install i2c-tools
安装i2c-tools工具
- 查询i2c总线
sudo i2cdetect -l
- 检测i2c地址
sudo i2cdetect -r -y 0
(最后的0为i2c-0总线)
6.SPI框架驱动
6.1设备树
spi-max-frequency
属性:spi总线的最大速度(默认100000,即100k)- status:是否使能该总线
- okay
- disabled
- spi设备节点需要写到spi总线节点中,作为总线节点的子节点
- 通过
cs-gpios
属性指定CS引脚 - spi设备节点中
reg
和节点@
后的数字均为该设备所使用的spi通道(一般0即可) - 总线节点需要利用
pinctrl-names = "default";
和pinctrl-0
指定spi引脚(已经设置好) - spi控制器节点
compatible
属性必须为allwinner,sun8i-h3-spi或allwinner,sun6i-a31-spi,这里在sunxi-h3-h5.dtsi中已经设置好,无需再次设置
设备树修改成功后可以通过以下命令来查看是否注册到系统中:
1 | #查看设备节点,该目录下放着所有的i2c设备 |
6.2驱动
基本数据结构
- SPI总线结构体
spi_bus_type
- match函数:驱动和设备匹配是调用该函数来决定是否匹配
- SPI主机驱动结构体
spi_master
- transfer函数:控制器数据传输函数
- transfer_one_message函数:控制器数据传输函数,一次发一个spi_message包
- SPI设备驱动结构体
spi_driver
- probe函数:SPI设备和驱动匹配后调用,初始化用
- remove函数:同理,卸载用
- id_table:传统方式匹配ID列表
- driver:
- owner:所有者,一般为宏THIS_MODULE
- name:名字
- of_match_table:设备树匹配列表
- SPI传输信息结构体
spi_transfer
- tx_buf:发送数据
- rx_buf:接收数据
- len:数据长度
- SPI消息结构体
spi_message
,由spi_transfer
组成- complete函数:异步传输完成时调用
相关函数
- SPI主机
- spi_alloc_master:申请spi_master结构体
- spi_master_put:释放spi_master结构体
- spi_register_master:注册
- spi_unregister_master:注销
- SPI设备
- spi_register_driver:注册
- spi_unregister_driver:卸载
- 数据传输
- spi_message_init:初始化spi_message结构体
- spi_message_add_tail:将spi_transfer添加到spi_message队列中
- spi_sync:同步传输(会阻塞并等待SPI数据传输完成)
- spi_async:异步传输(传输完成后会调用spi_message.complete函数)
6.3调试
7.RGBLCD驱动
Zero默认支持800x480和480x272这两种常见分辨率的的RGB屏幕。这两种分辨率的屏幕,直接在编译时候选择对应的分辨率即可。如果需要修改,可以参考官方手册 https://www.kancloud.cn/lichee/lpi0/519472 修改uboot。这里采用的是480*242的RGBLCD。
前提条件:Uboot中需要配置好LCD驱动。设备树种会规定chose节点用于uboot和linux之间传递参数,simple-fb的参数也是在这里传递。 所以只需要uboot中设置屏幕没问题,内核即可正常显示
7.1设备树
1.添加framebuffer
参考内核中的设备树绑定文件:\Documentation\devicetree\bindings\display\simple-framebuffer-sunxi.txt
需要写到chosen
节点中
必要属性:
- compatible: “allwinner,simple-framebuffer”
- allwinner,pipeline为以下几个中的一个
- “de_be0-lcd0”
- “de_be1-lcd1”
- “de_be0-lcd0-hdmi”
- “de_be1-lcd1-hdmi”
例子:
-
官方例子
1
2
3
4
5
6
7
8
9
10
11
12
13chosen {
#address-cells = <1>;
#size-cells = <1>;
ranges;
framebuffer@0 {
compatible = "allwinner,simple-framebuffer", "simple-framebuffer";
allwinner,pipeline = "de_be0-lcd0-hdmi";
clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 43>,
<&ahb_gates 44>;
status = "disabled";
};
}; -
zero例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14chosen {
#address-cells = <1>;
#size-cells = <1>;
ranges;
simplefb_lcd: framebuffer@0 {
compatible = "allwinner,simple-framebuffer",
"simple-framebuffer";
allwinner,pipeline = "de0-lcd0";
clocks = <&ccu CLK_BUS_TCON0>, <&display_clocks 0>,
<&display_clocks 6>, <&ccu CLK_TCON0>;
status = "disabled";
};
};
2.添加屏幕节点
不知道从哪个版本开始,linux内核需要专门的 panel 节点(在根节点下)才行,具体的配置文件的选取可以从 linux ‣ drivers ‣ gpu ‣ drm ‣ panel
文件夹下选取,具体的 compatible 属性从上述文件夹下的panel-simple.c
文件中的匹配表platform_of_match
中选取
具体的配置可以参考内核帮助文档:\Documentation\devicetree\bindings\display\panel\simple-panel.txt
、\Documentation\devicetree\bindings\display\panel\panel-common.txt
、\Documentation\devicetree\bindings\graph.txt
这里用的是480*272的分辨率的屏幕,具体的 panel 节点配置如下:
1 | panel: panel { |
这里解释下ports、port和endpoint(\Documentation\devicetree\bindings\graph.txt)
Ports are described by child ‘port’ nodes contained in the device node.
Each port node contains an ‘endpoint’ subnode for each remote device port connected to this port. If a single port is connected to more than one remote device, an ‘endpoint’ child node must be provided for each link.
If more than one port is present in a device node or there is more than one endpoint at a port, or a port node needs to be associated with a selected hardware interface, a common scheme using ‘#address-cells’, ‘#size-cells’ and ‘reg’ properties is used to number the nodes.端口s由设备节点中包含的子“端口”节点描述。
每个端口节点都为连接到该端口的每个远程设备端口包含一个“端点”子节点。如果单个端口连接到多个远程设备,则必须为每个链接提供一个“端点”子节点。
如果设备节点中存在多个端口,或者某个端口处有多个端点,或者需要将端口节点与选定的硬件接口相关联,则使用“#address-cells”,“#size-cells”和’reg’属性的通用方案用于编号节点。All ‘port’ nodes can be grouped under an optional ‘ports’ node, which allows to specify #address-cells, #size-cells properties for the ‘port’ nodes independently from any other child device nodes a device might have.
所有“端口”节点都可以分组在一个可选的“端口s”节点下,该节点允许独立于设备可能具有的任何其他子设备节点,为“端口”节点指定#address-cells,#size-cells属性。
tcon0_out_lcd节点:
1 | //tcon0_out节点是液晶控制器节点tcon0的一个输出端口(port) |
同时这里的tcon
节点需要绑定上lcd对应的io口
1 | &tcon0 { |
lcd_rgb666_pins节点:
1 | pio: pinctrl@1c20800 { |
7.3调试
设备树可以通过以下方式检测:
1 | #会发现framebuffer节点后面被填充了实际地址 |
屏幕测试方法:
-
linux系统启动后,存在/dev/fb0节点:
1
cat /dev/urandom > /dev/fb0
-
改变uboot的启动参数
bootargs
,从屏幕输出启动信息1
2setenv bootargs 'console=tty1 console=ttyS0,115200 panic=5 rootwait root=/dev/mmcblk0p2 earlyprintk rw vt.global_cursor_default=0'
boot