2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
27 #if defined(USE_MSP_OVER_TELEMETRY)
29 #include "build/build_config.h"
31 #include "common/utils.h"
32 #include "common/crc.h"
33 #include "common/streambuf.h"
36 #include "msp/msp_protocol.h"
37 #include "msp/msp_serial.h"
39 #include "telemetry/crsf.h"
40 #include "telemetry/msp_shared.h"
41 #include "telemetry/smartport.h"
45 ---------------------------------------------------------------
46 How MSP frames are sent over CRSF:
47 CRSF frame types: 0x7A for MSP requests, 0x7B for responses.
48 CRSF extended header frames are used. i.e., Destination and origin addresses added after CRSF type.
50 <sync/address><length><type><destination><origin><status><MSP_body><CRC>
51 Status byte consists of three parts:
52 bits 0-3 represent the sequence number of the MSP frame,
53 bit 4 checks if current MSP chunk is the beginning of a new frame (1 if true),
54 bits 5-6 represent the version number of MSP protocol (MSPv1 or MSPv2)
55 bit 7 represents Error (1 if there was an error)
56 MSP_body is unmodified MSP frame without header ($ + M|X + <|>|!) and CRC.
57 MSP might be MSPv1 or MSPv2 or MSPv1_Jumbo.
59 MSP_body might be sent in chunks.
60 First (or only) chunk must always set start bit (#4) of status byte.
61 Each next chunk must have increased sequence number in status byte.
62 Size of chunk is recovered from size of CRSF frame.
63 Although last / only CRSF frame might have size bigger than needed for MSP-body.
64 Extra bytes must be ignored. So, the real size of MSP-body must be parsed from the MSP-body itself.
65 CRSF frames might be any size until maximum of 64 bytes for a CRSF frame.
66 So, maximum chunk size is 57 bytes. Although, MSP-body might be sent in shorter chunks.
67 Although, first chunk must consist full size any type of the MSP frame.
69 MSP-CRC is not sent over CRSF due to ther is already CRC of CRSF frame.
70 So, it must be recalculated of needed for MSP-receiver.
72 MSP frame must be returned to the origin address of the request
74 ---------------------------------------------------------------
76 #define TELEMETRY_MSP_VERSION 2
77 #define TELEMETRY_MSP_RES_ERROR (-10)
79 #define TELEMETRY_REQUEST_SKIPS_AFTER_EEPROMWRITE 5
81 enum { // constants for status of msp-over-telemetry frame
82 MSP_STATUS_SEQUENCE_MASK
= 0x0f, // 0b00001111, // sequence number mask
83 MSP_STATUS_START_MASK
= 0x10, // 0b00010000, // bit of starting frame (if 1, the frame is a first/single chunk of msp-frame)
84 MSP_STATUS_VERSION_MASK
= 0x60, // 0b01100000, // MSP version mask
85 MSP_STATUS_ERROR_MASK
= 0x80, // 0b10000000, // Error bit (1 if error)
86 MSP_STATUS_VERSION_SHIFT
= 5, // MSP version shift
89 enum { // error codes (they are not sent anywhere)
90 TELEMETRY_MSP_VER_MISMATCH
,
91 TELEMETRY_MSP_CRC_ERROR
,
93 TELEMETRY_MSP_REQUEST_IS_TOO_BIG
,
96 enum { // minimum length for a frame.
97 MIN_LENGTH_CHUNK
= 2, // status + at_least_one_byte
98 MIN_LENGTH_REQUEST_V1
= 3, // status + length + ID
99 MIN_LENGTH_REQUEST_JUMBO
= 5, // status + length=FF + ID + length_lo + length_hi
100 MIN_LENGTH_REQUEST_V2
= 6, // status + flag + ID_lo + ID_hi + size_lo + size_hi
103 enum { // byte position(index) in msp-over-telemetry request payload
105 MSP_INDEX_STATUS
= 0, // status byte
106 MSP_INDEX_SIZE_V1
= MSP_INDEX_STATUS
+ 1, // MSPv1 payload size
107 MSP_INDEX_ID_V1
= MSP_INDEX_SIZE_V1
+ 1, // MSPv1 ID/command/function byte
108 MSP_INDEX_PAYLOAD_V1
= MSP_INDEX_ID_V1
+ 1, // MSPv1 Payload start / CRC for zero payload
111 MSP_INDEX_SIZE_JUMBO_LO
= MSP_INDEX_PAYLOAD_V1
, // MSPv1_Jumbo Lo byte of payload size
112 MSP_INDEX_SIZE_JUMBO_HI
= MSP_INDEX_SIZE_JUMBO_LO
+ 1, // MSPv1_Jumbo Hi byte of payload size
113 MSP_INDEX_PAYLOAD_JUMBO
= MSP_INDEX_SIZE_JUMBO_HI
+ 1, // MSPv1_Jumbo first byte of payload itself
116 MSP_INDEX_FLAG_V2
= MSP_INDEX_SIZE_V1
, // MSPv2 flags byte
117 MSP_INDEX_ID_LO
= MSP_INDEX_ID_V1
, // MSPv2 Lo byte of ID/command/function
118 MSP_INDEX_ID_HI
= MSP_INDEX_ID_LO
+ 1, // MSPv2 Hi byte of ID/command/function
119 MSP_INDEX_SIZE_V2_LO
= MSP_INDEX_ID_HI
+ 1, // MSPv2 Lo byte of payload size
120 MSP_INDEX_SIZE_V2_HI
= MSP_INDEX_SIZE_V2_LO
+ 1, // MSPv2 Hi byte of payload size
121 MSP_INDEX_PAYLOAD_V2
= MSP_INDEX_SIZE_V2_HI
+ 1, // MSPv2 first byte of payload itself
124 STATIC_UNIT_TESTED
uint8_t requestBuffer
[MSP_TLM_INBUF_SIZE
];
125 STATIC_UNIT_TESTED
uint8_t responseBuffer
[MSP_TLM_OUTBUF_SIZE
];
126 STATIC_UNIT_TESTED mspPacket_t requestPacket
;
127 STATIC_UNIT_TESTED mspPacket_t responsePacket
;
128 static uint8_t lastRequestVersion
; // MSP version of last request. Temporary solution. It's better to keep it in requestPacket.
130 static mspDescriptor_t mspSharedDescriptor
;
132 void initSharedMsp(void)
134 responsePacket
.buf
.ptr
= responseBuffer
;
135 responsePacket
.buf
.end
= ARRAYEND(responseBuffer
);
137 mspSharedDescriptor
= mspDescriptorAlloc();
140 static void processMspPacket(void)
142 responsePacket
.cmd
= 0;
143 responsePacket
.result
= 0;
144 responsePacket
.buf
.ptr
= responseBuffer
;
145 responsePacket
.buf
.end
= ARRAYEND(responseBuffer
);
147 mspPostProcessFnPtr mspPostProcessFn
= NULL
;
148 if (mspFcProcessCommand(mspSharedDescriptor
, &requestPacket
, &responsePacket
, &mspPostProcessFn
) == MSP_RESULT_ERROR
) {
149 sbufWriteU8(&responsePacket
.buf
, TELEMETRY_MSP_ERROR
);
151 if (mspPostProcessFn
) {
152 mspPostProcessFn(NULL
);
155 sbufSwitchToReader(&responsePacket
.buf
, responseBuffer
);
158 void sendMspErrorResponse(uint8_t error
, int16_t cmd
)
160 responsePacket
.cmd
= cmd
;
161 responsePacket
.result
= 0;
162 responsePacket
.buf
.ptr
= responseBuffer
;
164 sbufWriteU8(&responsePacket
.buf
, error
);
165 responsePacket
.result
= TELEMETRY_MSP_RES_ERROR
;
166 sbufSwitchToReader(&responsePacket
.buf
, responseBuffer
);
169 // despite its name, the function actually handles telemetry frame payload with MSP in it
170 // it reads the MSP into requestPacket stucture and handles it after receiving all the chunks.
171 bool handleMspFrame(uint8_t *const payload
, uint8_t const payloadLength
, uint8_t *const skipsBeforeResponse
)
173 if (payloadLength
< MIN_LENGTH_CHUNK
) {
174 return false; // prevent analyzing garbage data
177 static uint8_t mspStarted
= 0;
178 static uint8_t lastSeq
= 0;
182 const uint8_t status
= payload
[MSP_INDEX_STATUS
];
183 const uint8_t seqNumber
= status
& MSP_STATUS_SEQUENCE_MASK
;
184 lastRequestVersion
= (status
& MSP_STATUS_VERSION_MASK
) >> MSP_STATUS_VERSION_SHIFT
;
186 if (lastRequestVersion
> TELEMETRY_MSP_VERSION
) {
187 sendMspErrorResponse(TELEMETRY_MSP_VER_MISMATCH
, 0);
191 if (status
& MSP_STATUS_START_MASK
) { // first packet in sequence
192 uint16_t mspPayloadSize
;
193 if (lastRequestVersion
== 1) { // MSPv1
194 if (payloadLength
< MIN_LENGTH_REQUEST_V1
) {
195 return false; // prevent analyzing garbage data
198 mspPayloadSize
= payload
[MSP_INDEX_SIZE_V1
];
199 requestPacket
.cmd
= payload
[MSP_INDEX_ID_V1
];
200 if (mspPayloadSize
== 0xff) { // jumbo frame
201 if (payloadLength
< MIN_LENGTH_REQUEST_JUMBO
) {
202 return false; // prevent analyzing garbage data
204 mspPayloadSize
= *(uint16_t*)&payload
[MSP_INDEX_SIZE_JUMBO_LO
];
205 sbufInit(&sbufInput
, payload
+ MSP_INDEX_PAYLOAD_JUMBO
, payload
+ payloadLength
);
207 sbufInit(&sbufInput
, payload
+ MSP_INDEX_PAYLOAD_V1
, payload
+ payloadLength
);
210 if (payloadLength
< MIN_LENGTH_REQUEST_V2
) {
211 return false; // prevent analyzing garbage data
213 requestPacket
.flags
= payload
[MSP_INDEX_FLAG_V2
];
214 requestPacket
.cmd
= *(uint16_t*)&payload
[MSP_INDEX_ID_LO
];
215 mspPayloadSize
= *(uint16_t*)&payload
[MSP_INDEX_SIZE_V2_LO
];
216 sbufInit(&sbufInput
, payload
+ MSP_INDEX_PAYLOAD_V2
, payload
+ payloadLength
);
218 if (mspPayloadSize
<= sizeof(requestBuffer
)) { // prevent buffer overrun
219 requestPacket
.result
= 0;
220 requestPacket
.buf
.ptr
= requestBuffer
;
221 requestPacket
.buf
.end
= requestBuffer
+ mspPayloadSize
;
223 } else { // this MSP packet is too big to fit in the buffer.
224 sendMspErrorResponse(TELEMETRY_MSP_REQUEST_IS_TOO_BIG
, requestPacket
.cmd
);
227 } else { // second onward chunk
228 if (!mspStarted
) { // no start packet yet, throw this one away
231 if (((lastSeq
+ 1) & MSP_STATUS_SEQUENCE_MASK
) != seqNumber
) {
232 // packet loss detected!
237 sbufInit(&sbufInput
, payload
+ 1, payload
+ payloadLength
);
242 const int payloadExpecting
= sbufBytesRemaining(&requestPacket
.buf
);
243 const int payloadIncoming
= sbufBytesRemaining(&sbufInput
);
245 if (payloadExpecting
> payloadIncoming
) {
246 sbufWriteData(&requestPacket
.buf
, sbufInput
.ptr
, payloadIncoming
);
247 sbufAdvance(&sbufInput
, payloadIncoming
);
249 } else { // this is the last/only chunk
250 if (payloadExpecting
) {
251 sbufWriteData(&requestPacket
.buf
, sbufInput
.ptr
, payloadExpecting
);
252 sbufAdvance(&sbufInput
, payloadExpecting
);
256 // Skip a few telemetry requests if command is MSP_EEPROM_WRITE
257 if (requestPacket
.cmd
== MSP_EEPROM_WRITE
&& skipsBeforeResponse
) {
258 *skipsBeforeResponse
= TELEMETRY_REQUEST_SKIPS_AFTER_EEPROMWRITE
;
262 sbufSwitchToReader(&requestPacket
.buf
, requestBuffer
);
267 bool sendMspReply(const uint8_t payloadSizeMax
, mspResponseFnPtr responseFn
)
269 static uint8_t seq
= 0;
271 uint8_t payloadArray
[payloadSizeMax
];
272 sbuf_t payloadBufStruct
;
273 sbuf_t
*payloadBuf
= sbufInit(&payloadBufStruct
, payloadArray
, payloadArray
+ payloadSizeMax
);
275 // detect first reply packet
276 if (responsePacket
.buf
.ptr
== responseBuffer
) {
277 // this is the first frame of the response packet. Add proper header and size.
279 uint8_t status
= MSP_STATUS_START_MASK
| (seq
++ & MSP_STATUS_SEQUENCE_MASK
) | (lastRequestVersion
<< MSP_STATUS_VERSION_SHIFT
);
280 if (responsePacket
.result
< 0) {
281 status
|= MSP_STATUS_ERROR_MASK
;
283 sbufWriteU8(payloadBuf
, status
);
285 const int size
= sbufBytesRemaining(&responsePacket
.buf
); // size might be bigger than 0xff
286 if (lastRequestVersion
== 1) { // MSPv1
288 // Sending Jumbo-frame
289 sbufWriteU8(payloadBuf
, 0xff);
290 sbufWriteU8(payloadBuf
, responsePacket
.cmd
);
291 sbufWriteU16(payloadBuf
, (uint16_t)size
);
293 sbufWriteU8(payloadBuf
, size
);
294 sbufWriteU8(payloadBuf
, responsePacket
.cmd
);
297 sbufWriteU8 (payloadBuf
, responsePacket
.flags
); // MSPv2 flags
298 sbufWriteU16(payloadBuf
, responsePacket
.cmd
); // command is 16 bit in MSPv2
299 sbufWriteU16(payloadBuf
, (uint16_t)size
); // size is 16 bit in MSPv2
302 sbufWriteU8(payloadBuf
, (seq
++ & MSP_STATUS_SEQUENCE_MASK
) | (lastRequestVersion
<< MSP_STATUS_VERSION_SHIFT
)); // header without 'start' flag
305 const int inputRemainder
= sbufBytesRemaining(&responsePacket
.buf
);// size might be bigger than 0xff
306 const int chunkRemainder
= sbufBytesRemaining(payloadBuf
); // free space remainder for current chunk
308 if (inputRemainder
>= chunkRemainder
) {
310 sbufWriteData(payloadBuf
, responsePacket
.buf
.ptr
, chunkRemainder
);
311 sbufAdvance(&responsePacket
.buf
, chunkRemainder
);
312 responseFn(payloadArray
, payloadSizeMax
);
316 sbufWriteData(payloadBuf
, responsePacket
.buf
.ptr
, inputRemainder
);
317 sbufAdvance(&responsePacket
.buf
, inputRemainder
);
318 sbufSwitchToReader(&responsePacket
.buf
, responseBuffer
);// for CRC calculation
320 responseFn(payloadArray
, payloadBuf
->ptr
- payloadArray
);