Sfoglia il codice sorgente

Upgrade protocol to v2.1 - read/write registers + CRC

D.R.racer 1 anno fa
parent
commit
2f0ceabce5

+ 22 - 0
Firmware/mmu2_crc.cpp

@@ -0,0 +1,22 @@
+/// @file
+#include "mmu2_crc.h"
+
+#ifdef __AVR__
+#include <util/crc16.h>
+#endif
+
+namespace modules {
+namespace crc {
+
+#ifdef __AVR__
+uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
+    return _crc8_ccitt_update(crc, b);
+}
+#else
+uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
+    return CCITT_updateCX(crc, b);
+}
+#endif
+
+} // namespace crc
+} // namespace modules

+ 43 - 0
Firmware/mmu2_crc.h

@@ -0,0 +1,43 @@
+/// @file
+#pragma once
+#include <stdint.h>
+
+namespace modules {
+
+/// Contains all the necessary functions for computation of CRC
+namespace crc {
+
+class CRC8 {
+public:
+    /// Compute/update CRC8 CCIIT from 8bits.
+    /// Details: https://www.nongnu.org/avr-libc/user-manual/group__util__crc.html
+    static uint8_t CCITT_update(uint8_t crc, uint8_t b);
+
+    static constexpr uint8_t CCITT_updateCX(uint8_t crc, uint8_t b) {
+        uint8_t data = crc ^ b;
+        for (uint8_t i = 0; i < 8; i++) {
+            if ((data & 0x80U) != 0) {
+                data <<= 1U;
+                data ^= 0x07U;
+            } else {
+                data <<= 1U;
+            }
+        }
+        return data;
+    }
+
+    /// Compute/update CRC8 CCIIT from 16bits (convenience wrapper)
+    static constexpr uint8_t CCITT_updateW(uint8_t crc, uint16_t w) {
+        union U {
+            uint8_t b[2];
+            uint16_t w;
+            explicit constexpr inline U(uint16_t w)
+                : w(w) {}
+        } u(w);
+        return CCITT_updateCX(CCITT_updateCX(crc, u.b[0]), u.b[1]);
+    }
+};
+
+} // namespace crc
+
+} // namespace modules

+ 196 - 82
Firmware/mmu2_protocol.cpp

@@ -9,7 +9,7 @@
 //  any of the running operation statuses: OID: [T|L|U|E|C|W|K][0-4]
 //  <OID> P[0-9]      : command being processed i.e. operation running, may contain a state number
 //  <OID> E[0-9][0-9] : error 1-9 while doing a tool change
