前言
我們可以使用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章專門講了加密,在理論部分進行了對應修改。