Boot | 手把手教你写BootLoader

在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。这段说人话意思就是:工程中不会将JTAG等烧录口引出但是一定有一个对外的通信口我们升级程序时就可以利用可操作的通信接口把要升级的程序文件发送到芯片再擦写到对应地址的内存中,这样就实现了程序升级。

熊猫大概去年这个时候写的BootLoader工程(以下简称boot),最近正好被朋友问起发现好多细节已经记不清,就赶快写一篇关于boot专题加深记忆也把思路提供给大家。

 

概  述

 

针对飞思卡尔的MC9S12XET256芯片(熟悉吗?BMS最老的基础平台MCU)开发了下位机部分,上位机部分软件。上位机软件用的VS平台,语言是C#,下位机用的CW5.1,下载工具是USBCAN-2E-U。

目前一共有三种思路启动boot:

  1. 特殊硬件管脚:复位上电之后通过判断PP0管脚输入电平状态判断进入APP还是boot,PP0默认上拉高电平进入APP如果要进入boot则需要复位时按下按键,这种方式用作调试可以但是实际工程中我们根本没有条件操作按键,所以不推荐;

     

    Boot | 手把手教你写BootLoader_第1张图片

     

  2. 上电进入延时退出:每次上下电复位都先进去boot模式然后等待升级通信指令,如果收到升级指令则开始升级,反之延时耗完进入APP。这种方式适合可以方便操作上下电并且对上电时序响应没有严格要求的系统;

     

  3. EEPROM控制字:每次上电先判断控制字。比如控制字为0x11表示进入boot模式,0x22表示进入APP模式。比如APP正常运行时这时候你要升级发送升级指令下位机执行:收到升级指令——擦写控制字为0x11——复位——读取控制字为0x11——进入boot模式——开始升级——升级完成——擦写控制字为0x22——复位——读取控制字为0x22——进入APP模式——正常运行新程序。这种方式比较灵活免去人工操作上下电,上电开机不用延时,适用的系统比较多而且很重要的一点只要不升级成功控制字永远不会被擦写,一直保持在boot模式直到升级成功为止,这就避免了升级过程出现不可抗力因素导致升级失败的状况。

     

 

内存划分

 

最难最关键的一步,你要编写这个芯片的boot就必须理解这个芯片的内存,让我们一起参照datasheet学习;

Boot | 手把手教你写BootLoader_第2张图片

如图这个芯片资源有

P-Flash部分:16*16k=256K 一共16页每页16k(Number of 16K pages addressable via PPAGE register)所以一共265K;      

 

RAM部分:4*4k=16k 一共4页每页4K(Number of 4K pages addressing the RAM. RAM can also be mapped to 0x4000 - 0x7FFF)一共16K;

D-Flash和buffer-RAM部:D-Flash空间=32*1K=32K,buffer-RAM=4*1K=4K说明:D-Flash一个页窗就是 1K, 总共划分了 32 页, 所以有 32*1K 内存空间大小, 对应的全局地址就是0x10-0000 -- 0x10-7FFF;buffer RAM 一个页窗就是 1K, 总共划分了 4 页, 所以有 4*1K 内存空间大小,对应的全局地址就是 0x13-F000 -- 0x13-FFFF;这里 buffer RAM 和 D-Flash 总共 32 + 4K的空间模拟用作 EEPROM, 因为这个芯片没有 EEPROM, 所以用 buffer RAM 和 D-Flash 模拟用作 EEPROM, 简称 EEE, 同时 buffer RAM 也简称 EEE-RAM。叫什么不重要我们只要知道他们被用作EEPROM就可以了。

 

好了内存我们搞清楚了好需要关注一个东西Part ID,请看图

也就是说part ID是用来区分S12X系列芯片的的唯一ID,因为每个芯片内存不同所有我们要找到自己用的芯片的part ID

 

Boot | 手把手教你写BootLoader_第3张图片

代码里定义成宏定义

 