-//  <OID> F           : operation finished - will be repeated to "Q" messages until a new command is issued
+//  <OID> F[0-9]      : operation finished - will be repeated to "Q" messages until a new command is issued
 
 namespace modules {
 namespace protocol {
@@ -40,14 +40,17 @@ DecodeStatus Protocol::DecodeRequest(uint8_t c) {
         case 'S':
         case 'B':
         case 'E':
-        case 'W':
+        case 'W': // write is gonna be a special one
         case 'K':
         case 'F':
         case 'f':
         case 'H':
+        case 'R':
             requestMsg.code = (RequestMsgCodes)c;
             requestMsg.value = 0;
-            rqState = RequestStates::Value;
+            requestMsg.value2 = 0;
+            requestMsg.crc8 = 0;
+            rqState = (c == 'W') ? RequestStates::Address : RequestStates::Value; // prepare special automaton path for Write commands
             return DecodeStatus::NeedMoreData;
         default:
             requestMsg.code = RequestMsgCodes::unknown;
@@ -55,18 +58,61 @@ DecodeStatus Protocol::DecodeRequest(uint8_t c) {
             return DecodeStatus::Error;
         }
     case RequestStates::Value:
-        if (IsDigit(c)) {
-            requestMsg.value *= 10;
-            requestMsg.value += c - '0';
+        if (IsHexDigit(c)) {
+            requestMsg.value <<= 4U;
+            requestMsg.value |= Char2Nibble(c);
+            return DecodeStatus::NeedMoreData;
+        } else if (IsCRCSeparator(c)) {
+            rqState = RequestStates::CRC;
+            return DecodeStatus::NeedMoreData;
+        } else {
+            requestMsg.code = RequestMsgCodes::unknown;
+            rqState = RequestStates::Error;
+            return DecodeStatus::Error;
+        }
+    case RequestStates::Address:
+        if (IsHexDigit(c)) {
+            requestMsg.value <<= 4U;
+            requestMsg.value |= Char2Nibble(c);
+            return DecodeStatus::NeedMoreData;
+        } else if (c == ' ') { // end of address, value coming
+            rqState = RequestStates::WriteValue;
             return DecodeStatus::NeedMoreData;
-        } else if (IsNewLine(c)) {
-            rqState = RequestStates::Code;
-            return DecodeStatus::MessageCompleted;
         } else {
             requestMsg.code = RequestMsgCodes::unknown;
             rqState = RequestStates::Error;
             return DecodeStatus::Error;
         }
+    case RequestStates::WriteValue:
+        if (IsHexDigit(c)) {
+            requestMsg.value2 <<= 4U;
+            requestMsg.value2 |= Char2Nibble(c);
+            return DecodeStatus::NeedMoreData;
+        } else if (IsCRCSeparator(c)) {
+            rqState = RequestStates::CRC;
+            return DecodeStatus::NeedMoreData;
+        } else {
+            requestMsg.code = RequestMsgCodes::unknown;
+            rqState = RequestStates::Error;
+            return DecodeStatus::Error;
+        }
+    case RequestStates::CRC:
+        if (IsHexDigit(c)) {
+            requestMsg.crc8 <<= 4U;
+            requestMsg.crc8 |= Char2Nibble(c);
+            return DecodeStatus::NeedMoreData;
+        } else if (IsNewLine(c)) {
+            // check CRC at this spot
+            if (requestMsg.crc8 != requestMsg.ComputeCRC8()) {
+                // CRC mismatch
+                requestMsg.code = RequestMsgCodes::unknown;
+                rqState = RequestStates::Error;
+                return DecodeStatus::Error;
+            } else {
+                rqState = RequestStates::Code;
+                return DecodeStatus::MessageCompleted;
+            }
+        }
     default: //case error:
         if (IsNewLine(c)) {
             rqState = RequestStates::Code;
@@ -80,12 +126,28 @@ DecodeStatus Protocol::DecodeRequest(uint8_t c) {
 }
 
 uint8_t Protocol::EncodeRequest(const RequestMsg &msg, uint8_t *txbuff) {
-    constexpr uint8_t reqSize = 3;
     txbuff[0] = (uint8_t)msg.code;
-    txbuff[1] = msg.value + '0';
-    txbuff[2] = '\n';
-    return reqSize;
-    static_assert(reqSize <= MaxRequestSize(), "Request message length exceeded the maximum size, increase the magic constant in MaxRequestSize()");
+    uint8_t i = 1 + UInt8ToHex(msg.value, txbuff + 1);
+
+    i += AppendCRC(msg.CRC(), txbuff + i);
+
+    txbuff[i] = '\n';
+    ++i;
+    return i;
+    static_assert(7 <= MaxRequestSize(), "Request message length exceeded the maximum size, increase the magic constant in MaxRequestSize()");
+}
+
+uint8_t Protocol::EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff) {
+    const RequestMsg msg(RequestMsgCodes::Write, address, value);
+    uint8_t i = BeginEncodeRequest(msg, txbuff);
+    // dump the value
+    i += UInt16ToHex(value, txbuff + i);
+
+    i += AppendCRC(msg.CRC(), txbuff + i);
+
+    txbuff[i] = '\n';
+    ++i;
+    return i;
 }
 
 DecodeStatus Protocol::DecodeResponse(uint8_t c) {
@@ -107,8 +169,11 @@ DecodeStatus Protocol::DecodeResponse(uint8_t c) {
         case 'F':
         case 'f':
         case 'H':
+        case 'R':
             responseMsg.request.code = (RequestMsgCodes)c;
             responseMsg.request.value = 0;
+            responseMsg.request.value2 = 0;
+            responseMsg.request.crc8 = 0;
             rspState = ResponseStates::RequestValue;
             return DecodeStatus::NeedMoreData;
         case 0x0a:
@@ -120,9 +185,9 @@ DecodeStatus Protocol::DecodeResponse(uint8_t c) {
             return DecodeStatus::Error;
         }
     case ResponseStates::RequestValue:
-        if (IsDigit(c)) {
-            responseMsg.request.value *= 10;
-            responseMsg.request.value += c - '0';
+        if (IsHexDigit(c)) {
+            responseMsg.request.value <<= 4U;
+            responseMsg.request.value += Char2Nibble(c);
             return DecodeStatus::NeedMoreData;
         } else if (c == ' ') {
             rspState = ResponseStates::ParamCode;
@@ -149,13 +214,34 @@ DecodeStatus Protocol::DecodeResponse(uint8_t c) {
             return DecodeStatus::Error;
         }
     case ResponseStates::ParamValue:
-        if (IsDigit(c)) {
-            responseMsg.paramValue *= 10;
-            responseMsg.paramValue += c - '0';
+        if (IsHexDigit(c)) {
+            responseMsg.paramValue <<= 4U;
+            responseMsg.paramValue += Char2Nibble(c);
+            return DecodeStatus::NeedMoreData;
+        } else if (IsCRCSeparator(c)) {
+            rspState = ResponseStates::CRC;
+            return DecodeStatus::NeedMoreData;
+        } else {
+            responseMsg.paramCode = ResponseMsgParamCodes::unknown;
+            rspState = ResponseStates::Error;
+            return DecodeStatus::Error;
+        }
+    case ResponseStates::CRC:
+        if (IsHexDigit(c)) {
+            responseMsg.request.crc8 <<= 4U;
+            responseMsg.request.crc8 += Char2Nibble(c);
             return DecodeStatus::NeedMoreData;
         } else if (IsNewLine(c)) {
-            rspState = ResponseStates::RequestCode;
-            return DecodeStatus::MessageCompleted;
+            // check CRC at this spot
+            if (responseMsg.request.crc8 != responseMsg.ComputeCRC8()) {
+                // CRC mismatch
+                responseMsg.paramCode = ResponseMsgParamCodes::unknown;
+                rspState = ResponseStates::Error;
+                return DecodeStatus::Error;
+            } else {
+                rspState = ResponseStates::RequestCode;
+                return DecodeStatus::MessageCompleted;
+            }
         } else {
             responseMsg.paramCode = ResponseMsgParamCodes::unknown;
             rspState = ResponseStates::Error;
@@ -173,75 +259,103 @@ DecodeStatus Protocol::DecodeResponse(uint8_t c) {
 }
 
 uint8_t Protocol::EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff) {
-    txbuff[0] = (uint8_t)msg.code;
-    txbuff[1] = msg.value + '0';
-    txbuff[2] = ' ';
-    txbuff[3] = (uint8_t)ar;
-    txbuff[4] = '\n';
-    return 5;
+    const ResponseMsg rsp(msg, ar, 0); // this needs some cleanup @@TODO - check assembly how bad is it
+    uint8_t i = BeginEncodeRequest(rsp.request, txbuff);
+    txbuff[i] = (uint8_t)ar;
+    ++i;
+    i += AppendCRC(rsp.CRC(), txbuff + i);
+    txbuff[i] = '\n';
+    ++i;
+    return i;
 }
 
 uint8_t Protocol::EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff) {
-    txbuff[0] = (uint8_t)msg.code;
-    txbuff[1] = msg.value + '0';
-    txbuff[2] = ' ';
-    txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
-    txbuff[4] = findaValue + '0';
-    txbuff[5] = '\n';
-    return 6;
+    return EncodeResponseRead(msg, true, findaValue, txbuff);
 }
 
-uint8_t Protocol::EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff) {
-    txbuff[0] = (uint8_t)msg.code;
-    txbuff[1] = msg.value + '0';
-    txbuff[2] = ' ';
-    txbuff[3] = (uint8_t)ResponseMsgParamCodes::Accepted;
-    uint8_t *dst = txbuff + 4;
-    if (value < 10) {
-        *dst++ = value + '0';
-    } else if (value < 100) {
-        *dst++ = value / 10 + '0';
-        *dst++ = value % 10 + '0';
-    } else {
-        *dst++ = value / 100 + '0';
-        *dst++ = (value / 10) % 10 + '0';
-        *dst++ = value % 10 + '0';
+uint8_t Protocol::EncodeResponseVersion(const RequestMsg &msg, uint16_t value, uint8_t *txbuff) {
+    return EncodeResponseRead(msg, true, value, txbuff);
+}
+
+uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff) {
+    const ResponseMsg rsp(msg, rcs.code, rcs.value);
+    uint8_t i = BeginEncodeRequest(msg, txbuff);
+    txbuff[i] = (uint8_t)rsp.paramCode;
+    ++i;
+    i += UInt16ToHex(rsp.paramValue, txbuff + i);
+    i += AppendCRC(rsp.CRC(), txbuff + i);
+    txbuff[i] = '\n';
+    return i + 1;
+}
+
+uint8_t Protocol::EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff) {
+    const ResponseMsg rsp(msg,
+        accepted ? ResponseMsgParamCodes::Accepted : ResponseMsgParamCodes::Rejected,
+        accepted ? value2 : 0 // be careful about this value for CRC computation - rejected status doesn't have any meaningful value which could be reconstructed from the textual form of the message
+    );
+    uint8_t i = BeginEncodeRequest(msg, txbuff);
+    txbuff[i] = (uint8_t)rsp.paramCode;
+    ++i;
+    if (accepted) {
+        // dump the value
+        i += UInt16ToHex(value2, txbuff + i);
     }
-    *dst = '\n';
-    return dst - txbuff + 1;
+    i += AppendCRC(rsp.CRC(), txbuff + i);
+    txbuff[i] = '\n';
+    return i + 1;
 }
 
-uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint16_t value, uint8_t *txbuff) {
-    txbuff[0] = (uint8_t)msg.code;
-    txbuff[1] = msg.value + '0';
-    txbuff[2] = ' ';
-    txbuff[3] = (uint8_t)code;
-    uint8_t *dst = txbuff + 4;
-    if (code != ResponseMsgParamCodes::Finished) {
-        if (value < 10) {
-            *dst++ = value + '0';
-        } else if (value < 100) {
-            *dst++ = value / 10 + '0';
-            *dst++ = value % 10 + '0';
-        } else if (value < 1000) {
-            *dst++ = value / 100 + '0';
-            *dst++ = (value / 10) % 10 + '0';
-            *dst++ = value % 10 + '0';
-        } else if (value < 10000) {
-            *dst++ = value / 1000 + '0';
-            *dst++ = (value / 100) % 10 + '0';
-            *dst++ = (value / 10) % 10 + '0';
-            *dst++ = value % 10 + '0';
-        } else {
-            *dst++ = value / 10000 + '0';
-            *dst++ = (value / 1000) % 10 + '0';
-            *dst++ = (value / 100) % 10 + '0';
-            *dst++ = (value / 10) % 10 + '0';
-            *dst++ = value % 10 + '0';
-        }
+uint8_t Protocol::UInt8ToHex(uint8_t value, uint8_t *dst) {
+    if (value == 0) {
+        *dst = '0';
+        return 1;
+    }
+
+    uint8_t v = value >> 4U;
+    uint8_t charsOut = 1;
+    if (v != 0) { // skip the first '0' if any
+        *dst = Nibble2Char(v);
+        ++dst;
+        charsOut = 2;
+    }
+    v = value & 0xfU;
+    *dst = Nibble2Char(v);
+    return charsOut;
+}
+
+uint8_t Protocol::UInt16ToHex(uint16_t value, uint8_t *dst) {
+    constexpr uint16_t topNibbleMask = 0xf000;
+    if (value == 0) {
+        *dst = '0';
+        return 1;
+    }
+    // skip initial zeros
+    uint8_t charsOut = 4;
+    while ((value & topNibbleMask) == 0) {
+        value <<= 4U;
+        --charsOut;
     }
-    *dst = '\n';
-    return dst - txbuff + 1;
+    for (uint8_t i = 0; i < charsOut; ++i) {
+        uint8_t n = (value & topNibbleMask) >> (8U + 4U);
+        value <<= 4U;
+        *dst = Nibble2Char(n);
+        ++dst;
+    }
+    return charsOut;
+}
+
+uint8_t Protocol::BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst) {
+    dst[0] = (uint8_t)msg.code;
+
+    uint8_t i = 1 + UInt8ToHex(msg.value, dst + 1);
+
+    dst[i] = ' ';
+    return i + 1;
+}
+
+uint8_t Protocol::AppendCRC(uint8_t crc, uint8_t *dst) {
+    dst[0] = '*'; // reprap-style separator of CRC
+    return 1 + UInt8ToHex(crc, dst + 1);
 }
 
 } // namespace protocol

+ 153 - 14
Firmware/mmu2_protocol.h

@@ -1,13 +1,13 @@
 /// @file protocol.h
 #pragma once
 #include <stdint.h>
+#include "mmu2_crc.h"
 
 namespace modules {
 
 /// @brief The MMU communication protocol implementation and related stuff.
 ///
 /// See description of the new protocol in the MMU 2021 doc
-/// @@TODO possibly add some checksum to verify the correctness of messages
 namespace protocol {
 
 /// Definition of request message codes
@@ -23,11 +23,12 @@ enum class RequestMsgCodes : uint8_t {
     Version = 'S',
     Button = 'B',
     Eject = 'E',
-    Wait = 'W',
+    Write = 'W',
     Cut = 'K',
     FilamentType = 'F',
     FilamentSensor = 'f',
-    Home = 'H'
+    Home = 'H',
+    Read = 'R'
 };
 
 /// Definition of response message parameter codes
@@ -37,20 +38,50 @@ enum class ResponseMsgParamCodes : uint8_t {
     Error = 'E',
     Finished = 'F',
     Accepted = 'A',
-    Rejected = 'R', 
+    Rejected = 'R',
     Button = 'B' // the MMU registered a button press and is sending it to the printer for processing
 };
 
 /// A request message - requests are being sent by the printer into the MMU.
 struct RequestMsg {
     RequestMsgCodes code; ///< code of the request message
-    uint8_t value; ///< value of the request message
+    uint8_t value; ///< value of the request message or address of variable to read/write
+    uint16_t value2; ///< in case or write messages - value to be written into the register
+
+    /// CRC8 check - please note we abuse this byte for CRC of ResponseMsgs as well.
+    /// The crc8 byte itself is not added into the CRC computation (obviously ;) )
+    /// Beware - adding any members of this data structure may need changing the way CRC is being computed!
+    uint8_t crc8;
+
+    constexpr uint8_t ComputeCRC8() const {
+        uint8_t crc = 0;
+        crc = modules::crc::CRC8::CCITT_updateCX(0, (uint8_t)code);
+        crc = modules::crc::CRC8::CCITT_updateCX(crc, value);
+        crc = modules::crc::CRC8::CCITT_updateCX(crc, value2);
+        return crc;
+    }
 
     /// @param code of the request message
     /// @param value of the request message
-    inline RequestMsg(RequestMsgCodes code, uint8_t value)
+    inline constexpr RequestMsg(RequestMsgCodes code, uint8_t value)
         : code(code)
-        , value(value) {}
+        , value(value)
+        , value2(0)
+        , crc8(ComputeCRC8()) {
+    }
+
+    /// Intended for write requests
+    /// @param code of the request message ('W')
+    /// @param address of the register
+    /// @param value to write into the register
+    inline constexpr RequestMsg(RequestMsgCodes code, uint8_t address, uint16_t value)
+        : code(code)
+        , value(address)
+        , value2(value)
+        , crc8(ComputeCRC8()) {
+    }
+
+    constexpr uint8_t CRC() const { return crc8; }
 };
 
 /// A response message - responses are being sent from the MMU into the printer as a response to a request message.
@@ -59,13 +90,33 @@ struct ResponseMsg {
     ResponseMsgParamCodes paramCode; ///< code of the parameter
     uint16_t paramValue; ///< value of the parameter
 
+    constexpr uint8_t ComputeCRC8() const {
+        uint8_t crc = request.ComputeCRC8();
+        crc = modules::crc::CRC8::CCITT_updateCX(crc, (uint8_t)paramCode);
+        crc = modules::crc::CRC8::CCITT_updateW(crc, paramValue);
+        return crc;
+    }
+
     /// @param request the source request message this response is a reply to
     /// @param paramCode code of the parameter
     /// @param paramValue value of the parameter
-    inline ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint16_t paramValue)
+    inline constexpr ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint16_t paramValue)
         : request(request)
         , paramCode(paramCode)
-        , paramValue(paramValue) {}
+        , paramValue(paramValue) {
+        this->request.crc8 = ComputeCRC8();
+    }
+
+    constexpr uint8_t CRC() const { return request.crc8; }
+};
+
+/// Combined commandStatus and its value into one data structure (optimization purposes)
+struct ResponseCommandStatus {
+    ResponseMsgParamCodes code;
+    uint16_t value;
+    inline constexpr ResponseCommandStatus(ResponseMsgParamCodes code, uint16_t value)
+        : code(code)
+        , value(value) {}
 };
 
 /// Message decoding return values
@@ -101,9 +152,18 @@ public:
     /// @returns number of bytes written into txbuff
     static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff);
 
+    /// Encodes Write request message msg into txbuff memory
+    /// It is expected the txbuff is large enough to fit the message
+    /// @returns number of bytes written into txbuff
+    static uint8_t EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff);
+
     /// @returns the maximum byte length necessary to encode a request message
     /// Beneficial in case of pre-allocating a buffer for enconding a RequestMsg.
-    static constexpr uint8_t MaxRequestSize() { return 3; }
+    static constexpr uint8_t MaxRequestSize() { return 13; }
+
+    /// @returns the maximum byte length necessary to encode a response message
+    /// Beneficial in case of pre-allocating a buffer for enconding a ResponseMsg.
+    static constexpr uint8_t MaxResponseSize() { return 14; }
 
     /// Encode generic response Command Accepted or Rejected
     /// @param msg source request message for this response
@@ -124,7 +184,7 @@ public:
     /// @param value version number (0-255)
     /// @param txbuff where to format the message
     /// @returns number of bytes written into txbuff
-    static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint8_t value, uint8_t *txbuff);
+    static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint16_t value, uint8_t *txbuff);
 
     /// Encode response to Query operation status
     /// @param msg source request message for this response
@@ -132,7 +192,15 @@ public:
     /// @param value related to status of operation(e.g. error code or progress)
     /// @param txbuff where to format the message
     /// @returns number of bytes written into txbuff
-    static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseMsgParamCodes code, uint16_t value, uint8_t *txbuff);
+    static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff);
+
+    /// Encode response to Read query
+    /// @param msg source request message for this response
+    /// @param accepted true if the read query was accepted
+    /// @param value2 variable value
+    /// @param txbuff where to format the message
+    /// @returns number of bytes written into txbuff
+    static uint8_t EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff);
 
     /// @returns the most recently lexed request message
     inline const RequestMsg GetRequestMsg() const { return requestMsg; }
