API
zb_serial.c
Go to the documentation of this file.
1 /*
2  * \file zb_serial.c
3  * \author Eric Dand
4  * \version 1.0
5  * \date 28 November 2014
6  * \copyright Apache Software License Version 2.0
7  *
8  * Implementation file for binary portion of the Zaber Serial API in C.
9  * See zb_serial.h for documentation.
10  */
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdint.h>
14 #include <limits.h>
15 
16 #include "zb_serial.h"
17 
18 static int zb_verbose = 1;
19 
21 {
22  zb_verbose = value;
23 }
24 
25 int zb_encode(uint8_t *destination, uint8_t device_number,
26  uint8_t command_number, int32_t data)
27 {
28  unsigned int i;
29  uint32_t udata = (uint32_t)data;
30 
31  if (destination == NULL)
32  {
34  }
35 
36  destination[0] = device_number;
37  destination[1] = command_number;
38 
39  for (i = 2; i < 6; i++)
40  {
41  destination[i] = (uint8_t)udata;
42  udata >>= 8;
43  }
44 
45  return Z_SUCCESS;
46 }
47 
48 int zb_decode(int32_t *destination, const uint8_t *reply)
49 {
50  unsigned int i;
51  uint32_t data = 0;
52 
53  if (destination == NULL)
54  {
56  }
57 
58  for (i = 5; i > 1; i--)
59  {
60  data <<= 8;
61  data |= reply[i];
62  }
63 
64  if ((data & 0x80000000UL) == 0)
65  {
66  *destination = (int32_t)data;
67  }
68  else
69  {
70  *destination = -(int32_t)(UINT32_MAX - data) - 1;
71  }
72 
73  return Z_SUCCESS;
74 }
75 
76 #if defined(_WIN32)
77 
78 /* These macros save us a lot of repetition. Call the specified function,
79  * and complain and return early with -1 if things go badly. */
80 #if defined(NDEBUG)
81 #define PRINT_ERROR(M)
82 #define PRINT_SYSCALL_ERROR(M)
83 #else
84 #define PRINT_ERROR(M) do { if (zb_verbose) { fprintf(stderr, "(%s: %d)" M\
85  "\n", __FILE__, __LINE__); } } while(0)
86 #define PRINT_SYSCALL_ERROR(M) do { if (zb_verbose) { fprintf(stderr,\
87  "(%s: %d) [ERROR] " M " failed with error code %d.\n",\
88  __FILE__, __LINE__, GetLastError()); } } while(0)
89 #endif
90 #define SYSCALL(F) do { if ((F) == 0) {\
91  PRINT_SYSCALL_ERROR(#F); return Z_ERROR_SYSTEM_ERROR; } } while (0)
92 
93 int zb_connect(z_port *port, const char *port_name)
94 {
95  DCB dcb = { 0 };
96  COMMTIMEOUTS timeouts;
97 
98  if (port_name == NULL)
99  {
100  PRINT_ERROR("[ERROR] port name cannot be NULL.");
101  return Z_ERROR_NULL_PARAMETER;
102  }
103 
104  *port = CreateFileA(port_name,
105  GENERIC_READ | GENERIC_WRITE,
106  0,
107  NULL,
108  OPEN_EXISTING,
109  0,
110  NULL);
111  if (*port == INVALID_HANDLE_VALUE)
112  {
113  PRINT_SYSCALL_ERROR("CreateFileA");
114  return Z_ERROR_NULL_PARAMETER;
115  }
116 
117  SYSCALL(GetCommState(*port, &dcb));
118  dcb.DCBlength = sizeof(DCB);
119  dcb.BaudRate = 9600;
120  dcb.fBinary = TRUE; /* Binary Mode (skip EOF check) */
121  dcb.fParity = FALSE; /* Disable parity checking */
122  dcb.fOutxCtsFlow = FALSE; /* No CTS handshaking on output */
123  dcb.fOutxDsrFlow = FALSE; /* No DSR handshaking on output */
124  dcb.fDtrControl = DTR_CONTROL_DISABLE; /* Disable DTR Flow control */
125  dcb.fDsrSensitivity = FALSE; /* No DSR Sensitivity */
126  dcb.fTXContinueOnXoff = TRUE; /* Continue TX when Xoff sent */
127  dcb.fOutX = FALSE; /* Disable output X-ON/X-OFF */
128  dcb.fInX = FALSE; /* Disable input X-ON/X-OFF */
129  dcb.fErrorChar = FALSE; /* Disable Err Replacement */
130  dcb.fNull = FALSE; /* Disable Null stripping */
131  dcb.fRtsControl = RTS_CONTROL_DISABLE; /* Disable Rts Flow control */
132  dcb.fAbortOnError = FALSE; /* Do not abort all reads and writes on Error */
133  dcb.wReserved = 0; /* Not currently used, but must be set to 0 */
134  dcb.XonLim = 0; /* Transmit X-ON threshold */
135  dcb.XoffLim = 0; /* Transmit X-OFF threshold */
136  dcb.ByteSize = 8; /* Number of bits/byte, 4-8 */
137  dcb.Parity = NOPARITY; /* 0-4=None,Odd,Even,Mark,Space */
138  dcb.StopBits = ONESTOPBIT; /* 0,1,2 = 1, 1.5, 2 */
139  SYSCALL(SetCommState(*port, &dcb));
140 
141  timeouts.ReadIntervalTimeout = MAXDWORD;
142  timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
143  timeouts.ReadTotalTimeoutConstant = READ_TIMEOUT; /* #defined in header */
144  timeouts.WriteTotalTimeoutMultiplier = 0;
145  timeouts.WriteTotalTimeoutConstant = 100;
146  SYSCALL(SetCommTimeouts(*port, &timeouts));
147 
148  return Z_SUCCESS;
149 }
150 
151 int zb_disconnect(z_port port)
152 {
153  SYSCALL(CloseHandle(port));
154  return Z_SUCCESS;
155 }
156 
157 int zb_send(z_port port, const uint8_t *command)
158 {
159  DWORD nbytes;
160 
161  if (command == NULL)
162  {
163  PRINT_ERROR("[ERROR] command cannot be NULL.");
164  return Z_ERROR_NULL_PARAMETER;
165  }
166 
167  SYSCALL(WriteFile(port, command, 6, &nbytes, NULL));
168  if (nbytes == 6)
169  {
170  return (int) nbytes;
171  }
172  return Z_ERROR_SYSTEM_ERROR;
173 }
174 
175 /* We read bytes one-at-a-time as ReadFile(port, destination, 6, &nread, NULL)
176  * often enough will "read" bytes of \0 in the middle of a message when it
177  * should instead be waiting for a real byte of data down the line.
178  * Worse, it reports afterwards that it has read a full 6 bytes, making this
179  * behaviour hard to track and harder to debug and compensate for. */
180 int zb_receive(z_port port, uint8_t *destination)
181 {
182  DWORD nread;
183  int i;
184  char c;
185 
186  for (i = 0; i < 6; i++)
187  {
188  SYSCALL(ReadFile(port, &c, 1, &nread, NULL));
189 
190  if (nread == 0) /* timed out */
191  {
192  PRINT_ERROR("[INFO] Read timed out.");
193  break;
194  }
195 
196  if (destination != NULL) destination[i] = c;
197  }
198 
199  if (i == 6)
200  {
201  return i;
202  }
203  /* if we didn't read a whole 6 bytes, we count that as an error. */
204  return Z_ERROR_SYSTEM_ERROR;
205 }
206 
207 int zb_drain(z_port port)
208 {
209  char c;
210  DWORD nread,
211  old_timeout;
212  COMMTIMEOUTS timeouts;
213 
214  SYSCALL(PurgeComm(port, PURGE_RXCLEAR));
215  SYSCALL(GetCommTimeouts(port, &timeouts));
216  old_timeout = timeouts.ReadTotalTimeoutConstant;
217  timeouts.ReadTotalTimeoutConstant = 100;
218  SYSCALL(SetCommTimeouts(port, &timeouts));
219 
220  do
221  {
222  SYSCALL(ReadFile(port, &c, 1, &nread, NULL));
223  }
224  while (nread == 1);
225 
226  timeouts.ReadTotalTimeoutConstant = old_timeout;
227  SYSCALL(SetCommTimeouts(port, &timeouts));
228 
229  return Z_SUCCESS;
230 }
231 
232 int zb_set_timeout(z_port port, int milliseconds)
233 {
234  COMMTIMEOUTS timeouts;
235 
236  SYSCALL(GetCommTimeouts(port, &timeouts));
237  timeouts.ReadTotalTimeoutConstant = milliseconds;
238  SYSCALL(SetCommTimeouts(port, &timeouts));
239 
240  return milliseconds;
241 }
242 
243 #elif defined(__unix__) || defined(__APPLE__) /* end of if defined(_WIN32) */
244 #include <fcntl.h>
245 #include <sys/types.h>
246 #include <sys/stat.h>
247 #include <termios.h>
248 #include <unistd.h>
249 
250 /* A little sugar for checking return values from system calls.
251  * I would have liked to use GNU/GCC's "statement expressions" so that one
252  * do something like "z_port port = SYSCALL(open([parameters]))", but they're
253  * a GNU extension, and therefore unfriendly to non-GCC compilers.
254  * Workaround to avoid dependence on statement expressions for SYSCALL macro:
255  * use SYSCALL on result of assignment instead. */
256 #if defined(NDEBUG)
257 #define PRINT_ERROR(M)
258 #define PRINT_SYSCALL_ERROR(M)
259 #else
260 #include <errno.h>
261 #define PRINT_ERROR(M) do { if (zb_verbose) { fprintf(stderr, "(%s: %d)" M\
262  "\n", __FILE__, __LINE__); } } while(0)
263 #define PRINT_SYSCALL_ERROR(M) do { if (zb_verbose) {\
264  fprintf(stderr, "(%s: %d) [ERROR] " M " failed: %s.\n",\
265  __FILE__, __LINE__, strerror(errno)); } } while(0)
266 #endif
267 /* A little sugar for checking return values from system calls.
268  * I would have liked to use GNU/GCC's "statement expressions" so that one
269  * do something like "z_port port = SYSCALL(open([parameters]))", but they're
270  * a GNU extension, and therefore unfriendly to non-GCC compilers.
271  * Workaround to avoid dependence on statement expressions for SYSCALL macro:
272  * use SYSCALL on result of assignment instead. */
273 #define SYSCALL(F) do { if ((F) < 0) { PRINT_SYSCALL_ERROR(#F);\
274  return Z_ERROR_SYSTEM_ERROR; } } while(0)
275 
276 int zb_connect(z_port *port, const char *port_name)
277 {
278  struct termios tio, orig_tio;
279 
280  if (port == NULL || port_name == NULL)
281  {
282  PRINT_ERROR("[ERROR] port and port_name cannot be NULL.");
283  return Z_ERROR_NULL_PARAMETER;
284  }
285 
286  /* blocking read/write */
287  SYSCALL(*port = open(port_name, O_RDWR | O_NOCTTY));
288  SYSCALL(tcgetattr(*port, &orig_tio));
289  memcpy(&tio, &orig_tio, sizeof(struct termios)); /* copy padding too */
290 
291  /* cfmakeraw() without cfmakeraw() for cygwin compatibility */
292  tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
293  | IGNCR | ICRNL | IXON);
294  tio.c_oflag &= ~OPOST;
295  tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
296  tio.c_cflag &= ~(CSIZE | PARENB);
297  /* end cfmakeraw() */
298  tio.c_cflag = CS8|CREAD|CLOCAL;
299 
300  /* READ_TIMEOUT is defined in zb_serial.h */
301  if (READ_TIMEOUT % 100 != 0)
302  {
303  tio.c_cc[VTIME] = READ_TIMEOUT / 100 + 1; /* Round up */
304  }
305  else
306  {
307  tio.c_cc[VTIME] = READ_TIMEOUT / 100;
308  }
309  tio.c_cc[VMIN] = 0;
310 
311  SYSCALL(cfsetospeed(&tio, B9600) & cfsetispeed(&tio, B9600));
312 
313  while(memcmp(&orig_tio, &tio, sizeof(struct termios)) != 0)
314  { /* memcmp is only OK here because we used memcpy above */
315  SYSCALL(tcsetattr(*port, TCSAFLUSH, &tio));
316  SYSCALL(tcgetattr(*port, &orig_tio));
317  }
318 
319  return Z_SUCCESS;
320 }
321 
322 int zb_disconnect(z_port port)
323 {
324  SYSCALL(close(port));
325  return Z_SUCCESS;
326 }
327 
328 int zb_send(z_port port, const uint8_t *command)
329 {
330  int nbytes;
331 
332  SYSCALL(nbytes = write(port, command, 6));
333  if (nbytes == 6)
334  {
335  return nbytes;
336  }
337  return Z_ERROR_SYSTEM_ERROR;
338 }
339 
340 /* More struggles with termios: we're forced to read one byte at a time on
341  * *NIX because of how termios.c_cc[VTIME] and [VMIN] work. From the termios
342  * man page:
343  *
344  * * if MIN == 0; TIME > 0: "read(2) returns either when at least one
345  * byte of data is available, or when the timer expires."
346  * * if MIN > 0; TIME > 0: "Because the timer is started only after the
347  * initial byte becomes available, at least one byte will be read."
348  *
349  * Neither of these cases are what we want, namely to start the timer
350  * immediately, and to only return when all 6 requested/MIN bytes are received
351  * or the timer times out. As a result, we instead set MIN to 0 (the first
352  * case above), then read 1 byte at a time to get the behaviour we want. */
353 int zb_receive(z_port port, uint8_t *destination)
354 {
355  int nread,
356  i;
357  char c;
358 
359  for (i = 0; i < 6; i++)
360  {
361  SYSCALL(nread = (int) read(port, &c, 1));
362 
363  if (nread == 0) /* timed out */
364  {
365  PRINT_ERROR("[INFO] Read timed out.");
366  break;
367  }
368 
369  if (destination != NULL) destination[i] = c;
370  }
371 
372  if (i == 6)
373  {
374  return i;
375  }
376  /* if we didn't read a whole 6 bytes, we count that as an error. */
377  return Z_ERROR_SYSTEM_ERROR;
378 }
379 
380 int zb_drain(z_port port)
381 {
382  struct termios tio;
383  int old_timeout;
384  char c;
385 
386  /* set timeout to 0.1s */
387  SYSCALL(tcgetattr(port, &tio));
388  old_timeout = tio.c_cc[VTIME];
389  tio.c_cc[VTIME] = 1;
390  SYSCALL(tcsetattr(port, TCSANOW, &tio));
391 
392  /* flush and read whatever else comes in */
393  SYSCALL(tcflush(port, TCIFLUSH));
394  while(read(port, &c, 1) > 0);
395 
396  /* set timeout back to what it was */
397  tio.c_cc[VTIME] = old_timeout;
398  SYSCALL(tcsetattr(port, TCSANOW, &tio));
399 
400  return Z_SUCCESS;
401 }
402 
403 int zb_set_timeout(z_port port, int milliseconds)
404 {
405  struct termios tio;
406  int new_time;
407 
408  if (milliseconds % 100 != 0)
409  {
410  new_time = milliseconds / 100 + 1;
411  }
412  else
413  {
414  new_time = milliseconds / 100; /* VTIME is in increments of 0.1s */
415  }
416 
417  SYSCALL(tcgetattr(port, &tio));
418  tio.c_cc[VTIME] = new_time;
419  SYSCALL(tcsetattr(port, TCSANOW, &tio));
420 
421  return new_time * 100;
422 }
423 
424 #endif
425 
GeneratorWrapper< T > value(T &&value)
Definition: catch.hpp:4001
#define READ_TIMEOUT
Definition: z_common.h:53
@ Z_ERROR_SYSTEM_ERROR
Definition: z_common.h:67
@ Z_ERROR_NULL_PARAMETER
Definition: z_common.h:71
@ Z_SUCCESS
Definition: z_common.h:65
#define SYSCALL(F)
Definition: za_serial.c:50
#define PRINT_ERROR(M)
Definition: za_serial.c:35
#define PRINT_SYSCALL_ERROR(M)
Definition: za_serial.c:40
static int zb_verbose
Definition: zb_serial.c:18
int zb_decode(int32_t *destination, const uint8_t *reply)
Definition: zb_serial.c:48
int zb_encode(uint8_t *destination, uint8_t device_number, uint8_t command_number, int32_t data)
Definition: zb_serial.c:25
void zb_set_verbose(int value)
Definition: zb_serial.c:20
Provides a set of functions for interacting with Zaber devices in the binary protocol.
int zb_disconnect(z_port port)
int zb_send(z_port port, const uint8_t *command)
int zb_receive(z_port port, uint8_t *destination)
int zb_drain(z_port port)
int zb_set_timeout(z_port port, int milliseconds)
int zb_connect(z_port *port, const char *port_name)