Boot | 手把手教你写BootLoader_第4张图片

 

还有一个要关注的参数如下图所示 ROM 空间划分了 2个块, 分别是 B1S,B0每个128K加起来256K这也就是芯片命名xet256的意义。 这个值可以从 PARTID 对应的寄存器 PARTIDH and PARTIDL (addresses 0x001A and 0x001B)读出来, 这个在P-Flash 擦除和编程的时候要用到。

Boot | 手把手教你写BootLoader_第5张图片

然后我们去看256K芯片内存细分

 

Boot | 手把手教你写BootLoader_第6张图片

0x7E_0000-0x7F_FFFF的128K对应P-Flask的B0;

0x78_0000-0x79_FFFF的128K对应P-Flash的B1;

接下来我们要关注Flash的配置命令寄存器它位于地址的最后

Boot | 手把手教你写BootLoader_第7张图片

Boot | 手把手教你写BootLoader_第8张图片

const unsigned char flash_array[] @0xFF08 = {0xFF,0xFF,0xFF,0xFF,0xCC,0xFF,0xFF,0xFE}; 对于xet256芯片我们这样来配置,依次看下几个寄存器代表的含义;

0xFF08-0xFF0B - reserved
0xFF0C - P-Flash Protection byte = 0xCC (Bootloader at 0xF000-0xFFFF is protected)   

Boot | 手把手教你写BootLoader_第9张图片

0xCC对应寄存器值为

Boot | 手把手教你写BootLoader_第10张图片

所以保护类型为高保护

Boot | 手把手教你写BootLoader_第11张图片

而保护区域为

Boot | 手把手教你写BootLoader_第12张图片

 

其他寄存器就按照默认配置

0xFF0D - EEE Protection byte = 0xFF (default state, protection is disabled)

0xFF0E - Flash Nonvolatile byte = 0xFF (default state, watchdog is disabled)
0xFF0F - Flash Security byte = 0xFE (default unsecured state)

关于P-Flash操作的命令可以在这里查到

Boot | 手把手教你写BootLoader_第13张图片

关于D-Flash操作命令可以在这里查到

Boot | 手把手教你写BootLoader_第14张图片

 

先明确一个概念在编译环境中我们操作的都是逻辑地址而真实芯片上是全局地址要访问真实的全局地址我们就要把逻辑地址转换成全局地址,s12x系列为16位芯片所以他的访问范围只有0x0000-0xFFFF的64K空间,那么如何访问更大的内存地址呢?比如我们现在用的256K空间甚至更大呢?对!没错给它加更多位 而这个芯片加位的方式是通过三个分页寄存器EPAGE,RPAGE,PPAGE来是实现的,也就是说分页寄存器+原有16位寻址的方式组成更多位来访问更大空间。好了把datasheet翻到206页我们来看图

Boot | 手把手教你写BootLoader_第15张图片

Boot | 手把手教你写BootLoader_第16张图片

 

用EEPROM 来举例说明。 先划分 0x0800 - 0x0bff 这 1K 的空间来作为活动分页页窗,然后在 PRM 文件中把全局 EEPROM 内存空间 256K 的地址划分成 256 页, 比如: EEPROM_00= READ_ONLY DATA_FAR IBCC_FAR 0x000800 TO 0x000BFF; 第一页对应的全局地址就是0x10_0000 - 0x10_BFF, 此时 EPAGE = 0X00。

 

按所含的信息的不同.prm 文件有六个组成部分构成, 这里仅讨论和内存空间映射关系紧密的三个部分,其他的不做讨论

 

SEGMENTS … END

定义和划分芯片所有可用的内存资源, 包括程序空间和数据空间。 一般我们将程序空间定义成ROM, 把数据空间定义成 RAM, 但这些名字都不是系统保留的关键词, 可以由用户随意修改.用户也可以把内存空间按地址和属性随意分割成大小不同的块,每块可以自由命名。 例如同样RAM, 可以使用不同的属性,使其有复位后变量清零和不清零之分。关于内存划分的具体方法在后面详解。

