protocol.c (3455B)
1 /* 2 * (c) 2021 by Tomasz bla Fortuna 3 * License: GPLv2 4 * 5 * This file is shared with the Shine firmware. Keep it in sync (and in the 6 * shine's clang formatting). 7 * 8 * Implementation of a robust serial protocol which can handle single dropped 9 * characters during transit without locking. 10 * 11 * At 115200, transmitting the shortest message takes 0.043ms, at 9600 - 0.52ms. 12 * 13 */ 14 15 #include "protocol.h" 16 #include "board.h" 17 #include "ch.h" 18 #include "hal.h" 19 20 /* UART communication protocol state */ 21 protocol_t proto; 22 23 void proto_init(protocol_t *proto, void (*callback)(const message_t *)) { 24 proto->previous_id = 0; 25 proto->callback = callback; 26 proto->state = STATE_SYNC_1; 27 proto->errors = 0; 28 } 29 30 static uint8_t msg_id = 0; 31 void proto_tx(uint8_t cmd, const unsigned char *buf, int payload_size, int retries) { 32 chDbgCheck(payload_size <= MAX_PAYLOAD_SIZE); 33 34 const uint8_t header[5] = { 35 0x7A, 0x1D, cmd, ++msg_id, payload_size, 36 }; 37 38 /* We don't implement ACKs, yet some messages should not be lost. */ 39 for (int i = 0; i < retries; i++) { 40 sdWrite(&PROTOCOL_SD, header, sizeof(header)); 41 if (payload_size) sdWrite(&PROTOCOL_SD, buf, payload_size); 42 } 43 } 44 45 static inline void messageReceived(protocol_t *proto) { 46 if (proto->buffer.msg_id != proto->previous_id) { 47 /* It's not a resend / duplicate */ 48 proto->callback(&proto->buffer); 49 proto->previous_id = proto->buffer.msg_id; 50 } 51 proto->state = STATE_SYNC_1; 52 } 53 54 void proto_consume(protocol_t *proto, uint8_t byte) { 55 switch (proto->state) { 56 case STATE_SYNC_1: 57 if (byte == 0x7A) { 58 proto->state = STATE_SYNC_2; 59 } else { 60 proto->errors++; 61 } 62 return; 63 64 case STATE_SYNC_2: 65 if (byte == 0x1D) { 66 proto->state = STATE_CMD; 67 } else { 68 proto->state = STATE_SYNC_1; 69 proto->errors++; 70 } 71 return; 72 73 case STATE_CMD: 74 proto->buffer.command = byte; 75 proto->state = STATE_ID; 76 return; 77 78 case STATE_ID: 79 proto->buffer.msg_id = byte; 80 proto->state = STATE_PAYLOAD_SIZE; 81 return; 82 83 case STATE_PAYLOAD_SIZE: 84 proto->buffer.payload_size = byte; 85 if (proto->buffer.payload_size > MAX_PAYLOAD_SIZE) { 86 proto->buffer.payload_size = MAX_PAYLOAD_SIZE; 87 proto->errors++; 88 } 89 proto->payload_position = 0; 90 if (proto->buffer.payload_size == 0) { 91 /* No payload - whole message received */ 92 messageReceived(proto); 93 } else { 94 proto->state = STATE_PAYLOAD; 95 } 96 return; 97 98 case STATE_PAYLOAD: 99 /* NOTE: This could be read with sdReadTimeout probably, but that breaks 100 * abstraction */ 101 proto->buffer.payload[proto->payload_position] = byte; 102 proto->payload_position++; 103 if (proto->payload_position == proto->buffer.payload_size) { 104 /* Payload read - message received */ 105 messageReceived(proto); 106 } 107 return; 108 } 109 } 110 111 void proto_silence(protocol_t *proto) { 112 if (proto->state != STATE_SYNC_1) { 113 proto->state = STATE_SYNC_1; 114 proto->errors++; 115 } 116 }