本人原创,昨晚写的,本帖目前仅在我人人网主页和这里发过,转载请注明出处。
请勿纠结于**中的“单片机”,我用的是TMS320F2812,对我来说也还是单片机。想写给大众的,所以尽量不使用比较专的词汇,并且尽可能多地忽略不必要的细节。
前言:
何为bootloader?确切的定义不去争论,在我这里是指单片机上电经过一系列折腾(boot)后跳转到一段小程序,这段小程序在几秒钟内监测用户是否需要装载(load)新程序,若不需要则跳转到Flash中储存的上次装载好的用户程序。这段小程序就被我称为bootloader。为何bootloader(或者说为何不用仿真器下载程序)?因为受不了蹩脚的仿真器插拔顺序;因为鄙视厂商用仿真器来弱化很多很基础概念;因为刚回公司时没有我私人的仿真器,懒得频繁跟人借;因为从单片机入门开始养成的匆忙实现各种helloworld后就埋头干活,以至于做了很多事情却很多础概念都不清楚;因为想一劳永逸,以后无论什么片子什么仿真器都对我的调试环境没有影响;因为……咳咳……这里是前言,头重脚轻了……打住……再啰嗦最后一句,为何自刷自?升级无止境,神马打死不改版再改遭雷劈版都是浮云;体验完全(除了新板第一次烧写以外)脱离仿真器的快感;“只有一次机会”的刺激感(以及没抓住机会的挫败感……)。
正文:
1、bootloader工作方式
上电经过一系列折腾后PC指针跳到FLASH区一个固定地址A,所以我把bootloader的入口地址链接到A。进入bootloader后几秒钟内查询用户输入端口,如果没有输入则把PC指针切到约定好的用户FLASH区起始地址B;若查询到用户输入则切换到待命模式。待命模式可以识别如下命令:下载——等待用户逐一传送程序文件至相应的FLASH区或RAM区(bootloader会保护自己所使用的区域,后面将要提到自刷失败的补救也失败就是由于保护做的太多,没空子可钻导致的);运行FLASH区程序——将PC指针切换到约定好的FLASH区地址B;运行RAM区程序——将PC指针切换到约定好的RAM区地址C;查看内存(如果有改写内存的功能在后面出错时就还能补救,可惜没有……后详……)
2、bootloader保护自己所在区域,怎么刷?
把新版bootloader链接到RAM中去,然后当成用户程序用之前的FLASH版bootloader加载进去并运行,再用这个RAM版的bootloader烧写新版的FLASH版bootloader(有点绕……)。其实中间版(RAM版)bootloader也可以链接到用户FLASH区的,可惜没有……后详……
3、怎么刷坏的?
RAM版bootloader对自己所在的RAM区也进行了保护。考虑到这种自刷自的方式“只有一次机会”,一旦刷错了,机器起不来,就不得不再插仿真器烧写了。为了更加“安全”,新版bootloader的RAM版和FLASH版同源,只有链接选项和自我保护的地址不同。这样RAM可以用来探路,如果RAM版的没问题再用RAM版的烧FLASH版的。问题出现在我做FLASH版的自我保护时忘记删掉直接复制粘贴带过来的RAM版的自我保护区(如果我忘记做FLASH版程序的自我保护而仅仅是错误地保护了RAM版程序的区域,就仍然还有救,可惜我没有……后详),然后我的FLASH版程序“保护”了3个重要的地址:在第1段里提到的地址A、B和C。问题来了,这一版FLASH程序可以烧写程序,但有几个区是无法烧写的,虽然你可以说没关系片内片外都还有大量的区域可用,但我当时做死的绝对跳转命令只能跳转到地址B和C,也就是虽然可以烧写用户程序但无法切换到用户程序。一个活的东西就这么死了……
4、“后详”
a.如果bootloader的待命模式当时支持写内存操作,我可以把改版程序链接到不受保护的区域,然后起始地址链接到地址C,bootloader不允许在烧写模式更改C地址的内容没关系,我可以通过写内存命令手动把机器码敲到地址C去。然后就可以从bootloader切到新程序那里了。对了,bootloader烧写时遇到无法烧写的地方就直接退出,其实也没关系,我可以手动从链接好的二进制文件中删掉地址C那部分的内容,把它变成一个没有入口的目标文件,从而保证烧写过程不被终止。
b.在发现新版FLASH版bootloader不能用时,还是可以从它再切换回过渡版RAM版bootloader的,因为我只是按复位键并没有下电。但是切换过去后发现常量区已经被新版FLASH版bootloader运行时冲掉了,所以过渡版仅仅是能启动,无**确运行需要访问常量的部分。如果当初过渡版放到FLASH区并以地址B为入口,就还能切回来。
c.如果新FLASH版没有对自己本身做保护,也就是我可以写地址A和B,但这两个地址本身就被程序使用着,运行中修改他们很可能破坏程序本身。整片把程序链接到这个区域肯定是不行的,但可以碰运气只在A或B两个word的地方放绝对跳转指令的机器码,期待在我切换到新程序之前不会那么点儿背要用到这两个地址的代码,然后像a那样把程序主体放在没有争议的区域,就仍然有希望启动新程序。这里插入个细节:写FLASH肯定不像写RAM那样容易,所以写地址A或B这一步不像a那样容易,而是手写一个目标二进制文件,里面只有一条放在地址A或B的机器码,然后通过下载命令把文件写进去。后来同事提醒说其实不算撞大运,A地址是程序的最最初入口地址,在bootloader已经启动并运行时改变A的内容并不会破坏程序逻辑。还存在一个问题就是FLASH擦除我也做了保护。擦除是从“0”“擦”成“1”,而写其实是从“1”“写”成“0”(有点别扭但不影响对本文的理解)。所以我对地址A的操作只能是把有“1”的某些地方变成“0”,但所有原本为“0”的地方不能变成“1”。这件事也不算难,只要把绝对跳转指令的目标地址选合适就可以了。
后记:
加班时特意把有问题的FLASH版程序只增加了一条写内存指令后用仿真器(一地的挫败感啊……)烧进去,然后通过4.a的步骤确实让程序“重新”活了起来。程序员的强迫症伤不起……
整两句不着边儿的
只有一次机会,搞砸了,再插上仿真器,你调戏(我没发错音……)的已经不是你原来的妹子了……
同事一句“刷成砖头了”形容的很恰当,不玩嵌入式的同学粗略扫下来恐怕也能了解手机刷成砖头是怎样一个过程了 |