PLACEMENT … END

将指派源程序中所定义的各种段如数据段DATA_SEG、CONST_SEG和代码段CODE_SEG 被具体放置到哪一个内存块中。它是将源程序中的定义描述和实际物理内存挂钩的桥梁。

STACKSIZE

定义系统堆栈长度, 其后给出的长度字节数可以根据实际应用需要进行修改.堆栈的实际定位取决于 RAM 内存的划分和使用情况。默认的情况下,堆栈放在 RAM 区域的起始部分。当然堆栈的定义不只有这种方式, 还可以使用 STACKTOP 关键字。

由 SEGMENTS 开始到 END 为止, 中间可以添加任意多行内存划分的定义, 每一行用分号结尾。 定义行的语法型式为:

[块名] = [属性 1] [属性 2] , …  [属性 n] [起始地址] TO [结束地址];

其中“块名” 的定义和 C 语言变量定义相同, 是以英文字母开头的一个字符串, 用户可以自己任意定义块名。“属性” 用户是不能自己定义的, 因为属性名指定了上面所说的“块名” 所对应的不同的

内存类型和访问方式, 而不同物理内存的类型和访问方式是一定的。对于“属性 1” , Codewarrior 5.0 中可以有三种不同的类型, 对于只读的 Flash-ROM 区属性一定是 READ_ONLY,对于可读写的 RAM 区属性可以是 READ_WRITE,也可以是 NO_INIT。它们两者的关键区别是 ANSI-C 的初始化代码会把定位在 READ_WRITE 块中的所有全局和静态变量自动清零, 而 NO_INIT 块中的变量将不会被自动清零。 当然只是复位时不清零,掉电时还是清零的后面的属性可能是DATA_FAR”、“DATA_NEAR”、“IBCC_FAR”、“IBCC_NEAR”四种类型。 其中,“DATA_FAR”和“DATA_NEAR” 相对应,当内存区域包含变量或者是常量时(通常是 RAM、 Flash 和EEPROM)必须指明上面两种属性中的一种, 由于涉及到内存的分页, 可以这样理解:“DATA_FAR” 属性指定的内存块为可以保存数据的非固定页, 而“DATA_NEAR” 属性指定的内存块为可以保存数据的固定页; 同理“IBCC_FAR” 和“IBCC_NEAR” 相对应, 当内存区域包含代码时(Flash 和 EEPROM) 必须指明上面两种属性中的一种, ”IBCC_FAR” 属性指定的内存块为可以保存代码的非固定页, 而“IBCC_NEAR” 属性指定的内存块为可以保存代码的固定页。

举个例子

例 1 RAM = READ_WRITE DATA_NEAR 0x2000 TO 0x3FFF;

上面这句话的意思是: 分配 0x2000-0x3FFF 的区域的块名为“RAM” (当然可以定义别的名称) 这一区域的物理内存的性质为 RAM, 属性应该为“READ_WRITE”并且这一区域中的两页都为固定页,为“DATA_NEAR”

好了终于把内存问题交代清楚了最后我们来划分内存,在CW环境中打开.Prm文件如图划分好

Boot | 手把手教你写BootLoader_第17张图片

 

CAN通信驱动和协议

 

前面说过boot要选一种对外的通信方式,这里我们用CAN通信,当然你也可以选择其他通信方式它只要能把数据发下去就行。

CAN初始化

 

Boot | 手把手教你写BootLoader_第18张图片

CAN发送函数

 

 

Boot | 手把手教你写BootLoader_第19张图片

 

CAN接收函数

Boot | 手把手教你写BootLoader_第20张图片

我用的协议很简单一帧用来握手,然后5个数据帧发完S19一行数据(为什么是5帧?后面解释)

Boot | 手把手教你写BootLoader_第21张图片

 

启动部分汇编代码

