大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE稳定放心使用
信息
当前版本作者联系方式(长期有效):E-mail: WindForest@yeah.net
当前版本:2022-4-16-R1.0-预发布-问题意见征集
5 常见外设操作
本章介绍了一些常见且常用外设的使用方法,你可以根据需要选择性练习。
5.1 USB无线网卡
本节以RTL8188EUS为例讲述如何使用内核中提供的驱动,使内核能够识别该设备,同时给出使用该网卡连接路由器的过程示例。
[前置依赖]
本节操作将直接使用 4.1 wpa_supplicant 章节中得到的二进制文件。
[说明]
本章节以网卡用作STA端为示例,用作AP的示例可阅读 4.2 hostapd 章节引文中相应内容,或查阅网上相关资料。
5.1.1 在内核中开启驱动支持
在内核目录下执行 menuconfig
进入内核配置界面。
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
进行以下修改:
-
Networking support
-
选择“RF switch subsystem support(射频开关子系统支持)”。
说明:该项用于避免wpa_supplicant出现“rfkill: Cannot open RFKILL control device”错误。
-
-
Networking support -> Wireless
- 选择“cfg80211 – wireless configuration API”项,将其编译到内核;
- 选择“cfg80211 wireless extensions compatibility”项;
- 选择“Generic IEEE 802.11 Networking Stack (mac80211)”项,将其编译到内核。
-
Device Drivers -> Network device support
- 选择“Wireless LAN”项,将其编译到内核,并取消选择其中所有子项。
-
Device Drivers
- 选择“Staging drivers”项,并选择其中的子项“Realtek RTL8188EU Wireless LAN NIC driver”,将其编译到内核。
保存并退出,重新编译内核镜像。
5.1.2 准备驱动需使用到的固件文件
内核中自带的RTL8188EUS驱动在挂载器件时需要对应的二进制固件,该固件可与开发机Linux中提供的通用。将开发机的 /lib/firmware/rtlwifi/rtl8188eufw.bin 文件拷贝到开发板同目录下(没有则新建)即可。
5.1.3 使用wpa_supplicant连接到热点
在开发板端建立wpa_supplicant运行配置文件 /etc/wpa.conf ,配置示例如下:
# 项目仅需要wpa_supplicant因此该控制接口可不引出,否则需为/var/run目录设置可写挂载
# ctrl_interface=/var/run/wpa_supplicant
# AP连接参数配置需根据实际进行修改,以下为示例
network={
ssid="TP-LINK_EBA438"
key_mgmt=WPA-PSK
psk="12345678"
}
可使用以下命令调用 wpa_supplicant 连接网络:
wpa_supplicant -D wext -i wlan0 -c /etc/wpa.conf &
当终端打印以下信息时表示网络连接成功:
如需自动获取IP地址,则需要运行DHCP客户端(AP端对应需配置运行DHCP服务端,路由器默认启用)。
首先将busybox源码目录下的 examples/simple.script 拷贝到目标开发板的 /usr/share/default.script ,并赋予其可执行权限。而后使用以下命令启动Busybox内置的DHCP客户端:
udhcpc -i wlan0 -b
5.1.4 使用hostapd将网卡作为AP
更详细的说明以及USB无线网卡用作AP的示例可阅读 4.2 hostapd 章节引文中相应内容,或查阅网上相关资料。
5.2 TF卡的挂载
前面章节中,我们的开发板启动到Shell后,其 / 根目录即为根文件系统所在分区,为了访问我们预留出的空分区,需要使用 mount
命令将其挂载到一个已存在的目录上,而后访问该挂载点即为访问挂载设备本身。
如果你已经学习过 mount
命令,那么本章手动挂载部分就可以略过了,否则建议花时间了解一下,因为挂载操作在嵌入式开发中实在是过于常用了(此时此刻我想的其实是NFS,这个你也需要去学习一下)。
[U盘的自动挂载差异]
本节操作以TF卡为例,因为在IPC中TF卡较U盘更为常用。U盘自动挂载与TF卡类似,只是对应的设备节点名称不同,网上的例子很多,你可以试一试。
例如当前U盘对应的设备节点为 /dev/sda 。
5.2.1 手动挂载
内核中默认已经开启了对MMC的支持,将TF卡插入开发板即可看到识别信息。
对于单分区TF卡而言,此时 /dev/mmcblk1 即为TF卡的块设备节点,它包含了TF卡全部空间; /dev/mmcblk1p1 也是一个块设备,它指代TF卡上的唯一分区。
使用以下命令将卡挂载到 /mnt 目录下:
mount /dev/mmcblk1p1 /mnt
而后访问 /mnt 挂载点即为访问TF卡。测试时使用的TF卡文件系统为FAT32,如果使用了其它的文件系统,可能需要使用 -t 参数手动指定。另外,内核支持的文件系统可通过 cat /proc/filesystems
命令查看。
使用完成后,使用 umount /mnt/
命令卸载挂载点后再弹出TF卡。
5.2.2 使用mdev自动挂载设备
mdev是一个基于uevent_helper机制获取内核uevent消息工作的轻量型设备管理程序,被广泛使用在各种嵌入式系统中。mdev集成在Busybox中,可在编译配置界面的 Linux System Utilities 条目下找到。Busybox为它提供了一个入门文档,参见 docs/mdev.txt 。
参考文档可知,如需使用运行中自定义动态挂载功能,需要满足以下支持:
- 需要启用并安装sysfs
- 需要启用内核热插拔(hotplug)
- 需要指示内核在添加或删除设备时调用mdev
- 若已启用proc文件系统,则使用
echo /sbin/mdev > /proc/sys/kernel/hotplug
进行设置 - 若未启用proc文件系统,则使用
sysctl -w kernel.hotplug=/sbin/mdev
进行设置
- 若已启用proc文件系统,则使用
- 确保 /dev 是tmpfs文件系统,或确保存储器上留有足够的空间
- 创建 /dev/pts 目录,并将其挂载为devpts文件系统
- 编写 /etc/mdev.conf 配置文件
所幸,如果你使用的是海思提供的根文件系统模板,那么上面的这些条件默认即是满足的。使用mdev实现动态挂载的核心问题变成了对配置文件的操作。配置文件模板可参考Busybox目录下的 examples/mdev.conf 。
此处以FAT32文件系统的单分区TF卡插在 MMC1 卡槽内时自动挂载该分区到 /mnt 目录为例,修改 /etc/mdev.conf 文件,添加以下条目即可,插拔TF卡前后即可通过访问/mnt目录查看效果。
mmcblk1p1 0:0 660 * if [ "$ACTION" = remove ];then umount -l /mnt;else mount -n /dev/mmcblk1p1 /mnt;fi
风险: 因未在拔除TF卡之前首先使用 umount
卸载挂载点,故可能造成文件系统损坏或数据丢失等问题,在下一次挂载时将提示:
FAT-fs (mmcblk1p1): Volume was not properly unmounted. Some data may be corrupt. Please run fsck.
5.2.3 使用udev自动挂载设备
和mdev不同,udev基于netlink机制监听内核发送的uevent事件,其事件处理效率较高,通常被用在较大型系统中,例如PC机。eudev是udev的一个分支,据某些资料,udev已被合入systemd,而eudev成为udev的继任者,但依然延续udev的名称。
使用udev需要首先编译该工具,在展开后SDK目录下的 osdrv/tools/board/eudev-3.2.7/ 目录下,首先按照 readme_cn.txt 编译构建gperf-3.1,而后在目录下执行 make
编译,将编译后的程序复制到根文件系统对应目录中即完成安装。
为了简便,此处仍以FAT32文件系统的单分区TF卡插在 MMC1 卡槽内时自动挂载该分区到 /mnt 目录为例。首先确保udev是运行的(使用 ps
命令查看),否则使用 udevd --daemon
命令启动它。
在 /etc/udev/rules.d/ 目录下新建规则文件 10-tfcard.rules ,内容如下:
ACTION=="add", KERNEL=="mmcblk1p1", RUN+="/etc/udev/mount-to-mnt.sh mmcblk1p1"
ACTION=="remove", KERNEL=="mmcblk1p1", RUN+="/etc/udev/umount-from-mnt.sh"
在 /etc/udev/ 目录下新建脚本 mount-to-mnt.sh 和 umount-from-mnt.sh ,内容分别是:
# --mount-to-mnt.sh文件内容--
#!/bin/sh
mount -n /dev/$1 /mnt
# --umount-from-mnt.sh文件内容--
#!/bin/sh
umount -l /mnt
并赋予它们可执行权限(提示:chmod +x mount-to-mnt.sh
)即可,插拔TF卡前后即可通过访问/mnt目录查看效果。
风险: 因未在拔除TF卡之前首先使用 umount
卸载挂载点,故可能造成文件系统损坏或数据丢失等问题,在下一次挂载时将提示:
FAT-fs (mmcblk1p1): Volume was not properly unmounted. Some data may be corrupt. Please run fsck.
若想实现多分区挂载,则需要在执行脚本上做些修改,例如增加对挂载点的自动创建/删除功能,文件系统判断功能以及异常处理等。鉴于这只是一个“入门演练”,就不再做更多尝试了。
6 扩展演练
本章记录了一些超出本次入门演练范围的内容,它们可能对你以后的学习有相当的参考价值,你可以根据需要选择性浏览,这样当后面你真正用到的时候就不会觉得陌生。
6.1 使用Buildroot构建根文件系统
Buildroot可用于构建一个完整的嵌入式系统,其集成了丰富的软件包,解决了众多应用的安装依赖问题,是嵌入式工程师必须掌握的一大利器。本节使用的Buildroot版本为撰写时的最新版本:Buildroot-2021.11.2 ,我们将使用该构建工具自动化根文件系统的搭建。
下载Buildroot并解压,在终端中使用 make menuconfig
命令打开配置界面并进行如下修改:
-
Target options
- 修改 Target Architecture 为 ARM (little endian)
- 修改 Target Architecture Variant 为 cortex-A7
- 修改 Target ABI 为 EABI
- 修改 Floating point strategy 为 NEON/VFPv4
-
Build options
- 修改 Mirrors and Download locations 中 Kernel.org mirror 为 “https://mirror.bjtu.edu.cn/kernel” 以加快下载速度
-
Toolchain
- 修改 Toolchain type 为 External toolchain
- 修改 Toolchain 为 Custom toolchain
- 保持 Toolchain origin 为 Pre-installed toolchain
- 修改 Toolchain path 为“/opt/hisi-linux/x86-arm/arm-himix200-linux”
- 修改 Toolchain prefix 为“arm-himix200-linux”
- 修改 External toolchain gcc version 为 6.x
- 修改 External toolchain kernel headers series 为 3.5.x
- 修改 External toolchain C library 为 glibc/eglibc
- 选中 Toolchain has C++ support?
-
System configuration
- 可修改 System hostname 为自定义名称,如“Hi3516DV300”
- 可修改 System banner 为自定义标语或删除亦可
- 按需配置 /dev management 选项,有了前面章节的经验之后,理解该选项应该不难
- 配置 Root password ,如“root”
-
Kernel
如果无需使用Buildroot编译内核,则取消勾选 Linux Kernel 项即可,否则按照以下步骤进行配置,使Buildroot编译海思提供的内核。
- 修改 Kernel version 为 Custom version ,并填充 Kernel version 字段为“4.9.37”
- 填写 Custom kernel patches 指向所使用的patch文件路径,例如此处填写“/home/wind/buildroot-2021.11.2/linux-4.9.37.patch”,该文件可从海思SDK中获得
- 修改 Kernel configuration 为 Using a custom (def)config file
- 修改 Configuration file path 指向所使用的defconfig文件路径,例如此处填写“/home/wind/buildroot-2021.11.2/hi3516dv300_emmc_smp_defconfig”,该文件可从打完补丁后的内核中获得
- 修改 Kernel binary format 为 uImage
-
Target packages
可根据需要选择。
-
Filesystem images
- 保持 tar the root filesystem 在选中状态,以生成打包后的根文件系统。
建议手动制作文件系统镜像。使用Buildroot生成的镜像可能存在无法直接适配硬件的风险,笔者未做尝试。
-
Bootloaders
U-Boot使用海思SDK中提供的版本即可,且因涉及DDR适配等操作,没有必要手动编译。
-
Host utilities
无需操作。
-
Legacy config options
无需操作。
保存设置并退出,使用 make
启动构建,构建过程中需保持网络畅通。构建完成后,Buildroot目录下的 output/inamges/ 目录下即存放着内核uImage镜像和最终的根文件系统。
文件系统镜像打包和烧录过程等不再赘述。
最终启动效果如下图所示,root账户密码为上述构建配置过程中的自定义字符串。
6.2 理解设备树
Hi3516DV300的设备树文件位置为内核目录下的 arch/arm/boot/dts/hi3516dv300.dtsi ,以下为针对该文件的部分注释。
提示:可将这些内容拷贝到其它编辑器(如Notepad++等)中查看,建议使用 等宽字体 显示。
/*
* Copyright (c) 2013-2014 Linaro Ltd.
* Copyright (c) 2015-2017 HiSilicon Technologies Co., Ltd.
*
* 该程序是免费软件;您可以根据自由软件基金会发布的GNU通用
* 公共许可条款重新分发和/或修改它;许可证的第2版,或(由
* 您选择)任何更高版本。
*
* 分发此程序的目的是希望它有用,但不提供任何保证;甚至没
* 有对适销性或特定用途适用性的默示保证。有关详细信息,请
* 参阅GNU通用公共许可证。
*
* 您应该已经收到了一份GNU通用公共许可证的副本以及该程序。
* 如果没有,请参阅<http://www.gnu.org/licenses/>。
*
*/
#include <../../../../../include/generated/autoconf.h> |
#include "skeleton.dtsi" |
#include <dt-bindings/clock/hi3516dv300-clock.h> |定义了时钟节点ID,隶属CLK子系统
/ { |
aliases { |[aliases节点]仅用于为设备树中的节点起别名,
serial0 = &uart0; |别名可以被全设备树各处引用,方便客户快速
#ifndef CONFIG_ARCH_HISI_BVT_AMP |找到修改位置。
i2c0 = &i2c_bus0; |
i2c1 = &i2c_bus1; |
i2c2 = &i2c_bus2; | ↑
i2c3 = &i2c_bus3; |I2C节点别名
#endif | ↓
i2c4 = &i2c_bus4; |
i2c5 = &i2c_bus5; |
i2c6 = &i2c_bus6; |
i2c7 = &i2c_bus7; |
#ifndef CONFIG_ARCH_HISI_BVT_AMP |
spi0 = &spi_bus0; | ↑
spi1 = &spi_bus1; |SPI节点别名
spi2 = &spi_bus2; | ↓
#endif |
gpio0 = &gpio_chip0; |
gpio1 = &gpio_chip1; |
gpio2 = &gpio_chip2; |
gpio3 = &gpio_chip3; |
gpio4 = &gpio_chip4; |
gpio5 = &gpio_chip5; | ↑
gpio6 = &gpio_chip6; |GPIO节点别名
gpio7 = &gpio_chip7; | ↓
gpio8 = &gpio_chip8; |
gpio9 = &gpio_chip9; |
gpio10 = &gpio_chip10; |
gpio11 = &gpio_chip11; |
}; |
|
------------------------------------------------------------------------------------------------------------|
|
cpus { |[CPU节点]
#address-cells = <1>; |绝对起始地址本身所占字长为1,即u32
#size-cells = <0>; |reg所指地址空间大小所占字长为0,即仅当前地址位置
enable-method = "hisilicon,hi3516dv300"; |在arch/arm/mach-hibvt/mach-hi3516dv300.c
| ↓调用cpu初始化
cpu@0 { | arch/arm/mach-hibvt/platsmp.c
device_type = "cpu"; |
compatible = "arm,cortex-a7"; |
clock-frequency = <HI3516DV300_FIXED_1000M>; |
reg = <0>; |与#address-cells和#size-cells相对应,
}; |对于CPU不同核心分配不同的地址
#ifndef CONFIG_ARCH_HISI_BVT_AMP |注:默认情况下未配置CONFIG_ARCH_HISI_BVT_AMP
cpu@1 { |
device_type = "cpu"; |
compatible = "arm,cortex-a7"; |
clock-frequency = <HI3516DV300_FIXED_1000M>; |
reg = <1>; |
}; |
#endif |
}; |
|
clock: clock@12010000 { |[Clock Providers]隶属CLK子系统,提供PLL时钟分配,
compatible = "hisilicon,hi3516dv300-clock"; |在drivers/clk/hisilicon/clk-hi3516dv300.c中被调用,
#address-cells = <1>; |同时使用dt-bindings/clock/hi3516dv300-clock.h头文件。
#size-cells = <1>; |
#clock-cells = <1>; |时钟数量信息所占字长为1,即u32
|在drivers/clocksource/timer-hisp804.c(和其它?)中被判断。
#reset-cells = <2>; ?|
reg = <0x12010000 0x1000>; |CRG寄存器基址及对应寄存器空间(见用户指南-系统-时钟章节)
}; |与#address-cells和#size-cells相对应,各组中包含1+1个值,
|表示基址及从该基址处起多大的空间。后面不再解释。
|
gic: interrupt-controller@10300000 { |[GIC全局中断控制器]
compatible = "arm,cortex-a7-gic"; |
#interrupt-cells = <3>; |中断描述所需字长为3,即interrupts=<中断域 中断 触发方式>
#address-cells = <0>; | “中断域”,或作“中断类型”
interrupt-controller; |interrupt-controller空属性用于声明该节点接收中断,该角色
/* gic dist base, gic cpu base , no virtual support */ |负责收集各个外设的异步事件并通知processor。
reg = <0x10301000 0x1000>, <0x10302000 0x100>; |
}; |
|
syscounter { |[ARMv7通用定时器]
|可参见https://wiki.osdev.org/ARMv7_Generic_Timers
compatible = "arm,armv7-timer"; |
interrupt-parent = <&gic>; |此设备节点隶属于GIC中断控制器
interrupts = <1 13 0xf08>, |NOTE:当前设备树中仅此节点使用中断域1,其余节点均为中断域0
<1 14 0xf08>; |
clock-frequency = <50000000>; |工作时钟频率为50M
}; |
|
-----------------------------------------------------------|
|
soc { |[SoC节点]片内资源
#address-cells = <1>; |
#size-cells = <1>; |
compatible = "simple-bus"; |将该节点下的所有子节点都注册为platform device
interrupt-parent = <&gic>; |此设备节点隶属于GIC中断控制器
ranges; |地址空间1:1映射
|
clk_3m: clk_3m { |[3MHz固定时钟源]
compatible = "fixed-clock"; |主SOC子系统Timer的计数时钟可以选择为总线时钟
#clock-cells = <0>; |(50MHz)或3MHz时钟。(见用户指南-系统-定时器章节)
clock-frequency = <3000000>; |
}; |
|
clk_apb: clk_apb { |[50MHz固定总线时钟源]
compatible = "fixed-clock"; |APB(Advanced Peripheral Bus)高级外设总线,主要用来
#clock-cells = <0>; | 连接高性能低带宽的外围设备。
clock-frequency = <50000000>; |
}; |
|
pmu { |[电源管理单元]
compatible = "arm,cortex-a7-pmu"; |
interrupts = <0 54 4>; |<中断域0 中断号54 高电平触发>,后面不再解释。
}; |中断触发方式:
|1 上升沿触发
|2 下降沿触发
|4 高电平触发
|8 低电平触发
|
#ifdef CONFIG_HIEDMACV310 |注:默认情况下配置CONFIG_HIEDMACV310
hiedmacv310_0: hiedma-controller@10060000 { |[DMA控制器节点]负责处理UART0~4/I2C0~7/SPI0~2硬件请求
compatible = "hisilicon,hiedmacv310"; |驱动匹配位置:drivers/dma/hiedmacv310.c
reg = <0x10060000 0x1000>; |DMAC寄存器空间(见用户指南-系统-DMA控制器章节)
interrupts = <0 28 4>; |
clocks = <&clock HI3516DV300_DMAC_CLK>, <&clock HI3516DV300_DMAC_AXICLK>;
clock-names = "apb_pclk", "axi_aclk"; |
#clock-cells = <2>; |
resets = <&clock 0x194 0>; |DMAC相关的时钟及软复位控制寄存器(见用户指南CRG寄存器)
reset-names = "dma-reset"; |
dma-requests = <32>; |控制器支持的DMA请求信号数量(aka. 外设请求线)
dma-channels = <8>; |控制器支持的DMA通道数
devid = <0>; |设备ID,在Hi3516DV300中仅1个,其它芯片中可能有多个EDMAC
#dma-cells = <2>; |
status = "okay"; |状态:启用。
}; |
#endif |
#ifdef CONFIG_HIEDMAC |注:默认情况下未配置CONFIG_HIEDMAC
hiedmacv310_0: hiedma-controller@10060000 {
compatible = "hisilicon,hiedmacv310_n";
reg = <0x10060000 0x1000>;
interrupts = <0 28 4>;
clocks = <&clock HI3516DV300_DMAC_CLK>, <&clock HI3516DV300_DMAC_AXICLK>;
clock-names = "apb_pclk", "axi_aclk";
#clock-cells = <2>;
resets = <&clock 0x194 0>;
reset-names = "dma-reset";
dma-requests = <32>;
dma-channels = <8>;
devid = <0>;
#dma-cells = <2>;
status = "okay";
};
#endif
sysctrl: system-controller@12020000 { |[系统控制寄存器](见用户指南-系统-系统控制器章节)
compatible = "hisilicon,sysctrl"; |驱动匹配位置:drivers/irqchip/irq-gic.c
reg = <0x12020000 0x1000>; |
reboot-offset = <0x4>; |该偏移位置为系统软复位寄存器
#clock-cells = <1>; |
}; |
|
amba { |[AMBA总线]
#address-cells = <1>; |AMBA(Advanced Microcontroller Bus Architecture)
#size-cells = <1>; |高级微控制器总线架构
compatible = "arm,amba-bus"; |
ranges; |地址空间1:1映射
|
timer@hisp804 { |[定时器节点](见内核文档:arm,sp804.yaml)
compatible = "hisilicon,hisp804"; |驱动匹配位置:drivers/clocksource/timer-hisp804.c
/* timer0 & timer1 & timer2 */ |
reg = <0x12000000 0x20>, /* clocksource */ |定时器0用作时钟源
<0x12000020 0x20>, /* local timer for each cpu */ |定时器1用作CPU0时钟
<0x12001000 0x20>; |定时器2用作CPU1时钟
interrupts = <0 1 4>, /* irq of local timer */ |
<0 2 4>; |
clocks = <&clock HI3516DV300_FIXED_3M>, |
<&clock HI3516DV300_FIXED_3M>, |
<&clock HI3516DV300_FIXED_3M>; |3个定时器的时钟源均为3MHz时钟(非总线时钟)
clock-names = "timer0", "timer1", "timer2"; |
}; |
|
dual_timer2: dual_timer@12002000 { |[定时器节点](见内核文档:arm,sp804.yaml)
compatible = "arm,sp804", "arm,primecell"; |
/* timer4 & timer5 */ |使用定时器4、5(见用户指南-系统-定时器章节)
interrupts = <0 3 4>; |
reg = <0x12002000 0x1000>; |寄存器范围跨越Timer4和Timer5
clocks = <&clk_3m>, <&clk_3m>, <&clk_apb>; |
clock-names = "timer20", "timer21", "apb_pclk"; ?|同时使用了3M固定时钟源和50MHz总线时钟
status = "disabled"; |状态:禁用。后面不再解释。
}; |
|
uart0: uart@120a0000 { |[UART0节点]
compatible = "arm,pl011", "arm,primecell"; |驱动匹配位置:drivers/tty/serial/amba-pl011.c
reg = <0x120a0000 0x1000>; |
interrupts = <0 6 4>; |
clocks = <&clock HI3516DV300_UART0_CLK>; |
clock-names = "apb_pclk"; |使用50MHz总线时钟
status = "disabled"; |
}; |
|注意:这里未给UART0分配DMA请求编号。
|
#ifndef CONFIG_ARCH_HISI_BVT_AMP |
uart1: uart@120a1000 { |[UART1节点]
compatible = "arm,pl011", "arm,primecell"; |有关PL011的说明请自行查找《PrimeCell UART(PL011)》
reg = <0x120a1000 0x1000>; |
interrupts = <0 7 4>; |
clocks = <&clock HI3516DV300_UART1_CLK>; |
clock-names = "apb_pclk"; |
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 19 19>, <&hiedmacv310_0 18 18>; |分配DMA请求编号
dma-names = "tx","rx"; |(具体对应情况见用户指南-系统-DMA控制器章节)
#endif |
status = "disabled"; |
}; |
#endif |
uart2: uart@120a2000 { |[UART2节点]
compatible = "arm,pl011", "arm,primecell"; |
reg = <0x120a2000 0x1000>; |
interrupts = <0 8 4>; |
clocks = <&clock HI3516DV300_UART2_CLK>; |
clock-names = "apb_pclk"; |
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 21 21>, <&hiedmacv310_0 20 20>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
uart3: uart@120a3000 { |[UART3节点]
compatible = "arm,pl011", "arm,primecell"; |
reg = <0x120a3000 0x1000>; |
interrupts = <0 9 4>; |
clocks = <&clock HI3516DV300_UART3_CLK>; |
clock-names = "apb_pclk"; |
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 23 23>, <&hiedmacv310_0 22 22>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
uart4: uart@120a4000 { |[UART4节点]
compatible = "arm,pl011", "arm,primecell"; |
reg = <0x120a4000 0x1000>; |
interrupts = <0 10 4>; |
clocks = <&clock HI3516DV300_UART4_CLK>; |
clock-names = "apb_pclk"; |
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 25 25>, <&hiedmacv310_0 24 24>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
}; |
|
#ifndef CONFIG_ARCH_HISI_BVT_AMP |
i2c_bus0: i2c@120b0000 { |[I2C0节点]
compatible = "hisilicon,hibvt-i2c"; |驱动匹配位置:drivers/i2c/busses/i2c-hibvt.c
reg = <0x120b0000 0x1000>; |
clocks = <&clock HI3516DV300_I2C0_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 1 1>, <&hiedmacv310_0 0 0>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
i2c_bus1: i2c@120b1000 { |[I2C1节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b1000 0x1000>; |
clocks = <&clock HI3516DV300_I2C1_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 3 3>, <&hiedmacv310_0 2 2>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
i2c_bus2: i2c@120b2000 { |[I2C2节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b2000 0x1000>; |
clocks = <&clock HI3516DV300_I2C2_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 5 5>, <&hiedmacv310_0 4 4>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
i2c_bus3: i2c@120b3000 { |[I2C3节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b3000 0x1000>; |
clocks = <&clock HI3516DV300_I2C3_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 7 7>, <&hiedmacv310_0 6 6>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
#endif |
i2c_bus4: i2c@120b4000 { |[I2C4节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b4000 0x1000>; |
clocks = <&clock HI3516DV300_I2C4_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 9 9>, <&hiedmacv310_0 8 8>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
i2c_bus5: i2c@120b5000 { |[I2C5节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b5000 0x1000>; |
clocks = <&clock HI3516DV300_I2C5_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 11 11>, <&hiedmacv310_0 10 10>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
i2c_bus6: i2c@120b6000 { |[I2C6节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b6000 0x1000>; |
clocks = <&clock HI3516DV300_I2C6_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 13 13>, <&hiedmacv310_0 12 12>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
i2c_bus7: i2c@120b7000 { |[I2C7节点]
compatible = "hisilicon,hibvt-i2c"; |
reg = <0x120b7000 0x1000>; |
clocks = <&clock HI3516DV300_I2C7_CLK>; |
#ifdef CONFIG_HIEDMAC |
dmas = <&hiedmacv310_0 15 15>, <&hiedmacv310_0 14 14>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
#ifndef CONFIG_ARCH_HISI_BVT_AMP |
spi_bus0: spi@120c0000 { |[SPI0节点]
compatible = "arm,pl022", "arm,primecell"; |和PL011类似,PL022指代PrimeCell SPI。
arm,primecell-periphid = <0x00800022>; ?|
reg = <0x120c0000 0x1000>; |SPI0寄存器
interrupts = <0 68 4>; |
clocks = <&clock HI3516DV300_SPI0_CLK>; |
clock-names = "apb_pclk"; |使用50MHz总线时钟
#address-cells = <1>; |
#size-cells = <0>; |
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 27 27>, <&hiedmacv310_0 26 26>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
spi_bus1: spi@120c1000 { |[SPI1节点]
compatible = "arm,pl022", "arm,primecell"; |
arm,primecell-periphid = <0x00800022>; |
reg = <0x120c1000 0x1000>, <0x12030000 0x4>; ?|暂不清楚为什么会用到0x12030000位置的MISC寄存器
interrupts = <0 69 4>; |
clocks = <&clock HI3516DV300_SPI1_CLK>; |
clock-names = "apb_pclk"; |
#address-cells = <1>; |
#size-cells = <0>; |
num-cs = <2>; |片选数量为2
hisi,spi_cs_sb = <2>; |驱动匹配位置:drivers/spi/spi-pl022.c
hisi,spi_cs_mask_bit = <0x4>;//0100 |驱动匹配位置:drivers/spi/spi-pl022.c
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 29 29>, <&hiedmacv310_0 28 28>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
|
spi_bus2: spi@120c2000 { |[SPI2节点]
compatible = "arm,pl022", "arm,primecell"; |
arm,primecell-periphid = <0x00800022>; |
reg = <0x120c2000 0x1000>; |
interrupts = <0 70 4>; |
clocks = <&clock HI3516DV300_SPI2_CLK>; |
clock-names = "apb_pclk"; |
#address-cells = <1>; |
#size-cells = <0>; |
#ifdef CONFIG_HIEDMACV310 |
dmas = <&hiedmacv310_0 31 31>, <&hiedmacv310_0 30 30>; |
dma-names = "tx","rx"; |
#endif |
status = "disabled"; |
}; |
#endif |
|
ipcm: ipcm@045E0000 { |[IPCM节点]
compatible = "hisilicon,ipcm-interrupt"; ?|异构多核通信相关?
interrupt-parent = <&gic>; |
interrupts = <0 10 4>; |
reg = <0x10300000 0x4000>; |
status = "okay"; |
}; |
|
mdio0: mdio@10011100 { |[MDIO节点]
compatible = "hisilicon,hisi-femac-mdio"; |
reg = <0x10011100 0x10>; |MDIO控制寄存器
clocks = <&clock HI3516DV300_ETH0_CLK>; |
clock-names = "mdio"; |
assigned-clocks = <&clock HI3516DV300_ETH0_CLK>; |
assigned-clock-rates = <54000000>; |分配的时钟速率:54MHz
resets = <&clock 0x16c 3>; |
reset-names = "external-phy"; |驱动匹配位置:drivers/net/phy/mdio-hisi-femac.c
#address-cells = <1>; |
#size-cells = <0>; |
}; |
|
hisi_femac0: ethernet@10010000 { |[FEMAC节点]
compatible = "hisilicon,hi3516dv300-femac", |无驱动匹配位置(驱动中未添加hi3516dv300-femac)
"hisilicon,hisi-femac-v2"; |驱动匹配位置:drivers/net/ethernet/hisilicon/ \
reg = <0x10010000 0x1000>,<0x10011300 0x200>; |hisi-femac/hisi_femac.c
interrupts = <0 32 4>; |
clocks = <&clock HI3516DV300_ETH0_CLK>; |
resets = <&clock 0x16c 0>; |
reset-names = "mac"; |
}; |
|
fmc: flash-memory-controller@10000000 { |[闪存控制器节点]
compatible = "hisilicon,hisi-fmc"; |驱动匹配位置:drivers/mfd/hisi_fmc.c
reg = <0x10000000 0x1000>, <0x14000000 0x10000>; |FMC寄存器 & FMC存储空间(16M)
reg-names = "control", "memory"; |
clocks = <&clock HI3516DV300_FMC_CLK>; |
max-dma-size = <0x2000>; |
#address-cells = <1>; |
#size-cells = <0>; |
|
hisfc:spi-nor@0 { |
compatible = "hisilicon,fmc-spi-nor"; |驱动匹配位置:drivers/mfd/hisi_fmc.c
assigned-clocks = <&clock HI3516DV300_FMC_CLK>; |
assigned-clock-rates = <24000000>; |分配的时钟速率:24MHz
#address-cells = <1>; |
#size-cells = <0>; |
}; |
|
hisnfc:spi-nand@0 { |
compatible = "hisilicon,fmc-spi-nand"; |驱动匹配位置:drivers/mfd/hisi_fmc.c
assigned-clocks = <&clock HI3516DV300_FMC_CLK>; |
assigned-clock-rates = <24000000>; |分配的时钟速率:24MHz
#address-cells = <1>; |
#size-cells = <0>; |
}; |
}; |
|
mmc0: himci.eMMC@0x10100000 { |[MMC0-eMMC节点]
compatible = "hisilicon,hi3516dv300-himci"; |驱动匹配位置:drivers/mmc/host/himci/himci.c
reg = <0x10100000 0x1000>; |eMMC寄存器
interrupts = <0 64 4>; |
clocks = <&clock HI3516DV300_MMC0_CLK>; |
clock-names = "mmc_clk"; |
resets = <&clock 0x148 0>; |
reset-names = "mmc_reset"; |
max-frequency = <100000000>; |最大频率:100MHz
bus-width = <4>; |使用4线模式(未配置则为1线模式)
cap-mmc-highspeed; |标识此槽位支持高速MMC
cap-mmc-hw-reset; |标识此槽位支持硬件复位
mmc-hs200-1_8v; |MMC高速200M_1.8V供电电压
devid = <0>; |
status = "disabled"; |
}; |
|
mmc1: himci.SD@0x100f0000 { |[MMC1-SD节点]
compatible = "hisilicon,hi3516dv300-himci"; |
reg = <0x100f0000 0x1000>; |
interrupts = <0 30 4>; |
clocks = <&clock HI3516DV300_MMC1_CLK>; |
clock-names = "mmc_clk"; |
resets = <&clock 0x160 0>; |
reset-names = "mmc_reset"; |
max-frequency = <100000000>; |
bus-width = <4>; |
cap-sd-highspeed; |标识此槽位支持高速SD卡
sd-uhs-sdr12; |超高速卡支持12MB/s总线速度
sd-uhs-sdr25; |超高速卡支持25MB/s总线速度
sd-uhs-sdr50; |超高速卡支持50MB/s总线速度
sd-uhs-sdr104; |超高速卡支持104MB/s总线速度
full-pwr-cycle; |支持设备卡的整个电源周期
devid = <1>; |
status = "disabled"; |
}; |
|
mmc2: himci.SD@0x10020000 { |[MMC2-SD节点]
compatible = "hisilicon,hi3516dv300-himci"; |
reg = <0x10020000 0x1000>; |
interrupts = <0 31 4>; |
clocks = <&clock HI3516DV300_MMC2_CLK>; |
clock-names = "mmc_clk"; |
resets = <&clock 0x154 0>; |
reset-names = "mmc_reset"; |
max-frequency = <100000000>; |
bus-width = <4>; |
cap-sd-highspeed; |
sd-uhs-sdr12; |
sd-uhs-sdr25; |
sd-uhs-sdr50; |
sd-uhs-sdr104; |
full-pwr-cycle; |
devid = <2>; |
status = "disabled"; |
}; |
|
hidmac: hidma-controller@10060000 { |[DMAC节点]
compatible = "hisilicon,hisi-dmac"; |驱动匹配位置:drivers/hidmac/hi_pl08x.c
reg = <0x10060000 0x1000>; |
interrupts = <0 28 4>; |
clocks = <&clock HI3516DV300_DMAC_CLK>; |
clock-names = "dmac_clk"; |
resets = <&clock 0xc8 4>; |
reset-names = "dma-reset"; |
#dma-cells = <2>; |
status = "disabled"; |
}; |
|
usb_phy: phy { |[USB_PHY节点]
compatible = "hisilicon,hisi-usb-phy"; |驱动匹配位置:drivers/phy/hibvt/phy-hisi-usb.c
reg = <0x12010000 0x1000>; |CRG寄存器基址及对应寄存器空间
#phy-cells = <0>; |
}; |
|
#define USB_HOST 1 |
#if USB_HOST |
xhci_0@0x100e0000 { |[XHCI节点]xHCI主机控制器
compatible = "generic-xhci"; |
reg = <0x100e0000 0x10000>; |USB控制寄存器
interrupts = <0 27 4>; |
usb2-lpm-disable; |禁用USB2.0链路电源管理
}; |
#else |
hidwc3_0@0x100e0000 { |[DWC3节点]
compatible = "snps,dwc3"; |DesignWare Core SuperSpeed USB 3.0 Controller
reg = <0x100e0000 0x10000>; |
interrupts = <0 27 4>; |
interrupt-names = "peripheral"; |
maximum-speed = "high-speed"; |
dr_mode = "peripheral"; |
}; |
#endif |
gpio_chip0: gpio_chip@120d0000 { |[GPIO0节点]
compatible = "arm,pl061", "arm,primecell"; |和PL011/PL022类似,有关PL061的信息请自行查找
reg = <0x120d0000 0x1000>; |《General Purpose Input/Output(PL061)》。
interrupts = <0 16 4>; |注意:该中断号非中断源编号
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |使用50MHz总线时钟
#gpio-cells = <2>; |该控制器下每个引脚需使用2个u32描述。
status = "disabled"; |
}; |
|
gpio_chip1: gpio_chip@120d1000 { |[GPIO1节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d1000 0x1000>; |
interrupts = <0 17 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip2: gpio_chip@120d2000 { |[GPIO2节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d2000 0x1000>; |
interrupts = <0 18 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip3: gpio_chip@120d3000 { |[GPIO3节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d3000 0x1000>; |
interrupts = <0 19 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip4: gpio_chip@120d4000 { |[GPIO4节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d4000 0x1000>; |
interrupts = <0 20 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip5: gpio_chip@120d5000 { |[GPIO5节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d5000 0x1000>; |
interrupts = <0 21 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip6: gpio_chip@120d6000 { |[GPIO6节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d6000 0x1000>; |
interrupts = <0 22 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip7: gpio_chip@120d7000 { |[GPIO7节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d7000 0x1000>; |
interrupts = <0 23 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip8: gpio_chip@120d8000 { |[GPIO8节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d8000 0x1000>; |
interrupts = <0 24 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip9: gpio_chip@120d9000 { |[GPIO9节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120d9000 0x1000>; |
interrupts = <0 25 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip10: gpio_chip@120da000 { |[GPIO10节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120da000 0x1000>; |
interrupts = <0 26 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
gpio_chip11: gpio_chip@120db000 { |[GPIO11节点]
compatible = "arm,pl061", "arm,primecell"; |
reg = <0x120db000 0x1000>; |
interrupts = <0 80 4>; |
clocks = <&clock HI3516DV300_SYSAPB_CLK>; |
clock-names = "apb_pclk"; |
#gpio-cells = <2>; |
status = "disabled"; |
}; |
|
cipher: cipher@0x100c0000 { |[加密器节点]
compatible = "hisilicon,hisi-cipher"; |驱动位置:drivers/crypto/hisi-cipher/目录下
reg = <0x100c0000 0x10000>; |
reg-names = "cipher"; |
interrupts = <0 71 4>, <0 72 4>, <0 71 4>, <0 72 4>; ?|
interrupt-names = "cipher", "nonsec_cipher", "hash", "nonsec_hash";
};
};
----------------------------------------------------------------------------------|
|
media { |[Media节点]
#address-cells = <1>; |该节点下的子节点为海思MPP相关模块,
#size-cells = <1>; |无需也无法做什么特定的分析,且节点
compatible = "simple-bus"; |信息较为简单,故略过注释。
...
}
上面这些注释其实是我在学习的时候随手做的笔记,我也不是很清楚个中细节,但这就是我目前所能做的。若其中有错谬之处,希望不会误导读者。
6.3 使用USB Gadget连接开发板分区到PC
SDK提供的内核配置中,已经开启了对海量存储设备的USB Gadget驱动支持,只需将USB控制器更改为设备模式即可使用。
使用 menuconfig
配置内核:
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
进行以下修改,以改变USB控制器从主机模式到从机模式:
- Device Drivers -> PHY Subsystem -> Hisilicon USB related configuration
- 取消选择 USB DRD0 Mode Select HOST
- 选择 USB DRD0 Mode Select DEVICE
保存并重新编译内核,将生成的 uImage 烧录到板端,将内核模块驱动照上文所述拷贝到根文件系统对应目录。
[关于USB控制器模式选择]
参考《Hi3516DV300 专业型 Smart IP Camera SoC 用户指南》USB DRD相关章节知:USB DRD支持单独工作在Host或者Device模式而不支持OTG。
同时参考设备树相关节点知,若上述内核配置中同时选中主、从机模式,则因为主从机设备节点地址相同,则后匹配的驱动将覆盖之前的寄存器设置,设备工作在从机状态下。
启动到系统后,执行以下命令挂载USB Gadget驱动。以 3.2.1 eMMC存储器分区规划 章节中使用的分区表为例,将第4分区指定为挂载分区:
modprobe libcomposite.ko
modprobe usb_f_mass_storage.ko
modprobe g_mass_storage.ko file=/dev/mmcblk0p4 stall=0 removable=1
[相关ko文件来源]
挂载ko文件前需按照 2.4 Kernel的编译 和 2.5 根文件系统制作 章节所述将编译出的模块文件拷贝到开发板指定目录中,否则
modprobe
将无法找到这些模块。
出现以下提示即为操作成功:
[未切换USB控制器模式时的提示]
若未在内核或设备树中切换USB DRD的工作模式为从机模式,则挂载上述ko模块时会提示以下信息:
将开发板通过USB连接到PC,即可被识别为一个U盘,可对该U盘执行如格式化和读写等操作。
[文件系统支持]
在Linux中,可通过
cat /proc/filesystems
命令查看内核当前支持的文件系统,选择被PC和板端Linux同时支持的文件系统可使分区同时被两者访问,例如在Windows下将其格式化为FAT32,但需注意可能存在的同步问题。
6.4 FBTFT的使用——SPI外设和Linux FB
6.4.1 OLED12864和FBTFT驱动
OLED12864是一个常见的显示屏模块,它的驱动IC是SSD1306/SSD1315…等。如果你曾有过单片机学习经历并且用过这个屏幕,你会很容易理解显示的实质是写SSD1306内部寄存器。在Linux环境下,驱动和应用是分开的:驱动挂载到内核,和底层设备进行通信;应用程序通过系统提供的接口访问驱动从而控制硬件设备。
[注意]
FBTFT仅支持SPI接口的屏幕,OLED12864根据控制IC和硬件设计的不同有多种接口的版本如SPI、IIC、并口等,购买时需要注意。
本节以SSD1306驱动的单色屏为例,若读者驱动IC与所述不同,请参照后文描述自行适配。
[补充]
FBTFT的驱动被列为Staging表示在更高版本中FB机制将被弃用,DRM代之。
6.4.2 向内核中添加SSD1306驱动支持
Linux4.9.37中已包含TFTFB驱动,只需在内核中开启对SSD1306的支持即可。
[注意]
为防止操作失败,以下修改在 2.4 Kernel的编译 章节基础上进行。
使用 menuconfig
配置内核:
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
进行以下修改:
-
Device Drivers
-
选择 SPI support
-
选择 Staging drivers
-
-
Device Drivers -> Staging drivers
- 选择 Support for small TFT LCD display modules ,将其编译进内核
-
Device Drivers -> Staging drivers -> Support for small TFT LCD display modules
- 选择 FB driver for the SSD1306 OLED Controller ,将其编译为模块
- 选择 Generic FB driver for TFT LCD displays ,将其编译为模块
- 选择 Module to for adding FBTFT devices ,将其编译为模块
[内核配置说明]
将驱动配置为模块而非直接编译进内核的原因是我们没有设法在系统启动初期配置对应GPIO的引脚复用,这将影响到器件的初始化过程,手动挂载则不会有这个问题。
而后修改设备树,在硬件所连接的SPI节点位置增加设备信息。
[引脚连接说明]
测试使用的开发板上,将OLED12864连接到SPI1。
SPI1相关引脚和测试中OLED12864使用到的GPIO控制引脚如下表所示,器件请参照你所使用硬件的原理图自行焊接。
使用到的功能 芯片引脚 开发板引脚 OLED12864模块丝印 SPI1_CS(0) D18 SPI1_CSN0/GPIO8_2 CS SPI1_MOSI E19 SPI1_SDO/GPIO8_1 D1 SPI1_CLK F19 SPI1_SCLK/GPIO8_0 D0 SPI1_MISO F18 SPI1_SDI/GPIO8_3 无 GPIO H18 GPIO10_6 DC GPIO J18 GPIO10_7 RST 模块供电可使用3.3V。
修改 ./arch/arm/boot/dts/hi3516dv300-demb.dts ,在SPI1节点增加OLED12864连接描述:
&spi_bus1{
status = "okay";
num-cs = <2>;
/* spidev@0 { compatible = "rohm,dh2228fv"; reg = <0>; pl022,interface = <0>; pl022,com-mode = <0>; spi-max-frequency = <50000000>; }; */
ssd1306@0 {
compatible = "solomon,ssd1306";
reg = <0>;
spi-max-frequency = <50000000>;
buswidth = <8>;
dc-gpios = <&gpio_chip10 6 0>;
reset-gpios = <&gpio_chip10 7 0>;
};
spidev@1 {
compatible = "rohm,dh2228fv";
reg = <1>;
pl022,interface = <0>;
pl022,com-mode = <0>;
spi-max-frequency = <50000000>;
};
};
其中,各属性内容的含义和使用可参见内核源码目录下的 ./drivers/staging/fbtft/fbtft_device.c 文件。
[注意]
如果模块和开发板之间的连线不够稳定,较高的时钟可能导致信号失真。
修改完成后重新编译内核和设备树并烧录到板端,同时将相应的内核模块安装到根文件系统对应位置。
进入开发板系统后,首先使用以下命令更改对应引脚的复用状态、GPIO方向并设置上拉:
devmem 0x112F0020 32 0x501
devmem 0x112F0024 32 0x501
devmem 0x112F0028 32 0x501
devmem 0x112F002C 32 0x501
devmem 0x112F0000 32 0x500
devmem 0x112F0004 32 0x500
devmem 0x120DA400 8 0xC0
确保模块位置正确,在shell中执行以下命令以挂载驱动:
modprobe fb_ssd1306
驱动将注册frame buffer设备,/dev/ 目录下将出现 fb0 设备节点。
6.4.3 FB设备的基本使用
“FB”全称“FrameBuffer”,即帧缓冲,这是Linux为显示设备提供的一个接口,它将显存映射为一片连续的地址空间,将对显示设备的操作转换为对内存的读写,极大地方便了应用程序进行窗口图形绘制的过程,Linux FB是一个必须了解的概念。
FB设备的基本使用步骤为:打开设备->查询信息并配置设备->内存映射->读写映射后的帧缓冲->解映射->关闭设备。
示例程序如下。
//+------------------------------------------------------------------------------------------+
//| 前言
//+------------------------------------------------------------------------------------------+
//| FrameBuffer(帧缓冲)设备是Linux为显示设备提供的一个接口,它将显存映射为一片连续的地
//| 址空间,将对显示设备的操作转换为对内存的读写,极大地方便了应用程序进行窗口图形绘制的过程。
//+------------------------------------------------------------------------------------------+
//| 文件说明
//+------------------------------------------------------------------------------------------+
//| 本程序基于的硬件为Hi3516DV300+OLED12864(SSD1306 SPI)。程序打开/dev/fb0设备,获取设备
//| 相关的信息打印到标准输出,并执行一些绘制操作,而后关闭设备并退出。
//+------------------------------------------------------------------------------------------+
//| 头文件包含
//+------------------------------------------------------------------------------------------+
/*|*/#include <stdio.h>
/*|*/#include <stdlib.h>
/*|*/#include <stdbool.h>
/*|*/#include <stdint.h>
/*|*/#include <signal.h>
/*|*/#include <unistd.h>
/*|*/#include <pthread.h>
/*|*/#include <fcntl.h>
/*|*/#include <linux/fb.h> //提供对Linux FB操作支持
/*|*/#include <sys/mman.h>
/*|*/#include <sys/ioctl.h>
//+------------------------------------------------------------------------------------------+
//| 全局变量
//+------------------------------------------------------------------------------------------+
//| ----------------------------------------
//| [说明]
//| fbtft_fd: FB设备文件描述符
//| fbtft_p : 显示缓冲区映射地址
//| ----------------------------------------
/*|*/static int fbtft_fd = -1;
/*|*/static char *fbtft_p = NULL;
//| ----------------------------------------
//| [说明]以下两个结构体分别保存获取的fb设备
//| 的可变信息和不可变信息。
//| ----------------------------------------
/*|*/struct fb_var_screeninfo fbtft_variable_info;
/*|*/struct fb_fix_screeninfo fbtft_fixed_info;
//+------------------------------------------------------------------------------------------+
//| 函数名称:signal_handle
//| 功能描述:信号处理
//| 参数说明:信号标号
//| 返回值说明:无
//| 备注:
//+------------------------------------------------------------------------------------------+
static void signal_handle(int signo)
{
if(SIGINT == signo || SIGTERM == signo)
{
exit(1);
}
}
//+------------------------------------------------------------------------------------------+
//| 函数名称:usage
//| 功能描述:打印使用说明
//| 参数说明:无
//| 返回值说明:无
//| 备注:
//+------------------------------------------------------------------------------------------+
static void usage(void)
{
printf("Usage:\n");
printf("This program obtains relevant information and draws a frame on the Linux FB device (OLED12864).\n");
printf("The program is only suitable for the screen driven by SSD1306, 16 bytes_per_pixel.\n");
printf("Author:Linyar Version:STv13 E-mail:WindForest@yeah.net\n");
printf("[NOTICE]Input Ctrl+C to stop program.\n");
}
//+------------------------------------------------------------------------------------------+
//| 函数名称:calculate_location
//| 功能描述:计算一个像素点在显示缓冲区中的位置
//| 参数说明:像素点自左上至右下,以0起始的坐标
//| 返回值说明:像素在缓冲区中的偏移位置
//| 备注:
//+------------------------------------------------------------------------------------------+
static int calculate_location(uint32_t _x, uint32_t _y)
{
int bytes_per_pixel = fbtft_variable_info.bits_per_pixel / 8;
return (_x * bytes_per_pixel + _y * fbtft_variable_info.xres * bytes_per_pixel);
}
//+------------------------------------------------------------------------------------------+
//| 函数名称:draw_point
//| 功能描述:绘制某个点
//| 参数说明:位置和填充。pad:1表示点亮像素,0表示熄灭像素
//| 返回值说明:成功返回0
//| 备注:[注意]当前画点函数仅适用16bit的bytes_per_pixel。
//+------------------------------------------------------------------------------------------+
static int draw_point(uint32_t _x, uint32_t _y, bool pad)
{
if(fbtft_p == NULL)
{
return -1;
}
if(_x > fbtft_variable_info.xres || _y > fbtft_variable_info.yres)
{
printf("Illegal coordinates: %d*%d!\n", _x, _y);
return -2;
}
long position = calculate_location(_x, _y);
if(pad)
{
*(fbtft_p + position) = 0xFF;
*(fbtft_p + position + 1) = 0xFF;
}
else
{
*(fbtft_p + position) = 0x00;
*(fbtft_p + position + 1) = 0x00;
}
return 0;
}
//+------------------------------------------------------------------------------------------+
//| 函数名称:main
//| 功能描述:主函数
//| 参数说明:略
//| 返回值说明:成功返回0
//| 备注:
//+------------------------------------------------------------------------------------------+
int main(int argc, const char *argv[])
{
signal(SIGINT, signal_handle);
signal(SIGTERM, signal_handle);
/* 打印程序说明 */
usage();
/* 打开Linux FB设备 */
fbtft_fd = open("/dev/fb0", O_RDWR);
if(fbtft_fd < 0)
{
printf("Open FB device failed with %d!\n", fbtft_fd);
goto EXIT;
}
/* 获取设备信息 */
if(ioctl(fbtft_fd, FBIOGET_FSCREENINFO, &fbtft_fixed_info) != 0)
{
printf("Get FB device's fixed info failed!\n");
goto END_0;
}
if(ioctl(fbtft_fd, FBIOGET_VSCREENINFO, &fbtft_variable_info) != 0)
{
printf("Get FB device's variable info failed!\n");
goto END_0;
}
/* 展示设备主要信息 */
printf("+----------------FB Device's Fixed Info-------------------+\n");
printf("|Identification: %s\n" , fbtft_fixed_info.id );
printf("|Physical mem start at 0x%lx\n", fbtft_fixed_info.smem_start );
printf("|Physical mem length: %d\n" , fbtft_fixed_info.smem_len );
printf("|Line length: %d\n" , fbtft_fixed_info.line_length );
printf("|Mapped mem start at 0x%lx\n" , fbtft_fixed_info.mmio_start );
printf("|Mapped mem length: %d\n" , fbtft_fixed_info.mmio_len );
printf("|type : %d\n" , fbtft_fixed_info.type );
printf("|type_aux : %d\n" , fbtft_fixed_info.type_aux );
printf("|visual : %d\n" , fbtft_fixed_info.visual );
printf("|xpanstep : %d\n" , fbtft_fixed_info.xpanstep );
printf("|ypanstep : %d\n" , fbtft_fixed_info.ypanstep );
printf("|ywrapstep: %d\n" , fbtft_fixed_info.ywrapstep );
printf("|accel : %d\n" , fbtft_fixed_info.accel );
printf("+---------------FB Device's Variable Info-----------------+\n");
printf("|Visible resolution: %d*%d\n" , fbtft_variable_info.xres, fbtft_variable_info.yres);
printf("|Virtual resolution: %d*%d\n" , fbtft_variable_info.xres_virtual, fbtft_variable_info.yres_virtual);
printf("|X offset from virtual to visible: %d\n", fbtft_variable_info.xoffset);
printf("|Y offset from virtual to visible: %d\n", fbtft_variable_info.yoffset);
printf("|bits_per_pixel: %d\n" , fbtft_variable_info.bits_per_pixel);
printf("|grayscale : %d\n" , fbtft_variable_info.grayscale );
printf("|nonstd : %d\n" , fbtft_variable_info.nonstd );
printf("|activate : %d\n" , fbtft_variable_info.activate );
printf("|height : %d\n" , fbtft_variable_info.height );
printf("|width : %d\n" , fbtft_variable_info.width );
printf("|accel_flags : %d\n" , fbtft_variable_info.accel_flags );
printf("|pixclock : %d\n" , fbtft_variable_info.pixclock );
printf("|left_margin : %d\n" , fbtft_variable_info.left_margin );
printf("|right_margin : %d\n" , fbtft_variable_info.right_margin );
printf("|upper_margin : %d\n" , fbtft_variable_info.upper_margin );
printf("|lower_margin : %d\n" , fbtft_variable_info.lower_margin );
printf("|hsync_len : %d\n" , fbtft_variable_info.hsync_len );
printf("|vsync_len : %d\n" , fbtft_variable_info.vsync_len );
printf("|sync : %d\n" , fbtft_variable_info.sync );
printf("|vmode : %d\n" , fbtft_variable_info.vmode );
printf("|rotate : %d\n" , fbtft_variable_info.rotate );
printf("+---------------------------------------------------------+\n");
/* 映射并获取FB设备操作地址 */
long screensize = 0;
screensize = fbtft_variable_info.xres * fbtft_variable_info.yres * fbtft_variable_info.bits_per_pixel / 8;
fbtft_p = (char *)mmap(0, screensize, PROT_READ|PROT_WRITE, MAP_SHARED, fbtft_fd, 0);
if((int)fbtft_p == -1)
{
printf("Mmap FB device's mem space failed with %d!\n", (int)fbtft_p);
goto END_0;
}
//绘制边框
for(int i = 0; i < fbtft_variable_info.xres; i++)
{
draw_point(i, 0, 1);
draw_point(i, fbtft_variable_info.yres - 1, 1);
}
for(int i = 1; i < fbtft_variable_info.yres - 1; i++)
{
draw_point(0, i, 1);
draw_point(fbtft_variable_info.xres - 1, i, 1);
}
//填充测试
int k = 3;
while(k != 0)
{
for(int j = 1; j < fbtft_variable_info.xres - 1; j++)
{
for(int i = 1; i < fbtft_variable_info.yres - 1; i++)
{
draw_point(j, i, k % 2);
}
usleep(10000);
}
k--;
}
munmap(fbtft_p, screensize);
END_0:
close(fbtft_fd);
EXIT:
return 0;
}
将代码保存为 fbtft_ssd1306_test.c,而后在开发机环境执行 arm-himix200-linux-gcc -o fbtft_ssd1306_test fbtft_ssd1306_test.c
编译,赋予可执行权限后在板端执行生成的 fbtft_ssd1306_test 程序即可。
6.4.4 映射终端到屏幕
完成上述步骤后,在内核中额外进行以下配置:
- Device Drivers -> Graphics support -> Console display driver support
- 选择 Framebuffer Console support
- 选择 Map the console to the primary display device
重新编译内核并烧录。
正确配置引脚复用并挂载驱动后,可在屏幕上看到闪烁的光标。可使用 echo
指令进行测试,如:
echo "Turix - SSD1306 test." > /dev/tty0
即可在屏幕上看到打印输出。
[说明-关闭屏幕光标&闪烁]
内核配置时,进行以下步骤:
- 启用General setup -> Configure standard kernel features (expert users)
- 禁用Device Driver -> Graphics support -> Console display driver support -> Framebuffer Console support
或在启动后执行:
echo 0 > /sys/class/graphics/fbcon/cursor_blink echo 0 > /sys/class/vtconsole/vtcon1/bind
前者将关闭光标闪烁,后者将关闭终端映射。
[说明-卸载fb_ssd1306驱动]
卸载前需先关闭终端映射,否则会提示资源占用:
rmmod: can't unload module 'fb_ssd1306': Resource temporarily unavailable
。
7 未果的尝试
本章节记录了一些作者浅尝辄止,最终还是没有完全完成的实践内容。
7.1 W5500和有线网络
注意:该章节中存在未解决的问题。
7.1.1 W5500以太网控制器
W5500 是系列产品中的一个,这个系列包括如W5100S/W5200/W5300/W5500等。它们之前的区别可参考各自的芯片手册。与通常的以太网PHY芯片不同,这些以太网控制器内置了TCP/IP协议栈,因此更适合单片机等资源较少的MCU使用,Linux本身有完整的TCP/IP实现,且SoC内部通常集成以太网控制器,无需外置的芯片。
7.1.2 向内核添加W5500驱动支持
Linux4.9.37中已包含W5500驱动,因此只需在内核中开启对W5500的支持即可。
使用 menuconfig
配置内核:
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
进行以下修改:
- Device Drivers -> Network device support
- 选择 Ethernet driver support
- Device Drivers -> Network device support -> Ethernet driver support
- 选择 WIZnet devices
- Device Drivers -> Network device support -> Ethernet driver support -> WIZnet W5100 Ethernet support
- 选择 WIZnet W5100 Ethernet support 项及其子项 WIZnet W5100/W5200/W5500 Ethernet support for SPI mode ,将其编译为模块
- Device Drivers -> GPIO Support -> Memory mapped GPIO drivers
- 取消选择 PrimeCell PL061 GPIO support
[内核配置说明]
将驱动配置为模块而非直接编译进内核的原因是我们没有设法在系统启动初期配置对应GPIO的引脚复用,这将影响到器件的初始化过程,手动挂载则不会有这个问题。
而后修改设备树,在硬件所连接的SPI节点位置增加设备信息。
[引脚连接说明]
测试使用的开发板上,将W5500连接到SPI1。
SPI1相关引脚和测试中W5500使用到的GPIO控制引脚如下表所示,器件请参照你所使用硬件的原理图自行焊接。
使用到的功能 芯片引脚 开发板引脚 W5500引脚 SPI1_CS(0) D18 SPI1_CSN0/GPIO8_2 SCS SPI1_MOSI E19 SPI1_SDO/GPIO8_1 MOSI SPI1_CLK F19 SPI1_SCLK/GPIO8_0 SCLK SPI1_MISO F18 SPI1_SDI/GPIO8_3 MISO GPIO H18 GPIO10_6 INT W5500驱动程序中,使用寄存器操作方式进行复位操作,因此无需连接RST引脚。
模块供电可使用3.3V。
修改 ./arch/arm/boot/dts/hi3516dv300-demb.dts ,在SPI1节点增加W5500连接描述:
&spi_bus1{
status = "okay";
num-cs = <2>;
/*
spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
pl022,interface = <0>;
pl022,com-mode = <0>;
spi-max-frequency = <50000000>;
};
*/
w5500@0{
compatible = "w5500";
reg = <0>;
interrupt-parent = <&gic>;
interrupts = <0 26 4>; /* 58-32=26 */
gpios = <&gpio_chip10 6 0>;
spi-max-frequency = <25000000>;
status = "okay";
};
spidev@1 {
compatible = "rohm,dh2228fv";
reg = <1>;
pl022,interface = <0>;
pl022,com-mode = <0>;
spi-max-frequency = <50000000>;
};
};
[关于中断号]
需要特别注意的是,在这里我们为W5100分配了与GPIO10相同的中断号,但却没有做中断共享,因此需要屏蔽掉GPIO相关的驱动,以防止后续挂载W5500时申请中断号发生冲突。
Hi3516DV300的中断源编号参见《Hi3516DV300 专业型 Smart IP Camera SoC 用户指南》中断系统相关章节。保留的中断源无法使用。
[注意]
如果模块和开发板之间的连线不够稳定,较高的时钟可能导致信号失真。
修改W5500驱动对申请的中断的触发方式,在 drivers/net/ethernet/wiznet/w5500.c 文件第1184行附近:
if (ops->may_sleep) {
err = request_threaded_irq(priv->irq, NULL, w5100_interrupt,
修改为: IRQF_TRIGGER_RISING | IRQF_ONESHOT ,
netdev_name(ndev), ndev);
} else {
err = request_irq(priv->irq, w5100_interrupt,
IRQF_TRIGGER_LOW, netdev_name(ndev), ndev);
}
修改完成后重新编译内核、内核模块和设备树并烧录到板端,同时将相应的内核模块安装到根文件系统对应位置。系统启动后使用以下命令更改对应引脚的复用状态、设置上拉并挂载模块驱动(解释说明参见 4.6 himm和devmem 章节):
devmem 0x112F0020 32 0x501
devmem 0x112F0024 32 0x501
devmem 0x112F0028 32 0x501
devmem 0x112F002C 32 0x501
devmem 0x120DA404 8 0x40
devmem 0x120DA408 8 0x40
devmem 0x120DA40C 8 0x00
devmem 0x120DA41C 8 0xFF
devmem 0x120DA410 8 0x40
modprobe w5100-spi
[关于模块安装位置]
如果不想重新打包根文件系统,可在编译完内核模块后,直接将 drivers/net/ethernet/wiznet/ 目录下的 w5100.ko 和 w5100-spi.ko 拷贝到板端执行
insmod
挂载亦可,注意挂载顺序。insmod w5100.ko && insmod w5100-spi.ko
而后执行 ifconfig -a
即可查看到 eth1 网络设备。
执行 cat /proc/interrupts
可查看该设备注册的中断。
而后可使用ethtool和iperf3等工具对设备进行查看和测试。使用ethtool查询网卡信息的命令:ethtool -i eth1
。使用iperf3测试过程如下图:
[在Buildroot中添加ethtool和iperf3]
如果你选择Buildroot制作根文件系统,可在编译Buildroot之前使用
menuconfig
添加ethtool和iperf3软件包:
- Target packages -> Networking applications
- 选择 ethtool
- 选择 iperf3
7.1.3 存在的问题
在Hi3516DV300上使用W5500存在以下两点问题:
(1)中断号分配问题
如前文所述,为了使W5500的驱动正常获取到中断号,我们在内核中禁用了全部GPIO的驱动,使得用户无法使用用户态GPIO设备。即便在W5500驱动中为中断申请函数 request_threaded_irq()
的传参增加 IRQF_SHARED
标志也无法与GPIO共享中断,仍会提示以下信息:
genirq: Flags mismatch irq 40. 00000088 (eth1) vs. 00000084 (120da000.gpio_chip)
w5100: probe of spi1.0 failed with error -16
(2)发送队列超时
当开发板侧使用iperf3服务端,PC侧使用iperf3客户端进行吞吐量测试时,可触发以下 transmit queue 0 timed out
错误:
该错误导致数据收发延迟甚至无法通信,但当前暂未解决。
7.2 使用USB Gadget复合驱动与PC进行串口&网络通信
SDK提供的内核配置中,已经开启了 RNDIS + CDC Serial + Storage configuration 的USB Gadget复合驱动支持,只需将USB控制器更改为设备模式即可使用。相关的配置项可在内核配置界面中的 Device Drivers -> USB Support -> USB Gadget Support -> Multifunction Composite Gadget 路径找到。
使用 menuconfig
配置内核:
make ARCH=arm CROSS_COMPILE=arm-himix200-linux- menuconfig
进行以下修改:
- Device Drivers -> PHY Subsystem -> Hisilicon USB related configuration
- 取消选择 USB DRD0 Mode Select HOST
- 选择 USB DRD0 Mode Select DEVICE
保存并重新编译内核,将生成的 uImage 烧录到板端,将内核模块驱动照上文所述拷贝到根文件系统对应目录。
启动到系统后,可执行以下命令挂载复合驱动。
modprobe g_multi.ko
[重要-挂载命令说明]
在一般的说明中,g_multi驱动挂载时需指定用作g_mass_storage的挂载路径参数,如 6.3 使用USB Gadget连接开发板分区到PC 章节所示。但实测发现对于Hi3516DV300,g_multi驱动无法使PC端识别出存储器盘符,本节不对此进行深入探究,仅叙述如何使用u_serial和RNDIS,因此上述挂载命令中未增加路径参数。
对u_serial和RNDIS的挂载也可分别、独立进行,此处不表。
7.2.1 u_serial的使用
将开发板连接到PC,可在设备管理器中看到识别出的串口:
此时开发板系统中的 /dev/ 目录下应当自动生成了 ttyGS0 节点,若未自动生成,可使用 cat /sys/class/tty/ttyGS0/dev
命令查看设备的设备号,而后使用 mknod
命令自行创建。
借由该串口设备,开发板和PC机即可进行通信,默认波特率115200。经配置后可在PC上通过该串口登录板端串口控制台等,有兴趣的读者可自行查找相关资料进行学习。
7.2.2 RNDIS设备的使用
RNDIS使设备被识别为一个网卡,将开发板连接到PC,可在设备管理器中看到识别出的RNDIS设备:
右键该设备,选择【更新驱动程序】->【浏览我的电脑以查找驱动程序】->【让我从计算机上的可用驱动程序列表中选取】->【网络适配器】->【Microsoft-远程NDIS兼容设备】,在弹出的警告中选择“是”,然后点击“下一步”以完成安装。
此时在PC端将识别出一个网卡设备:
对应着板端的 usb0 网络设备节点:
将两者配置在同一子网下即可相互通信。通过此方法还可以连接开发板到外网,此处不表,有兴趣的读者可自行查找相关资料进行学习。
使用iperf3对网络吞吐量进行测试的结果如下:
测试时发现,当开发板侧为iperf3客户端,PC侧为iperf3服务端时,测速期间会影响到PC上其它USB设备的使用,如鼠标卡顿;反之没有明显不良影响。
7.2.3 存在的问题
(1)在Hi3516DV300上g_multi驱动中对mass_storage的支持无效
详见本节内容伊始的复合驱动挂载截图,板端打印信息中未见mass_storage设备的日志输出,且驱动挂载后PC机上未识别到存储设备。
(2)RNDIS测试过程中USB设备间的干扰问题
详见 7.2.2 RNDIS设备的使用 一节。暂不清楚这种表现是否属于正常。
8 问答
本文档撰写期间未收到目标读者群的提问投稿。
没有什么FAQ文档能够涵盖所有问题,有人的地方就会有问题。但正是这些问题,赋予了“调试”以“意义”,赋予了“经验”以“价值”。
太多细节被我们略过了——我们终究无法在如此短暂的时间里一窥嵌入式的全貌,这姑且算是一个遗憾。但那又如何呢?壮丽的征程,你已经走在其上了!
———— 2022-4-16@燕卫博 ————
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/190080.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...