章節說明
STM32 IAP固件升級實驗分為以下的章節(加粗的字體是本章節的內容):
一、Flash和RAM的區域划分、工程建立、程序分散加載、程序燒寫
二、Stm32 bootloader、application、firmware 程序的分析和編寫
三、使用DMA收發串口的不定長數據
四、通信協議的設計
五、STM32 IAP程序的設計
六、上位機的程序的編寫
一、前言
為了能使上位機和下位機能進行可靠的通信,所以需要設計一個相對可靠的協議。當然設計的協議也不會太復雜,但是該有的功能還是得有。數據頭部,控制指令,數據長度,數據,校驗碼。這種類型通訊協議用於串口相對還是比較簡單便捷的,當然也可以設計的很復雜例如:加入版本控制,物理設備號等等。在本文中就不涉及太復雜的東西了,就設計一個相對完整又相對簡單的協議。話不多說,接下來進入正題吧。
二、通訊格式
設計的通訊格式為問答式(即一問一答的方式),分為控制指令,和應答指令。
1、控制指令格式
- 首部 : 所有的數據包都要加包頭:0xA5A5
- 父指令: 根據需求來設計
- 子指令: 根據需求來設計
- 長度 : 數據長度字節,用來指定該幀中攜帶數據的長度(單位是長度)
- 數據 : 該幀中寫帶的數據內容
- 校驗碼: 校驗碼使用校驗和的方式;校驗和是子指令到校驗和之間的所有字節之和,超出 2 字節的進位忽略
2、應答指令格式
- 首部 : 所有的數據包都要加包頭:0xA5A5
- 父指令: 根據需求來設計
- 子指令: 根據需求來設計
- 長度 : 數據長度字節,用來指定該幀中攜帶數據的長度(單位是長度)
- 數據 : 該幀中寫帶的數據內容
- 校驗碼: 校驗碼使用校驗和的方式;校驗和是子指令到校驗和之間的所有字節之和,超出 2 字節的進位忽略
三、控制指令設計
1、查詢指令/復位指令
2、程序更新控制指令
3、數據傳送指令
四、應答指令設計
1、查詢應答指令
2、狀態指令
五、幀管理程序設計
1、幀管理數據結構
/* 回調函數的類型定義如下 */
/* typedef uint16_t (*FRAME_VALIDATE)(u8 *buf, u16 len); */
typedef struct
{
u8 *buffer;//指向一塊buff,用於存儲一幀的數據
u16 head; //數據存儲在頭部的位置
u16 count; //接收到的數據
u16 BUFFERLENGTH; //buffer的最大長度
u16 MIN_FRAME_LEN; //最小幀
FRAME_VALIDATE ValidateFrame; //這個是一個回調函數的指針,主要是處理幀的回掉函數
} FrameBufferStr;
2、結構體初始化函數
注意修改堆(heap)的大小
void Frame_Buffer_Init(FrameBufferStr *frame, u16 len, FRAME_VALIDATE validate_cb)
{
/* 但是要值得注意啟動文件的堆內存的大小(默認是 512Byte)。*/
/* 如果使用大於等於512Byte的大小,則需要修改堆內存大小,否則會報 Fault 異常 */
/* 由於本實驗需要的緩存需要3Kb,所以已經在啟動文件里面修改為4KB */
frame->buffer = (u8*)malloc(len);
frame->BUFFERLENGTH = len;
frame->ValidateFrame = validate_cb;
Frame_Buffer_Clean(frame);
}
3、清buff函數
void Frame_Buffer_Clean(FrameBufferStr *frame)
{
memset(frame->buffer, 0, frame->BUFFERLENGTH);
frame->head = 0;
frame->count = 0;
}
4、幀管理程序
void Append_Frame_Buffer(FrameBufferStr *frame, u8 *input, u16 length)
{
u16 i = 0;
//s16 head=0;
u16 mlen=0;
// 如果上傳數據長度比緩存總長度還要長
if (length > frame->BUFFERLENGTH)
{
// 此處可添加錯誤代碼
return;
}
/* 需要將后面的數據copy到前面 */
if((u16)frame->count + length > frame->BUFFERLENGTH)
{
/* frame->head 記錄frame->buffer的頭部,frame->count記錄接收到數據的尾部*/
memmove(frame->buffer, frame->buffer+frame->head, frame->count - frame->head);
/* 重新計算接收到數據的尾部 */
frame->count = frame->count - frame->head;
/* 頭部指向為0 */
frame->head = 0;
/* 將后面的數據清零 */
memset(frame->buffer + frame->count, 0, frame->BUFFERLENGTH - frame->count);
/* 后面需要添加的數據超出了fream的范圍 */
if((frame->count + length) > frame->BUFFERLENGTH)
{
// index out of buffer range
/* 將數據清零 */
frame->count=0;
frame->head=0;
return;
}
}
if(length != 0)
{
/* 復制后面添加進來的數據 */
memcpy(frame->buffer+frame->count, input, length);
frame->count+=length;
}
i = 0;
while(frame->head + i < frame->count)
{
/* 調用回調函數,解析接收數據,一個一個往下迭代,直到找到需要校驗的起始頭部 */
mlen = frame->ValidateFrame(frame->buffer + frame->head + i, frame->count - frame->head - i);
if(mlen > 0)
{
/* 返回的長度比buffer的總長度長,也將frame初始化 */
if(mlen > frame->BUFFERLENGTH)
{
frame->head = 0;
frame->count = 0;
memset(frame->buffer, 0, frame->BUFFERLENGTH);
break;
}
/* 處理完這幀數據了,將數據清零 */
frame->head = frame->head + i + mlen;
/* 如果計算完的后的frame->head后比 frame->count大,說明接收的數據有偏差*/
if(frame->head > frame->count)
{
frame->count = frame->head;
}
i = 0;
if(frame->head == frame->count)
{
/* 有偏差則需要重新將frame->buffer初始化 */
frame->head = 0;
frame->count = 0;
memset(frame->buffer, 0, frame->BUFFERLENGTH);
break;
}
}
else
{
i++;
}
}
}
六、在串口使用
1、DMA的回調函數
在IAP實驗三里面有講到,使用DMA接收串口數據時,首先需要給
USART2_Service
函數指針賦值讓DMA接收函數回調。下面的USART2_DMA_Callback
就是賦值給USART2_Service
的函數。而這個函數也比較簡單,就是將數據存放到幀管理程序中。
void USART2_DMA_Callback(u8 *buff, u16 len)
{
/* 里面調用幀管理程序,用於接收一幀數據 */
Append_Frame_Buffer(&USART2_Frame,buff,len);
}
2、USART2 的 Frame 管理程序初始化
/* 定義一個 FrameBufferStr 變量*/
FrameBufferStr USART2_Frame;
/* frame_cb 是幀管理程序的回調函數,由用戶定義,在IAP實驗中,主要用於處理協議的 */
void USART2_Frame_Init(FRAME_VALIDATE frame_cb)
{
/* 初始化USART2_Frame */
Frame_Buffer_Init(&USART2_Frame,USART2_FRAME_BUFFER_LEN,frame_cb);
/* 給 USART2_Service 賦值*/
USART2_Service = USART2_DMA_Callback;
}
3、幀管理程序的回調函數
/* 這個函數就是處理協議的回調函數了,具體怎么處理就留給用戶來設計了 */
/* 在本實驗中,這里只是做一個接收復位指令,然后執行軟件復位 */
uint16_t Protocol_Handle(u8 *buf, u16 len)
{
/* 判斷頭是否正確 */
if(buf[0] == 0xa5 && buf[1] == 0xa5){
/* 等待接收夠一幀數據 */
/* 這個 len 是包括頭部的,不是協議上的len*/
if(len >= 8){
/* 判斷校驗位 */
uint16_t check = (uint16_t)(buf[len-2]<<8 | buf[len-1]);
if(check == 1){
/* 判斷是否是軟復位指令 */
if(buf[2] == 0 && buf[3] == 1){
/* 執行軟復位 */
SoftReset();
}
}
return len;
}
}
return 0;
}
七、總結&實驗現象
1、使用方法
通過上面步驟的層層封裝,只要使用下面的步驟就可以正常使用:
- 定義一個幀管理的回調函數
- 調用
USART2_Frame_Init
初始化- 使能串口
- 在回調函數中處理協議
2、本實驗的現象
當通過
USART2
發送復位指令之后(下圖中左邊的調試助手)就可以看STM32
復位,並且打印復位信息(右邊的調試住手)。
這個實驗是在application
環境下執行的。
3、project
最后將快要完成IAP實驗的工程送給大家--->點我project!!!
說明:由於上面的通訊協議還沒實驗過,有可能還會更改,但是更改的一般不會很多。