内存划分好了,通信链路也通了就剩下关于启动部分的代码编写了,这个是第二种方式每次上电都进入boot

 

Boot | 手把手教你写BootLoader_第22张图片

升级完成执行APP

Boot | 手把手教你写BootLoader_第23张图片

 

S19文件的理解

 

S-record每行最大是78个字节,156个字符

S-record format:

type(类型):2个字符。用来描述记录的类型 (S0,S1,S2,S3,S5,S7,S8,S9)。

count(计数):2个字符。用来组成和说明了一个16进制的值,显示了在记录中剩余成对字符的计数。

address(地址):4或6或8个字节。用来组成和说明了一个16进制的值,显示了数据应该装载的地址, 这部分的长度取决于载入地址的字节数。2个字节的地址占用4个字符,3个字节的地址占用6个字符,4个字节的地址占用8个字符。

data(数据):0—64字符。用来组成和说明一个代表了内存载入数据或者描述信息的16进制的值。

checksum(校验和):2个字符。这些字符当被配对并换算成16进制数据的时候形成了一个最低有效字符节,该字符节用来表达作为补充数据,地址和数据库的字符对所代表的(字节的)补码的byte总和。即计数值、地址场和数据场的若干字符以两个字符为一对,将它们相加求和,和的溢  出部分不计,只保留最低两位字符NN,checksum =0xFF-0xNN。

S0 Record:记录类型是“S0” (0x5330)。地址场没有被用,用零置位(0x0000)。数据场中的信息被划分为以下四个子域(此行表示程序的开始,不需烧入memory):

name(名称):20个字符,用来编码单元名称

ver(版本):2个字符,用来编码版本号

rev(修订版本):2个字符,用来编码修订版本号

description(描述):0-36个字符,用来编码文本注释。

S1 Record:记录类型是“S1” (0x5331)。地址场由2个字节地址来说明。数据场由可载入的数据组成。

S2 Record:记录类型是“S2” (0x5332)。地址场由3个字节地址来说明。数据场由可载入的数据组成。

S3 Record:记录类型是“S3” (0x5333)。地址场由4个字节地址来说明。数据场由可载入的数据组成。

S5 Record:记录类型是“S5” (0x5335)。地址场由2字节的值说明,包含了先前传输的S1、S2、S3记录的计数。没有数据场。

S7 Record:记录类型是“S7” (0x5337)。地址场由4字节的地址说明,包含了开始执行地址。没有数据场。此行表示程序的结束,不需烧入memory。

S8 Record:记录类型是“S8” (0x5338)。地址场由3字节的地址说明,包含了开始执行地址。没有数据场。此行表示程序的结束,不需烧入memory。

S9 Record:记录类型是“S9” (0x5339)。地址场由2字节的地址说明,包含了开始执行地址。没有数据场。此行表示程序的结束,不需烧入memory。

这里介绍一个官方转换工具,选择好自己用的芯片输入选Banked输出选择Linear添加输入文件点击转换就可以了

Boot | 手把手教你写BootLoader_第24张图片

 

转换完是这样的

Boot | 手把手教你写BootLoader_第25张图片

所有数据都为S2类型 每行固定76个字符 地址为6个字符,其中数据64个字符每两个字符组成一个16进制数,也就是有32个16进制数据最后有个校验和,S9为结束帧;这就是用5帧发完一行的原因,当然这个转换可以集成在上位机中完成。

下图为main函数

Boot | 手把手教你写BootLoader_第26张图片

 

上位机编写

上位机调用现成dll文件,这里不再讨论

Boot | 手把手教你写BootLoader_第27张图片

好了大功告成!

由于本公众号还没有留言功能欢迎关注后在“撩我”菜单栏添加熊猫个人微信讨论学习一起进步。

生命不息、学习不止,加油!

 

Boot | 手把手教你写BootLoader_第28张图片

Boot | 手把手教你写BootLoader_第29张图片

 

 

你可能感兴趣的:(BMS,硬件;bms,;,bootloader,汽车)