XModem協議

無痕幽雨發表於2018-05-23

出處:XModem協議

XModem協議介紹:
XModem是一種在串列埠通訊中廣泛使用的非同步檔案傳輸協議,分為XModem和1k-XModem協議兩種,前者使用128位元組的資料塊,後者使用1024位元組即1k位元組的資料塊。


一、XModem校驗和協議

1. XModem資訊包格式
XModem協議最早由Ward Christensen在20世紀70年代提出並實現的,傳輸資料單位為資訊包,資訊包格式如下:

---------------------------------------------------------------------------
|     Byte1     |    Byte2    |     Byte3      |Byte4~Byte131|  Byte132   |
|-------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data |  Check Sum |
---------------------------------------------------------------------------

 

2. 校驗和的計算
所有的資料位元組都將參與和運算,由於校驗和只佔一個位元組,如果累加的和超過255將從零開始繼續累加。


3. 欄位定義
<SOH> 01H
<EOT> 04H
<ACK> 06H
<NAK> 15H
<CAN> 18H

4. 校驗和方式的XModem傳輸流程
傳輸流程如圖所示:

------------------------------------------------------------------------------
|               SENDER                |          |          RECIEVER         |
|                                     |  <---    |  NAK                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  NAK                      |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  NAK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
|                                     |  <---    |  ACK                      |
| EOT                                 |  --->    |                           |
|                                     |  <---    |  ACK                      |
------------------------------------------------------------------------------

對於傳送方僅僅支援校驗和的傳輸方式,接收方應首先傳送NAK訊號來發起傳輸,如果傳送方沒有資料傳送過來,需要超時等待3秒之後再發起NAK訊號來進行資料傳輸。對於資料傳輸正確,接收方需要傳送ACK訊號來進行確認,如果資料傳輸有誤,則傳送NAK訊號,傳送方在接收到NAK訊號之後需要重新發起該次資料傳輸,如果資料已近傳輸完成,傳送方需要傳送EOT訊號,來結束資料傳輸。

5. 如何取消資料傳輸
當接收方傳送CAN表示無條件結束本次傳輸過程,傳送方收到CAN後,無需傳送EOT來確認,直接停止資料的傳送。


二、XModem-CRC16協議

1. XModem-CRC16資訊包格式
XModem協議在90年代做過一次修改,將132位元組處的校驗和改成雙位元組的CRC16校驗,CRC16校驗的資訊包格式如下:

------------------------------------------------------------------------------
|     Byte1     |    Byte2    |     Byte3      |Byte4~Byte131|Byte132~Byte133|
|----------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data |   16Bit CRC   |
------------------------------------------------------------------------------

 

2. CRC16的計算
比較複雜,表示看不懂,以後有時間再研究吧,先給出一份原始碼,來自:
http://web.mit.edu/6.115/www/miscfiles/amulet/amulet-help/xmodem.htm

 

[cpp] view plain copy

  1. int calcrc(char *ptr, int count)  
  2. {  
  3.     int crc;  
  4.     char i;  
  5.   
  6.     crc = 0;  
  7.     while (--count >= 0)  
  8.     {  
  9.         crc = crc ^ (int) *ptr++ << 8;  
  10.         i = 8;  
  11.         do  
  12.         {  
  13.             if (crc & 0x8000)  
  14.                 crc = crc << 1 ^ 0x1021;  
  15.             else  
  16.                 crc = crc << 1;  
  17.         } while (--i);  
  18.     }  
  19.   
  20.     return (crc);  
  21. }  

需要注意的是,在傳送方,CRC是高位元組在前,低位元組在後。

 

3. CRC16校驗的XModem傳輸流程
傳輸流程如圖所示:

 

---------------------------------------------------------------------------
|               SENDER             |          |           RECIEVER        |
|                                  |  <---    |  'C'                      |
|                                  |          |  Time out after 3 second  |
|                                  |  <---    |  'C'                      |
| SOH|0x01|0xFE|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  ACK                      |
| SOH|0x02|0xFD|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  NAK                      |
| SOH|0x02|0xFD|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  ACK                      |
| SOH|0x03|0xFC|Data[0~127]|CRC16| |  --->    |                           |
|                                  |  <---    |  ACK                      |
| .                                |          |  .                        |
| .                                |          |  .                        |
| .                                |          |  .                        |
|                                  |  <---    |  ACK                      |
| EOT                              |  --->    |                           |
|                                  |  <---    |  ACK                      |
---------------------------------------------------------------------------

 

