├── .gitignore ├── CMakeLists.txt ├── LICENSES └── MIT ├── README.md ├── angstrom.cmake ├── linux-serial-test.c └── measure-baud-rate-example.png /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | Makefile 4 | cmake_install.cmake 5 | linux-serial-test 6 | build 7 | 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | 3 | project(linux-serial-test C) 4 | cmake_minimum_required(VERSION 2.6) 5 | add_executable(linux-serial-test linux-serial-test.c) 6 | target_link_libraries(linux-serial-test rt) 7 | install(TARGETS linux-serial-test DESTINATION bin) 8 | -------------------------------------------------------------------------------- /LICENSES/MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person 2 | obtaining a copy of this software and associated documentation files 3 | (the "Software"), to deal in the Software without restriction, 4 | including without limitation the rights to use, copy, modify, merge, 5 | publish, distribute, sublicense, and/or sell copies of the Software, 6 | and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be 10 | included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 16 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linux-serial-test 2 | 3 | # Linux Serial Test Application 4 | 5 | # Compiling 6 | 7 | This is a very simple program -- there are several ways to compile it on Linux: 8 | 9 | ## directly using GCC 10 | 11 | `gcc -o linux-serial-test linux-serial-test.c` 12 | 13 | ## Using CMake 14 | 15 | - `cmake ./` 16 | - `make` 17 | 18 | # Usage 19 | 20 | ``` 21 | Usage: linux-serial-test [OPTION] 22 | 23 | -h, --help 24 | -b, --baud Baud rate, 115200, etc (115200 is default) 25 | -p, --port Port (/dev/ttyS0, etc) (must be specified) 26 | -d, --divisor UART Baud rate divisor (can be used to set custom baud rates) 27 | -R, --rx_dump Dump Rx data (ascii, raw) 28 | -T, --detailed_tx Detailed Tx data 29 | -s, --stats Dump serial port stats every 5s 30 | -S, --stop-on-err Stop program if we encounter an error 31 | -y, --single-byte Send specified byte to the serial port 32 | -z, --second-byte Send another specified byte to the serial port 33 | -c, --rts-cts Enable RTS/CTS flow control 34 | -B, --2-stop-bit Use two stop bits per character 35 | -P, --parity Use parity bit (odd, even, mark, space) 36 | -k, --loopback Use internal hardware loop back 37 | -K, --write-follow Write follows the read count (can be used for multi-serial loopback) 38 | -e, --dump-err Display errors 39 | -r, --no-rx Don't receive data (can be used to test flow control) 40 | when serial driver buffer is full 41 | -t, --no-tx Don't transmit data 42 | -l, --rx-delay Delay between reading data (ms) (can be used to test flow control) 43 | -a, --tx-delay Delay between writing data (ms) 44 | -w, --tx-bytes Number of bytes for each write (default is to repeatedly write 1024 bytes 45 | until no more are accepted) 46 | -q, --rs485 Enable RS485 direction control on port, and set delay from when TX is 47 | finished and RS485 driver enable is de-asserted. Delay is specified in 48 | bit times. To optionally specify a delay from when the driver is enabled 49 | to start of TX use 'after_delay.before_delay' (-q 1.1) 50 | -Q, --rs485_rts Deassert RTS on send, assert after send. Omitting -Q inverts this logic. 51 | -o, --tx-time Number of seconds to transmit for (defaults to 0, meaning no limit) 52 | -i, --rx-time Number of seconds to receive for (defaults to 0, meaning no limit) 53 | -A, --ascii Output bytes range from 32 to 126 (default is 0 to 255) 54 | -I, --rx-timeout Receive timeout 55 | -O, --tx-timeout Transmission timeout 56 | -W, --tx-wait Number of seconds to wait before to transmit (defaults to 0, meaning no wait) 57 | -Z, --error-on-timeout Treat timeouts as errors 58 | -n, --no-icount Do not request driver for counts of input serial line interrupts (TIOCGICOUNT) 59 | -f, --flush-buffers Flush RX and TX buffers before starting 60 | ``` 61 | 62 | 63 | # Examples 64 | 65 | ## Stress test a connection 66 | 67 | linux-serial-test -s -e -p /dev/ttyO0 -b 3000000 68 | 69 | This will send full bandwidth data with a counting pattern on the TX signal. 70 | On any data received on RX, the program will look for a counting pattern and 71 | report any missing data in the pattern. This test can be done using a loopback 72 | cable. 73 | 74 | ## Test flow control 75 | 76 | linux-serial-test -s -e -p /dev/ttyO0 -c -l 250 77 | 78 | This enables RTS/CTS flow control and sends a counting pattern on the TX signal. 79 | Reads are delayed by 250ms between reads, which will cause the buffer to fill up 80 | and start using flow control. As before any missing data in the pattern is 81 | reported, and if flow control is working correctly there should be none. 82 | 83 | This test can be done using a loopback cable, or by running the program on both 84 | ends of the connection. For a two-port solution invoke the following command on 85 | the receiver side: 86 | 87 | linux-serial-test -s -e -p /dev/ttyO1 -t -c -l 250 88 | 89 | and on the transmitter side: 90 | 91 | linux-serial-test -s -e -p /dev/ttyO0 -r -c 92 | 93 | ## Stress test that can be used in a script 94 | 95 | linux-serial-test -s -e -p /dev/ttyO0 -b 115200 -o 5 -i 7 96 | 97 | This transmits for five seconds and receives for seven seconds, after which it 98 | will exit. The exit code will be zero if the number of received bytes matched 99 | the number of transmitted bytes and the received pattern was correct, so this 100 | can be used as part of an automated test script. 101 | 102 | ## Output a pattern where you can easily verify baud rate with scope: 103 | 104 | linux-serial-test -y 0x55 -z 0x0 -p /dev/ttyO0 -b 3000000 105 | 106 | This outputs 10 bits that are easy to measure, and then multiply by 10 107 | in your head to get baud rate. 108 | 109 | ![verify baud rate](https://github.com/cbrake/linux-serial-test/blob/master/measure-baud-rate-example.png) 110 | -------------------------------------------------------------------------------- /angstrom.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # to use, cmake --DCMAKE_TOOLCHAIN_FILE=angstrom.cmake ... 3 | 4 | set (CMAKE_C_COMPILER arm-angstrom-linux-gnueabi-gcc) 5 | set (CMAKE_CXX_COMPILER arm-angstrom-linux-gnueabi-g++) 6 | 7 | -------------------------------------------------------------------------------- /linux-serial-test.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | /* 23 | * glibc for MIPS has its own bits/termios.h which does not define 24 | * CMSPAR, so we vampirise the value from the generic bits/termios.h 25 | */ 26 | #ifndef CMSPAR 27 | #define CMSPAR 010000000000 28 | #endif 29 | 30 | /* 31 | * Define modem line bits 32 | */ 33 | #ifndef TIOCM_LOOP 34 | #define TIOCM_LOOP 0x8000 35 | #endif 36 | 37 | // command line args 38 | int _cl_baud = 0; 39 | char *_cl_port = NULL; 40 | int _cl_divisor = 0; 41 | int _cl_rx_dump = 0; 42 | int _cl_rx_dump_ascii = 0; 43 | int _cl_tx_detailed = 0; 44 | int _cl_rx_detailed = 0; 45 | int _cl_stats = 0; 46 | int _cl_stop_on_error = 0; 47 | int _cl_single_byte = -1; 48 | int _cl_another_byte = -1; 49 | int _cl_rts_cts = 0; 50 | int _cl_2_stop_bit = 0; 51 | int _cl_parity = 0; 52 | int _cl_odd_parity = 0; 53 | int _cl_stick_parity = 0; 54 | int _cl_loopback = 0; 55 | int _cl_dump_err = 0; 56 | int _cl_no_rx = 0; 57 | int _cl_no_tx = 0; 58 | int _cl_rx_delay = 0; 59 | int _cl_tx_delay = 0; 60 | int _cl_tx_bytes = 0; 61 | int _cl_rs485_after_delay = -1; 62 | int _cl_rs485_before_delay = 0; 63 | int _cl_rs485_rts_after_send = 0; 64 | int _cl_do_not_touch_modem_lines = 0; 65 | int _cl_tx_time = 0; 66 | int _cl_rx_time = 0; 67 | int _cl_tx_wait = 0; 68 | int _cl_ascii_range = 0; 69 | int _cl_write_after_read = 0; 70 | int _cl_rx_timeout_ms = 2000; 71 | int _cl_tx_timeout_ms = 2000; 72 | int _cl_error_on_timeout = 0; 73 | int _cl_no_icount = 0; 74 | int _cl_flush_buffers = 0; 75 | 76 | // Module variables 77 | unsigned char _write_count_value = 0; 78 | unsigned char _read_count_value = 0; 79 | int _fd = -1; 80 | unsigned char * _write_data; 81 | ssize_t _write_size; 82 | 83 | // keep our own counts for cases where the driver stats don't work 84 | long long int _write_count = 0; 85 | long long int _read_count = 0; 86 | long long int _error_count = 0; 87 | 88 | volatile sig_atomic_t sigint_received = 0; 89 | void sigint_handler(int s) 90 | { 91 | sigint_received += 1; 92 | 93 | //if it hangs in the loop or afterwards, this one gives the opportunity to stop right away 94 | if (sigint_received > 3) { 95 | exit(-1); 96 | } 97 | } 98 | 99 | static void exit_handler(void) 100 | { 101 | printf("Exit handler: Cleaning up ...\n"); 102 | tcflush(_fd, TCIOFLUSH); 103 | 104 | if (_fd >= 0) { 105 | flock(_fd, LOCK_UN); 106 | close(_fd); 107 | } 108 | 109 | if (_cl_port) { 110 | free(_cl_port); 111 | _cl_port = NULL; 112 | } 113 | 114 | if (_write_data) { 115 | free(_write_data); 116 | _write_data = NULL; 117 | } 118 | } 119 | 120 | static void dump_data(unsigned char * b, int count) 121 | { 122 | printf("%i bytes: ", count); 123 | int i; 124 | for (i=0; i < count; i++) { 125 | printf("%02x ", b[i]); 126 | } 127 | 128 | printf("\n"); 129 | } 130 | 131 | static void dump_data_ascii(unsigned char * b, int count) 132 | { 133 | int i; 134 | for (i=0; i < count; i++) { 135 | printf("%c", b[i]); 136 | } 137 | } 138 | 139 | static void set_baud_divisor(int speed, int custom_divisor) 140 | { 141 | // default baud was not found, so try to set a custom divisor 142 | struct serial_struct ss; 143 | int ret; 144 | 145 | if (ioctl(_fd, TIOCGSERIAL, &ss) < 0) { 146 | ret = -errno; 147 | perror("TIOCGSERIAL failed"); 148 | exit(ret); 149 | } 150 | 151 | ss.flags = (ss.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST; 152 | if (custom_divisor) { 153 | ss.custom_divisor = custom_divisor; 154 | } else { 155 | ss.custom_divisor = (ss.baud_base + (speed/2)) / speed; 156 | int closest_speed = ss.baud_base / ss.custom_divisor; 157 | 158 | if (closest_speed < speed * 98 / 100 || closest_speed > speed * 102 / 100) { 159 | fprintf(stderr, "Cannot set speed to %d, closest is %d\n", speed, closest_speed); 160 | exit(-EINVAL); 161 | } 162 | 163 | printf("closest baud = %i, base = %i, divisor = %i\n", closest_speed, ss.baud_base, 164 | ss.custom_divisor); 165 | } 166 | 167 | if (ioctl(_fd, TIOCSSERIAL, &ss) < 0) { 168 | ret = -errno; 169 | perror("TIOCSSERIAL failed"); 170 | exit(ret); 171 | } 172 | } 173 | 174 | static void clear_custom_speed_flag() 175 | { 176 | struct serial_struct ss; 177 | int ret; 178 | 179 | if (ioctl(_fd, TIOCGSERIAL, &ss) < 0) { 180 | // return silently as some devices do not support TIOCGSERIAL 181 | return; 182 | } 183 | 184 | if ((ss.flags & ASYNC_SPD_MASK) != ASYNC_SPD_CUST) 185 | return; 186 | 187 | ss.flags &= ~ASYNC_SPD_MASK; 188 | 189 | if (ioctl(_fd, TIOCSSERIAL, &ss) < 0) { 190 | ret = -errno; 191 | perror("TIOCSSERIAL failed"); 192 | exit(ret); 193 | } 194 | } 195 | 196 | // converts integer baud to Linux define 197 | static int get_baud(int baud) 198 | { 199 | switch (baud) { 200 | case 1200: 201 | return B1200; 202 | case 2400: 203 | return B2400; 204 | case 4800: 205 | return B4800; 206 | case 9600: 207 | return B9600; 208 | case 19200: 209 | return B19200; 210 | case 38400: 211 | return B38400; 212 | case 57600: 213 | return B57600; 214 | case 115200: 215 | return B115200; 216 | case 230400: 217 | return B230400; 218 | case 460800: 219 | return B460800; 220 | case 500000: 221 | return B500000; 222 | case 576000: 223 | return B576000; 224 | case 921600: 225 | return B921600; 226 | #ifdef B1000000 227 | case 1000000: 228 | return B1000000; 229 | #endif 230 | #ifdef B1152000 231 | case 1152000: 232 | return B1152000; 233 | #endif 234 | #ifdef B1500000 235 | case 1500000: 236 | return B1500000; 237 | #endif 238 | #ifdef B2000000 239 | case 2000000: 240 | return B2000000; 241 | #endif 242 | #ifdef B2500000 243 | case 2500000: 244 | return B2500000; 245 | #endif 246 | #ifdef B3000000 247 | case 3000000: 248 | return B3000000; 249 | #endif 250 | #ifdef B3500000 251 | case 3500000: 252 | return B3500000; 253 | #endif 254 | #ifdef B4000000 255 | case 4000000: 256 | return B4000000; 257 | #endif 258 | default: 259 | return -1; 260 | } 261 | } 262 | 263 | void set_modem_lines(int fd, int bits, int mask) 264 | { 265 | if (_cl_do_not_touch_modem_lines) 266 | return; 267 | 268 | int status, ret; 269 | 270 | if (ioctl(fd, TIOCMGET, &status) < 0) { 271 | ret = -errno; 272 | perror("TIOCMGET failed"); 273 | exit(ret); 274 | } 275 | 276 | status = (status & ~mask) | (bits & mask); 277 | 278 | if (ioctl(fd, TIOCMSET, &status) < 0) { 279 | ret = -errno; 280 | perror("TIOCMSET failed"); 281 | exit(ret); 282 | } 283 | } 284 | 285 | static void display_help(void) 286 | { 287 | printf("Usage: linux-serial-test [OPTION]\n" 288 | "\n" 289 | " -h, --help\n" 290 | " -b, --baud Baud rate, 115200, etc (115200 is default)\n" 291 | " -p, --port Port (/dev/ttyS0, etc) (must be specified)\n" 292 | " -d, --divisor UART Baud rate divisor (can be used to set custom baud rates)\n" 293 | " -D, --rx_dump Dump Rx data (ascii, raw)\n" 294 | " -T, --detailed_tx Detailed Tx data\n" 295 | " -R, --detailed_rx Detailed Rx data\n" 296 | " -s, --stats Dump serial port stats every 5s\n" 297 | " -S, --stop-on-err Stop program if we encounter an error\n" 298 | " -y, --single-byte Send specified byte to the serial port\n" 299 | " -z, --second-byte Send another specified byte to the serial port\n" 300 | " -c, --rts-cts Enable RTS/CTS flow control\n" 301 | " -B, --2-stop-bit Use two stop bits per character\n" 302 | " -P, --parity Use parity bit (odd, even, mark, space)\n" 303 | " -k, --loopback Use internal hardware loop back\n" 304 | " -K, --write-follow Write follows the read count (can be used for multi-serial loopback)\n" 305 | " -e, --dump-err Display errors\n" 306 | " -r, --no-rx Don't receive data (can be used to test flow control)\n" 307 | " when serial driver buffer is full\n" 308 | " -t, --no-tx Don't transmit data\n" 309 | " -l, --rx-delay Delay between reading data (ms) (can be used to test flow control)\n" 310 | " -a, --tx-delay Delay between writing data (ms)\n" 311 | " -w, --tx-bytes Number of bytes for each write (default is to repeatedly write 1024 bytes\n" 312 | " until no more are accepted)\n" 313 | " -q, --rs485 Enable RS485 direction control on port, and set delay from when TX is\n" 314 | " finished and RS485 driver enable is de-asserted. Delay is specified in\n" 315 | " bit times. To optionally specify a delay from when the driver is enabled\n" 316 | " to start of TX use 'after_delay.before_delay' (-q 1.1)\n" 317 | " -Q, --rs485_rts Deassert RTS on send, assert after send. Omitting -Q inverts this logic.\n" 318 | " -m, --no-modem Do not clobber against any modem lines.\n" 319 | " -o, --tx-time Number of seconds to transmit for (defaults to 0, meaning no limit)\n" 320 | " -i, --rx-time Number of seconds to receive for (defaults to 0, meaning no limit)\n" 321 | " -A, --ascii Output bytes range from 32 to 126 (default is 0 to 255)\n" 322 | " -I, --rx-timeout Receive timeout\n" 323 | " -O, --tx-timeout Transmission timeout\n" 324 | " -W, --tx-wait Number of seconds to wait before to transmit (defaults to 0, meaning no wait)\n" 325 | " -Z, --error-on-timeout Treat timeouts as errors\n" 326 | " -n, --no-icount Do not request driver for counts of input serial line interrupts (TIOCGICOUNT)\n" 327 | " -f, --flush-buffers Flush RX and TX buffers before starting\n" 328 | "\n" 329 | ); 330 | } 331 | 332 | static void process_options(int argc, char * argv[]) 333 | { 334 | for (;;) { 335 | int option_index = 0; 336 | static const char *short_options = "hb:p:d:D:TRsSy:z:cBertq:Qml:a:w:o:i:P:kKAI:O:W:Znf"; 337 | static const struct option long_options[] = { 338 | {"help", no_argument, 0, 0}, 339 | {"baud", required_argument, 0, 'b'}, 340 | {"port", required_argument, 0, 'p'}, 341 | {"divisor", required_argument, 0, 'd'}, 342 | {"rx_dump", required_argument, 0, 'D'}, 343 | {"detailed_tx", no_argument, 0, 'T'}, 344 | {"detailed_rx", no_argument, 0, 'R'}, 345 | {"stats", no_argument, 0, 's'}, 346 | {"stop-on-err", no_argument, 0, 'S'}, 347 | {"single-byte", required_argument, 0, 'y'}, 348 | {"second-byte", required_argument, 0, 'z'}, 349 | {"rts-cts", no_argument, 0, 'c'}, 350 | {"2-stop-bit", no_argument, 0, 'B'}, 351 | {"parity", required_argument, 0, 'P'}, 352 | {"loopback", no_argument, 0, 'k'}, 353 | {"write-follows", no_argument, 0, 'K'}, 354 | {"dump-err", no_argument, 0, 'e'}, 355 | {"no-rx", no_argument, 0, 'r'}, 356 | {"no-tx", no_argument, 0, 't'}, 357 | {"rx-delay", required_argument, 0, 'l'}, 358 | {"tx-delay", required_argument, 0, 'a'}, 359 | {"tx-bytes", required_argument, 0, 'w'}, 360 | {"rs485", required_argument, 0, 'q'}, 361 | {"rs485_rts", no_argument, 0, 'Q'}, 362 | {"no-modem", no_argument, 0, 'm'}, 363 | {"tx-time", required_argument, 0, 'o'}, 364 | {"rx-time", required_argument, 0, 'i'}, 365 | {"tx-wait", required_argument, 0, 'W'}, 366 | {"ascii", no_argument, 0, 'A'}, 367 | {"rx-timeout", required_argument, 0, 'I'}, 368 | {"tx-timeout", required_argument, 0, 'O'}, 369 | {"error-on-timeout", no_argument, 0, 'Z'}, 370 | {"no-icount", no_argument, 0, 'n'}, 371 | {"flush-buffers", no_argument, 0, 'f'}, 372 | {0,0,0,0}, 373 | }; 374 | 375 | int c = getopt_long(argc, argv, short_options, 376 | long_options, &option_index); 377 | 378 | if (c == EOF) { 379 | break; 380 | } 381 | 382 | switch (c) { 383 | case 0: 384 | case 'h': 385 | display_help(); 386 | exit(0); 387 | break; 388 | case 'b': 389 | _cl_baud = atoi(optarg); 390 | break; 391 | case 'p': 392 | _cl_port = strdup(optarg); 393 | break; 394 | case 'd': 395 | _cl_divisor = strtol(optarg, NULL, 0); 396 | break; 397 | case 'D': 398 | _cl_rx_dump = 1; 399 | _cl_rx_dump_ascii = !strcmp(optarg, "ascii"); 400 | break; 401 | case 'T': 402 | _cl_tx_detailed = 1; 403 | break; 404 | case 'R': 405 | _cl_rx_detailed = 1; 406 | break; 407 | case 's': 408 | _cl_stats = 1; 409 | break; 410 | case 'S': 411 | _cl_stop_on_error = 1; 412 | break; 413 | case 'y': { 414 | char * endptr; 415 | _cl_single_byte = strtol(optarg, &endptr, 0); 416 | break; 417 | } 418 | case 'z': { 419 | char * endptr; 420 | _cl_another_byte = strtol(optarg, &endptr, 0); 421 | break; 422 | } 423 | case 'c': 424 | _cl_rts_cts = 1; 425 | break; 426 | case 'B': 427 | _cl_2_stop_bit = 1; 428 | break; 429 | case 'P': 430 | _cl_parity = 1; 431 | _cl_odd_parity = (!strcmp(optarg, "mark")||!strcmp(optarg, "odd")); 432 | _cl_stick_parity = (!strcmp(optarg, "mark")||!strcmp(optarg, "space")); 433 | break; 434 | case 'k': 435 | _cl_loopback = 1; 436 | break; 437 | case 'K': 438 | _cl_write_after_read = 1; 439 | break; 440 | case 'e': 441 | _cl_dump_err = 1; 442 | break; 443 | case 'r': 444 | _cl_no_rx = 1; 445 | break; 446 | case 't': 447 | _cl_no_tx = 1; 448 | break; 449 | case 'l': { 450 | char *endptr; 451 | _cl_rx_delay = strtol(optarg, &endptr, 0); 452 | break; 453 | } 454 | case 'a': { 455 | char *endptr; 456 | _cl_tx_delay = strtol(optarg, &endptr, 0); 457 | break; 458 | } 459 | case 'w': { 460 | char *endptr; 461 | _cl_tx_bytes = strtol(optarg, &endptr, 0); 462 | break; 463 | } 464 | case 'q': { 465 | char *endptr; 466 | _cl_rs485_after_delay = strtol(optarg, &endptr, 0); 467 | _cl_rs485_before_delay = strtol(endptr+1, &endptr, 0); 468 | break; 469 | } 470 | case 'Q': 471 | _cl_rs485_rts_after_send = 1; 472 | break; 473 | case 'm': 474 | _cl_do_not_touch_modem_lines = 1; 475 | break; 476 | case 'o': { 477 | char *endptr; 478 | _cl_tx_time = strtol(optarg, &endptr, 0); 479 | break; 480 | } 481 | case 'i': { 482 | char *endptr; 483 | _cl_rx_time = strtol(optarg, &endptr, 0); 484 | break; 485 | } 486 | case 'W': { 487 | char *endptr; 488 | _cl_tx_wait = strtol(optarg, &endptr, 0); 489 | break; 490 | } 491 | case 'A': 492 | _cl_ascii_range = 1; 493 | break; 494 | case 'I': 495 | _cl_rx_timeout_ms = atoi(optarg); 496 | break; 497 | case 'O': 498 | _cl_tx_timeout_ms = atoi(optarg); 499 | break; 500 | case 'Z': 501 | _cl_error_on_timeout = 1; 502 | break; 503 | case 'n': 504 | _cl_no_icount = 1; 505 | break; 506 | case 'f': 507 | _cl_flush_buffers = 1; 508 | break; 509 | } 510 | } 511 | } 512 | 513 | static void dump_serial_port_stats(void) 514 | { 515 | struct serial_icounter_struct icount = { 0 }; 516 | 517 | printf("%s: count for this session: rx=%lld, tx=%lld, rx err=%lld\n", _cl_port, _read_count, _write_count, _error_count); 518 | 519 | if (!_cl_no_icount) { 520 | int ret = ioctl(_fd, TIOCGICOUNT, &icount); 521 | if (ret < 0) { 522 | perror("Error getting TIOCGICOUNT"); 523 | } else { 524 | printf("%s: TIOCGICOUNT: ret=%i, rx=%i, tx=%i, frame = %i, overrun = %i, parity = %i, brk = %i, buf_overrun = %i\n", 525 | _cl_port, ret, icount.rx, icount.tx, icount.frame, icount.overrun, icount.parity, icount.brk, 526 | icount.buf_overrun); 527 | } 528 | } 529 | } 530 | 531 | static unsigned char next_count_value(unsigned char c) 532 | { 533 | c++; 534 | if (_cl_ascii_range && c == 127) 535 | c = 32; 536 | return c; 537 | } 538 | 539 | static void process_read_data(void) 540 | { 541 | unsigned char rb[1024]; 542 | int actual_read_count = 0; 543 | while (actual_read_count < 1024) { 544 | int c = read(_fd, &rb, sizeof(rb)); 545 | if (c > 0) { 546 | if (_cl_rx_dump) { 547 | if (_cl_rx_dump_ascii) 548 | dump_data_ascii(rb, c); 549 | else 550 | dump_data(rb, c); 551 | } 552 | 553 | // verify read count is incrementing 554 | int i; 555 | for (i = 0; i < c; i++) { 556 | if (rb[i] != _read_count_value) { 557 | if (_cl_dump_err) { 558 | printf("Error, count: %lld, expected %02x, got %02x c %x\n", 559 | _read_count + i, _read_count_value, rb[i], c); 560 | } 561 | _error_count++; 562 | if (_cl_stop_on_error) { 563 | dump_serial_port_stats(); 564 | exit(-EIO); 565 | } 566 | _read_count_value = rb[i]; 567 | } 568 | _read_count_value = next_count_value(_read_count_value); 569 | } 570 | _read_count += c; 571 | actual_read_count += c; 572 | } else if (errno) { 573 | if (errno != EAGAIN) { 574 | perror("read failed"); 575 | } 576 | continue; // Retry the read 577 | } else { 578 | break; 579 | } 580 | } 581 | if (_cl_rx_detailed) { 582 | printf("Read %d bytes\n", actual_read_count); 583 | } 584 | } 585 | 586 | static void process_write_data(void) 587 | { 588 | ssize_t count = 0; 589 | ssize_t actual_write_size = 0; 590 | int repeat = (_cl_tx_bytes == 0); 591 | 592 | do 593 | { 594 | if (_cl_write_after_read == 0) { 595 | actual_write_size = _write_size; 596 | } else { 597 | actual_write_size = _read_count > _write_count ? _read_count - _write_count : 0; 598 | if (actual_write_size > _write_size) { 599 | actual_write_size = _write_size; 600 | } 601 | } 602 | if (actual_write_size == 0) { 603 | break; 604 | } 605 | 606 | ssize_t i; 607 | for (i = 0; i < actual_write_size; i++) { 608 | _write_data[i] = _write_count_value; 609 | _write_count_value = next_count_value(_write_count_value); 610 | } 611 | 612 | ssize_t c = write(_fd, _write_data, actual_write_size); 613 | 614 | if (c < 0) { 615 | if (errno != EAGAIN) { 616 | printf("write failed - errno=%d (%s)\n", errno, strerror(errno)); 617 | } 618 | c = 0; 619 | } 620 | 621 | count += c; 622 | 623 | if (c < actual_write_size) { 624 | _write_count_value = _write_data[c]; 625 | repeat = 0; 626 | } 627 | } while (repeat); 628 | 629 | _write_count += count; 630 | 631 | if (_cl_tx_detailed) 632 | printf("wrote %zd bytes\n", count); 633 | } 634 | 635 | 636 | static void setup_serial_port(int baud) 637 | { 638 | struct termios newtio; 639 | struct serial_rs485 rs485; 640 | int ret; 641 | 642 | _fd = open(_cl_port, O_RDWR | O_NONBLOCK); 643 | 644 | if (_fd < 0) { 645 | ret = -errno; 646 | perror("Error opening serial port"); 647 | exit(ret); 648 | } 649 | 650 | /* Lock device file */ 651 | if (flock(_fd, LOCK_EX | LOCK_NB) < 0) { 652 | ret = -errno; 653 | perror("Error failed to lock device file"); 654 | exit(ret); 655 | } 656 | 657 | bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */ 658 | 659 | /* man termios get more info on below settings */ 660 | newtio.c_cflag = baud | CS8 | CLOCAL | CREAD; 661 | 662 | if (_cl_rts_cts) { 663 | newtio.c_cflag |= CRTSCTS; 664 | } 665 | 666 | if (_cl_2_stop_bit) { 667 | newtio.c_cflag |= CSTOPB; 668 | } 669 | 670 | if (_cl_parity) { 671 | newtio.c_cflag |= PARENB; 672 | if (_cl_odd_parity) { 673 | newtio.c_cflag |= PARODD; 674 | } 675 | if (_cl_stick_parity) { 676 | newtio.c_cflag |= CMSPAR; 677 | } 678 | } 679 | 680 | newtio.c_iflag = 0; 681 | newtio.c_oflag = 0; 682 | newtio.c_lflag = 0; 683 | 684 | // block for up till 128 characters 685 | newtio.c_cc[VMIN] = 128; 686 | 687 | // 0.5 seconds read timeout 688 | newtio.c_cc[VTIME] = 5; 689 | 690 | /* now clean the modem line and activate the settings for the port */ 691 | tcflush(_fd, TCIOFLUSH); 692 | tcsetattr(_fd,TCSANOW,&newtio); 693 | 694 | /* enable/disable rs485 direction control, first check if RS485 is supported */ 695 | if(ioctl(_fd, TIOCGRS485, &rs485) < 0) { 696 | if (_cl_rs485_after_delay >= 0) { 697 | /* error could be because hardware is missing rs485 support so only print when actually trying to activate it */ 698 | perror("Error getting RS-485 mode"); 699 | } 700 | } else { 701 | if (rs485.flags & SER_RS485_ENABLED) { 702 | printf("RS485 already enabled on port, ignoring delays if set\n"); 703 | } else { 704 | if (_cl_rs485_after_delay >= 0) { 705 | /* enable RS485 */ 706 | rs485.flags |= SER_RS485_ENABLED | SER_RS485_RX_DURING_TX | 707 | (_cl_rs485_rts_after_send ? SER_RS485_RTS_AFTER_SEND : SER_RS485_RTS_ON_SEND); 708 | rs485.flags &= ~(_cl_rs485_rts_after_send ? SER_RS485_RTS_ON_SEND : SER_RS485_RTS_AFTER_SEND); 709 | rs485.delay_rts_after_send = _cl_rs485_after_delay; 710 | rs485.delay_rts_before_send = _cl_rs485_before_delay; 711 | if(ioctl(_fd, TIOCSRS485, &rs485) < 0) { 712 | perror("Error setting RS-485 mode"); 713 | } 714 | } else { 715 | /* disable RS485 */ 716 | rs485.flags &= ~(SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND); 717 | rs485.delay_rts_after_send = 0; 718 | rs485.delay_rts_before_send = 0; 719 | if(ioctl(_fd, TIOCSRS485, &rs485) < 0) { 720 | perror("Error setting RS-232 mode"); 721 | } 722 | } 723 | } 724 | } 725 | } 726 | 727 | static int diff_ms(const struct timespec *t1, const struct timespec *t2) 728 | { 729 | struct timespec diff; 730 | 731 | diff.tv_sec = t1->tv_sec - t2->tv_sec; 732 | diff.tv_nsec = t1->tv_nsec - t2->tv_nsec; 733 | if (diff.tv_nsec < 0) { 734 | diff.tv_sec--; 735 | diff.tv_nsec += 1000000000; 736 | } 737 | return (diff.tv_sec * 1000 + diff.tv_nsec/1000000); 738 | } 739 | 740 | static int diff_s(const struct timespec *t1, const struct timespec *t2) 741 | { 742 | return t1->tv_sec - t2->tv_sec; 743 | } 744 | 745 | static int compute_error_count(void) 746 | { 747 | long long int result; 748 | if (_cl_no_rx == 1 || _cl_no_tx == 1) 749 | result = _error_count; 750 | else 751 | result = llabs(_write_count - _read_count) + _error_count; 752 | 753 | return (result > 125) ? 125 : (int)result; 754 | } 755 | 756 | int main(int argc, char * argv[]) 757 | { 758 | printf("Linux serial test app\n"); 759 | 760 | signal(SIGINT, sigint_handler); 761 | signal(SIGTERM, sigint_handler); 762 | atexit(&exit_handler); //does not work for SIGINT/SIGTERM without the previous signal handlers 763 | 764 | process_options(argc, argv); 765 | 766 | int wait_time = _cl_tx_wait; 767 | 768 | if (!_cl_port) { 769 | fprintf(stderr, "ERROR: Port argument required\n"); 770 | display_help(); 771 | exit(-EINVAL); 772 | } 773 | 774 | int baud = B115200; 775 | 776 | if (_cl_baud && !_cl_divisor) 777 | baud = get_baud(_cl_baud); 778 | 779 | if (baud <= 0 || _cl_divisor) { 780 | printf("NOTE: non standard baud rate, trying custom divisor\n"); 781 | baud = B38400; 782 | setup_serial_port(B38400); 783 | set_baud_divisor(_cl_baud, _cl_divisor); 784 | } else { 785 | setup_serial_port(baud); 786 | /* 787 | * The flag ASYNC_SPD_CUST might have already been set, so 788 | * clear it to avoid confusing the kernel uart dirver. 789 | */ 790 | clear_custom_speed_flag(); 791 | } 792 | 793 | set_modem_lines(_fd, _cl_loopback ? TIOCM_LOOP : 0, TIOCM_LOOP); 794 | 795 | if (_cl_single_byte >= 0) { 796 | unsigned char data[2]; 797 | int bytes = 1; 798 | int written; 799 | data[0] = (unsigned char)_cl_single_byte; 800 | if (_cl_another_byte >= 0) { 801 | data[1] = (unsigned char)_cl_another_byte; 802 | bytes++; 803 | } 804 | written = write(_fd, &data, bytes); 805 | if (written < 0) { 806 | int ret = errno; 807 | perror("write()"); 808 | exit(ret); 809 | } else if (written != bytes) { 810 | fprintf(stderr, "ERROR: write() returned %d, not %d\n", written, bytes); 811 | exit(-EIO); 812 | } 813 | return 0; 814 | } 815 | 816 | _write_size = (_cl_tx_bytes == 0) ? 1024 : _cl_tx_bytes; 817 | 818 | _write_data = malloc(_write_size); 819 | if (_write_data == NULL) { 820 | fprintf(stderr, "ERROR: Memory allocation failed\n"); 821 | exit(-ENOMEM); 822 | } 823 | 824 | if (_cl_ascii_range) { 825 | _read_count_value = _write_count_value = 32; 826 | } 827 | 828 | struct pollfd serial_poll; 829 | serial_poll.fd = _fd; 830 | if (!_cl_no_rx) { 831 | serial_poll.events |= POLLIN; 832 | } else { 833 | serial_poll.events &= ~POLLIN; 834 | } 835 | 836 | if (!_cl_no_tx) { 837 | serial_poll.events |= POLLOUT; 838 | } else { 839 | serial_poll.events &= ~POLLOUT; 840 | } 841 | 842 | if (_cl_flush_buffers) { 843 | printf("Flush RX buffer.\n"); 844 | // Wait 100ms delay to let data arrive before flushing the I/O 845 | // buffers. This is a unfortunately a known workaround. 846 | usleep(100000); 847 | tcflush(_fd, TCIOFLUSH); 848 | } 849 | 850 | struct timespec start_time, last_stat, last_timeout, last_read, last_write; 851 | 852 | clock_gettime(CLOCK_MONOTONIC, &start_time); 853 | last_stat = start_time; 854 | last_timeout = start_time; 855 | last_read = start_time; 856 | last_write = start_time; 857 | 858 | if (_cl_tx_wait) 859 | serial_poll.events &= ~POLLOUT; 860 | 861 | while (!(_cl_no_rx && _cl_no_tx) && !sigint_received ) { 862 | struct timespec current; 863 | int retval = poll(&serial_poll, 1, 1000); 864 | 865 | clock_gettime(CLOCK_MONOTONIC, ¤t); 866 | 867 | if (_cl_tx_wait) { 868 | if (diff_s(¤t, &start_time) >= _cl_tx_wait) { 869 | _cl_tx_wait = 0; 870 | _cl_no_tx = 0; 871 | serial_poll.events |= POLLOUT; 872 | printf("Start transmitting.\n"); 873 | } else { 874 | if (!_cl_no_tx) { 875 | _cl_no_tx = 1; 876 | serial_poll.events &= ~POLLOUT; 877 | } 878 | } 879 | } 880 | 881 | if (retval == -1) { 882 | perror("poll()"); 883 | } else if (retval) { 884 | if (serial_poll.revents & POLLIN) { 885 | if (_cl_rx_delay) { 886 | // only read if it has been rx-delay ms 887 | // since the last read 888 | if (diff_ms(¤t, &last_read) > _cl_rx_delay) { 889 | process_read_data(); 890 | last_read = current; 891 | } 892 | } else { 893 | process_read_data(); 894 | last_read = current; 895 | } 896 | } 897 | 898 | if (serial_poll.revents & POLLOUT) { 899 | if (_cl_tx_delay) { 900 | // only write if it has been tx-delay ms 901 | // since the last write 902 | if (diff_ms(¤t, &last_write) > _cl_tx_delay) { 903 | process_write_data(); 904 | last_write = current; 905 | } 906 | } else { 907 | process_write_data(); 908 | last_write = current; 909 | } 910 | } 911 | } 912 | 913 | // Has it been at least a second since we reported a timeout? Have we ever reported a timeout? 914 | if ((diff_ms(¤t, &last_timeout) > 1000) || (diff_ms(&last_timeout, &start_time) == 0)) { 915 | int rx_timeout, tx_timeout; 916 | 917 | // Has it been over two seconds since we transmitted or received data? 918 | rx_timeout = (!_cl_no_rx && diff_ms(¤t, &last_read) > _cl_rx_timeout_ms); 919 | tx_timeout = (!_cl_no_tx && diff_ms(¤t, &last_write) > _cl_tx_timeout_ms); 920 | // Special case - we don't want to warn about receive 921 | // timeouts at the end of a loopback test (where we are 922 | // no longer transmitting and the receive count equals 923 | // the transmit count). 924 | if (_cl_no_tx && _write_count != 0 && _write_count == _read_count) { 925 | rx_timeout = 0; 926 | } 927 | 928 | if (rx_timeout || tx_timeout) { 929 | const char *s; 930 | if (rx_timeout) { 931 | printf("%s: No data received for %.1fs.", 932 | _cl_port, (double)diff_ms(¤t, &last_read) / 1000); 933 | s = " "; 934 | if (_cl_error_on_timeout) { 935 | printf(" Exiting due to timeout.\n"); 936 | exit(-ETIMEDOUT); 937 | } 938 | } else { 939 | s = ""; 940 | } 941 | if (tx_timeout) { 942 | printf("%sNo data transmitted for %.1fs.", 943 | s, (double)diff_ms(¤t, &last_write) / 1000); 944 | if (_cl_error_on_timeout) { 945 | printf(" Exiting due to timeout.\n"); 946 | exit(-ETIMEDOUT); 947 | } 948 | } 949 | printf("\n"); 950 | last_timeout = current; 951 | } 952 | } 953 | 954 | if (_cl_stats) { 955 | if (current.tv_sec - last_stat.tv_sec > 5) { 956 | dump_serial_port_stats(); 957 | last_stat = current; 958 | } 959 | } 960 | 961 | if (_cl_tx_time && !_cl_tx_wait) { 962 | if (current.tv_sec - start_time.tv_sec >= wait_time && 963 | current.tv_sec - start_time.tv_sec - wait_time >= _cl_tx_time ) { 964 | _cl_tx_time = 0; 965 | _cl_no_tx = 1; 966 | serial_poll.events &= ~POLLOUT; 967 | printf("Stopped transmitting.\n"); 968 | } 969 | } 970 | 971 | if (_cl_rx_time) { 972 | if (current.tv_sec - start_time.tv_sec >= _cl_rx_time) { 973 | _cl_rx_time = 0; 974 | _cl_no_rx = 1; 975 | serial_poll.events &= ~POLLIN; 976 | printf("Stopped receiving.\n"); 977 | } 978 | } 979 | } 980 | 981 | printf("Terminating ...\n"); 982 | tcdrain(_fd); 983 | dump_serial_port_stats(); 984 | set_modem_lines(_fd, 0, TIOCM_LOOP); //seems not to be relevant for RTS reset 985 | 986 | return compute_error_count(); 987 | } 988 | -------------------------------------------------------------------------------- /measure-baud-rate-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbrake/linux-serial-test/f89258be397c56a2f95bba45ce40227bff4b54b4/measure-baud-rate-example.png --------------------------------------------------------------------------------