我们平时在单片机上编写程序,生成固件的时候,一般都会在固件里面添加版本号信息,方便下一次修改或者进行OTA升级。目前,写入固件版本号信息主要有以下两种方法,一种是通过使用关键字attribute,在编译阶段,把固件版本号固定到flash中(注意:attribute是GNU编译器特有的,我在MDK上能实现,但是在微软的Visual stido发现是实现不了的)。另外一种方法是,通过在开机后,读写flash,对flash解锁,随后写入相关信息,然后再给flash上锁,完成一次固件信息的固化。但是,今天我想要介绍第三种方法,即是通过在STM32启动文件中,直接在保留的字节里加入版本号信息,方便又快捷,下面我们详细来对比以下四种方法的优劣之处。
首先,第一种方法,通过关键字attribute在固件中添加相关信息,其具体语法如下,const uint32_t fw_version __attribute((at(address))=XX;
由于信息是固化在flash里面,其属于非易失性的ROM,而且我们在程序运行过程中也不会去修改固件标记,所以加上const关键字,fw_version代表你要写进去的版本号,address代表你要写入的地址位置,例如,我要在0x08000010处写入我的版本号位1.0.0,我们可以这样写,const uint32_t fw_version __attribute((at(address))=100;下面我们可以看看运行效果, 我的芯片是STM32-F103ZET6,首先,先看看不烧录固件地址的程序运行状况,方便对比。
此时程序运行正常,在右侧的flash中,0x08000000是栈顶指针,0x08000004是PC指针,后面依次是启动文件中规定的中断地址,程序没有问题。flash中的中断地址比函数的地址+1是由于Thumb-2指令集要求跳转地址的最低有效位(LSB)必须是1,所以其记录的地址会加1,程序烧录后,能正常运行。此时,我们尝试使用attribute将相关固件信息,固定在指定地址,我们再打开map表和查看flash上的信息,发现上面的地址信息会乱掉,程序启动后,进入死机状态。
观察FLASH上的数据我们可以发现,0x08000010处成功地写入100,证明我们的写入是有效的,但是,我们细心观察可以发现,在0x8000000和0x08000000处已经不是程序PC指针跳转地址和栈顶地址了,程序无法正常启动,观察左侧LR寄存器,0xFFFFFFF9进入硬件错误,证明通过这种写法去写入固件信息是不可行的。我们再看看map表,固件信息前面都是一些C的库函数,而中断信息已经不见了,反而放在0x08001000的后面,
由于,在地址的前面处加入,会导致flash上烧录的信息错乱,所以我们只能尝试在flash的后面加入,以致于其不影响其他函数烧录到相应的flash地址,我尝试在0x0800FFF0处写入,
const uint32_t fw_version __attribute((at(0x0800FFF0)))=100; 此时,程序能正常运行,也成功在固定地址写入,但是我们观察flash可以发现,写入地址前面的数据也受到影响,单片机上的flash在没写入的状态下,一律初始化为FF,现在,都是00,证明使用这个方法,会导致一个扇区的浪费,所以使用attribute+固定地址并不是一个好的方法。
我们还可以使用另外一种方法写入固件信息,STM32单片机内部是norflash,所以我们可以按字节去寻址写入,非常方便,而不必像NANDFLASH那样去,先进行页擦除,再进行页写入,整个过程比较耗费时间,对系统的实时性有一定的影响。但是由于FLASH具有只能从1写到0的特性,而不能从1写道0,所以,我们写入相应的区域地址时,依然需要校验当前写入区域是否全为0XFF,如果不是全为0XFF,我们依然需要进行扇区擦除,为之后的写入作准备。下面我们进行简单的演示。
首先,先进行Flash上面的数据读取,函数实现如下:
将两个8位数据凑成一个半字的数据,然后写入,其中STM32_PAGE_SIZE宏定义为2048,这个根据芯片的FLASH每一页大小确定
最后发现,FLASH上成功写入数据。
但是使用该办法,无法利用中断区域没有利用的保留字空间,因为程序运行中,会发生各种中断,如果对中断向量表的区域进行擦写,这是不被允许的,所以虽然使用Flash擦写的方式能够精确到每一个地址,不用造成扇区浪费,但还是无法利用中断向量表的保留字空间,而且,此办法,需要编写相应的FLASH擦写程序,也比较费时费力。
于是,我们可以利用分散加载技术,通过将我们定义的段固定到指定的地址,指定其大小,随后我们再往段里面写入的相应的数据,我们即可在编译的时候,将数据擦写到固定的位置。这样的方法,比通过程序进行写Flash要简单,也是通过编译时的烧录算法,直接将变量烧写到指定位置,但是由于单片机一开始就已经指定了头部存放的中断变量,跳转地址等信息,没办法在这些空间加入新的段,所以也只能在没有使用过的空间指定地址,但是分段后有一个好处,就是分段后再使用attribute与直接使用attribute相比,可以精准地在相关地址写入变量信息,而不必对扇区的其他位置造成影响,节约了空间。我们可以看一看运行效果。
但是以上三种方法依然无法实现我们使用启动文件保留字空间的办法,最后,我请教了硬汉嵌入式论坛的大佬,通过总结发现,中断向量表中,其位置存储的就是函数的执行地址,也即是函数名直接被翻译为地址存储了,那我们不用通过翻译,直接把数字写进去,可不可以呢,我们实践了一下,最后惊喜的发现,是可以的。下面我们可以看一下效果。
踏破铁鞋无觅处,得来全不费功夫。虽然大费周章,最后发现仅需要通过在启动文件直接添加相关变量即可,但是这次的研究过程也带来不少收获,希望各位大佬共同研究。