和校驗和方式不同的是,當接收方要求傳送方以CRC16校驗方式傳送資料時以'C'來請求,傳送方對此做出應答,流程就如上圖所示。當傳送方僅僅支援校驗和方式時,則接收方要傳送NAK來請求,要求以校驗和方式來傳送資料,如果僅僅支援CRC16校驗方式,則只能傳送'C'來請求。如果兩者都支援的話,優先傳送'C'來請求,流程如圖所示:

------------------------------------------------------------------------------
|               SENDER                |          |           RECIEVER        |
|                                     |  <---    |  'C'                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  NAK                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  'C'                      |
|                                     |          |  Time out after 3 second  |
|                                     |  <---    |  NAK                      |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  NAK                      |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| |  --->    |                           |
|                                     |  <---    |  ACK                      |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
| .                                   |          |  .                        |
|                                     |  <---    |  ACK                      |
| EOT                                 |  --->    |                           |
|                                     |  <---    |  ACK                      |
------------------------------------------------------------------------------

最後,如果資訊包中的資料如果不足128位元組,剩餘的部分要以0x1A(Ctrl-Z)來填充。


三、1k-XModem協議
1k-XModem協議同XModem-CRC16協議差不多,只是資料塊長度變成了1024位元組即1k,同時每個資訊報的第一個位元組的SOH變成了STX,STX定義為 <STX> 0x02,能有效的加快資料傳輸速率。

 

 

 

使用Java實現Xmodem協議

 

 

 

Xmodem協議

 

1.介紹

Xmodem是一種在串列埠通訊中廣泛使用的非同步檔案傳輸協議,分為Xmodem(使用128位元組的資料塊)和1k-Xmodem(使用1024位元組即1k位元組的資料塊)協議兩種。
本文實現的是128位元組資料塊的Xmodem協議,採用CRC16校驗,在專案中應用時,傳送端和接收端可根據具體情況修改雙方的協議。

標準Xmodem協議(使用128位元組的資料塊)幀格式:

Byte1 Byte2 Byte3 Byte4 ~ byte131 Byte132
控制字元 包序號 包序號的反碼 資料 校驗和

如果你對串列埠通訊還不太瞭解,可以看下我寫的這篇部落格使用Java實現串列埠通訊

2.實現

在和嵌入式同學除錯的過程中,發現傳送端傳送資料過快,導致接收端處理不過來,所以在send方法中開啟了一個子執行緒來處理資料傳送邏輯,方便加入延時處理。
接收方法中,傳送C是表示以CRC方式校驗。


public class Xmodem {

    // 開始
    private final byte SOH = 0x01;
    // 結束
    private final byte EOT = 0x04;
    // 應答
    private final byte ACK = 0x06;
    // 重傳
    private final byte NAK = 0x15;
    // 無條件結束
    private final byte CAN = 0x18;

    // 以128位元組塊的形式傳輸資料
    private final int SECTOR_SIZE = 128;
    // 最大錯誤(無應答)包數
    private final int MAX_ERRORS = 10;

    // 輸入流,用於讀取串列埠資料
    private InputStream inputStream;
    // 輸出流,用於傳送串列埠資料
    private OutputStream outputStream;

    public Xmodem(InputStream inputStream, OutputStream outputStream) {
        this.inputStream = inputStream;
        this.outputStream = outputStream;
    }

