前言
我们可以使用BDM设备很方便地烧写单片机程序、查看内存、调试程序。但是反过来想,我辛辛苦苦写的程序,写的算法,别人不也可以使用BDM设备很方便的读出来看么!虽然别人只能看到二进制代码,或者说汇编代码,但是到底经过分析还是能知道你的程序怎么写的,这还得了!!!
幸好,MC9S12(X)提供了加密功能(手册上对应Secure这个单词),BDM是无法读取处于加密状态的单片机的内存的,也就很好的保护了我们的知识成果。
最近我闲着无聊,研究了下这个加密功能,顺便出个小教程分享给大家研究成果,内容基于自己的理解,可能有很多不严谨或者错误的地方,敬请指出。
教程中顺便把之前写的几个小模块串起来演示了一遍。
另外,这里所讲的基于MC9S12XEP100,对于其他衍生产品可能会有一些差别,请自行参照数据手册。
注:此文为本人通过学习MC9S12XEP100数据手册,自行测试而写出,其中所有程序(除了操作系统和工程文件自动生成的部分)完全自己编写,所有图也是自己截取,部分图是从数据手册上截取的。仅供学习交流使用,不得用于商业用途。如需转载请告知本人并注明出处。谢谢!
正文
理论篇
加密的作用
首先要明确的是,没有绝对安全的加密,飞思卡尔的策略只是让无权限用户难以读取FLASH和EEPROM中的内存。
要让你的代码足够安全还需要你写的程序的配合,加密功能是管不了你自己的代码去读取内存往外面报告的。
加密后,S12XE芯片会:
- 保护非易失性内存(Flash,EEPROM)的内容
- 限制NVM命令的执行
- 禁止通过BDM访问内部内存
- 禁止在扩展模式下访问内部Flash/EEPROM
- 禁止CPU和XGATE的调试特性
下图(来自芯片手册第9章)综述了加密对各大功能的影响。
图 1.S12XE的加密对可用特性的影响
直观的看,加密后,√少了好多好多,换句话说,加密对许多功能进行了限制。可以看到BDM那两行在加密之后就只剩下可怜的一个√了,还是限制于只能读取外设寄存器的,所以待会我们成功加密后你会发现程序一烧进单片机,BDM就报错无法通信,内存也读不出来了,不要惊慌,因为就是这样的!!而不管加不加密,都不影响应用程序代码对Flash和EEPROM的访问(第一二行)。
总之,加密后:
普通芯片模式(NS)下
- 完全禁止BDM操作
- Flash和EEPROM命令的执行受限
- 禁止通过DBG模块追踪代码执行
- 禁止调试XGATE代码(断点、单步执行)
特殊芯片模式(SS)下
- 禁用BDM固件命令
- BDM硬件命令受限于寄存器地址
- Flash和EEPROM命令的执行受限。
- 禁止通过DBG模块追踪代码执行
- 禁止调试XGATE代码(断点、单步执行)
扩展模式(NX、ES、EX和ST)下
- 完全禁止BDM操作
- 禁用内部Flash和EEPROM内存
- Flash和EEPROM命令的执行受限
- 禁止通过DBG模块追踪代码执行
- 禁止调试XGATE代码(断点、单步执行)
而NVM命令在加密前后分别有对应的限制,见下图。
图 2.加密对NVM指令的影响
细心的读者肯定发现了图中有两个指令很特别,Erase All Blocks和Unsecure Flash,这两个指令居然只在special模式下可运行,所以虽然我的FTM模块驱动中留了接口,但实际上其实基本是用不到的(注:如果不知道NVM和FTM是什么的话可以把它们当做一个东西,简单来说,FTM模块通过NVM指令来执行各种任务)。
特殊模式指在重置后BDM是激活的
解密方法
有加密总得有解密,不然不连自己都看不到了么。
解密有以下三种方法:
- 后面秘钥访问
通过验证后门秘钥,可以暂时解密,这需要用户应用程序自己设计对外的接口以验证用户提供的秘钥,后面的程序示例了一个简单的接口。 - 重编程加密位
在普通模式下,可以擦除后重编程FLASH配置字段以使芯片(从下次重置后)处于解密状态,但是因为Flash擦除时是一整个扇区擦除的,所以这样会导致0xFE00–0xFFFF (0x7F_FE00–0x7F_FFFF)的内存都被擦除了,里头包含后门秘钥以及如中断向量表等,所以这种方法在普通模式下并不是很推荐。 - 特殊模式下完全擦除内存
在特殊模式下可以通过擦除所有EEPROM和Flash内存来解密芯片。这可以通过BDM来实现(见救救孩子篇),也可以通过(接着BDM时)在应用程序中使用NVM指令Erase All Blocks或Unsecure Flash来实现(注:Unsecure Flash就是通过擦除整个Flash和EEPROM的方式来解密芯片的)。
加密相关寄存器
FLASH配置字段
全局地址 | 本地地址 | 大小(字节) | 描述 |
---|---|---|---|
0x7F_FF00 – 0x7F_FF07 | 0xFF00 – 0xFF07 | 8 | 后门密钥 |
0x7F_FF08 – 0x7F_FF0B | 0xFF08 – 0xFF0B | 4 | 保留 |
0x7F_FF0C | 0xFF0C | 1 | P-Flash 保护字节 |
0x7F_FF0D | 0xFF0D | 1 | EEE 保护字节 |
0x7F_FF0E | 0xFF0E | 1 | Flash 非易失字节 |
0x7F_FF0F | 0xFF0F | 1 | Flash 加密字节 |
表 1. Flash配置字段
MC9S12XEP100的0xFF00 - 0xFF0F是Flash用于存放配置信息的地方。其中与我们这次内容相关的是第一条和最后一条,其他的我们暂时不去管它。
0xFF00 – 0xFF07这8个字节放的就是我们后门用于解密时对照用的密钥,需要我们烧写进去。
后门密钥的这四个word不能有任一是0x0000或0xFFFF。
而Flash加密字节的功能呢,请看下一章。
可能有的小可爱想:
就8个字节呀,那我暴力破解就好了,穷举所有可能的组合,就是时间问题。
这里有两个问题:
1. 验证后门那个命令每次上电后只能调用一次,失败后哪怕你后面输入的是正确的密码都会报失败,想要重试只能下电重置后再试。 如果你实现了一失败就控制下电重置,暴力破解确实是有可能。
2. 但是另一个问题就是,这个命令只能在normal模式下才能运行,也就是说必须有内置的程序为外部提供接口来输入密钥才行,所以你首先得知道接口是什么,然后就是写接口程序的人设计的好不好的问题了。
为了防止这样的暴力破解,写接口时要设置其他保护措施。比如使用非易失性内存来记录失败次数,连续失败多少次就再也不接受密钥这样的保护措施。
Flash加密寄存器(Flash Security Register,FSEC)
图 3.Flash加密寄存器
Flash加密寄存器是只读的。
在重置序列中,它会装载上述Flash配置字段中的Flash加密字节,如果这个过程中发现错误,会导致模块进入加密状态,并且禁止使用后门密钥来解密。
此寄存器的字段描述如下:
字段 | 描述 |
---|---|
7–6 KEYEN[1:0] | 后门密钥加密使能位 —— KEYEN[1:0] 决定了Flash模块是否启用后门密钥,见表3 |
5–2 RNV[5:2} | 预留非易失位 —— RNV位应该保留在擦除状态(1)以备未来使用 |
1–0 SEC[1:0] | Flash加密位 —— SEC[1:0] 决定了MCU的加密状态,见表4。如使用后门密钥解密了Flash模块,SEC则会被设为10 |
表 2. FSEC字段描述
KEYEN[1:0] | 后面密钥访问状态 |
---|---|
00 | 禁止 |
01 | 禁止 |
10 | 启用 |
11 | 禁止 |
表 3.Flash KEYEN状态
SEC[1:0] | 加密状态 |
---|---|
00 | 加密 |
01 | 加密 |
10 | 解密 |
11 | 加密 |
表 4.Flash 加密状态
所以为了设置加密你的MC9S12(X),关键就是改写这些这些寄存器的值,实际就是设置Flash配置字段的值。
发起验证密钥命令
为了发起上述验证密钥的命令,需要了解要怎么和FTM模块的内存控制器进行交互,本篇教程不准备深入这个内容,我已经写好了FTM模块驱动,可以直接拿来用。
https://blog.csdn.net/lin_strong/article/details/79938296
注意,此命令执行期间必须保证程序在RAM中运行,否则程序会跑飞。后面的实践篇会介绍方法。
实践篇
好,接下来我们开始加密我们的设备吧!
这里我使用了UCOS-II RTOS为基础开始我们的项目。
https://blog.csdn.net/lin_strong/article/details/80327520
配置 Flash配置字段
先不想接口的事,我们把设备先加密了看看效果。
之前说过,设置密码就是烧写0xFF00处的那8个字节,为了方便起见,我们就把密码设为ASCII字符串,就“ABCDEFGH”把,正好8个字节。
然后呢,参照下几张表,Flash加密字段应该设为 10111101(二进制),也就是 启用后门密钥,加密状态。
然后怎么让IDE知道在那个地址烧写这几个值呢,这就需要用到CodeWarrior的扩展语法了。
volatile const INT8U BackdoorKey[8] @0xFF00 = {'A','B','C','D','E','F','G','H'}; // key: ABCDEFGH
volatile const INT8U fsec @0xFF0F = 0b10111101; // Enable backdoor key access, secured
就是那个@XXXXX,这个语法用来指定某个变量应该在某个地址。所以这样写我们就指定了0xFF00开始处的8个字节分别为ABCDEFGH,0xFF0F处的值为0b10111101。顺便一提,0b也是CodeWarrior扩展的语法,ANSI C不包含这个语法。
const是因为这是在Flash内存,就是用来放常量的。不写const会不会有什么后果我没试过,感兴趣的可以尝试下。
volatile是告诉编译器,这个变量是真实存在的,不要擅作主张把它优化掉了。但是光写了这个还不是很保险,为了确保这个地址有这几个值,我们还需要打开.prm文件,一般叫做Project.prm,在里头找到ENTRIES这个语法块。这样改:
ENTRIES
BackdoorKey;fsec
END
这样链接器就也知道这几个对象不能被丢掉了,确保他们被烧写进去。
然后编译项目。
连接BDM。
烧写项目。
一切顺利。
哎呀!
图 4. 烧写加密的程序后BDM通信失败
这TM什么鬼。怎么通信不上了!!!
诶,怎么memory里全成?了?
怎么重新烧写也烧不了了!
别急,这就是我们想要的效果呀,因为程序被加密了。所以BDM就无法和MCU通信,就看不到内存里的东西了。
我们来急救下,先往下翻到 救救孩子篇,先通信上再说。
定个通信协议
因为只是个教程,我们就简单定个通信协议,使用SCI进行通信。我们的协议是这样的:
每当收到pw这两个字符后,就把后面收到的8个字符作为密钥来验证。
所以pw为帧头,帧长度为10。
然后就是我写的通用接收机登场啦。
https://blog.csdn.net/lin_strong/article/details/80499831
我们按照模块的要求先分配内存空间以及定义函数。
……
static void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder);
……
#define RXBUF_SIZE 20
static RX_FLAG RxFlag;
static INT8U Header[] = {'p','w'};
static INT8U RxBuffer[RXBUF_SIZE];
static RX_MAC RxMac;
然后相关的初始化语句差不多长这个样子
// 初始化帧头标志串
RX_FLAG_INIT(&RxFlag,Header,2,FLAG_OPTION_HEADER);
// 初始化接收机,flush时调用onFlush
RxMac_Init(&RxMac,&RxFlag,1,2,RxBuffer,RXBUF_SIZE,NULL,NULL,onFlush);
// 一个帧的长度固定为 2(帧头) + 8(数据) = 10
RxMac_SetRxSize(&RxMac,10);
这样我们就完成了这个协议下接收机的设置。
大概解释下:
首先用RX_FLAG_INIT初始化RxFlag这个标志串变量,设置Header的头两个字符pw为帧头。
然后用RxMac_Init初始化了RxMac这个接收机,让他以RxFlag为标志串来判断帧头帧尾,使用RxBuffer作为缓冲区,flush结果通过onFlush来告知。
然后设置接收机的接收区大小为10,这样找到帧头后收到第8个字符就会触发flush,由于协议中没有帧尾,于是得通过缓冲区满的机制使得接收机flush。
之后每次收到数据时只要调用RxMac_FeedData给接收机传递数据,接收机就会把解析的结果通过onFlush函数告诉我们了。
将程序放到RAM中
之前说过,在执行验证密钥的操作中,程序要一直在RAM中跑,这就要求最起码发起内存控制器指令的那段程序得在RAM中,为了实现这一点,需要把这段程序定位到RAM中。具体来说需要做这些事情:
首先添加数据段,打开prm文件,找到
……
SEGMENTS
……
/* non-paged RAM */
RAM = READ_WRITE DATA_NEAR 0x2000 TO 0x3FFF;
/* non-banked FLASH */
ROM_4000 = READ_ONLY DATA_NEAR IBCC_NEAR 0x4000 TO 0x7FFF;
ROM_C000 = READ_ONLY DATA_NEAR IBCC_NEAR 0xC000 TO 0xFEFF;
……
END
PLACEMENT
……
END
改为:
……
SEGMENTS
……
/* non-paged RAM */
RAM = READ_WRITE DATA_NEAR 0x2000 TO 0x3F0F;
/* non-banked FLASH */
ROM_4000 = READ_ONLY DATA_NEAR IBCC_NEAR 0x4000 TO 0x7FFF;
ROM_C000 = READ_ONLY DATA_NEAR IBCC_NEAR 0xC000 TO 0xFE0F;
RAM_CODE_SEG = READ_ONLY DATA_NEAR IBCC_NEAR 0xFE10 TO 0xFEFF RELOCATE_TO 0x3F10;
……
END
PLACEMENT
……
RAM_CODE INTO RAM_CODE_SEG;
……
END
然后,只要在想放到RAM中的函数(的定义和声明)前后加这个语句
#pragma push
#pragma CODE_SEG RAM_CODE
……
(函数)
……
#pragma pop
所有用到这些函数的地方就都知道要到对应的RAM中去调用这个函数了,这里是把函数存在0xFE10到0xFEFF这段地址中,然后重定位到0x3F10开头的RAM中。
因为我的FTM模块中已经写了这个语句了,所以实际上只要修改了prm文件就行。
但是有一点要注意,编译器不会自动帮你把程序拷贝过去,所以需要自己手动拷贝,方法如下:
#define RAM_CODE_RELOCATE 0x3F10
#define __SEG_START_REF(a) __SEG_START_ ## a
#define __SEG_END_REF(a) __SEG_END_ ## a
#define __SEG_SIZE_REF(a) __SEG_SIZE_ ## a
#define __SEG_START_DEF(a) extern char __SEG_START_REF (a) []
#define __SEG_END_DEF(a) extern char __SEG_END_REF (a) []
#define __SEG_SIZE_DEF(a) extern char __SEG_SIZE_REF (a) []
__SEG_START_DEF (RAM_CODE);
__SEG_END_DEF (RAM_CODE);
__SEG_SIZE_DEF (RAM_CODE);
#define CopyCodeToRAM() \
memcpy((unsigned char *)RAM_CODE_RELOCATE,(unsigned char *)__SEG_START_REF(RAM_CODE),\
(unsigned short)__SEG_SIZE_REF(RAM_CODE))
只要调用CopyCodeToRAM()就会完成拷贝工作,需要保证在调用FTM模块的指令之前调用这个函数。
加上FTM模块驱动
现在我们把FTM的驱动添加进去。这用到了我之前写的FTM驱动
https://blog.csdn.net/lin_strong/article/details/79938296
给各位省了不少事吧!
把这个驱动添加到程序中。
修改必要的设置(主要是头文件中的FCLK_DIV参数)后调用
FTM_Init();
初始化模块。
然后验证密钥只需要使用提供的接口
FTM_VerifyBackdoorAccessKey
就行了,很方便吧!
所以onFlush中的代码主体就长这个样
void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder){
INT8U err;
// 只处理找到帧头的情况
if(state.headerFound != 1)
return;
// 将缓冲区中第三个字节开始的8个字节作为KEY来验证
err = FTM_VerifyBackdoorAccessKey((FTM_KEYGEN *)&pBuf[2]);
switch(err){
case FTM_ERR_NONE:
// 成功
break;
case FTM_ERR_ACCERR:
// 出错
break;
default:
// 出错
break;
}
}
加上信道
基本都准备好了,剩下就是收发数据的事情了,为了方便,这里也直接使用我之前写的SCI模块了,当然可以改为其他方式收发数据。
https://blog.csdn.net/lin_strong/article/details/73662524
具体使用参照博客,不是重点,这里就不细说了。
注意设置SCI的中断向量。
最终程序
好了,现在所有部分都拼接好了,因为有各种现成的模块,实现业务时就好像搭积木一样简单。
我们再加上一些提示信息以及设置一些杂项,这个小程序就算完成了。最终的main.c文件如下:
/*
*********************************************************************************************************
* uC/OS-II
* The Real-Time Kernel
* Framework
*
* By : Lin Shijun
* Note: This is a framework for uCos-ii project with only S12CPU, none float, banked memory model.
* You can use this framework with same modification as the start point of your project.
* I've removed the os_probe module,since I thought it useless in most case.
* This framework is adapted from the official release.
*********************************************************************************************************
*/
#include "includes.h"
#include "FTM.h"
#include "SCI_def.h"
#include "RxMac.h"
/*
*********************************************************************************************************
* REALLOCATE
*********************************************************************************************************
*/
#define RAM_CODE_RELOCATE 0x3F10
#define __SEG_START_REF(a) __SEG_START_ ## a
#define __SEG_END_REF(a) __SEG_END_ ## a
#define __SEG_SIZE_REF(a) __SEG_SIZE_ ## a
#define __SEG_START_DEF(a) extern char __SEG_START_REF (a) []
#define __SEG_END_DEF(a) extern char __SEG_END_REF (a) []
#define __SEG_SIZE_DEF(a) extern char __SEG_SIZE_REF (a) []
__SEG_START_DEF (RAM_CODE);
__SEG_END_DEF (RAM_CODE);
__SEG_SIZE_DEF (RAM_CODE);
#define CopyCodeToRAM() \
memcpy((unsigned char *)RAM_CODE_RELOCATE,(unsigned char *)__SEG_START_REF(RAM_CODE),\
(unsigned short)__SEG_SIZE_REF(RAM_CODE))
/*
*********************************************************************************************************
* FLASH PROTECTION FIELD
*********************************************************************************************************
*/
volatile const INT8U BackdoorKey[8] @0xFF00 = {'A','B','C','D','E','F','G','H'}; // key: ABCDEFGH
volatile const INT8U fsec @0xFF0F = 0b10111101; // Enable backdoor key access, secured
/*
FSEC Field Descriptions
Field Description
7–6 KEYEN[1:0] Backdoor Key Security Enable Bits—The KEYEN[1:0] bits define the enabling of backdoor
key access to the Flash module as shown in Table 29-11. (10 -> ENABLE)
5–2 RNV[5:2} Reserved Nonvolatile Bits — The RNV bits should remain in the erased state for future
enhancements.
1–0 SEC[1:0] Flash Security Bits — The SEC[1:0] bits define the security state of the MCU as shown
in Table 29-12. If the Flash module is unsecured using backdoor key access, the SEC
bits are forced to 10. (10 -> UNSECURED)
//*/
/*
*********************************************************************************************************
* STACK SPACE DECLARATION
*********************************************************************************************************
*/
static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];
/*
*********************************************************************************************************
* TASK FUNCTION DECLARATION
*********************************************************************************************************
*/
static void AppTaskStart(void *p_arg);
/*
*********************************************************************************************************
* LOCAL FUNCTION DECLARE
*********************************************************************************************************
*/
static void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder);
/* 协议:
pw为帧头,后面的8个字符代表输入的key
*/
// 接收机
#define RXBUF_SIZE 20
static RX_FLAG RxFlag;
static INT8U Header[] = {'p','w'};
static INT8U RxBuffer[RXBUF_SIZE];
static RX_MAC RxMac;
/*
*********************************************************************************************************
* MAIN FUNCTION
*********************************************************************************************************
*/
void main(void) {
INT8U err;
BSP_IntDisAll(); /* Disable ALL interrupts to the interrupt controller */
OSInit(); /* Initialize uC/OS-II */
err = OSTaskCreate(AppTaskStart,
NULL,
(OS_STK *)&AppTaskStartStk[APP_TASK_START_STK_SIZE - 1],
APP_TASK_START_PRIO);
OSStart();
}
static void AppTaskStart (void *p_arg){
INT8U c,err;
(void)p_arg; /* Prevent compiler warning */
BSP_Init();
// 初始化FTM
FTM_Init();
CopyCodeToRAM();
// 初始化SCI
SCI_Init(SCI0);
SCI_EnableTrans(SCI0);
SCI_EnableRxInt(SCI0);
SCI_EnableRecv(SCI0);
SCI_BufferInit();
// 等待FTM初始化完成
if(FCLKDIV != (FCLK_DIV | 0x80)) //Check to make sure value is written.
while(1);
// 清零标志位(如果初始化中发现错误会导致标志位置位,导致第一次验证误以为出错)
FSTAT = (FTM_FSTAT_MASK_FPVIOL | FTM_FSTAT_MASK_ACCERR | FTM_FSTAT_MASK_MGSTAT0 | FTM_FSTAT_MASK_MGSTAT1 );
FERSTAT = 0xFF;
// 初始化帧头标志串
RX_FLAG_INIT(&RxFlag,Header,2,FLAG_OPTION_HEADER);
// 初始化接收机,flush时调用onFlush
RxMac_Init(&RxMac,&RxFlag,1,2,RxBuffer,RXBUF_SIZE,NULL,NULL,onFlush);
// 一个帧的长度固定为 2(帧头) + 8(帧尾) = 10
RxMac_SetRxSize(&RxMac,10);
SCI_PutCharsB(SCI0,"Hello world.",12,0);
while (DEF_TRUE){
// 不断从SCI0获取数据并喂给接收机
c = SCI_GetCharB(SCI0,0,&err);
RxMac_FeedData(&RxMac,c);
}
}
void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder){
INT8U err;
// 只处理找到帧头的情况
if(state.headerFound != 1)
return;
// 为了防止验证过程中进入中断导致程序跑飞,这里暂时禁止中断
DisableInterrupts;
// 将缓冲区中第三个字节开始的8个字节作为KEY来验证
err = FTM_VerifyBackdoorAccessKey((FTM_KEYGEN *)&pBuf[2]);
EnableInterrupts;
switch(err){
case FTM_ERR_NONE:
SCI_PutCharsB(SCI0,"\r\nsuccess.",10,0);
break;
case FTM_ERR_ACCERR:
SCI_PutCharsB(SCI0,"\r\nfail.\r\nwrong key or have mismatched.",38,0);
break;
default:
SCI_PutCharsB(SCI0,"\r\nfail.\r\nunknown.",17,0);
break;
}
}
杂项说明
首先来看看 onFlush中的这段
// 为了防止验证过程中进入中断导致程序跑飞,这里暂时禁止中断
DisableInterrupts;
// 将缓冲区中第三个字节开始的8个字节作为KEY来验证
err = FTM_VerifyBackdoorAccessKey((FTM_KEYGEN *)&pBuf[2]);
EnableInterrupts;
之前说过,在验证操作中,程序只能在RAM中跑,因为我们已经把FTM模块的核心程序移到RAM中了,所以正常不会出问题。但是因为操作系统使用了ECT模块不断发生中断来计时和切换程序,所以有一定的概率会在验证过程中正好发生中断,而中断程序是在flash中的,这样单片机就会跑飞。为了防止这种情况出现,我在调用接口前直接禁用了中断。
另一种解决思路是确保所有可能会在执行期间运行的程序全部在RAM中,这样可能就很复杂了,但是确是是可行的。
// 等待FTM初始化完成
if(FCLKDIV != (FCLK_DIV | 0x80)) //Check to make sure value is written.
while(1);
// 清零标志位(如果初始化中发现错误会导致标志位置位,导致第一次验证误以为出错)
FSTAT = (FTM_FSTAT_MASK_FPVIOL | FTM_FSTAT_MASK_ACCERR | FTM_FSTAT_MASK_MGSTAT0 | FTM_FSTAT_MASK_MGSTAT1 );
FERSTAT = 0xFF;
上面这段呢,则是因为在重置序列中如果探测到错误,比如Flash中的数据校验错误,会导致标志位置位,这样安装FTM模块驱动当前的逻辑,会导致第一次调用时误判。所以清零标志位来避免这种情况。
测试篇
好了,程序准备好了,我们连上232,打开串口调试助手准备开始测试吧。
将程序烧进单片机(喂,这个不用教的吧)
图 5. 程序烧写
搓搓小手。
烧完程序后出现了图 3里的那个提示,BDM连不上单片机了,嗯,就应该是这样的。这时候不管你怎么努力都无法和单片机通信了。
这时我们把HIWAVE(调试器)窗口关掉,要不然会影响单片机工作。然后重置单片机。
在窗口调试助手我们看到了欢迎信息,经典的“hello world”!
图 6.Hello world
然后我们先来测试测试密码错误的情况。按照协议给一个错误密钥。
图 7.密钥错误
可以看到提示失败,然后再来输入正确的密钥试一下!
图 8.只能验证密钥一次
好吧,第一次失败了再试几次都没用的,我们重置下单片机,然后再试正确的密钥。
图 9.密钥正确
成功!
让我们打开HIWAVE来验证下。
直接打开HIWAVE,选择正确的BDM。自动连接上了。
图 10.解密后HIWAVE直接连接
然后就和平常使用调试器时一样了,如果想要把项目加载进来的话,先暂停程序,然后:
TBDML HCS12-> Load-> 找到自己的工程->Load Symbol
图 11.加载工程文件符号
然后整个界面就和你平时使用调试器时一模一样的了。
当然,要是你重启的话就又连不上了。
救救孩子篇
在我们测试加密功能时,一不小心就会把单片机永久加密上,然后又没有留后门,然后就爆炸了,通信不上了!
这时候怎么办呢???
什么,你说换一片就好了? 土豪,我们能做朋友么!
对于我们大部分节俭的人(穷b),肯定是要挽救一下原来的芯片的。具体操作如下:
- 该接的接好了,打开HIWAVE,选择对应的BDM,这时候肯定会报错无法通信。
图12. BDM无法与MCU通信
莫慌,点Cancel取消 - 接下来我们开始用BDM解密,TBDML HCS12->Select HC12 MCU,然后选aotodectect那项,确定。
图13. 选择MCU
图14. 选择MCU - TBDML HCS12->Select Derivative选择自己的设备,由于默认就是XEP100,所以其实可以不看这一项,其他衍生版本的话可能要设置下
图15. 选择衍生型号
图16. 选择衍生型号 - 接下来可能要设置下命令文件,如果你是从工程中启动的HIWAVE的话这步可能就能省略了。TBDML HCS12->Command Files。找到Unsecure选项卡,浏览,随便找个工程文件,里头的cmd文件夹中找到那个TBDMLXXXXXXunsecuredXXXXX.cmd文件,确定。
图17. 设置命令文件
图18. 设置命令文件 - 然后就是最激动人心的一步了!TBDML HCS12->Unsecure。一开始会让你按照晶振频率进行个设置,然后不管报什么错就一直点“是”就行了,
图19. 解密 - 登登当挡!解密完成! 然后单片机就和新的一样了,又可以为所欲为了。
图20. 解密成功
如果遇到遇到什么问题了就参照步骤再鼓捣鼓捣,基本两三次内就能成功了,实在搞不定底下留言。
伸手要源码篇
肯定有人想直接要源码,好吧,我直接把工程传到资源上去,一个工程中包含了这么多个模块,实在是太超值了!
https://download.csdn.net/download/lin_strong/10538064
后记
由于能力限制,文中肯定有很多错误或不严谨之处,请各位大佬指出。
更新历史
2018/07/26 突然发现数据手册第9章专门讲了加密,在理论部分进行了对应修改。