uc-PC板子上跑的是Linux 4.13.3,使用设备树。内核不经压缩,就地运行(XIP,全称execute in place)。
需要使能的功能有:网络、显示、USB、SD卡、摄像头、yaffs。这些功能全部开启的话,内核大小将为4MB以上,而片内flash只有2MB,不模块化的话只能跑在SDRAM上,这回导致性能低下,所以调通设备驱动之后应该进行模块化,将关键部分编译进内核,内核塞进片内flash,从而提高性能。
以下将只描述它们如何配置,而不涉及具体驱动的软件架构。
- 配置之前,应该设置环境变量:ARCH=arm CROSS_COMPILE=arm-none-eabi-(其他编译工具链设置也类似),不然的话会按照host的架构去配置。
- 配置时一般用
make menuconfig
,如果装了qt的话还可以用make xconfig
;后者可以用鼠标操作,全屏的话看到的信息量更多。 - 搜索某一个配置时,不用加
CONFIG_
前缀,而且不用管大小写。 - 某一个配置没有显示出来,是因为它依赖的配置还没有使能;这时就要耐着性子去一个个翻找。。。
网络
Linux内核实现了相当完整而庞大的网络协议栈——对于单片机来说实在过于庞大;并且,协议栈部分是必须编译进内核而不能模块化的,因此这一块只能放置于片内flash中;而且,只在defconfig的基础上使能网络的话,内核就超过了2MB了。因此在调通板子上的驱动之前,内核都只能在SDRAM上面就地运行。。参考前文bootloader的配置。
下面实现基本的网络功能:获取IP、跑通nfs。诸如DNS之类的暂时不弄。
上位机搭建NFS服务器
$ sudo apt install nfs-common
我的nfs目录是/home/hyq/nfs
,为此修改配置文件/etc/exports
:
/home/hyq/nfs *(rw,sync,no_root_squash,no_subtree_check)
重启服务以生效:
$ sudo /etc/init.d/nfs-kernel-server restart
一件小事:
有时候调板子是这种情况:电脑要连校园网WiFi来科学上网(因为有ipv6),板子连着一个不上网的路由器,电脑又需要通过网线来连那个路由器来访问板子。这时候电脑如果不插网线就能上网,插了就上不了网了。这是因为默认的路由指向了以太网。需要通过route
命令来将路由改到WiFi上。首先查看路由表:
$ route
内核 IP 路由表
目标 网关 子网掩码 标志 跃点 引用 使用 接口
default OpenWrt.lan 0.0.0.0 UG 100 0 0 enp2s0
...
我电脑的以太网接口是enp2s0,WiFi接口是wlp1s0(别的电脑可能分别是eth0和wlan0)。default到了enp2s0,将其路由到wlp1s0:
$ sudo route del default
$ sudo route add default gw <只连WiFi时候的网关> wlp1s0
此时默认就路由到WiFi上了。此时或许还可能上不了网但是能ping外网。此时就要在/etc/resolv.conf
里面改DNS,比方说改为114.114.114.114,然后就可以上网了。
内核配置
在第一层次的配置中使能Networking support
,然后在其下Networking options
下面使能TCP/IP networking
,我只留了这些配置:
并且添加网卡驱动:分别需要MAC和phy的驱动。在Device Drivers
->Network device support
->Ethernet driver support
下面选择STM32的MAC。
phy的驱动可以选择默认的CONFIG_FIXED_PHY
,也可以选择SMSC的phy,都可以成功驱动LAN8720A。在Device Drivers
->Network device support
->PHY Device support and infrastructure
下选择。
然后修改设备树:
首先在stm32f429.dtsi
里的pinctrl节点下增加一个RMII引脚的配置,并且在ethernet节点下面添加mac地址;mac地址可以随便设置,只要不跟局域网里面的设备重合就行了:
/ {
...
soc {
...
pinctrl: pin-controller {
...
// RMII引脚复用的配置
ethernet_rmii: rmii@0 {
pins {
pinmux = <STM32F429_PB12_FUNC_ETH_MII_TXD0_ETH_RMII_TXD0>,
<STM32F429_PB13_FUNC_ETH_MII_TXD1_ETH_RMII_TXD1>,
<STM32F429_PB11_FUNC_ETH_MII_TX_EN_ETH_RMII_TX_EN>,
...
slew-rate = <3>;
};
};
...
};
...
// ethernet节点
mac: ethernet@40028000 {
// 添加mac地址
mac-address = [C0 B1 3D 88 88 89];
// 其他属性照旧
};
};
};
然后在stm32f429-disco.dts
里补充设置ethernet节点:
&mac {
status = "okay";
pinctrl-0 = <ðernet_rmii>;
pinctrl-names = "default";
phy-mode = "rmii";
snps,reset-gpio = <&gpioh 2 GPIO_ACTIVE_HIGH>;
snps,reset-active-low;
snps,reset-delays-us = <0 10000 100000>;
};
这些设置的来源是Documentation/devicetree/bindings/net/stm32-dwmac.txt。这里并不需要像stm32429i-eval评估板设置的那么繁琐,只需要设置哪一套引脚、RMII模式,以及phy芯片的reset引脚即可。
源码里的小bug
stm32的MAC初始化是drivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c
的stm32_dwmac_init()
,它根据配置将外设设置为MII模式或RMII模式。原代码第42行左右RMII模式寄存器设置错误。本网卡只有RMII模式,而stm32429i-eval的网卡工作在MII模式,估计因此他们没查出这个bug。
- val = (plat_dat->interface == PHY_INTERFACE_MODE_MII) ? 0 : 1;
+ val = (plat_dat->interface == PHY_INTERFACE_MODE_MII) ? 0 : MII_PHY_SEL_MASK;
启动的printk
[ 3.430000] libphy: Fixed MDIO Bus: probed
[ 3.470000] stm32-dwmac 40028000.ethernet: PTP uses main clock
[ 3.480000] stm32-dwmac 40028000.ethernet: no reset control found
[ 3.480000] stmmac - user ID: 0x10, Synopsys ID: 0x35
[ 3.490000] stm32-dwmac 40028000.ethernet: Ring mode enabled
[ 3.500000] stm32-dwmac 40028000.ethernet: DMA HW capability register supported
[ 3.510000] stm32-dwmac 40028000.ethernet: Enhanced/Alternate descriptors
[ 3.510000] stm32-dwmac 40028000.ethernet: Enabled extended descriptors
[ 3.520000] stm32-dwmac 40028000.ethernet: RX Checksum Offload Engine supported
[ 3.530000] stm32-dwmac 40028000.ethernet: COE Type 2
[ 3.530000] stm32-dwmac 40028000.ethernet: TX Checksum insertion supported
[ 3.540000] stm32-dwmac 40028000.ethernet: Wake-Up On Lan supported
[ 3.550000] stm32-dwmac 40028000.ethernet: Enable RX Mitigation via HW Watchdog Timer
[ 4.200000] libphy: stmmac: probed
...
[ 4.330000] stm32_rtc 40002800.rtc: setting system clock to 2000-01-01 03:07:42 UTC (946696062)
[ 4.540000] SMSC LAN8710/LAN8720 stmmac-0:00: attached PHY driver [SMSC LAN8710/LAN8720] (mii_bus:phy_addr=stmmac-0:00, irq=-1)
[ 4.600000] stmmac_init_dma_engine, reset addr: 40028000
[ 4.600000] stm32-dwmac 40028000.ethernet eth0: IEEE 1588-2008 Advanced Timestamp supported
[ 6.930000] stm32-dwmac 40028000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx
nfs作为根文件系统
首先电脑的nfs目录下面得有完整的rootfs;可以暂时先从网上下载一个可用的rootfs,然后解压到那里。
然后配置内核以使能NFS;并且需要配置ROOT_NFS
:在File systems
->Network File Systems
下面即可配置。
最后在stm32f429-disco.dts里面添加内核参数。现在的风格是,kernel command line加在设备树的chosen节点下面,而不是uboot传tags。
/ {
chosen {
bootargs = "root=/dev/nfs rw "
"ip=192.168.2.123:192.168.2.1:::stm32:eth0:on "
"nfsroot=192.168.2.202:/home/hyq/nfs";
};
...
};
其中,ip
可以设置为ip=dhcp
,也可以像上述那样设置一个固定的地址;nfsroot
对应到上位机的ip地址和nfs目录。
ping
因为没有DNS,所以不能ping百度之类的域名,只能ping IP地址。而且,如果内核参数是ip=dhcp
的话,可以ping通外网;如果只是一个固定地址就只能ping局域网里面的东西。。下面ping的是114.114.114.114这个DNS服务器:
显示
主流内核中STM32的LTDC驱动居然放在drivers/gpu/drm/stm/目录下面。。。想必ST认为它的DMA2D可以算是一个gpu了,于是就要用内核里面的DRM架构了。。实在志不在小。。而emcraft的uclinux中stm32显示驱动还仅仅放在drivers/video/目录下面。
对于单片机来说,使用DRM架构的一大缺点是它实在太庞大了,塞不进片内flash,只能模块化以跑在SDRAM里,因而性能低下;因此应该选用尽量简单的驱动以避免这一点。
使用内核中的DRM驱动
内核配置:开启DRM:
还要开启STM32的DRM支持:
还要开启一个panel。这里为简单起见,选择simple-panel
DRM架构需要有一个panel,即外接的那个液晶屏控制器。有些RGB的lcd也要用诸如I2C接口去配置一些奇怪的参数。板子上直接将RGB用DAC转化成VGA信号了,所以选择一个“哑”的panel,即CONFIG_PANEL_SIMPLE。在drivers/gpu/drm/panel/panel-simple.c里面实现了一大堆LCD,它们只需要开关背光灯就行了。于是在里面找一个想要的尺寸——比如标准VGA信号的640x480——的屏幕,这里选择了et057090dhu,然后稍微改一下那些时钟配置,适合60HZ的VGA:
static const struct drm_display_mode edt_et057090dhu_mode = {
.clock = 31468,
.hdisplay = 640,
.hsync_start = 640 + 16,
.hsync_end = 640 + 16 + 96,
.htotal = 640 + 16 + 96 + 48,
.vdisplay = 480,
.vsync_start = 480 + 10,
.vsync_end = 480 + 10 + 2,
.vtotal = 480 + 10 + 2 + 33,
.vrefresh = 60,
.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
};
设备树需要增加panel-rgb节点,并且有两个endpoint相互指引。改写如下:
/{
panel_rgb: panel-rgb {
compatible = "edt,et057090dhu";
status = "okay";
port {
panel_in_rgb: endpoint {
// 链接到下面的ltdc_out_rgb节点
remote-endpoint = <<dc_out_rgb>;
};
};
};
...
};
<dc {
status = "okay";
pinctrl-0 = <<dc_pins>;
pinctrl-names = "default";
dma-ranges;
port {
ltdc_out_rgb: endpoint {
// 链接到上面的panel_in_rgb节点
remote-endpoint = <&panel_in_rgb>;
};
};
};
为了启用tty终端,还需要选择CONFIG_FRAMEBUFFER_CONSOLE
:
接上显示器,启动之后,就可以看到它在屏幕上慢悠悠地printk()了。。
一个小问题
内核初始化结束后,会统一将未用到的外设的时钟关掉;但是这个ltdc的驱动并没有向内核注册时钟,于是它最后就被关掉了。。。于是在drivers/clk/clk-stm32f4.c里为ltdc时钟添加CLK_IGNORE_UNUSED
的属性。
static const struct stm32f4_gate_data
stm32f429_gates[] __initconst = {
...
{ STM32F4_RCC_APB2ENR, 26, "ltdc", "apb2_div",
CLK_IGNORE_UNUSED },
};
仅使用simple-fb
这是最简单的方法:bootloader里面初始化显示器,然后给内核传参framebuffer的地址,从而不用那个庞大的DRM架构。初始化部分在bootloader里已讲述。
在Device Drivers
->Graphics support
->Frame buffer Devices
下找到Simple framebuffer support
:
在设备树的chosen节点下增加一个framebuffer节点:
/ {
chosen {
framebuffer0: framebuffer@83f00000 {
compatible = "simple-framebuffer";
reg = <0x83f00000 (640 * 480 * 2)>;
width = <640>;
height = <480>;
stride = <(640 * 2)>;
format = "r5g6b5";
clocks = <&rcc 1 CLK_LCD>;
};
};
};
其中reg属性是framebuffer的地址,其余属性顾名思义。
显示驱动就可以塞进内核从而跑在片内flash里了。启动之后明显感到刷屏速度增加了数倍。
USB主机
STM32F429有两个USB 2.0,一个是全速USB,一个是全速、高速USB,二者片内集成了全速的phy,而后者需要外接phy芯片才能实现高速USB。为简单起见,uc-PC只引出了两个全速USB。虽然它们的寄存器组都非常相似,而且它们都使用Linux内核中相同的驱动,但是它们的表现不太一样。高速USB工作在全速USB下会有点不爽。
基本配置
跟很多SOC一样,stm32的很多外设都是Designware的IP,所以驱动比较通用;在drivers/usb/dwc2里面见到stm32的USB驱动竟诸如树莓派(BCM2835)之类的USB驱动并列时,不必惊讶。内核配置在Device Drivers
->USB support
下面找到DesignWare USB2 DRD Core Support
:
设备树里面添加两个USB节点:
&usbotg_hs {
compatible = "st,stm32f4x9-fsotg";
dr_mode = "host";
pinctrl-0 = <&usbotg_fs_pins_b>;
pinctrl-names = "default";
status = "okay";
};
&usbotg_fs {
compatible = "st,stm32f4x9-fsotg";
dr_mode = "host";
pinctrl-0 = <&usbotg_fs_pins_a>;
pinctrl-names = "default";
status = "okay";
};
但是按照默认代码,高速USB启动时候有错:
[4.320000] dwc2 40040000.usb: dwc2_core_reset() HANG! Soft Reset GRSTCTL=80000001
这是因为,dwc的USB HS在配置之前需要复位:通过置位GRSTCTL的第0位,然后等待硬件清零。stm32的USB HS默认是外接PHY芯片的高速模式,软件复位时候需要等PHY芯片。但是板子只有内置PHY。选择内置PHY需要置位GUSBCFG的第6位。
drivers/usb/dwc2/platform.c:dwc2_driver_probe()函数中,首先软件复位,然后在后面的drivers/usb/dwc2/hcd.c:dwc2_fs_phy_init()才设置为内置PHY。前面的软件复位等的是外部PHY,于是卡死,无论后面再怎么设置内置PHY。因此需要在前面先将其设置为全速USB模式:
// 第418行前面,先设置为全速USB模式
// 其实严格来说应该用writel接口的。。。
*(uint32_t*)0x4004000c |= 0x00000040;
msleep(1);
dwc2_core_reset_and_force_dr_mode(hsotg);
然后就能启动了。虽然下面的printk还是有点问题,但是启动之后这个USB是没问题的,姑且就不管它了。
[4.950000] dwc2 40040000.usb: dwc2_wait_for_mode: Couldn't set host mode
[4.960000] dwc2 40040000.usb: DWC OTG Controller
[4.960000] dwc2 40040000.usb: new USB bus registered, assigned bus number 1
[4.970000] dwc2 40040000.usb: irq 50, io mem 0x40040000
[5.010000] hub 1-0:1.0: USB hub found
[5.020000] hub 1-0:1.0: 1 port detected
添加简单设备:USB串口
直接添加驱动就可以了,比方说PL2303驱动:
启动后插上PL2303串口,就可以有/dev/ttyUSB0了:
(其实USB hub是相当通用的233)
添加U盘驱动
需要先在Device Drivers
->SCSI device support
里面使能SCSI以及SCSI disk:
然后在Device Drivers
->USB support
下面使能USB Mass Storage:
还要有一个文件系统:比方说fatfs,在File systems
->DOS/FAT/NT Filesystems
下面:
而且还需要有一个字符编码,即所谓的”Native language support”,不然U盘会挂载失败。这里选用”NLS ISO 8859-1”:
插入U盘后就可以有以下printk:
[ 49.210000] usb 2-1: new full-speed USB device number 2 using dwc2
[ 49.460000] usb 2-1: not running at top speed; connect to a high speed hub
[ 49.480000] usb 2-1: New USB device found, idVendor=14cd, idProduct=1212
[ 49.490000] usb 2-1: New USB device strings: Mfr=1, Product=3, SerialNumber=2
[ 49.500000] usb 2-1: Product: Mass Storage Device
[ 49.510000] usb 2-1: Manufacturer: Generic
[ 49.520000] usb 2-1: SerialNumber: 121220130416
[112.900000] SCSI subsystem initialized
[112.990000] usb-storage 2-1:1.0: USB Mass Storage device detected
[113.010000] scsi host0: usb-storage 2-1:1.0
[113.020000] usbcore: registered new interface driver usb-storage
[114.080000] scsi 0:0:0:0: Direct-Access Mass Storage Device 1.00 PQ: 0 ANSI: 0 CCS
[120.110000] sd 0:0:0:0: [sda] 15523840 512-byte logical blocks: (7.95 GB/7.40 GiB)
[120.130000] sd 0:0:0:0: [sda] Write Protect is off
[120.140000] sd 0:0:0:0: [sda] Mode Sense: 03 00 00 00
[120.160000] sd 0:0:0:0: [sda] No Caching mode page found
[120.170000] sd 0:0:0:0: [sda] Assuming drive cache: write through
[120.200000] random: crng init done
[120.210000] sda: sda1 sda2
[120.230000] sd 0:0:0:0: [sda] Attached SCSI removable disk
可见它已经识别到/dev/sda1、/dev/sda2了。这个U盘里是树莓派的rootfs,其中第一个分区是fat格式,可以挂载来看看:
/ # mount -t vfat /dev/sda1 /mnt/
[ 233.320000] FAT-fs (sda1): Volume was not properly unmounted. Some data may be corrupt. Please run fsck.
/ # ls mnt
bcm2709-rpi-2-b.dtb fixup.dat start.elf
bcm2710-rpi-3-b.dtb fixup_cd.dat start_cd.elf
bootcode.bin fixup_db.dat start_db.elf
capture fixup_x.dat start_x.elf
cmdline.txt kernel7.img
config.txt overlays
/ #
添加键盘驱动
键盘驱动也相当通用,直接配置它即可。在Device Drivers
->HID support
里面选择Generic HID driver
,并且在USB HID drivers
下面还要选USB HID transport layer
:
然后插上一个无线键盘(一定要无线键盘或者机械键盘,普通的有线键盘不行。。),就可以有下面的printk:
[ 830.670000] usb 2-1: new full-speed USB device number 3 using dwc2
[ 830.930000] usb 2-1: New USB device found, idVendor=24ae, idProduct=2010
[ 830.940000] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 830.950000] usb 2-1: Product: Rapoo 2.4G Wireless Device
[ 830.960000] usb 2-1: Manufacturer: RAPOO
[ 831.000000] input: RAPOO Rapoo 2.4G Wireless Device as /devices/platform/soc/50000000.usb/usb2/2-1/2-1:1.0/0003:24AE:2010.0001/input/input0
[ 831.010000] hid-generic 0003:24AE:2010.0001: input,hidraw0: USB HID v1.10 Mouse [RAPOO Rapoo 2.4G Wireless Device] on usb-50000000.usb-1/input0
[ 831.080000] input: RAPOO Rapoo 2.4G Wireless Device as /devices/platform/soc/50000000.usb/usb2/2-1/2-1:1.1/0003:24AE:2010.0002/input/input1
[ 831.160000] hid-generic 0003:24AE:2010.0002: input,hiddev96,hidraw1: USB HID v1.10 Device [RAPOO Rapoo 2.4G Wireless Device] on usb-50000000.usb-1/input1
[ 831.210000] input: RAPOO Rapoo 2.4G Wireless Device as /devices/platform/soc/50000000.usb/usb2/2-1/2-1:1.2/0003:24AE:2010.0003/input/input2
[ 831.290000] hid-generic 0003:24AE:2010.0003: input,hidraw2: USB HID v1.10 Keyboard [RAPOO Rapoo 2.4G Wireless Device] on usb-50000000.usb-1/input2
此时敲击键盘,屏幕上的终端将会有显示:
其实Linux的input事件是全局性的,因此只要驱动配好了,就有反应了。
为啥不能用普通的有线键盘?
stm32的USB在Linux下的驱动有个很大的问题:它不识别低速设备。。。整天枚举失败。。。这估计是stm32跑Linux的性能太低了,以至于设备跑得比stm32都要快,于是就说设备不接受地址云云。。而低端的有线键盘都是低速设备,于是枚举失败。。
[ 1506.090000] usb 2-1: new low-speed USB device number 6 using dwc2
[ 1506.310000] usb 2-1: device descriptor read/64, error -71
SD卡
emcraft的4.2内核中,SD卡驱动直接用arm通用的驱动”arm,primecell”,这个compatible在drivers/of/platform.c里面匹配,并生成一个amba总线。另外它在drivers/mmc/host/mmci.c里面实现了一个variant_stm32f4,从而接入Linux的mmc驱动栈中。
按理说主流的Linux 4.13内核也是可以这么干的,但是这里的drivers/mmc/host/mmci.c的数据结构跟前者并不完全相同,而且逻辑处理也不同了。这导致SD卡整天不能识别。。
方便起见,直接换能用的代码
将emcraft的mmci.c、mmci.h复制过来直接用。。。
内核配置:选择ARM AMBA Multimedia Card Interface support
以及MMC block device driver
:
设备树增加mmc节点,基本照抄emcraft的,不过并没用DMA,因为性能比较低下,DMA很容易下溢死掉:
sdio:sdi@40012C00 {
compatible = "arm,primecell";
reg = <0x40012C00 0x400>;
interrupts = <49>;
max-frequency = <25000000>;
bus-width = <4>;
voltage-ranges = <3200 3300 3300 3400>;
clocks = <&rcc 0 171>;
clock-names = "apb_pclk";
arm,primecell-periphid = <0x40480180>;
status = "disabled";
};
...
&sdio {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&sdio_pins>;
cd-gpio = <&gpioh 15 GPIO_ACTIVE_LOW>;
};
其中cd-gpio
是卡识别的GPIO。
挂载SD卡
插卡后,将有以下的printk:
[ 5492.300000] mmc0: host does not support reading read-only switch, assuming write-enable
[ 5492.310000] mmc0: new SD card at address 0001
[ 5492.320000] mmcblk0: mmc0:0001 971 MiB
[ 5492.340000] mmcblk0: p1 p2
/ # ls /dev/mmcblk0*
/dev/mmcblk0 /dev/mmcblk0p1 /dev/mmcblk0p2
/ #
此时就可以正常挂载它了。不过有时候会出现EIO错误;发出EIO的地方是mmci.c:mmci_data_irq第995行左右的 if (status & MCI_RXOVERRUN),这是SDIO的FIFO溢出了,说明主控取数据太慢了。。解决的办法是开硬件流控制,当sdio发现fifo溢出时候会卡一下时钟,免得sd卡跑的太快。这就需要在variant_stm32f4里面加一个控制位:
.clkreg_enable = MCI_ST_UX500_HWFCEN,
这是SDIO_CLKCR的第14位。
有时候也会有EILSEQ错误,也是在mmci.c:mmci_data_irq里面发出的,起因是CRC错误,这可能是因为走线不太好或者接触不良啥的。于是降低时钟频率:调高SDIO_CLKCR的低8位分频数,于是variant_stm32f4的clkreg_enable变成:
.clkreg_enable = MCI_ST_UX500_HWFCEN | 0x00000006,
然后就没错了。。。
摄像头
Linux里面集成了STM32的DCMI驱动,直接用就行了。
基本配置
在Device Drivers
->Multimedia support
->V4L platform devices
里面使能DCMI的驱动:
然后添加摄像头驱动。记得要先取消掉Autoselect ancillary drivers
的选项,下面才有一系列的摄像头驱动可选:
不妨添加OV2640的驱动。这是一个200万像素的摄像头,可以输出RGB、YUV、JPEG格式的照片。
然后设备树中,在I2C节点下面添加OV2640的节点:
&i2c1 {
status = "okay";
ov2640: camera@30 {
compatible = "ovti,ov2640";
reg = <0x30>;
clocks = <&clk_ext_camera>;
clock-names = "xvclk";
status = "okay";
port {
// 指向下面的dcmi_0 endpoint
ov2640_0: endpoint {
remote-endpoint = <&dcmi_0>;
};
};
};
};
&dcmi {
status = "okay";
port {
dcmi_0: endpoint {
// 指向上面的ov2640 endpoint
remote-endpoint = <&ov2640_0>;
bus-width = <8>;
hsync-active = <0>;
vsync-active = <0>;
pclk-sample = <1>;
};
};
};
配置基本上照抄stm32429i-eval的配置。
启动之后可以看到摄像头的printk:
[ 5.600000] stm32f4-i2c 40005400.i2c: STM32F4 I2C driver registered
[ 5.760000] Linux video capture interface: v2.00
[ 5.870000] stm32-dcmi 50050000.dcmi: Probe done
[ 5.970000] ov2640 0-0030: ov2640 Product ID 26:42 Manufacturer ID 7f:a2
[ 5.990000] i2c i2c-0: OV2640 Probed
v4l2应用程序测试
v4l2有个官方测试程序capture.c,它将摄像头数据写入文件中。可以改写一下,将裸RGB数据写到/dev/fb0中,那么就可以在显示器上看到摄像头的数据了。比方说这样写:
// 先打开/dev/fb0
fp = fopen("/dev/fb0", "w");
framebuffer = malloc(640*480*2);
// 在主循环里面将数据写入framebuffer
static void process_image(const void *p, int size)
{
short *fb = framebuffer, *src = p;
if (out_buf) {
for(int y = 0; y < 240; y++)
for(int x = 0; x < 320; x++)
fb[y*2*640 + x*2] = src[y*320 + x];
fseek(fp, 0, SEEK_SET);
fwrite(fb, 640*480*2, 1, fp);
}
fflush(stderr);
fprintf(stderr, ".");
fflush(stdout);
}
编译之后,使用.capture -o -f -c 10
,即是获取10张照片并写入framebuffer。可以在显示器上看到照片:
不过由于STM32的DMA最多只能搬运64k个单位的数据,以32字为单位的话最多就是256kb大小的数据。于是摄像头只能设置为320x240大小。并且由于应用程序在SDRAM上跑,性能低下,以至于两秒钟才能出一张图片。。。而且SDRAM的带宽太小,拍照时还会时常说DMA下溢orz。。。据ST的手册推荐的话,应该利用DMA的双缓冲特性从而突破64k的限制,不过内核中并没有实现这一点。
yaffs2
yaffs已经是一个相当老的文件系统了。。。它在几十上百MB的SLC上面跑到很欢快,但是并不太适用于MLC、TLC之类的几GB以上的大容量nand flash。不过对于uc-PC来说已经足够了。yaffs2适应于大页(2kb)的nand flash,yaffs适应于小页(512B)的nand flash。yaffs2声称它可工作在yaffs模式下面从而向前适应(但是我实验结果发现它并不如此)。
yaffs可以从它官网上下载。
打补丁
根据它的README-linux,使用以下命令去给内核打补丁:
./patch-ker.sh c m <内核目录>
然后内核配置时候就可以有yaffs的选项了:
yaffs2已经可以支持4.9的内核了。不过在4.13内核上编译有问题,提示CURRENT_TIME这个宏未定义。发现这是因为Linux打算解决y2038的千年虫问题,需要改用别的计时方式。我直接用了current_kernel_time()代替了。
接下来要在内核里面添加nand flash的驱动。为方便起见,使用platform-nand驱动,即CONFIG_MTD_NAND_PLATFORM
:
仿照arch/arm/mach-omap1/board-h2.c,在arch/arm/mach-stm32/board-dt.c里面定义分区表以及驱动信息。
试探性的定义了三个分区:
/* 分区表 */
static struct mtd_partition stm32_nand_partitions[] = {
{
.name = "first",
.offset = 0,
.size = 128 * 1024,
.mask_flags = MTD_WRITEABLE, /* force read-only */
},
{
.name = "second",
.offset = MTDPART_OFS_APPEND,
.size = 256 * 1024,
.mask_flags = MTD_WRITEABLE, /* force read-only */
},
{
.name = "rootfs",
.offset = MTDPART_OFS_APPEND,
.size = 120 * SZ_1M,
},
};
然后是platform data:
static struct platform_nand_data stm32_nand_platdata = {
.chip = {
// 分区表
.nr_partitions = ARRAY_SIZE(stm32_nand_partitions),
.partitions = stm32_nand_partitions,
},
.ctrl = {
// 读写驱动程序
.cmd_ctrl = fmc_send_cmd,
.dev_ready = fmc_read_rb,
},
};
接着是platform device:
static struct platform_device stm32_nand_device = {
.name = "gen_nand",
.dev = {
.platform_data = &stm32_nand_platdata,
},
};
最后,在模块构造函数里面注册platform device:
static void __init board_stm32_ucpc_init(void)
{
printk("STM32 UCPC: nand platform device");
platform_add_devices(stm32_devices, ARRAY_SIZE(stm32_devices));
}
启动之后,可以看到识别到三个分区:
[ 0.970000] nand: device found, Manufacturer ID: 0xec, Chip ID: 0xf1
[ 0.980000] nand: Samsung NAND 128MiB 3,3V 8-bit
[ 0.980000] nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64
[ 0.990000] Scanning device for bad blocks
[ 1.090000] Creating 3 MTD partitions on "gen_nand.0":
[ 1.100000] 0x000000000000-0x000000020000 : "first"
[ 1.120000] 0x000000020000-0x000000060000 : "second"
[ 1.150000] 0x000000060000-0x000007860000 : "rootfs"
yaffs作为根文件系统
查看dmesg,可以看到有Creating xxx MTD partitions on xxx
的信息。根据它出现的时间先后顺序,这些块设备依次形成/dev/mtdblock0
、/dev/mtdblock1
、/dev/mtdblock2
等等。将含有rootfs的mtd分区传参给内核。
但是在这之前先得将rootfs烧录进nand flash。我使用了最简单的方法:先uboot通过tftp启动一个跑在SDRAM上的内核,挂载mtd分区以及nfs,然后从nfs上面拷贝rootfs到mtd分区里面。。。然后改写kernel command line,将mtd分区作为rootfs传参。