/******************************************************************************/ /* Copyright (C) 2020 Garz&Fricke GmbH */ /* No use or disclosure of this information in any form without */ /* the written permission of the author */ /******************************************************************************/ /******************************************************************************/ /* */ /* File: rs485echo.c */ /* */ /* Description: This is an RS485 echo test designed for EMC verification. */ /* */ /* Total Tests: 1 */ /* */ /* Test Name: rs485echo */ /* */ /* Test Assertion */ /* & Strategy: A random test string is sent on the serial port. The opposite */ /* side is supposed to echo the string back to the sender. The */ /* sender compares the sent and received strings. If they are */ /* equal, the test is passed, else it fails. */ /* */ /* Preceeding the random test string, the sender sends a header */ /* consisting of a key word and the data length. The receiver */ /* runs as a state machine, waiting for the keyword in its idle */ /* state. As soon as the keyword is recognized, it switches to */ /* the receive state. When the data has been received completely */ /* it echoes the string back to the sender and switches to idle */ /* state again. If the keyword is recognized within the data, */ /* the current string is discarded and the receive state is */ /* reset. This ensures that the receiver always goes back to a */ /* stable state, even if the data gets corrupted due to outside */ /* influences, e.g. EMI. */ /* */ /* Author: Tim Jaacks <tim.jaacks@garz-fricke.com> */ /* */ /******************************************************************************/ /* Standard Include Files */ #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/param.h> #include <sys/time.h> #include <time.h> #define TTYDEFCHARS #include <termios.h> #undef TTYDEFCHARS #include <asm/ioctls.h> #include <linux/serial.h> /* Harness Specific Include Files. */ #include "old/test.h" #include "config.h" /* Local Defines */ #if !defined(TRUE) && !defined(FALSE) #define TRUE 1 #define FALSE 0 #endif #if LTP_VERSION >= 20140115 #define Tst_count tst_count #endif /* Possible receiver states */ enum state { IDLE, RECEIVE_HEADER, RECEIVE_DATA_LENGTH, RECEIVE_DATA, }; /* The message structure, including a fixed header, data length and the data */ struct message { char header[4]; int length; char data[]; }; /* Extern Global Variables */ extern int Tst_count; /* counter for tst_xxx routines. */ extern char *TESTDIR; /* temporary dir created by tst_tmpdir() */ /* Global Variables */ char *TCID = "rs485echo "; /* test program identifier. */ int TST_TOTAL = 1; /* total number of tests in this file. */ char HEADER[] = "~.-"; /* data package header */ int fd; /* Handle for open serial port */ char* device_name = "/dev/ttymxc2"; /* String containing the device name */ struct termios termios; /* termios structure for serial port */ int bytes = 8; /* Number of bytes to be sent */ struct message *message; /* Message to be sent */ int size_of_message; /* Size of message */ char* received_data = NULL; /* Received string */ int repetitions = 1; /* Number of repetitions */ int timeout_ms = 1000; /* Receive timeout in milliseconds */ int baudrate = 9600; /* Baud rate in bps */ int use_rs485 = TRUE; /* Use RS485 mode */ int use_rtscts = FALSE; /* Use RTSCTS flowcontrol (not available in RS485 mode) */ int echo_mode = FALSE; /* Start in echo mode */ enum state state; /* Current echo mode state */ int verbose = FALSE; /* Enable more debug messages */ speed_t baud_constant[] = { B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000 }; unsigned long baud_value[] = { 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000 }; /* Extern Global Functions */ /******************************************************************************/ /* */ /* Function: cleanup */ /* */ /* Description: Performs all one time clean up for this test on successful */ /* completion, premature exit or failure. Closes all temporary */ /* files, removes all temporary directories exits the test with */ /* appropriate return code by calling tst_exit() function. */ /* */ /* Input: None. */ /* */ /* Output: None. */ /* */ /* Return: On failure - Exits calling tst_exit(). Non '0' return code. */ /* On success - Exits calling tst_exit(). With '0' return code. */ /* */ /******************************************************************************/ extern void cleanup() { /* Free all allocated memory. */ if (message) free(message); if (received_data) free(received_data); /* Close all open file descriptors. */ if (close(fd) == -1) { tst_resm(TWARN, "close(%s) Failed, errno=%d : %s", device_name, errno, strerror(errno)); } /* Remove all temporary directories used by this test. */ // Insert real code here /* kill child processes if any. */ // Insert code here /* Exit with appropriate return code. */ tst_exit(); } /* Local Functions */ static char *random_string(char *str, size_t length) { const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (size_t n = 0; n < length; n++) { int key = rand() % (int) (sizeof charset - 1); str[n] = charset[key]; } str[length] = 0; return str; } void sigint_handler(int sig) { cleanup(); tst_exit(); exit(0); } /******************************************************************************/ /* */ /* Function: setup */ /* */ /* Description: Performs all one time setup for this test. This function is */ /* typically used to capture signals, create temporary dirs */ /* and temporary files that may be used in the course of this */ /* test. */ /* */ /* Input: None. */ /* */ /* Output: None. */ /* */ /* Return: On failure - Exits by calling cleanup(). */ /* On success - returns 0. */ /* */ /******************************************************************************/ void setup() { int i; struct serial_rs485 rs485; speed_t baud = B0; // Check if baudrate is supported for (i = 0; i < ARRAY_SIZE(baud_constant); i++) { if (baud_value[i] == baudrate) { baud = baud_constant[i]; break; } } if (baud == B0) tst_brkm(TBROK, cleanup, "Baud rate not supported (%d)", baudrate); if (!echo_mode) { // Initialize random generator struct timespec now; clock_gettime(CLOCK_MONOTONIC_RAW,&now); srand(now.tv_sec + now.tv_nsec); // Allocate space for message and construct message header size_of_message = sizeof(struct message) + bytes; message = malloc(size_of_message + 1); memcpy(message->header, HEADER, sizeof(HEADER)); message->length = bytes; message->data[bytes] = 0; } /* Initialize serial communication */ // Open port fd = open (device_name, O_RDWR | O_SYNC); if (fd == -1) { tst_brkm(TBROK, cleanup, "Unable to open serial port %s. Error:%d, %s\n", device_name, errno, strerror(errno)); } if (use_rs485) { // Enable RS485 half duplex mode rs485.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND; if (ioctl(fd, TIOCSRS485, &rs485) != 0) { tst_brkm(TBROK, cleanup, "Unable to set RS485 half duplex mode on serial port %s. Error:%d, %s\n", device_name, errno, strerror(errno)); } } // Get old attribute if (tcgetattr(fd, &termios)) { // This is not a serial device tst_brkm(TBROK, cleanup, "Can't read device configuration. Is this a serial device? \ Error:%d, %s\n", errno, strerror(errno)); } termios.c_cflag = TTYDEF_CFLAG; termios.c_iflag = TTYDEF_IFLAG; termios.c_oflag = TTYDEF_OFLAG; termios.c_lflag = TTYDEF_LFLAG; memcpy(termios.c_cc, ttydefchars, MIN(sizeof(termios.c_cc), sizeof(ttydefchars))); // Set Speed into termios structure if (cfsetspeed (&termios, baud)) { tst_brkm(TBROK, cleanup, "Can't set baud rate. Error:%d, %s\n", errno, strerror(errno)); } // Disable Echo, make Data=8, Stop=7, no parity, disable all controls // see "man cfmakeraw" cfmakeraw (&termios); // Set internal timeout value to 1 * 0.1 seconds, i.e. read() will // always return after 100 ms, even if there is no character present. termios.c_cc[VMIN] = 0; termios.c_cc[VTIME] = 1; if (use_rtscts) { // Enable RTS CTS flowcontrol termios.c_cflag |= CRTSCTS; } // set new attribute if (tcsetattr (fd, TCSANOW, &termios)) { tst_brkm(TBROK, cleanup, "Can't set device configuration. Error:%d, %s\n", errno, strerror(errno)); } return; } /******************************************************************************/ /* */ /* Function: help */ /* */ /* Description: This function is called when the test is started with */ /* parameter -h. It displays all parameter options specifically */ /* available for this test. */ /* */ /* Input: None. */ /* */ /* Output: None. */ /* */ /* Return: Nothing. */ /* */ /******************************************************************************/ void help() { printf(" -d s Select serial port specified by device name s (default: /dev/ttymxc2)\n"); printf(" -s x Set baudrate to x bps (default: 9600)\n"); printf(" -b x Send x bytes (default: 8)\n"); printf(" -r x Repeat send and receive x times (default: 1)\n"); printf(" -o x Set receive timeout to x milliseconds (default: 1000)\n"); printf(" -n Do not use RS485 mode\n"); printf(" -c Enable rtscts flowcontrol, not to be used in RS485 mode\n"); printf(" -e Echo mode, wait for data and echo it back\n"); printf(" -x x Set delay time between receive and next send in us (default: 300)\n"); printf(" -t x Set delay time after receiving the first byte, used to test flow control (default: 0)\n"); printf(" -v Verbose\n"); } /******************************************************************************/ /* */ /* Function: main */ /* */ /* Description: Entry point to this test-case. It parses all the command line */ /* inputs, calls the global setup and executes the test. It logs */ /* the test status and results appropriately using the LTP API's */ /* On successful completion or premature failure, cleanup() func */ /* is called and test exits with an appropriate return code. */ /* */ /* Input: Describe input arguments to this test-case */ /* -l - Number of iteration */ /* -v - Prints verbose output */ /* -V - Prints the version number */ /* */ /* Exit: On failure - Exits by calling cleanup(). */ /* On success - exits with 0 exit value. */ /* */ /******************************************************************************/ int main(int argc, /* number of command line parameters */ char **argv) /* pointer to the array of the command line parameters. */ { int cycle_count = 0; int byte_count = 0; int send_delay = 300; int receive_sleep = 0; char received_byte; int received_state_bytes = 0; // Number of bytes received in current state int data_length = 0; struct timeval start, now; int elapsed_us; int timeout_happened = FALSE; int mismatch_happened = FALSE; int opt_device_name = 0, opt_repetitions = 0, opt_bytes = 0, opt_timeout = 0, opt_baudrate = 0, opt_nors485 = 0, opt_pingpong = 0, opt_delaytime = 0, opt_verbose = 0, opt_rtscts = 0, opt_receive_sleep = 0; char *str_device_name, *str_repetitions, *str_bytes, *str_timeout, *str_baudrate, *str_delaytime, *str_receive_sleep; option_t options[] = { {"d:", &opt_device_name, &str_device_name}, {"r:", &opt_repetitions, &str_repetitions}, {"b:", &opt_bytes, &str_bytes}, {"o:", &opt_timeout, &str_timeout}, {"s:", &opt_baudrate, &str_baudrate}, {"x:", &opt_delaytime, &str_delaytime}, {"e", &opt_pingpong, NULL}, {"n", &opt_nors485, NULL}, {"c", &opt_rtscts, NULL}, {"t:", &opt_receive_sleep, &str_receive_sleep}, {"v", &opt_verbose, NULL}, {NULL, NULL, NULL} }; // Register ctrl+c handler signal(SIGINT, sigint_handler); // Parse options #if LTP_VERSION > 20140115 tst_parse_opts(argc, argv, options, &help); #else char *msg; if((msg = parse_opts(argc, argv, options, &help)) != NULL) tst_brkm(TBROK, cleanup, "OPTION PARSING ERROR - %s", msg); #endif if (opt_device_name) device_name = str_device_name; if (opt_repetitions) { repetitions = atoi(str_repetitions); if(repetitions < 1) { help(); exit(1); } } if (opt_bytes) bytes = atoi(str_bytes); if (opt_timeout) timeout_ms = atoi(str_timeout); if (opt_baudrate) baudrate = atoi(str_baudrate); if (opt_delaytime) { send_delay = atoi(str_delaytime); tst_resm(TINFO, "Delay %d us", send_delay); } if (opt_receive_sleep) { receive_sleep = atoi(str_receive_sleep); tst_resm(TINFO, "Receive Delay %d s", receive_sleep ); } if (opt_nors485) { tst_resm(TINFO, "No RS485 mode"); use_rs485 = FALSE; } if (opt_rtscts) { if(use_rs485){ tst_resm(TINFO, "RTS CTS flowcontrol is not compatible with rs485, ignoring the setting."); }else{ tst_resm(TINFO, "Enable RTS CTS flowcontrol"); use_rtscts = TRUE; } } if (opt_pingpong){ echo_mode = TRUE; } if (opt_verbose){ verbose = TRUE; } // Perform global test setup setup(); tst_resm(TINFO, "Starting RS485 echo test%s", echo_mode?" in echo mode.":"."); if (!echo_mode) { tst_resm(TINFO, "Sending %d bytes to %s (%d,8N1) and waiting for echo.", bytes, device_name, baudrate); tst_resm(TINFO, "(repetitions: %d, timeout: %d seconds)", repetitions, timeout_ms); received_data = malloc(message->length + 1); received_data[message->length] = 0; for (cycle_count = 0; cycle_count < repetitions && !timeout_happened && !mismatch_happened; cycle_count++) { // Generate random string for message random_string(message->data, message->length); tst_resm(TINFO, "Cycle %d data (first 1000 bytes): %.1000s%s", cycle_count+1, message->data, message->length > 1000 ? "[...]" : ""); // Send message write(fd, (char*)message, size_of_message); // Get current time for measuring timeout gettimeofday(&start, NULL); byte_count = 0; while (byte_count < message->length && !timeout_happened) { // Read serial port byte_count += read(fd, &received_data[byte_count], 1); // Check if timeout occured gettimeofday(&now, NULL); elapsed_us = (now.tv_sec - start.tv_sec) * 1000000 + now.tv_usec - start.tv_usec; if (elapsed_us > timeout_ms * 1000) { timeout_happened = TRUE; break; } } if (strcmp(message->data, received_data) != 0) mismatch_happened = TRUE; usleep(send_delay); } /* Print results and exit test-case */ if (timeout_happened) { tst_resm(TFAIL, "Timeout occured. Other side did not respond. Received " "only %d bytes in cycle %d", byte_count, cycle_count); } else if (mismatch_happened) { tst_resm(TFAIL, "Mismatch. Received data in cycle %d did not equal " "sent data.", cycle_count); } else { tst_resm(TPASS, "All received strings equaled sent strings. Test passed."); } } else { // Echo mode state = IDLE; timeout_happened = FALSE; byte_count = 0; tst_resm(TINFO, "Waiting for messages on %s (%d,8N1)", device_name, baudrate); while(TRUE) { // Read serial port byte_count = read(fd, &received_byte, 1); if (byte_count < 1) continue; // Detect header byte regardless of current state if (received_byte == HEADER[0]) { if (verbose) { tst_resm(TINFO, "Start of message detected"); } if(receive_sleep){ // Sleep X seconds to make the flow control hit in sleep(receive_sleep); } received_state_bytes = 1; state = RECEIVE_HEADER; if (received_data) { free(received_data); received_data = NULL; } } else switch(state) { case IDLE: // Just waiting for header byte break; case RECEIVE_HEADER: if (received_byte == HEADER[received_state_bytes]) { received_state_bytes++; if (received_state_bytes == sizeof(HEADER)) { // Received complete header, move on to next state received_state_bytes = 0; data_length = 0; state = RECEIVE_DATA_LENGTH; if (verbose) { tst_resm(TINFO, "Reading data length"); } } } else { if (verbose) { tst_resm(TINFO, "Invalid header"); } // Received wrong header byte, back to idle received_state_bytes = 0; state = IDLE; } break; case RECEIVE_DATA_LENGTH: data_length = data_length | (received_byte << (received_state_bytes * 8)); received_state_bytes++; if (received_state_bytes == sizeof(data_length)) { received_state_bytes = 0; // Allocate space for received string received_data = malloc(data_length + 1); received_data[data_length] = 0; state = RECEIVE_DATA; if (verbose) { tst_resm(TINFO, "Reading %d data bytes", data_length); } } break; case RECEIVE_DATA: received_data[received_state_bytes] = received_byte; received_state_bytes++; if (received_state_bytes == data_length) { // Received complete data, echo it back usleep(send_delay); tst_resm(TINFO, "Received data: %.1000s%s", received_data, data_length > 1000 ? "[...]" : ""); write(fd, received_data, data_length); tst_resm(TINFO, "Echoed data back to sender"); free(received_data); received_data = NULL; received_state_bytes = 0; state = IDLE; } break; } } } cleanup(); tst_exit(); }