├── Examples ├── SoftSerialIntAPTest │ └── SoftSerialIntAPTest.ino ├── SoftSerialIntExample │ └── SoftSerialIntExample.ino └── TwoPortRXExample │ └── TwoPortRXExample.ino ├── README.md ├── SoftSerialIntAP.cpp ├── SoftSerialIntAP.h └── keywords.txt /Examples/SoftSerialIntAPTest/SoftSerialIntAPTest.ino: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | SoftSerialIntAP Test Program 3 | Multi-instance Library/C++ Object for Maple/other STM32F1/Wiring 4 | Copyright 2015 Ron Curry, InSyte Technologies 5 | Author Ron Curry, InSyte Technologies 6 | 7 | * Permission is hereby granted, free of charge, to any person 8 | * obtaining a copy of this software and associated documentation 9 | * files (the "Software"), to deal in the Software without 10 | * restriction, including without limitation the rights to use, copy, 11 | * modify, merge, publish, distribute, sublicense, and/or sell copies 12 | * of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 22 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 23 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | **************************************************************/ 27 | 28 | #include 29 | #include 30 | #include 31 | //#include 32 | #include 33 | #include "SoftSerialIntAP.h" 34 | 35 | 36 | // lDelay - Simple delay function independent of systick or interrupts 37 | // Total delay is approximately 1ms per iteration @ 72mhz clock 38 | // delay overhead is ~+1.487usec. one loop = ~137ns * 7300. 39 | // Therefore, total delay is ~ (delay * 0.137us * 7300) + 1.487us 40 | inline void lDelay(uint32_t delay) { 41 | uint32_t i, j; 42 | 43 | j = delay * 7300; 44 | for (i = 0; i < j; i++) { 45 | asm volatile( 46 | "nop \n\t" 47 | ::); 48 | } 49 | } 50 | 51 | 52 | SoftSerialIntAP SWSerial1(20, 21, 4); 53 | SoftSerialIntAP SWSerial0(15, 16, 3); 54 | 55 | void setup() { 56 | // put your setup code here, to run once: 57 | 58 | SWSerial1.begin(115200); 59 | SWSerial0.begin(115200); 60 | Serial.begin(230400); 61 | 62 | // systick_disable(); 63 | 64 | } 65 | 66 | uint32_t baudrate[] = {300, 1200,2400, 4800,9600,14400,19200,28800,31250,38400,57600, 115200}; 67 | int16_t currentBaud = 11; 68 | int receiveFlag = 0; 69 | int silentRXFlag = 0; 70 | int txFlag = 0; 71 | int txTestFlag = 1; 72 | int rxSourceFlag = 0; 73 | int txSourceFlag = 0; 74 | int exFlag = 0; 75 | int exIndex = 0; 76 | uint16_t stepSize = 1; 77 | char testString0[] = "Testing TX Port 0 ...\n"; 78 | char testString1[] = "Testing TX Port 1 ...\n"; 79 | char testString2[] = "0123456789\n"; 80 | void loop() { 81 | char inChar; 82 | 83 | lDelay (3000); 84 | Serial.println("Starting now...."); 85 | while (1) { 86 | 87 | 88 | if (exFlag) { 89 | 90 | // First sync up by clearing read buffer and write buffer 91 | while (SWSerial0.available() > 0) 92 | SWSerial0.read(); 93 | 94 | lDelay(5000); 95 | 96 | while (1) { 97 | SWSerial0.write((char)(testString2[exIndex])); 98 | while (1) { 99 | if (Serial.available()) 100 | goto jOut; 101 | 102 | if (SWSerial0.available()) { 103 | break; 104 | } 105 | } 106 | 107 | inChar = (char)SWSerial0.read(); 108 | Serial.print(inChar); 109 | 110 | exIndex++; 111 | if (exIndex > 10) 112 | exIndex = 0; 113 | } 114 | } 115 | jOut: 116 | 117 | 118 | //************************************************************************ 119 | if (txFlag) { 120 | if ((txSourceFlag == 1) || (txSourceFlag == 2)) { 121 | if (txTestFlag == 0) 122 | SWSerial1.write(0x55); 123 | else if (txTestFlag == 1) { 124 | SWSerial1.write(testString1, 22); 125 | } else 126 | SWSerial1.write(testString2, 10); 127 | lDelay(20); 128 | } 129 | if ((txSourceFlag == 0) || (txSourceFlag == 2)) { 130 | if (txTestFlag == 0) 131 | SWSerial0.write(0x55); 132 | else if (txTestFlag == 1) { 133 | SWSerial0.write(testString0, 22); 134 | } else 135 | SWSerial0.write(testString2, 10); 136 | 137 | lDelay(20); 138 | } 139 | } 140 | 141 | //************************************************************************* 142 | if (receiveFlag) { 143 | if ((rxSourceFlag == 1) || (rxSourceFlag == 2)) { 144 | while (SWSerial1.available()) { 145 | if (SWSerial1.overflow()) { 146 | Serial.println("\nOVERFLOW Port 1"); 147 | 148 | #if DEBUG_DELAY 149 | Serial.print("Buffer write pointer, buffer read pointer = "); 150 | Serial.print(SWSerial1.getOverFlowTail(), DEC); 151 | Serial.print(" "); 152 | Serial.println(SWSerial1.getOverFlowHead(), DEC); 153 | // receiveFlag = 0; 154 | #endif 155 | } 156 | else { 157 | inChar = SWSerial1.read(); 158 | if (silentRXFlag == 2) { 159 | Serial.write(inChar); 160 | } 161 | } 162 | } 163 | } 164 | 165 | if ((rxSourceFlag == 0) || (rxSourceFlag == 2)) { 166 | while (SWSerial0.available()) { 167 | if (SWSerial0.overflow()) { 168 | Serial.println("\nOVERFLOW Port 0"); 169 | 170 | #if DEBUG_DELAY 171 | Serial.print("Buffer write pointer, buffer read pointer = "); 172 | Serial.print(SWSerial0.getOverFlowTail(), DEC); 173 | Serial.print(" "); 174 | Serial.println(SWSerial0.getOverFlowHead(), DEC); 175 | // receiveFlag = 0; 176 | #endif 177 | } 178 | else { 179 | inChar = SWSerial0.read(); 180 | if (silentRXFlag == 1) { 181 | Serial.write(inChar); 182 | } 183 | } 184 | } 185 | } 186 | 187 | } 188 | 189 | 190 | if (Serial.available()) { 191 | 192 | inChar = Serial.read(); 193 | 194 | switch (inChar) { 195 | case 'x': 196 | exFlag ^= 1; 197 | exIndex = 0; 198 | Serial.print("\nSerial Exchange Test = "); Serial.println(exFlag, DEC); 199 | break; 200 | case '?': 201 | Serial.println("Commands...."); 202 | Serial.println("? - Display this help menu."); 203 | Serial.println("R - Toggle on/off receive test"); 204 | Serial.println("r - Toggle receive source between port 0, 1, both"); 205 | Serial.println("p - Toggle on/off print received bytes to console"); 206 | Serial.println("T - Toggle on/off transmit test"); 207 | Serial.println("t - Toggle transmit source between port 0, 1, both"); 208 | Serial.println("5 - Toggle between sending a string or 0x55"); 209 | Serial.println("x - Toggle on/off round robin send/receive test."); 210 | Serial.println(" (Requires serialtest_maple running on seperate device)"); 211 | Serial.println("B - Bump baud rate higher"); 212 | Serial.println("b - Bump baud rate lower"); 213 | Serial.println("I - Bump intra-bit delay higher"); 214 | Serial.println("i - Bump intra-bit delay lower"); 215 | Serial.println("C - Bump bit centering later"); 216 | Serial.println("c - Bump bit centering earlier"); 217 | Serial.println("s = Print status of flags and other info"); 218 | break; 219 | case 's': 220 | Serial.print("\n\nR - "); Serial.print("RX Test off(0) or on(1) = "); Serial.println(receiveFlag, DEC); 221 | Serial.print("r - "); Serial.print("RX Port "); Serial.println(rxSourceFlag, DEC); 222 | Serial.print("p - "); Serial.print("Print to console off(0) or on(1) = "); Serial.println(silentRXFlag, DEC); 223 | Serial.print("T - "); Serial.print("TX Test off(0) or on(1) = "); Serial.println(txFlag, DEC); 224 | Serial.print("5 - "); if (txTestFlag) Serial.println("Sending string"); else Serial.println("Sending 0x55"); 225 | Serial.print("t - "); Serial.print("TX Port = "); Serial.println(txSourceFlag, DEC); 226 | Serial.print("Baud = "); Serial.println(baudrate[currentBaud], DEC); 227 | #if DEBUG_DELAY 228 | Serial.print("Timer Port 0 = "); Serial.println(SWSerial0.getrxtxTimer(), DEC); 229 | Serial.print("Timer Port 1 = "); Serial.println(SWSerial1.getrxtxTimer(), DEC); 230 | Serial.print("Bitperiod = "); Serial.println(SWSerial0.getBitPeriod(), DEC); 231 | Serial.print("Centering = "); Serial.println(SWSerial0.getBitCentering(), DEC); 232 | Serial.print("Stepsize = "); Serial.println(stepSize, DEC); 233 | Serial.print("TXRead = "); Serial.println(SWSerial0.getTXHead(), DEC); 234 | Serial.print("TXWrite = "); Serial.println(SWSerial0.getTXTail(), DEC); 235 | Serial.print("RXRead = "); Serial.println(SWSerial0.getRXHead(), DEC); 236 | Serial.print("RXWrite = "); Serial.println(SWSerial0.getRXTail(), DEC); 237 | #endif 238 | Serial.print("Available = "); Serial.println(SWSerial0.available(), DEC); 239 | break; 240 | case 'T': 241 | // do tx test 242 | txFlag ^= 1; 243 | Serial.print("\nTX Test = "); Serial.println(txFlag, DEC); 244 | break; 245 | case 't': 246 | txSourceFlag += 1; 247 | if (txSourceFlag > 2) 248 | txSourceFlag = 0; 249 | Serial.print("\nTransmit source = "); Serial.println(txSourceFlag, DEC); 250 | break; 251 | case '5': 252 | txTestFlag += 1; 253 | if (txTestFlag >2) 254 | txTestFlag = 0; 255 | break; 256 | case 'R': 257 | receiveFlag ^= 1; 258 | break; 259 | case 'r': 260 | rxSourceFlag += 1; 261 | if (rxSourceFlag > 2) 262 | rxSourceFlag = 0; 263 | Serial.print("\nSource = "); Serial.println(rxSourceFlag, DEC); 264 | break; 265 | case 'p': 266 | silentRXFlag ^= 1; 267 | break; 268 | case 'B': 269 | // bump up baud to next rate 270 | SWSerial0.end(); 271 | SWSerial1.end(); 272 | currentBaud++; 273 | if (currentBaud > 11) 274 | currentBaud = 0; 275 | SWSerial0.begin(baudrate[currentBaud]); 276 | SWSerial1.begin(baudrate[currentBaud]); 277 | Serial.println(); Serial.println(baudrate[currentBaud], DEC); 278 | break; 279 | case 'b': 280 | // bump up baud to next rate 281 | SWSerial0.end(); 282 | currentBaud--; 283 | if (currentBaud < 0) 284 | currentBaud = 11; 285 | SWSerial0.begin(baudrate[currentBaud]); 286 | SWSerial1.begin(baudrate[currentBaud]); 287 | Serial.println(); Serial.println(baudrate[currentBaud], DEC); 288 | break; 289 | 290 | #if DEBUG_DELAY 291 | case 'I': 292 | // bump up rx intrabit delay value 293 | SWSerial0.setBitPeriod(SWSerial0.getBitPeriod() + stepSize); 294 | SWSerial1.setBitPeriod(SWSerial1.getBitPeriod() + stepSize); 295 | break; 296 | case 'i': 297 | // bump up rx intrabit delay value 298 | SWSerial0.setBitPeriod(SWSerial0.getBitPeriod() - stepSize); 299 | SWSerial1.setBitPeriod(SWSerial1.getBitPeriod() - stepSize); 300 | break; 301 | case 'C': 302 | // bump up rx bit centering delay value 303 | SWSerial0.setBitCentering(SWSerial0.getBitCentering() + stepSize); 304 | SWSerial1.setBitCentering(SWSerial1.getBitCentering() + stepSize); 305 | break; 306 | case 'c': 307 | // bump down rx centering delay value 308 | SWSerial0.setBitCentering(SWSerial0.getBitCentering() - stepSize); 309 | SWSerial1.setBitCentering(SWSerial1.getBitCentering() - stepSize); 310 | break; 311 | case '0': 312 | // set step size to 1 313 | stepSize = 1; 314 | break; 315 | case '1': 316 | // set step size to 10 317 | stepSize = 10; 318 | break; 319 | case '2': 320 | // set step size to 100 321 | stepSize = 100; 322 | break; 323 | #endif 324 | default: 325 | break; 326 | } 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /Examples/SoftSerialIntExample/SoftSerialIntExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | SoftSerialInt mySerial(15, 16, 3); 4 | 5 | void setup() 6 | { 7 | Serial.begin(57600); 8 | Serial.println("Goodnight moon!"); 9 | 10 | // set the data rate for the SoftwareSerial port 11 | mySerial.begin(4800); 12 | mySerial.println("Hello, world?"); 13 | } 14 | 15 | void loop() // run over and over 16 | { 17 | if (mySerial.available()) 18 | Serial.print((char)mySerial.read()); 19 | if (Serial.available()) 20 | mySerial.print((char)Serial.read()); 21 | } 22 | -------------------------------------------------------------------------------- /Examples/TwoPortRXExample/TwoPortRXExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | SoftSerialInt ss(15, 16, 3); 4 | SoftSerialInt ss2(20, 21, 4); 5 | 6 | /* This sample shows how to correctly process received data 7 | on two different "soft" serial ports. Here we listen on 8 | the first port (ss) until we receive a '?' character. Then 9 | we begin listening on the other soft port. 10 | */ 11 | 12 | void setup() 13 | { 14 | // Start the HW serial port 15 | Serial.begin(57600); 16 | 17 | // Start each soft serial port 18 | ss.begin(4800); 19 | ss2.begin(4800); 20 | 21 | // By default, the most recently "begun" port is listening. 22 | // We want to listen on ss, so let's explicitly select it. 23 | ss.listen(); 24 | 25 | // Simply wait for a ? character to come down the pipe 26 | Serial.println("Data from the first port: "); 27 | char c = 0; 28 | do 29 | if (ss.available()) 30 | { 31 | c = (char)ss.read(); 32 | Serial.print(c); 33 | } 34 | while (c != '?'); 35 | 36 | // Now listen on the second port 37 | ss2.listen(); 38 | 39 | Serial.println("Data from the second port: "); 40 | } 41 | 42 | void loop() 43 | { 44 | if (ss2.available()) 45 | { 46 | char c = (char)ss2.read(); 47 | Serial.print(c); 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SoftSerialIntAP 2 | Interrupt Driven Software Serial Library for STM32Duino - Use any GPIO 3 | 4 | * Multi-instance Software Serial Library for STM32Duino 5 | * Using Timers and Interrupts enabling use of any GPIO pins for RX/TX 6 | * 7 | * Copyright 2015 Ron Curry, InSyte Technologies 8 | * 9 | 10 | Features: 11 | - Fully interrupt driven - no delay routines. 12 | - Any GPIO pin may be used for tx or rx (hence the AP libray name suffix) 13 | - Circular buffers on both send and receive. 14 | - Works up to 115,200 baud. 15 | - Member functions compatible with Hardware Serial and Arduino NewSoftSerial Libs 16 | - Extensions for non-blocking read and transmit control 17 | - Supports up to 4 ports (one timer used per port) without modification. 18 | - Easily modified for more ports or different timers on chips with more timers. 19 | - Can do full duplex under certain circumstances at lower baud rates. 20 | - Can send/receive simultaneously with other ports/instantiatious at some baud 21 | rates and certain circumstances. 22 | 23 | Notes: 24 | 25 | Performance 26 | - More than two ports use has not been extensively tested. High ISR latencies in the 27 | low-level Maple timer ISR code, C++ ISR wranglings, and the STM32 sharing interrupt 28 | architecture (not in that order) restrict simultaneous port use and speeds. Two ports 29 | sending simultaniously at 115,200. As well, two ports simultansiously receiving at 57,600 30 | simultaneously have been successfully tested. Various other situations have been 31 | tested. Results are dependent on various factors - you'll need to experiment. 32 | 33 | Reliability 34 | - Because of the way STM32 shares interrupts and the way STM32Arduino low level ISRs 35 | processes them latencies can be very high for interrupts on a given timer. I won't go 36 | into all the details of why but some interrupts can be essentially locked out. Causing 37 | extremely delayed interrupt servicing. Some of this could be alleviated with more 38 | sophisticated low-level ISRs that make sure that all shared interrupt sources get 39 | serviced on an equal basis but that's not been done yet. 40 | This impacts the ability to do full duplex on a single port even at medium bit rates. 41 | I've done some experimentation with two ports/instantiations with RX on one port 42 | and TX on the other with good results for full-duplex. In any case, to be sure 43 | that the serial data streams are not corrupted at higher data rates it's best to 44 | write the application software such that rx and tx are not sending/receiving 45 | simultaneously in each port and that other ports are not operating simultaneously 46 | as well. This is, effectively, how the existing NewSoftSerial library on Arduino 47 | operates so not a new concept. Again, experiment and find what works in your 48 | application. 49 | 50 | Improvements 51 | No doubt the code can be improved upon. If you use the code PLEASE give back by 52 | providing soure code for improvements or modifications you've made! 53 | - Specific improvements that come to mind and I'd like to explore are: 54 | o Replacing the STM32/Maple timer interrupt handlers with something more streamlined 55 | and lower latency and overhead. 56 | o A better way to implement the high level C++ ISR's to reduce latency/overhead 57 | o Minor improvements that can save cycles in the C++ ISR's such as using bit-banding 58 | o Possibly a way to coordinate RX/TX to increase full-duplex capability. 59 | 60 | License 61 | * Permission is hereby granted, free of charge, to any person 62 | * obtaining a copy of this software and associated documentation 63 | * files (the "Software"), to deal in the Software without 64 | * restriction, including without limitation the rights to use, copy, 65 | * modify, merge, publish, distribute, sublicense, and/or sell copies 66 | * of the Software, and to permit persons to whom the Software is 67 | * furnished to do so, subject to the following conditions: 68 | * 69 | * The above copyright notice and this permission notice shall be 70 | * included in all copies or substantial portions of the Software. 71 | * 72 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 73 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 74 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 75 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 76 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 77 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 78 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 79 | * SOFTWARE. 80 | -------------------------------------------------------------------------------- /SoftSerialIntAP.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * SoftSerialIntAP.cpp 3 | * Multi-instance Software Serial Library for STM32Duino 4 | * Using Timers and Interrupts enabling use of any GPIO pins for RX/TX 5 | * 6 | * Copyright 2015 Ron Curry, InSyte Technologies 7 | * 8 | 9 | Features: 10 | - Fully interrupt driven - no delay routines. 11 | - Any GPIO pin may be used for tx or rx (hence the AP libray name suffix) 12 | - Circular buffers on both send and receive. 13 | - Works up to 115,200 baud. 14 | - Member functions compatible with Hardware Serial and Arduino NewSoftSerial Libs 15 | - Extensions for non-blocking read and transmit control 16 | - Supports up to 4 ports (one timer used per port) without modification. 17 | - Easily modified for more ports or different timers on chips with more timers. 18 | - Can do full duplex under certain circumstances at lower baud rates. 19 | - Can send/receive simultaneously with other ports/instantiatious at some baud 20 | rates and certain circumstances. 21 | 22 | Notes: 23 | 24 | Performance 25 | - More than two ports use has not been extensively tested. High ISR latencies in the 26 | low-level Maple timer ISR code, C++ ISR wranglings, and the STM32 sharing interrupt 27 | architecture (not in that order) restrict simultaneous port use and speeds. Two ports 28 | sending simultaniously at 115,200. As well, two ports simultansiously receiving at 57,600 29 | simultaneously have been successfully tested. Various other situations have been 30 | tested. Results are dependent on various factors - you'll need to experiment. 31 | 32 | Reliability 33 | - Because of the way STM32 shares interrupts and the way STM32Arduino low level ISRs 34 | processes them latencies can be very high for interrupts on a given timer. I won't go 35 | into all the details of why but some interrupts can be essentially locked out. Causing 36 | extremely delayed interrupt servicing. Some of this could be alleviated with more 37 | sophisticated low-level ISRs that make sure that all shared interrupt sources get 38 | serviced on an equal basis but that's not been done yet. 39 | This impacts the ability to do full duplex on a single port even at medium bit rates. 40 | I've done some experimentation with two ports/instantiations with RX on one port 41 | and TX on the other with good results for full-duplex. In any case, to be sure 42 | that the serial data streams are not corrupted at higher data rates it's best to 43 | write the application software such that rx and tx are not sending/receiving 44 | simultaneously in each port and that other ports are not operating simultaneously 45 | as well. This is, effectively, how the existing NewSoftSerial library on Arduino 46 | operates so not a new concept. Again, experiment and find what works in your 47 | application. 48 | 49 | Improvements 50 | No doubt the code can be improved upon. If you use the code PLEASE give back by 51 | providing soure code for improvements or modifications you've made! 52 | - Specific improvements that come to mind and I'd like to explore are: 53 | o Replacing the STM32/Maple timer interrupt handlers with something more streamlined 54 | and lower latency and overhead. 55 | o A better way to implement the high level C++ ISR's to reduce latency/overhead 56 | o Minor improvements that can save cycles in the C++ ISR's such as using bit-banding 57 | o Possibly a way to coordinate RX/TX to increase full-duplex capability. 58 | 59 | License 60 | * Permission is hereby granted, free of charge, to any person 61 | * obtaining a copy of this software and associated documentation 62 | * files (the "Software"), to deal in the Software without 63 | * restriction, including without limitation the rights to use, copy, 64 | * modify, merge, publish, distribute, sublicense, and/or sell copies 65 | * of the Software, and to permit persons to whom the Software is 66 | * furnished to do so, subject to the following conditions: 67 | * 68 | * The above copyright notice and this permission notice shall be 69 | * included in all copies or substantial portions of the Software. 70 | * 71 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 72 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 73 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 74 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 75 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 76 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 77 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 78 | * SOFTWARE. 79 | *****************************************************************************/ 80 | 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include "SoftSerialIntAP.h" 86 | 87 | /****************************************************************************** 88 | * Timer Definitions 89 | * Change these if you wish to use different timer channels 90 | ******************************************************************************/ 91 | #define TIMER_MAX_COUNT 0xffff 92 | #define TX_TIMER_CHANNEL TIMER_CH3 93 | #define TX_TIMER_MASK TIMER_DIER_CC3IE_BIT 94 | #define TX_TIMER_PENDING TIMER_SR_CC3IF_BIT 95 | #define TX_CCR CCR3 96 | #define RX_TIMER_CHANNEL TIMER_CH4 97 | #define RX_TIMER_MASK TIMER_DIER_CC4IE_BIT 98 | #define RX_TIMER_PENDING TIMER_SR_CC4IF_BIT 99 | #define RX_CCR CCR4 100 | 101 | /****************************************************************************** 102 | * ISR Related to Statics 103 | * Don't modify anything here unless you know why and what 104 | ******************************************************************************/ 105 | SoftSerialIntAP *SoftSerialIntAP::interruptObject1; 106 | SoftSerialIntAP *SoftSerialIntAP::interruptObject2; 107 | SoftSerialIntAP *SoftSerialIntAP::interruptObject3; 108 | SoftSerialIntAP *SoftSerialIntAP::interruptObject4; 109 | 110 | 111 | voidFuncPtr SoftSerialIntAP::handleRXEdgeInterruptP[4] = { 112 | handleRXEdgeInterrupt1, handleRXEdgeInterrupt2, handleRXEdgeInterrupt3, handleRXEdgeInterrupt4 113 | }; 114 | 115 | voidFuncPtr SoftSerialIntAP::handleRXBitInterruptP[4] = { 116 | handleRXBitInterrupt1, handleRXBitInterrupt2, handleRXBitInterrupt3, handleRXBitInterrupt4 117 | }; 118 | 119 | voidFuncPtr SoftSerialIntAP::handleTXBitInterruptP[4] = { 120 | handleTXBitInterrupt1, handleTXBitInterrupt2, handleTXBitInterrupt3, handleTXBitInterrupt4 121 | }; 122 | 123 | 124 | /****************************************************************************** 125 | * Convenience functions to disable/enable tx and rx interrupts 126 | ******************************************************************************/ 127 | // Mask transmit interrupt 128 | inline void SoftSerialIntAP::noTXInterrupts() { 129 | *bb_perip(&(timerSerialDEV->regs).gen->DIER, TX_TIMER_MASK) = 0; 130 | } 131 | 132 | 133 | // Enable transmit interrupt 134 | // Note: Purposely does not clear pending interrupt 135 | inline void SoftSerialIntAP::txInterrupts() { 136 | *bb_perip(&(timerSerialDEV->regs).gen->DIER, TX_TIMER_MASK) = 1; 137 | } 138 | 139 | 140 | // Test if transmit interrupt is enabled 141 | inline uint16_t SoftSerialIntAP::isTXInterruptEnabled() { 142 | return (*bb_perip(&(timerSerialDEV->regs).gen->DIER, TX_TIMER_MASK)); 143 | } 144 | 145 | 146 | // Clear pending interrupt and enable receive interrupt 147 | // Note: Clears pending interrupt 148 | inline void SoftSerialIntAP::txInterruptsClr() { 149 | *bb_perip(&(timerSerialDEV->regs).gen->SR, TX_TIMER_PENDING) = 0; // Clear int pending 150 | *bb_perip(&(timerSerialDEV->regs).gen->DIER, TX_TIMER_MASK) = 1; 151 | } 152 | 153 | 154 | // Mask receive start bit interrupt 155 | inline void SoftSerialIntAP::noRXStartInterrupts() { 156 | bb_peri_set_bit(&EXTI_BASE->FTSR, gpioBit, 0); 157 | } 158 | 159 | 160 | // Enable receive start bit interrupt 161 | // Note: Purposely does not clear pending interrupt 162 | inline void SoftSerialIntAP::rxStartInterrupts() { 163 | bb_peri_set_bit(&EXTI_BASE->FTSR, gpioBit, 1); 164 | } 165 | 166 | 167 | // Mask receive interrupt 168 | inline void SoftSerialIntAP::noRXInterrupts() { 169 | *bb_perip(&(timerSerialDEV->regs).gen->DIER, RX_TIMER_MASK) = 0; 170 | } 171 | 172 | 173 | // Enable receive interrupt 174 | // Note: Purposely does not clear pending interrupt 175 | inline void SoftSerialIntAP::rxInterrupts() { 176 | *bb_perip(&(timerSerialDEV->regs).gen->DIER, RX_TIMER_MASK) = 1; 177 | } 178 | 179 | 180 | // Clear pending interrupt and enable receive interrupt 181 | // Note: Clears pending interrupt 182 | inline void SoftSerialIntAP::rxInterruptsClr() { 183 | *bb_perip(&(timerSerialDEV->regs).gen->SR, RX_TIMER_PENDING) = 0; // Clear int pending 184 | *bb_perip(&(timerSerialDEV->regs).gen->DIER, RX_TIMER_MASK) = 1; 185 | 186 | } 187 | 188 | 189 | /****************************************************************************** 190 | * Specialized functions to set interrupt priorities and assign object ointers 191 | * These are needed due to the gyrations required to support per instance ISRs 192 | ******************************************************************************/ 193 | // Set Interrupt Priority for EXTInt line 194 | void setEXTIntPriority(uint8_t pin, uint8_t priority) { 195 | 196 | switch((exti_num)(PIN_MAP[pin].gpio_bit)) { 197 | case EXTI0: 198 | nvic_irq_set_priority(NVIC_EXTI0, priority); 199 | break; 200 | case EXTI1: 201 | nvic_irq_set_priority(NVIC_EXTI1, priority); 202 | break; 203 | case EXTI2: 204 | nvic_irq_set_priority(NVIC_EXTI2, priority); 205 | break; 206 | case EXTI3: 207 | nvic_irq_set_priority(NVIC_EXTI3, priority); 208 | break; 209 | case EXTI4: 210 | nvic_irq_set_priority(NVIC_EXTI4, priority); 211 | break; 212 | case EXTI5: 213 | case EXTI6: 214 | case EXTI7: 215 | case EXTI8: 216 | case EXTI9: 217 | nvic_irq_set_priority(NVIC_EXTI_9_5, priority); 218 | break; 219 | case EXTI10: 220 | case EXTI11: 221 | case EXTI12: 222 | case EXTI13: 223 | case EXTI14: 224 | case EXTI15: 225 | nvic_irq_set_priority(NVIC_EXTI_15_10, priority); 226 | break; 227 | } 228 | 229 | } 230 | 231 | 232 | // Set Interrupt Priority for Timer Interrupts 233 | void setTimerIntPriority(uint8_t timerNumber, uint8_t priority) { 234 | 235 | switch(timerNumber) { 236 | case 1: 237 | nvic_irq_set_priority(NVIC_TIMER1_UP, priority); 238 | nvic_irq_set_priority(NVIC_TIMER1_CC, priority); 239 | break; 240 | case 2: 241 | nvic_irq_set_priority(NVIC_TIMER2, priority); 242 | break; 243 | case 3: 244 | nvic_irq_set_priority(NVIC_TIMER3, priority); 245 | break; 246 | case 4: 247 | nvic_irq_set_priority(NVIC_TIMER4, priority); 248 | break; 249 | } 250 | 251 | } 252 | 253 | 254 | // Set the correct interruptObject for this instance 255 | void SoftSerialIntAP::setInterruptObject(uint8_t timerNumber) { 256 | 257 | switch (timerNumber) { 258 | case 1: 259 | interruptObject1 = this; 260 | break; 261 | case 2: 262 | interruptObject2 = this; 263 | break; 264 | case 3: 265 | interruptObject3 = this; 266 | break; 267 | case 4: 268 | interruptObject4 = this; 269 | break; 270 | } 271 | } 272 | 273 | 274 | /****************************************************************************** 275 | * Constructor / Destructor 276 | ******************************************************************************/ 277 | // Constructor 278 | SoftSerialIntAP::SoftSerialIntAP(int receivePinT = 15, int transmitPinT = 16, uint8_t rxtxTimerT = 1) : 279 | receivePin(receivePinT), 280 | transmitPin(transmitPinT), 281 | timerSerial(rxtxTimerT), 282 | rxtxTimer(rxtxTimerT) 283 | { 284 | // Assign pointer to the hardware registers 285 | timerSerialDEV = timerSerial.c_dev(); 286 | 287 | // Translate transmit pin number to external interrupt number 288 | gpioBit = (exti_num)(PIN_MAP[transmitPin].gpio_bit); 289 | 290 | // Setup ISR pointer for this instance and timer (one timer per instance) 291 | // This is a workaround for c++ 292 | setInterruptObject(rxtxTimer); 293 | 294 | } 295 | 296 | 297 | // Destructor 298 | SoftSerialIntAP::~SoftSerialIntAP() { 299 | end(); 300 | } 301 | 302 | 303 | /****************************************************************************** 304 | * TX and RX Interrupt Service Routines 305 | ******************************************************************************/ 306 | // Transmits next bit. Called by timer ch1 compare interrupt 307 | void SoftSerialIntAP::txNextBit(void) { 308 | 309 | // State 0 through 7 - receive bits 310 | if (txBitCount <= 7) { 311 | if (bitRead(transmitBuffer[transmitBufferRead], txBitCount) == 1) { 312 | digitalWrite(transmitPin,HIGH); 313 | } else { 314 | digitalWrite(transmitPin,LOW); 315 | } 316 | 317 | // txTimingCount = ((uint16_t)(timerSerialDEV->regs).gen->TX_CCR) + bitPeriod; 318 | timerSerial.setCompare(TX_TIMER_CHANNEL, ((uint16_t)(timerSerialDEV->regs).gen->TX_CCR) + bitPeriod); 319 | 320 | interrupts(); 321 | // Bump the bit/state counter to state 8 322 | txBitCount++; 323 | 324 | #if DEBUG_DELAY 325 | digitalWrite(DEBUG_PIN1,1); 326 | digitalWrite(DEBUG_PIN1,0); 327 | #endif 328 | 329 | // State 8 - Send the stop bit and reset state to state -1 330 | // Shutdown timer interrupt if buffer empty 331 | } else if (txBitCount == 8) { 332 | 333 | // Send the stop bit 334 | digitalWrite(transmitPin, HIGH); 335 | 336 | interrupts(); 337 | 338 | transmitBufferRead = (transmitBufferRead == SS_MAX_TX_BUFF ) ? 0 : transmitBufferRead + 1; 339 | // transmitBufferRead = (transmitBufferRead + 1) % (SS_MAX_TX_BUFF - 1); 340 | 341 | if ((transmitBufferRead != transmitBufferWrite) && activeTX) { 342 | 343 | timerSerial.setCompare(TX_TIMER_CHANNEL, ((uint16_t)(timerSerialDEV->regs).gen->TX_CCR) + bitPeriod); 344 | txBitCount = 10; 345 | 346 | } else { 347 | 348 | // Buffer empty so shutdown delay/timer until "write" puts data in 349 | noTXInterrupts(); 350 | 351 | } 352 | 353 | // Send start bit for new byte 354 | } else if (txBitCount == 10) { 355 | digitalWrite(transmitPin, 0); 356 | interrupts(); 357 | 358 | // txTimingCount = ((uint16_t)(timerSerialDEV->regs).gen->TX_CCR) + bitPeriod; 359 | timerSerial.setCompare(TX_TIMER_CHANNEL, ((uint16_t)(timerSerialDEV->regs).gen->TX_CCR) + bitPeriod); 360 | 361 | txBitCount = 0; 362 | } 363 | 364 | } 365 | 366 | 367 | // Start Bit Receive ISR 368 | inline void SoftSerialIntAP::onRXPinChange(void){ 369 | 370 | // Test if this is really the start bit and not a spurious edge 371 | if ((rxBitCount == 9) && activeRX) { 372 | 373 | // Receive Timer/delay interrupt should be off now - unmask it and center the sampling time 374 | // rxTimingCount = ((uint16_t)(timerSerialDEV->regs).adv->CCR4 + startBitPeriod); 375 | timerSerial.setCompare(RX_TIMER_CHANNEL, ((uint16_t)(timerSerialDEV->regs.gen->CNT) + startBitPeriod)); 376 | rxInterruptsClr(); 377 | 378 | // Mask pinchange interrupt to reduce needless interrupt 379 | // overhead while receiving this byte 380 | noRXStartInterrupts(); 381 | interrupts(); 382 | 383 | // Set state/bit to first bit 384 | rxBitCount = 0; 385 | 386 | } else 387 | interrupts(); 388 | } 389 | 390 | 391 | // Receive next bit. Called by timer ch2 interrupt 392 | inline void SoftSerialIntAP::rxNextBit(void) { 393 | 394 | if (rxBitCount < 8) { 395 | 396 | // rxTimingCount = ((uint16_t)(timerSerialDEV->regs).adv->CCR4 + bitPeriod); 397 | timerSerial.setCompare(RX_TIMER_CHANNEL, ((uint16_t)(timerSerialDEV->regs.gen->RX_CCR) + bitPeriod)); 398 | 399 | receiveBuffer[receiveBufferWrite] >>= 1; 400 | if (digitalRead(receivePin)) 401 | receiveBuffer[receiveBufferWrite] |= 0x80; 402 | 403 | #if DEBUG_DELAY 404 | digitalWrite(DEBUG_PIN,1); 405 | digitalWrite(DEBUG_PIN,0); 406 | #endif 407 | 408 | interrupts(); 409 | 410 | rxBitCount++; 411 | 412 | // State 8 - Save incoming byte and update buffer 413 | } else if (rxBitCount == 8) { 414 | 415 | // Finish out stop bit while we... 416 | // Calculate location in buffer for next incoming byte 417 | // Test if buffer full 418 | // If the buffer isn't full update the tail pointer to point to next location 419 | // Else if it is now full set the buffer overflow flag 420 | // FYI - With this logic we effectively only have an (SS_MAX_RX_BUFF - 1) buffer size 421 | 422 | interrupts(); 423 | uint8_t next = (receiveBufferWrite + 1) % SS_MAX_RX_BUFF; 424 | if (next != receiveBufferRead) { 425 | receiveBufferWrite = next; 426 | 427 | } else { 428 | bufferOverflow = true; 429 | 430 | 431 | #if DEBUG_DELAY 432 | overFlowTail = receiveBufferWrite; 433 | overFlowHead = receiveBufferRead; 434 | 435 | digitalWrite(DEBUG_PIN1, 1); 436 | digitalWrite(DEBUG_PIN1, 0); 437 | #endif 438 | 439 | } 440 | 441 | // Re-enable start bit detection 442 | rxStartInterrupts(); 443 | 444 | // Shutdown nextbit timer interrupt until next start bit detected 445 | noRXInterrupts(); 446 | 447 | // Set for state 9 to receive next byte 448 | rxBitCount = 9; 449 | 450 | } else { 451 | interrupts(); 452 | } 453 | 454 | } 455 | 456 | 457 | /****************************************************************************** 458 | * Begin - Instance setup 459 | ******************************************************************************/ 460 | void SoftSerialIntAP::begin(uint32_t tBaud) { 461 | 462 | digitalWrite(transmitPin, 1); 463 | pinMode(receivePin, INPUT_PULLUP); 464 | pinMode(transmitPin, OUTPUT); 465 | 466 | #if DEBUG_DELAY 467 | pinMode(DEBUG_PIN, OUTPUT); 468 | digitalWrite(DEBUG_PIN, 0); 469 | pinMode(DEBUG_PIN1, OUTPUT); 470 | digitalWrite(DEBUG_PIN1, 0); 471 | #endif 472 | 473 | // Initialize the timer 474 | noInterrupts(); 475 | timerSerial.pause(); 476 | 477 | if (tBaud > 2400) { 478 | bitPeriod = (uint16_t)((uint32_t)(CYCLES_PER_MICROSECOND * 1000000) / tBaud); 479 | startBitPeriod = bitPeriod + (bitPeriod / 2) - 300; 480 | timerSerial.setPrescaleFactor(1); 481 | } else if (tBaud > 300) { 482 | bitPeriod = (uint16_t)(((uint32_t)(CYCLES_PER_MICROSECOND * 1000000) / 16) / tBaud); 483 | startBitPeriod = bitPeriod + (bitPeriod / 2); 484 | timerSerial.setPrescaleFactor(16); 485 | } else { 486 | bitPeriod = (uint16_t)(((uint32_t)(CYCLES_PER_MICROSECOND * 1000000) / 16) / tBaud) / 2; 487 | bitPeriod -= 600; 488 | startBitPeriod = bitPeriod + (bitPeriod / 2); 489 | timerSerial.setPrescaleFactor(16); 490 | } 491 | 492 | timerSerial.setOverflow(TIMER_MAX_COUNT); 493 | 494 | // Set transmit bit timer 495 | // Compare value set later 496 | timerSerial.setMode(TX_TIMER_CHANNEL, TIMER_OUTPUT_COMPARE); 497 | 498 | // State tx machine start state, attach bit interrupt, and mask it until a byte is sent 499 | transmitBufferRead = transmitBufferWrite = 0; 500 | txBitCount = 9; 501 | timerSerial.attachInterrupt(TX_TIMER_CHANNEL, handleTXBitInterruptP[rxtxTimer - 1]); 502 | noTXInterrupts(); 503 | 504 | // Set receive bit timer 505 | // Compare value set later 506 | timerSerial.setMode(RX_TIMER_CHANNEL, TIMER_OUTPUT_COMPARE); 507 | 508 | // Set rx State machine start state, attach the bit interrupt and mask it until start bit is received 509 | receiveBufferRead = receiveBufferWrite = 0; 510 | rxBitCount = 9; 511 | timerSerial.attachInterrupt(RX_TIMER_CHANNEL, handleRXBitInterruptP[rxtxTimer - 1]); 512 | noRXInterrupts(); 513 | 514 | // Make the timer we are using a high priority interrupt 515 | setTimerIntPriority(rxtxTimer, 0); 516 | 517 | // Load the timer values and start it 518 | timerSerial.refresh(); 519 | timerSerial.resume(); 520 | 521 | // Set start bit interrupt and priority and leave it enabled to rx first byte 522 | attachInterrupt(receivePin, handleRXEdgeInterruptP[rxtxTimer - 1], FALLING); 523 | setEXTIntPriority(receivePin, 0); 524 | 525 | bufferOverflow = false; 526 | receiveBufferRead = receiveBufferWrite = 0; 527 | transmitBufferRead = transmitBufferWrite = 0; 528 | 529 | 530 | interrupts(); 531 | 532 | listen(); 533 | talk(); 534 | 535 | } 536 | 537 | 538 | /****************************************************************************** 539 | * RX Related Public Methods 540 | ******************************************************************************/ 541 | // Sets current instance listening. Transmit is always enabled 542 | // If his instance was already activeRX does nothing and returns false 543 | bool SoftSerialIntAP::listen() { 544 | 545 | // If receive not activeRX then re-init and set activeRX 546 | if (!activeRX) { 547 | 548 | // Reset receieve buffer and mark activeRX 549 | bufferOverflow = false; 550 | receiveBufferRead = receiveBufferWrite = 0; 551 | activeRX = true; 552 | 553 | // Turn the receive start bit detection on 554 | rxStartInterrupts(); 555 | 556 | return true; 557 | } 558 | return false; 559 | } 560 | 561 | 562 | // Stop Listening - Shuts down only RX - Use end() to stop both rx and tx 563 | // Returns true if was listening when called 564 | // This instance will stop all RX interrupts after current in-process 565 | // byte is finished receiving (if any). 566 | // If no in-process receive byte it stops immediately 567 | bool SoftSerialIntAP::stopListening() { 568 | 569 | if (activeRX) { 570 | 571 | noRXStartInterrupts(); 572 | activeRX = false; 573 | return true; 574 | 575 | } else 576 | return false; 577 | 578 | } 579 | 580 | 581 | // Completely shutsdown this instance 582 | // Not an RX related method but needs to be after stopListening 583 | void SoftSerialIntAP::end() { 584 | 585 | stopListening(); 586 | timerSerial.pause(); 587 | detachInterrupt(receivePin); 588 | timerSerial.detachInterrupt(RX_TIMER_CHANNEL); 589 | timerSerial.detachInterrupt(TX_TIMER_CHANNEL); 590 | timerSerial.setMode(TX_TIMER_CHANNEL, TIMER_DISABLED); 591 | timerSerial.setMode(RX_TIMER_CHANNEL, TIMER_DISABLED); 592 | digitalWrite(transmitPin, 1); 593 | } 594 | 595 | 596 | // Returns number of bytes in the RX buffer 597 | int SoftSerialIntAP::available() { 598 | int i; 599 | 600 | if (!activeRX) 601 | return 0; 602 | 603 | noRXInterrupts(); 604 | i = (receiveBufferWrite + SS_MAX_RX_BUFF - receiveBufferRead) % SS_MAX_RX_BUFF; 605 | rxInterrupts(); 606 | 607 | return i; 608 | } 609 | 610 | // Non-blocking read. 611 | // Returns -1 if this instance isn't listening or the buffer is empty 612 | int SoftSerialIntAP::readnb() { 613 | 614 | if (!activeRX) 615 | return -1; 616 | 617 | if (receiveBufferRead == receiveBufferWrite) 618 | return -1; 619 | 620 | uint8_t inData = receiveBuffer[receiveBufferRead]; 621 | 622 | noRXInterrupts(); 623 | receiveBufferRead = (receiveBufferRead + 1) % SS_MAX_RX_BUFF; 624 | rxInterrupts(); 625 | 626 | return inData; 627 | } 628 | 629 | 630 | // Blocking read to be compatible with HardwareSerial 631 | // Blocks until byte is available in buffer 632 | // Returns -1 if instance is not activeRX 633 | int SoftSerialIntAP::read() { 634 | 635 | if (!activeRX) 636 | return -1; 637 | 638 | // Wait if buffer is empty 639 | while (receiveBufferRead == receiveBufferWrite); 640 | 641 | uint8_t inData = receiveBuffer[receiveBufferRead]; 642 | 643 | noRXInterrupts(); 644 | receiveBufferRead = (receiveBufferRead + 1) % SS_MAX_RX_BUFF; 645 | rxInterrupts(); 646 | 647 | return inData; 648 | } 649 | 650 | 651 | // Flush the receive buffer 652 | void SoftSerialIntAP::flush() { 653 | 654 | noRXInterrupts(); 655 | receiveBufferRead = receiveBufferWrite = 0; 656 | rxInterrupts(); 657 | 658 | } 659 | 660 | 661 | // Return the next item in the receive buffer but leave in buffer 662 | int SoftSerialIntAP::peek() { 663 | 664 | if (!activeRX) 665 | return -1; 666 | 667 | // If buffer is empty return false 668 | if (receiveBufferRead == receiveBufferWrite) 669 | return -1; 670 | 671 | // Otherwise read the byte at head of buffer but don't delete 672 | return receiveBuffer[receiveBufferRead]; 673 | 674 | } 675 | 676 | 677 | /****************************************************************************** 678 | * TX Related Public Method(s) 679 | ******************************************************************************/ 680 | // Sets current instance enabled for sending 681 | // If his instance was already activeRX does nothing and returns false 682 | bool SoftSerialIntAP::talk() { 683 | 684 | // If transmit not active then re-init and set activeTX 685 | if (!activeTX) { 686 | 687 | // Reset transmit buffer and mark active 688 | transmitBufferRead = transmitBufferWrite = 0; 689 | activeTX = true; 690 | 691 | // Turn transmit interrupts on 692 | txInterrupts(); 693 | 694 | return true; 695 | } 696 | return false; 697 | } 698 | 699 | 700 | // Stop Sending - Shuts down only TX - Use end() to stop both rx and tx 701 | // or "stopListening" for rx 702 | // Returns true if sending already enabled when called 703 | // This instance will stop sending at end of current byte immediately 704 | bool SoftSerialIntAP::stopTalking() { 705 | 706 | if (activeTX) { 707 | 708 | while (txBitCount < 8); 709 | activeTX = false; 710 | noTXInterrupts(); 711 | return true; 712 | 713 | } else 714 | return false; 715 | 716 | } 717 | 718 | 719 | // Virtual write 720 | // Saves tx byte in buffer and restarts transmit delay timer 721 | // 1 bit time latency prior to transmit start if buffer was empty 722 | size_t SoftSerialIntAP::write(uint8_t b) { 723 | 724 | // Check if transmit timer interrupt enabled and if not unmask it 725 | // transmit timer interrupt will get masked by transmit ISR when buffer becomes empty 726 | if (!isTXInterruptEnabled()) { 727 | 728 | // Save new data in buffer 729 | transmitBuffer[transmitBufferWrite] = b; 730 | // transmitBufferWrite = (transmitBufferWrite + 1) % (SS_MAX_TX_BUFF - 1); 731 | transmitBufferWrite = (transmitBufferWrite == SS_MAX_TX_BUFF) ? 0 : transmitBufferWrite + 1; 732 | 733 | // Set state to 10 (send start bit) and re-enable transmit interrupt 734 | txBitCount = 10; 735 | timerSerial.setCompare(TX_TIMER_CHANNEL, (int16_t)((timerSerialDEV->regs).bas->CNT) + 1); 736 | txInterruptsClr(); 737 | 738 | } else { 739 | 740 | // Blocks if buffer full 741 | bool i; 742 | do { 743 | // noTXInterrupts(); 744 | // i = (((transmitBufferWrite + 1) == transmitBufferRead) || 745 | // ((transmitBufferWrite == SS_MAX_TX_BUFF) && (transmitBufferRead == 0))); 746 | i = (((transmitBufferWrite + 1) % SS_MAX_TX_BUFF) == transmitBufferRead); 747 | // txInterrupts(); 748 | } while (i); 749 | 750 | // Save new data in buffer and bump the write pointer 751 | transmitBuffer[transmitBufferWrite] = b; 752 | // noTXInterrupts(); 753 | // transmitBufferWrite = (transmitBufferWrite + 1) % (SS_MAX_TX_BUFF - 1); 754 | transmitBufferWrite = (transmitBufferWrite == SS_MAX_TX_BUFF) ? 0 : transmitBufferWrite + 1; 755 | // txInterrupts(); 756 | } 757 | 758 | return 1; 759 | 760 | } 761 | 762 | /****************************************************************************** 763 | * 764 | * Intermediate Level Interrupt Service Routines 765 | * One ISR for each interrupt times 4 to support 4 instantiations of the class 766 | * on up to 4 different timers. 767 | * 768 | * This is to work around the fact that static data and 769 | * static member functions become part of the base class and are common to all 770 | * instantiations and ISRs must be static in order to derive a std C type pointer to 771 | * them which is required by the NVIC and hardware timer interrupt code. If there is 772 | * a better way to do this I'd welcome to learn about it. 773 | * 774 | * These are at the bottom of the file just to get them out of the way. 775 | ******************************************************************************/ 776 | 777 | inline void SoftSerialIntAP::handleRXBitInterrupt1() { 778 | 779 | // if (interruptObject1) 780 | // { 781 | noInterrupts(); 782 | interruptObject1->rxNextBit(); 783 | interrupts(); 784 | // } 785 | } 786 | 787 | 788 | inline void SoftSerialIntAP::handleRXEdgeInterrupt1() { 789 | if (interruptObject1) 790 | // { 791 | noInterrupts(); 792 | interruptObject1->onRXPinChange(); 793 | interrupts(); 794 | // } 795 | } 796 | 797 | 798 | inline void SoftSerialIntAP::handleTXBitInterrupt1() { 799 | // if (interruptObject1) 800 | // { 801 | noInterrupts(); 802 | interruptObject1->txNextBit(); 803 | interrupts(); 804 | // } 805 | } 806 | 807 | inline void SoftSerialIntAP::handleRXBitInterrupt2() { 808 | // if (interruptObject2) 809 | // { 810 | noInterrupts(); 811 | interruptObject2->rxNextBit(); 812 | interrupts(); 813 | // } 814 | } 815 | 816 | 817 | inline void SoftSerialIntAP::handleRXEdgeInterrupt2() { 818 | // if (interruptObject2) 819 | // { 820 | noInterrupts(); 821 | interruptObject2->onRXPinChange(); 822 | interrupts(); 823 | // } 824 | } 825 | 826 | 827 | inline void SoftSerialIntAP::handleTXBitInterrupt2() { 828 | // if (interruptObject2) 829 | // { 830 | noInterrupts(); 831 | interruptObject2->txNextBit(); 832 | interrupts(); 833 | // } 834 | } 835 | 836 | inline void SoftSerialIntAP::handleRXBitInterrupt3() { 837 | // if (interruptObject3) 838 | // { 839 | noInterrupts(); 840 | interruptObject3->rxNextBit(); 841 | interrupts(); 842 | // } 843 | } 844 | 845 | 846 | inline void SoftSerialIntAP::handleRXEdgeInterrupt3() { 847 | // if (interruptObject3) 848 | // { 849 | noInterrupts(); 850 | interruptObject3->onRXPinChange(); 851 | interrupts(); 852 | // } 853 | } 854 | 855 | 856 | inline void SoftSerialIntAP::handleTXBitInterrupt3() { 857 | // if (interruptObject3) 858 | // { 859 | noInterrupts(); 860 | interruptObject3->txNextBit(); 861 | interrupts(); 862 | // } 863 | } 864 | 865 | inline void SoftSerialIntAP::handleRXBitInterrupt4() { 866 | // if (interruptObject4) 867 | // { 868 | noInterrupts(); 869 | interruptObject4->rxNextBit(); 870 | interrupts(); 871 | // } 872 | } 873 | 874 | 875 | inline void SoftSerialIntAP::handleRXEdgeInterrupt4() { 876 | // if (interruptObject4) 877 | // { 878 | noInterrupts(); 879 | interruptObject4->onRXPinChange(); 880 | interrupts(); 881 | // } 882 | } 883 | 884 | 885 | inline void SoftSerialIntAP::handleTXBitInterrupt4() { 886 | // if (interruptObject4) 887 | // { 888 | noInterrupts(); 889 | interruptObject4->txNextBit(); 890 | interrupts(); 891 | // } 892 | 893 | } 894 | 895 | -------------------------------------------------------------------------------- /SoftSerialIntAP.h: -------------------------------------------------------------------------------- 1 | #ifndef SoftSerialIntAP_h 2 | #define SoftSerialIntAP_h 3 | 4 | /****************************************************************************** 5 | * SoftSerialIntAP.cpp 6 | * Multi-instance Software Serial Library for STM32Duino 7 | * Using Timers and Interrupts enabling use of any GPIO pins for RX/TX 8 | * 9 | * Copyright 2015 Ron Curry, InSyte Technologies 10 | * 11 | 12 | Features: 13 | - Fully interrupt driven - no delay routines. 14 | - Any GPIO pin may be used for tx or rx (hence the AP libray name suffix) 15 | - Circular buffers on both send and receive. 16 | - Works up to 115,200 baud. 17 | - Member functions compatible with Hardware Serial and Arduino NewSoftSerial Libs 18 | - Extensions for non-blocking read and transmit control 19 | - Supports up to 4 ports (one timer used per port) without modification. 20 | - Easily modified for more ports or different timers on chips with more timers. 21 | - Can do full duplex under certain circumstances at lower baud rates. 22 | - Can send/receive simultaneously with other ports/instantiatious at some baud 23 | rates and certain circumstances. 24 | 25 | Notes: 26 | 27 | Performance 28 | - More than two ports use has not been extensively tested. High ISR latencies in the 29 | low-level Maple timer ISR code, C++ ISR wranglings, and the STM32 sharing interrupt 30 | architecture (not in that order) restrict simultaneous port use and speeds. Two ports 31 | sending simultaniously at 115,200. As well, two ports simultansiously receiving at 57,600 32 | simultaneously have been successfully tested. Various other situations have been 33 | tested. Results are dependent on various factors - you'll need to experiment. 34 | 35 | Reliability 36 | - Because of the way STM32 shares interrupts and the way STM32Arduino low level ISRs 37 | processes them latencies can be very high for interrupts on a given timer. I won't go 38 | into all the details of why but some interrupts can be essentially locked out. Causing 39 | extremely delayed interrupt servicing. Some of this could be alleviated with more 40 | sophisticated low-level ISRs that make sure that all shared interrupt sources get 41 | serviced on an equal basis but that's not been done yet. 42 | This impacts the ability to do full duplex on a single port even at medium bit rates. 43 | I've done some experimentation with two ports/instantiations with RX on one port 44 | and TX on the other with good results for full-duplex. In any case, to be sure 45 | that the serial data streams are not corrupted at higher data rates it's best to 46 | write the application software such that rx and tx are not sending/receiving 47 | simultaneously in each port and that other ports are not operating simultaneously 48 | as well. This is, effectively, how the existing NewSoftSerial library on Arduino 49 | operates so not a new concept. Again, experiment and find what works in your 50 | application. 51 | 52 | Improvements 53 | No doubt the code can be improved upon. If you use the code PLEASE give back by 54 | providing soure code for improvements or modifications you've made! 55 | - Specific improvements that come to mind and I'd like to explore are: 56 | o Replacing the STM32/Maple timer interrupt handlers with something more streamlined 57 | and lower latency and overhead. 58 | o A better way to implement the high level C++ ISR's to reduce latency/overhead 59 | o Minor improvements that can save cycles in the C++ ISR's such as using bit-banding 60 | o Possibly a way to coordinate RX/TX to increase full-duplex capability. 61 | 62 | License 63 | * Permission is hereby granted, free of charge, to any person 64 | * obtaining a copy of this software and associated documentation 65 | * files (the "Software"), to deal in the Software without 66 | * restriction, including without limitation the rights to use, copy, 67 | * modify, merge, publish, distribute, sublicense, and/or sell copies 68 | * of the Software, and to permit persons to whom the Software is 69 | * furnished to do so, subject to the following conditions: 70 | * 71 | * The above copyright notice and this permission notice shall be 72 | * included in all copies or substantial portions of the Software. 73 | * 74 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 75 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 76 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 77 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 78 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 79 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 80 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 81 | * SOFTWARE. 82 | *****************************************************************************/ 83 | 84 | #include 85 | #include 86 | 87 | #include 88 | #include 89 | 90 | /****************************************************************************** 91 | * Definitions 92 | ******************************************************************************/ 93 | #define DEBUG_DELAY 1 94 | #define DEBUG_PIN 17 95 | #define DEBUG_PIN1 18 96 | 97 | #define SSI_RX_BUFF_SIZE 64 98 | #define SSI_TX_BUFF_SIZE 64 99 | #define SS_MAX_RX_BUFF (SSI_RX_BUFF_SIZE - 1) // RX buffer size 100 | #define SS_MAX_TX_BUFF (SSI_TX_BUFF_SIZE - 1) // TX buffer size 101 | #define _SSI_VERSION 1.1 // Library Version 102 | 103 | /****************************************************************************** 104 | * Class Definition 105 | ******************************************************************************/ 106 | class SoftSerialIntAP : public Stream 107 | { 108 | private: 109 | 110 | // Per object data 111 | uint8_t transmitPin; 112 | exti_num gpioBit; 113 | uint8_t receivePin; 114 | HardwareTimer timerSerial; 115 | timer_dev *timerSerialDEV; 116 | uint8_t rxtxTimer; 117 | bool activeRX; 118 | bool activeTX; 119 | 120 | #if DEBUG_DELAY 121 | volatile uint8_t overFlowTail; 122 | volatile uint8_t overFlowHead; 123 | #endif 124 | 125 | uint16_t bitPeriod; 126 | uint16_t startBitPeriod; 127 | volatile uint16_t rxTimingCount; 128 | volatile uint16_t txTimingCount; 129 | volatile uint8_t bufferOverflow; 130 | 131 | volatile int8_t rxBitCount; 132 | volatile uint16_t receiveBufferWrite; 133 | volatile uint16_t receiveBufferRead; 134 | volatile uint8_t receiveBuffer[SS_MAX_RX_BUFF]; 135 | 136 | volatile int8_t txBitCount; 137 | volatile uint16_t transmitBufferWrite; 138 | volatile uint16_t transmitBufferRead; 139 | volatile uint8_t transmitBuffer[SS_MAX_TX_BUFF]; 140 | 141 | // Static Data 142 | static SoftSerialIntAP *interruptObject1; // This looks inefficient but it reduces 143 | static SoftSerialIntAP *interruptObject2; // interrupt latency a small amount 144 | static SoftSerialIntAP *interruptObject3; 145 | static SoftSerialIntAP *interruptObject4; 146 | static voidFuncPtr handleRXEdgeInterruptP[4]; 147 | static voidFuncPtr handleRXBitInterruptP[4]; 148 | static voidFuncPtr handleTXBitInterruptP[4]; 149 | 150 | 151 | // Static Methods 152 | // Better way to do this? 153 | static inline void handleRXBitInterrupt1() __attribute__((__always_inline__)); 154 | static inline void handleRXEdgeInterrupt1() __attribute__((__always_inline__)); 155 | static inline void handleTXBitInterrupt1() __attribute__((__always_inline__)); 156 | 157 | static inline void handleRXBitInterrupt2() __attribute__((__always_inline__)); 158 | static inline void handleRXEdgeInterrupt2() __attribute__((__always_inline__)); 159 | static inline void handleTXBitInterrupt2() __attribute__((__always_inline__)); 160 | 161 | static inline void handleRXBitInterrupt3() __attribute__((__always_inline__)); 162 | static inline void handleRXEdgeInterrupt3() __attribute__((__always_inline__)); 163 | static inline void handleTXBitInterrupt3() __attribute__((__always_inline__)); 164 | 165 | static inline void handleRXBitInterrupt4() __attribute__((__always_inline__)); 166 | static inline void handleRXEdgeInterrupt4() __attribute__((__always_inline__)); 167 | static inline void handleTXBitInterrupt4() __attribute__((__always_inline__)); 168 | 169 | // Private Methods 170 | inline void noTXInterrupts() __attribute__((__always_inline__)); 171 | inline void txInterrupts() __attribute__((__always_inline__)); 172 | inline uint16_t isTXInterruptEnabled() __attribute__((__always_inline__)); 173 | inline void txInterruptsClr() __attribute__((__always_inline__)); 174 | inline void noRXStartInterrupts() __attribute__((__always_inline__)); 175 | inline void rxStartInterrupts() __attribute__((__always_inline__)); 176 | inline void noRXInterrupts() __attribute__((__always_inline__)); 177 | inline void rxInterrupts() __attribute__((__always_inline__)); 178 | inline void rxInterruptsClr() __attribute__((__always_inline__)); 179 | inline void onRXPinChange(void) __attribute__((__always_inline__)); 180 | inline void rxNextBit(void) __attribute__((__always_inline__)); 181 | inline void txNextBit(void) __attribute__((__always_inline__)); 182 | void setInterruptObject(uint8_t timerNumber); 183 | 184 | public: 185 | // Public Methods 186 | SoftSerialIntAP(int receivePinT, int transmitPinT, uint8_t rxtxTimerT /*, bool inverseLogic*/ ); 187 | 188 | ~SoftSerialIntAP(); 189 | 190 | static int library_version() { return _SSI_VERSION; } 191 | void begin(uint32_t tBaud); 192 | bool listen(); 193 | bool isListening() { return activeRX; } 194 | bool stopListening(); 195 | bool talk(); 196 | bool isTalkingT() { return activeTX; } 197 | bool stopTalking(); 198 | void end(); 199 | bool overflow() { bool ret = bufferOverflow; if (ret) bufferOverflow = false; return ret; } 200 | int readnb(); // Non-blocking read 201 | 202 | virtual int peek(); 203 | virtual size_t write(uint8_t byte); 204 | virtual int read(); 205 | virtual int available(); 206 | virtual void flush(); 207 | operator bool() { return true; } 208 | 209 | // Debug use only 210 | #if DEBUG_DELAY 211 | uint16_t getBitPeriod() { return bitPeriod; } 212 | void setBitPeriod(uint16_t period) { bitPeriod = period; } 213 | uint16_t getBitCentering() { return startBitPeriod; } 214 | void setBitCentering(uint16_t period) { startBitPeriod = period; } 215 | uint8_t getOverFlowTail() { return overFlowTail; } 216 | uint8_t getOverFlowHead() { return overFlowHead; } 217 | uint8_t getTXHead(){ return transmitBufferRead; } 218 | uint8_t getTXTail(){ return transmitBufferWrite; } 219 | uint8_t getRXHead(){ return receiveBufferRead; } 220 | uint8_t getRXTail(){ return receiveBufferWrite; } 221 | uint8_t getrxtxTimer(){ return rxtxTimer; } 222 | #endif 223 | 224 | using Print::write; 225 | 226 | }; 227 | 228 | #endif 229 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map 3 | # for SoftSerialIntCC 4 | ####################################### 5 | 6 | ####################################### 7 | # Datatypes (KEYWORD1) 8 | ####################################### 9 | 10 | SoftSerialIntCC KEYWORD1 11 | 12 | ####################################### 13 | # Methods and Functions (KEYWORD2) 14 | ####################################### 15 | 16 | begin KEYWORD2 17 | end KEYWORD2 18 | isListening KEYWORD2 19 | stopListening KEYWORD2 20 | overflow KEYWORD2 21 | library_version KEYWORD2 22 | flush KEYWORD2 23 | listen KEYWORD2 24 | talk KEYWORD2 25 | isTalking KEYWORD2 26 | stopTalking KEYWORD2 27 | 28 | ####################################### 29 | # Constants (LITERAL1) 30 | ####################################### 31 | 32 | --------------------------------------------------------------------------------