Xmodem 協議封裝,用於字型檔編碼下載,軟體升級

NO IMAGE

使用Xmodem有一段時間了,使用起來移植效能不夠,通過這次徹底拋離了底層通訊部分,可以用在任何通訊介面上面了,跟底層的通訊已經無關了,使用了大量的回撥,回撥主要完成通訊的收發,以及資料儲存等功能,我目前主要使用在STM32 IAP升級(寫入到內部flash),app升級(寫入到外部flash W25Q128),字型檔以及各種編碼下載(寫入到外部flash W25Q128)。

//資料包格式比較簡單

//	Xmodem 包格式
//	Byte1 				Byte2 			Byte3 				Byte4~131 		Byte132~133
//	Start Of Hearder 	Packet Number 	~(Packet Number) 	Packet Data 	16-Bit CRC
//1K-Xmodem 包格式
//	Byte1 				Byte2 			Byte3 				Byte4~1027 	Byte1028~1029
//	Start Of Hearder 	Packet Number 	~(Packet Number) 	Packet Data 	16-Bit CRC

//c檔案

/*************************************************************************************************************
 * 檔名:            Xmodem.c
 * 功能:            Xmodem協議實現
 * 作者:            [email protected]
 * 建立時間:        2014-08-19
 * 最後修改時間:    2017-09-05
 * 詳細:            使用串列埠實現Xmodem協議應用層
                    2017-03-23:修改採用回撥以及控制代碼方式,可移植性更強
                    2017-04-04:增加傳送延時
                    2017-09-05:傳送NAK與結束增加延時,防止傳送過快,並且修改如果通訊超時則傳送NAK,大大提高通訊可靠性
                    2017-09-06:修復第一包資料丟失問題,增加資料包id重複檢查,大大提高資料的可靠性,防止重複的資料包
*************************************************************************************************************/
#include "system.h"
#include "usart.h"
#include "main.h"
#include "Xmodem.h"
#if SYS_WDG_EN_
#include "wdg.h"    
#endif    
//除錯開關
#define XMODEM_DBUG    0
#if XMODEM_DBUG
    #include "system.h"
    #define xmodem_debug(format,...)    uart_printf(format,##__VA_ARGS__)
#else
    #define xmodem_debug(format,...)    /\