    /**
     * 傳送資料
     * 
     * @param filePath
     *            檔案路徑
     */
    public void send(final String filePath) {
        new Thread() {
            public void run() {
                try {
                    // 錯誤包數
                    int errorCount;
                    // 包序號
                    byte blockNumber = 0x01;
                    // 校驗和
                    int checkSum;
                    // 讀取到緩衝區的位元組數量
                    int nbytes;
                    // 初始化資料緩衝區
                    byte[] sector = new byte[SECTOR_SIZE];
                    // 讀取檔案初始化
                    DataInputStream inputStream = new DataInputStream(
                            new FileInputStream(filePath));

                    while ((nbytes = inputStream.read(sector)) > 0) {
                        // 如果最後一包資料小於128個位元組,以0xff補齊
                        if (nbytes < SECTOR_SIZE) {
                            for (int i = nbytes; i < SECTOR_SIZE; i++) {
                                sector[i] = (byte) 0xff;
                            }
                        }

                        // 同一包資料最多傳送10次
                        errorCount = 0;
                        while (errorCount < MAX_ERRORS) {
                            // 組包
                            // 控制字元 + 包序號 + 包序號的反碼 + 資料 + 校驗和
                            putData(SOH);
                            putData(blockNumber);
                            putData(~blockNumber);
                            checkSum = CRC16.calc(sector) & 0x00ffff;
                            putChar(sector, (short) checkSum);
                            outputStream.flush();

                            // 獲取應答資料
                            byte data = getData();
                            // 如果收到應答資料則跳出迴圈,傳送下一包資料
                            // 未收到應答,錯誤包數+1,繼續重發
                            if (data == ACK) {
                                break;
                            } else {
                                ++errorCount;
                            }
                        }
                        // 包序號自增
                        blockNumber = (byte) ((++blockNumber) % 256);
                    }

                    // 所有資料傳送完成後,傳送結束標識
                    boolean isAck = false;
                    while (!isAck) {
                        putData(EOT);
                        isAck = getData() == ACK;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }

    /**
     * 接收資料
     * 
     * @param filePath
     *            檔案路徑
     * @return 是否接收完成
     * @throws IOException
     *             異常
     */
    public boolean receive(String filePath) throws Exception {
        // 錯誤包數
        int errorCount = 0;
        // 包序號
        byte blocknumber = 0x01;
        // 資料
        byte data;
        // 校驗和
        int checkSum;
        // 初始化資料緩衝區
        byte[] sector = new byte[SECTOR_SIZE];
        // 寫入檔案初始化
        DataOutputStream outputStream = new DataOutputStream(
                new FileOutputStream(filePath));

        // 傳送字元C,CRC方式校驗
        putData((byte) 0x43);

        while (true) {
            if (errorCount > MAX_ERRORS) {
                outputStream.close();
                return false;
            }

            // 獲取應答資料
            data = getData();
            if (data != EOT) {
                try {
                    // 判斷接收到的是否是開始標識
                    if (data != SOH) {
                        errorCount++;
                        continue;
                    }

                    // 獲取包序號
                    data = getData();
                    // 判斷包序號是否正確
                    if (data != blocknumber) {
                        errorCount++;
                        continue;
                    }

                    // 獲取包序號的反碼
                    byte _blocknumber = (byte) ~getData();
                    // 判斷包序號的反碼是否正確
                    if (data != _blocknumber) {
                        errorCount++;
                        continue;
                    }

                    // 獲取資料
                    for (int i = 0; i < SECTOR_SIZE; i++) {
                        sector[i] = getData();
                    }

                    // 獲取校驗和
                    checkSum = (getData() & 0xff) << 8;
                    checkSum |= (getData() & 0xff);
                    // 判斷校驗和是否正確
                    int crc = CRC16.calc(sector);
                    if (crc != checkSum) {
                        errorCount++;
                        continue;
                    }

                    // 傳送應答
                    putData(ACK);
                    // 包序號自增
                    blocknumber++;
                    // 將資料寫入本地
                    outputStream.write(sector);
                    // 錯誤包數歸零
                    errorCount = 0;

                } catch (Exception e) {
                    e.printStackTrace();

                } finally {
                    // 如果出錯傳送重傳標識
                    if (errorCount != 0) {
                        putData(NAK);
                    }
                }
            } else {
                break;
            }
        }

        // 關閉輸出流
        outputStream.close();
        // 傳送應答
        putData(ACK);

        return true;
    }

    /**
     * 獲取資料
     * 
     * @return 資料
     * @throws IOException
     *             異常
     */
    private byte getData() throws IOException {
        return (byte) inputStream.read();
    }

    /**
     * 傳送資料
     * 
     * @param data
     *            資料
     * @throws IOException
     *             異常
     */
    private void putData(int data) throws IOException {
        outputStream.write((byte) data);
    }

    /**
     * 傳送資料
     * 
     * @param data
     *            資料
     * @param checkSum
     *            校驗和
     * @throws IOException
     *             異常
     */
    private void putChar(byte[] data, short checkSum) throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(data.length + 2).order(
                ByteOrder.BIG_ENDIAN);
        bb.put(data);
        bb.putShort(checkSum);
        outputStream.write(bb.array());
    }
}

CRC16校驗演算法,採用的是查表法。


public class CRC16 {

    private static final char crctable[] = { 0x0000, 0x1021, 0x2042, 0x3063,
            0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
            0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252,
            0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a,
            0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
            0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509,
            0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630,
            0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
            0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7,
            0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af,
            0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
            0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e,
            0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
            0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
            0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4,
            0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc,
            0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
            0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
            0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da,
            0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
            0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589,
            0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481,
            0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
            0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0,
            0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f,
            0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
            0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e,
            0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16,
            0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
            0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45,
            0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c,
            0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
            0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };

    public static char calc(byte[] bytes) {
        char crc = 0x0000;
        for (byte b : bytes) {
            crc = (char) ((crc << 8) ^ crctable[((crc >> 8) ^ b) & 0x00ff]);
        }
        return (char) (crc);
    }
}

3.使用


// serialPort為串列埠物件
Xmodem xmodem = new Xmodem(serialPort.getInputStream(),serialPort.getOutputStream());
// filePath為檔案路徑
// ./bin/xxx.bin
xmodem.send(filePath);

4.寫在最後

完整的程式碼下載



作者:容華謝後
連結:https://www.jianshu.com/p/6dabbfe61495
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

 

 

 

2018.06.08 10.58

      xmodem協議模組今天測試完畢了,說下我的思路和感受。寫之前一定要有一個大題的思路,

這個東西做成什麼樣子(API介面設計,狀態機設計),然後就是畫各個狀態圖->合併狀態圖->原始碼實現。

其實我比較懶,懶得動筆畫,我習慣寫之前把狀態列舉出來,然後寫的過程中會根據思路增加或者減少狀態。

       xmodem的API介面:

bool     xmodem_init(       xmodem_t *ptXmodem,user_api_t *ptApi);
bool     xmodem_start_rx(   xmodem_t *ptXmodem,xmodem_select_check_mode_t tCheckMode,uint16_t hwFrameLong);
bool     xmodem_cfg_tx_mode(xmodem_t *ptXmodem,uint16_t hwFrameLong);
bool     xmodem_cancel_rx(  xmodem_t *ptXmodem);
fsm_rt_t xmodem_tx(         xmodem_t *ptXmodem);
fsm_rt_t xmodem_check(      void     *ptXmodem,bool *pbIsRequestDrop,queue_peek_byte_t* ptReadByteHandler);

    模式:

typedef enum{
    XMODEM_CHECKOUT_SUM = 0,
    XMODEM_CHECKOUT_CRC16,
    XMODEM_CHECKOUT_AUTO,
}xmodem_select_check_mode_t;

注:

1、傳送模式自適應,無需配置模式;

2、接收模式三種模式都可以配置,如果配置為自適應,則'C'和NAK每隔3s交替傳送;

3、幀長只做了1024和非1024(最大幀長為1024),非1024按著128格式走;

4、不支援收和發檔案同時進行;   

5、接收執行緒由協議解析引擎驅動(每次進入接收都需要使用者啟動);

6、傳送函式需要由使用者傳送執行緒驅動(開始傳送前需要配置幀長,預設128);

 

      接收狀態機,相對於傳送狀態機要複雜一些,其中有幾個狀態是獨立:強制停止接收態,傳送模式

接收協議解析,這幾個狀態是通過API強制改變的。傳送模式簡單分為三大部分:傳送啟動字元(每隔3s發一次),

第一幀協議接收(這裡面可能跳轉到傳送啟動字元狀態),後續幀接收(這裡面有傳送ACK和NAK)。

typedef enum{
    XMODEM_CHECK_STATE_START = 0,
    XMODEM_CHECK_STATE_WAIT_START_RX_FLAG,
    XMODEM_CHECK_STATE_START_UP_TX_C,
    XMODEM_CHECK_STATE_START_UP_TX_NAK,
    XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_START,
    XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_NUM,
    XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_CHECK_NUM,
    XMODEM_CHECK_STATE_START_UP_WAIT_FRAME_DATA,
    XMODEM_CHECK_STATE_START_UP_FRAME_CHECK,

    XMODEM_CHECK_STATE_WAIT_FRAME_START,
    XMODEM_CHECK_STATE_WAIT_FRAME_DATA,
    XMODEM_CHECK_STATE_WAIT_FRAME_CHECK,
    XMODEM_CHECK_STATE_WAIT_RETURN_ACK,
    XMODEM_CHECK_STATE_WAIT_RETURN_NAK,
    XMODEM_CHECK_STATE_RX_EOT,
    XMODEM_CHECK_STATE_USER_FORCE_STOP_RX,

    //TX
    XMODEM_CHECK_STATE_TX_CHECK_START_FLAG,
    XMODEM_CHECK_STATE_TX_WAIT_RETURN,
}xmodem_check_state_t;

        xmodem的接收模式就相對簡單了,讀取字串流,打包傳送,等待返回字元。

typedef enum{
    XMODEM_TX_STATE_START = 0,
    XMODEM_TX_STATE_IS_INIT_API,
    XMODEM_TX_STATE_WAIT_START_FLAG,
    XMODEM_TX_STATE_READ_BUFFER,
    XMODEM_TX_STATE_TX,
    XMODEM_TX_STATE_WAIT_RETURN,
    XMODEM_TX_STATE_TX_EOT,
}xmodem_tx_state_t;

       xmodem需要外部提供一些API藉口,如下:

typedef void        start_timer(uint16_t hwDelayMs);
typedef bool        check_timer_flag(void);
typedef bool        user_buffer_write(uint8_t *pchBuffer,uint16_t hwNum);
typedef uint16_t    user_buffer_read( uint8_t *pchBuffer,uint16_t hwNum);
typedef fsm_rt_t    xmodem_serial_out(uint8_t *pchBuffer,uint16_t hwSize);

typedef struct  _user_api_t    user_api_t;
struct  _user_api_t{
    //延時相關
    start_timer         *ptStartTimer;
    check_timer_flag    *ptCheckTimerFlag;

    //APP層對接
    user_buffer_write   *ptWriteBuffer;
    user_buffer_read    *ptReadBuffer;

    //輸出資料
    xmodem_serial_out   *ptSerialOut;
};

說明:

1、start_timer和check_timer_flag延時相關函式;

2、user_buffer_write向使用者輸出資料流,完整一幀協議;

3、user_buffer_read讀取使用者要傳送資料,每次讀取一幀長度,返回實際讀取資料長度,如果為0,則表示檔案傳送完成;

4、xmodem_serial_out向UART的傳送Buffer寫入資料,狀態機方式,主要是考慮UART的傳送buffer小於幀長;

原始碼我就不釋出了,沒啥意義,需要配合我的協議解析引擎和queue佇列才能使用。

 

2018.07.30

前幾天看了遍程式碼,總是感覺哪裡不對,但是又說不上來,今天看了個相似歷程,算是明白自己的程式碼哪裡有問題了:

1、狀態機太"複雜"了,這裡的複雜是指一個函式裡面跑了幾個狀態機,都在一個平面;不是說一個函式不能跑幾個狀態機,

而是狀態機應該是巢狀關係,不應該是平鋪的關係;

2、資料流程沒有去抽象,用了一個很大的函式去處理,也就是沒有所謂的層次感;

總結就是,寫程式碼應該是像搭積木,用一塊塊積木去拼湊自己的模型;而不應該是像攤大餅一樣,平鋪在一起。

修改程式碼,回頭再發上來。

2018.08.02

今天程式碼改造完了,有了縱向的層次感,想積木一樣,一層一層搭建起來。但是裡面仍然有一些根據初始化配置不同而處理

邏輯不同,比如:長度是128還是1K,校驗方式是校驗和還是CRC16。這些怎麼處理?

我們可以這樣處理,邏輯層抽象出通用的API介面,把上述四種情況封裝成四個處理函式,然後在初始化時候根據不同的

配置初始化指標,這樣對於邏輯層來說處理邏輯完全是通用的。

 

2018.08.16

今天抽個時間把程式碼改了下,以前的處理邏輯是這樣的:

模式定義:

typedef enum{
    XMODEM_128_SUM = 0,
    XMODEM_128_CRC16,
    XMODEM_1K_SUM,
    XMODEM_1K_CRC16,
}xmodem_select_mode_t;

初始化:

bool xmodem_init(xmodem_t *ptXmodem,xmodem_cfg_t *ptCfg,user_api_t *ptApi)
{
    CLASS(xmodem_t)  *ptThis    =   (CLASS(xmodem_t) *)ptXmodem;
    if(NULL == ptThis || NULL == ptCfg || NULL == ptApi){
        return false;
    }

    if(NULL == ptApi->ptWriteBuffer     ||
       NULL == ptApi->ptStartTimer      ||
       NULL == ptApi->ptCheckTimerFlag  ||
       NULL == ptApi->ptSerialRead      ||
       NULL == ptApi->ptSerialWrite     ||
       NULL == ptCfg->pchBuffer           ){
        this.ptUserApi          = NULL;
        this.tUserApiIsInitFlag = false;
        return false;
    }

    this.tXmodemSelectMode             = ptCfg->tXmodemSelectMode;
    this.pchBuffer                     = ptCfg->pchBuffer;
    this.chByte                        = 0x00;

    this.ptUserApi                     = ptApi;
    this.tUserApiIsInitFlag            = true;

    return true;
}

下面的處理邏輯:

                switch(this.tXmodemSelectMode){
                    case XMODEM_128_SUM:
                        //break;
                    case XMODEM_128_CRC16:
                        if(SOH == this.pchBuffer[s_hwRevCnt]){
                            s_tState = XMODEM_REV_FRAME_DATA;
                            s_hwRevCnt++;
                        }else if(EOT == this.pchBuffer[s_hwRevCnt]){
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_RX_CPL;
                        }else{
                            //RST_XMODEM_REV_FRAME_FSM();
                            return  XMODEM_REV_FRAME_DROP;
                        }
                        break;
                    case XMODEM_1K_SUM:
                        //break;
                    case XMODEM_1K_CRC16:
                        if(STX == this.pchBuffer[s_hwRevCnt]){
                            s_tState = XMODEM_REV_FRAME_DATA;
                            s_hwRevCnt++;
                        }else if(EOT == this.pchBuffer[s_hwRevCnt]){
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_RX_CPL;
                        }else{
                            //RST_XMODEM_REV_FRAME_FSM();
                            return  XMODEM_REV_FRAME_DROP;
                        }
                        break;
                    default:
                        while(1);
                }
         if(XMODEM_REV_FRAME_CPL == tTemp){
                s_hwRevCnt++;
                switch(this.tXmodemSelectMode){
                    case XMODEM_128_SUM:
                        if(132 > s_hwRevCnt){
                            break;
                        }
                        chTemp = calsum(this.pchBuffer,s_hwRevCnt-1);
                        if(chTemp == this.pchBuffer[s_hwRevCnt-1]){
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CPL;
                        }else{
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CHECK_ERROR;
                        }
                        break;
                    case XMODEM_128_CRC16:
                        if(133 > s_hwRevCnt){
                            break;
                        }
                        hwTemp = calcrc(this.pchBuffer,s_hwRevCnt-2);
                        tCrc16.chCrcL = this.pchBuffer[s_hwRevCnt-1];
                        tCrc16.chCrcH = this.pchBuffer[s_hwRevCnt-2];
                        if(tCrc16.hwCrc16 == hwTemp){
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CPL;
                        }else{
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CHECK_ERROR;
                        }
                        break;
                    case XMODEM_1K_SUM:
                        if(1028 > s_hwRevCnt){
                            break;
                        }
                        chTemp = calsum(this.pchBuffer,s_hwRevCnt-1);
                        if(chTemp == this.pchBuffer[s_hwRevCnt-1]){
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CPL;
                        }else{
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CHECK_ERROR;
                        }
                        break;
                    case XMODEM_1K_CRC16:
                        if(1029 > s_hwRevCnt){
                            break;
                        }
                        hwTemp = calcrc(this.pchBuffer,s_hwRevCnt-2);
                        tCrc16.chCrcL = this.pchBuffer[s_hwRevCnt-1];
                        tCrc16.chCrcH = this.pchBuffer[s_hwRevCnt-2];
                        if(tCrc16.hwCrc16 == hwTemp){
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CPL;
                        }else{
                            RST_XMODEM_REV_FRAME_FSM();
                            return XMODEM_REV_FRAME_CHECK_ERROR;
                        }
                        break;
                    default:
                        while(1);
                }

有幾處程式碼都是類似的形式,看著是不是辣眼睛?後來改為了這樣:

初始化:

bool xmodem_init(xmodem_t *ptXmodem,xmodem_cfg_t *ptCfg,user_api_t *ptApi)
{
    CLASS(xmodem_t)  *ptThis    =   (CLASS(xmodem_t) *)ptXmodem;
    if(NULL == ptThis || NULL == ptCfg || NULL == ptApi){
        return false;
    }

    if(NULL == ptApi->ptWriteBuffer     ||
       NULL == ptApi->ptStartTimer      ||
       NULL == ptApi->ptCheckTimerFlag  ||
       NULL == ptApi->ptSerialRead      ||
       NULL == ptApi->ptSerialWrite     ||
       NULL == ptCfg->pchBuffer           ){
        this.ptUserApi          = NULL;
        this.tUserApiIsInitFlag = false;
        return false;
    }

    switch(ptCfg->tXmodemSelectMode){
        case XMODEM_128_SUM:
            this.cRxFrameHead   =   SOH;
            this.hwRxFrameSize  =   128+3+1;
            this.cTxStartUp     =   SUM_SAT;
            this.ptXmodemCheck  =   xmodem_sum_check;
            break;
        case XMODEM_128_CRC16:
            this.cRxFrameHead   =   SOH;
            this.hwRxFrameSize  =   128+3+2;
            this.cTxStartUp     =   CRC16_SAT;
            this.ptXmodemCheck  =   xmodem_crc_check;
            break;
        case XMODEM_1K_SUM:
            this.cRxFrameHead   =   STX;
            this.hwRxFrameSize  =   1024+3+1;
            this.cTxStartUp     =   SUM_SAT;
            this.ptXmodemCheck  =   xmodem_sum_check;
            break;
        case XMODEM_1K_CRC16:
            this.cRxFrameHead   =   STX;
            this.hwRxFrameSize  =   1024+3+2;
            this.cTxStartUp     =   CRC16_SAT;
            this.ptXmodemCheck  =   xmodem_crc_check;
            break;
        default:
            return false;
    }
    this.pchBuffer                     = ptCfg->pchBuffer;
    this.chByte                        = 0x00;

    this.ptUserApi                     = ptApi;
    this.tUserApiIsInitFlag            = true;

    return true;
}

然後下面的處理:

                if(this.cRxFrameHead == this.pchBuffer[s_hwRevCnt]){
                    s_tState = XMODEM_REV_FRAME_DATA;
                    s_hwRevCnt++;
                }else if(EOT == this.pchBuffer[s_hwRevCnt]){
                    RST_XMODEM_REV_FRAME_FSM();
                    return XMODEM_REV_FRAME_RX_CPL;
                }else{
                    //RST_XMODEM_REV_FRAME_FSM();
                    return  XMODEM_REV_FRAME_DROP;
                }
                s_hwRevCnt++;
                if(this.hwRxFrameSize > s_hwRevCnt){
                    break;
                }
                if(this.ptXmodemCheck(ptXmodem)){
                    RST_XMODEM_REV_FRAME_FSM();
                    return XMODEM_REV_FRAME_CPL;                    
                }else{
                    RST_XMODEM_REV_FRAME_FSM();
                    return XMODEM_REV_FRAME_CHECK_ERROR;
                }

這樣看是不是舒服很多。。。。。。