@@ -150,10 +218,15 @@ public:
         rspState = ResponseStates::RequestCode;
     }
 
+#ifndef UNITTEST
 private:
+#endif
     enum class RequestStates : uint8_t {
         Code, ///< starting state - expects message code
         Value, ///< expecting code value
+        Address, ///< expecting address for Write command
+        WriteValue, ///< value to be written (Write command)
+        CRC, ///< CRC
         Error ///< automaton in error state
     };
 
@@ -165,18 +238,84 @@ private:
         RequestValue, ///< expecting code value
         ParamCode, ///< expecting param code
         ParamValue, ///< expecting param value
+        CRC, ///< expecting CRC value
         Error ///< automaton in error state
     };
 
     ResponseStates rspState;
     ResponseMsg responseMsg;
 
-    static bool IsNewLine(uint8_t c) {
+    static constexpr bool IsNewLine(uint8_t c) {
         return c == '\n' || c == '\r';
     }
-    static bool IsDigit(uint8_t c) {
+    static constexpr bool IsDigit(uint8_t c) {
         return c >= '0' && c <= '9';
     }
+    static constexpr bool IsCRCSeparator(uint8_t c) {
+        return c == '*';
+    }
+    static constexpr bool IsHexDigit(uint8_t c) {
+        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
+    }
+    static constexpr uint8_t Char2Nibble(uint8_t c) {
+        switch (c) {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            return c - '0';
+        case 'a':
+        case 'b':
+        case 'c':
+        case 'd':
+        case 'e':
+        case 'f':
+            return c - 'a' + 10;
+        default:
+            return 0;
+        }
+    }
+
+    static constexpr uint8_t Nibble2Char(uint8_t n) {
+        switch (n) {
+        case 0:
+        case 1:
+        case 2:
+        case 3:
+        case 4:
+        case 5:
+        case 6:
+        case 7:
+        case 8:
+        case 9:
+            return n + '0';
+        case 0xa:
+        case 0xb:
+        case 0xc:
+        case 0xd:
+        case 0xe:
+        case 0xf:
+            return n - 10 + 'a';
+        default:
+            return 0;
+        }
+    }
+
+    /// @returns number of characters written
+    static uint8_t UInt8ToHex(uint8_t value, uint8_t *dst);
+
+    /// @returns number of characters written
+    static uint8_t UInt16ToHex(uint16_t value, uint8_t *dst);
+
+    static uint8_t BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst);
+
+    static uint8_t AppendCRC(uint8_t crc, uint8_t *dst);
 };
 
 } // namespace protocol

+ 2 - 2
Firmware/mmu2_protocol_logic.cpp

@@ -7,8 +7,8 @@
 namespace MMU2 {
 
 static constexpr uint8_t supportedMmuFWVersionMajor = 2;
-static constexpr uint8_t supportedMmuFWVersionMinor = 0;
-static constexpr uint8_t supportedMmuFWVersionBuild = 19;
+static constexpr uint8_t supportedMmuFWVersionMinor = 1;
+static constexpr uint8_t supportedMmuFWVersionBuild = 1;
 
 StepStatus ProtocolLogicPartBase::ProcessFINDAReqSent(StepStatus finishedRV, State nextState){
     if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)