├── .gitignore ├── graphics ├── receive timing.ai ├── receive timing.pdf ├── transmit timing.ai ├── transmit timing.pdf ├── transmit timing 1.5stopbits.ai └── transmit timing 1.5stopbits.pdf ├── SlowSoftSerial design notes.pdf ├── SlowSoftSerial design notes.docx ├── test ├── autotest-ctlr-pico │ └── SSS_test │ │ ├── .vscode │ │ ├── c_cpp_properties.json │ │ ├── tasks.json │ │ ├── settings.json │ │ └── launch.json │ │ ├── CMakeLists.txt │ │ ├── pico_sdk_import.cmake │ │ ├── SlowSoftSerial.h │ │ └── SSS_test.c ├── autotest-uut │ ├── .vscode │ │ └── c_cpp_properties.json │ └── autotest-uut.ino ├── README.md ├── autotest-ctlr-sss │ ├── SlowSoftSerial.h │ ├── autotest-ctlr-sss.ino │ └── SlowSoftSerial.cpp └── autotest-analyze │ └── autotest-analyze.py ├── SlowSoftSerial ├── examples │ ├── DuelingPorts │ │ └── DuelingPorts.ino │ ├── SerialPassthrough │ │ └── SerialPassthrough.ino │ ├── SendPoemDemo │ │ └── SendPoemDemo.ino │ └── VariousParameters │ │ └── VariousParameters.ino └── SlowSoftSerial.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | .cortex-debug.* 4 | test/autotest-uut/SlowSoftSerial.* 5 | .vscode/ -------------------------------------------------------------------------------- /graphics/receive timing.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/graphics/receive timing.ai -------------------------------------------------------------------------------- /graphics/receive timing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/graphics/receive timing.pdf -------------------------------------------------------------------------------- /graphics/transmit timing.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/graphics/transmit timing.ai -------------------------------------------------------------------------------- /graphics/transmit timing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/graphics/transmit timing.pdf -------------------------------------------------------------------------------- /SlowSoftSerial design notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/SlowSoftSerial design notes.pdf -------------------------------------------------------------------------------- /SlowSoftSerial design notes.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/SlowSoftSerial design notes.docx -------------------------------------------------------------------------------- /graphics/transmit timing 1.5stopbits.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/graphics/transmit timing 1.5stopbits.ai -------------------------------------------------------------------------------- /graphics/transmit timing 1.5stopbits.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MustBeArt/SlowSoftSerial/HEAD/graphics/transmit timing 1.5stopbits.pdf -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${env:PICO_SDK_PATH}/**" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/opt/homebrew/bin/arm-none-eabi-gcc", 11 | "cStandard": "gnu17", 12 | "cppStandard": "gnu++14", 13 | "intelliSenseMode": "gcc-arm" 14 | } 15 | ], 16 | "version": 4 17 | } 18 | -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Run the Makefile", 8 | "type": "shell", 9 | "options": { 10 | "cwd": "${fileDirname}/build" 11 | }, 12 | "command": "make", 13 | "problemMatcher": ["$gcc"], 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /test/autotest-uut/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "macFrameworkPath": [ 10 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" 11 | ], 12 | "compilerPath": "/opt/homebrew/bin/arm-none-eabi-g++", 13 | "cStandard": "c17", 14 | "cppStandard": "c++98", 15 | "intelliSenseMode": "linux-gcc-arm" 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /SlowSoftSerial/examples/DuelingPorts/DuelingPorts.ino: -------------------------------------------------------------------------------- 1 | #include "SlowSoftSerial.h" 2 | 3 | // It's ok to have several SlowSoftSerial objects, 4 | // but only one can be active at the same time. 5 | SlowSoftSerial sss1(0,1); 6 | SlowSoftSerial sss2(2,3); 7 | 8 | void setup() { 9 | Serial.begin(9600); // USB port for debug messages 10 | 11 | } 12 | 13 | void loop() { 14 | Serial.println("Activating port 1"); 15 | sss1.begin(9600); 16 | sss1.println("This message goes out port 1"); 17 | sss1.flush(); 18 | sss1.end(); 19 | 20 | Serial.println("Activating port 2"); 21 | sss2.begin(1200); 22 | sss2.println("Now on port 2 at a different baud rate!"); 23 | sss2.flush(); 24 | sss2.end(); 25 | 26 | Serial.println("Repeating in a few seconds ...); 27 | delay(5000); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureEnvironment": { 3 | "PICO_SDK_PATH": "/Users/kb5mu/pico/pico-sdk" 4 | }, 5 | "cmake.configureOnOpen": true, 6 | // These settings tweaks to the cmake plugin will ensure 7 | // that you debug using cortex-debug instead of trying to launch 8 | // a Pico binary on the host 9 | "cmake.statusbar.advanced": { 10 | "debug": { 11 | "visibility": "hidden" 12 | }, 13 | "launch": { 14 | "visibility": "hidden" 15 | }, 16 | //"build": { 17 | // "visibility": "hidden" 18 | //}, 19 | "buildTarget": { 20 | "visibility": "hidden" 21 | } 22 | }, 23 | "cmake.buildBeforeRun": true, 24 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 25 | "cortex-debug.openocdPath": "/Users/kb5mu/Documents/pico/openocd/src/openocd", 26 | "cmake.sourceDirectory": "${workspaceFolder}" 27 | } -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generated Cmake Pico project file 2 | 3 | cmake_minimum_required(VERSION 3.12) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_SYSTEM_NAME Generic) 8 | 9 | # initalize pico_sdk from installed location 10 | # (note this can come from environment, CMake cache etc) 11 | set(PICO_SDK_PATH "/Users/kb5mu/Documents/pico/pico-sdk") 12 | 13 | # Pull in Pico SDK (must be before project) 14 | include(pico_sdk_import.cmake) 15 | 16 | project(SSS_test C CXX) 17 | 18 | # Initialise the Pico SDK 19 | pico_sdk_init() 20 | 21 | # Add executable. Default name is the project name, version 0.1 22 | 23 | add_executable(SSS_test 24 | SSS_test.c 25 | ) 26 | 27 | pico_set_program_name(SSS_test "SSS_test") 28 | pico_set_program_version(SSS_test "0.1") 29 | 30 | pico_enable_stdio_uart(SSS_test 1) 31 | pico_enable_stdio_usb(SSS_test 0) 32 | #pico_enable_stdio_semihosting(SSS_test 1) 33 | 34 | # Add the standard library to the build 35 | target_link_libraries(SSS_test pico_stdlib) 36 | 37 | pico_add_extra_outputs(SSS_test) 38 | 39 | -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Pico Debug @ main", 9 | "type":"cortex-debug", 10 | "cwd": "${workspaceRoot}", 11 | "executable": "${command:cmake.launchTargetPath}", 12 | "request": "launch", 13 | // Connect to an already running OpenOCD instance 14 | // "servertype": "external", 15 | // "gdbTarget": "localhost:3333", 16 | // or try to launch our own OpenOCD instance 17 | "servertype": "openocd", 18 | //"gdbpath" : "arm-none-eabi-gdb", 19 | 20 | "configFiles": [ 21 | "interface/picoprobe.cfg", 22 | "target/rp2040.cfg" 23 | ], 24 | "svdFile": "/Users/kb5mu/Documents/pico/pico-sdk/src/rp2040/hardware_regs/rp2040.svd", 25 | "runToMain": true, 26 | // Work around for stopping at main on restart 27 | "postRestartCommands": [ 28 | "break main", 29 | "continue" 30 | ], 31 | "searchDir": ["/Users/kb5mu/Documents/pico/openocd/tcl"], 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /SlowSoftSerial/examples/SerialPassthrough/SerialPassthrough.ino: -------------------------------------------------------------------------------- 1 | /* 2 | SerialPassthrough sketch 3 | 4 | Some boards, like the Arduino 101, the MKR1000, Zero, or the Micro, have one 5 | hardware serial port attached to Digital pins 0-1, and a separate USB serial 6 | port attached to the IDE Serial Monitor. This means that the "serial 7 | passthrough" which is possible with the Arduino UNO (commonly used to interact 8 | with devices/shields that require configuration via serial AT commands) will 9 | not work by default. 10 | 11 | This sketch allows you to emulate the serial passthrough behaviour. Any text 12 | you type in the IDE Serial monitor will be written out to the serial port on 13 | Digital pins 0 and 1, and vice-versa. 14 | 15 | On the 101, MKR1000, Zero, and Micro, "Serial" refers to the USB Serial port 16 | attached to the Serial Monitor, and "Serial1" refers to the hardware serial 17 | port attached to pins 0 and 1. This sketch will emulate Serial passthrough 18 | using those two Serial ports on the boards mentioned above, but you can change 19 | these names to connect any two serial ports on a board that has multiple ports. 20 | 21 | created 23 May 2016 22 | by Erik Nyquist 23 | 24 | modified for SlowSoftSerial 2019-12-01 Paul Williamson 25 | */ 26 | 27 | #include "SlowSoftSerial.h" 28 | 29 | SlowSoftSerial sss(0,1); 30 | 31 | void setup() { 32 | Serial.begin(9600); 33 | sss.begin(9600); 34 | } 35 | 36 | void loop() { 37 | if (Serial.available()) { // If anything comes in Serial (USB), 38 | sss.write(Serial.read()); // read it and send it out sss (pins 0 & 1) 39 | } 40 | 41 | if (sss.available()) { // If anything comes in sss (pins 0 & 1) 42 | Serial.write(sss.read()); // read it and send it out Serial (USB) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SlowSoftSerial/examples/SendPoemDemo/SendPoemDemo.ino: -------------------------------------------------------------------------------- 1 | #include "SlowSoftSerial.h" 2 | 3 | #define BAUDRATE 9600 4 | 5 | #define RX_PIN 0 6 | #define TX_PIN 1 7 | #define CTS_PIN 18 8 | 9 | SlowSoftSerial sss((uint8_t)RX_PIN, (uint8_t)TX_PIN); 10 | 11 | //******************************************************** 12 | // 13 | // Main sketch 14 | // 15 | //******************************************************** 16 | 17 | const char poem[] = 18 | "\n\n\nI met a traveler from an antique land\n" 19 | "Who said: two vast and trunkless legs of stone\n" 20 | "Stand in the desert . . . Near them, on the sand,\n" 21 | "Half sunk, a shattered visage lies, whose frown,\n" 22 | "And wrinkled lip, and sneer of cold command,\n" 23 | "Tell that its sculptor well those passions read\n" 24 | "Which yet survive, stamped on these lifeless things,\n" 25 | "The hand that mocked them, and the heart that fed:\n" 26 | "And on the pedestal these words appear:\n" 27 | "\"My name is Ozymandias, king of kings:\n" 28 | "Look on my works, ye Mighty, and despair!\"\n" 29 | "Nothing beside remains. Round the decay\n" 30 | "Of that colossal wreck, boundless and bare\n" 31 | "The lone and level sands stretch far away.\n "; 32 | 33 | void send_poem_randomly(void) { 34 | int len = strlen(poem) - 5; 35 | int i = 0; 36 | int n, j; 37 | long t; 38 | 39 | while (i < len) { 40 | t = random(0,150000); // microseconds 41 | n = random(1,5); 42 | if (t > 0) { 43 | delayMicroseconds(t); 44 | } 45 | for (j=0; j < n; j++) { 46 | Serial.write(poem[i]); 47 | sss.write(poem[i++]); 48 | } 49 | } 50 | } 51 | 52 | void send_poem_fast(void) { 53 | int len = strlen(poem); 54 | int i = 0; 55 | 56 | for (i=0; i < len; i++) { 57 | Serial.write(poem[i]); 58 | sss.write(poem[i]); 59 | } 60 | } 61 | 62 | void setup() { 63 | sss.begin(BAUDRATE); 64 | Serial.begin(BAUDRATE); 65 | 66 | while (!Serial); 67 | 68 | sss.attachCts((uint8_t)CTS_PIN); 69 | } 70 | 71 | 72 | void loop() { 73 | 74 | while (1) { 75 | send_poem_randomly(); 76 | send_poem_fast(); 77 | delay(2000); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading PICO SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /SlowSoftSerial/examples/VariousParameters/VariousParameters.ino: -------------------------------------------------------------------------------- 1 | #include "SlowSoftSerial.h" 2 | 3 | // This sketch sends at all the data bits, parity, and stop bits combinations 4 | // that SlowSoftSerial supports. If you watch the output with a terminal 5 | // emulator at any one setting, it will very probably become confused by all 6 | // the errors, and display even the correctly received characters incorrectly. 7 | // This sketch may only be useful with a serial analyzer to understand the 8 | // output. 9 | 10 | #define NUM_MODES 60 11 | 12 | int dps[NUM_MODES] = { 13 | SSS_SERIAL_5N1, SSS_SERIAL_6N1, SSS_SERIAL_7N1, SSS_SERIAL_8N1, 14 | SSS_SERIAL_5N2, SSS_SERIAL_6N2, SSS_SERIAL_7N2, SSS_SERIAL_8N2, 15 | SSS_SERIAL_5E1, SSS_SERIAL_6E1, SSS_SERIAL_7E1, SSS_SERIAL_8E1, 16 | SSS_SERIAL_5E2, SSS_SERIAL_6E2, SSS_SERIAL_7E2, SSS_SERIAL_8E2, 17 | SSS_SERIAL_5O1, SSS_SERIAL_6O1, SSS_SERIAL_7O1, SSS_SERIAL_8O1, 18 | SSS_SERIAL_5O2, SSS_SERIAL_6O2, SSS_SERIAL_7O2, SSS_SERIAL_8O2, 19 | SSS_SERIAL_5M1, SSS_SERIAL_6M1, SSS_SERIAL_7M1, SSS_SERIAL_8M1, 20 | SSS_SERIAL_5M2, SSS_SERIAL_6M2, SSS_SERIAL_7M2, SSS_SERIAL_8M2, 21 | SSS_SERIAL_5S1, SSS_SERIAL_6S1, SSS_SERIAL_7S1, SSS_SERIAL_8S1, 22 | SSS_SERIAL_5S2, SSS_SERIAL_6S2, SSS_SERIAL_7S2, SSS_SERIAL_8S2, 23 | SSS_SERIAL_5N15, SSS_SERIAL_6N15, SSS_SERIAL_7N15, SSS_SERIAL_8N15, 24 | SSS_SERIAL_5E15, SSS_SERIAL_6E15, SSS_SERIAL_7E15, SSS_SERIAL_8E15, 25 | SSS_SERIAL_5O15, SSS_SERIAL_6O15, SSS_SERIAL_7O15, SSS_SERIAL_8O15, 26 | SSS_SERIAL_5M15, SSS_SERIAL_6M15, SSS_SERIAL_7M15, SSS_SERIAL_8M15, 27 | SSS_SERIAL_5S15, SSS_SERIAL_6S15, SSS_SERIAL_7S15, SSS_SERIAL_8S15, 28 | 29 | }; 30 | 31 | const char *dps_names[NUM_MODES] = { 32 | "5N1", "6N1", "7N1", "8N1", 33 | "5N2", "6N2", "7N2", "8N2", 34 | "5E1", "6E1", "7E1", "8E1", 35 | "5E2", "6E2", "7E2", "8E2", 36 | "5O1", "6O1", "7O1", "8O1", 37 | "5O2", "6O2", "7O2", "8O2", 38 | "5M1", "6M1", "7M1", "8M1", 39 | "5M2", "6M2", "7M2", "8M2", 40 | "5S1", "6S1", "7S1", "8S1", 41 | "5S2", "6S2", "7S2", "8S2", 42 | "5N15", "6N15", "7N15", "8N15", 43 | "5E15", "6E15", "7E15", "8E15", 44 | "5O15", "6O15", "7O15", "8O15", 45 | "5M15", "6M15", "7M15", "8M15", 46 | "5S15", "6S15", "7S15", "8S15", 47 | }; 48 | 49 | 50 | SlowSoftSerial sss(0, 1); 51 | 52 | void setup() { 53 | // Set up the USB serial port for monitoring status 54 | Serial.begin(9600); 55 | 56 | while (!Serial); // wait for the USB to start up 57 | } 58 | 59 | void loop() { 60 | int mode; 61 | int chr; 62 | 63 | for (mode=0; mode < NUM_MODES; mode++) { 64 | Serial.print("Sending at "); 65 | Serial.println(dps_names[mode]); 66 | 67 | sss.begin(9600, dps[mode]); 68 | sss.print("\nTesting "); 69 | sss.print(dps_names[mode]); 70 | sss.print(" now\n"); 71 | sss.flush(); 72 | 73 | // If you have a loopback jumper installed, receive what 74 | // we just transmitted (it'll be in the buffer, up to 64). 75 | // However, note that the ASCII characters we sent won't fit 76 | // in 5-bit serial words at all, and only the digits will 77 | // fit in 6-bit serial words, so only the 7-bit and 8-bit 78 | // messages will be readable in the serial monitor. 79 | while (sss.available()) { 80 | chr = sss.read(); 81 | if (isprint(chr) || isspace(chr)) { 82 | Serial.write(chr); 83 | } else { 84 | Serial.write('<'); 85 | Serial.print(chr, HEX); 86 | Serial.write('>'); 87 | } 88 | } 89 | 90 | sss.end(); 91 | 92 | delay(100); 93 | } 94 | 95 | Serial.println("Repeating in a few seconds ..."); 96 | delay(5000); 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlowSoftSerial serial library for Teensyduino 2 | 3 | SlowSoftSerial is another alternative serial port library for 4 | a Teensy 3.x or 4.x board under the Arduino environment. 5 | 6 | It was originally created for the 7 | [Cadetwriter](https://github.com/IBM-1620/Cadetwriter) project, 8 | because the hardware serial ports on Teensy 3.5 don't work at 9 | standard baud rates slower than 1200 baud, and AltSoftSerial 10 | doesn't work on the same pins as the Serial1 UART or at all of 11 | the needed word length, parity, and stop bit settings. 12 | 13 | ## Features 14 | 15 | * Wide range of (slower) baud rates from 1 baud to 9600+ baud, 16 | all with essentially perfect accuracy 17 | 18 | * RX and TX on ANY two GPIO pins on the Teensy 19 | 20 | * Simultaneous receive and transmit 21 | 22 | * Full range of data word length, parity, and stop bit settings 23 | per the Arduino serial API definition: 5N1, 6N1, 7N1, 8N1, 5N2, 24 | 6N2, 7N2, 8N2, 5E1, 6E1, 7E1, 8E1, 5E2, 6E2, 7E2, 8E2, 5O1, 6O1, 25 | 7O1, 8O1, 5O2, 6O2, 7O2, and 8O2 26 | 27 | * Additional support for 1.5 stop bit modes: 5N15, 6N15, 7N15, 28 | 8N15, 5E15, 6E15, 7E15, 8E15, 5O15, 6O15, 7O15, and 8O15 29 | 30 | * Additional support for mark and space parity modes: 5M1, 6M1, 31 | 7M1, 8M1, 5M15, 6M15, 7M15, 8M15, 5M2, 6M2, 7M2, 8M2, 5S1, 6S1, 32 | 7S1, 8S1, 5S15, 6S15, 7S15, 8S15, 5S2, 6S2, 7S2, 8S2 33 | 34 | * Arbitrary non-standard baud rates like 45.45 baud or 456.78 baud 35 | 36 | * Support for inverted signaling voltages 37 | 38 | * Imposes minimal (< 3 microsecond) interrupt latency on other libraries 39 | 40 | * Can work simultaneously with USB Serial, hardware UARTs Serial1 - Serial6, 41 | and AltSoftSerial 42 | 43 | * Standard Arduino Serial API interface, so most everything works 44 | the way you expect 45 | 46 | * Optional hardware handshaking (CTS) on transmit on any GPIO pin 47 | 48 | ## Limitations 49 | 50 | * Sensitive to interrupt latency from other libraries. Speed limit 51 | of 9600 baud assumes other libraries impose up to 15 microseconds of 52 | interrupt latency. Higher speeds (up to about 28800 on Teensy 3.5 or 53 | 115200 on Teensy 4.0) are possible if no other libraries are adding 54 | latency. 55 | 56 | * Uses two of the Periodic Interrupt Timers (of which there are only 57 | four on the Teensy 3.x or 4.0) 58 | 59 | * Only one active SlowSoftSerial port at a time is currently supported, 60 | but you can have multiple ports defined and switch between them. 61 | 62 | * Parity is not checked on receive, because the Arduino API has no way 63 | to report parity errors. 64 | 65 | * No support (yet) for built-in hardware flow control handshaking on 66 | receive (because Cadetwriter does this at the application level). 67 | 68 | * No support (yet) for built-in software XON/XOFF flow control in 69 | either direction. 70 | 71 | * No support for 9-bit or longer characters. 72 | 73 | * The first transmitted character after a pause is delayed an extra 74 | bit time (half a bit time for 1.5 stop bit modes) just to keep the 75 | code prettier. (Buffered characters are sent with no extra delay.) 76 | 77 | ## Installation 78 | 79 | Assuming you're already programming a Teensy 3.x or 4.0 board under the 80 | Arduino IDE environment, 81 | 82 | * Clone or download this repository 83 | 84 | * Copy or move the SlowSoftSerial folder into your Arduino libraries 85 | directory in your Arduino sketchbook. You can find or change the 86 | location of your sketchbook in the Arduino IDE's Preferences, under 87 | Settings. 88 | 89 | * Restart the Arduino IDE 90 | 91 | ## Documentation 92 | 93 | See [SlowSoftSerial Design Notes](SlowSoftSerial%20design%20notes.pdf) 94 | 95 | ## Testing 96 | 97 | A setup for mostly-automated testing of SlowSoftSerial is given in 98 | the test directory. 99 | ## Examples 100 | 101 | Example sketches demonstrating SlowSoftSerial port are included in 102 | the library. Once you've installed the library, you can open the 103 | examples from the Files, Examples menu in the Arduino IDE. 104 | 105 | * __SendPoemDemo__ transmits (only) through a SlowSoftSerial port. 106 | It first sends the poem in random short bursts, exercising the 107 | port's ability to start and stop transmitting on any character 108 | boundary. Then it transmits the same poem at full speed, and repeats 109 | forever. It simultaneously sends the same characters to the USB Serial 110 | port, so you can monitor its progress in the Arduino IDE's serial 111 | monitor. The demo enables CTS hardware handshaking on transmit. 112 | 113 | * __SerialPassthrough__ connects a SlowSoftSerial port to the USB 114 | Serial port, in both directions. Wire up your port to a terminal 115 | emulator and you can type back and forth between the terminal emulator 116 | and the Arduino IDE's serial monitor. Try pasting a longer text into 117 | the terminal emulator (but note, there is no receive flow control 118 | built in to SlowSoftSerial at this time). 119 | 120 | * __DuelingPorts__ demonstrates the use of multiple SlowSoftSerial 121 | ports (with only one being active at a time). 122 | 123 | * __VariousParameters__ demonstrates the use of all the various 124 | data word length, parity type, and stop bit length parameters 125 | supported by SlowSoftSerial. It sends a short text message with 126 | each parameter setting, while simultaneously trying to receive 127 | with the same setting. You can watch the transmissions with a 128 | terminal emulator or serial analyzer, or you can hook up a jumper 129 | between the RX and TX pins and see the results in the USB serial 130 | monitor. 131 | 132 | See also the test setup in the test directory. 133 | 134 | ## License 135 | 136 | This project is licensed under GPLv3. 137 | 138 | ## Maturity = Beta 139 | 140 | SlowSoftSerial has been bench tested on Teensy 3.5 and Teensy 4.0, 141 | and has been successfully integrated with the Cadetwriter firmware. 142 | It has been subjected to automated tests of every supported serial 143 | port configuration, at standard baud rates from 45.45 to 19200. 144 | However, it has not yet been battle tested in the field. 145 | -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/SlowSoftSerial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Definitions for databits, parity, and stopbits configuration word. 4 | // These are taken from the official Arduino API, but I've had to rename 5 | // them because other serial libraries are not consistent with these 6 | // official values. 7 | #define SSS_SERIAL_PARITY_EVEN (0x1ul) 8 | #define SSS_SERIAL_PARITY_ODD (0x2ul) 9 | #define SSS_SERIAL_PARITY_NONE (0x3ul) 10 | #define SSS_SERIAL_PARITY_MARK (0x4ul) 11 | #define SSS_SERIAL_PARITY_SPACE (0x5ul) 12 | #define SSS_SERIAL_PARITY_MASK (0xFul) 13 | 14 | #define SSS_SERIAL_STOP_BIT_1 (0x10ul) 15 | #define SSS_SERIAL_STOP_BIT_1_5 (0x20ul) 16 | #define SSS_SERIAL_STOP_BIT_2 (0x30ul) 17 | #define SSS_SERIAL_STOP_BIT_MASK (0xF0ul) 18 | 19 | #define SSS_SERIAL_DATA_5 (0x100ul) 20 | #define SSS_SERIAL_DATA_6 (0x200ul) 21 | #define SSS_SERIAL_DATA_7 (0x300ul) 22 | #define SSS_SERIAL_DATA_8 (0x400ul) 23 | #define SSS_SERIAL_DATA_MASK (0xF00ul) 24 | 25 | #define SSS_SERIAL_5N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 26 | #define SSS_SERIAL_6N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 27 | #define SSS_SERIAL_7N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 28 | #define SSS_SERIAL_8N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 29 | #define SSS_SERIAL_5N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 30 | #define SSS_SERIAL_6N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 31 | #define SSS_SERIAL_7N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 32 | #define SSS_SERIAL_8N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 33 | #define SSS_SERIAL_5E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 34 | #define SSS_SERIAL_6E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 35 | #define SSS_SERIAL_7E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 36 | #define SSS_SERIAL_8E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 37 | #define SSS_SERIAL_5E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 38 | #define SSS_SERIAL_6E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 39 | #define SSS_SERIAL_7E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 40 | #define SSS_SERIAL_8E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 41 | #define SSS_SERIAL_5O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 42 | #define SSS_SERIAL_6O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 43 | #define SSS_SERIAL_7O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 44 | #define SSS_SERIAL_8O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 45 | #define SSS_SERIAL_5O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 46 | #define SSS_SERIAL_6O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 47 | #define SSS_SERIAL_7O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 48 | #define SSS_SERIAL_8O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 49 | #define SSS_SERIAL_5M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 50 | #define SSS_SERIAL_6M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 51 | #define SSS_SERIAL_7M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 52 | #define SSS_SERIAL_8M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 53 | #define SSS_SERIAL_5M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 54 | #define SSS_SERIAL_6M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 55 | #define SSS_SERIAL_7M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 56 | #define SSS_SERIAL_8M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 57 | #define SSS_SERIAL_5S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 58 | #define SSS_SERIAL_6S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 59 | #define SSS_SERIAL_7S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 60 | #define SSS_SERIAL_8S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 61 | #define SSS_SERIAL_5S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 62 | #define SSS_SERIAL_6S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 63 | #define SSS_SERIAL_7S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 64 | #define SSS_SERIAL_8S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 65 | 66 | #define SSS_SERIAL_5N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 67 | #define SSS_SERIAL_6N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 68 | #define SSS_SERIAL_7N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 69 | #define SSS_SERIAL_8N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 70 | #define SSS_SERIAL_5E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 71 | #define SSS_SERIAL_6E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 72 | #define SSS_SERIAL_7E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 73 | #define SSS_SERIAL_8E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 74 | #define SSS_SERIAL_5O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 75 | #define SSS_SERIAL_6O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 76 | #define SSS_SERIAL_7O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 77 | #define SSS_SERIAL_8O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 78 | #define SSS_SERIAL_5M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 79 | #define SSS_SERIAL_6M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 80 | #define SSS_SERIAL_7M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 81 | #define SSS_SERIAL_8M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 82 | #define SSS_SERIAL_5S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 83 | #define SSS_SERIAL_6S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 84 | #define SSS_SERIAL_7S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 85 | #define SSS_SERIAL_8S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 86 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Packet-Based Test Framework for SlowSoftSerial 2 | 3 | SlowSoftSerial is a bit-banged serial port driver that supports a wide variety of baud rates, parity settings, stop bits, etc. Testing all of these modes is quite tedious to do by hand. This test framework is an attempt to test SlowSoftSerial more thoroughly by automating the process. 4 | 5 | The idea is to connect two microcontrollers together through serial ports. One device is a Teensy (or other supported target) running SlowSoftSerial, and is referred to as the unit under test or UUT. The other device could be anything with a serial port, and is referred to as the controller. Both devices start with a standard serial port configuration (9600 8N1), and exchange a stream of framed, CRC-checked packets. The controller sends command packets and the UUT responds. Some command packets include instructions to change baud rate, parity, stop bits, etc., so that the controller can sequence the UUT through its repertoire of modes. The UUT always acknowledges using the old settings before making the specified change, so that the controller always knows what's happening. 6 | 7 | The goal is that, when every test is passing, the operator need only monitor status reports from the controller to verify that all is well. We assume that when things go wrong, the operator is able to monitor the serial communications between the UUT and the controller and debug. No attempt is made to enable the controller to diagnose problems on its own, or to correct them on the fly. The controller is only expected to detect problems, report the fact, and stop. 8 | 9 | Initial work was done with a Raspberry Pi Pico board (first generation) with the RP2040 microcontroller as the controller, since it has hardware UART support for baud rates in SlowSoftSerial's range. This is a wholly independent implementation of a serial port, which helps to validate SlowSoftSerial's implementation. However, the RP2040's UART API does not support 1.5 stop bits, or mark or space parity, so another approach will be needed to exercise all the modes of SlowSoftSerial. This could be done with the programmable I/O (PIO) controller on the RP2040 in place of the UART, but that implementation would likely be buggier than SlowSoftSerial. I haven't been able to find another microcontroller or computer serial port that is flexible enough to test everything in SlowSoftSerial. 10 | 11 | The fallback is to use a second Teensy running SlowSoftSerial as the test controller. This is not an independent implementation in any sense, though. To achieve some degree of independence, the data in both directions is captured using a Saleae logic analyzer. The async serial analyzer provided by Saleae is helpful in manually confirming many details of the various configurations. To make this __much__ easier, a plug-in for Saleae Logic 2 was written to display test protocol packets based on the characters found by Saleae's async serial analyzer. [SlowSoftSerial Test Packets Decoder](https://github.com/MustBeArt/SlowSoftSerial-test-packets-decoder) 12 | 13 | Once again, though, this configuration is not flexible enough to fully test SlowSoftSerial, because of limitations in Saleae's async analyzer. So, the raw data captured by the logic analyzer is exported in Saleae's Logic 2.0 binary format, and analyzed in detail offline by the Python script found in the [autotest-analyze](https://github.com/MustBeArt/SlowSoftSerial/tree/testrig/test/autotest-analyze) directory. That program understands enough of the test protocol to follow along as the controller walks the UUT through any combination of baud rates and serial configurations supported by SlowSoftSerial. 14 | 15 | ## Packet Format 16 | 17 | ### Framing and Data Transparency 18 | 19 | All packets are sent using a binary-transparent framing format based on that used in [SLIP](https://tools.ietf.org/html/rfc1055) (Serial Line IP) and [KISS TNC](http://www.ax25.net/kiss.aspx) protocols. These formats use a special character that is only transmitted to mark the beginning and the end of every frame, which is called FEND for _Frame End_. When that character appears in the data stream, it is replaced with a two-character sequence, introduced by a special character that is only transmitted for this purpose. That character is called FESC for _Frame Escape_, and it also must be replaced with a two-character sequence when it appears in the data stream. The second character of a two-character sequence is either TFEND for _Transposed Frame End_ or TFESC for _Transposed Frame Escape_. TFEND and TFESC do not need to be treated specially when they appear in the data stream; they only have special meaning when immediately following a FESC character. 20 | 21 | The standard values for these special characters are 0xC0, 0xDB, 0xDC, and 0xDD. These values were probably chosen because they don't appear in ASCII text, but there's a problem. They can only be used when the data word width is at least 8 bits. SlowSoftSerial supports word widths down to 5 bits, so we must choose non-standard values that fit within 5 bits. Here are the ones we use: 22 | 23 | | Char | Code | 24 | |-------|------| 25 | | FEND | 0x10 | 26 | | FESC | 0x1B | 27 | | TFEND | 0x1C | 28 | | TFESC | 0x1D | 29 | 30 | ### Error Checking 31 | 32 | All packets are sent with a 32-bit CRC error check at the end. Packets that do not pass the CRC check are ignored. 33 | 34 | The CRC must also be transmitted in a way that is compatible with data word widths as narrow as 5 bits. We transmit the CRC as 8 characters, each containing 4 bits of the CRC. The CRC bits are the least significant bits in the characters, and the remaining 1 to 4 bits in the character are zero. The first character contains the four most significant bits of the CRC, and so on; the last character of the packet contains the four least significant bits of the CRC. 35 | 36 | That means that the 0x10 bit of every character in the CRC encoding is 0. Since that bit is a 1 in every special character used for framing, the characters of the CRC need not be processed for data transparency in the framing process. 37 | 38 | We use this 8-character encoding of the CRC even when using a data word width of 8 bits. 39 | 40 | ### Packet Format 41 | 42 | The first character (after the leading FEND) of each packet is a command/response indicator. 43 | 44 | |Value|Description| 45 | |-----|-----------| 46 | | 0x00| Command sent from controller to UUT 47 | | 0x01| Response sent from UUT to controller 48 | | 0x02| Debug info sent by UUT, not requested by controller| 49 | |Other values| Reserved| 50 | 51 | Normally, the UUT is silent unless responding to a command from the controller, and the command specifies what response is required. However, the UUT may send a debug packet at any time. 52 | 53 | The second character is a command code. The controller may send any command code, 54 | but the UUT is required to respond with the same command code it is responding to. The rest of the response depends on the command. 55 | 56 | |Value|Name|Description| 57 | |-----|----|-----------| 58 | | 0x00|NOP| No operation.| 59 | | 0x01|ID|Request UUT version info.| 60 | | 0x02|ECHO|CTLR sends any number of arbitrary characters, and the UUT echoes them back verbatim.| 61 | | 0x03|BABBLE|CTLR sends a length, and the UUT sends back that many pseudo-random characters.| 62 | | 0x04|PARAMS|Change any or all of the baud rate, word width, stop bits, and parity type.| 63 | |0x1F|EXT|Reserved for expansion of the command codes.| 64 | 65 | ### Packet Definitions 66 | 67 | #### NOP Packet 68 | The NOP packet does nothing but trigger an acknowledgement from the UUT. 69 | 70 | The CTLR may include any number of additional characters in the NOP packet. The UUT includes such characters in the CRC check, but does not process them or echo them back. 71 | 72 | If the CRC check passes, the UUT shall respond with a two-byte NOP response packet. 73 | 74 | #### ID Packet 75 | The ID packet requests version information from the UUT. 76 | 77 | If the CRC check passes, the UUT shall respond with an ID response packet. After the command code, it shall include a human-readable ASCII message describing its platform, SlowSoftSerial library version, and UUT program version. (This is only useful with 7- or 8-bit word widths.) 78 | 79 | #### ECHO Packet 80 | The ECHO packet requires the UUT to send back the entire contents of the packet. 81 | 82 | If the CRC check passes, the UUT shall respond with an ECHO response packet. The packet shall be identical to the command packet, except for the command/response indicator. 83 | 84 | The UUT shall be able to respond to ECHO packets of at least 10,000 characters. 85 | 86 | The UUT is not permitted to begin responding before checking the CRC, so it may not stream the response without first buffering the entire packet. 87 | 88 | #### BABBLE Packet 89 | The BABBLE packet requires the UUT to send a pseudo-random block of characters of a specified length. The UUT may use any method to generate the characters. The CTLR relies on the CRC to check their correctness. 90 | 91 | The CTLR encodes the length the same way CRCs are encoded, as 8 characters each containing 4 bits of the value, most significant nibble first. 92 | 93 | #### PARAMS Packet 94 | The PARAMS packet requires the UUT to first respond with a PARAMS response packet, echoing back the parameters received, and then switch to using the specified baud rate, data word width, number of stop bits, and parity type. 95 | 96 | | Field | Characters | Description | 97 | |-------|------------|-------------| 98 | | Baud rate | 8 | Baud rate in millibauds per second, encoded the same way as CRCs. For example, 9600 baud is 9,600,000 millibaud, which is 00927c00 in hex, so it would be encoded as characters 00 00 09 02 07 0c 00 00 | 99 | | Config | 8 | Serial configuration word as defined in SlowSoftSerial.h, encoded the same way as CRCs. For example, 8N1 is 0413 in hex, so it would be encoded as characters 00 00 00 00 00 04 01 03 | 100 | 101 | -------------------------------------------------------------------------------- /SlowSoftSerial/SlowSoftSerial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Stream.h" 5 | #include "IntervalTimer.h" 6 | 7 | #define _SSS_TX_BUFFER_SIZE 64 8 | #define _SSS_RX_BUFFER_SIZE 64 9 | 10 | #define _SSS_MAX_OPTABLE_SIZE 48 // 4 ops per bit, 8E2 is 12 bits long 11 | 12 | #define _SSS_MIN_BAUDRATE 1.0 // arbitrary; don't divide by zero. 13 | 14 | // Definitions for an extension to the ::end() method 15 | #define SSS_RETAIN_PINS (true) 16 | #define SSS_RELEASE_PINS (false) 17 | 18 | // Definitions for databits, parity, and stopbits configuration word. 19 | // These are taken from the official Arduino API, but I've had to rename 20 | // them because other serial libraries are not consistent with these 21 | // official values. 22 | #define SSS_SERIAL_PARITY_EVEN (0x1ul) 23 | #define SSS_SERIAL_PARITY_ODD (0x2ul) 24 | #define SSS_SERIAL_PARITY_NONE (0x3ul) 25 | #define SSS_SERIAL_PARITY_MARK (0x4ul) 26 | #define SSS_SERIAL_PARITY_SPACE (0x5ul) 27 | #define SSS_SERIAL_PARITY_MASK (0xFul) 28 | 29 | #define SSS_SERIAL_STOP_BIT_1 (0x10ul) 30 | #define SSS_SERIAL_STOP_BIT_1_5 (0x20ul) 31 | #define SSS_SERIAL_STOP_BIT_2 (0x30ul) 32 | #define SSS_SERIAL_STOP_BIT_MASK (0xF0ul) 33 | 34 | #define SSS_SERIAL_DATA_5 (0x100ul) 35 | #define SSS_SERIAL_DATA_6 (0x200ul) 36 | #define SSS_SERIAL_DATA_7 (0x300ul) 37 | #define SSS_SERIAL_DATA_8 (0x400ul) 38 | #define SSS_SERIAL_DATA_MASK (0xF00ul) 39 | 40 | #define SSS_SERIAL_5N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 41 | #define SSS_SERIAL_6N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 42 | #define SSS_SERIAL_7N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 43 | #define SSS_SERIAL_8N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 44 | #define SSS_SERIAL_5N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 45 | #define SSS_SERIAL_6N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 46 | #define SSS_SERIAL_7N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 47 | #define SSS_SERIAL_8N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 48 | #define SSS_SERIAL_5E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 49 | #define SSS_SERIAL_6E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 50 | #define SSS_SERIAL_7E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 51 | #define SSS_SERIAL_8E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 52 | #define SSS_SERIAL_5E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 53 | #define SSS_SERIAL_6E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 54 | #define SSS_SERIAL_7E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 55 | #define SSS_SERIAL_8E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 56 | #define SSS_SERIAL_5O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 57 | #define SSS_SERIAL_6O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 58 | #define SSS_SERIAL_7O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 59 | #define SSS_SERIAL_8O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 60 | #define SSS_SERIAL_5O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 61 | #define SSS_SERIAL_6O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 62 | #define SSS_SERIAL_7O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 63 | #define SSS_SERIAL_8O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 64 | #define SSS_SERIAL_5M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 65 | #define SSS_SERIAL_6M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 66 | #define SSS_SERIAL_7M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 67 | #define SSS_SERIAL_8M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 68 | #define SSS_SERIAL_5M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 69 | #define SSS_SERIAL_6M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 70 | #define SSS_SERIAL_7M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 71 | #define SSS_SERIAL_8M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 72 | #define SSS_SERIAL_5S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 73 | #define SSS_SERIAL_6S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 74 | #define SSS_SERIAL_7S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 75 | #define SSS_SERIAL_8S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 76 | #define SSS_SERIAL_5S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 77 | #define SSS_SERIAL_6S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 78 | #define SSS_SERIAL_7S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 79 | #define SSS_SERIAL_8S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 80 | 81 | #define SSS_SERIAL_5N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 82 | #define SSS_SERIAL_6N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 83 | #define SSS_SERIAL_7N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 84 | #define SSS_SERIAL_8N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 85 | #define SSS_SERIAL_5E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 86 | #define SSS_SERIAL_6E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 87 | #define SSS_SERIAL_7E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 88 | #define SSS_SERIAL_8E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 89 | #define SSS_SERIAL_5O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 90 | #define SSS_SERIAL_6O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 91 | #define SSS_SERIAL_7O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 92 | #define SSS_SERIAL_8O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 93 | #define SSS_SERIAL_5M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 94 | #define SSS_SERIAL_6M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 95 | #define SSS_SERIAL_7M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 96 | #define SSS_SERIAL_8M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 97 | #define SSS_SERIAL_5S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 98 | #define SSS_SERIAL_6S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 99 | #define SSS_SERIAL_7S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 100 | #define SSS_SERIAL_8S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 101 | 102 | class SlowSoftSerial : public Stream 103 | { 104 | public: 105 | SlowSoftSerial(uint8_t rxPin, uint8_t txPin, bool inverse = false); 106 | ~SlowSoftSerial() { end(); } 107 | 108 | // Note we use a floating point value for baud rate. We can do that! 109 | // And if legacy code passes an integral value, it just gets converted. 110 | void begin(double baudrate, uint16_t config); 111 | void begin(double baudrate) { begin(baudrate, SSS_SERIAL_8N1); } 112 | 113 | void end(bool releasePins); 114 | void end() { end(SSS_RELEASE_PINS); } 115 | 116 | int available(void); 117 | int availableForWrite(void); 118 | int peek(void); 119 | int read(void); 120 | void flush(void); 121 | size_t write(uint8_t); 122 | using Print::write; // pull in write(str) and write(buf, size) from Print 123 | 124 | operator bool() { return true; }; // we are always ready 125 | 126 | // listen is a SoftwareSerial thing, since it can only receive on one port. 127 | // We don't have that limitation, though we are limited by the number of timers. 128 | bool listen() { return false; } 129 | bool isListening() { return true; } 130 | 131 | // The standard Arduino serial API doesn't support handshaking, but the 132 | // Teensyduino UART API does support hardware handshaking. We adopt their 133 | // API. 134 | // void attachRts(uint8_t); not yet implemented 135 | void attachCts(uint8_t); 136 | 137 | // Unfortunately, this has to be public because of the horrific workaround 138 | // needed to register a callback with IntervalTimer or attachInterrupt. 139 | void _tx_baud_handler(void); 140 | void _tx_halfbaud_handler(void); 141 | bool _tx_halfbaud; // flag: double the interrupt rate for 1.5 stop bits case 142 | void _rx_timer_handler(void); 143 | void _rx_start_handler(void); 144 | 145 | private: 146 | static int _active_count; 147 | bool _instance_active; 148 | 149 | uint16_t _add_parity(uint8_t chr); 150 | void _fill_op_table(int rxbits, int stopbits); 151 | 152 | // port configuration 153 | double _tx_microseconds; // transmit interrupt duration (1x or 2x the baud rate) 154 | double _rx_microseconds; // receive sample duration (4x the baud rate) 155 | uint16_t _parity; // use definitions above, like ones in HardwareSerial.h 156 | uint8_t _num_bits_to_send; // includes parity and stop bit(s) but not start bit 157 | uint16_t _parity_bit; // bitmask for the parity bit; 0 if no parity 158 | int16_t _stop_bits; // bit(s) to OR in to data word 159 | uint8_t _databits_mask; // bitmask of bits that fit in the word size 160 | uint16_t _rx_shiftin_bit; // bit to OR in to data word as received bits shift in 161 | uint8_t _rxPin; 162 | uint8_t _txPin; 163 | uint8_t _rtsPin; 164 | uint8_t _ctsPin; 165 | bool _rts_attached; 166 | bool _cts_attached; 167 | bool _inverse; 168 | IntervalTimer _tx_timer; 169 | IntervalTimer _rx_timer; 170 | 171 | // transmit buffer and its variables 172 | volatile int _tx_buffer_count; 173 | int _tx_write_index; 174 | int _tx_read_index; 175 | uint16_t _tx_buffer[_SSS_TX_BUFFER_SIZE]; // contains data "as sent" with parity and stop bits 176 | 177 | // transmit state 178 | int _tx_data_word; 179 | int _tx_bit_count; 180 | bool _tx_enabled = true; 181 | bool _tx_running = false; 182 | bool _tx_baud_divider; // in 1.5 stop bit case, toggles 0 1 to halve the interrupt rate 183 | bool _tx_extra_half_stop; // flag: we need to add an extra half stop bit to this character 184 | 185 | // receive buffer and its variables 186 | volatile int _rx_buffer_count; 187 | int _rx_write_index; 188 | int _rx_read_index; 189 | uint16_t _rx_buffer[_SSS_RX_BUFFER_SIZE]; // contains data with parity (no stop bits) 190 | 191 | // receive state 192 | uint8_t _rx_op_table[_SSS_MAX_OPTABLE_SIZE]; 193 | uint8_t _rx_op; // index into the operation table 194 | uint16_t _rx_data_word; // word under construction as we receive it 195 | int _rx_bit_value; // bit value as we sample it repeatedly 196 | 197 | }; 198 | 199 | -------------------------------------------------------------------------------- /test/autotest-ctlr-sss/SlowSoftSerial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Stream.h" 5 | #include "IntervalTimer.h" 6 | 7 | #define _SSS_TX_BUFFER_SIZE 64 8 | #define _SSS_RX_BUFFER_SIZE 64 9 | 10 | #define _SSS_MAX_OPTABLE_SIZE 48 // 4 ops per bit, 8E2 is 12 bits long 11 | 12 | #define _SSS_MIN_BAUDRATE 1.0 // arbitrary; don't divide by zero. 13 | 14 | // Definitions for an extension to the ::end() method 15 | #define SSS_RETAIN_PINS (true) 16 | #define SSS_RELEASE_PINS (false) 17 | 18 | // Definitions for databits, parity, and stopbits configuration word. 19 | // These are taken from the official Arduino API, but I've had to rename 20 | // them because other serial libraries are not consistent with these 21 | // official values. 22 | #define SSS_SERIAL_PARITY_EVEN (0x1ul) 23 | #define SSS_SERIAL_PARITY_ODD (0x2ul) 24 | #define SSS_SERIAL_PARITY_NONE (0x3ul) 25 | #define SSS_SERIAL_PARITY_MARK (0x4ul) 26 | #define SSS_SERIAL_PARITY_SPACE (0x5ul) 27 | #define SSS_SERIAL_PARITY_MASK (0xFul) 28 | 29 | #define SSS_SERIAL_STOP_BIT_1 (0x10ul) 30 | #define SSS_SERIAL_STOP_BIT_1_5 (0x20ul) 31 | #define SSS_SERIAL_STOP_BIT_2 (0x30ul) 32 | #define SSS_SERIAL_STOP_BIT_MASK (0xF0ul) 33 | 34 | #define SSS_SERIAL_DATA_5 (0x100ul) 35 | #define SSS_SERIAL_DATA_6 (0x200ul) 36 | #define SSS_SERIAL_DATA_7 (0x300ul) 37 | #define SSS_SERIAL_DATA_8 (0x400ul) 38 | #define SSS_SERIAL_DATA_MASK (0xF00ul) 39 | 40 | #define SSS_SERIAL_5N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 41 | #define SSS_SERIAL_6N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 42 | #define SSS_SERIAL_7N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 43 | #define SSS_SERIAL_8N1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 44 | #define SSS_SERIAL_5N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 45 | #define SSS_SERIAL_6N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 46 | #define SSS_SERIAL_7N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 47 | #define SSS_SERIAL_8N2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 48 | #define SSS_SERIAL_5E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 49 | #define SSS_SERIAL_6E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 50 | #define SSS_SERIAL_7E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 51 | #define SSS_SERIAL_8E1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 52 | #define SSS_SERIAL_5E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 53 | #define SSS_SERIAL_6E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 54 | #define SSS_SERIAL_7E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 55 | #define SSS_SERIAL_8E2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 56 | #define SSS_SERIAL_5O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 57 | #define SSS_SERIAL_6O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 58 | #define SSS_SERIAL_7O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 59 | #define SSS_SERIAL_8O1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 60 | #define SSS_SERIAL_5O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 61 | #define SSS_SERIAL_6O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 62 | #define SSS_SERIAL_7O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 63 | #define SSS_SERIAL_8O2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 64 | #define SSS_SERIAL_5M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 65 | #define SSS_SERIAL_6M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 66 | #define SSS_SERIAL_7M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 67 | #define SSS_SERIAL_8M1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 68 | #define SSS_SERIAL_5M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 69 | #define SSS_SERIAL_6M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 70 | #define SSS_SERIAL_7M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 71 | #define SSS_SERIAL_8M2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 72 | #define SSS_SERIAL_5S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 73 | #define SSS_SERIAL_6S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 74 | #define SSS_SERIAL_7S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 75 | #define SSS_SERIAL_8S1 (SSS_SERIAL_STOP_BIT_1 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 76 | #define SSS_SERIAL_5S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 77 | #define SSS_SERIAL_6S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 78 | #define SSS_SERIAL_7S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 79 | #define SSS_SERIAL_8S2 (SSS_SERIAL_STOP_BIT_2 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 80 | 81 | #define SSS_SERIAL_5N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_5) 82 | #define SSS_SERIAL_6N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_6) 83 | #define SSS_SERIAL_7N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_7) 84 | #define SSS_SERIAL_8N15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_NONE | SSS_SERIAL_DATA_8) 85 | #define SSS_SERIAL_5E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_5) 86 | #define SSS_SERIAL_6E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_6) 87 | #define SSS_SERIAL_7E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_7) 88 | #define SSS_SERIAL_8E15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_EVEN | SSS_SERIAL_DATA_8) 89 | #define SSS_SERIAL_5O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_5) 90 | #define SSS_SERIAL_6O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_6) 91 | #define SSS_SERIAL_7O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_7) 92 | #define SSS_SERIAL_8O15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_ODD | SSS_SERIAL_DATA_8) 93 | #define SSS_SERIAL_5M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_5) 94 | #define SSS_SERIAL_6M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_6) 95 | #define SSS_SERIAL_7M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_7) 96 | #define SSS_SERIAL_8M15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_MARK | SSS_SERIAL_DATA_8) 97 | #define SSS_SERIAL_5S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_5) 98 | #define SSS_SERIAL_6S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_6) 99 | #define SSS_SERIAL_7S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_7) 100 | #define SSS_SERIAL_8S15 (SSS_SERIAL_STOP_BIT_1_5 | SSS_SERIAL_PARITY_SPACE | SSS_SERIAL_DATA_8) 101 | 102 | class SlowSoftSerial : public Stream 103 | { 104 | public: 105 | SlowSoftSerial(uint8_t rxPin, uint8_t txPin, bool inverse = false); 106 | ~SlowSoftSerial() { end(); } 107 | 108 | // Note we use a floating point value for baud rate. We can do that! 109 | // And if legacy code passes an integral value, it just gets converted. 110 | void begin(double baudrate, uint16_t config); 111 | void begin(double baudrate) { begin(baudrate, SSS_SERIAL_8N1); } 112 | 113 | void end(bool releasePins); 114 | void end() { end(SSS_RELEASE_PINS); } 115 | 116 | int available(void); 117 | int availableForWrite(void); 118 | int peek(void); 119 | int read(void); 120 | void flush(void); 121 | size_t write(uint8_t); 122 | using Print::write; // pull in write(str) and write(buf, size) from Print 123 | 124 | operator bool() { return true; }; // we are always ready 125 | 126 | // listen is a SoftwareSerial thing, since it can only receive on one port. 127 | // We don't have that limitation, though we are limited by the number of timers. 128 | bool listen() { return false; } 129 | bool isListening() { return true; } 130 | 131 | // The standard Arduino serial API doesn't support handshaking, but the 132 | // Teensyduino UART API does support hardware handshaking. We adopt their 133 | // API. 134 | // void attachRts(uint8_t); not yet implemented 135 | void attachCts(uint8_t); 136 | 137 | // Unfortunately, this has to be public because of the horrific workaround 138 | // needed to register a callback with IntervalTimer or attachInterrupt. 139 | void _tx_baud_handler(void); 140 | void _tx_halfbaud_handler(void); 141 | bool _tx_halfbaud; // flag: double the interrupt rate for 1.5 stop bits case 142 | void _rx_timer_handler(void); 143 | void _rx_start_handler(void); 144 | 145 | private: 146 | static int _active_count; 147 | bool _instance_active; 148 | 149 | uint16_t _add_parity(uint8_t chr); 150 | void _fill_op_table(int rxbits, int stopbits); 151 | 152 | // port configuration 153 | double _tx_microseconds; // transmit interrupt duration (1x or 2x the baud rate) 154 | double _rx_microseconds; // receive sample duration (4x the baud rate) 155 | uint16_t _parity; // use definitions above, like ones in HardwareSerial.h 156 | uint8_t _num_bits_to_send; // includes parity and stop bit(s) but not start bit 157 | uint16_t _parity_bit; // bitmask for the parity bit; 0 if no parity 158 | int16_t _stop_bits; // bit(s) to OR in to data word 159 | uint8_t _databits_mask; // bitmask of bits that fit in the word size 160 | uint16_t _rx_shiftin_bit; // bit to OR in to data word as received bits shift in 161 | uint8_t _rxPin; 162 | uint8_t _txPin; 163 | uint8_t _rtsPin; 164 | uint8_t _ctsPin; 165 | bool _rts_attached; 166 | bool _cts_attached; 167 | bool _inverse; 168 | IntervalTimer _tx_timer; 169 | IntervalTimer _rx_timer; 170 | 171 | // transmit buffer and its variables 172 | volatile int _tx_buffer_count; 173 | int _tx_write_index; 174 | int _tx_read_index; 175 | uint16_t _tx_buffer[_SSS_TX_BUFFER_SIZE]; // contains data "as sent" with parity and stop bits 176 | 177 | // transmit state 178 | int _tx_data_word; 179 | int _tx_bit_count; 180 | bool _tx_enabled = true; 181 | bool _tx_running = false; 182 | bool _tx_baud_divider; // in 1.5 stop bit case, toggles 0 1 to halve the interrupt rate 183 | bool _tx_extra_half_stop; // flag: we need to add an extra half stop bit to this character 184 | 185 | // receive buffer and its variables 186 | volatile int _rx_buffer_count; 187 | int _rx_write_index; 188 | int _rx_read_index; 189 | uint16_t _rx_buffer[_SSS_RX_BUFFER_SIZE]; // contains data with parity (no stop bits) 190 | 191 | // receive state 192 | uint8_t _rx_op_table[_SSS_MAX_OPTABLE_SIZE]; 193 | uint8_t _rx_op; // index into the operation table 194 | uint16_t _rx_data_word; // word under construction as we receive it 195 | int _rx_bit_value; // bit value as we sample it repeatedly 196 | 197 | }; 198 | 199 | -------------------------------------------------------------------------------- /test/autotest-analyze/autotest-analyze.py: -------------------------------------------------------------------------------- 1 | import array 2 | import string 3 | import struct 4 | import sys 5 | import typing 6 | import zlib 7 | from collections import namedtuple 8 | from typing import List, Optional, Tuple, Union 9 | 10 | DUMP_CHARACTERS: int = 0 11 | 12 | # tolerance in bauds for edge placement and glitch bursts 13 | epsilon: float = 1.0 / 16.0 14 | 15 | # default serial parameters; all traces should start with these 16 | baudrate: float = 9600.0 17 | onebaud: float = 1.0 / baudrate 18 | num_data_bits: int = 8 # could be 5, 6, 7, 8 19 | num_data_and_parity_bits: int = 8 # could be 5, 6, 7, 8, or 9 20 | num_stop_bits: float = 1.0 # could be 1, 1.5, or 2 21 | parity_type: str = "N" # could be N, O, E, M, S 22 | 23 | # Special characters for framing packets in SlowSoftSerial test protocol 24 | FEND: int = 0x10 # Frame End (and beginning) 25 | FESC: int = 0x1B # Frame Escape 26 | TFEND: int = 0x1C # Transposed Frame End 27 | TFESC: int = 0x1D # Transposed Frame Escape 28 | 29 | # initial conditions for interpreting packets 30 | in_packet: bool = False 31 | in_escape_seq: bool = False 32 | packet: bytearray = bytearray() 33 | packet_start_time: float = 0 34 | 35 | # PARAM packet decoding 36 | # 4-bit fields need 16 entries for safe lookup 37 | width_decode: List[int] = [0, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 38 | parity_decode: List[str] = ["X", "E", "O", "N", "M", "S", "X", "X", "X", "X", "X", "X", "X", "X", "X", "X"] 39 | stop_decode: List[float] = [0, 1, 1.5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 40 | 41 | # For parsing Saleae Logic 2.0 binary-format capture files. 42 | # see https://support.saleae.com/faq/technical-faq/binary-export-format-logic-2 43 | TYPE_DIGITAL = 0 44 | TYPE_ANALOG = 1 45 | expected_version = 0 46 | 47 | DigitalData = namedtuple( 48 | "DigitalData", 49 | ("initial_state", "begin_time", "end_time", "num_transitions", "transition_times"), 50 | ) 51 | 52 | 53 | def parse_digital(f: typing.BinaryIO) -> DigitalData: 54 | """Parse a logic analyzer capture file in Saleae Logic 2.0 binary format""" 55 | # Parse header 56 | identifier = f.read(8) 57 | if identifier != b"": 58 | raise Exception("Not a saleae file") 59 | 60 | version, datatype = struct.unpack("=ii", f.read(8)) 61 | 62 | if version != expected_version or datatype != TYPE_DIGITAL: 63 | raise Exception("Unexpected data type: {}".format(datatype)) 64 | 65 | # Parse digital-specific data 66 | initial_state, begin_time, end_time, num_transitions = struct.unpack("=iddq", f.read(28)) 67 | 68 | # Parse transition times 69 | transition_times = array.array("d") 70 | transition_times.fromfile(f, num_transitions) 71 | 72 | return DigitalData(initial_state, begin_time, end_time, num_transitions, transition_times) 73 | 74 | 75 | def decode_32bit_value(packet: bytearray) -> int: 76 | """Decode a 32-bit value from the 4 LSBits of the last 8 bytes, as used for 77 | both CRCs and integer parameters in the SlowSoftSerial test protocol""" 78 | 79 | if len(packet) != 8: 80 | return 0 81 | 82 | value = ( 83 | packet[-8] << 28 84 | | packet[-7] << 24 85 | | packet[-6] << 20 86 | | packet[-5] << 16 87 | | packet[-4] << 12 88 | | packet[-3] << 8 89 | | packet[-2] << 4 90 | | packet[-1] 91 | | packet[-8] & 0xF0 92 | ) 93 | return value 94 | 95 | 96 | def check_crc(packet: bytearray) -> bool: 97 | """Check the CRC in a packet in SlowSoftSerial test protocol format 98 | and return True if the CRC is good. Diagnose a bad CRC and return False.""" 99 | 100 | if len(packet) < 10: 101 | return False 102 | 103 | received = decode_32bit_value(packet[-8:]) 104 | crc = zlib.crc32(packet[:-8]) & 0xFFFFFFFF 105 | 106 | if received == crc: 107 | return True 108 | else: 109 | print("BAD CRC", hex(received), "should be", hex(crc)) 110 | return False 111 | 112 | 113 | def describe_packet(packet: bytearray, start_time: float, end_time: float) -> str: 114 | """Analyze the contents of a packet in SlowSoftSerial test protocol format 115 | and return a one-line description of the packet. Also, process the PARAMS Response 116 | packet to enable this program to follow changes in baud rate and serial configuration.""" 117 | 118 | global baudrate, onebaud, num_data_bits, num_data_and_parity_bits, num_stop_bits, parity_type 119 | 120 | # We might need end_time one day, but for now it is unused. 121 | del end_time 122 | 123 | description = f"{start_time:12.06f}: " # column-aligned timestamp for each packet (up to 99,999 seconds) 124 | 125 | if len(packet) < 10: 126 | description += "TOO SHORT" 127 | return description 128 | 129 | if not check_crc(packet): 130 | description += "BAD CRC" 131 | return description 132 | 133 | if packet[0] == 0: 134 | description += "CMD " 135 | elif packet[0] == 1: 136 | description += "RSP " 137 | elif packet[0] == 2: 138 | description += "DBG " 139 | else: 140 | description += "UNK" 141 | return description 142 | 143 | if packet[1] == 0: 144 | description += "NOP " 145 | if len(packet) > 10: 146 | description += "+" 147 | description += str(len(packet) - 10) 148 | elif packet[1] == 1: 149 | description += "ID" 150 | if packet[0] == 1 and len(packet) > 10: 151 | description += ": " 152 | for ch in packet[2:-9]: 153 | description += chr(ch) 154 | elif packet[1] == 2: 155 | description += "ECHO " 156 | if len(packet) > 10: 157 | description += "+" 158 | description += str(len(packet) - 10) 159 | elif packet[1] == 3: 160 | description += "BABBLE" 161 | if packet[0] == 0: 162 | if len(packet) != 18: 163 | description += "(wrong length)" 164 | else: 165 | description += ": " 166 | description += str(decode_32bit_value(packet[2:10])) 167 | elif packet[0] == 1: 168 | description += ": " 169 | description += str(len(packet) - 10) 170 | elif packet[1] == 4: 171 | description += "PARAMS " 172 | if len(packet) != 26: 173 | description += "(wrong length)" 174 | else: 175 | proposed_baudrate = decode_32bit_value(packet[2:10]) / 1000.0 176 | config = decode_32bit_value(packet[10:18]) 177 | width = width_decode[(config >> 8) & 0x0F] 178 | parity = parity_decode[config & 0x0F] 179 | stop = stop_decode[(config >> 4) & 0x0F] 180 | description += f"PARAMS {proposed_baudrate:5.3f} baud, {width}{parity}{stop}" 181 | 182 | if packet[0] == 1: # change takes effect after the response packet 183 | baudrate = proposed_baudrate 184 | onebaud = 1.0 / baudrate 185 | num_data_bits = width 186 | num_data_and_parity_bits = width 187 | if parity != "N": 188 | num_data_and_parity_bits += 1 189 | num_stop_bits = stop 190 | parity_type = parity 191 | elif packet[1] == 0x1F: 192 | description += "EXT " 193 | else: 194 | description += "UNK" 195 | 196 | return description 197 | 198 | 199 | def process_character(char: int, char_start_time: float, char_end_time: float) -> None: 200 | """Process a single successfully-received character""" 201 | 202 | global in_packet, packet, in_escape_seq, packet_start_time 203 | 204 | if DUMP_CHARACTERS: 205 | if char == FEND: 206 | print("🤨", end=" ") 207 | elif chr(char) in string.printable and chr(char) not in string.whitespace: 208 | print(chr(char), end=" ") 209 | else: 210 | print(f"{char:02x}", end=" ") 211 | 212 | if not in_packet: # looking for starting flag 213 | if char == FEND: # Here's the start of a packet 214 | if len(packet) != 0: # found some garbage between packets 215 | print(f"Non-packet data, {len(packet)} characters") 216 | packet = bytearray() # discard the garbage, start empty 217 | in_packet = True 218 | packet_start_time = char_start_time 219 | else: 220 | packet.extend(char.to_bytes(1, "big")) # save non-packet garbage 221 | else: 222 | if char == FEND: # here's the proper end of the packet 223 | print(describe_packet(packet, packet_start_time, char_end_time)) 224 | packet = bytearray() # discard processed packet 225 | in_packet = False 226 | else: 227 | if char == FESC: 228 | in_escape_seq = True 229 | return # this was the first of a two-byte sequence 230 | elif in_escape_seq: 231 | in_escape_seq = False 232 | if char == TFESC: # TFESC 233 | char = FESC # transpose to FESC 234 | elif char == TFEND: # TFEND 235 | char = FEND # transpose to FEND 236 | else: # ill-formed framing 237 | in_packet = False 238 | return 239 | packet.extend(char.to_bytes(1, "big")) # add the (de-escaped) character to packet 240 | 241 | 242 | def strip_parity(char: int) -> Tuple[int, bool]: 243 | """Check the parity bit (if called for in the current configuration). 244 | Return a tuple of which the first element is the data bits received 245 | (even if parity did not check) and the second is a boolean that is 246 | True when parity was correct.""" 247 | 248 | parity_bit = (char & (1 << num_data_bits)) != 0 249 | data_bits = char & ((1 << num_data_bits) - 1) 250 | computed_even_parity = bool((0x6996 >> ((data_bits ^ (data_bits >> 4)) & 0x0F)) & 0x01) 251 | 252 | if parity_type == "N": 253 | return data_bits, True 254 | elif parity_type == "M": 255 | if parity_bit == 0: 256 | return data_bits, False 257 | elif parity_type == "S": 258 | if parity_bit == 1: 259 | return data_bits, False 260 | elif parity_type == "E": 261 | if parity_bit != computed_even_parity: 262 | return data_bits, False 263 | elif parity_type == "O": 264 | if parity_bit == computed_even_parity: 265 | return data_bits, False 266 | else: 267 | print(f"Unknown parity type {parity_type}") 268 | sys.exit(1) 269 | 270 | return data_bits, True 271 | 272 | 273 | def receive_char_from(data: DigitalData, start_bit_transition: int) -> int: 274 | """Start at transition index and process one character's worth of transitions, 275 | returning the index of the presumed start bit of the next character.""" 276 | 277 | t0 = data.transition_times[start_bit_transition] 278 | transition = start_bit_transition + 1 279 | previous_tbaud = 0 280 | level_before = 1 # logical level of the signal BEFORE this transition 281 | START_BIT: int = -1 282 | state: int = START_BIT # after START_BIT, state counts up from 0 to data bits + parity bits 283 | char: int = 0 # start with an empty character; we'll OR in a 1 when we see a high bit 284 | # if parity is in use, we'll put that bit in here too, above the MSbit 285 | 286 | while transition < data.num_transitions: 287 | t = data.transition_times[transition] 288 | tbaud = (t - t0) / onebaud # time in bauds from beginning of start bit 289 | level_before = 0 if level_before else 1 # keep track of current signal value 290 | 291 | # diagnose transitions that come much too soon, with respect to baud rate 292 | if tbaud < previous_tbaud + epsilon: 293 | print(f"Glitch transition at {t=}") 294 | # do not update previous_tbaud; we don't want the glitch window to drift. 295 | # otherwise ignore this transition 296 | transition += 1 297 | continue 298 | previous_tbaud = tbaud 299 | 300 | if state == START_BIT: 301 | if tbaud < (1 - epsilon) or level_before == 1: 302 | # framing error: transition during a start bit or finished start bit with signal high 303 | print(f"Framing error: broken start bit at {t=}") 304 | return start_bit_transition + 2 # earliest possible start bit candidate 305 | else: 306 | # start bit ended normally, now we're looking for data bit 0 307 | state = 0 308 | # fall through to check for data bit(s) 309 | 310 | while state in range(0, num_data_and_parity_bits): 311 | if tbaud < (1 + state + epsilon): 312 | # transition was valid but at the start of the current data bit, carry on 313 | transition += 1 314 | break 315 | elif tbaud < (1 + state + 1 - epsilon): 316 | # framing error: transition inside the data bit. 317 | print(f"Framing error: transition during data bit at {t=}") 318 | return start_bit_transition + 2 # earliest possible start bit candidate 319 | else: 320 | if level_before == 1: 321 | char |= 1 << state # high bit received, put it into the character 322 | state += 1 323 | if state in range(0, num_data_and_parity_bits): 324 | continue 325 | 326 | # Now we've seen all the data and parity bits, start looking for a valid stop bit. 327 | if tbaud < (1 + state + epsilon): 328 | # transition was at the beginning of the stop bit 329 | # look to the next transition 330 | transition += 1 331 | # if there is a next transition before the end of the capture 332 | if transition < data.num_transitions: 333 | continue 334 | # else: end of capture terminates this stop bit successfully, we assume 335 | elif tbaud < (1 + state + num_stop_bits - epsilon): 336 | # framing error: transition inside the stop bit(s) 337 | print(f"Framing error: transition during stop bit(s) at {t=}") 338 | return start_bit_transition + 2 # earliest possible start bit candidate 339 | elif level_before == 0: 340 | # framing error: wrong level during stop bit(s) 341 | print(f"Framing error: wrong level during stop bit(s) at {t=}") 342 | return start_bit_transition + 2 # earliest possible start bit candidate 343 | 344 | data_bits, parity_good = strip_parity(char) 345 | if parity_good: 346 | process_character(data_bits, t0, t0 + onebaud * (1 + num_data_and_parity_bits + num_stop_bits)) 347 | else: 348 | print(f"Parity error at {t=}") 349 | 350 | return transition 351 | # This is the transition that successfully concluded the stop bits of 352 | # the current character. Thus, it is the presumed start bit of the next character. 353 | # Unless the character ended with the end of the capture, in which case 354 | # this is one past the end of the array. 355 | 356 | return data.num_transitions # one past the last transition in the data array 357 | 358 | 359 | if __name__ == "__main__": 360 | if len(sys.argv) != 3: 361 | print("SlowSoftSerial test protocol captured trace analyzer") 362 | print(f" Usage: {sys.argv[0]} file1 file2") 363 | sys.exit() 364 | 365 | filename0 = sys.argv[1] 366 | filename1 = sys.argv[2] 367 | print("Opening " + filename0 + " and " + filename1) 368 | 369 | with open(filename0, "rb") as f: 370 | data0 = parse_digital(f) 371 | 372 | with open(filename1, "rb") as f: 373 | data1 = parse_digital(f) 374 | 375 | if data0.initial_state != data1.initial_state: 376 | print("Initial states don't match") 377 | sys.exit(1) 378 | # Matching initial states; we will assume this is the idle state (1). 379 | # This automatically handles inverted or non-inverted logic levels, 380 | # provided that the capture starts when the line is idle in both directions. 381 | 382 | if data0.initial_state == 0: 383 | print("Detected inverted logic at beginning of trace") 384 | 385 | if data0.begin_time != data1.begin_time: 386 | print("Begin times don't match") 387 | sys.exit(1) 388 | 389 | if data0.end_time != data1.end_time: 390 | print("End times don't match") 391 | sys.exit(1) 392 | 393 | n0 = 0 394 | t0 = data0.transition_times[n0] 395 | 396 | n1 = 0 397 | t1 = data1.transition_times[n1] 398 | 399 | while n0 < data0.num_transitions or n1 < data1.num_transitions: 400 | if t0 < t1: 401 | t0_char_end = t0 + onebaud * (1 + num_data_and_parity_bits + num_stop_bits) 402 | n0 = receive_char_from(data0, n0) 403 | if n0 < data0.num_transitions: 404 | t0 = data0.transition_times[n0] 405 | if t0_char_end > t1: 406 | print(f"Doubletalk at t={t1}") 407 | sys.exit(1) 408 | elif n0 == data0.num_transitions: 409 | t0 = data0.end_time 410 | else: 411 | t1_char_end = t1 + onebaud * (1 + num_data_and_parity_bits + num_stop_bits) 412 | n1 = receive_char_from(data1, n1) 413 | if n1 < data1.num_transitions: 414 | t1 = data1.transition_times[n1] 415 | if t1_char_end > t0: 416 | print(f"Doubletalk at t={t0}") 417 | sys.exit(1) 418 | elif n1 == data1.num_transitions: 419 | t1 = data1.end_time 420 | 421 | print(f"{data0.end_time:12.06f}: End of capture") 422 | -------------------------------------------------------------------------------- /test/autotest-uut/autotest-uut.ino: -------------------------------------------------------------------------------- 1 | // Unit-Under-Test Driver for Automated Testing of SlowSoftSerial 2 | // See README.md for discussion and protocol definition. 3 | // 4 | // This Arduino (Teensyduino) sketch runs on the target platform, 5 | // such as a Teensy 3.x or 4.x board. Another program runs on 6 | // another host connected via the SlowSoftSerial port, which we 7 | // call the controller. The controller is in charge of sequencing 8 | // this program through (some of) its paces. 9 | // 10 | // We make some effort to surface explanatory info onto the serial 11 | // link, but only what's easy. The assumption is that a serial 12 | // analyzer is available to observe what went wrong. 13 | // 14 | // This program also displays some trace information on the console 15 | // (the USB serial port on the target board). This can be minimal, 16 | // or it can include a full trace of packets sent and received. 17 | 18 | #include "SlowSoftSerial.h" 19 | 20 | #define BUILTIN_LED 13 21 | 22 | // Do you want noisy packet trace output? 23 | #define PACKET_TRACE 24 | 25 | const char VERSION_INFO[] = "SlowSoftSerial Tester 0.02"; 26 | const char DBG_MSG_UNKNOWN_COMMAND_CODE[] = "Unknown command code"; 27 | const char DBG_MSG_INVALID_PARAMS[] = "Invalid baud rate or serial params"; 28 | 29 | /* Special characters for KISS */ 30 | #define FEND 0x10 /* Frame End */ 31 | #define FESC 0x1B /* Frame Escape */ 32 | #define TFEND 0x1C /* Transposed frame end */ 33 | #define TFESC 0x1D /* Transposed frame escape */ 34 | 35 | #define CHARACTERS_IN_CRC 8 // 32-bit CRC, sent 4 bits per character 36 | 37 | // Packet Command Structure 38 | #define HEADER_LEN 2 39 | // First Byte: 40 | #define DIR_CMD 0 41 | #define DIR_RSP 1 42 | #define DIR_DBG 2 43 | // Second Byte: 44 | #define CMD_NOP 0 45 | #define CMD_ID 1 46 | #define CMD_ECHO 2 47 | #define CMD_BABBLE 3 48 | #define CMD_PARAMS 4 49 | #define CMD_EXT 0x1f 50 | 51 | // Protocol spec requires us to handle ECHO or BABBLE payloads of up to 10,000 characters. 52 | #define PACKET_BUF_SIZE (10000 + HEADER_LEN + 2*CHARACTERS_IN_CRC) 53 | unsigned char packet_buf[PACKET_BUF_SIZE]; 54 | 55 | #define NUMBER_OF_VALID_SERIAL_CONFIGS 60 56 | uint16_t valid_serial_configs[NUMBER_OF_VALID_SERIAL_CONFIGS] = { 57 | SSS_SERIAL_5N1, SSS_SERIAL_6N1, SSS_SERIAL_7N1, SSS_SERIAL_8N1, 58 | SSS_SERIAL_5N15, SSS_SERIAL_6N15, SSS_SERIAL_7N15, SSS_SERIAL_8N15, 59 | SSS_SERIAL_5N2, SSS_SERIAL_6N2, SSS_SERIAL_7N2, SSS_SERIAL_8N2, 60 | SSS_SERIAL_5E1, SSS_SERIAL_6E1, SSS_SERIAL_7E1, SSS_SERIAL_8E1, 61 | SSS_SERIAL_5E15, SSS_SERIAL_6E15, SSS_SERIAL_7E15, SSS_SERIAL_8E15, 62 | SSS_SERIAL_5E2, SSS_SERIAL_6E2, SSS_SERIAL_7E2, SSS_SERIAL_8E2, 63 | SSS_SERIAL_5O1, SSS_SERIAL_6O1, SSS_SERIAL_7O1, SSS_SERIAL_8O1, 64 | SSS_SERIAL_5O15, SSS_SERIAL_6O15, SSS_SERIAL_7O15, SSS_SERIAL_8O15, 65 | SSS_SERIAL_5O2, SSS_SERIAL_6O2, SSS_SERIAL_7O2, SSS_SERIAL_8O2, 66 | SSS_SERIAL_5M1, SSS_SERIAL_6M1, SSS_SERIAL_7M1, SSS_SERIAL_8M1, 67 | SSS_SERIAL_5M15, SSS_SERIAL_6M15, SSS_SERIAL_7M15, SSS_SERIAL_8M15, 68 | SSS_SERIAL_5M2, SSS_SERIAL_6M2, SSS_SERIAL_7M2, SSS_SERIAL_8M2, 69 | SSS_SERIAL_5S1, SSS_SERIAL_6S1, SSS_SERIAL_7S1, SSS_SERIAL_8S1, 70 | SSS_SERIAL_5S15, SSS_SERIAL_6S15, SSS_SERIAL_7S15, SSS_SERIAL_8S15, 71 | SSS_SERIAL_5S2, SSS_SERIAL_6S2, SSS_SERIAL_7S2, SSS_SERIAL_8S2, 72 | }; 73 | 74 | // Current Serial Parameters 75 | double current_baud_rate = 9600.0; 76 | uint16_t current_serial_config = SSS_SERIAL_8N1; 77 | unsigned int charactersize_mask = 0x00ff; 78 | 79 | // Here's our serial port connected to the controller. 80 | SlowSoftSerial sss(0,1); 81 | 82 | 83 | // Convert the serial port's read() method, which may not be blocking, 84 | // into one that definitely is. 85 | int blocking_read(void) 86 | { 87 | while (! sss.available()); 88 | return sss.read(); 89 | } 90 | 91 | 92 | // Get one complete frame of data from the serial port, and places it in buf. 93 | // 94 | // Returns the number of bytes placed in buf, or 0 if the frame was ill-formed 95 | // or if no data is available. 96 | int get_frame(unsigned char *buf, int max_length) 97 | { 98 | unsigned char *bufp; 99 | int chr; 100 | 101 | bufp = buf; 102 | 103 | // Eat bytes up to the first FEND 104 | while (blocking_read() != FEND); 105 | 106 | // Eat as many FENDs as we find 107 | while ((chr = blocking_read()) == FEND); 108 | 109 | while ((chr != FEND) && (bufp <= buf+max_length)) { 110 | if (chr == FESC) { 111 | chr = blocking_read(); 112 | if (chr == TFESC) { 113 | *bufp++ = FESC; // put escaped character in buffer 114 | } else if (chr == TFEND) { 115 | (*bufp++ = FEND); 116 | } else { 117 | Serial.println("Ill formed frame"); 118 | return 0; // ill-formed frame 119 | } 120 | } else { 121 | *bufp++ = (unsigned char)chr; 122 | } 123 | 124 | chr = blocking_read(); 125 | } 126 | 127 | if (bufp > buf+max_length) 128 | { 129 | Serial.println("Frame too long"); 130 | return 0; // frame too big for buffer 131 | } 132 | 133 | return (bufp - buf); // return length of buffer 134 | } 135 | 136 | 137 | // Table used to expedite computation of 32-bit CRC 138 | static uint32_t crc_table[16] = { 139 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 140 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 141 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 142 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c 143 | }; 144 | 145 | 146 | // One byte worth of CRC computation 147 | uint32_t crc_update(uint32_t crc, byte data) 148 | { 149 | byte tbl_idx; 150 | tbl_idx = crc ^ (data >> (0 * 4)); 151 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 152 | tbl_idx = crc ^ (data >> (1 * 4)); 153 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 154 | 155 | return crc; 156 | } 157 | 158 | 159 | // The protocol encodes integers, including the CRC used for error detection, 160 | // in the least significant four bits of eight consecutive characters. This 161 | // encoding makes it work with serial word sizes less than 8 bits. 162 | uint32_t decode_uint32(unsigned char *buf) { 163 | uint32_t value; 164 | 165 | // The buffer contains 8 characters which are supposed to be 4 bits wide. 166 | // We don't check, so if any characters are > 0x0F, that will cause the 167 | // result to be wrong. That's what we want for a CRC check. 168 | value = buf[0] << 28 169 | | buf[1] << 24 170 | | buf[2] << 20 171 | | buf[3] << 16 172 | | buf[4] << 12 173 | | buf[5] << 8 174 | | buf[6] << 4 175 | | buf[7] 176 | | (buf[0] & 0xF0) // Catch any stray high bits in the top nybble 177 | ; 178 | 179 | return value; 180 | } 181 | 182 | void encode_uint32(unsigned char *buf, uint32_t value) { 183 | buf[0] = (value >> 28) & 0x0f; 184 | buf[1] = (value >> 24) & 0x0f; 185 | buf[2] = (value >> 20) & 0x0f; 186 | buf[3] = (value >> 16) & 0x0f; 187 | buf[4] = (value >> 12) & 0x0f; 188 | buf[5] = (value >> 8) & 0x0f; 189 | buf[6] = (value >> 4) & 0x0f; 190 | buf[7] = value & 0x0f; 191 | } 192 | 193 | 194 | // Given a buffer whose final 8 bytes contain an encoded CRC, 195 | // check that the CRC matches the one we compute from all the 196 | // bytes in the buffer before the CRC. 197 | bool check_packet_crc(unsigned char *buf, int len) 198 | { 199 | uint32_t crc = ~0L; 200 | uint32_t packet_crc; 201 | 202 | for (int i=0; i < (len-CHARACTERS_IN_CRC); i++) { 203 | crc = crc_update(crc, buf[i]); 204 | } 205 | crc = ~crc; 206 | packet_crc = decode_uint32(buf + len - CHARACTERS_IN_CRC); 207 | 208 | // Check for CRC equality and return the boolean result 209 | return crc == packet_crc; 210 | } 211 | 212 | 213 | // Given a buffer with room at the end for 8 more bytes, 214 | // compute the CRC of all the bytes in the buffer and 215 | // encode the CRC into 8 bytes after the buffer. 216 | int add_packet_crc(unsigned char *buf, int len) 217 | { 218 | uint32_t crc = ~0L; 219 | 220 | for (int i=0; i < len; i++) { 221 | crc = crc_update(crc, buf[i]); 222 | } 223 | crc = ~crc; 224 | encode_uint32(buf+len, crc); 225 | 226 | return(len+CHARACTERS_IN_CRC); 227 | } 228 | 229 | 230 | // Given a buffer of bytes, output it as a frame using the 231 | // modified SLIP/KISS framing protocol specified in README.md. 232 | void put_frame(unsigned char *buf, int len) 233 | { 234 | int i; 235 | 236 | digitalWrite(BUILTIN_LED, 1); // LED on for transmitting 237 | 238 | sss.write(FEND); /* all frames begin with FEND */ 239 | 240 | for (i=0; i= HEADER_LEN+CHARACTERS_IN_CRC) { 286 | switch(buf[0]) { 287 | case DIR_CMD: Serial.print(" Cmd"); 288 | break; 289 | case DIR_RSP: Serial.print(" Rsp"); 290 | break; 291 | case DIR_DBG: Serial.print(" Dbg"); 292 | break; 293 | default: sprintf(fbuf, " 0x%2X", (int)buf[0]); 294 | Serial.print(fbuf); 295 | break; 296 | } 297 | 298 | switch(packet_buf[1]) { 299 | case CMD_NOP: Serial.print(" NOP"); 300 | break; 301 | case CMD_ID: Serial.print(" ID"); 302 | break; 303 | case CMD_ECHO: Serial.print(" ECHO"); 304 | break; 305 | case CMD_BABBLE: Serial.print(" BABBLE"); 306 | break; 307 | case CMD_PARAMS: Serial.print(" PARAMS"); 308 | break; 309 | default: sprintf(fbuf, " 0x%2X", (int)buf[1]); 310 | Serial.print(fbuf); 311 | break; 312 | } 313 | } 314 | 315 | Serial.print(" Len: "); 316 | Serial.print(len); 317 | Serial.print(" CRC: "); 318 | Serial.print(crc_good ? "Good" : "Bad"); 319 | Serial.println(); 320 | } 321 | #endif // PACKET_TRACE 322 | 323 | 324 | // Fill up a buffer with random nonsense, for the BABBLE command. 325 | // Limit the character values to what will fit into the current 326 | // serial word size. 327 | void random_fill(unsigned char *buf, int len) { 328 | int i; 329 | int charactersize_max = charactersize_mask + 1; // e.g., for 5 bits 0b00011111 + 1 = 0b00100000, 330 | // which is the (exclusive) max value of a character 331 | 332 | for (i=0; i < len; i++) { 333 | buf[i] = random(charactersize_max); 334 | } 335 | } 336 | 337 | 338 | // Check that the received value for baud rate is plausible. 339 | // The received baud rate is an integer in millibaud, and the 340 | // range of rates we'll allow is 1 to 115200 baud. 341 | bool validate_baud_rate(uint32_t value) { 342 | if (value < 1000 || value > 115200000) { 343 | Serial.print("Bogus baud rate "); 344 | Serial.println(value); 345 | return false; 346 | } 347 | 348 | return true; 349 | } 350 | 351 | 352 | // Check that the received value for serial configuration is 353 | // one of the ones we know how to do. 354 | bool validate_serial_config(uint32_t value) { 355 | int i; 356 | 357 | for (i=0; i < NUMBER_OF_VALID_SERIAL_CONFIGS; i++) { 358 | if (value == valid_serial_configs[i]) { 359 | return true; 360 | } 361 | } 362 | 363 | Serial.print("Bogus serial config 0x"); 364 | Serial.println(value, HEX); 365 | 366 | return false; // Didn't match any known serial configuration 367 | } 368 | 369 | 370 | // Change baud rate and serial configuration in response to PARAMS packet 371 | void change_serial_params(double baud, uint16_t config) { 372 | sss.end(false); // Stop serial port but don't release the pins 373 | sss.begin(baud, config); 374 | } 375 | 376 | 377 | // Arduino one-time setup at startup. 378 | void setup() { 379 | Serial.begin(9600); 380 | sss.begin(9600, SSS_SERIAL_8N1); 381 | 382 | pinMode(BUILTIN_LED, OUTPUT); // LED to flash on transmit 383 | digitalWrite(BUILTIN_LED, 0); 384 | 385 | while (!Serial); 386 | 387 | Serial.println(VERSION_INFO); 388 | Serial.println("Waiting for frames ..."); 389 | } 390 | 391 | 392 | // Arduino main loop, runs forever. 393 | void loop() { 394 | int len; 395 | bool crc_good; 396 | uint32_t babble_length, baud_rate, serial_config; // received packet parameters decoded 397 | 398 | len = get_frame(packet_buf, PACKET_BUF_SIZE); 399 | 400 | if (len > 8) { 401 | crc_good = check_packet_crc(packet_buf, len); 402 | } else { 403 | crc_good = 0; 404 | } 405 | 406 | #ifdef PACKET_TRACE 407 | dump_packet_buf(len, crc_good); 408 | #endif 409 | 410 | if (crc_good && packet_buf[0] == DIR_CMD) { 411 | switch(packet_buf[1]) { 412 | case CMD_NOP: packet_buf[0] = DIR_RSP; 413 | // packet_buf[1] = CMD_NOP; 414 | // Ignore any further packet contents 415 | put_frame(packet_buf, add_packet_crc(packet_buf, HEADER_LEN)); // NOP response 416 | break; 417 | 418 | case CMD_ID: packet_buf[0] = DIR_RSP; 419 | // packet_buf[1] = CMD_ID; 420 | memcpy(packet_buf+HEADER_LEN, VERSION_INFO, strlen(VERSION_INFO) + 1); 421 | put_frame(packet_buf, add_packet_crc(packet_buf, HEADER_LEN + strlen(VERSION_INFO) + 1)); // ID response 422 | break; 423 | 424 | case CMD_ECHO: packet_buf[0] = DIR_RSP; 425 | // packet_buf[1] = CMD_ECHO; 426 | // leave the entire packet payload alone and echo it 427 | put_frame(packet_buf, add_packet_crc(packet_buf, len-CHARACTERS_IN_CRC)); // ECHO response 428 | break; 429 | 430 | case CMD_BABBLE: if (len >= HEADER_LEN + CHARACTERS_IN_CRC) { 431 | babble_length = decode_uint32(packet_buf+HEADER_LEN); 432 | if (babble_length <= PACKET_BUF_SIZE-(HEADER_LEN+2*CHARACTERS_IN_CRC)) { 433 | packet_buf[0] = DIR_RSP; 434 | // packet_buf[1] = CMD_BABBLE; 435 | // leave the babble length alone 436 | random_fill(packet_buf+HEADER_LEN+CHARACTERS_IN_CRC, babble_length); 437 | put_frame(packet_buf, add_packet_crc(packet_buf, HEADER_LEN + CHARACTERS_IN_CRC + babble_length)); 438 | } 439 | } 440 | break; 441 | 442 | case CMD_PARAMS: if ( (len >= HEADER_LEN + 2 * CHARACTERS_IN_CRC) 443 | && (baud_rate = decode_uint32(packet_buf+HEADER_LEN)) 444 | && (serial_config = decode_uint32(packet_buf+HEADER_LEN+CHARACTERS_IN_CRC)) 445 | && validate_baud_rate(baud_rate) 446 | && validate_serial_config(serial_config) 447 | ) { 448 | packet_buf[0] = DIR_RSP; 449 | // packet_buf[1] = CD_PARAMS; 450 | // leave the parameters alone and echo them back 451 | put_frame(packet_buf, add_packet_crc(packet_buf, len-CHARACTERS_IN_CRC)); // PARAMS ack response 452 | change_serial_params(0.001 * (double)baud_rate, (uint16_t)serial_config); 453 | } else { 454 | packet_buf[0] = DIR_DBG; 455 | // leave the CMD_PARAMS command code and parameters in packet_buf[1] 456 | memcpy(packet_buf+HEADER_LEN+2*CHARACTERS_IN_CRC, DBG_MSG_INVALID_PARAMS, strlen(DBG_MSG_INVALID_PARAMS)); 457 | put_frame(packet_buf, add_packet_crc(packet_buf, HEADER_LEN + 2*CHARACTERS_IN_CRC + strlen(DBG_MSG_INVALID_PARAMS))); 458 | } 459 | break; 460 | 461 | default: packet_buf[0] = DIR_DBG; 462 | // leave the bad command code in packet_buf[1] 463 | memcpy(packet_buf+HEADER_LEN, DBG_MSG_UNKNOWN_COMMAND_CODE, strlen(DBG_MSG_UNKNOWN_COMMAND_CODE)); 464 | put_frame(packet_buf, add_packet_crc(packet_buf, HEADER_LEN + strlen(DBG_MSG_UNKNOWN_COMMAND_CODE))); 465 | break; 466 | } 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /test/autotest-ctlr-sss/autotest-ctlr-sss.ino: -------------------------------------------------------------------------------- 1 | // Controller Driver for Automated Testing of SlowSoftSerial 2 | // See README.md for discussion and protocol definition. 3 | // 4 | // This program runs as test controller on a Teensy running SlowSoftSerial, 5 | // connected via serial port to the target platform (UUT for Unit Under Test), 6 | // which is also a Teensy running SlowSoftSerial. 7 | // 8 | // It is in charge of sequencing the UUT through (some of) its paces. 9 | // 10 | 11 | 12 | #include 13 | #include "SlowSoftSerial.h" 14 | 15 | 16 | #define puts(x) printf(x "\n"); 17 | 18 | #define BUILTIN_LED 13 19 | 20 | // By convention, we start every test at 9600 baud, 8N1 21 | // Current Serial Parameters 22 | double current_baud = 9600.0; 23 | int current_width = SSS_SERIAL_DATA_8; 24 | int current_parity = SSS_SERIAL_PARITY_NONE; 25 | int current_stopbits = SSS_SERIAL_STOP_BIT_1; 26 | uint16_t current_serial_config = SSS_SERIAL_8N1; 27 | unsigned int charactersize_mask = 0x00ff; 28 | 29 | // Here's our serial port connected to the controller. 30 | SlowSoftSerial sss(0,1); 31 | 32 | unsigned char width_masks[] = { 0x00, 0x1F, 0x3F, 0x7F, 0xFF }; 33 | #define CURRENT_WIDTH_MASK (width_masks[current_width >> 8]) // convert current_width to a mask 34 | #define CURRENT_WIDTH_BITS ((current_width >> 8) + 4) // convert current_width to # of bits 35 | 36 | // In serial configuration changes, 0 means leave that parameter alone 37 | #define STET 0 38 | 39 | // Packet Command Structure 40 | // First Byte: 41 | #define DIR_CMD 0 42 | #define DIR_RSP 1 43 | #define DIR_DBG 2 44 | // Second Byte: 45 | #define CMD_NOP 0 46 | #define CMD_ID 1 47 | #define CMD_ECHO 2 48 | #define CMD_BABBLE 3 49 | #define CMD_PARAMS 4 50 | #define CMD_EXT 0x1f 51 | 52 | #define MAX_DATA_LEN 10000 53 | #define BUFLEN (MAX_DATA_LEN*2+10) // big enough for all bytes to be transposed 54 | unsigned char buffer[BUFLEN]; 55 | 56 | 57 | // Special characters for framing 58 | #define FEND 0x10 // Frame End 59 | #define FESC 0x1B // Frame Escape 60 | #define TFEND 0x1C // Transposed frame end 61 | #define TFESC 0x1D //Transposed frame escape 62 | 63 | #define CHARACTERS_IN_CRC 8 // 32-bit CRC, sent 4 bits per character 64 | 65 | int word_width = 8; 66 | #define WORD_WIDTH_MASK (0xFF >> (8-word_width)) 67 | 68 | // What to do when there's nothing to do 69 | void tight_loop_contents(void) { 70 | } 71 | 72 | 73 | // Get one character from the interrupt-driven receive buffer, 74 | // if one is available before the specified absolute timeout. 75 | // 76 | // Returns true if a characters was received, false if not. 77 | // The character is placed in *chr_p 78 | // 79 | bool serial_getc_timeout(unsigned long tmax, unsigned char *chr_p) { 80 | while (1) { 81 | if (sss.available()) { 82 | *chr_p = sss.read(); 83 | return true; 84 | } else if (millis() > tmax) { 85 | return false; // timeout before a character was available 86 | } else { 87 | tight_loop_contents(); 88 | } 89 | } 90 | } 91 | 92 | 93 | // Get one complete frame of data from the serial port, and place it in buf, 94 | // if the complete frame arrives within a reasonable time based on the current 95 | // communications parameters and the expected response size. Expected size 96 | // includes only the data characters and not the header, framing, stuffing, 97 | // or CRC. 98 | // 99 | // Returns the number of bytes placed in buf, or 0 if the frame was 100 | // ill-formed or if a complete frame was not received. 101 | int get_frame_with_expected_data_size(unsigned char *buf, int expected_size_in_characters) 102 | { 103 | unsigned char *bufp; 104 | unsigned char chr; 105 | uint32_t timeout = 10 + ((long)expected_size_in_characters*2 + 10) * (CURRENT_WIDTH_BITS + 4) * 1000 / current_baud; 106 | unsigned long timeout_time = millis() + timeout; 107 | 108 | newframe: 109 | 110 | bufp = buf; 111 | 112 | // eat bytes up to first FEND 113 | while (serial_getc_timeout(timeout_time, &chr) && chr != FEND) 114 | ; 115 | 116 | // eat as many FENDs as we find 117 | while (serial_getc_timeout(timeout_time, &chr) && chr == FEND) 118 | ; 119 | 120 | // fill the buffer with received characters, with de-escaping 121 | while ((chr != FEND) && (bufp < buf+BUFLEN)) { 122 | if (chr == FESC) { 123 | (void) serial_getc_timeout(timeout_time, &chr); // don't worry about timeout 124 | if (chr == TFESC) { 125 | *bufp++ = FESC; // put escaped character in buffer 126 | } else if (chr == TFEND) { 127 | *bufp++ = FEND; 128 | } else { 129 | puts("Ill-formed frame"); 130 | return 0; // ill-formed frame 131 | } 132 | } else { 133 | *bufp++ = (unsigned char)chr; // put unescaped character in buffer 134 | } 135 | 136 | if ( ! serial_getc_timeout(timeout_time, &chr)) { // Get next character 137 | printf("Frame timeout %ldms\n", timeout); 138 | return 0; // timeout before a full frame arrives 139 | } 140 | } 141 | 142 | if (bufp >= buf+BUFLEN) 143 | { 144 | puts("Warning: frame is too big! Discarded.\n"); 145 | goto newframe; 146 | } 147 | 148 | // puts("Good frame"); 149 | return (bufp - buf); // return length of buffer 150 | } 151 | 152 | 153 | // Transmit a frame through the interrupt-driven tx buffer, 154 | // adding framing on the fly. 155 | // 156 | // Blocks in sss.write() if the frame is larger than the space 157 | // available in the transmit buffer. 158 | void put_frame(unsigned char *buf, int len) 159 | { 160 | int i; 161 | 162 | sss.write(FEND); // all frames begin with FEND 163 | 164 | for (i=0; i> (0 * 4)); 213 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 214 | tbl_idx = crc ^ (data >> (1 * 4)); 215 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 216 | 217 | return crc; 218 | } 219 | 220 | 221 | // The protocol encodes integers, including the CRC used for error detection, 222 | // in the least significant four bits of eight consecutive characters. This 223 | // encoding makes it work with serial word sizes less than 8 bits. 224 | uint32_t decode_uint32(unsigned char *buf) { 225 | uint32_t value; 226 | 227 | // The buffer contains 8 characters which are supposed to be 4 bits wide. 228 | // We don't check, so if any characters are > 0x0F, that will cause the 229 | // result to be wrong. That's what we want for a CRC check. 230 | value = buf[0] << 28 231 | | buf[1] << 24 232 | | buf[2] << 20 233 | | buf[3] << 16 234 | | buf[4] << 12 235 | | buf[5] << 8 236 | | buf[6] << 4 237 | | buf[7] 238 | | (buf[0] & 0xF0) // catch any stray high bits in the top nybble 239 | ; 240 | 241 | return value; 242 | } 243 | 244 | 245 | void encode_uint32(unsigned char *buf, uint32_t value) { 246 | buf[0] = (value >> 28) & 0x0f; 247 | buf[1] = (value >> 24) & 0x0f; 248 | buf[2] = (value >> 20) & 0x0f; 249 | buf[3] = (value >> 16) & 0x0f; 250 | buf[4] = (value >> 12) & 0x0f; 251 | buf[5] = (value >> 8) & 0x0f; 252 | buf[6] = (value >> 4) & 0x0f; 253 | buf[7] = (value ) & 0x0f; 254 | } 255 | 256 | 257 | // Check the CRC found in the last 8 characters of the buffer. 258 | // 259 | // Returns true if the CRC checks. 260 | // 261 | bool check_packet_crc(unsigned char *buf, int len) 262 | { 263 | uint32_t crc = ~0L; 264 | uint32_t packet_crc; 265 | 266 | for (int i=0; i < (len-CHARACTERS_IN_CRC); i++) { 267 | crc = crc_update(crc, buf[i]); 268 | } 269 | crc = ~crc; 270 | packet_crc = decode_uint32(buf + len - CHARACTERS_IN_CRC); 271 | 272 | // Check for CRC equality and return the boolean result 273 | return crc == packet_crc; 274 | } 275 | 276 | 277 | // Given a buffer with extra room reserved at the end for 278 | // a CRC, compute the CRC and write it into the buffer. 279 | // 280 | // Returns the new length of the buffer. 281 | // 282 | int add_packet_crc(unsigned char *buf, int len) 283 | { 284 | unsigned long crc = ~0L; 285 | 286 | for (int i=0; i < len; i++) { 287 | crc = crc_update(crc, buf[i]); 288 | } 289 | crc = ~crc; 290 | encode_uint32(buf+len, crc); 291 | 292 | return(len+CHARACTERS_IN_CRC); 293 | } 294 | 295 | 296 | // Declare a test failure. 297 | // This means just stop and do nothing. Let the user analyze. 298 | void failure(void) 299 | { 300 | printf("Test failed.\n"); 301 | while (1) { 302 | tight_loop_contents(); 303 | } 304 | } 305 | 306 | 307 | //------------- Test Routines -------------------- 308 | 309 | // Send a NOP and try to receive a NOP response. 310 | // We will keep trying forever if the UUT does not respond. 311 | // This is suitable for starting up a fresh connection. 312 | void get_a_nop_response(void) 313 | { 314 | unsigned char nop_cmd[2 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_NOP }; 315 | int len = add_packet_crc(nop_cmd, 2); 316 | int response_len; 317 | int max_tries = 3; // try receiving response several times 318 | 319 | while (1) { 320 | put_frame_with_LED(nop_cmd, len); 321 | 322 | for (int i=0; i < max_tries; i++) { 323 | response_len = get_frame_with_expected_data_size(buffer, 0); 324 | if ((response_len >= (2 + CHARACTERS_IN_CRC)) 325 | && (buffer[0] == DIR_RSP) 326 | && (buffer[1] == CMD_NOP) 327 | && check_packet_crc(buffer, response_len)) { 328 | return; 329 | } 330 | } 331 | } 332 | } 333 | 334 | 335 | // Send a NOP command with some extra bytes in the payload. 336 | // This is permitted by the spec. The UUT is supposed to ignore them 337 | // and not include them in the response. 338 | void send_nop_with_junk(void) 339 | { 340 | unsigned char nop_cmd[12 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_NOP, 'a', 0x10, 'b', 0x1b, 'c', 0x1c, 'd', 0x1d, 'e', 0x1e }; 341 | int len = add_packet_crc(nop_cmd, 12); 342 | int response_len; 343 | int max_tries = 3; // try receiving response several times 344 | 345 | put_frame_with_LED(nop_cmd, len); 346 | 347 | for (int i=0; i < max_tries; i++) { 348 | response_len = get_frame_with_expected_data_size(buffer, 0); 349 | if ((response_len >= (2 + CHARACTERS_IN_CRC)) 350 | && (buffer[0] == DIR_RSP) 351 | && (buffer[1] == CMD_NOP) 352 | && check_packet_crc(buffer, response_len)) { 353 | return; 354 | } 355 | } 356 | 357 | printf("NOP with junk failed\n"); 358 | failure(); 359 | } 360 | 361 | 362 | // Send a NOP command with a bad CRC, to demonstrate CRC checking. 363 | void send_nop_with_bad_crc(void) 364 | { 365 | unsigned char nop_cmd[12 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_NOP, 'a', 0x10, 'b', 0x1b, 'c', 0x1c, 'd', 0x1d, 'e', 0x1e }; 366 | int len = add_packet_crc(nop_cmd, 12); 367 | 368 | nop_cmd[len-1] ^= 1; // insert bit error 369 | put_frame_with_LED(nop_cmd, len); 370 | // We do not expect a response to a packet with a bad CRC. 371 | delay(30); // leave a gap in the timeline for readability 372 | } 373 | 374 | 375 | // Get identification info from the UUT and print it. 376 | // 377 | // Since we're printing out a message carried in the packet, 378 | // this is only useful for serial word widths of 7 or 8. 379 | // 380 | void obtain_uut_info(void) { 381 | unsigned char id_cmd[2 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_ID }; 382 | int len = add_packet_crc(id_cmd, 2); 383 | int response_len; 384 | int max_tries = 3; // try receiving response several times 385 | 386 | put_frame_with_LED(id_cmd, len); 387 | 388 | for (int i=0; i < max_tries; i++) { 389 | response_len = get_frame_with_expected_data_size(buffer, 256); 390 | if ((response_len >= (2 + CHARACTERS_IN_CRC)) 391 | && (buffer[0] == DIR_RSP) 392 | && (buffer[1] == CMD_ID) 393 | && check_packet_crc(buffer, response_len)) { 394 | buffer[response_len] = 0; // null terminate response 395 | printf("UUT Info: %s\n", buffer+2); 396 | return; 397 | } 398 | } 399 | 400 | printf("Obtain UUT Info failed.\n"); 401 | failure(); 402 | } 403 | 404 | 405 | // Send a PARAMS packet and wait for the response. 406 | void set_params(double baud, uint16_t config) { 407 | 408 | unsigned char params_cmd[2 + CHARACTERS_IN_CRC * 3] = { DIR_CMD, CMD_PARAMS }; 409 | uint32_t millibaud = baud * 1000; 410 | int response_len; 411 | int max_tries = 3; // try receiving response several times 412 | 413 | encode_uint32(params_cmd+2, millibaud); 414 | encode_uint32(params_cmd+2+8, config); 415 | add_packet_crc(params_cmd, 18); 416 | 417 | put_frame_with_LED(params_cmd, 26); 418 | 419 | for (int i=0; i < max_tries; i++) { 420 | response_len = get_frame_with_expected_data_size(buffer, 16); 421 | if ((response_len == 26) 422 | && (buffer[0] == DIR_RSP) 423 | && (memcmp(buffer+1, params_cmd+1, 17) == 0) 424 | && check_packet_crc(buffer, 26)) { 425 | printf("Set baud=%.03f config=0x%04x\n", baud, config); 426 | return; 427 | } 428 | } 429 | 430 | printf("No response to set params command\n"); 431 | failure(); 432 | } 433 | 434 | 435 | // Complete a change in speed or serial parameters. This includes sending 436 | // the command packet, getting the response, and transitioning the local 437 | // UART to the new settings. 438 | // If any argument is set to 0, that means leave that setting alone. 439 | // The encoding of each config argument is per the SlowSoftSerial specification, 440 | // which implies the non-zero ones can be ORed together to make a config code. 441 | void change_params(double baud, int width, int parity, int stopbits) { 442 | 443 | double new_baud = (baud == STET) ? current_baud : baud; 444 | int new_width = (width == STET) ? current_width : width; 445 | int new_parity = (parity == STET) ? current_parity : parity; 446 | int new_stopbits = (stopbits == STET) ? current_stopbits : stopbits; 447 | uint16_t new_config = new_width | new_parity | new_stopbits; 448 | int old_bittime_ms = (int)(1000.0/current_baud); 449 | 450 | // Send the command and get a response 451 | set_params(new_baud, new_config); 452 | 453 | current_baud = new_baud; 454 | current_width = new_width; 455 | current_parity = new_parity; 456 | current_stopbits = new_stopbits; 457 | current_serial_config = new_config; 458 | 459 | // Switch over the local serial port 460 | sss.end(); 461 | sss.begin(current_baud, current_serial_config); 462 | 463 | // Wait for UUT to execute the change 464 | delay(1 + old_bittime_ms); 465 | } 466 | 467 | 468 | // Send an ECHO command of the specified length and receive the response. 469 | // 470 | // We don't check the actual echoed data against the sent data, because of 471 | // (feared) memory constraints. Instead, we just check the CRC. 472 | void try_packet_echo(int len) { 473 | int response_len; 474 | int max_tries = 3; // try receiving response several times 475 | int final_length; 476 | 477 | if (len > MAX_DATA_LEN) { 478 | printf("ECHO length is too long.\n"); 479 | return; 480 | } 481 | 482 | // Create an ECHO packet 483 | buffer[0] = DIR_CMD; 484 | buffer[1] = CMD_ECHO; 485 | for (int i=2; i < len+2; i++) { 486 | buffer[i] = rand() & CURRENT_WIDTH_MASK; 487 | } 488 | final_length = add_packet_crc(buffer, len+2); 489 | 490 | put_frame_with_LED(buffer, final_length); 491 | 492 | for (int i=0; i < max_tries; i++) { 493 | response_len = get_frame_with_expected_data_size(buffer, final_length); 494 | if ((response_len == final_length) 495 | && (buffer[0] == DIR_RSP) 496 | && (buffer[1] == CMD_ECHO) 497 | && check_packet_crc(buffer, response_len)) { 498 | return; 499 | } 500 | } 501 | 502 | printf("No response to ECHO command\n"); 503 | failure(); 504 | } 505 | 506 | 507 | // Send a BABBLE command of a specified length and receive the response. 508 | // 509 | // We send the BABBLE command just once, but try several times to receive the 510 | // response; this allows for the UUT to send debug packets or other unexpected 511 | // responses without failing the test. 512 | void try_babble(int len) { 513 | unsigned char babble_cmd[2 + CHARACTERS_IN_CRC + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_BABBLE }; 514 | int response_len; 515 | int max_tries = 3; // try receiving response several times 516 | int sent_length, recv_length; 517 | 518 | if (len > MAX_DATA_LEN) { 519 | printf("BABBLE length is too long.\n"); 520 | return; 521 | } 522 | 523 | // Create a BABBLE command packet 524 | encode_uint32(babble_cmd+2, len); 525 | sent_length = add_packet_crc(babble_cmd, 10); 526 | recv_length = sent_length + len; 527 | 528 | put_frame_with_LED(babble_cmd, sent_length); 529 | 530 | for (int i=0; i < max_tries; i++) { 531 | response_len = get_frame_with_expected_data_size(buffer, recv_length); 532 | if ((response_len == recv_length) 533 | && (buffer[0] == DIR_RSP) 534 | && (memcmp(buffer+1, babble_cmd+1, 9) == 0) 535 | && check_packet_crc(buffer, response_len)) { 536 | return; 537 | } 538 | } 539 | 540 | printf("No response to BABBLE command\n"); 541 | failure(); 542 | } 543 | 544 | 545 | void cycle_all_params(void) 546 | { 547 | double baud_rates[] = {19200, 9600, 4800, 2400, 1200, 300, 150, 110, 45.45}; 548 | int num_baud_rates = 9; 549 | 550 | int word_widths[] = { SSS_SERIAL_DATA_8, 551 | SSS_SERIAL_DATA_7, 552 | SSS_SERIAL_DATA_6, 553 | SSS_SERIAL_DATA_5, 554 | }; 555 | int num_word_widths = 4; 556 | 557 | int parity_modes[] = { SSS_SERIAL_PARITY_NONE, 558 | SSS_SERIAL_PARITY_EVEN, 559 | SSS_SERIAL_PARITY_ODD, 560 | SSS_SERIAL_PARITY_MARK, 561 | SSS_SERIAL_PARITY_SPACE, 562 | }; 563 | int num_parity_modes = 5; 564 | 565 | int stopbit_modes[] = { SSS_SERIAL_STOP_BIT_1, 566 | SSS_SERIAL_STOP_BIT_1_5, 567 | SSS_SERIAL_STOP_BIT_2, 568 | }; 569 | int num_stopbit_modes = 3; 570 | 571 | for (int baud_i = 0; baud_i < num_baud_rates; baud_i++) { 572 | for (int width_j = 0; width_j < num_word_widths; width_j++) { 573 | for (int parity_k = 0; parity_k < num_parity_modes; parity_k++) { 574 | for (int stopbits_l = 0; stopbits_l < num_stopbit_modes; stopbits_l++) { 575 | change_params(baud_rates[baud_i], 576 | word_widths[width_j], 577 | parity_modes[parity_k], 578 | stopbit_modes[stopbits_l]); 579 | try_packet_echo(100); 580 | } 581 | } 582 | } 583 | } 584 | 585 | // Set params back to nominal 586 | change_params(9600.0, SSS_SERIAL_DATA_8, SSS_SERIAL_PARITY_NONE, SSS_SERIAL_STOP_BIT_1); 587 | try_packet_echo(100); 588 | 589 | } 590 | 591 | 592 | // Arduino one-time setup at startup. 593 | void setup() { 594 | Serial.begin(9600); 595 | sss.begin(9600, SSS_SERIAL_8N1); 596 | 597 | pinMode(BUILTIN_LED, OUTPUT); // LED to flash on transmit 598 | digitalWrite(BUILTIN_LED, 0); 599 | 600 | while (!Serial); 601 | 602 | Serial.println("Slow Soft Serial test controller 0.2"); 603 | 604 | // First emit some unformatted stuff for sanity check 605 | sss.write("Hello UART number one!\r\n"); 606 | sss.flush(); 607 | 608 | // Begin test procedures 609 | get_a_nop_response(); // establish communication with UUT 610 | puts("UUT NOP heard"); 611 | 612 | //set_params(9600.0, 0x0413); // stay at 8N1 for now 613 | change_params(STET, STET, STET, STET); // don't really change for now 614 | 615 | send_nop_with_junk(); // emit some stuff to test frame escaping 616 | send_nop_with_bad_crc();// emit some stuff to test CRC checking 617 | 618 | obtain_uut_info(); // ask the UUT for its identity and display 619 | 620 | for (int i=0; i < 2; i++) { 621 | change_params(1200, STET, STET, STET); // try a real baud rate change 622 | send_nop_with_junk(); 623 | obtain_uut_info(); 624 | change_params(9600, STET, STET, STET); 625 | send_nop_with_junk(); 626 | } 627 | 628 | try_packet_echo(10); 629 | printf("ECHO 10 worked\n"); 630 | try_packet_echo(100); 631 | printf("ECHO 100 worked\n"); 632 | //try_packet_echo(1000); 633 | //printf("ECHO 1000 worked\n"); 634 | //try_packet_echo(10000); 635 | //printf("ECHO 10,000 worked\n"); 636 | 637 | try_babble(100); 638 | printf("BABBLE 100 worked\n"); 639 | //try_babble(1000); 640 | //printf("BABBLE 1000 worked\n"); 641 | //try_babble(10000); 642 | //printf("BABBLE 10000 worked\n"); 643 | 644 | cycle_all_params(); 645 | 646 | puts("Test completed."); 647 | } 648 | 649 | void loop() { 650 | tight_loop_contents(); 651 | } 652 | -------------------------------------------------------------------------------- /test/autotest-ctlr-pico/SSS_test/SSS_test.c: -------------------------------------------------------------------------------- 1 | // Controller Driver for Automated Testing of SlowSoftSerial 2 | // See README.md for discussion and protocol definition. 3 | // 4 | // This program runs as test controller on a Raspberry Pi Pico (first generation), 5 | // connected via serial port to the target platform (UUT for Unit Under Test). 6 | // It is in charge of sequencing the UUT through (some of) its paces. 7 | // 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "pico/stdlib.h" 15 | #include "hardware/uart.h" 16 | #include "hardware/gpio.h" 17 | #include "hardware/divider.h" 18 | #include "hardware/irq.h" 19 | #include "hardware/regs/intctrl.h" 20 | #include "SlowSoftSerial.h" 21 | 22 | // UART defines 23 | // By default the stdout UART is `uart0`, so we will use the second one 24 | #define UART_ID uart1 25 | #define UART_IRQ UART1_IRQ 26 | 27 | // Use pins 4 and 5 for UART1 28 | // Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments 29 | #define UART_TX_PIN 4 30 | #define UART_RX_PIN 5 31 | 32 | // By convention, we start every test at 9600 baud, 8N1 33 | // These definitions are for the local UART 34 | #define INITIAL_BAUD_RATE 9600 35 | #define INITIAL_WORD_WIDTH 8 36 | #define INITIAL_PARITY UART_PARITY_NONE 37 | #define INITIAL_STOP_BITS 1 38 | // and these definitions are for the UUT 39 | double current_baud = 9600.0; 40 | int current_width = SSS_SERIAL_DATA_8; 41 | int current_parity = SSS_SERIAL_PARITY_NONE; 42 | int current_stopbits = SSS_SERIAL_STOP_BIT_1; 43 | 44 | unsigned char width_masks[] = { 0x00, 0x1F, 0x3F, 0x7F, 0xFF }; 45 | #define CURRENT_WIDTH_MASK (width_masks[current_width >> 8]) // convert current_width to a mask 46 | #define CURRENT_WIDTH_BITS ((current_width >> 8) + 4) // convert current_width to # of bits 47 | 48 | // In serial configuration changes, 0 means leave that parameter alone 49 | #define STET 0 50 | 51 | // Pin for on-board LED 52 | #define LED_PIN PICO_DEFAULT_LED_PIN 53 | 54 | // Packet Command Structure 55 | // First Byte: 56 | #define DIR_CMD 0 57 | #define DIR_RSP 1 58 | #define DIR_DBG 2 59 | // Second Byte: 60 | #define CMD_NOP 0 61 | #define CMD_ID 1 62 | #define CMD_ECHO 2 63 | #define CMD_BABBLE 3 64 | #define CMD_PARAMS 4 65 | #define CMD_EXT 0x1f 66 | 67 | #define MAX_DATA_LEN 10000 68 | #define BUFLEN (MAX_DATA_LEN*2+10) // big enough for all bytes to be transposed 69 | unsigned char buffer[BUFLEN]; 70 | 71 | 72 | // Special characters for framing 73 | #define FEND 0x10 // Frame End 74 | #define FESC 0x1B // Frame Escape 75 | #define TFEND 0x1C // Transposed frame end 76 | #define TFESC 0x1D //Transposed frame escape 77 | 78 | #define CHARACTERS_IN_CRC 8 // 32-bit CRC, sent 4 bits per character 79 | 80 | int word_width = INITIAL_WORD_WIDTH; 81 | #define WORD_WIDTH_MASK (0xFF >> (8-word_width)) 82 | 83 | // Interrupt-driver UART buffers for receive and transmit 84 | #define RX_BUF_LEN 64 // arbitrary 85 | unsigned char rx_buf[RX_BUF_LEN]; 86 | volatile int rx_head, rx_tail; 87 | 88 | #define TX_BUF_LEN 64 // arbitrary 89 | unsigned char tx_buf[TX_BUF_LEN]; 90 | volatile int tx_head, tx_tail; 91 | 92 | 93 | // I believe the SDK's handling of UART receive interrupts is wrong for the FIFO enabled 94 | // case. They enable the receive interrupt (which fires when the FIFO reaches a level) 95 | // but they don't enable the timeout interrupt (which fires when the FIFO is non-empty 96 | // but below the trigger level for some duration of time). Thus, the last few characters 97 | // do not get received. This is a patched version of their interrupt enabler that takes 98 | // care of that, or so I hope. 99 | /*! \brief Setup UART interrupts 100 | * \ingroup hardware_uart 101 | * 102 | * Enable the UART's interrupt output. An interrupt handler will need to be installed prior to calling 103 | * this function. 104 | * 105 | * \param uart UART instance. \ref uart0 or \ref uart1 106 | * \param rx_has_data If true an interrupt will be fired when the RX FIFO contain data. 107 | * \param tx_needs_data If true an interrupt will be fired when the TX FIFO needs data. 108 | */ 109 | static inline void my_uart_set_irq_enables(uart_inst_t *uart, bool rx_has_data, bool tx_needs_data) { 110 | uart_get_hw(uart)->imsc = (!!tx_needs_data << UART_UARTIMSC_TXIM_LSB) | 111 | (!!rx_has_data << UART_UARTIMSC_RXIM_LSB) | 112 | (!!rx_has_data << UART_UARTIMSC_RTIM_LSB); // PTW there it is 113 | if (rx_has_data) { 114 | // Set minimum threshold 115 | hw_write_masked(&uart_get_hw(uart)->ifls, 0 << UART_UARTIFLS_RXIFLSEL_LSB, 116 | UART_UARTIFLS_RXIFLSEL_BITS); 117 | } 118 | if (tx_needs_data) { 119 | // Set maximum threshold 120 | hw_write_masked(&uart_get_hw(uart)->ifls, 0 << UART_UARTIFLS_TXIFLSEL_LSB, 121 | UART_UARTIFLS_TXIFLSEL_BITS); 122 | } 123 | } 124 | 125 | 126 | // UART Receive and Transmit Interrupt Handler 127 | // This handler just adds some buffering to allow for interrupt latency. 128 | // It doesn't do any processing. 129 | // 130 | // This interrupt handler is written as if multiple characters could come in 131 | // per interrupt, which could happen if we had UART FIFOs enabled. However, if 132 | // we do that, we don't seem to get a final interrupt at the end of an incoming 133 | // frame. Rather than try to get clever about running the FIFO dry, we'll just 134 | // hope that taking an interrupt for every character is fast enough for our 135 | // purposes. 136 | // 137 | void on_uart_irq(void) 138 | { 139 | while (uart_is_readable(UART_ID)) { 140 | rx_buf[rx_head] = uart_getc(UART_ID); 141 | rx_head = (rx_head + 1) % RX_BUF_LEN; 142 | if (rx_head == rx_tail) { // check for overflow 143 | rx_tail = (rx_tail + 1) % RX_BUF_LEN; // discard oldest char (is this best?) 144 | } 145 | } 146 | 147 | while (uart_is_writable(UART_ID)) { 148 | if (tx_head != tx_tail) { 149 | uart_putc_raw(UART_ID, tx_buf[tx_tail]); 150 | tx_tail = (tx_tail+1) % TX_BUF_LEN; 151 | } else { // transmit buffer is empty now 152 | my_uart_set_irq_enables(UART_ID, true, false); // disable TX interrupts 153 | break; 154 | } 155 | } 156 | } 157 | 158 | 159 | // Blocking UART transmit through the interrupt-driven buffer 160 | void serial_putc(unsigned char c) { 161 | // wait for there to be some room in the buffer 162 | while (tx_tail == (tx_head+1) % TX_BUF_LEN) { 163 | tight_loop_contents(); 164 | } 165 | 166 | tx_buf[tx_head] = c; 167 | tx_head = (tx_head+1) % TX_BUF_LEN; 168 | 169 | my_uart_set_irq_enables(UART_ID, true, true); // make sure TX interrupts are enabled 170 | } 171 | 172 | 173 | // Get one character from the interrupt-driven receive buffer, 174 | // if one is available before the specified absolute timeout. 175 | // 176 | // Returns true if a characters was received, false if not. 177 | // The character is placed in *chr_p 178 | // 179 | bool serial_getc_timeout(absolute_time_t tmax, unsigned char *chr_p) { 180 | while (rx_head == rx_tail) { 181 | if (absolute_time_diff_us(get_absolute_time(), tmax) < 0) { 182 | return false; // timeout before a character was available 183 | } else { 184 | tight_loop_contents(); 185 | } 186 | } 187 | 188 | *chr_p = rx_buf[rx_tail]; // get a character 189 | rx_tail = (rx_tail+1) % RX_BUF_LEN; 190 | return true; 191 | } 192 | 193 | 194 | // Get one complete frame of data from the serial port, and place it in buf, 195 | // if the complete frame arrives within a reasonable time based on the current 196 | // communications parameters and the expected response size. Expected size 197 | // includes only the data characters and not the header, framing, stuffing, 198 | // or CRC. 199 | // 200 | // Returns the number of bytes placed in buf, or 0 if the frame was 201 | // ill-formed or if a complete frame was not received. 202 | int get_frame_with_expected_data_size(unsigned char *buf, int expected_size_in_characters)\ 203 | { 204 | unsigned char *bufp; 205 | unsigned char chr; 206 | int count = 0; 207 | uint32_t timeout = 10 + ((long)expected_size_in_characters*2 + 10) * (CURRENT_WIDTH_BITS + 4) * 1000 / current_baud; 208 | absolute_time_t timeout_time = make_timeout_time_ms(timeout); 209 | 210 | newframe: 211 | 212 | bufp = buf; 213 | 214 | // eat bytes up to first FEND 215 | while (serial_getc_timeout(timeout_time, &chr) && chr != FEND) 216 | ; 217 | 218 | // eat as many FENDs as we find 219 | while (serial_getc_timeout(timeout_time, &chr) && chr == FEND) 220 | ; 221 | 222 | // fill the buffer with received characters, with de-escaping 223 | while ((chr != FEND) && (bufp < buf+BUFLEN)) { 224 | if (chr == FESC) { 225 | (void) serial_getc_timeout(timeout_time, &chr); // don't worry about timeout 226 | if (chr == TFESC) { 227 | *bufp++ = FESC; // put escaped character in buffer 228 | } else if (chr == TFEND) { 229 | *bufp++ = FEND; 230 | } else { 231 | puts("Ill-formed frame"); 232 | return 0; // ill-formed frame 233 | } 234 | } else { 235 | *bufp++ = (unsigned char)chr; // put unescaped character in buffer 236 | } 237 | 238 | if ( ! serial_getc_timeout(timeout_time, &chr)) { // Get next character 239 | printf("Frame timeout %ldms\n", timeout); 240 | return 0; // timeout before a full frame arrives 241 | } 242 | } 243 | 244 | if (bufp >= buf+BUFLEN) 245 | { 246 | puts("Warning: frame is too big! Discarded.\n"); 247 | goto newframe; 248 | } 249 | 250 | // puts("Good frame"); 251 | return (bufp - buf); // return length of buffer 252 | } 253 | 254 | 255 | // Transmit a frame through the interrupt-driven tx buffer, 256 | // adding framing on the fly. 257 | // 258 | // Blocks in serial_putc() if the frame is larger than the space 259 | // available in the transmit buffer. 260 | void put_frame(unsigned char *buf, int len) 261 | { 262 | int i; 263 | 264 | serial_putc(FEND); // all frames begin with FEND 265 | 266 | for (i=0; i> (0 * 4)); 315 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 316 | tbl_idx = crc ^ (data >> (1 * 4)); 317 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 318 | 319 | return crc; 320 | } 321 | 322 | 323 | // The protocol encodes integers, including the CRC used for error detection, 324 | // in the least significant four bits of eight consecutive characters. This 325 | // encoding makes it work with serial word sizes less than 8 bits. 326 | uint32_t decode_uint32(unsigned char *buf) { 327 | uint32_t value; 328 | 329 | // The buffer contains 8 characters which are supposed to be 4 bits wide. 330 | // We don't check, so if any characters are > 0x0F, that will cause the 331 | // result to be wrong. That's what we want for a CRC check. 332 | value = buf[0] << 28 333 | | buf[1] << 24 334 | | buf[2] << 20 335 | | buf[3] << 16 336 | | buf[4] << 12 337 | | buf[5] << 8 338 | | buf[6] << 4 339 | | buf[7] 340 | | (buf[0] & 0xF0) // catch any stray high bits in the top nybble 341 | ; 342 | 343 | return value; 344 | } 345 | 346 | 347 | void encode_uint32(unsigned char *buf, uint32_t value) { 348 | buf[0] = (value >> 28) & 0x0f; 349 | buf[1] = (value >> 24) & 0x0f; 350 | buf[2] = (value >> 20) & 0x0f; 351 | buf[3] = (value >> 16) & 0x0f; 352 | buf[4] = (value >> 12) & 0x0f; 353 | buf[5] = (value >> 8) & 0x0f; 354 | buf[6] = (value >> 4) & 0x0f; 355 | buf[7] = (value ) & 0x0f; 356 | } 357 | 358 | 359 | // Check the CRC found in the last 8 characters of the buffer. 360 | // 361 | // Returns true if the CRC checks. 362 | // 363 | bool check_packet_crc(unsigned char *buf, int len) 364 | { 365 | uint32_t crc = ~0L; 366 | uint32_t packet_crc; 367 | 368 | for (int i=0; i < (len-CHARACTERS_IN_CRC); i++) { 369 | crc = crc_update(crc, buf[i]); 370 | } 371 | crc = ~crc; 372 | packet_crc = decode_uint32(buf + len - CHARACTERS_IN_CRC); 373 | 374 | // Check for CRC equality and return the boolean result 375 | return crc == packet_crc; 376 | } 377 | 378 | 379 | // Given a buffer with extra room reserved at the end for 380 | // a CRC, compute the CRC and write it into the buffer. 381 | // 382 | // Returns the new length of the buffer. 383 | // 384 | int add_packet_crc(unsigned char *buf, int len) 385 | { 386 | unsigned long crc = ~0L; 387 | 388 | for (int i=0; i < len; i++) { 389 | crc = crc_update(crc, buf[i]); 390 | } 391 | crc = ~crc; 392 | encode_uint32(buf+len, crc); 393 | 394 | return(len+CHARACTERS_IN_CRC); 395 | } 396 | 397 | 398 | // Declare a test failure. 399 | // This means just stop and do nothing. Let the user analyze. 400 | void failure(void) 401 | { 402 | printf("Test failed.\n"); 403 | while (1) { 404 | tight_loop_contents(); 405 | } 406 | } 407 | 408 | 409 | //------------- Test Routines -------------------- 410 | 411 | // Send a NOP and try to receive a NOP response. 412 | // We will keep trying forever if the UUT does not respond. 413 | // This is suitable for starting up a fresh connection. 414 | void get_a_nop_response(void) 415 | { 416 | unsigned char nop_cmd[2 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_NOP }; 417 | int len = add_packet_crc(nop_cmd, 2); 418 | int response_len; 419 | int max_tries = 3; // try receiving response several times 420 | 421 | while (1) { 422 | put_frame_with_LED(nop_cmd, len); 423 | 424 | for (int i=0; i < max_tries; i++) { 425 | response_len = get_frame_with_expected_data_size(buffer, 0); 426 | if ((response_len >= (2 + CHARACTERS_IN_CRC)) 427 | && (buffer[0] == DIR_RSP) 428 | && (buffer[1] == CMD_NOP) 429 | && check_packet_crc(buffer, response_len)) { 430 | return; 431 | } 432 | } 433 | } 434 | } 435 | 436 | 437 | // Send a NOP command with some extra bytes in the payload. 438 | // This is permitted by the spec. The UUT is supposed to ignore them 439 | // and not include them in the response. 440 | void send_nop_with_junk(void) 441 | { 442 | unsigned char nop_cmd[12 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_NOP, 'a', 0x10, 'b', 0x1b, 'c', 0x1c, 'd', 0x1d, 'e', 0x1e }; 443 | int len = add_packet_crc(nop_cmd, 12); 444 | int response_len; 445 | int max_tries = 3; // try receiving response several times 446 | 447 | put_frame_with_LED(nop_cmd, len); 448 | 449 | for (int i=0; i < max_tries; i++) { 450 | response_len = get_frame_with_expected_data_size(buffer, 0); 451 | if ((response_len >= (2 + CHARACTERS_IN_CRC)) 452 | && (buffer[0] == DIR_RSP) 453 | && (buffer[1] == CMD_NOP) 454 | && check_packet_crc(buffer, response_len)) { 455 | return; 456 | } 457 | } 458 | 459 | printf("NOP with junk failed\n"); 460 | failure(); 461 | } 462 | 463 | 464 | // Send a NOP command with a bad CRC, to demonstrate CRC checking. 465 | void send_nop_with_bad_crc(void) 466 | { 467 | unsigned char nop_cmd[12 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_NOP, 'a', 0x10, 'b', 0x1b, 'c', 0x1c, 'd', 0x1d, 'e', 0x1e }; 468 | int len = add_packet_crc(nop_cmd, 12); 469 | int response_len; 470 | int max_tries = 3; // try receiving response several times 471 | 472 | nop_cmd[len-1] ^= 1; // insert bit error 473 | put_frame_with_LED(nop_cmd, len); 474 | // We do not expect a response to a packet with a bad CRC. 475 | sleep_ms(30); // leave a gap in the timeline for readability 476 | } 477 | 478 | 479 | // Get identification info from the UUT and print it. 480 | // 481 | // Since we're printing out a message carried in the packet, 482 | // this is only useful for serial word widths of 7 or 8. 483 | // 484 | void obtain_uut_info(void) { 485 | unsigned char id_cmd[2 + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_ID }; 486 | int len = add_packet_crc(id_cmd, 2); 487 | int response_len; 488 | int max_tries = 3; // try receiving response several times 489 | 490 | put_frame_with_LED(id_cmd, len); 491 | 492 | for (int i=0; i < max_tries; i++) { 493 | response_len = get_frame_with_expected_data_size(buffer, 256); 494 | if ((response_len >= (2 + CHARACTERS_IN_CRC)) 495 | && (buffer[0] == DIR_RSP) 496 | && (buffer[1] == CMD_ID) 497 | && check_packet_crc(buffer, response_len)) { 498 | buffer[response_len] = 0; // null terminate response 499 | printf("UUT Info: %s\n", buffer+2); 500 | return; 501 | } 502 | } 503 | 504 | printf("Obtain UUT Info failed.\n"); 505 | failure(); 506 | } 507 | 508 | 509 | // Send a PARAMS packet and wait for the response. 510 | void set_params(double baud, uint16_t config) { 511 | 512 | unsigned char params_cmd[2 + CHARACTERS_IN_CRC * 3] = { DIR_CMD, CMD_PARAMS }; 513 | uint32_t millibaud = baud * 1000; 514 | int response_len; 515 | int max_tries = 3; // try receiving response several times 516 | 517 | encode_uint32(params_cmd+2, millibaud); 518 | encode_uint32(params_cmd+2+8, config); 519 | add_packet_crc(params_cmd, 18); 520 | 521 | put_frame_with_LED(params_cmd, 26); 522 | 523 | for (int i=0; i < max_tries; i++) { 524 | response_len = get_frame_with_expected_data_size(buffer, 16); 525 | if ((response_len == 26) 526 | && (buffer[0] == DIR_RSP) 527 | && (memcmp(buffer+1, params_cmd+1, 17) == 0) 528 | && check_packet_crc(buffer, 26)) { 529 | printf("Set baud=%.03f config=0x%04x\n", floor(baud), config); 530 | return; 531 | } 532 | } 533 | 534 | printf("No response to set params command\n"); 535 | failure(); 536 | } 537 | 538 | 539 | // Complete a change in speed or serial parameters. This includes sending 540 | // the command packet, getting the response, and transitioning the local 541 | // UART to the new settings. 542 | // If any argument is set to 0, that means leave that setting alone. 543 | // The encoding of each config argument is per the SlowSoftSerial specification, 544 | // which implies the non-zero ones can be ORed together to make a config code. 545 | void change_params(double baud, int width, int parity, int stopbits) { 546 | 547 | double new_baud = (baud == STET) ? current_baud : baud; 548 | int new_width = (width == STET) ? current_width : width; 549 | int new_parity = (parity == STET) ? current_parity : parity; 550 | int new_stopbits = (stopbits == STET) ? current_stopbits : stopbits; 551 | 552 | // Send the command and get a response 553 | set_params(new_baud, new_width | new_parity | new_stopbits); 554 | 555 | current_baud = new_baud; 556 | current_width = new_width; 557 | current_parity = new_parity; 558 | current_stopbits = new_stopbits; 559 | 560 | // translate parameters for the local UART 561 | uint uart_baud = (uint)(current_baud + 0.5); // Note we're mangling any fractional part 562 | uint uart_data_bits = (current_width >> 8) + 4; // valid for 5 through 8 563 | uint uart_stop_bits = (current_stopbits == SSS_SERIAL_STOP_BIT_1) ? 1 : 2; // no support for 1.5 564 | uart_parity_t uart_parity = UART_PARITY_NONE; 565 | switch (current_parity) { 566 | case SSS_SERIAL_PARITY_NONE: uart_parity = UART_PARITY_NONE; break; 567 | case SSS_SERIAL_PARITY_EVEN: uart_parity = UART_PARITY_EVEN; break; 568 | case SSS_SERIAL_PARITY_ODD: uart_parity = UART_PARITY_ODD; break; 569 | default: break; // No support for MARK or SPACE parity 570 | } 571 | 572 | // Switch over the local UART 573 | uart_set_baudrate(UART_ID, (uint)current_baud); 574 | uart_set_format(UART_ID, uart_data_bits, uart_stop_bits, uart_parity); 575 | 576 | // Wait for UUT to execute the change 577 | sleep_ms(1); 578 | } 579 | 580 | 581 | // Send an ECHO command of the specified length and receive the response. 582 | // 583 | // We don't check the actual echoed data against the sent data, because of 584 | // (feared) memory constraints. Instead, we just check the CRC. 585 | void try_packet_echo(int len) { 586 | int response_len; 587 | int max_tries = 3; // try receiving response several times 588 | int final_length; 589 | 590 | if (len > MAX_DATA_LEN) { 591 | printf("ECHO length is too long.\n"); 592 | return; 593 | } 594 | 595 | // Create an ECHO packet 596 | buffer[0] = DIR_CMD; 597 | buffer[1] = CMD_ECHO; 598 | for (int i=2; i < len+2; i++) { 599 | buffer[i] = rand() & CURRENT_WIDTH_MASK; 600 | } 601 | final_length = add_packet_crc(buffer, len+2); 602 | 603 | put_frame_with_LED(buffer, final_length); 604 | 605 | for (int i=0; i < max_tries; i++) { 606 | response_len = get_frame_with_expected_data_size(buffer, final_length); 607 | if ((response_len == final_length) 608 | && (buffer[0] == DIR_RSP) 609 | && (buffer[1] == CMD_ECHO) 610 | && check_packet_crc(buffer, response_len)) { 611 | return; 612 | } 613 | } 614 | 615 | printf("No response to ECHO command\n"); 616 | failure(); 617 | } 618 | 619 | 620 | // Send a BABBLE command of a specified length and receive the response. 621 | // 622 | // We send the BABBLE command just once, but try several times to receive the 623 | // response; this allows for the UUT to send debug packets or other unexpected 624 | // responses without failing the test. 625 | void try_babble(int len) { 626 | unsigned char babble_cmd[2 + CHARACTERS_IN_CRC + CHARACTERS_IN_CRC] = { DIR_CMD, CMD_BABBLE }; 627 | int response_len; 628 | int max_tries = 3; // try receiving response several times 629 | int sent_length, recv_length; 630 | 631 | if (len > MAX_DATA_LEN) { 632 | printf("BABBLE length is too long.\n"); 633 | return; 634 | } 635 | 636 | // Create a BABBLE command packet 637 | encode_uint32(babble_cmd+2, len); 638 | sent_length = add_packet_crc(babble_cmd, 10); 639 | recv_length = sent_length + len; 640 | 641 | put_frame_with_LED(babble_cmd, sent_length); 642 | 643 | for (int i=0; i < max_tries; i++) { 644 | response_len = get_frame_with_expected_data_size(buffer, recv_length); 645 | if ((response_len == recv_length) 646 | && (buffer[0] == DIR_RSP) 647 | && (memcmp(buffer+1, babble_cmd+1, 9) == 0) 648 | && check_packet_crc(buffer, response_len)) { 649 | return; 650 | } 651 | } 652 | 653 | printf("No response to BABBLE command\n"); 654 | failure(); 655 | } 656 | 657 | 658 | int main() 659 | { 660 | uint actual_baudrate; 661 | unsigned long iteration; 662 | 663 | stdio_init_all(); 664 | 665 | // Set the TX and RX pins by using the function select on the GPIO 666 | gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); 667 | gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART); 668 | 669 | // Set up our UART 670 | uart_init(UART_ID, INITIAL_BAUD_RATE); 671 | uart_set_hw_flow(UART_ID, false, false); 672 | uart_set_format(UART_ID, INITIAL_WORD_WIDTH, INITIAL_STOP_BITS, INITIAL_PARITY); 673 | uart_set_fifo_enabled(UART_ID, true); // it'd be nice if this had documentation 674 | irq_set_exclusive_handler(UART_IRQ, on_uart_irq); 675 | irq_set_enabled(UART_IRQ, true); 676 | my_uart_set_irq_enables(UART_ID, true, false); // IRQ for receive 677 | // we will enable the transmit IRQ when we've buffered something to transmit 678 | rx_head = rx_tail = 0; 679 | tx_head = tx_tail = 0; 680 | 681 | // Set up LED to blink when transmitting 682 | gpio_init(LED_PIN); 683 | gpio_set_dir(LED_PIN, GPIO_OUT); 684 | 685 | sleep_ms(3000); // Wait for serial terminal to be ready 686 | puts("Hello, this is the Slow Soft Serial test controller"); 687 | 688 | // Transmitting some stuff here seems to unstick the UART. 689 | // !!! Figure out why and do something more elegant. 690 | uart_puts(UART_ID, "Hello UART number one!\r\n"); 691 | uart_tx_wait_blocking(UART_ID); 692 | 693 | // Begin test procedures 694 | get_a_nop_response(); // establish communication with UUT 695 | puts("UUT NOP heard"); 696 | 697 | //set_params(9600.0, 0x0413); // stay at 8N1 for now 698 | change_params(STET, STET, STET, STET); // don't really change for now 699 | 700 | send_nop_with_junk(); // emit some stuff to test frame escaping 701 | send_nop_with_bad_crc();// emit some stuff to test CRC checking 702 | 703 | obtain_uut_info(); // ask the UUT for its identity and display 704 | 705 | change_params(1200, STET, STET, STET); // try a real baud rate change 706 | send_nop_with_junk(); 707 | obtain_uut_info(); 708 | change_params(9600, STET, STET, STET); 709 | send_nop_with_junk(); 710 | 711 | try_packet_echo(10); 712 | try_packet_echo(10); 713 | try_packet_echo(10); 714 | printf("ECHO 10 worked\n"); 715 | try_packet_echo(100); 716 | printf("ECHO 100 worked\n"); 717 | try_packet_echo(1000); 718 | printf("ECHO 1000 worked\n"); 719 | try_packet_echo(10000); 720 | printf("ECHO 10,000 worked\n"); 721 | 722 | try_babble(100); 723 | printf("BABBLE 100 worked\n"); 724 | try_babble(1000); 725 | printf("BABBLE 1000 worked\n"); 726 | try_babble(10000); 727 | printf("BABBLE 10000 worked\n"); 728 | 729 | puts("Test completed."); 730 | 731 | while (1) { 732 | tight_loop_contents(); 733 | } 734 | 735 | return 0; 736 | } 737 | -------------------------------------------------------------------------------- /test/autotest-ctlr-sss/SlowSoftSerial.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "SlowSoftSerial.h" 3 | 4 | #define _SSS_START_LEVEL (_inverse ? HIGH : LOW) 5 | #define _SSS_STOP_LEVEL (_inverse ? LOW : HIGH) 6 | #define CTS_ASSERTED (_inverse ? (digitalRead(_ctsPin) == HIGH) : (digitalRead(_ctsPin) == LOW)) 7 | 8 | // Operations of receive processing. These go in the op table to schedule 9 | // processing that occurs on receive timer interrupts. See design notes. 10 | #define _SSS_OP_NULL 0 11 | #define _SSS_OP_START 1 12 | #define _SSS_OP_CLEAR 2 13 | #define _SSS_OP_VOTE0 3 14 | #define _SSS_OP_VOTE1 4 15 | #define _SSS_OP_SHIFT 5 16 | #define _SSS_OP_STOP 6 17 | #define _SSS_OP_FINAL 7 18 | 19 | // Well, this is ugly. I need to be able to use callback functions for both 20 | // the IntervalTimer and the pin change interrupt. These cannot be member 21 | // functions, unless they are static, and static member functions can't 22 | // access the instance members. Thanks, C++. 23 | // We can tolerate a limitation to only a single serial port, reluctantly, 24 | // so we can save a pointer to the single instance here. That way a static 25 | // member function (the "trampoline") can invoke a non-static member 26 | // callback function. 27 | // The limitation to a single serial port is not too severe, since each 28 | // serial port uses up two precious timers. Teensy processors have either 29 | // two or four timers, so the best we could do would be two serial ports 30 | // anyway. That capability could be added. 31 | 32 | SlowSoftSerial *instance_p = NULL; 33 | int SlowSoftSerial::_active_count = 0; // don't allow more than 1 34 | 35 | // Forward. 36 | static void _rx_start_trampoline(void); 37 | static void _rx_timer_trampoline(void); 38 | static void _tx_trampoline(void); 39 | 40 | /////////////////////////////////////////////////////////////////////// 41 | // Public Member Functions 42 | /////////////////////////////////////////////////////////////////////// 43 | 44 | SlowSoftSerial::SlowSoftSerial(uint8_t rxPin, uint8_t txPin, bool inverse) { 45 | _rxPin = rxPin; 46 | _txPin = txPin; 47 | _inverse = inverse; 48 | _instance_active = false; 49 | } 50 | 51 | 52 | void SlowSoftSerial::begin(double baudrate, uint16_t config) { 53 | if (_active_count > 0) { 54 | return; 55 | } 56 | 57 | _tx_timer.end(); // just in case begin is called out of sequence 58 | 59 | if (baudrate < _SSS_MIN_BAUDRATE) { 60 | return; 61 | } 62 | _tx_microseconds = 1000000.0/baudrate; // 1x baud rate; will be halved in 1.5 stop bit case 63 | _rx_microseconds = 250000.0/baudrate; // 4x baud rate 64 | 65 | _tx_halfbaud = 0; // flag; will be set in 1.5 stop bit cases 66 | 67 | switch (config) { 68 | case SSS_SERIAL_5N1: 69 | _num_bits_to_send = 6; 70 | _stop_bits = 0x20; 71 | _parity_bit = 0; 72 | _parity = SSS_SERIAL_PARITY_NONE; 73 | _databits_mask = 0x1F; 74 | _rx_shiftin_bit = 0x10; 75 | _fill_op_table(5,SSS_SERIAL_STOP_BIT_1); 76 | break; 77 | 78 | case SSS_SERIAL_6N1: 79 | _num_bits_to_send = 7; 80 | _stop_bits = 0x40; 81 | _parity_bit = 0; 82 | _parity = SSS_SERIAL_PARITY_NONE; 83 | _databits_mask = 0x3F; 84 | _rx_shiftin_bit = 0x20; 85 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1); 86 | break; 87 | 88 | case SSS_SERIAL_7N1: 89 | _num_bits_to_send = 8; 90 | _stop_bits = 0x80; 91 | _parity_bit = 0; 92 | _parity = SSS_SERIAL_PARITY_NONE; 93 | _databits_mask = 0x7F; 94 | _rx_shiftin_bit = 0x40; 95 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1); 96 | break; 97 | 98 | case SSS_SERIAL_8N1: 99 | _num_bits_to_send = 9; 100 | _stop_bits = 0x100; 101 | _parity_bit = 0; 102 | _parity = SSS_SERIAL_PARITY_NONE; 103 | _databits_mask = 0xFF; 104 | _rx_shiftin_bit = 0x80; 105 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1); 106 | break; 107 | 108 | case SSS_SERIAL_5N2: 109 | _num_bits_to_send = 7; 110 | _stop_bits = 0x60; 111 | _parity_bit = 0; 112 | _parity = SSS_SERIAL_PARITY_NONE; 113 | _databits_mask = 0x1F; 114 | _rx_shiftin_bit = 0x10; 115 | _fill_op_table(5,SSS_SERIAL_STOP_BIT_2); 116 | break; 117 | 118 | case SSS_SERIAL_6N2: 119 | _num_bits_to_send = 8; 120 | _stop_bits = 0xC0; 121 | _parity_bit = 0; 122 | _parity = SSS_SERIAL_PARITY_NONE; 123 | _databits_mask = 0x3F; 124 | _rx_shiftin_bit = 0x20; 125 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_2); 126 | break; 127 | 128 | case SSS_SERIAL_7N2: 129 | _num_bits_to_send = 9; 130 | _stop_bits = 0x180; 131 | _parity_bit = 0; 132 | _parity = SSS_SERIAL_PARITY_NONE; 133 | _databits_mask = 0x7F; 134 | _rx_shiftin_bit = 0x40; 135 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_2); 136 | break; 137 | 138 | case SSS_SERIAL_8N2: 139 | _num_bits_to_send = 10; 140 | _stop_bits = 0x300; 141 | _parity_bit = 0; 142 | _parity = SSS_SERIAL_PARITY_NONE; 143 | _databits_mask = 0xFF; 144 | _rx_shiftin_bit = 0x80; 145 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_2); 146 | break; 147 | 148 | case SSS_SERIAL_5E1: 149 | _num_bits_to_send = 7; 150 | _stop_bits = 0x40; 151 | _parity_bit = 0x20; 152 | _parity = SSS_SERIAL_PARITY_EVEN; 153 | _databits_mask = 0x1F; 154 | _rx_shiftin_bit = 0x20; 155 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1); 156 | break; 157 | 158 | case SSS_SERIAL_6E1: 159 | _num_bits_to_send = 8; 160 | _stop_bits = 0x80; 161 | _parity_bit = 0x40; 162 | _parity = SSS_SERIAL_PARITY_EVEN; 163 | _databits_mask = 0x3F; 164 | _rx_shiftin_bit = 0x40; 165 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1); 166 | break; 167 | 168 | case SSS_SERIAL_7E1: 169 | _num_bits_to_send = 9; 170 | _stop_bits = 0x100; 171 | _parity_bit = 0x80; 172 | _parity = SSS_SERIAL_PARITY_EVEN; 173 | _databits_mask = 0x7F; 174 | _rx_shiftin_bit = 0x80; 175 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1); 176 | break; 177 | 178 | case SSS_SERIAL_8E1: 179 | _num_bits_to_send = 10; 180 | _stop_bits = 0x200; 181 | _parity_bit = 0x100; 182 | _parity = SSS_SERIAL_PARITY_EVEN; 183 | _databits_mask = 0xFF; 184 | _rx_shiftin_bit = 0x100; 185 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1); 186 | break; 187 | 188 | case SSS_SERIAL_5E2: 189 | _num_bits_to_send = 8; 190 | _stop_bits = 0xC0; 191 | _parity_bit = 0x20; 192 | _parity = SSS_SERIAL_PARITY_EVEN; 193 | _databits_mask = 0x1F; 194 | _rx_shiftin_bit = 0x20; 195 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_2); 196 | break; 197 | 198 | case SSS_SERIAL_6E2: 199 | _num_bits_to_send = 9; 200 | _stop_bits = 0x180; 201 | _parity_bit = 0x40; 202 | _parity = SSS_SERIAL_PARITY_EVEN; 203 | _databits_mask = 0x3F; 204 | _rx_shiftin_bit = 0x40; 205 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_2); 206 | break; 207 | 208 | case SSS_SERIAL_7E2: 209 | _num_bits_to_send = 10; 210 | _stop_bits = 0x300; 211 | _parity_bit = 0x80; 212 | _parity = SSS_SERIAL_PARITY_EVEN; 213 | _databits_mask = 0x7F; 214 | _rx_shiftin_bit = 0x80; 215 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_2); 216 | break; 217 | 218 | case SSS_SERIAL_8E2: 219 | _num_bits_to_send = 11; 220 | _stop_bits = 0x600; 221 | _parity_bit = 0x100; 222 | _parity = SSS_SERIAL_PARITY_EVEN; 223 | _databits_mask = 0xFF; 224 | _rx_shiftin_bit = 0x100; 225 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_2); 226 | break; 227 | 228 | case SSS_SERIAL_5O1: 229 | _num_bits_to_send = 7; 230 | _stop_bits = 0x40; 231 | _parity_bit = 0x20; 232 | _parity = SSS_SERIAL_PARITY_ODD; 233 | _databits_mask = 0x1F; 234 | _rx_shiftin_bit = 0x20; 235 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1); 236 | break; 237 | 238 | case SSS_SERIAL_6O1: 239 | _num_bits_to_send = 8; 240 | _stop_bits = 0x80; 241 | _parity_bit = 0x40; 242 | _parity = SSS_SERIAL_PARITY_ODD; 243 | _databits_mask = 0x3F; 244 | _rx_shiftin_bit = 0x40; 245 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1); 246 | break; 247 | 248 | case SSS_SERIAL_7O1: 249 | _num_bits_to_send = 9; 250 | _stop_bits = 0x100; 251 | _parity_bit = 0x80; 252 | _parity = SSS_SERIAL_PARITY_ODD; 253 | _databits_mask = 0x7F; 254 | _rx_shiftin_bit = 0x80; 255 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1); 256 | break; 257 | 258 | case SSS_SERIAL_8O1: 259 | _num_bits_to_send = 10; 260 | _stop_bits = 0x200; 261 | _parity_bit = 0x100; 262 | _parity = SSS_SERIAL_PARITY_ODD; 263 | _databits_mask = 0xFF; 264 | _rx_shiftin_bit = 0x100; 265 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1); 266 | break; 267 | 268 | case SSS_SERIAL_5O2: 269 | _num_bits_to_send = 8; 270 | _stop_bits = 0xC0; 271 | _parity_bit = 0x20; 272 | _parity = SSS_SERIAL_PARITY_ODD; 273 | _databits_mask = 0x1F; 274 | _rx_shiftin_bit = 0x20; 275 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_2); 276 | break; 277 | 278 | case SSS_SERIAL_6O2: 279 | _num_bits_to_send = 9; 280 | _stop_bits = 0x180; 281 | _parity_bit = 0x40; 282 | _parity = SSS_SERIAL_PARITY_ODD; 283 | _databits_mask = 0x3F; 284 | _rx_shiftin_bit = 0x40; 285 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_2); 286 | break; 287 | 288 | case SSS_SERIAL_7O2: 289 | _num_bits_to_send = 10; 290 | _stop_bits = 0x300; 291 | _parity_bit = 0x80; 292 | _parity = SSS_SERIAL_PARITY_ODD; 293 | _databits_mask = 0x7F; 294 | _rx_shiftin_bit = 0x80; 295 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_2); 296 | break; 297 | 298 | case SSS_SERIAL_8O2: 299 | _num_bits_to_send = 11; 300 | _stop_bits = 0x600; 301 | _parity_bit = 0x100; 302 | _parity = SSS_SERIAL_PARITY_ODD; 303 | _databits_mask = 0xFF; 304 | _rx_shiftin_bit = 0x100; 305 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_2); 306 | break; 307 | 308 | case SSS_SERIAL_5M1: 309 | _num_bits_to_send = 7; 310 | _stop_bits = 0x40; 311 | _parity_bit = 0x20; 312 | _parity = SSS_SERIAL_PARITY_MARK; 313 | _databits_mask = 0x1F; 314 | _rx_shiftin_bit = 0x20; 315 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1); 316 | break; 317 | 318 | case SSS_SERIAL_6M1: 319 | _num_bits_to_send = 8; 320 | _stop_bits = 0x80; 321 | _parity_bit = 0x40; 322 | _parity = SSS_SERIAL_PARITY_MARK; 323 | _databits_mask = 0x3F; 324 | _rx_shiftin_bit = 0x40; 325 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1); 326 | break; 327 | 328 | case SSS_SERIAL_7M1: 329 | _num_bits_to_send = 9; 330 | _stop_bits = 0x100; 331 | _parity_bit = 0x80; 332 | _parity = SSS_SERIAL_PARITY_MARK; 333 | _databits_mask = 0x7F; 334 | _rx_shiftin_bit = 0x80; 335 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1); 336 | break; 337 | 338 | case SSS_SERIAL_8M1: 339 | _num_bits_to_send = 10; 340 | _stop_bits = 0x200; 341 | _parity_bit = 0x100; 342 | _parity = SSS_SERIAL_PARITY_MARK; 343 | _databits_mask = 0xFF; 344 | _rx_shiftin_bit = 0x100; 345 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1); 346 | break; 347 | 348 | case SSS_SERIAL_5M2: 349 | _num_bits_to_send = 8; 350 | _stop_bits = 0xC0; 351 | _parity_bit = 0x20; 352 | _parity = SSS_SERIAL_PARITY_MARK; 353 | _databits_mask = 0x1F; 354 | _rx_shiftin_bit = 0x20; 355 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_2); 356 | break; 357 | 358 | case SSS_SERIAL_6M2: 359 | _num_bits_to_send = 9; 360 | _stop_bits = 0x180; 361 | _parity_bit = 0x40; 362 | _parity = SSS_SERIAL_PARITY_MARK; 363 | _databits_mask = 0x3F; 364 | _rx_shiftin_bit = 0x40; 365 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_2); 366 | break; 367 | 368 | case SSS_SERIAL_7M2: 369 | _num_bits_to_send = 10; 370 | _stop_bits = 0x300; 371 | _parity_bit = 0x80; 372 | _parity = SSS_SERIAL_PARITY_MARK; 373 | _databits_mask = 0x7F; 374 | _rx_shiftin_bit = 0x80; 375 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_2); 376 | break; 377 | 378 | case SSS_SERIAL_8M2: 379 | _num_bits_to_send = 11; 380 | _stop_bits = 0x600; 381 | _parity_bit = 0x100; 382 | _parity = SSS_SERIAL_PARITY_MARK; 383 | _databits_mask = 0xFF; 384 | _rx_shiftin_bit = 0x100; 385 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_2); 386 | break; 387 | 388 | case SSS_SERIAL_5S1: 389 | _num_bits_to_send = 7; 390 | _stop_bits = 0x40; 391 | _parity_bit = 0x20; 392 | _parity = SSS_SERIAL_PARITY_SPACE; 393 | _databits_mask = 0x1F; 394 | _rx_shiftin_bit = 0x20; 395 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1); 396 | break; 397 | 398 | case SSS_SERIAL_6S1: 399 | _num_bits_to_send = 8; 400 | _stop_bits = 0x80; 401 | _parity_bit = 0x40; 402 | _parity = SSS_SERIAL_PARITY_SPACE; 403 | _databits_mask = 0x3F; 404 | _rx_shiftin_bit = 0x40; 405 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1); 406 | break; 407 | 408 | case SSS_SERIAL_7S1: 409 | _num_bits_to_send = 9; 410 | _stop_bits = 0x100; 411 | _parity_bit = 0x80; 412 | _parity = SSS_SERIAL_PARITY_SPACE; 413 | _databits_mask = 0x7F; 414 | _rx_shiftin_bit = 0x80; 415 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1); 416 | break; 417 | 418 | case SSS_SERIAL_8S1: 419 | _num_bits_to_send = 10; 420 | _stop_bits = 0x200; 421 | _parity_bit = 0x100; 422 | _parity = SSS_SERIAL_PARITY_SPACE; 423 | _databits_mask = 0xFF; 424 | _rx_shiftin_bit = 0x100; 425 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1); 426 | break; 427 | 428 | case SSS_SERIAL_5S2: 429 | _num_bits_to_send = 8; 430 | _stop_bits = 0xC0; 431 | _parity_bit = 0x20; 432 | _parity = SSS_SERIAL_PARITY_SPACE; 433 | _databits_mask = 0x1F; 434 | _rx_shiftin_bit = 0x20; 435 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_2); 436 | break; 437 | 438 | case SSS_SERIAL_6S2: 439 | _num_bits_to_send = 9; 440 | _stop_bits = 0x180; 441 | _parity_bit = 0x40; 442 | _parity = SSS_SERIAL_PARITY_SPACE; 443 | _databits_mask = 0x3F; 444 | _rx_shiftin_bit = 0x40; 445 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_2); 446 | break; 447 | 448 | case SSS_SERIAL_7S2: 449 | _num_bits_to_send = 10; 450 | _stop_bits = 0x300; 451 | _parity_bit = 0x80; 452 | _parity = SSS_SERIAL_PARITY_SPACE; 453 | _databits_mask = 0x7F; 454 | _rx_shiftin_bit = 0x80; 455 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_2); 456 | break; 457 | 458 | case SSS_SERIAL_8S2: 459 | _num_bits_to_send = 11; 460 | _stop_bits = 0x600; 461 | _parity_bit = 0x100; 462 | _parity = SSS_SERIAL_PARITY_SPACE; 463 | _databits_mask = 0xFF; 464 | _rx_shiftin_bit = 0x100; 465 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_2); 466 | break; 467 | 468 | case SSS_SERIAL_5N15: 469 | _num_bits_to_send = 6; 470 | _stop_bits = 0x60; 471 | _parity_bit = 0; 472 | _parity = SSS_SERIAL_PARITY_NONE; 473 | _databits_mask = 0x1F; 474 | _rx_shiftin_bit = 0x10; 475 | _fill_op_table(5,SSS_SERIAL_STOP_BIT_1_5); 476 | _tx_halfbaud = 1; 477 | _tx_microseconds = _tx_microseconds/2.0; 478 | break; 479 | 480 | case SSS_SERIAL_6N15: 481 | _num_bits_to_send = 7; 482 | _stop_bits = 0xC0; 483 | _parity_bit = 0; 484 | _parity = SSS_SERIAL_PARITY_NONE; 485 | _databits_mask = 0x3F; 486 | _rx_shiftin_bit = 0x20; 487 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1_5); 488 | _tx_halfbaud = 1; 489 | _tx_microseconds = _tx_microseconds/2.0; 490 | break; 491 | 492 | case SSS_SERIAL_7N15: 493 | _num_bits_to_send = 8; 494 | _stop_bits = 0x180; 495 | _parity_bit = 0; 496 | _parity = SSS_SERIAL_PARITY_NONE; 497 | _databits_mask = 0x7F; 498 | _rx_shiftin_bit = 0x40; 499 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1_5); 500 | _tx_halfbaud = 1; 501 | _tx_microseconds = _tx_microseconds/2.0; 502 | break; 503 | 504 | case SSS_SERIAL_8N15: 505 | _num_bits_to_send = 9; 506 | _stop_bits = 0x300; 507 | _parity_bit = 0; 508 | _parity = SSS_SERIAL_PARITY_NONE; 509 | _databits_mask = 0xFF; 510 | _rx_shiftin_bit = 0x80; 511 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1_5); 512 | _tx_halfbaud = 1; 513 | _tx_microseconds = _tx_microseconds/2.0; 514 | break; 515 | 516 | case SSS_SERIAL_5E15: 517 | _num_bits_to_send = 7; 518 | _stop_bits = 0xC0; 519 | _parity_bit = 0x20; 520 | _parity = SSS_SERIAL_PARITY_EVEN; 521 | _databits_mask = 0x1F; 522 | _rx_shiftin_bit = 0x20; 523 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1_5); 524 | _tx_halfbaud = 1; 525 | _tx_microseconds = _tx_microseconds/2.0; 526 | break; 527 | 528 | case SSS_SERIAL_6E15: 529 | _num_bits_to_send = 8; 530 | _stop_bits = 0x180; 531 | _parity_bit = 0x40; 532 | _parity = SSS_SERIAL_PARITY_EVEN; 533 | _databits_mask = 0x3F; 534 | _rx_shiftin_bit = 0x40; 535 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1_5); 536 | _tx_halfbaud = 1; 537 | _tx_microseconds = _tx_microseconds/2.0; 538 | break; 539 | 540 | case SSS_SERIAL_7E15: 541 | _num_bits_to_send = 9; 542 | _stop_bits = 0x300; 543 | _parity_bit = 0x80; 544 | _parity = SSS_SERIAL_PARITY_EVEN; 545 | _databits_mask = 0x7F; 546 | _rx_shiftin_bit = 0x80; 547 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1_5); 548 | _tx_halfbaud = 1; 549 | _tx_microseconds = _tx_microseconds/2.0; 550 | break; 551 | 552 | case SSS_SERIAL_8E15: 553 | _num_bits_to_send = 10; 554 | _stop_bits = 0x600; 555 | _parity_bit = 0x100; 556 | _parity = SSS_SERIAL_PARITY_EVEN; 557 | _databits_mask = 0xFF; 558 | _rx_shiftin_bit = 0x100; 559 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1_5); 560 | _tx_halfbaud = 1; 561 | _tx_microseconds = _tx_microseconds/2.0; 562 | break; 563 | 564 | case SSS_SERIAL_5O15: 565 | _num_bits_to_send = 7; 566 | _stop_bits = 0xC0; 567 | _parity_bit = 0x20; 568 | _parity = SSS_SERIAL_PARITY_ODD; 569 | _databits_mask = 0x1F; 570 | _rx_shiftin_bit = 0x20; 571 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1_5); 572 | _tx_halfbaud = 1; 573 | _tx_microseconds = _tx_microseconds/2.0; 574 | break; 575 | 576 | case SSS_SERIAL_6O15: 577 | _num_bits_to_send = 8; 578 | _stop_bits = 0x180; 579 | _parity_bit = 0x40; 580 | _parity = SSS_SERIAL_PARITY_ODD; 581 | _databits_mask = 0x3F; 582 | _rx_shiftin_bit = 0x40; 583 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1_5); 584 | _tx_halfbaud = 1; 585 | _tx_microseconds = _tx_microseconds/2.0; 586 | break; 587 | 588 | case SSS_SERIAL_7O15: 589 | _num_bits_to_send = 9; 590 | _stop_bits = 0x300; 591 | _parity_bit = 0x80; 592 | _parity = SSS_SERIAL_PARITY_ODD; 593 | _databits_mask = 0x7F; 594 | _rx_shiftin_bit = 0x80; 595 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1_5); 596 | _tx_halfbaud = 1; 597 | _tx_microseconds = _tx_microseconds/2.0; 598 | break; 599 | 600 | case SSS_SERIAL_8O15: 601 | _num_bits_to_send = 10; 602 | _stop_bits = 0x600; 603 | _parity_bit = 0x100; 604 | _parity = SSS_SERIAL_PARITY_ODD; 605 | _databits_mask = 0xFF; 606 | _rx_shiftin_bit = 0x100; 607 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1_5); 608 | _tx_halfbaud = 1; 609 | _tx_microseconds = _tx_microseconds/2.0; 610 | break; 611 | 612 | case SSS_SERIAL_5M15: 613 | _num_bits_to_send = 7; 614 | _stop_bits = 0xC0; 615 | _parity_bit = 0x20; 616 | _parity = SSS_SERIAL_PARITY_MARK; 617 | _databits_mask = 0x1F; 618 | _rx_shiftin_bit = 0x20; 619 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1_5); 620 | _tx_halfbaud = 1; 621 | _tx_microseconds = _tx_microseconds/2.0; 622 | break; 623 | 624 | case SSS_SERIAL_6M15: 625 | _num_bits_to_send = 8; 626 | _stop_bits = 0x180; 627 | _parity_bit = 0x40; 628 | _parity = SSS_SERIAL_PARITY_MARK; 629 | _databits_mask = 0x3F; 630 | _rx_shiftin_bit = 0x40; 631 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1_5); 632 | _tx_halfbaud = 1; 633 | _tx_microseconds = _tx_microseconds/2.0; 634 | break; 635 | 636 | case SSS_SERIAL_7M15: 637 | _num_bits_to_send = 9; 638 | _stop_bits = 0x300; 639 | _parity_bit = 0x80; 640 | _parity = SSS_SERIAL_PARITY_MARK; 641 | _databits_mask = 0x7F; 642 | _rx_shiftin_bit = 0x80; 643 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1_5); 644 | _tx_halfbaud = 1; 645 | _tx_microseconds = _tx_microseconds/2.0; 646 | break; 647 | 648 | case SSS_SERIAL_8M15: 649 | _num_bits_to_send = 10; 650 | _stop_bits = 0x600; 651 | _parity_bit = 0x100; 652 | _parity = SSS_SERIAL_PARITY_MARK; 653 | _databits_mask = 0xFF; 654 | _rx_shiftin_bit = 0x100; 655 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1_5); 656 | _tx_halfbaud = 1; 657 | _tx_microseconds = _tx_microseconds/2.0; 658 | break; 659 | 660 | case SSS_SERIAL_5S15: 661 | _num_bits_to_send = 7; 662 | _stop_bits = 0xC0; 663 | _parity_bit = 0x20; 664 | _parity = SSS_SERIAL_PARITY_SPACE; 665 | _databits_mask = 0x1F; 666 | _rx_shiftin_bit = 0x20; 667 | _fill_op_table(6,SSS_SERIAL_STOP_BIT_1_5); 668 | _tx_halfbaud = 1; 669 | _tx_microseconds = _tx_microseconds/2.0; 670 | break; 671 | 672 | case SSS_SERIAL_6S15: 673 | _num_bits_to_send = 8; 674 | _stop_bits = 0x180; 675 | _parity_bit = 0x40; 676 | _parity = SSS_SERIAL_PARITY_SPACE; 677 | _databits_mask = 0x3F; 678 | _rx_shiftin_bit = 0x40; 679 | _fill_op_table(7,SSS_SERIAL_STOP_BIT_1_5); 680 | _tx_halfbaud = 1; 681 | _tx_microseconds = _tx_microseconds/2.0; 682 | break; 683 | 684 | case SSS_SERIAL_7S15: 685 | _num_bits_to_send = 9; 686 | _stop_bits = 0x300; 687 | _parity_bit = 0x80; 688 | _parity = SSS_SERIAL_PARITY_SPACE; 689 | _databits_mask = 0x7F; 690 | _rx_shiftin_bit = 0x80; 691 | _fill_op_table(8,SSS_SERIAL_STOP_BIT_1_5); 692 | _tx_halfbaud = 1; 693 | _tx_microseconds = _tx_microseconds/2.0; 694 | break; 695 | 696 | case SSS_SERIAL_8S15: 697 | _num_bits_to_send = 10; 698 | _stop_bits = 0x600; 699 | _parity_bit = 0x100; 700 | _parity = SSS_SERIAL_PARITY_SPACE; 701 | _databits_mask = 0xFF; 702 | _rx_shiftin_bit = 0x100; 703 | _fill_op_table(9,SSS_SERIAL_STOP_BIT_1_5); 704 | _tx_halfbaud = 1; 705 | _tx_microseconds = _tx_microseconds/2.0; 706 | break; 707 | 708 | default: 709 | return; // failure, unrecognized configuration word 710 | } 711 | 712 | // Initialize transmit 713 | // Writing both before and after eliminates a potential glitch. 714 | digitalWriteFast(_txPin, _SSS_STOP_LEVEL); 715 | pinMode(_txPin, OUTPUT); 716 | digitalWriteFast(_txPin, _SSS_STOP_LEVEL); 717 | 718 | _tx_buffer_count = 0; 719 | _tx_write_index = 0; 720 | _tx_read_index = 0; 721 | _tx_bit_count = 0; 722 | _rts_attached = false; 723 | _cts_attached = false; 724 | _tx_enabled = true; 725 | _tx_running = false; 726 | 727 | // Initialize receive 728 | pinMode(_rxPin, _inverse ? INPUT_PULLDOWN : INPUT_PULLUP); 729 | 730 | _rx_buffer_count = 0; 731 | _rx_write_index = 0; 732 | _rx_read_index = 0; 733 | 734 | // Keep track of our one and only active instance 735 | _instance_active = true; 736 | _active_count = 1; 737 | instance_p = this; 738 | 739 | attachInterrupt(digitalPinToInterrupt(_rxPin), _rx_start_trampoline, _inverse ? RISING : FALLING); 740 | } 741 | 742 | 743 | void SlowSoftSerial::end(bool releasePins) { 744 | if (!_instance_active) { 745 | return; 746 | } 747 | 748 | _tx_timer.end(); // called first to avoid any conflict for variables 749 | _rx_timer.end(); 750 | detachInterrupt(digitalPinToInterrupt(_rxPin)); 751 | 752 | if (releasePins == SSS_RELEASE_PINS) { 753 | pinMode(_txPin, INPUT); 754 | pinMode(_rxPin, INPUT); 755 | pinMode(_ctsPin, INPUT); 756 | } 757 | 758 | _tx_buffer_count = 0; 759 | _tx_enabled = false; 760 | _tx_running = false; 761 | _cts_attached = false; 762 | 763 | // this instance is no longer active, so it's OK to activate another one 764 | instance_p = NULL; 765 | _active_count = 0; 766 | _instance_active = false; 767 | } 768 | 769 | 770 | int SlowSoftSerial::available(void) { 771 | return _rx_buffer_count; 772 | } 773 | 774 | 775 | int SlowSoftSerial::availableForWrite(void) { 776 | return _SSS_TX_BUFFER_SIZE - _tx_buffer_count; 777 | } 778 | 779 | 780 | int SlowSoftSerial::peek(void) { 781 | int chr; 782 | 783 | if (_rx_buffer_count > 0) { 784 | chr = _rx_buffer[_rx_read_index]; 785 | // If we were going to check receive parity, this would be the place to do it. 786 | // However, the Arduino serial API has no notion of checking for errors. 787 | return chr & _databits_mask; 788 | } else { 789 | return -1; // nothing available to peek 790 | } 791 | } 792 | 793 | 794 | int SlowSoftSerial::read(void) { 795 | int chr = peek(); 796 | 797 | if (chr != -1) { 798 | if (++_rx_read_index >= _SSS_RX_BUFFER_SIZE) { 799 | _rx_read_index = 0; 800 | } 801 | 802 | noInterrupts(); 803 | _rx_buffer_count--; 804 | interrupts(); 805 | } 806 | 807 | return chr; 808 | } 809 | 810 | 811 | // NOTE: flush() can take unbounded time to complete 812 | // if CTS flow control is in use. 813 | void SlowSoftSerial::flush(void) { 814 | while (_tx_buffer_count > 0 || _tx_running) { 815 | yield(); 816 | } 817 | } 818 | 819 | 820 | size_t SlowSoftSerial::write(uint8_t chr) { 821 | uint16_t data_as_sent; 822 | 823 | // What should we do with characters that don't fit in the word size? 824 | // Probably the least surprising and confusing thing is to send them 825 | // anyway, truncated to the word size. 826 | chr &= _databits_mask; 827 | 828 | data_as_sent = _add_parity(chr) | _stop_bits; 829 | if (_inverse) { 830 | data_as_sent ^= 0xFFFF; 831 | } 832 | 833 | // Arduino Stream semantics require a blocking write() 834 | while (_tx_buffer_count >= _SSS_TX_BUFFER_SIZE) { // assumed atomic 835 | yield(); 836 | } 837 | 838 | // add this character to the transmit buffer 839 | // This is safe, because tx_write_index is not used by the ISR. 840 | _tx_buffer[_tx_write_index++] = data_as_sent; 841 | if (_tx_write_index >= _SSS_TX_BUFFER_SIZE) { 842 | _tx_write_index = 0; 843 | } 844 | 845 | noInterrupts(); 846 | _tx_buffer_count++; 847 | interrupts(); 848 | 849 | // Start the baud rate interrupt if it isn't already running 850 | // Note: we waste a baud before starting to transmit, in order 851 | // to keep the transmit logic all in one place (the interrupt) 852 | if (!_tx_running) { 853 | if (_tx_timer.begin(_tx_trampoline, _tx_microseconds)) { 854 | _tx_running = true; 855 | _tx_baud_divider = _tx_halfbaud; // only waste half a baud in 1.5 stop bits case 856 | } 857 | } 858 | 859 | return 1; // We "sent" the one character 860 | } 861 | 862 | 863 | void SlowSoftSerial::attachCts(uint8_t pin_number) { 864 | _ctsPin = pin_number; 865 | _cts_attached = true; 866 | pinMode(_ctsPin, _inverse ? INPUT_PULLUP : INPUT_PULLDOWN); 867 | } 868 | 869 | 870 | /////////////////////////////////////////////////////////////////////// 871 | // Transmit Private Functions 872 | /////////////////////////////////////////////////////////////////////// 873 | 874 | // Determine the number of 1 bits in the character. 875 | // Return 1 if it's an odd number, 0 if it's an even number. 876 | static bool _parity_is_odd(uint8_t chr) { 877 | chr = chr ^ (chr >> 4); // isn't this clever? Not original with me. 878 | chr = chr ^ (chr >> 2); 879 | chr = chr ^ (chr >> 1); 880 | return (chr & 0x01); 881 | } 882 | 883 | 884 | uint16_t SlowSoftSerial::_add_parity(uint8_t chr) { 885 | uint16_t data_word = chr; 886 | 887 | switch (_parity) { 888 | case SSS_SERIAL_PARITY_ODD: 889 | if (!_parity_is_odd(chr)) { 890 | data_word |= _parity_bit; 891 | } 892 | break; 893 | 894 | case SSS_SERIAL_PARITY_EVEN: 895 | if (_parity_is_odd(chr)) { 896 | data_word |= _parity_bit; 897 | } 898 | break; 899 | 900 | case SSS_SERIAL_PARITY_MARK: 901 | data_word |= _parity_bit; 902 | break; 903 | 904 | case SSS_SERIAL_PARITY_SPACE: 905 | default: 906 | break; 907 | } 908 | 909 | return data_word; 910 | } 911 | 912 | 913 | // Handle the hardware transmit interrupt for 1.5-stop-bits case. 914 | // 915 | // In the common cases, the transmit interrupt comes once per baud. 916 | // In order to handle accurate timing for the case of 1.5 stop bits, 917 | // we run the transmit interrupt at double the baud rate. 918 | // 919 | // All processing is exactly the same except that an extra half baud 920 | // is inserted at the end of the stop bit. So, this function passes 921 | // interrupts through to the normal _tx_baud_handler() function. 922 | // After processing 1 stop bit in the 1.5 stop bit case, 923 | // _tx_baud_handler() sets the tx_baud_divider flag, which causes 924 | // the following passed-through interrupt(s) to be advanced by half a baud. 925 | void SlowSoftSerial::_tx_halfbaud_handler(void) { 926 | if (_tx_baud_divider) { 927 | _tx_baud_divider = 0; // order is important here! 928 | _tx_baud_handler(); // because this may overwrite _tx_baud_divider 929 | } else { 930 | _tx_baud_divider = 1; 931 | } 932 | } 933 | 934 | // Handle the transmit interrupt 935 | // Interrupt occurs periodically while we're actively transmitting 936 | // or waiting for handshaking to allow transmitting. 937 | // For most cases we only need the interrupts that occur at an 938 | // integer number of bauds from the beginning of the start bit. 939 | // For cases with 1.5 stop bits, though, we need to take one final 940 | // interrupt that's halfway between those integer bauds. 941 | void SlowSoftSerial::_tx_baud_handler(void) { 942 | uint16_t data_as_sent; 943 | 944 | if (_tx_bit_count > 0) { 945 | // We're in the middle of sending a character, keep sending it 946 | digitalWriteFast(_txPin, _tx_data_word & 0x01); 947 | _tx_data_word >>= 1; 948 | _tx_bit_count--; 949 | return; 950 | } 951 | 952 | if (_tx_extra_half_stop) { 953 | // We've sent the first 1 stop bit, now send a half-baud more 954 | _tx_extra_half_stop = 0; 955 | _tx_baud_divider = 1; // this jumps timing by half a baud 956 | return; 957 | } 958 | 959 | if (_tx_buffer_count == 0) { 960 | // Nothing more to transmit right now, shut it down 961 | _tx_running = false; 962 | _tx_timer.end(); 963 | digitalWriteFast(_txPin, _SSS_STOP_LEVEL); // just to be sure 964 | return; 965 | } 966 | 967 | if (!_tx_enabled || (_cts_attached && !CTS_ASSERTED)) { 968 | // we are not allowed to transmit right now. 969 | // keep the timer interrupt running, we'll poll. 970 | return; 971 | } 972 | 973 | // Get the next character and begin to send it 974 | data_as_sent = _tx_buffer[_tx_read_index++]; 975 | if (_tx_read_index >= _SSS_TX_BUFFER_SIZE) { 976 | _tx_read_index = 0; 977 | } 978 | _tx_buffer_count--; 979 | digitalWriteFast(_txPin, _SSS_START_LEVEL); 980 | _tx_data_word = data_as_sent; 981 | _tx_bit_count = _num_bits_to_send; 982 | _tx_extra_half_stop = _tx_halfbaud; 983 | } 984 | 985 | 986 | void _tx_trampoline(void) { 987 | if (instance_p->_tx_halfbaud) { 988 | instance_p->_tx_halfbaud_handler(); 989 | } else { 990 | instance_p->_tx_baud_handler(); 991 | } 992 | } 993 | 994 | 995 | /////////////////////////////////////////////////////////////////////// 996 | // Receive Private Functions 997 | /////////////////////////////////////////////////////////////////////// 998 | 999 | // Create the operations schedule table. See design notes. 1000 | // This controls what happens on each RX timer event during a 1001 | // single character of reception. 1002 | // rxbits includes data bits and parity bits, if any. 1003 | void SlowSoftSerial::_fill_op_table(int rxbits, int stopbits) { 1004 | int i = 0; 1005 | int bit; 1006 | 1007 | _rx_op_table[i++] = _SSS_OP_START; 1008 | _rx_op_table[i++] = _SSS_OP_START; 1009 | _rx_op_table[i++] = _SSS_OP_START; 1010 | _rx_op_table[i++] = _SSS_OP_CLEAR; 1011 | for (bit=0; bit < rxbits; bit++) { 1012 | _rx_op_table[i++] = _SSS_OP_VOTE0; 1013 | _rx_op_table[i++] = _SSS_OP_VOTE1; 1014 | _rx_op_table[i++] = _SSS_OP_VOTE1; 1015 | _rx_op_table[i++] = _SSS_OP_SHIFT; 1016 | } 1017 | _rx_op_table[i++] = _SSS_OP_STOP; 1018 | _rx_op_table[i++] = _SSS_OP_STOP; 1019 | if (stopbits == SSS_SERIAL_STOP_BIT_1_5) { 1020 | _rx_op_table[i++] = _SSS_OP_STOP; 1021 | _rx_op_table[i++] = _SSS_OP_STOP; 1022 | } else if (stopbits == SSS_SERIAL_STOP_BIT_2) { 1023 | _rx_op_table[i++] = _SSS_OP_STOP; 1024 | _rx_op_table[i++] = _SSS_OP_STOP; 1025 | _rx_op_table[i++] = _SSS_OP_STOP; 1026 | _rx_op_table[i++] = _SSS_OP_STOP; 1027 | } 1028 | _rx_op_table[i++] = _SSS_OP_FINAL; 1029 | } 1030 | 1031 | 1032 | void SlowSoftSerial::_rx_start_handler(void) { 1033 | 1034 | if (_rx_timer.begin(_rx_timer_trampoline, _rx_microseconds)) { 1035 | detachInterrupt(digitalPinToInterrupt(_rxPin)); 1036 | _rx_op = 0; // start at the 0th operation in the table 1037 | } else { 1038 | // timer not available, but there isn't much we can do. 1039 | // continue to try every time we see a start bit! 1040 | } 1041 | } 1042 | 1043 | 1044 | static void _rx_start_trampoline(void) { 1045 | instance_p->_rx_start_handler(); 1046 | } 1047 | 1048 | 1049 | void SlowSoftSerial::_rx_timer_handler(void) { 1050 | 1051 | // The worst case execution path through this routine probably determines 1052 | // the impact we have on interrupt latency for other handlers in the system, 1053 | // so we've gone to some length to keep each path short. 1054 | // A switch statement might or might not be slower than a jump table, 1055 | // depending on compiler optimization. Switch statements are easier, 1056 | // and the difference is not likely to be huge. 1057 | 1058 | switch (_rx_op_table[_rx_op++]) { 1059 | case _SSS_OP_START: 1060 | // We are somewhere in the middle of the start bit. 1061 | // Just make sure it's still a valid start bit. 1062 | if (digitalRead(_rxPin) != _SSS_START_LEVEL) { 1063 | // stop the timer and go back to waiting for a start bit. 1064 | // must have been noise, or baud rate error, or something. 1065 | _rx_timer.end(); 1066 | attachInterrupt(digitalPinToInterrupt(_rxPin), _rx_start_trampoline, _inverse ? RISING : FALLING); 1067 | } 1068 | break; 1069 | 1070 | case _SSS_OP_CLEAR: 1071 | // We have reached the end of the stop bit. So far, so good. 1072 | // This interrupt is on top of a possible data transition, so we 1073 | // can't meaningfully sample the RX pin. We can just get set up 1074 | // for receiving the data bits. 1075 | _rx_data_word = 0; 1076 | break; 1077 | 1078 | case _SSS_OP_VOTE0: 1079 | // We're ready to take the first sample of a data or parity bit. 1080 | _rx_bit_value = digitalRead(_rxPin); 1081 | break; 1082 | 1083 | case _SSS_OP_VOTE1: 1084 | // We're still in the middle of a data or parity bit. 1085 | // Just make sure it hasn't changed on us. 1086 | if (digitalRead(_rxPin) != _rx_bit_value) { 1087 | // stop the timer and go back to waiting for a start bit. 1088 | // must have been noise, or baud rate error, or something. 1089 | _rx_timer.end(); 1090 | attachInterrupt(digitalPinToInterrupt(_rxPin), _rx_start_trampoline, _inverse ? RISING : FALLING); 1091 | } 1092 | break; 1093 | 1094 | case _SSS_OP_SHIFT: 1095 | // We have reached the end of a data or parity bit. 1096 | // This interrupt is on top of a possible data transition, so we 1097 | // can't meaningfully sample the RX pin. We can just shift the 1098 | // new bit in. The LS bit arrives first, so we have to shift right 1099 | // to get the bits in the right order. 1100 | _rx_data_word >>= 1; 1101 | if (_rx_bit_value) { 1102 | _rx_data_word |= _rx_shiftin_bit; 1103 | } 1104 | break; 1105 | 1106 | case _SSS_OP_STOP: 1107 | // We are somewhere in the middle of the stop bit. 1108 | // Just make sure it's a valid stop bit. 1109 | if (digitalRead(_rxPin) != _SSS_STOP_LEVEL) { 1110 | // stop the timer and go back to waiting for a start bit. 1111 | // must have been noise, or baud rate error, or something. 1112 | _rx_timer.end(); 1113 | attachInterrupt(digitalPinToInterrupt(_rxPin), _rx_start_trampoline, _inverse ? RISING : FALLING); 1114 | } 1115 | break; 1116 | 1117 | case _SSS_OP_FINAL: 1118 | // We have reached the last sample point near the end of the stop bit. 1119 | // This will be our last timer event for this character, because the earliest 1120 | // possible start bit for the next character comes at the same instant as the 1121 | // next timer event would. 1122 | // We'll check one last time that the stop bit is valid, and then we'll 1123 | // wrap up processing for this received character. Either way, we'll set up 1124 | // for receiving the next character. 1125 | if (digitalRead(_rxPin) == _SSS_STOP_LEVEL) { 1126 | // stop bit passed the last check, no timing errors on this character! 1127 | // We store the data and parity bits. If there's to be any parity checking, 1128 | // it must occur as the characters are read out of the buffer (and not in 1129 | // interrupt context). 1130 | if (_rx_buffer_count < _SSS_RX_BUFFER_SIZE) { 1131 | _rx_buffer[_rx_write_index++] = _rx_data_word; 1132 | if (_rx_write_index >= _SSS_RX_BUFFER_SIZE) { 1133 | _rx_write_index = 0; 1134 | } 1135 | 1136 | _rx_buffer_count++; 1137 | } 1138 | } 1139 | // stop the timer and go back to waiting for a start bit. 1140 | _rx_timer.end(); 1141 | attachInterrupt(digitalPinToInterrupt(_rxPin), _rx_start_trampoline, _inverse ? RISING : FALLING); 1142 | break; 1143 | 1144 | case _SSS_OP_NULL: 1145 | default: 1146 | break; 1147 | } 1148 | 1149 | } 1150 | 1151 | 1152 | void _rx_timer_trampoline(void) { 1153 | instance_p->_rx_timer_handler(); 1154 | } 1155 | --------------------------------------------------------------------------------