/
#endif    //XMODEM_DBUG
//    Xmodem 包格式
//    Byte1                 Byte2             Byte3                 Byte4~131         Byte132~133
//    Start Of Hearder     Packet Number     ~(Packet Number)     Packet Data     16-Bit CRC
//1K-Xmodem 包格式
//    Byte1                 Byte2             Byte3                 Byte4~1027     Byte1028~1029
//    Start Of Hearder     Packet Number     ~(Packet Number)     Packet Data     16-Bit CRC
/*************************************************************************************************************************
* 函式            :    bool XMODE_Init(XMODE_HANDLE *pHandle,
                        bool (* pSendData)(u8 *pDataBuff, u16 DataLen), 
                        int (* pReadData)(u8 **pDataBuff, u8 ByteTimeOut, u16 TimeOut, u16 *pReceiveDelay), 
                        void (*pClearRxData)(void),
                        bool (*pReceivePacket)(u8 *pPackData,u16 PackSize,u32 RecDataSize),
                        bool (*pTransEnd)(bool isTransOK, u32 RecDataSize))
* 功能            :    初始化XMODE
* 引數            :    pHandle:控制代碼;
                    pSendData:傳送回撥函式(pDataBuff:傳送資料緩衝區,DataLen:傳送資料長度)
                    pReadData:接收資料回撥函式,會等待直到資料被寫入到接收緩衝區(pDataBuff:接收資料緩衝區,ByteTimeOut:等待的位元組超時時間,單位ms,TimeOut:資料包超時時間,單位ms),pReceiveDelay:返回接收延時,單位ms
                    pClearRxData:清除接收資料緩衝區回撥函式
                    pReceivePacket:收到一包資料後的回撥函式,(返回false會退出通訊)用於應用層對資料進行儲存(pPackData:接收到的資料包,PackSize:包大小;RecDataSize:已經接收的資料包大小,不含當前包的資料)
                    pTransEnd:傳輸結束時回撥函式,(返回false會退出通訊)用於傳輸結束的處理(isTransOK:TRUE,傳輸正常完成,FALSE:傳輸錯誤結束;RecDataSize:總共收到的資料大小)
* 返回            :    TRUE:初始化成;FALSE:初始化錯誤
* 依賴            :    通訊介面
* 作者            :    [email protected]
* 時間            :    2013-05-08
* 最後修改時間     :     2017-03-23
* 說明            :     XMODE通訊協議通訊介面初始化,預設超時時間為等待啟動30秒,資料包超時2秒
;                                //清除接收資料緩衝區
*************************************************************************************************************************/
bool XMODE_Init(XMODE_HANDLE *pHandle,bool (* pSendData)(u8 *pDataBuff, u16 DataLen), int (* pReadData)(u8 **pDataBuff, u8 ByteTimeOut, u16 TimeOut, u16 *pReceiveDelay), 
    void (*pClearRxData)(void),bool (*pReceivePacket)(u8 *pPackData,u16 PackSize,u32 RecDataSize),bool (*pTransEnd)(bool isTransOK, u32 RecDataSize))
{
    if(pHandle == NULL) return FALSE;                    //錯誤,無效的指標
    pHandle->RecDataSize = 0;                            //接收到的資料大小
    pHandle->RecPackCnt = 0;                            //接收到的資料包計數
    pHandle->pSendData = pSendData;                        //傳送回撥指標,    傳送資料回撥函式
    pHandle->pReadData = pReadData;                        //讀取資料回撥指標,>0返回接收到的資料長度,否則失敗
    pHandle->pClearRxData = pClearRxData;                //清除接收資料緩衝區
    pHandle->pReceivePacket = pReceivePacket;            //收到資料包回撥指標
    pHandle->pTransEnd = pTransEnd;                        //傳輸結束時回撥(可能是出錯結束)
    pHandle->WaitStartTimeOutSer = 30;                    //等待啟動傳輸超時時間
    pHandle->PackTimeOutSer = 2;                        //資料包超時時間
    pHandle->pXMODEM_128Pack = NULL;                    //128B資料包指標
    pHandle->pXMODEM_1KPack = NULL;                        //1KB資料包指標
    pHandle->TransRetry = 10;                            //失敗重試次數,預設10次
    pHandle->TxByteTimeUs = 0;                            //傳送延時預設為0
    
    return TRUE;
}
/*************************************************************************************************************************
* 函式            :    bool XMODE_SetTimeOut(XMODE_HANDLE *pHandle, u16 WaitStartTimeOutSer, u16 PackTimeOutSer, u8 TransRetry,u8 TxByteTimeUs)
* 功能            :    設定XMODE超時時間
* 引數            :    pHandle:控制代碼;WaitStartTimeOutSer:等待啟動超時時間,單位秒鐘;PackTimeOutSer:資料包超時時間,單位秒鐘;TransRetry:出錯重試次數,1-255次;TxByteTimeUs:傳送位元組延時
* 返回            :    TRUE:初始化成;FALSE:初始化錯誤
* 依賴            :    通訊介面
* 作者            :    [email protected]
* 時間            :    2013-05-08
* 最後修改時間     :     2017-03-23
* 說明            :     設定超時時間
*************************************************************************************************************************/
bool XMODE_SetTimeOut(XMODE_HANDLE *pHandle, u16 WaitStartTimeOutSer, u16 PackTimeOutSer, u8 TransRetry,u8 TxByteTimeUs)
{
    if(pHandle == NULL) return FALSE;                                    //錯誤,無效的指標
    pHandle->WaitStartTimeOutSer = WaitStartTimeOutSer;                    //等待啟動傳輸超時時間
    if(pHandle->WaitStartTimeOutSer < 1) pHandle->WaitStartTimeOutSer = 1;
    pHandle->PackTimeOutSer = PackTimeOutSer;                            //資料包超時時間
    if(pHandle->PackTimeOutSer < 1) pHandle->PackTimeOutSer = 1;
    pHandle->TransRetry = TransRetry;                                    //出錯重試次數
    if(pHandle->TransRetry < 1) pHandle->TransRetry = 1;
    pHandle->TxByteTimeUs = TxByteTimeUs;                                //傳送位元組延時,用於RS485介面,傳送後需要進行延時
    
    return TRUE;
}
/*************************************************************************************************************************
* 函式            :    int XMODEM_Start(XMODE_HANDLE *pHandle, u8 **pRxBuff)
* 功能            :    傳送啟動請求
* 引數            :    pHandle:控制代碼;pRxBuff:接收緩衝區(存放第一包資料)
* 返回            :    <=0:響應超時;其它:接收的資料長度
* 依賴            :    通訊介面
* 作者            :    [email protected]
* 時間            :    2013-05-08
* 最後修改時間     :     2017-03-23
* 說明            :     用於通訊資料校驗
                    2017-09-06:增加指標,用於返回第一包資料
*************************************************************************************************************************/
int XMODEM_Start(XMODE_HANDLE *pHandle, u8 **pRxBuff)
{
    u32 TimeOut = pHandle->WaitStartTimeOutSer*10;            //轉換為100m單位
    int len;
    
    if(pHandle == NULL) return FALSE;
    pHandle->DataBuff[0] = X_CRC_MODE;                        //採用CRC模式的校驗請求頭
    while(TimeOut --)
    {
        pHandle->pSendData(pHandle->DataBuff,1);            //傳送請求訊號
        if(pHandle->TxByteTimeUs > 0) XMODEM_DelayMS((10*pHandle->TxByteTimeUs)/1000 1);    //傳送延時
        pHandle->pClearRxData();                            //清除接收
        len = pHandle->pReadData(pRxBuff,10,100,NULL);        //接收資料
        if(len > 0)                    //等待接收
        {
            pHandle->RecDataSize = 0;                        //接收到的資料大小清零
            pHandle->RecPackCnt = 0;                        //接收到的資料包計數清零
            return len;
        }                
            
#if SYS_WDG_EN_
        IWDG_Feed();                                        //喂狗
#endif            
    }
    return -1;
}
//傳送ACK
__inline void XMODEM_SendACK(XMODE_HANDLE *pHandle)
{
    pHandle->DataBuff[0] = (u8)X_ACK;
    pHandle->pSendData(pHandle->DataBuff,1);            //傳送請求訊號
    if(pHandle->TxByteTimeUs > 0) XMODEM_DelayMS((10*pHandle->TxByteTimeUs)/1000 1);    //傳送延時
}
//傳送NAK
__inline void XMODEM_SendNAK(XMODE_HANDLE *pHandle)
{
    XMODEM_DelayMS(20);
    pHandle->DataBuff[0] = (u8)X_NAK;
    pHandle->pSendData(pHandle->DataBuff,1);            //傳送請求訊號
    if(pHandle->TxByteTimeUs > 0) XMODEM_DelayMS((10*pHandle->TxByteTimeUs)/1000 1);    //傳送延時
}
//取消傳輸
__inline void XMODEM_CancelTran(XMODE_HANDLE *pHandle)
{
    XMODEM_DelayMS(20);
    pHandle->DataBuff[0] = (u8)X_CAN;
    pHandle->pSendData(pHandle->DataBuff,1);            //傳送請求訊號
    if(pHandle->TxByteTimeUs > 0) XMODEM_DelayMS((10*pHandle->TxByteTimeUs)/1000 1);    //傳送延時
}
//判斷是否結束
__inline bool XMODEM_isTranEnd(u8 Data, XMODE_HANDLE *pHandle)
{
    if(Data == X_EOT) return TRUE;
    else return FALSE;
}
/*************************************************************************************************************************
* 函式            :    u16 XMODEM_CRC16(u8 *pData, u16 DataLen)
* 功能            :    crc16校驗
* 引數            :    pData:資料緩衝區;DataLen:資料長度
* 返回            :    crc16結果
* 依賴            :    通訊介面
* 作者            :    [email protected]
* 時間            :    2013-05-08
* 最後修改時間     :     2017-03-23
* 說明            :     用於通訊資料校驗,僅用於XMODEM,不可與modbus-rtu協議用的crc16混用(兩者計算結果會不一致)
                    多項式碼0x1021
*************************************************************************************************************************/
u16 XMODEM_CRC16(u8 *pData, u16 DataLen)
{
    u16 crc = 0;
    char i;
    u16 j;
    for(j = 0;j < DataLen;j   )
    {
        crc = crc ^ (int) *pData   << 8;
        i = 8;
        do
        {
            if (crc & 0x8000)
                crc = crc << 1 ^ 0x1021;
            else
                crc = crc << 1;
        } while (--i);
    }
    return (crc);
}
/*************************************************************************************************************************
* 函式            :    u32 XMODEM_DownloadFile(XMODE_HANDLE *pHandle, u32 MaxDataSize)
* 功能            :    使用XMODEM下載檔案
* 引數            :    pHandle:控制代碼;MaxDataSize:限制最大下載資料量
* 返回            :    0:錯誤;其它:接收的資料長度
* 依賴            :    底層
* 作者            :    [email protected]
* 時間            :    2013-05-08
* 最後修改時間     :     2017-03-23
* 說明            :     使用CRC校驗模式,支援128,1K資料包
*************************************************************************************************************************/
u32 XMODEM_DownloadFile(XMODE_HANDLE *pHandle, u32 MaxDataSize)
{
    u16 crc16;
    u16 temp;
    u16 retry = 0;
    int len;
    bool isStart = FALSE;
    u8 *pRxBuff;
    u8 LastPackCnt = 0;                        //用於記錄上一次包序號,每次包序號不能重複
    
    len = XMODEM_Start(pHandle, &pRxBuff);    //等待開始傳輸
    if(len <= 0)
    {
        return 0;
    }
    isStart = TRUE;                            //開始傳輸
    while(1)
    {
        if(isStart != TRUE)                    //非第一包資料才開始接收,因為第一包資料已經在等待開始時接收到了
        {
            pHandle->pClearRxData();                                                    //清除接收緩衝區
            len = pHandle->pReadData(&pRxBuff, 2,pHandle->PackTimeOutSer*1000,NULL);    //接收資料
        }
        isStart = FALSE;                //第一次開始傳輸狀態無效
        if(len <= 0) 
        {
            retry   ;
            XMODEM_SendNAK(pHandle);    //傳送重試
        }
        else
        {
            pHandle->pXMODEM_128Pack = (XMODEM_128B_PACK *)pRxBuff;                    //128B資料包指標
            pHandle->pXMODEM_1KPack = (XMODEM_1KB_PACK *)pRxBuff;                    //1KB資料包指標
            switch(pHandle->pXMODEM_128Pack->X_Start)
            {
                case X_SOH:    //128
                {
                    if(len < 128) 
                    {
                        XMODEM_SendNAK(pHandle); //傳送NAK 
                        retry  ;
                    }
                    else
                    {
                        crc16 = XMODEM_CRC16(pHandle->pXMODEM_128Pack->X_PackData, 128);
                        temp = pHandle->pXMODEM_128Pack->X_CRC16H;
                        temp <<= 8;
                        temp  = pHandle->pXMODEM_128Pack->X_CRC16L;
                        if(crc16 != temp)            //CRC校驗錯誤,重傳
                        {
                            XMODEM_SendNAK(pHandle); //傳送NAK 
                            retry  ;
                        }
                        else
                        {
                            if(LastPackCnt!=pHandle->pXMODEM_128Pack->X_PackNum)     //包序號不一樣
                            {
                                LastPackCnt = pHandle->pXMODEM_128Pack->X_PackNum;    //記錄上一次的包序號
                                if(pHandle->pReceivePacket(pHandle->pXMODEM_128Pack->X_PackData, 128, pHandle->RecDataSize)==FALSE)    //收到資料包,呼叫回撥
                                {
                                    //使用者返回退出下載
                                    if(pHandle->pTransEnd != NULL)    //判斷回撥是否有效
                                    {
                                        pHandle->pTransEnd(FALSE, pHandle->RecDataSize);    //傳輸完成,呼叫回撥,有錯誤
                                    }
                                    
                                    XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                    XMODEM_DelayMS(10);
                                    XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                    XMODEM_DelayMS(500);
                                    xmodem_debug("使用者取消下載!\r\n");
                                    return 0;
                                }
                                
                                pHandle->RecDataSize  = 128;                    //接收到的資料大小增加
                                pHandle->RecPackCnt   ;                            //接收到的資料包計數增加
                                if(pHandle->RecDataSize > MaxDataSize)
                                {
                                    if(pHandle->pTransEnd != NULL)    //判斷回撥是否有效
                                    {
                                        pHandle->pTransEnd(FALSE, pHandle->RecDataSize);    //傳輸完成,呼叫回撥,有錯誤
                                    }
                                    
                                    XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                    XMODEM_DelayMS(10);
                                    XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                    XMODEM_DelayMS(500);
                                    xmodem_debug("檔案下載失敗,大小超出範圍(%dB)!\r\n", MaxDataSize);
                                    return 0;
                                }
                            }
                            else    //故障,重複的資料包
                            {
                                XMODEM_DelayMS(10);
                            }
                            XMODEM_SendACK(pHandle);                        //傳送ACK響應
                            retry = 0;
                        }
                    }    
                }break;
                case X_STX:    //1k
                {
                    if(len < 1024) 
                    {
                        XMODEM_SendNAK(pHandle); //傳送NAK 
                        retry  ;
                    }
                    else
                    {
                        crc16 = XMODEM_CRC16(pHandle->pXMODEM_1KPack->X_PackData, 1024);
                        temp = pHandle->pXMODEM_1KPack->X_CRC16H;
                        temp <<= 8;
                        temp  = pHandle->pXMODEM_1KPack->X_CRC16L;
                        if(crc16 != temp)                                    //CRC校驗錯誤,重傳
                        {
                            XMODEM_SendNAK(pHandle);                        //傳送NAK 
                            retry  ;
                        }
                        else
                        {
                            if(LastPackCnt!=pHandle->pXMODEM_128Pack->X_PackNum)     //包序號不一樣
                            {
                                LastPackCnt = pHandle->pXMODEM_128Pack->X_PackNum;    //記錄上一次的包序號
                                
                                if(pHandle->pReceivePacket != NULL)    //判斷回撥是否有效
                                {
                                    if(pHandle->pReceivePacket(pHandle->pXMODEM_1KPack->X_PackData, 1024, pHandle->RecDataSize)==FALSE)    //收到資料包,呼叫回撥
                                    {
                                        //使用者返回退出下載
                                        if(pHandle->pTransEnd != NULL)    //判斷回撥是否有效
                                        {
                                            pHandle->pTransEnd(FALSE, pHandle->RecDataSize);    //傳輸完成,呼叫回撥,有錯誤
                                        }
                                        
                                        XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                        XMODEM_DelayMS(10);
                                        XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                        XMODEM_DelayMS(500);
                                        xmodem_debug("使用者取消下載!\r\n");
                                        return 0;
                                    }
                                }
                                
                                pHandle->RecDataSize  = 1024;                    //接收到的資料大小增加
                                pHandle->RecPackCnt   ;                            //接收到的資料包計數增加
                                
                                if(pHandle->RecDataSize > MaxDataSize)
                                {
                                    if(pHandle->pTransEnd != NULL)    //判斷回撥是否有效
                                    {
                                        pHandle->pTransEnd(FALSE, pHandle->RecDataSize);    //傳輸完成,呼叫回撥,有錯誤
                                    }
                            
                                    XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                    XMODEM_DelayMS(10);
                                    XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                                    XMODEM_DelayMS(500);
                                    xmodem_debug("檔案下載失敗,程式碼超出範圍(%dB)!\r\n", MaxDataSize);
                                    return 0;
                                }
                            }
                            else    //故障,重複的資料包
                            {
                                XMODEM_DelayMS(10);
                            }
                            XMODEM_SendACK(pHandle);                        //傳送ACK響應
                            retry = 0;
                        }
                    }    
                }break;
                case X_EOT:    //傳輸結束,最後一包
                {
                    if(pHandle->pTransEnd != NULL)    //判斷回撥是否有效
                    {
                        if(pHandle->pTransEnd(TRUE, pHandle->RecDataSize) == FALSE)    //傳輸完成,呼叫回撥,有錯誤則退出
                        {
                            XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                            XMODEM_DelayMS(10);
                            XMODEM_CancelTran(pHandle);                    //傳送結束傳輸
                            XMODEM_DelayMS(500);
                            xmodem_debug("使用者取消下載!\r\n");
                            return 0;
                        }
                    }
                    XMODEM_SendACK(pHandle);
                    retry = 0;
                    XMODEM_DelayMS(10);
                    XMODEM_SendACK(pHandle);
                    XMODEM_DelayMS(500);
                    xmodem_debug("檔案下載成功!\r\n");
                    return pHandle->RecDataSize;
                }
                default:XMODEM_SendNAK(pHandle); retry  ;break;
            }
        }
        
        if(retry > pHandle->TransRetry)     //重傳過多
        {
            if(pHandle->pTransEnd != NULL)    //判斷回撥是否有效
            {
                pHandle->pTransEnd(FALSE, pHandle->RecDataSize);    //傳輸完成,呼叫回撥,有錯誤
            }
                        
            XMODEM_CancelTran(pHandle);                            //取消傳輸
            XMODEM_DelayMS(10);
            XMODEM_CancelTran(pHandle);                            //取消傳輸
            XMODEM_DelayMS(500);
            xmodem_debug("下載失敗,重試次數過多!\r\n");
            return 0;
        }
#if SYS_WDG_EN_
        IWDG_Feed();                                        //喂狗
#endif
    }
}

