如何在STM32上移植Linux?超详细的实操经验分享
刚从硬件跳槽为嵌软时,没有任何⼀丝的准备。⼀⼊职,领导就交代了⼀项特难的任务——在stm32上移植linux!
瞬间我就懵了,没办法硬着头⽪上吧,先搜集资料,我之前跑的是ok6410的板⼦上运⾏的linux,现在是在stm32上移植,以前stm32倒是玩过,研究⽣期间就捣⿎过它,但现在还没从抓烙铁的硬件当中缓过神来,就转到嵌⼊式软件的开发,更头疼的是stm32没有MMU!没有MMU!了⼀下,好吧,有个uClinux!
于是开始学习各种相关的知识,了解到linux的启动⼀般是u-boot——》liunx内核——》根⽂件系统,那么⾸先要做个基于stm32的u-boot,先初始化时钟、外设、中断什么的,看了韦东⼭⽼师的视频感觉很好,理解了不少,从⼀⽆所知到有点明⽩了。
移植u-boot到stm32f407
这时⽤的板⼦是stm32f103,ST对这个板⼦早在08年就发布了⽀持它的u-boot、Uclinux内核(领导额外买的,说是要我对照着对应修改⽀持stm32f407的uClinux内核),但是只有Uclinux内核有源码,u-boot就给了个hex⽂件尴尬,其实cortx m3与cortx m4之前架构已经⼤不⼀样了,这样修改的话对于我来说⽆疑是很难的,我⼀听头都⼤了,⼜是单⼲,烦,没办法照做呗!那就先弄stm32f103的,把之前那个编译没有
错误的引导程序拷⼊,在stm32的0x08003000的位置拷⼊官⽅提供的uClinux内核,⼀启动,接上串⼝,打开串⼝助⼿,⼀看啥都没有。。。
到底错在哪啦?仔细想想,先是要看看最后跳转内核那步到底有没有成功,那就先验证这⼀步,参照原⼦的IAR跳转历程,编了个跑马灯跳转程序,就是引导程序没变,拷在地址0x08000000,⽽跑马灯程序拷在0x08003000上,如果led 灯亮灭就说明跳转⽆误,于是⼀启动,灯不亮。抓狂抓狂怎么情况啊,后仔细排查发现是跳转函数,引导程序参照的是u-boot源码来编写的,⾥⾯的函数⽤函数指针赋个地址(0x08003000),最后跳转过去。折腾了两天最后对着原⼦的程序修改,灯居然可以亮灭了,我现在想想也不知道是什么问题,不过⾄少现在可以实现跳转了。
再把内核拷到0x08003000,⼀启动,串⼝助⼿还是没有任何输出,这下就真的烦了,郁闷死了,stm32f103还搞不定还想搞stm32f407。。。之后开始各种原因,各种修改,领导各种催,在stm32f103和stm32f407两个板⼦之间这搞搞,那搞搞,休息时间就看看韦⽼师的视频,资料看看有什么灵感,但是还是没什么进展。
后来在⽹上搜到⼀个哥们居然在stm32f407上移植u-boot成功了,⽽且还有启动图晒出来,这下我就想,⼈家可以我为什么就不能?于是继续,终于在⽹上到了这个u-boot的源码,根据⾃⼰的stm32f407的板⼦修改串⼝,时钟等,安装好对应的交叉编译链,注意应该是arm-non-eabi不带linux的,
因为是裸板程序不关linux啥事,然后⼀跑,终于在串⼝助⼿看到久违的u-boot启动图,狂喜!想想那段⽇⼦确实是在压⼒之下成长的,感觉技术上有了很⼤的提升了。
领导过来⼀看见有u-boot(有点东西交差了。。。)就说要把外部的SRAM驱动加上,以便于跑linux内核,这个sram只有512K,这么⼩能跑得了linux内核吗?这是后话,先把sram驱动加到u-boot上再说。
先参照原⼦的sram程序修改运⾏试试看看,结果可以运⾏但是写⼊再读出,有⼏个地址的数据总有错误,于是⼀直苦思冥想,想到了⼀个可能,驱动外部SRAM⽤到的是stm32的FSMC配置,它有btcr寄存器设置,分为bcr和btr设置,原⼦的开发板⽤的是1M16位的,⽽我的是512k8位,在btr寄存器设置那⾥应该是设成8位⽽不是16位,于是把相关设置位置0,这下数据正常了。
接下来就是在u-boot上添加sram的驱动,这个u-boot编写得还蛮好,不过它配置的是外部8M的SDRAM,那么我就在sdram_init()的函数上添加配置sram的代码,把原来配置sdram的代码通通删去。折腾了两天,编写修改成功,⼀开机,串⼝助⼿正常输出启动信息,⽤u-boot的md、mw指令验证sram的驱动是否可⾏,之间也遇到⼀些问题,如在前100个地址写ff,md查看有⼏个地址数据不对,不是显⽰ff,⽤之前的sram裸板程序也是如此,⼀想软件程序肯定是没问题,那就是硬件问题,幸亏还搞过⼀段时间硬件,不然被公司硬件⼯程师给坑了,⽤万⽤表仔细检测,果然发现sram有⼏个数据线虚焊了,怪不得数据有误,拿烙铁⼀拖,OK!数据正常了,嗯!想成为合格的嵌⼊式软件⼯程师还是要软件硬件相结合,不能脱离了硬件啊
好!⾄此基于stm32f407的u-boot移植成功,外加外部2M的SRAM驱动(后来把512K升级为2M,因为后来内核内存不够跑到⼀半kernel panic挂了,此乃后话),最后上⼀张u-boot启动图。⼈⽣第⼀篇在CSDN的博⽂,希望以后⾃⼰不断学习技术不断提升,努⼒!
linux内核文件放在哪
移植uClinux内核到stm32f407
上⾯介绍了先移植基于stm32f407的u-boot,下⾯会讲到其中最难的移植stm32f407的内核这部分,这个内核源代码我也是在⽹上到了,看介绍是国外⼤神修改⽽成的,真的万分感谢这位⼤神,⽹上的资源其实很多,要善于挖掘,善于搜寻。
内核代码是我⽆意中down下来的,刚得到代码时并没有对在stm32f407上跑uClinux有太多的信⼼,⼀是⽹上还没有在stm32f407跑uClinux的资料(⾄少我没到过)⽹上都对在stm32上跑uClinux都是唱衰的态度,的确stm32跑起uClinux系统,资源是有些匮乏,⽽stm32f407内部flash只有1M的空间,其中u-boot占了128K,那么内核就存储在0x08020000处,剩下900k的空间使⽤,还有我的板⼦还有外部2M 的SRAM,但更要命的是得到的代码是基于stm32f429的uClinux,很多⼈都在stm32f429上成功运⾏了,但是却从没在stm32f407成功过,但我已经没有退路了,项⽬需要、领导要求,只能硬着头⽪瞎改,其实对于stm32f103改成stm32f429已经好很多了,最起码stm32f429的架构和stm32f407的架构⼤致相同(内部存储和时钟和gpio等略有不同),于是就按照⾃⼰⼿上的板⼦来改,期间遇到了不少的问题,也想过放弃,不过好⽍坚持了下来,因为着急压⼒⼭⼤所以看了不少书,查了很多资料也学到了很多东西对u-boot和内核代码有了深⼊的理解,
特别感谢的是jserv⽼师,我⾛投⽆路之下给他发了⼏封邮件,他回答了我两个极为重要的问题,建议把外部的512K换成⾄少2M的SRAM,不然内核就真的跑不动了,跑到⼀半就kernel panic….
然后就是针对stm32f407来修改内核代码,stm32f429⽤的是串⼝3,我⽤的是串⼝1,改!时钟不对,改!储存地址不同,改!stm32f429不单是有外部的SRAM,空间8M还有NOR flash,财⼤⽓粗,资源随便⽤,不像我的stm32f407只有外部2M的SRAM(领导说硬件就那样,节约成本,⽆语。。),幸好uClinux代码是⽤XIP的⽅式来运⾏的,就是代码段放在内部flash中就地执⾏,数据段和bss段其它段就放在sram上运⾏,这样算算,空间还是⾜够的。
其间还出现这样的问题:
卡了我⼀个星期,当时我就百思不得其解,在创建⾼速缓存那⾥就出现内核错误运⾏不下去了,仔细⽐对了stm32f103的uClinux源代码,也没发现什么错误,⼀个多星期没有进展,内核恐慌我也恐慌了,幸好领导知道情况后也不催促我,⽽是买了⼀本《ARM Linux内核源代码分析》给我,叫我好好研读,解决问题,于是就看⾥⾯构建kmem cache那⼀篇,linux内核源码过于复杂,看得我头都⼤了,后来想想这不是办法啊,是不是⼜是硬件问题?因为原先⽤的是512k 的sram升级到2M,公司的硬件⼯程师⼜重新改版了,于是我⼜⽤电烙铁把stm32芯⽚,sram芯⽚,和他们之间的上拉电阻,⼜重新焊了⼀遍,⼀上电就正常运⾏到下⼀步了,唉~之前移植u-boot的sram驱动也是硬件坑我的啊,真不敢相信我不懂点硬件的话会坑到我什么时候。。。
接着瞎捣⿎着,前后花了将近两个⽉,就成这样了:
- 想想还真是运⽓好。。。
接下来遇到的问题,应该是少了根⽂件系统,这个uClinux代码原来是配有根⽂件系统的,是romfs,但是存储空间不够了。
uClinux的根⽂件系统未能挂载起来,因为系统原来配置的根⽂件系统是romfs,是基于stm32f429的,stm32f429的内部flash存储空间有2M,romfs占⽤空间为300多kB,这样存放显然是充⾜的,但是对于stm32f407来说,它的内部flash存储空间为1M,这样存放的话,存储空间是不够的(u-boot占⽤空间0x08000000-0x08020000,内核占⽤空间约为
0x08020000-0x080BB000,约620多KB,那么只有剩下约250多KB的空间供根⽂件系统存放),所以根据这个情况,我想是另外搭建占⽤内存空间更⼩的initramfs作为uClinux的根⽂件系统来挂载。
构建stm32f407-uClinux的initramfs根⽂件系统
上⽂讲到内核运⾏到free init memory:8k这个地⽅就卡住,运⾏不下去了,在查阅了相关资料后,推测是缺少根⽂件系
上⽂讲到内核运⾏到free init memory:8k这个地⽅就卡住,运⾏不下去了,在查阅了相关资料后,推测是缺少根⽂件系统所导致的,原来的内核源代码是搭配有根⽂件系统的bin⽂件,是romfs但没有源码,前⾯讲过我现在项⽬使⽤的是stm32f407,内部flash容量和外部SRAM都不⾜以拷⼊这个原配的romfs挂起为根⽂件系统来使⽤。
接下来就是寻⼀种经济适⽤的⽂件系统来作为内核的根⽂件系统,从⽹上查阅相关资料可以知道,Y
AFFS2⽀持的是nandflash,jffs⽀持nor flash,这些看来对于我⼿上的stm32f407来说是不适⽤的,于是我仔细研究了stm32f103的源代码,发现它是有两种启动的⽅式,⼀种采⽤的是⽤iniramfs作为根⽂件系统,xip启动,在stm32f103内部flash只有512k 的情况居然跑起了Uclinux,另⼀种是jfss2挂在外部nor flash上,显然我这种情况⽬前只有参考第⼀种⽅式来,采⽤initramfs作为根⽂件系统。于是开始构建initramfs相关⽂件。仔细研究stm32f103 XIP启动⽅式的内核配置make menuconfig中, CONFIG_INITRAMFS_SOURCE=”initramfs-filelist” ⽽initramfs-filelist位于Uclinux/linux-2.6.x下,打开⼀看是这样的:
⼀开始我看不懂这⾥⾯的shell什么意思,⽹上到⼀篇⽂章,写得很清楚,把它copy过来,学习⼀下:
把initramfs编译到内核⾥⾯去
使⽤initramfs最简单的⽅式,莫过于⽤已经做好的把kernel⾥⾯那个空的给换掉。这是2.6 kernel天⽣⽀持的,所以,你不⽤做什么特殊的设置。
kernel的config option⾥⾯有⼀项CONFIG_INITRAMFS_SOURCE(I.E. General setup—>Initramfs source file(s) in menuconfig)。这个选项指向放着内核打包initramfs需要的所有⽂件。默认情况下,这个选项是留空的,所以内核编译出来之后initramfs也就是空的,也就是前⾯提到的rootfs什么都不做的情形。
CONFIG_INITRAMFS_SOURCE 可以是⼀个绝对路径,也可以是⼀个从kernel’s top build dir(你敲⼊build或者是make 的地⽅)开始的相对路径。⽽指向的⽬标可以有以下三种:⼀个已经做好的,或者⼀个已经为制作准备好所有内容的⽂件夹,或者是⼀个text的配置⽂件。第三种⽅式是最灵活的,我们先依次来介绍这三种⽅法。
1)使⽤⼀个已经做好的档案
If you already have your own initramfs_ file (because you created it yourself, or saved file produced by a previous kernel build), you can point CONFIG_INITRAMFS_SOURCE at it and the kernel build will autodetect the file type and link it into the resulting kernel image.
You can also leave CONFIG_INITRAMFS_SOURCE empty, and instead copy file to
usr/initramfs_ in your kernel’s build directory. The kernel’s makefile won’t generate a new archive if it doesn’t need to.
Either way, if you build a kernel like this you can boot it without supplying an external initrd image, and it’ll still finish its boot by running your init program out of rootfs. This is packaging method #2, if you’d like to try it now.
2)指定给内核⼀个⽂件或者⽂件夹
If CONFIG_INITRAMFS_SOURCE points to a directory, the kernel will archive it up for you. This is a very easy way to create an initramfs archive, and is method #3.
Interestingly, the kernel build doesn’t use the standard cpio command to create initramfs archives. You don’t even need to have any cpio tools installed on your build system. Instead the kernel build (i
n usr/Makefile) generates a text file describing the directory with the “gen_initramfs_list.sh”, and then feeds that de to a program called “gen_init_cpio”(built from C source in the kernel’s usr directory), which create the cpio archive. This looks something like the following:
s/gen_initramfs_list.sh $CONFIG_INITRAMFS_SOURCE > usr/initramfs_list
usr/gen_init_cpio usr/initramfs_list > usr/initramfs_data.cpio
gzip usr/initramfs_data.cpio
To package up our hello world program, you could simply copy it into its own directory, name it “init”, point
CONFIG_INITRAMFS_SOURCE at that directory, and rebuild the kernel. The resulting kernel should end its boot by printing “hello world”. And if you need to tweak the contents of that directory, rebuilding the kernel will re-package the contents of that directory if anything has changed.
The downside of this method is that it if your initramfs has device nodes, or cares about file ownership and permissions, you need to be able to create those things in a directory for it to copy. This is hard to do if you haven’t got root access, or are using a cross-compile environment like cygwi
n. That’s where the fourth and final method comes in.
3)使⽤configuration⽂件initramfs_list来告诉内核initramfs在哪⾥
This is the most flexible method. The kernel’s gen_initramfs_list.sh creates a text deion file listing the contents of initramfs, and gen_init_cpio uses this file to produce an archive. This file is a standard text file, easily editable, containing one line per file. Each line starts with a keyword indicating what type of entry it describes.
The config file to create our “hello world” initramfs only needs a single line:
file /init usr/hello 500 0 0
This takes the file “hello” and packages it so it shows up as /init in rootfs, with permissions 500, with uid and gid 0. It expects to find the source file “hello” in a “usr” subdirectory under the kernel’s build directory. (If you’re building the kernel in a different directory than the source directory, this path would be relative to the build directory, not the source directory.)
To try it yourself, copy “hello” into usr in the kernel’s build directory, copy the above configuration line to its own file, use “make menuconfig” to point CONFIG_INITRAMFS_SOURCE to that file, run the k
ernel build, and test boot the new kernel. Alternately, you can put the “hello” file in its own directory and use “s/gen_initramfs_list.sh dirname” to create a configuration file (where dirname is the path to your directory, from the kernel’s build directory). For large projects, you may want to generate a starting configuration with the , and then customize it with any text editor.
This configuration file can also specify device nodes (with the “nod” keyword), directories (“dir”), symbolic links (“slink”), named FIFO pipes (“pipe”), and unix domain sockets (“sock”). Full documentation on this file’s format is available by running “usr/gen_init_cpio” (with no arguments) after a kernel build.
A more complicated example containing device nodes and symlinks could look like this:
dir /dev 755 0 0

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。