Async Comm
A library for asynchronous serial communication
serial_protocol.cpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD-3 License)
3  *
4  * Copyright (c) 2018 Daniel Koch.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * * Redistributions of source code must retain the above copyright notice, this
11  * list of conditions and the following disclaimer.
12  *
13  * * Redistributions in binary form must reproduce the above copyright notice,
14  * this list of conditions and the following disclaimer in the documentation
15  * and/or other materials provided with the distribution.
16  *
17  * * Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived from
19  * this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
56 #include <async_comm/serial.h>
57 
58 #include <cstdint>
59 #include <cstdio>
60 
61 #include <chrono>
62 #include <condition_variable>
63 #include <mutex>
64 
65 // specify the number of messages to send
66 #define NUM_MSGS 40000
67 
68 // define attributes of the serial protocol
69 #define START_BYTE 0xA5
70 
71 #define START_BYTE_LEN 1
72 #define PAYLOAD_LEN 12
73 #define CRC_LEN 1
74 #define PACKET_LEN (START_BYTE_LEN + PAYLOAD_LEN + CRC_LEN)
75 
76 // specify relevant serial port options
77 #define BAUD_RATE 921600
78 #define NUM_START_BITS 1
79 #define NUM_STOP_BITS 1
80 
81 
93 uint8_t update_crc(uint8_t inCrc, uint8_t inData)
94 {
95  uint8_t i;
96  uint8_t data;
97 
98  data = inCrc ^ inData;
99 
100  for ( i = 0; i < 8; i++ )
101  {
102  if (( data & 0x80 ) != 0 )
103  {
104  data <<= 1;
105  data ^= 0x07;
106  }
107  else
108  {
109  data <<= 1;
110  }
111  }
112  return data;
113 }
114 
115 
125 void pack_message(uint8_t* dst, uint32_t id, uint32_t v1, uint32_t v2)
126 {
127  dst[0] = START_BYTE;
128  memcpy(dst+1, &id, 4);
129  memcpy(dst+5, &v1, 4);
130  memcpy(dst+9, &v2, 4);
131 
132  uint8_t crc = 0;
133  for (size_t i = 0; i < PACKET_LEN-1; i++)
134  {
135  crc = update_crc(crc, dst[i]);
136  }
137  dst[PACKET_LEN-1] = crc;
138 }
139 
140 
151 void unpack_payload(uint8_t* src, uint32_t *id, uint32_t *v1, uint32_t *v2)
152 {
153  memcpy(id, src, 4);
154  memcpy(v1, src+4, 4);
155  memcpy(v2, src+8, 4);
156 }
157 
158 
163 {
164  PARSE_STATE_IDLE,
165  PARSE_STATE_GOT_START_BYTE,
166  PARSE_STATE_GOT_PAYLOAD
167 };
168 
169 ParseState parse_state = PARSE_STATE_IDLE;
170 uint8_t receive_buffer[PAYLOAD_LEN];
171 
172 volatile int receive_count = 0;
173 bool received[NUM_MSGS];
174 
177 volatile bool all_messages_received = false;
178 
179 
184 void parse_byte(uint8_t byte)
185 {
186  static size_t payload_count;
187  static uint8_t crc;
188 
189  switch (parse_state)
190  {
191  case PARSE_STATE_IDLE:
192  if (byte == START_BYTE)
193  {
194  payload_count = 0;
195  crc = 0;
196  crc = update_crc(crc, byte);
197 
198  parse_state = PARSE_STATE_GOT_START_BYTE;
199  }
200  break;
201  case PARSE_STATE_GOT_START_BYTE:
202  receive_buffer[payload_count] = byte;
203  crc = update_crc(crc, byte);
204  if (++payload_count >= PAYLOAD_LEN)
205  {
206  parse_state = PARSE_STATE_GOT_PAYLOAD;
207  }
208  break;
209  case PARSE_STATE_GOT_PAYLOAD:
210  if (byte == crc)
211  {
212  uint32_t id, v1, v2;
213  unpack_payload(receive_buffer, &id, &v1, &v2);
214  received[id] = true;
215  receive_count++;
216 
217  // notify the main thread when all messages have been received
218  if (receive_count >= NUM_MSGS)
219  {
220  {
221  std::unique_lock<std::mutex> lock(mutex);
222  all_messages_received = true;
223  }
224  condition_variable.notify_one();
225  }
226  } // otherwise ignore it
227  parse_state = PARSE_STATE_IDLE;
228  break;
229  }
230 }
231 
232 
241 void callback(const uint8_t* buf, size_t len)
242 {
243  for (size_t i = 0; i < len; i++)
244  {
245  parse_byte(buf[i]);
246  }
247 }
248 
249 
250 int main(int argc, char** argv)
251 {
252  // initialize
253  char* port;
254  if (argc < 2)
255  {
256  std::printf("USAGE: %s PORT\n", argv[0]);
257  return 1;
258  }
259  else
260  {
261  std::printf("Using port %s\n", argv[1]);
262  port = argv[1];
263  }
264 
265  // open serial port
266  async_comm::Serial serial(port, BAUD_RATE);
268 
269  if (!serial.init())
270  {
271  std::printf("Failed to initialize serial port\n");
272  return 2;
273  }
274 
275  // initialize variable for tracking which messages we've received
276  memset(received, 0, sizeof(received));
277 
278  auto start = std::chrono::high_resolution_clock::now();
279 
280  // pack and send the specified number of messages with unique IDs and data
281  uint8_t buffer[PACKET_LEN];
282  for (uint32_t i = 0; i < NUM_MSGS; i++)
283  {
284  pack_message(buffer, i, i*2, i*4);
285  serial.send_bytes(buffer, PACKET_LEN);
286  }
287  auto finish_write = std::chrono::high_resolution_clock::now();
288 
289  // wait to receive all messages
290  {
291  std::unique_lock<std::mutex> lock(mutex);
292  condition_variable.wait(lock, []{ return all_messages_received; });
293  }
294 
295  auto finish_read = std::chrono::high_resolution_clock::now();
296 
297  // close serial port
298  serial.close();
299 
300  // did we get all the messages back?
301  int num_received = 0;
302  for (int i = 0; i < NUM_MSGS; i++)
303  {
304  if (received[i])
305  {
306  num_received++;
307  }
308  else
309  {
310  std::printf("Missing message %d\n", i);
311  }
312  }
313 
314  // evaluate and print performance
315  std::chrono::duration<double, std::milli> write_time = finish_write - start;
316  std::chrono::duration<double, std::milli> read_time = finish_read - start;
317 
318  std::printf("Received %d of %d messages\n", num_received, NUM_MSGS);
319  std::printf("Elapsed write time: %fms\n", write_time.count());
320  std::printf("Elapsed read time: %fms\n", read_time.count());
321 
322  int num_bytes = NUM_MSGS * PACKET_LEN;
323  double expected_time = num_bytes * (8 + NUM_START_BITS + NUM_STOP_BITS) / (double) BAUD_RATE;
324  std::printf("Expected read time: %fms\n", expected_time*1e3);
325  std::printf("Total: %d bytes\n", num_bytes);
326 
327  return 0;
328 }
Asynchronous communication class for a serial port.
Definition: serial.h:56
volatile int receive_count
Keeps track of how many valid messages have been received.
ParseState parse_state
Current state of the parser state machine.
uint8_t update_crc(uint8_t inCrc, uint8_t inData)
Recursively update the cyclic redundancy check (CRC)
volatile bool all_messages_received
flag for whether all messages have been received back
void send_bytes(const uint8_t *src, size_t len)
Send bytes from a buffer over the port.
Definition: comm.cpp:97
bool init()
Initializes and opens the port.
Definition: comm.cpp:61
void pack_message(uint8_t *dst, uint32_t id, uint32_t v1, uint32_t v2)
Pack message contents into a buffer.
void register_receive_callback(std::function< void(const uint8_t *, size_t)> fun)
Register a callback function for when bytes are received on the port.
Definition: comm.cpp:110
void close()
Closes the port.
Definition: comm.cpp:74
ParseState
States for the parser state machine.
void unpack_payload(uint8_t *src, uint32_t *id, uint32_t *v1, uint32_t *v2)
Unpack the contents of a message payload buffer.
std::mutex mutex
mutex for synchronization between the main thread and callback thread
void callback(const uint8_t *buf, size_t len)
Callback function for the async_comm library.
uint8_t receive_buffer[PAYLOAD_LEN]
Buffer for accumulating received payload.
void parse_byte(uint8_t byte)
Passes a received byte through the parser state machine.
std::condition_variable condition_variable
condition variable used to suspend main thread until all messages have been received back ...
bool received[NUM_MSGS]
Keeps track of which messages we&#39;ve received back.