//.h檔案

/*************************************************************************************************************
* 檔名:			Xmodem.h
* 功能:			Xmodem協議實現
* 作者:			[email protected]
* 建立時間:		2014-08-19
* 最後修改時間:	2014-08-19
* 詳細:			使用串列埠實現Xmodem協議應用層
*************************************************************************************************************/
#ifndef _X_MODEM_H_
#define _X_MODEM_H_
#include "system.h"
#include "USART.h"
#ifdef _UCOS_II_	//支援ucos作業系統,使用系統延時
#define XMODEM_DelayMS(x)				OSTimeDlyHMSM(0,0,0,x)						//延時ms,最大延時999ms
#else
#define XMODEM_DelayMS(x)				Delay_MS(x)						
#endif //_UCOS_II_
//XMODEM 相關定義說明
#define X_SOH		0x01		// Xmodem資料頭
#define X_STX		0x02		// 1K-Xmodem資料頭
#define X_EOT		0x04		// 傳送結束
#define X_ACK		0x06		// 認可響應
#define X_NAK		0x15		// 不認可響應
#define X_CAN		0x18		// 撤銷傳送
#define X_EOF		0x1A		// 填充資料包
//128B資料包格式
typedef struct
{
u8	X_Start;
u8	X_PackNum;
u8	X_bPackNum;
u8	X_PackData[128];
u8	X_CRC16H;
u8	X_CRC16L;
}XMODEM_128B_PACK;
//1024B資料包格式
typedef struct
{
u8	X_Start;
u8	X_PackNum;
u8	X_bPackNum;
u8	X_PackData[1024];
u8	X_CRC16H;
u8	X_CRC16L;
}XMODEM_1KB_PACK;
typedef struct
{
u32 RecDataSize;										//接收到的資料大小
u32 RecPackCnt;											//接收到的資料包計數
bool (* pSendData)(u8 *pDataBuff, u16 DataLen);			//傳送回撥指標,	傳送資料回撥函式
int (* pReadData)(u8 **pDataBuff, u8 ByteTimeOut, u16 TimeOut, u16 *pReceiveDelay);	//讀取資料回撥指標,>0返回接收到的資料長度,否則失敗
bool (*pReceivePacket)(u8 *pPackData,u16 PackSize,u32 RecDataSize);	//收到資料包回撥指標,返回false會退出傳輸
bool (*pTransEnd)(bool isTransOK, u32 RecDataSize);		//傳輸結束回撥指標,返回false會退出傳輸
void (*pClearRxData)(void);								//清除接收資料緩衝區
XMODEM_128B_PACK *pXMODEM_128Pack;						//128bit資料包格式
XMODEM_1KB_PACK *pXMODEM_1KPack;						//1K資料包格式
u16 WaitStartTimeOutSer;								//等待啟動傳輸超時時間
u16 PackTimeOutSer;										//資料包超時時間
u8 TxByteTimeUs;										//傳送位元組延時,微秒,用於RS485通訊延時
u8 DataBuff[2];											//分配的臨時緩衝區
u8 TransRetry;											//失敗重試次數,預設10次
}XMODE_HANDLE;
//啟動傳輸校驗模式
typedef enum
{
X_CRC_MODE = 'C',		//傳輸使用CRC16校驗模式
X_ACC_MODE = X_NAK,		//傳輸使用累加校驗模式
}XMODEM_START_MODE;
//資料包大小
typedef enum
{
X_PACK128B = X_SOH,		//128B資料包
X_PACK1kB = X_STX,		//1KB資料包
}XMODEM_PACK_MODE;
//XMODEM 通訊初始化
bool XMODE_Init(XMODE_HANDLE *pHandle, bool (* pSendData)(u8 *pDataBuff, u16 DataLen), int (* pReadData)(u8 **pDataBuff, u8 ByteTimeOut, u16 TimeOut, u16 *pReceiveDelay), 
void (*pClearRxData)(void),bool (*pReceivePacket)(u8 *pPackData,u16 PackSize,u32 RecDataSize),bool (*pTransEnd)(bool isTransOK, u32 RecDataSize));
//XMODEM 超時設定
bool XMODE_SetTimeOut(XMODE_HANDLE *pHandle, u16 WaitStartTimeOutSer, u16 PackTimeOutSer, u8 TransRetry,u8 TxByteTimeUs);
//XMODEM 下載資料
u32 XMODEM_DownloadFile(XMODE_HANDLE *pHandle, u32 MaxDataSize);
#endif /*_X_MODEM_H_*/

