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