下面是2個下載的例子,我主要是示意通訊與儲存介面格式,實際使用需要按照自己的平臺進行移植

//例子1:下載程式到STM32內部flash,主要提供資料收發介面,以及儲存介面示意,這些介面需要根據自己的平臺做移植
/*************************************************************************************************************
* 檔名:			UpgradeBIOS.c
* 功能:			升級BIOS相關
* 作者:			[email protected]
* 建立時間:		2017-05-16
* 最後修改時間:	2017-05-16
* 詳細:			使用xmodem直接刷STM32flash,如果失敗了請不要重啟,否則會無法進入系統
*************************************************************************************************************/
#include "system.h"
#include "usart.h"
#include "main.h"
#include "xmodem.h"
#include "UpgradeBIOS.h"
#include "STM32Flash.h"
#include "w25x16.h"
#include "STM32_CRC.h"
#include "rtu.h"
#include "board.h"
#if SYS_WDG_EN_
#include "wdg.h"
#endif
#include <stdlib.h>
#define UP_PROGRAM_STM32_BIOS_ADDR		STM32_FLASH_BASE	//BIOS程式內部flash基址
static u32 UpgradeBiosDataSaveCnt = 0;		//升級應用程式已經儲存的資料大小
//升級檔案介面XMODE控制代碼
XMODE_HANDLE UpgradeBIOSHandle;
//傳送資料介面
bool UpgradeBiosSendData(u8 DataBuff[], u16 DataLen)
{
UARTx_SendData(UART_PRINTF_CH, DataBuff, DataLen);
return TRUE;
}
//接收資料介面
int UpgradeBiosReadData(u8 **pDataBuff,u8 ByteTimeOutMs, u16 TimeOutMs, u16 *pReceiveDelayMs)
{
u32 cnt = 0;
u16 TempTime;
UARTx_ClearRxCnt(UART_PRINTF_CH);	//清除串列埠接收緩衝區,開始結束資料
if(ByteTimeOutMs < 1) ByteTimeOutMs = 1;	//位元組超時時間,2個幀之間的間隔最小時間
TimeOutMs /= ByteTimeOutMs;
TimeOutMs  = 1;
TempTime = TimeOutMs;
while(TimeOutMs --)
{
cnt = UARTx_GetRxCnt(UART_PRINTF_CH);
OSTimeDlyHMSM(0,0,0,ByteTimeOutMs);;
if((cnt > 0) && (cnt == UARTx_GetRxCnt(UART_PRINTF_CH)))
{
if(pReceiveDelayMs!=NULL)	//需要返回延時
{
*pReceiveDelayMs = (TempTime-TimeOutMs)*ByteTimeOutMs;
}
*pDataBuff = SysCommBuff;						//接收緩衝區
return cnt;
}
#if SYS_WDG_EN_
IWDG_Feed();										//喂狗
#endif			
}
return 0;
}
//清除接收緩衝區
void UpgradeBiosClearData(void)
{
UARTx_ClearRxCnt(UART_PRINTF_CH);	//清除串列埠緩衝區
}
//收到資料包回撥-用於寫資料到內部flash
bool UpgradeBiosReceivePacketExitFlash(u8 *pPackData,u16 PackSize,u32 RecDataSize)
{
if((PackSize != 128) && (PackSize!=1024)) return FALSE;										//xmodem只支援128位元組或1024位元組的資料包
//寫入資料到內部flash
STM32FLASH_Write(UP_PROGRAM_STM32_BIOS_ADDR RecDataSize, (u16 *)pPackData, (PackSize 1)/2);
UpgradeBiosDataSaveCnt  = PackSize;
return TRUE;
}
//傳輸結束時回撥(可能是出錯結束)
bool UpgradeBiosTransEndtExitFlash(bool isTransOK, u32 RecDataSize)
{
if(isTransOK==FALSE) return FALSE;	//失敗返回
if(UpgradeBiosDataSaveCnt == RecDataSize) return TRUE;
if(UpgradeBiosDataSaveCnt > RecDataSize) return FALSE;			//儲存的資料不能大於接收的資料
if((RecDataSize-UpgradeBiosDataSaveCnt) > 0) return FALSE;		//不能有未儲存的資料
return FALSE;	//儲存出錯
}
//使用XMODEM下載資料到內部flash
//返回程式大小,如果失敗了返回<=0
int XMODEM_DownloadFileToSTM32Flash(u32 MaxFileSize)
{
XMODE_Init(&UpgradeBIOSHandle, 			//控制代碼
UpgradeBiosSendData, 				//傳送資料回撥函式
UpgradeBiosReadData, 				//讀取資料回撥函式
UpgradeBiosClearData, 				//清除接收資料緩衝回撥函式
UpgradeBiosReceivePacketExitFlash, 	//接收到資料包回撥函式
UpgradeBiosTransEndtExitFlash		//傳輸結束回撥函式
);
UpgradeBiosDataSaveCnt = 0;				//已經儲存的資料大小清零
return XMODEM_DownloadFile(&UpgradeBIOSHandle, MaxFileSize);
}
//使能系統命令列
#if SYS_CMD_EN_
#include "cmd.h"
#include "string.h"
CMD_TYPE const CMD_UP_BIOS	= {"UP BIOS", 0XD3476564, CMD_UpBIOS, "\t\t升級BIOS"};				//升級BIOS程式
//進入升級BIOS模式
void CMD_UpBIOS(char *pStr)
{
cmd_printf("已經進入升級BIOS模式,等待連線,超時10S!\r\n>");
cmd_printf("請在10S內進入Xmodem下載模式!\r\n>");
RTC_DisableInt();									//關閉RTC中斷,防止喚醒後臺任務
OSTaskSuspend(BACK_TASK_Prio);						//掛起後臺任務執行緒
OSTaskSuspend(LED_TASK_Prio);						//掛起LED任務執行緒
OSTaskSuspend(GPRS_TASK_Prio);						//掛起GPRS程序
OSTaskSuspend(COLL_TASK_Prio);						//資料採集時間查詢程序
OSTaskSuspend(OTHER_TASK_Prio);						//OTHER
OSTaskSuspend(MODBUS1_TASK_Prio);					//MODBUS1	
OSTaskSuspend(MODBUS2_TASK_Prio);					//MODBUS2
OSTaskSuspend(KEY_TASK_Prio);						//KEY
OSTimeDlyHMSM(0,0,0,500);							//延時500毫秒
if(XMODEM_DownloadFileToSTM32Flash(100*1024) == 0)//寫入BIOS程式
{
cmd_printf("升級BIOS失敗,重啟後將無法啟動,建議重新升級!\r\n");
}
else
{
cmd_printf("升級BIOS成功!\r\n");
}
CMD_Help(NULL);
OSTaskResume(LED_TASK_Prio);						//恢復掛起LED任務執行緒
OSTaskResume(GPRS_TASK_Prio);						//恢復掛起GPRS程序
OSTaskResume(COLL_TASK_Prio);						//恢復資料採集時間查詢程序
OSTaskResume(OTHER_TASK_Prio);						//恢復OTHER
OSTaskResume(MODBUS1_TASK_Prio);					//恢復MODBUS1	
OSTaskResume(MODBUS2_TASK_Prio);					//恢復MODBUS2
OSTaskResume(KEY_TASK_Prio);						//恢復KEY
RTC_EnableInt();									//恢復RTC中斷
}
#endif //SYS_CMD_EN_
//例子2:下載字型檔編碼到外部flash,使用的是SPI 介面flash,W25Q128,儲存介面稍有不同,因為我每次將資料集齊4K才進行儲存,這樣可以提高儲存效率,降低flash損耗,當然最後一包可能不足4K,會另外進行處理的。
/*************************************************************************************************************
* 檔名:			DownFont.c
* 功能:			下載字型檔相關
* 作者:			[email protected]
* 建立時間:		2017-03-29
* 最後修改時間:	2017-03-29
* 詳細:			使用xmodem或tFileModem下載檔案
*************************************************************************************************************/
#include "system.h"
#include "usart.h"
#include "main.h"
#include "xmodem.h"
#include "tFileModem.h"
#include "upgrade.h"
#include "STM32Flash.h"
#include "STM32_CRC.h"
#if SYS_WDG_EN_
#include "wdg.h"
#endif
#include <stdlib.h>
#include "DownFont.h"
#include "RTU.h"
#include "BOARD.h"
#define DOWN_FONT_EXIT_SECTOR		FLASH_BIN_SECTOR				//儲存到外部flash的位置
static u32 DownFontDataSaveCnt = 0;		//升級應用程式已經儲存的資料大小
//收到資料包回撥-用於寫資料到外部flash
//需要使用到W25X16的4K臨時緩衝區,在升級與校驗的時候確保沒有其它執行緒訪問W25X16,最好掛起其它執行緒
bool DownFontReceivePacketExitFlash(u8 *pPackData,u16 PackSize,u32 RecDataSize)
{
if((PackSize != 128) && (PackSize!=1024)) return FALSE;										//xmodem只支援128位元組或1024位元組的資料包
memcpy(&SPI_FLASH_BUF[RecDataSize%4096], pPackData, PackSize);
if(((RecDataSize PackSize)%4096) == 0)														//4K對齊,一次寫入到外部flash
{
if(W25X16_EraseSector((RecDataSize PackSize)/4096-1   DOWN_FONT_EXIT_SECTOR) == FALSE)	//擦除一個扇區
{
return FALSE;
}
if(W25X16_WriteNoCheck(SPI_FLASH_BUF, DOWN_FONT_EXIT_SECTOR*4096 RecDataSize PackSize-4096, 4096)  == FALSE)	//寫入一個扇區
{
return FALSE;
}
DownFontDataSaveCnt  = 4096;
}
return TRUE;
}
//傳輸結束時回撥(可能是出錯結束)
//由於需要4K對齊,因此最後一包不足4K需要單獨進行處理
//需要使用到W25X16的4K臨時緩衝區,在升級與校驗的時候確保沒有其它執行緒訪問W25X16,最好掛起其它執行緒
bool DownFontTransEndtExitFlash(bool isTransOK, u32 RecDataSize)
{
if(isTransOK==FALSE) return FALSE;	//失敗返回
if(DownFontDataSaveCnt == RecDataSize) return TRUE;
if(DownFontDataSaveCnt > RecDataSize) return FALSE;			//儲存的資料不能大於接收的資料
if((RecDataSize-DownFontDataSaveCnt) >= 4096) return FALSE;	//未儲存的資料大小不能超過4K
if(W25X16_EraseSector(DownFontDataSaveCnt/4096   DOWN_FONT_EXIT_SECTOR) == TRUE)													//擦除一個扇區
{
if(W25X16_WriteNoCheck(SPI_FLASH_BUF, DOWN_FONT_EXIT_SECTOR*4096 DownFontDataSaveCnt, RecDataSize-DownFontDataSaveCnt)  == TRUE)	//寫入一個扇區
{
return TRUE;
}
}
return FALSE;	//儲存出錯
}
//使用XMODEM下載字型檔資料到外部flash
//返回程式大小,如果失敗了返回<=0
int XMODEM_DownloadFontToExitFlash(u32 MaxFileSize)
{
XMODE_Init(&UpgradeHandle, 			//控制代碼
UpgradeSendData, 				//傳送資料回撥函式
UpgradeReadData, 				//讀取資料回撥函式
UpgradeClearData, 				//清除接收資料緩衝回撥函式
DownFontReceivePacketExitFlash, //接收到資料包回撥函式
DownFontTransEndtExitFlash		//傳輸結束回撥函式
);
DownFontDataSaveCnt = 0;				//已經儲存的資料大小清零
return XMODEM_DownloadFile(&UpgradeHandle, MaxFileSize);
}
//使能系統命令列
#if SYS_CMD_EN_
#include "cmd.h"
#include "string.h"
CMD_TYPE const CMD_DOWN_BIN	= {"DOWN BIN", 0X51A36D01, CMD_DownFont, "\t下載字型檔資料"};			//下載字型檔資料
//升級應用層-需要使用到W25X16的4K臨時緩衝區,在升級與校驗的時候確保沒有其它執行緒訪問W25X16,最好掛起其它執行緒
void CMD_DownFont(char *pStr)
{
u32 DataSize;
cmd_printf("已經進入升級程式模式,等待連線,超時10S!\r\n>");
cmd_printf("請在10S內進入Xmodem下載字型檔模式!\r\n>");
RTC_DisableInt();									//關閉RTC中斷,防止喚醒後臺任務
OSTaskSuspend(BACK_TASK_Prio);						//掛起後臺任務執行緒
OSTaskSuspend(LED_TASK_Prio);						//掛起LED任務執行緒
OSTaskSuspend(GPRS_TASK_Prio);						//掛起GPRS程序
OSTaskSuspend(COLL_TASK_Prio);						//資料採集時間查詢程序
OSTaskSuspend(OTHER_TASK_Prio);						//OTHER
OSTaskSuspend(MODBUS1_TASK_Prio);					//MODBUS1	
OSTaskSuspend(MODBUS2_TASK_Prio);					//MODBUS2
OSTaskSuspend(KEY_TASK_Prio);						//KEY
DataSize = XMODEM_DownloadFontToExitFlash(FLASH_BIN_SIZE);//下載字型檔
if(DataSize==0)
{
cmd_printf("下載字型檔失敗!\r\n>");
}
else
{
cmd_printf("下載字型檔成功(%dB)!\r\n",DataSize);
}
CMD_Help(NULL);
OSTaskResume(LED_TASK_Prio);						//恢復掛起LED任務執行緒
OSTaskResume(GPRS_TASK_Prio);						//恢復掛起GPRS程序
OSTaskResume(COLL_TASK_Prio);						//恢復資料採集時間查詢程序
OSTaskResume(OTHER_TASK_Prio);						//恢復OTHER
OSTaskResume(MODBUS1_TASK_Prio);					//恢復MODBUS1	
OSTaskResume(MODBUS2_TASK_Prio);					//恢復MODBUS2
OSTaskResume(KEY_TASK_Prio);						//恢復KEY
RTC_EnableInt();									//恢復RTC中斷
}
#endif //SYS_CMD_EN_