├── Arduino ├── controller_leonardo │ └── controller_leonardo.ino └── controller_uno │ └── controller_uno.ino ├── Plugin └── misc_modules │ └── arduino_controller │ ├── CMakeLists.txt │ └── src │ ├── main.cpp │ ├── serialib.cpp │ └── serialib.h └── README.md /Arduino/controller_leonardo/controller_leonardo.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | SDR++ Controller 3 | by Bartłomiej Marcinkowski 4 | 5 | Designed and tested on: 6 | - Arduino Leonardo 7 | - DFRobot DFR0009 LCD + Keys shield 8 | - DFRobot EC11 rotary encoder button x2 9 | 10 | Encoder support based on source code taken from DFRobot site: 11 | https://wiki.dfrobot.com/EC11_Rotary_Encoder_Module_SKU__SEN0235 (no author info) 12 | 13 | LCD and Keys support based on source code taken from Botland store site: 14 | https://botland.com.pl/arduino-shield-klawiatury-i-wyswietlacze/2729-dfrobot-lcd-keypad-shield-v11-wyswietlacz-dla-arduino-5903351243780.html 15 | by Mark Bramwell, July 2010 16 | 17 | Greetings to RTL-SDR Polska group @ Facebook 18 | https://www.facebook.com/groups/2628926590655863 19 | **************************************************************************************/ 20 | 21 | #include 22 | #include 23 | 24 | LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // select the pins used on the LCD panel 25 | 26 | // define some values used by the panel and buttons (and knobs) 27 | int lcd_key = 0; 28 | int adc_key_in = 0; 29 | int isbtnPressed = 0; 30 | int incomingByte = 0; 31 | int startCommand = 0; 32 | int bytePos = 0; 33 | int adc_value = 0; 34 | int knobCommand = 0; 35 | int knobKey = 0; 36 | int lastSNR = 0; 37 | 38 | int coolDown = 0; 39 | 40 | int encoderPinA = 0; 41 | int encoderPinB = 1; 42 | int buttonPin = 11; 43 | 44 | int encoderPinC = 2; 45 | int encoderPinD = 3; 46 | 47 | //left encoder knob 48 | volatile int lastEncodedL = 0; 49 | //long lastencoderValueL = 0; 50 | int lastMSBL = 0; 51 | int lastLSBL = 0; 52 | 53 | //right encoder knob 54 | volatile int lastEncodedR = 0; 55 | //long lastencoderValueR = 0; 56 | int lastMSBR = 0; 57 | int lastLSBR = 0; 58 | 59 | //volatile long encoderValueL = 0; 60 | //volatile long encoderValueR = 0; 61 | 62 | unsigned long currentReadtime = 0; 63 | unsigned long lastReadtime = 0; 64 | 65 | char freq[16] = "0"; 66 | char snr[5] = "0"; 67 | 68 | #define inputNONE 0 69 | 70 | #define knobRIGHT 1 71 | #define knobUP 2 72 | #define knobDOWN 3 73 | #define knobLEFT 4 74 | 75 | #define btnSELECT 5 76 | #define btnRIGHT 6 77 | #define btnUP 7 78 | #define btnDOWN 8 79 | #define btnLEFT 9 80 | #define btnKNOB 0xa 81 | 82 | #define commandCTRL 67 // C - general command for extra actions 83 | #define commandDISP 68 // D - display debug message 84 | #define commandFREQ 70 // F - display frequency 85 | #define commandMODE 77 // M - display mode 86 | #define commandRST 82 // R - reset 87 | #define commandSNR 83 // S - SNR level 88 | #define commandEOL 10 // 0x0a - std end of line 89 | 90 | enum modes_t { 91 | NFM, 92 | WFM, 93 | AM, 94 | DSB, 95 | USB, 96 | CW, 97 | LSB, 98 | RAW 99 | }; 100 | 101 | char *modeStr[8] = { 102 | "NFM","WFM","AM","DSB","USB","CW","LSB","RAW" 103 | }; 104 | 105 | modes_t dMode; 106 | 107 | int read_knob_button() { 108 | if(!digitalRead(buttonPin)) { 109 | return btnKNOB; 110 | } else { 111 | return inputNONE; 112 | } 113 | } 114 | 115 | int read_LCD_buttons(){ // read the buttons 116 | adc_key_in = analogRead(0); // read the value from the sensor 117 | 118 | // my buttons when read are centered at these valies: 0, 144, 329, 504, 741 119 | // we add approx 50 to those values and check to see if we are close 120 | // We make this the 1st option for speed reasons since it will be the most likely result 121 | 122 | if (adc_key_in > 1000) return inputNONE; 123 | 124 | // For V1.1 us this threshold 125 | if (adc_key_in < 50) return btnRIGHT; 126 | if (adc_key_in < 250) return btnUP; 127 | if (adc_key_in < 450) return btnDOWN; 128 | if (adc_key_in < 650) return btnLEFT; 129 | if (adc_key_in < 850) return btnSELECT; 130 | 131 | // For V1.0 comment the other threshold and use the one below: 132 | /* 133 | if (adc_key_in < 50) return btnRIGHT; 134 | if (adc_key_in < 195) return btnUP; 135 | if (adc_key_in < 380) return btnDOWN; 136 | if (adc_key_in < 555) return btnLEFT; 137 | if (adc_key_in < 790) return btnSELECT; 138 | */ 139 | return inputNONE; // when all others fail, return this. 140 | } 141 | 142 | //long readEncoderValue(void){ // not used , taken from DFRobot example 143 | // return encoderValue/4; // we are not counting impulses 144 | //} 145 | 146 | 147 | // void(* resetFunc) (void) = 0; 148 | // ^^ this works different on Leonardo. Serial waits for disconnection? not sure how to handle that 149 | 150 | void readCommand() { 151 | char rchar; 152 | if (Serial.available() > 0) { 153 | incomingByte = Serial.read(); 154 | 155 | if (incomingByte == commandEOL){ 156 | startCommand = 0; 157 | bytePos = 0; 158 | } 159 | 160 | switch (startCommand) { 161 | case 0: 162 | break; 163 | case commandFREQ: 164 | if(bytePos < sizeof(freq) - 1) { 165 | freq[bytePos] = incomingByte; 166 | bytePos++; 167 | } 168 | //Serial.println(freq); 169 | break; 170 | case commandSNR: 171 | if(bytePos < sizeof(snr) - 1) { 172 | snr[bytePos] = incomingByte; 173 | bytePos++; 174 | } 175 | //Serial.println(freq); 176 | break; 177 | case commandMODE: 178 | dMode = char(incomingByte) - '0'; 179 | if(dMode > 7 || dMode < 0) { 180 | Serial.println("D Wrong mode"); 181 | dMode = 0; 182 | } else { 183 | Serial.println(modeStr[dMode]); 184 | } 185 | break; 186 | default: 187 | break; 188 | } 189 | 190 | 191 | if(startCommand == 0) { 192 | switch(incomingByte){ 193 | case commandRST: 194 | //Serial.println("D Reset command received"); 195 | delay(200); 196 | Serial.println("D SDR++ Controller init"); 197 | //resetFunc(); // again this doesn't work on Leonardo 198 | break; 199 | case commandFREQ: 200 | //Serial.println("D Receinve frequency start"); 201 | memset(freq,0,sizeof(freq)); 202 | startCommand = commandFREQ; 203 | //delay(200); 204 | break; 205 | case commandMODE: 206 | startCommand = commandMODE; 207 | break; 208 | case commandSNR: 209 | memset(snr,0,sizeof(snr)); 210 | startCommand = commandSNR; 211 | break; 212 | case commandEOL: 213 | //Serial.println("D EOL"); 214 | break; 215 | default: 216 | Serial.println("D Unknown command"); 217 | Serial.println(incomingByte, DEC); 218 | Serial.println(incomingByte); 219 | 220 | } 221 | } 222 | //Serial.print("I received: "); 223 | //Serial.println(incomingByte, DEC); 224 | } 225 | } 226 | 227 | // This part is a modified example code from DFRobot site. 228 | // Adjusted to handle two knobs at the same time. 229 | void updateEncoder(){ 230 | 231 | int MSBR = digitalRead(encoderPinA); //MSB = most significant bit 232 | int LSBR = digitalRead(encoderPinB); //LSB = least significant bit 233 | 234 | int encodedR = (MSBR << 1) |LSBR; //converting the 2 pin value to single number 235 | int sumR = (lastEncodedR << 2) | encodedR; //adding it to the previous encoded value 236 | 237 | if(sumR == 0b1101 || sumR == 0b0100 || sumR == 0b0010 || sumR == 0b1011) { 238 | knobCommand = knobRIGHT; 239 | } 240 | if(sumR == 0b1110 || sumR == 0b0111 || sumR == 0b0001 || sumR == 0b1000) { 241 | knobCommand = knobLEFT; 242 | } 243 | lastEncodedR = encodedR; //store this value for next time 244 | 245 | int MSBL = digitalRead(encoderPinC); //MSB = most significant bit 246 | int LSBL = digitalRead(encoderPinD); //LSB = least significant bit 247 | 248 | int encodedL = (MSBL << 1) |LSBL; //converting the 2 pin value to single number 249 | int sumL = (lastEncodedL << 2) | encodedL; //adding it to the previous encoded value 250 | if(sumL == 0b1101 || sumL == 0b0100 || sumL == 0b0010 || sumL == 0b1011) { 251 | knobCommand = knobUP; 252 | } 253 | if(sumL == 0b1110 || sumL == 0b0111 || sumL == 0b0001 || sumL == 0b1000) { 254 | knobCommand = knobDOWN; 255 | } 256 | 257 | lastEncodedL = encodedL; //store this value for next time 258 | 259 | if (coolDown < 10) { // this strange, added to fix garbage on boot, I don't like it 260 | coolDown++; 261 | knobCommand = inputNONE; 262 | } 263 | } 264 | 265 | void setup(){ 266 | Serial.begin(9600); 267 | while (!Serial) { 268 | Serial.println("D SDR++ Controller init"); 269 | } 270 | pinMode(encoderPinA, INPUT); 271 | pinMode(encoderPinB, INPUT); 272 | pinMode(encoderPinC, INPUT); 273 | pinMode(encoderPinD, INPUT); 274 | pinMode(buttonPin, INPUT); 275 | 276 | digitalWrite(encoderPinA, INPUT_PULLUP); //turn pullup resistor on 277 | digitalWrite(encoderPinB, INPUT_PULLUP); //turn pullup resistor on 278 | 279 | digitalWrite(encoderPinC, INPUT_PULLUP); //turn pullup resistor on 280 | digitalWrite(encoderPinD, INPUT_PULLUP); //turn pullup resistor on 281 | 282 | //call updateEncoder() when any high/low changed seen 283 | //on interrupt 0 (pin 2), or interrupt 1 (pin 3) 284 | // This works well for UNO with one knob. Unfortunately there are only 285 | // two interrupts and we have to hande four of encoder lines. So... 286 | // We do not use interrupts at all and just call updateEncoder() directly. 287 | // this version is for Leonardo so we can use interrupts! 288 | attachInterrupt(0, updateEncoder, CHANGE); 289 | attachInterrupt(1, updateEncoder, CHANGE); 290 | attachInterrupt(2, updateEncoder, CHANGE); 291 | attachInterrupt(3, updateEncoder, CHANGE); 292 | 293 | lcd.begin(16, 2); 294 | lcd.setCursor(0,0); 295 | lcd.print("SDR++ Controller"); 296 | delay(1000); 297 | lcd.clear(); 298 | } 299 | 300 | void loop(){ 301 | 302 | readCommand(); 303 | lcd.setCursor(0,0); 304 | lcd.print("F: "); 305 | lcd.print(freq); 306 | lcd.print(" "); 307 | lcd.setCursor(0,1); 308 | lcd.print("["); 309 | lcd.print(modeStr[dMode]); 310 | lcd.print("] "); 311 | lcd.setCursor(6,5); 312 | lcd.print("SNR: "); 313 | lcd.print(snr); 314 | lcd.print(" "); 315 | 316 | lcd_key = read_LCD_buttons(); 317 | knobKey = read_knob_button(); 318 | currentReadtime = millis(); 319 | 320 | //updateEncoder(); // this should be called by interrupt 321 | 322 | if (lcd_key != inputNONE && isbtnPressed == 0) { 323 | Serial.write(commandCTRL); 324 | Serial.println(lcd_key); 325 | Serial.flush(); 326 | isbtnPressed = lcd_key; 327 | } else if (knobCommand != inputNONE) { 328 | Serial.write(commandCTRL); 329 | Serial.println(knobCommand); 330 | Serial.flush(); 331 | knobCommand = inputNONE; 332 | } else if (knobKey != inputNONE && isbtnPressed == 0) { 333 | Serial.write(commandCTRL); 334 | Serial.println(knobKey,HEX); 335 | Serial.flush(); 336 | isbtnPressed = knobKey; 337 | } else if (lcd_key == inputNONE && knobKey == inputNONE && isbtnPressed != 0) { 338 | isbtnPressed = 0; 339 | } 340 | 341 | 342 | 343 | 344 | // This part may be used to handle extra actions for buttons. 345 | // Left as it was in the original source sode 346 | /* 347 | switch (lcd_key){ // depending on which button was pushed, we perform an action 348 | case btnRIGHT:{ // push button "RIGHT" and show the word on the screen 349 | lcd.print("RIGHT "); 350 | break; 351 | } 352 | case btnLEFT:{ 353 | lcd.print("LEFT "); // push button "LEFT" and show the word on the screen 354 | break; 355 | } 356 | case btnUP:{ 357 | lcd.print("UP "); // push button "UP" and show the word on the screen 358 | break; 359 | } 360 | case btnDOWN:{ 361 | lcd.print("DOWN "); // push button "DOWN" and show the word on the screen 362 | break; 363 | } 364 | case btnSELECT:{ 365 | lcd.print("SELECT"); // push button "SELECT" and show the word on the screen 366 | break; 367 | } 368 | case btnNONE:{ 369 | lcd.print("NONE "); // No action will show "None" on the screen 370 | break; 371 | } 372 | } 373 | */ 374 | } 375 | -------------------------------------------------------------------------------- /Arduino/controller_uno/controller_uno.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************* 2 | SDR++ Controller 3 | by Bartłomiej Marcinkowski 4 | 5 | Designed and tested on: 6 | - Arduino Uno rev3 7 | - DFRobot DFR0009 LCD + Keys shield 8 | - DFRobot EC11 rotary encoder button x2 9 | 10 | Encoder support based on source code taken from DFRobot site: 11 | https://wiki.dfrobot.com/EC11_Rotary_Encoder_Module_SKU__SEN0235 (no author info) 12 | 13 | LCD and Keys support based on source code taken from Botland store site: 14 | https://botland.com.pl/arduino-shield-klawiatury-i-wyswietlacze/2729-dfrobot-lcd-keypad-shield-v11-wyswietlacz-dla-arduino-5903351243780.html 15 | by Mark Bramwell, July 2010 16 | 17 | Greetings to RTL-SDR Polska group @ Facebook 18 | https://www.facebook.com/groups/2628926590655863 19 | **************************************************************************************/ 20 | 21 | #include 22 | #include 23 | 24 | LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // select the pins used on the LCD panel 25 | 26 | // define some values used by the panel and buttons (and knobs) 27 | int lcd_key = 0; 28 | int adc_key_in = 0; 29 | int isbtnPressed = 0; 30 | int incomingByte = 0; 31 | int startCommand = 0; 32 | int bytePos = 0; 33 | int adc_value = 0; 34 | int knobCommand = 0; 35 | int knobKey = 0; 36 | 37 | int coolDown = 0; 38 | 39 | int encoderPinA = 2; 40 | int encoderPinB = 3; 41 | int buttonPin = 11; 42 | 43 | int encoderPinC = 12; 44 | int encoderPinD = 13; 45 | 46 | //left encoder knob 47 | volatile int lastEncodedL = 0; 48 | long lastencoderValueL = 0; 49 | int lastMSBL = 0; 50 | int lastLSBL = 0; 51 | 52 | //right encoder knob 53 | volatile int lastEncodedR = 0; 54 | volatile long encoderValue = 0; 55 | long lastencoderValueR = 0; 56 | int lastMSBR = 0; 57 | int lastLSBR = 0; 58 | 59 | char freq[16] = "0"; 60 | char snr[5] = "0"; 61 | 62 | #define inputNONE 0 63 | 64 | #define knobRIGHT 1 65 | #define knobUP 2 66 | #define knobDOWN 3 67 | #define knobLEFT 4 68 | 69 | #define btnSELECT 5 70 | #define btnRIGHT 6 71 | #define btnUP 7 72 | #define btnDOWN 8 73 | #define btnLEFT 9 74 | #define btnKNOB 0xa 75 | 76 | #define commandCTRL 67 // C - general command for extra actions 77 | #define commandDISP 68 // D - display debug message 78 | #define commandFREQ 70 // F - display frequency 79 | #define commandMODE 77 // M - display mode 80 | #define commandRST 82 // R - reset 81 | #define commandSNR 83 // S - SNR level 82 | #define commandEOL 10 // 0x0a - std end of line 83 | 84 | enum modes_t { 85 | NFM, 86 | WFM, 87 | AM, 88 | DSB, 89 | USB, 90 | CW, 91 | LSB, 92 | RAW 93 | }; 94 | 95 | char *modeStr[8] = { 96 | "NFM","WFM","AM","DSB","USB","CW","LSB","RAW" 97 | }; 98 | 99 | modes_t dMode; 100 | 101 | int read_knob_button() { 102 | if(!digitalRead(buttonPin)) { 103 | return btnKNOB; 104 | } else { 105 | return inputNONE; 106 | } 107 | } 108 | 109 | int read_LCD_buttons(){ // read the buttons 110 | adc_key_in = analogRead(0); // read the value from the sensor 111 | 112 | // my buttons when read are centered at these valies: 0, 144, 329, 504, 741 113 | // we add approx 50 to those values and check to see if we are close 114 | // We make this the 1st option for speed reasons since it will be the most likely result 115 | 116 | if (adc_key_in > 1000) return inputNONE; 117 | 118 | // For V1.1 us this threshold 119 | if (adc_key_in < 50) return btnRIGHT; 120 | if (adc_key_in < 250) return btnUP; 121 | if (adc_key_in < 450) return btnDOWN; 122 | if (adc_key_in < 650) return btnLEFT; 123 | if (adc_key_in < 850) return btnSELECT; 124 | 125 | // For V1.0 comment the other threshold and use the one below: 126 | /* 127 | if (adc_key_in < 50) return btnRIGHT; 128 | if (adc_key_in < 195) return btnUP; 129 | if (adc_key_in < 380) return btnDOWN; 130 | if (adc_key_in < 555) return btnLEFT; 131 | if (adc_key_in < 790) return btnSELECT; 132 | */ 133 | return inputNONE; // when all others fail, return this. 134 | } 135 | 136 | //long readEncoderValue(void){ // not used , taken from DFRobot example 137 | // return encoderValue/4; // we are not counting impulses 138 | //} 139 | 140 | void(* resetFunc) (void) = 0; 141 | 142 | void readCommand() { 143 | char rchar; 144 | if (Serial.available() > 0) { 145 | incomingByte = Serial.read(); 146 | 147 | if (incomingByte == commandEOL){ 148 | startCommand = 0; 149 | bytePos = 0; 150 | } 151 | 152 | switch (startCommand) { 153 | case 0: 154 | break; 155 | case commandFREQ: 156 | if(bytePos < sizeof(freq) - 1) { 157 | freq[bytePos] = incomingByte; 158 | bytePos++; 159 | } 160 | //Serial.println(freq); 161 | break; 162 | case commandSNR: 163 | if(bytePos < sizeof(snr) - 1) { 164 | snr[bytePos] = incomingByte; 165 | bytePos++; 166 | } 167 | //Serial.println(freq); 168 | break; 169 | case commandMODE: 170 | dMode = char(incomingByte) - '0'; 171 | if(dMode > 7 || dMode < 0) { 172 | Serial.println("D Wrong mode"); 173 | dMode = 0; 174 | } else { 175 | Serial.println(modeStr[dMode]); 176 | } 177 | break; 178 | default: 179 | break; 180 | } 181 | 182 | 183 | if(startCommand == 0) { 184 | switch(incomingByte){ 185 | case commandRST: 186 | //Serial.println("D Reset command received"); 187 | delay(200); 188 | resetFunc(); 189 | break; 190 | case commandFREQ: 191 | //Serial.println("D Receinve frequency start"); 192 | memset(freq,0,sizeof(freq)); 193 | startCommand = commandFREQ; 194 | //delay(200); 195 | break; 196 | case commandMODE: 197 | startCommand = commandMODE; 198 | break; 199 | case commandSNR: 200 | memset(snr,0,sizeof(snr)); 201 | startCommand = commandSNR; 202 | break; 203 | case commandEOL: 204 | //Serial.println("D EOL"); 205 | break; 206 | default: 207 | Serial.println("D Unknown command"); 208 | Serial.println(incomingByte, DEC); 209 | Serial.println(incomingByte); 210 | 211 | } 212 | } 213 | //Serial.print("I received: "); 214 | //Serial.println(incomingByte, DEC); 215 | } 216 | } 217 | 218 | // This part is a modified example code from DFRobot site. 219 | // Adjusted to handle two knobs at the same time. 220 | void updateEncoder(){ 221 | //Serial.print(firstcall); 222 | 223 | int MSBR = digitalRead(encoderPinA); //MSB = most significant bit 224 | int LSBR = digitalRead(encoderPinB); //LSB = least significant bit 225 | 226 | int encodedR = (MSBR << 1) |LSBR; //converting the 2 pin value to single number 227 | int sumR = (lastEncodedR << 2) | encodedR; //adding it to the previous encoded value 228 | if(sumR == 0b1101 || sumR == 0b0100 || sumR == 0b0010 || sumR == 0b1011) { 229 | // encoderValue ++; 230 | knobCommand = knobRIGHT; 231 | } 232 | if(sumR == 0b1110 || sumR == 0b0111 || sumR == 0b0001 || sumR == 0b1000) { 233 | // encoderValue --; 234 | knobCommand = knobLEFT; 235 | } 236 | lastEncodedR = encodedR; //store this value for next time 237 | 238 | int MSBL = digitalRead(encoderPinC); //MSB = most significant bit 239 | int LSBL = digitalRead(encoderPinD); //LSB = least significant bit 240 | 241 | int encodedL = (MSBL << 1) |LSBL; //converting the 2 pin value to single number 242 | int sumL = (lastEncodedL << 2) | encodedL; //adding it to the previous encoded value 243 | if(sumL == 0b1101 || sumL == 0b0100 || sumL == 0b0010 || sumL == 0b1011) { 244 | // encoderValue ++; 245 | knobCommand = knobUP; 246 | } 247 | if(sumL == 0b1110 || sumL == 0b0111 || sumL == 0b0001 || sumL == 0b1000) { 248 | // encoderValue --; 249 | knobCommand = knobDOWN; 250 | } 251 | lastEncodedL = encodedL; //store this value for next time 252 | if (coolDown < 10) { // this strange, added to fix garbage on boot, I don't like it 253 | coolDown++; 254 | knobCommand = inputNONE; 255 | } 256 | } 257 | 258 | void setup(){ 259 | Serial.begin(9600); 260 | Serial.println("D SDR++ Controller init"); 261 | 262 | pinMode(encoderPinA, INPUT); 263 | pinMode(encoderPinB, INPUT); 264 | pinMode(buttonPin, INPUT); 265 | 266 | digitalWrite(encoderPinA, HIGH); //turn pullup resistor on 267 | digitalWrite(encoderPinB, HIGH); //turn pullup resistor on 268 | 269 | digitalWrite(encoderPinC, HIGH); //turn pullup resistor on 270 | digitalWrite(encoderPinD, HIGH); //turn pullup resistor on 271 | 272 | //call updateEncoder() when any high/low changed seen 273 | //on interrupt 0 (pin 2), or interrupt 1 (pin 3) 274 | // This works well for UNO with one knob. Unfortunately there are only 275 | // two interrupts and we have to hande four of encoder lines. So... 276 | // We do not use interrupts at all and just call updateEncoder() directly. 277 | //attachInterrupt(0, updateEncoder, CHANGE); 278 | //attachInterrupt(1, updateEncoder, CHANGE); 279 | //Not the best solution probably but as for now it does the job. 280 | 281 | lcd.begin(16, 2); 282 | lcd.setCursor(0,0); 283 | lcd.print("SDR++ Controller"); 284 | delay(1000); 285 | lcd.clear(); 286 | } 287 | 288 | void loop(){ 289 | 290 | readCommand(); 291 | 292 | lcd.setCursor(0,0); 293 | lcd.print("F: "); 294 | lcd.print(freq); 295 | lcd.print(" "); 296 | 297 | lcd.setCursor(0,1); 298 | lcd.print("["); 299 | lcd.print(modeStr[dMode]); 300 | lcd.print("] "); 301 | 302 | lcd.setCursor(6,5); 303 | lcd.print("SNR: "); 304 | lcd.print(snr); 305 | lcd.print(" "); 306 | 307 | lcd_key = read_LCD_buttons(); 308 | knobKey = read_knob_button(); 309 | 310 | updateEncoder(); // this should be called by interrupt but ... read desc. above 311 | 312 | if (lcd_key != inputNONE && isbtnPressed == 0) { 313 | Serial.write(commandCTRL); 314 | Serial.println(lcd_key); 315 | Serial.flush(); 316 | isbtnPressed = lcd_key; 317 | } else if (knobCommand != inputNONE) { 318 | Serial.write(commandCTRL); 319 | Serial.println(knobCommand); 320 | Serial.flush(); 321 | knobCommand = inputNONE; 322 | } else if (knobKey != inputNONE && isbtnPressed == 0) { 323 | Serial.write(commandCTRL); 324 | Serial.println(knobKey,HEX); 325 | Serial.flush(); 326 | isbtnPressed = knobKey; 327 | } else if (lcd_key == inputNONE && knobKey == inputNONE && isbtnPressed != 0) { 328 | isbtnPressed = 0; 329 | } 330 | 331 | 332 | 333 | 334 | // This part may be used to handle extra actions for buttons. 335 | // Left as it was in the original source sode 336 | /* 337 | switch (lcd_key){ // depending on which button was pushed, we perform an action 338 | case btnRIGHT:{ // push button "RIGHT" and show the word on the screen 339 | lcd.print("RIGHT "); 340 | break; 341 | } 342 | case btnLEFT:{ 343 | lcd.print("LEFT "); // push button "LEFT" and show the word on the screen 344 | break; 345 | } 346 | case btnUP:{ 347 | lcd.print("UP "); // push button "UP" and show the word on the screen 348 | break; 349 | } 350 | case btnDOWN:{ 351 | lcd.print("DOWN "); // push button "DOWN" and show the word on the screen 352 | break; 353 | } 354 | case btnSELECT:{ 355 | lcd.print("SELECT"); // push button "SELECT" and show the word on the screen 356 | break; 357 | } 358 | case btnNONE:{ 359 | lcd.print("NONE "); // No action will show "None" on the screen 360 | break; 361 | } 362 | } 363 | */ 364 | } 365 | -------------------------------------------------------------------------------- /Plugin/misc_modules/arduino_controller/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(arduino_controller) 3 | 4 | file(GLOB SRC "src/*.cpp") 5 | 6 | add_library(arduino_controller SHARED ${SRC}) 7 | target_link_libraries(arduino_controller PRIVATE sdrpp_core) 8 | set_target_properties(arduino_controller PROPERTIES PREFIX "") 9 | 10 | target_include_directories(arduino_controller PRIVATE "src/" "../../decoder_modules/radio/src") 11 | 12 | if (MSVC) 13 | target_compile_options(arduino_controller PRIVATE /O2 /Ob2 /std:c++17 /EHsc) 14 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 15 | target_compile_options(arduino_controller PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup) 16 | else () 17 | target_compile_options(arduino_controller PRIVATE -O3 -std=c++17) 18 | endif () 19 | 20 | # Install directives 21 | install(TARGETS arduino_controller DESTINATION lib/sdrpp/plugins) -------------------------------------------------------------------------------- /Plugin/misc_modules/arduino_controller/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Arduino SDR++ Controller - Linux plugin. 3 | Bartłomiej Marcinkowski 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | //#include - not available in v1.06 16 | #define CONCAT(a, b) ((std::string(a) + b).c_str()) 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #if defined (_WIN32) || defined(_WIN64) 24 | #define DEFAULT_SERIAL_PORT "\\\\.\\COM4" 25 | #endif 26 | #if defined (__linux__) || defined(__APPLE__) 27 | #define DEFAULT_SERIAL_PORT "/dev/ttyACM0" 28 | #endif 29 | 30 | 31 | 32 | SDRPP_MOD_INFO{ 33 | /* Name: */ "arduino_controller", 34 | /* Description: */ "Arduino SDR++ controller", 35 | /* Author: */ "Bartłomiej Marcinkowski", 36 | /* Version: */ 0, 2, 0, 37 | /* Max instances */ 1 38 | }; 39 | 40 | ConfigManager config; 41 | 42 | class ArduinoController : public ModuleManager::Instance { 43 | public: 44 | ArduinoController(std::string name) { 45 | this->name = name; 46 | gui::menu.registerEntry(name, menuHandler, this, NULL); 47 | 48 | config.acquire(); 49 | if (!config.conf.contains(name)) { 50 | config.conf[name]["ttyport"] = DEFAULT_SERIAL_PORT; 51 | } 52 | config.release(true); 53 | std::string port = config.conf[name]["ttyport"]; 54 | strcpy(ttyport, port.c_str()); 55 | } 56 | 57 | ~ArduinoController() { 58 | gui::menu.removeEntry(name); 59 | } 60 | 61 | void postInit() { 62 | //connectArduino(); 63 | } 64 | 65 | void enable() { 66 | enabled = true; 67 | } 68 | 69 | void disable() { 70 | enabled = false; 71 | } 72 | 73 | bool isEnabled() { 74 | return enabled; 75 | } 76 | 77 | private: 78 | static void menuHandler(void* ctx) { 79 | // float menuWidth = ImGui::GetContentRegionAvailWidth(); - not available in v1.06 80 | float menuWidth = ImGui::GetContentRegionAvail().x; 81 | int freq = int(gui::waterfall.getCenterFrequency() + sigpath::vfoManager.getOffset(gui::waterfall.selectedVFO)); 82 | int mode; 83 | core::modComManager.callInterface(gui::waterfall.selectedVFO, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); 84 | float gSNR = gui::waterfall.selectedVFOSNR; 85 | 86 | ArduinoController * _this = (ArduinoController *)ctx; 87 | 88 | ImGui::Text("Arduino SDR++ controller"); 89 | 90 | ImGui::LeftLabel("Serial port:"); 91 | ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); 92 | ImGui::SameLine(); 93 | if (ImGui::InputText(CONCAT("##_arduino_controller_ttyport_", _this->name), _this->ttyport, 1023)) { 94 | config.acquire(); 95 | config.conf[_this->name]["ttyport"] = std::string(_this->ttyport); 96 | config.release(true); 97 | } 98 | 99 | if (_this->serial_port && ImGui::Button(CONCAT("Stop##_arduino_controller_stop_", _this->name), ImVec2(menuWidth, 0))) { 100 | _this->disconnectArduino(); 101 | } 102 | else if (!_this->serial_port && ImGui::Button(CONCAT("Start##_arduino_controller_stop_", _this->name), ImVec2(menuWidth, 0))) { 103 | _this->connectArduino(); 104 | } 105 | ImGui::TextUnformatted("Status:"); 106 | ImGui::SameLine(); 107 | if (_this->serial_port) { 108 | ImGui::TextColored(ImVec4(0.0, 1.0, 0.0, 1.0), "Connected"); 109 | } else { 110 | ImGui::TextColored(ImVec4(0.0, 1.0, 1.0, 1.0), "Disconnected"); 111 | } 112 | 113 | ImGui::Text("Serial debug > %s", _this->commandReady); 114 | 115 | if (_this->serial_port) { 116 | _this->readArduino(); 117 | _this->writeArduino(freq,mode,(int)gSNR); 118 | } 119 | } 120 | 121 | #if defined (_WIN32) || defined(_WIN64) // this is so ugly... 122 | #define CLOCK_MONOTONIC_RAW 4 123 | struct timespec { 124 | long tv_sec; 125 | long tv_nsec; 126 | }; 127 | 128 | int clock_gettime(int, struct timespec *spec) { 129 | # define CLOCK_MONOTONIC_RAW 4 130 | __int64 wintime; GetSystemTimeAsFileTime((FILETIME*)&wintime); 131 | wintime -=116444736000000000i64; //1jan1601 to 1jan1970 132 | spec->tv_sec =wintime / 10000000i64; //seconds 133 | spec->tv_nsec =wintime % 10000000i64 *100; //nano-seconds 134 | return 0; 135 | } 136 | #endif 137 | 138 | std::string name; 139 | char ttyport[1024]; 140 | bool enabled = true; 141 | struct timespec lastCall = {0,0}; 142 | int serial_port = 0; 143 | serialib serial; 144 | 145 | int lastFreq = 0; 146 | int lastDemod = 0; 147 | int lastSnr = 0; 148 | int isInit = 0; 149 | float zoom = 1.0; 150 | 151 | char commandBuf[256]; 152 | char commandReady[256]; 153 | 154 | 155 | /* Serial commands (read): 156 | 157 | C1 , C4 - Right Knob - fine tune based on snap interval 158 | C2 , C3 - Left Knob - tune , step = snap interval * 10 159 | C7 , C8 - Tune based on same rate - page up / page down 160 | C6 , C9 - Waterfall zoom in / zoom out 161 | C5 - Demodulator cycle 162 | 163 | */ 164 | void checkCommand(char *command) { 165 | 166 | ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO]; 167 | int snapInt = vfo->snapInterval; 168 | double freq = int(gui::waterfall.getCenterFrequency() + sigpath::vfoManager.getOffset(gui::waterfall.selectedVFO)); 169 | double srate = sigpath::signalPath.getSampleRate(); 170 | 171 | // right knob encoder (fine)tuner (snap interval) 172 | if(!strncmp(command,"C1",2)) { 173 | freq += snapInt; 174 | } 175 | if(!strncmp(command,"C4",2)) { 176 | freq -= snapInt; 177 | } 178 | 179 | // left knob encoder tuner (snap interval * 10) 180 | if(!strncmp(command,"C2",2)) { 181 | snapInt *= 10; 182 | freq += snapInt; 183 | } 184 | if(!strncmp(command,"C3",2)) { 185 | snapInt *= 10; 186 | freq -= snapInt; 187 | } 188 | 189 | // up/down tuner (sample rate based) aka pageup/pagedown 190 | if(!strncmp(command,"C7",2)) { 191 | freq += srate; 192 | } 193 | if(!strncmp(command,"C8",2)) { 194 | freq -= srate; 195 | } 196 | 197 | // zoom in / zoom out 198 | if(!strncmp(command,"C6",2)) { 199 | zoom -= 0.1; 200 | if(zoom < 0) { 201 | zoom = 0; 202 | } 203 | } 204 | if(!strncmp(command,"C9",2)) { 205 | zoom += 0.1; 206 | if(zoom > 1) { 207 | zoom = 1; 208 | } 209 | } 210 | 211 | // zoom in / zoom out main windows 212 | if (!strncmp(command,"C6",2) || !strncmp(command,"C9",2)) { 213 | double factor = (double)zoom * (double)zoom; // This code is duplicated from main_windows.c 214 | double wfBw = gui::waterfall.getBandwidth(); // I don't know how to update "Zoom" slider. 215 | double delta = wfBw - 1000.0; 216 | double finalBw = std::min(1000.0 + (factor * delta), wfBw); 217 | gui::waterfall.setViewBandwidth(finalBw); 218 | 219 | if (vfo != NULL) { 220 | gui::waterfall.setViewOffset(vfo->centerOffset); 221 | } 222 | } 223 | 224 | // set up new frequency in normal mode 225 | if (!strncmp(command,"C1",2) || !strncmp(command,"C2",2) || !strncmp(command,"C3",2) || !strncmp(command,"C4",2)) { 226 | freq = roundl(freq / snapInt) * snapInt; 227 | tuner::tune(tuner::TUNER_MODE_NORMAL, gui::waterfall.selectedVFO, freq); 228 | } 229 | 230 | // set up new frequency in center mode - for page up / page down / center knob click 231 | if (!strncmp(command,"C7",2) || !strncmp(command,"C8",2) || !strncmp(command,"CA",2)) { 232 | tuner::tune(tuner::TUNER_MODE_CENTER, gui::waterfall.selectedVFO, freq); 233 | } 234 | 235 | // cycle modes with SELECT button 236 | if(!strncmp(command,"C5",2)) { 237 | int mode; 238 | core::modComManager.callInterface(gui::waterfall.selectedVFO, RADIO_IFACE_CMD_GET_MODE, NULL, &mode); 239 | if(mode < 7) { 240 | mode++; 241 | } else { 242 | mode = 0; 243 | } 244 | core::modComManager.callInterface(gui::waterfall.selectedVFO, RADIO_IFACE_CMD_SET_MODE, &mode, NULL); 245 | } 246 | } 247 | 248 | void readArduino() { 249 | char read_buf[32]; 250 | memset(&read_buf, '\0', sizeof(read_buf)); 251 | 252 | // this is not working exactly as I expected. I do not like windows 253 | int num_bytes = serial.readBytes(read_buf,sizeof(read_buf),1,0); 254 | 255 | if (num_bytes < 0) { 256 | spdlog::info("Error reading: {0}", strerror(errno)); 257 | } else if (num_bytes > 0) { 258 | strncat(commandBuf,read_buf,num_bytes); 259 | if (commandBuf[strlen(commandBuf)-1] == '\n') { 260 | memcpy(commandReady,commandBuf,sizeof(commandReady)); 261 | memset(&commandBuf,'\0',sizeof(commandBuf)); 262 | spdlog::info(">> {0}", commandReady); 263 | if (isInit == 0) { 264 | isInit = 1; 265 | spdlog::info(">> Received first line"); 266 | } 267 | checkCommand(commandReady); 268 | } 269 | //spdlog::info(">> {0}", commandBuf); 270 | //memset(&commandBuf,'\0',sizeof(commandBuf)); 271 | } 272 | 273 | } 274 | /* Serial commands (write): 275 | 276 | F\n - Update frequency info 277 | S\n - Update SNR level 278 | M\n - Update demodulator info 279 | R\n - Reset controller 280 | 281 | */ 282 | 283 | // this should be rewritten to support some kind of ACK and queue 284 | // maybe some day ... 285 | 286 | void writeArduino(int frequency, int demod, int snr){ 287 | struct timespec currentCall = {0,0}; 288 | int delta_us = 0; 289 | char bsend; 290 | 291 | clock_gettime(CLOCK_MONOTONIC_RAW, ¤tCall); 292 | delta_us = (currentCall.tv_sec - lastCall.tv_sec) * 1000000 + (currentCall.tv_nsec - lastCall.tv_nsec) / 1000; 293 | 294 | // do not flood this poor Arduino 295 | if (delta_us > 200000 && isInit > 0) { 296 | if(frequency != lastFreq) { 297 | // spdlog::info(">> Delta [{0}] {1} {2} {3} {4}", delta_us,lastCall.tv_nsec,lastCall.tv_sec,currentCall.tv_nsec,currentCall.tv_sec); 298 | char msg[14]; 299 | memset(&msg,'\0',sizeof(msg)); 300 | snprintf(msg,14,"F%d\n",frequency); 301 | bsend = serial.writeString(msg); 302 | // spdlog::info(">> Send freq {0}", msg); 303 | lastFreq = frequency; 304 | lastCall = currentCall; 305 | return; 306 | } 307 | 308 | if(demod != lastDemod) { 309 | char dmsg[14]; 310 | memset(&dmsg,'\0',sizeof(dmsg)); 311 | snprintf(dmsg,14,"M%d\n",demod); 312 | bsend = serial.writeString(dmsg); 313 | // spdlog::info(">> Send demod {0}", dmsg); 314 | lastDemod = demod; 315 | lastCall = currentCall; 316 | return; 317 | } 318 | 319 | if(snr != lastSnr && delta_us > 500000) { 320 | char smsg[5]; 321 | memset(&smsg,'\0',sizeof(smsg)); 322 | if (lastSnr - snr > 6 && snr > 0 ) { // just to make readings more ... stable 323 | snr = (lastSnr + snr ) / 2; 324 | } 325 | snprintf(smsg,5,"S%d\n",snr); 326 | bsend = serial.writeString(smsg); 327 | // spdlog::info(">> Send SNR {0}", smsg); 328 | lastSnr = snr; 329 | lastCall = currentCall; 330 | return; 331 | } 332 | } 333 | } 334 | 335 | void disconnectArduino(){ 336 | serial.closeDevice(); 337 | serial_port = 0; 338 | isInit = 0; 339 | lastDemod = 0; 340 | lastFreq = 0; 341 | lastSnr = 0; 342 | spdlog::info("Disconnect Arduino"); 343 | } 344 | 345 | void connectArduino(){ 346 | // Connection to serial port 347 | char errorOpening = serial.openDevice(ttyport, 9600); 348 | if (errorOpening!=1) { 349 | spdlog::info("Error connecting to Arduino"); 350 | serial_port = 0; 351 | return; 352 | } 353 | serial_port = 1; 354 | clock_gettime(CLOCK_MONOTONIC_RAW, &lastCall); 355 | 356 | //send reset command 357 | char reset_char = serial.writeString("R\n"); 358 | 359 | } 360 | 361 | }; 362 | 363 | MOD_EXPORT void _INIT_() { 364 | // config.setPath(options::opts.root + "/arduino_controller_config.json"); - not available in v1.06 365 | config.setPath(core::args["root"].s() + "/arduino_controller_config.json"); 366 | config.load(json::object()); 367 | config.enableAutoSave(); 368 | } 369 | 370 | MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { 371 | return new ArduinoController(name); 372 | } 373 | 374 | MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { 375 | delete (ArduinoController *)instance; 376 | } 377 | 378 | MOD_EXPORT void _END_() { 379 | config.disableAutoSave(); 380 | config.save(); 381 | } 382 | -------------------------------------------------------------------------------- /Plugin/misc_modules/arduino_controller/src/serialib.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | \file serialib.cpp 3 | \brief Source file of the class serialib. This class is used for communication over a serial device. 4 | \author Philippe Lucidarme (University of Angers) 5 | \version 2.0 6 | \date december the 27th of 2019 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 10 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, 11 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 12 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | 15 | This is a licence-free software, it can be used by anyone who try to build a better world. 16 | */ 17 | 18 | #include "serialib.h" 19 | 20 | 21 | 22 | //_____________________________________ 23 | // ::: Constructors and destructors ::: 24 | 25 | 26 | /*! 27 | \brief Constructor of the class serialib. 28 | */ 29 | serialib::serialib() 30 | { 31 | #if defined (_WIN32) || defined( _WIN64) 32 | // Set default value for RTS and DTR (Windows only) 33 | currentStateRTS=true; 34 | currentStateDTR=true; 35 | hSerial = INVALID_HANDLE_VALUE; 36 | #endif 37 | #if defined (__linux__) || defined(__APPLE__) 38 | fd = -1; 39 | #endif 40 | } 41 | 42 | 43 | /*! 44 | \brief Destructor of the class serialib. It close the connection 45 | */ 46 | // Class desctructor 47 | serialib::~serialib() 48 | { 49 | closeDevice(); 50 | } 51 | 52 | 53 | 54 | //_________________________________________ 55 | // ::: Configuration and initialization ::: 56 | 57 | 58 | 59 | /*! 60 | \brief Open the serial port 61 | \param Device : Port name (COM1, COM2, ... for Windows ) or (/dev/ttyS0, /dev/ttyACM0, /dev/ttyUSB0 ... for linux) 62 | \param Bauds : Baud rate of the serial port. 63 | 64 | \n Supported baud rate for Windows : 65 | - 110 66 | - 300 67 | - 600 68 | - 1200 69 | - 2400 70 | - 4800 71 | - 9600 72 | - 14400 73 | - 19200 74 | - 38400 75 | - 56000 76 | - 57600 77 | - 115200 78 | - 128000 79 | - 256000 80 | 81 | \n Supported baud rate for Linux :\n 82 | - 110 83 | - 300 84 | - 600 85 | - 1200 86 | - 2400 87 | - 4800 88 | - 9600 89 | - 19200 90 | - 38400 91 | - 57600 92 | - 115200 93 | \param Databits : Number of data bits in one UART transmission. 94 | 95 | \n Supported values: \n 96 | - SERIAL_DATABITS_5 (5) 97 | - SERIAL_DATABITS_6 (6) 98 | - SERIAL_DATABITS_7 (7) 99 | - SERIAL_DATABITS_8 (8) 100 | - SERIAL_DATABITS_16 (16) (not supported on Unix) 101 | 102 | \param Parity: Parity type 103 | 104 | \n Supported values: \n 105 | - SERIAL_PARITY_NONE (N) 106 | - SERIAL_PARITY_EVEN (E) 107 | - SERIAL_PARITY_ODD (O) 108 | - SERIAL_PARITY_MARK (MARK) (not supported on Unix) 109 | - SERIAL_PARITY_SPACE (SPACE) (not supported on Unix) 110 | \param Stopbit: Number of stop bits 111 | 112 | \n Supported values: 113 | - SERIAL_STOPBITS_1 (1) 114 | - SERIAL_STOPBITS_1_5 (1.5) (not supported on Unix) 115 | - SERIAL_STOPBITS_2 (2) 116 | 117 | \return 1 success 118 | \return -1 device not found 119 | \return -2 error while opening the device 120 | \return -3 error while getting port parameters 121 | \return -4 Speed (Bauds) not recognized 122 | \return -5 error while writing port parameters 123 | \return -6 error while writing timeout parameters 124 | \return -7 Databits not recognized 125 | \return -8 Stopbits not recognized 126 | \return -9 Parity not recognized 127 | */ 128 | char serialib::openDevice(const char *Device, const unsigned int Bauds, 129 | SerialDataBits Databits, 130 | SerialParity Parity, 131 | SerialStopBits Stopbits) { 132 | #if defined (_WIN32) || defined( _WIN64) 133 | // Open serial port 134 | hSerial = CreateFileA(Device,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,/*FILE_ATTRIBUTE_NORMAL*/0,0); 135 | if(hSerial==INVALID_HANDLE_VALUE) { 136 | if(GetLastError()==ERROR_FILE_NOT_FOUND) 137 | return -1; // Device not found 138 | 139 | // Error while opening the device 140 | return -2; 141 | } 142 | 143 | // Set parameters 144 | 145 | // Structure for the port parameters 146 | DCB dcbSerialParams; 147 | dcbSerialParams.DCBlength=sizeof(dcbSerialParams); 148 | 149 | // Get the port parameters 150 | if (!GetCommState(hSerial, &dcbSerialParams)) return -3; 151 | 152 | // Set the speed (Bauds) 153 | switch (Bauds) 154 | { 155 | case 110 : dcbSerialParams.BaudRate=CBR_110; break; 156 | case 300 : dcbSerialParams.BaudRate=CBR_300; break; 157 | case 600 : dcbSerialParams.BaudRate=CBR_600; break; 158 | case 1200 : dcbSerialParams.BaudRate=CBR_1200; break; 159 | case 2400 : dcbSerialParams.BaudRate=CBR_2400; break; 160 | case 4800 : dcbSerialParams.BaudRate=CBR_4800; break; 161 | case 9600 : dcbSerialParams.BaudRate=CBR_9600; break; 162 | case 14400 : dcbSerialParams.BaudRate=CBR_14400; break; 163 | case 19200 : dcbSerialParams.BaudRate=CBR_19200; break; 164 | case 38400 : dcbSerialParams.BaudRate=CBR_38400; break; 165 | case 56000 : dcbSerialParams.BaudRate=CBR_56000; break; 166 | case 57600 : dcbSerialParams.BaudRate=CBR_57600; break; 167 | case 115200 : dcbSerialParams.BaudRate=CBR_115200; break; 168 | case 128000 : dcbSerialParams.BaudRate=CBR_128000; break; 169 | case 256000 : dcbSerialParams.BaudRate=CBR_256000; break; 170 | default : return -4; 171 | } 172 | //select data size 173 | BYTE bytesize = 0; 174 | switch(Databits) { 175 | case SERIAL_DATABITS_5: bytesize = 5; break; 176 | case SERIAL_DATABITS_6: bytesize = 6; break; 177 | case SERIAL_DATABITS_7: bytesize = 7; break; 178 | case SERIAL_DATABITS_8: bytesize = 8; break; 179 | case SERIAL_DATABITS_16: bytesize = 16; break; 180 | default: return -7; 181 | } 182 | BYTE stopBits = 0; 183 | switch(Stopbits) { 184 | case SERIAL_STOPBITS_1: stopBits = ONESTOPBIT; break; 185 | case SERIAL_STOPBITS_1_5: stopBits = ONE5STOPBITS; break; 186 | case SERIAL_STOPBITS_2: stopBits = TWOSTOPBITS; break; 187 | default: return -8; 188 | } 189 | BYTE parity = 0; 190 | switch(Parity) { 191 | case SERIAL_PARITY_NONE: parity = NOPARITY; break; 192 | case SERIAL_PARITY_EVEN: parity = EVENPARITY; break; 193 | case SERIAL_PARITY_ODD: parity = ODDPARITY; break; 194 | case SERIAL_PARITY_MARK: parity = MARKPARITY; break; 195 | case SERIAL_PARITY_SPACE: parity = SPACEPARITY; break; 196 | default: return -9; 197 | } 198 | // configure byte size 199 | dcbSerialParams.ByteSize = bytesize; 200 | // configure stop bits 201 | dcbSerialParams.StopBits = stopBits; 202 | // configure parity 203 | dcbSerialParams.Parity = parity; 204 | 205 | // Write the parameters 206 | if(!SetCommState(hSerial, &dcbSerialParams)) return -5; 207 | 208 | // Set TimeOut 209 | 210 | // Set the Timeout parameters 211 | timeouts.ReadIntervalTimeout=0; 212 | // No TimeOut 213 | timeouts.ReadTotalTimeoutConstant=MAXDWORD; 214 | timeouts.ReadTotalTimeoutMultiplier=0; 215 | timeouts.WriteTotalTimeoutConstant=MAXDWORD; 216 | timeouts.WriteTotalTimeoutMultiplier=0; 217 | 218 | // Write the parameters 219 | if(!SetCommTimeouts(hSerial, &timeouts)) return -6; 220 | 221 | // Opening successfull 222 | return 1; 223 | #endif 224 | #if defined (__linux__) || defined(__APPLE__) 225 | // Structure with the device's options 226 | struct termios options; 227 | 228 | 229 | // Open device 230 | fd = open(Device, O_RDWR | O_NOCTTY | O_NDELAY); 231 | // If the device is not open, return -1 232 | if (fd == -1) return -2; 233 | // Open the device in nonblocking mode 234 | fcntl(fd, F_SETFL, FNDELAY); 235 | 236 | 237 | // Get the current options of the port 238 | tcgetattr(fd, &options); 239 | // Clear all the options 240 | bzero(&options, sizeof(options)); 241 | 242 | // Prepare speed (Bauds) 243 | speed_t Speed; 244 | switch (Bauds) 245 | { 246 | case 110 : Speed=B110; break; 247 | case 300 : Speed=B300; break; 248 | case 600 : Speed=B600; break; 249 | case 1200 : Speed=B1200; break; 250 | case 2400 : Speed=B2400; break; 251 | case 4800 : Speed=B4800; break; 252 | case 9600 : Speed=B9600; break; 253 | case 19200 : Speed=B19200; break; 254 | case 38400 : Speed=B38400; break; 255 | case 57600 : Speed=B57600; break; 256 | case 115200 : Speed=B115200; break; 257 | default : return -4; 258 | } 259 | int databits_flag = 0; 260 | switch(Databits) { 261 | case SERIAL_DATABITS_5: databits_flag = CS5; break; 262 | case SERIAL_DATABITS_6: databits_flag = CS6; break; 263 | case SERIAL_DATABITS_7: databits_flag = CS7; break; 264 | case SERIAL_DATABITS_8: databits_flag = CS8; break; 265 | //16 bits and everything else not supported 266 | default: return -7; 267 | } 268 | int stopbits_flag = 0; 269 | switch(Stopbits) { 270 | case SERIAL_STOPBITS_1: stopbits_flag = 0; break; 271 | case SERIAL_STOPBITS_2: stopbits_flag = CSTOPB; break; 272 | //1.5 stopbits and everything else not supported 273 | default: return -8; 274 | } 275 | int parity_flag = 0; 276 | switch(Parity) { 277 | case SERIAL_PARITY_NONE: parity_flag = 0; break; 278 | case SERIAL_PARITY_EVEN: parity_flag = PARENB; break; 279 | case SERIAL_PARITY_ODD: parity_flag = (PARENB | PARODD); break; 280 | //mark and space parity not supported 281 | default: return -9; 282 | } 283 | 284 | // Set the baud rate 285 | cfsetispeed(&options, Speed); 286 | cfsetospeed(&options, Speed); 287 | // Configure the device : data bits, stop bits, parity, no control flow 288 | // Ignore modem control lines (CLOCAL) and Enable receiver (CREAD) 289 | options.c_cflag |= ( CLOCAL | CREAD | databits_flag | parity_flag | stopbits_flag); 290 | options.c_iflag |= ( IGNPAR | IGNBRK ); 291 | // Timer unused 292 | options.c_cc[VTIME]=0; 293 | // At least on character before satisfy reading 294 | options.c_cc[VMIN]=0; 295 | // Activate the settings 296 | tcsetattr(fd, TCSANOW, &options); 297 | // Success 298 | return (1); 299 | #endif 300 | 301 | } 302 | 303 | bool serialib::isDeviceOpen() 304 | { 305 | #if defined (_WIN32) || defined( _WIN64) 306 | return hSerial != INVALID_HANDLE_VALUE; 307 | #endif 308 | #if defined (__linux__) || defined(__APPLE__) 309 | return fd >= 0; 310 | #endif 311 | } 312 | 313 | /*! 314 | \brief Close the connection with the current device 315 | */ 316 | void serialib::closeDevice() 317 | { 318 | #if defined (_WIN32) || defined( _WIN64) 319 | CloseHandle(hSerial); 320 | hSerial = INVALID_HANDLE_VALUE; 321 | #endif 322 | #if defined (__linux__) || defined(__APPLE__) 323 | close (fd); 324 | fd = -1; 325 | #endif 326 | } 327 | 328 | 329 | 330 | 331 | //___________________________________________ 332 | // ::: Read/Write operation on characters ::: 333 | 334 | 335 | 336 | /*! 337 | \brief Write a char on the current serial port 338 | \param Byte : char to send on the port (must be terminated by '\0') 339 | \return 1 success 340 | \return -1 error while writting data 341 | */ 342 | char serialib::writeChar(const char Byte) 343 | { 344 | #if defined (_WIN32) || defined( _WIN64) 345 | // Number of bytes written 346 | DWORD dwBytesWritten; 347 | // Write the char to the serial device 348 | // Return -1 if an error occured 349 | if(!WriteFile(hSerial,&Byte,1,&dwBytesWritten,NULL)) return -1; 350 | // Write operation successfull 351 | return 1; 352 | #endif 353 | #if defined (__linux__) || defined(__APPLE__) 354 | // Write the char 355 | if (write(fd,&Byte,1)!=1) return -1; 356 | 357 | // Write operation successfull 358 | return 1; 359 | #endif 360 | } 361 | 362 | 363 | 364 | //________________________________________ 365 | // ::: Read/Write operation on strings ::: 366 | 367 | 368 | /*! 369 | \brief Write a string on the current serial port 370 | \param receivedString : string to send on the port (must be terminated by '\0') 371 | \return 1 success 372 | \return -1 error while writting data 373 | */ 374 | char serialib::writeString(const char *receivedString) 375 | { 376 | #if defined (_WIN32) || defined( _WIN64) 377 | // Number of bytes written 378 | DWORD dwBytesWritten; 379 | // Write the string 380 | if(!WriteFile(hSerial,receivedString,strlen(receivedString),&dwBytesWritten,NULL)) 381 | // Error while writing, return -1 382 | return -1; 383 | // Write operation successfull 384 | return 1; 385 | #endif 386 | #if defined (__linux__) || defined(__APPLE__) 387 | // Lenght of the string 388 | int Lenght=strlen(receivedString); 389 | // Write the string 390 | if (write(fd,receivedString,Lenght)!=Lenght) return -1; 391 | // Write operation successfull 392 | return 1; 393 | #endif 394 | } 395 | 396 | // _____________________________________ 397 | // ::: Read/Write operation on bytes ::: 398 | 399 | 400 | 401 | /*! 402 | \brief Write an array of data on the current serial port 403 | \param Buffer : array of bytes to send on the port 404 | \param NbBytes : number of byte to send 405 | \return 1 success 406 | \return -1 error while writting data 407 | */ 408 | char serialib::writeBytes(const void *Buffer, const unsigned int NbBytes) 409 | { 410 | #if defined (_WIN32) || defined( _WIN64) 411 | // Number of bytes written 412 | DWORD dwBytesWritten; 413 | // Write data 414 | if(!WriteFile(hSerial, Buffer, NbBytes, &dwBytesWritten, NULL)) 415 | // Error while writing, return -1 416 | return -1; 417 | // Write operation successfull 418 | return 1; 419 | #endif 420 | #if defined (__linux__) || defined(__APPLE__) 421 | // Write data 422 | if (write (fd,Buffer,NbBytes)!=(ssize_t)NbBytes) return -1; 423 | // Write operation successfull 424 | return 1; 425 | #endif 426 | } 427 | 428 | 429 | 430 | /*! 431 | \brief Wait for a byte from the serial device and return the data read 432 | \param pByte : data read on the serial device 433 | \param timeOut_ms : delay of timeout before giving up the reading 434 | If set to zero, timeout is disable (Optional) 435 | \return 1 success 436 | \return 0 Timeout reached 437 | \return -1 error while setting the Timeout 438 | \return -2 error while reading the byte 439 | */ 440 | char serialib::readChar(char *pByte,unsigned int timeOut_ms) 441 | { 442 | #if defined (_WIN32) || defined(_WIN64) 443 | // Number of bytes read 444 | DWORD dwBytesRead = 0; 445 | 446 | // Set the TimeOut 447 | timeouts.ReadTotalTimeoutConstant=timeOut_ms; 448 | 449 | // Write the parameters, return -1 if an error occured 450 | if(!SetCommTimeouts(hSerial, &timeouts)) return -1; 451 | 452 | // Read the byte, return -2 if an error occured 453 | if(!ReadFile(hSerial,pByte, 1, &dwBytesRead, NULL)) return -2; 454 | 455 | // Return 0 if the timeout is reached 456 | if (dwBytesRead==0) return 0; 457 | 458 | // The byte is read 459 | return 1; 460 | #endif 461 | #if defined (__linux__) || defined(__APPLE__) 462 | // Timer used for timeout 463 | timeOut timer; 464 | // Initialise the timer 465 | timer.initTimer(); 466 | // While Timeout is not reached 467 | while (timer.elapsedTime_ms()0 success, return the number of bytes read 487 | \return -1 error while setting the Timeout 488 | \return -2 error while reading the byte 489 | \return -3 MaxNbBytes is reached 490 | */ 491 | int serialib::readStringNoTimeOut(char *receivedString,char finalChar,unsigned int maxNbBytes) 492 | { 493 | // Number of characters read 494 | unsigned int NbBytes=0; 495 | // Returned value from Read 496 | char charRead; 497 | 498 | // While the buffer is not full 499 | while (NbBytes0 success, return the number of bytes read 535 | \return 0 timeout is reached 536 | \return -1 error while setting the Timeout 537 | \return -2 error while reading the byte 538 | \return -3 MaxNbBytes is reached 539 | */ 540 | int serialib::readString(char *receivedString,char finalChar,unsigned int maxNbBytes,unsigned int timeOut_ms) 541 | { 542 | // Check if timeout is requested 543 | if (timeOut_ms==0) return readStringNoTimeOut(receivedString,finalChar,maxNbBytes); 544 | 545 | // Number of bytes read 546 | unsigned int nbBytes=0; 547 | // Character read on serial device 548 | char charRead; 549 | // Timer used for timeout 550 | timeOut timer; 551 | long int timeOutParam; 552 | 553 | // Initialize the timer (for timeout) 554 | timer.initTimer(); 555 | 556 | // While the buffer is not full 557 | while (nbBytes0) 564 | { 565 | // Wait for a byte on the serial link with the remaining time as timeout 566 | charRead=readChar(&receivedString[nbBytes],timeOutParam); 567 | 568 | // If a byte has been received 569 | if (charRead==1) 570 | { 571 | // Check if the character received is the final one 572 | if (receivedString[nbBytes]==finalChar) 573 | { 574 | // Final character: add the end character 0 575 | receivedString [++nbBytes]=0; 576 | // Return the number of bytes read 577 | return nbBytes; 578 | } 579 | // This is not the final character, just increase the number of bytes read 580 | nbBytes++; 581 | } 582 | // Check if an error occured during reading char 583 | // If an error occurend, return the error number 584 | if (charRead<0) return charRead; 585 | } 586 | // Check if timeout is reached 587 | if (timer.elapsedTime_ms()>timeOut_ms) 588 | { 589 | // Add the end caracter 590 | receivedString[nbBytes]=0; 591 | // Return 0 (timeout reached) 592 | return 0; 593 | } 594 | } 595 | 596 | // Buffer is full : return -3 597 | return -3; 598 | } 599 | 600 | 601 | /*! 602 | \brief Read an array of bytes from the serial device (with timeout) 603 | \param buffer : array of bytes read from the serial device 604 | \param maxNbBytes : maximum allowed number of bytes read 605 | \param timeOut_ms : delay of timeout before giving up the reading 606 | \param sleepDuration_us : delay of CPU relaxing in microseconds (Linux only) 607 | In the reading loop, a sleep can be performed after each reading 608 | This allows CPU to perform other tasks 609 | \return >=0 return the number of bytes read before timeout or 610 | requested data is completed 611 | \return -1 error while setting the Timeout 612 | \return -2 error while reading the byte 613 | */ 614 | int serialib::readBytes (void *buffer,unsigned int maxNbBytes,unsigned int timeOut_ms, unsigned int sleepDuration_us) 615 | { 616 | #if defined (_WIN32) || defined(_WIN64) 617 | // Avoid warning while compiling 618 | UNUSED(sleepDuration_us); 619 | 620 | // Number of bytes read 621 | DWORD dwBytesRead = 0; 622 | 623 | // Set the TimeOut 624 | timeouts.ReadTotalTimeoutConstant=(DWORD)timeOut_ms; 625 | 626 | // Write the parameters and return -1 if an error occrured 627 | if(!SetCommTimeouts(hSerial, &timeouts)) return -1; 628 | 629 | 630 | // Read the bytes from the serial device, return -2 if an error occured 631 | if(!ReadFile(hSerial,buffer,(DWORD)maxNbBytes,&dwBytesRead, NULL)) return -2; 632 | 633 | // Return the byte read 634 | return dwBytesRead; 635 | #endif 636 | #if defined (__linux__) || defined(__APPLE__) 637 | // Timer used for timeout 638 | timeOut timer; 639 | // Initialise the timer 640 | timer.initTimer(); 641 | unsigned int NbByteRead=0; 642 | // While Timeout is not reached 643 | while (timer.elapsedTime_ms()0) 654 | { 655 | // Increase the number of read bytes 656 | NbByteRead+=Ret; 657 | // Success : bytes has been read 658 | if (NbByteRead>=maxNbBytes) 659 | return NbByteRead; 660 | } 661 | // Suspend the loop to avoid charging the CPU 662 | usleep (sleepDuration_us); 663 | } 664 | // Timeout reached, return the number of bytes read 665 | return NbByteRead; 666 | #endif 667 | } 668 | 669 | 670 | 671 | 672 | // _________________________ 673 | // ::: Special operation ::: 674 | 675 | 676 | 677 | /*! 678 | \brief Empty receiver buffer 679 | \return If the function succeeds, the return value is nonzero. 680 | If the function fails, the return value is zero. 681 | */ 682 | char serialib::flushReceiver() 683 | { 684 | #if defined (_WIN32) || defined(_WIN64) 685 | // Purge receiver 686 | return PurgeComm (hSerial, PURGE_RXCLEAR); 687 | #endif 688 | #if defined (__linux__) || defined(__APPLE__) 689 | // Purge receiver 690 | tcflush(fd,TCIFLUSH); 691 | return true; 692 | #endif 693 | } 694 | 695 | 696 | 697 | /*! 698 | \brief Return the number of bytes in the received buffer (UNIX only) 699 | \return The number of bytes received by the serial provider but not yet read. 700 | */ 701 | int serialib::available() 702 | { 703 | #if defined (_WIN32) || defined(_WIN64) 704 | // Device errors 705 | DWORD commErrors; 706 | // Device status 707 | COMSTAT commStatus; 708 | // Read status 709 | ClearCommError(hSerial, &commErrors, &commStatus); 710 | // Return the number of pending bytes 711 | return commStatus.cbInQue; 712 | #endif 713 | #if defined (__linux__) || defined(__APPLE__) 714 | int nBytes=0; 715 | // Return number of pending bytes in the receiver 716 | ioctl(fd, FIONREAD, &nBytes); 717 | return nBytes; 718 | #endif 719 | 720 | } 721 | 722 | 723 | 724 | // __________________ 725 | // ::: I/O Access ::: 726 | 727 | /*! 728 | \brief Set or unset the bit DTR (pin 4) 729 | DTR stands for Data Terminal Ready 730 | Convenience method :This method calls setDTR and clearDTR 731 | \param status = true set DTR 732 | status = false unset DTR 733 | \return If the function fails, the return value is false 734 | If the function succeeds, the return value is true. 735 | */ 736 | bool serialib::DTR(bool status) 737 | { 738 | if (status) 739 | // Set DTR 740 | return this->setDTR(); 741 | else 742 | // Unset DTR 743 | return this->clearDTR(); 744 | } 745 | 746 | 747 | /*! 748 | \brief Set the bit DTR (pin 4) 749 | DTR stands for Data Terminal Ready 750 | \return If the function fails, the return value is false 751 | If the function succeeds, the return value is true. 752 | */ 753 | bool serialib::setDTR() 754 | { 755 | #if defined (_WIN32) || defined(_WIN64) 756 | // Set DTR 757 | currentStateDTR=true; 758 | return EscapeCommFunction(hSerial,SETDTR); 759 | #endif 760 | #if defined (__linux__) || defined(__APPLE__) 761 | // Set DTR 762 | int status_DTR=0; 763 | ioctl(fd, TIOCMGET, &status_DTR); 764 | status_DTR |= TIOCM_DTR; 765 | ioctl(fd, TIOCMSET, &status_DTR); 766 | return true; 767 | #endif 768 | } 769 | 770 | /*! 771 | \brief Clear the bit DTR (pin 4) 772 | DTR stands for Data Terminal Ready 773 | \return If the function fails, the return value is false 774 | If the function succeeds, the return value is true. 775 | */ 776 | bool serialib::clearDTR() 777 | { 778 | #if defined (_WIN32) || defined(_WIN64) 779 | // Clear DTR 780 | currentStateDTR=true; 781 | return EscapeCommFunction(hSerial,CLRDTR); 782 | #endif 783 | #if defined (__linux__) || defined(__APPLE__) 784 | // Clear DTR 785 | int status_DTR=0; 786 | ioctl(fd, TIOCMGET, &status_DTR); 787 | status_DTR &= ~TIOCM_DTR; 788 | ioctl(fd, TIOCMSET, &status_DTR); 789 | return true; 790 | #endif 791 | } 792 | 793 | 794 | 795 | /*! 796 | \brief Set or unset the bit RTS (pin 7) 797 | RTS stands for Data Termina Ready 798 | Convenience method :This method calls setDTR and clearDTR 799 | \param status = true set DTR 800 | status = false unset DTR 801 | \return false if the function fails 802 | \return true if the function succeeds 803 | */ 804 | bool serialib::RTS(bool status) 805 | { 806 | if (status) 807 | // Set RTS 808 | return this->setRTS(); 809 | else 810 | // Unset RTS 811 | return this->clearRTS(); 812 | } 813 | 814 | 815 | /*! 816 | \brief Set the bit RTS (pin 7) 817 | RTS stands for Data Terminal Ready 818 | \return If the function fails, the return value is false 819 | If the function succeeds, the return value is true. 820 | */ 821 | bool serialib::setRTS() 822 | { 823 | #if defined (_WIN32) || defined(_WIN64) 824 | // Set RTS 825 | currentStateRTS=false; 826 | return EscapeCommFunction(hSerial,SETRTS); 827 | #endif 828 | #if defined (__linux__) || defined(__APPLE__) 829 | // Set RTS 830 | int status_RTS=0; 831 | ioctl(fd, TIOCMGET, &status_RTS); 832 | status_RTS |= TIOCM_RTS; 833 | ioctl(fd, TIOCMSET, &status_RTS); 834 | return true; 835 | #endif 836 | } 837 | 838 | 839 | 840 | /*! 841 | \brief Clear the bit RTS (pin 7) 842 | RTS stands for Data Terminal Ready 843 | \return If the function fails, the return value is false 844 | If the function succeeds, the return value is true. 845 | */ 846 | bool serialib::clearRTS() 847 | { 848 | #if defined (_WIN32) || defined(_WIN64) 849 | // Clear RTS 850 | currentStateRTS=false; 851 | return EscapeCommFunction(hSerial,CLRRTS); 852 | #endif 853 | #if defined (__linux__) || defined(__APPLE__) 854 | // Clear RTS 855 | int status_RTS=0; 856 | ioctl(fd, TIOCMGET, &status_RTS); 857 | status_RTS &= ~TIOCM_RTS; 858 | ioctl(fd, TIOCMSET, &status_RTS); 859 | return true; 860 | #endif 861 | } 862 | 863 | 864 | 865 | 866 | /*! 867 | \brief Get the CTS's status (pin 8) 868 | CTS stands for Clear To Send 869 | \return Return true if CTS is set otherwise false 870 | */ 871 | bool serialib::isCTS() 872 | { 873 | #if defined (_WIN32) || defined(_WIN64) 874 | DWORD modemStat; 875 | GetCommModemStatus(hSerial, &modemStat); 876 | return modemStat & MS_CTS_ON; 877 | #endif 878 | #if defined (__linux__) || defined(__APPLE__) 879 | int status=0; 880 | //Get the current status of the CTS bit 881 | ioctl(fd, TIOCMGET, &status); 882 | return status & TIOCM_CTS; 883 | #endif 884 | } 885 | 886 | 887 | 888 | /*! 889 | \brief Get the DSR's status (pin 6) 890 | DSR stands for Data Set Ready 891 | \return Return true if DTR is set otherwise false 892 | */ 893 | bool serialib::isDSR() 894 | { 895 | #if defined (_WIN32) || defined(_WIN64) 896 | DWORD modemStat; 897 | GetCommModemStatus(hSerial, &modemStat); 898 | return modemStat & MS_DSR_ON; 899 | #endif 900 | #if defined (__linux__) || defined(__APPLE__) 901 | int status=0; 902 | //Get the current status of the DSR bit 903 | ioctl(fd, TIOCMGET, &status); 904 | return status & TIOCM_DSR; 905 | #endif 906 | } 907 | 908 | 909 | 910 | 911 | 912 | 913 | /*! 914 | \brief Get the DCD's status (pin 1) 915 | CDC stands for Data Carrier Detect 916 | \return true if DCD is set 917 | \return false otherwise 918 | */ 919 | bool serialib::isDCD() 920 | { 921 | #if defined (_WIN32) || defined(_WIN64) 922 | DWORD modemStat; 923 | GetCommModemStatus(hSerial, &modemStat); 924 | return modemStat & MS_RLSD_ON; 925 | #endif 926 | #if defined (__linux__) || defined(__APPLE__) 927 | int status=0; 928 | //Get the current status of the DCD bit 929 | ioctl(fd, TIOCMGET, &status); 930 | return status & TIOCM_CAR; 931 | #endif 932 | } 933 | 934 | 935 | /*! 936 | \brief Get the RING's status (pin 9) 937 | Ring Indicator 938 | \return Return true if RING is set otherwise false 939 | */ 940 | bool serialib::isRI() 941 | { 942 | #if defined (_WIN32) || defined(_WIN64) 943 | DWORD modemStat; 944 | GetCommModemStatus(hSerial, &modemStat); 945 | return modemStat & MS_RING_ON; 946 | #endif 947 | #if defined (__linux__) || defined(__APPLE__) 948 | int status=0; 949 | //Get the current status of the RING bit 950 | ioctl(fd, TIOCMGET, &status); 951 | return status & TIOCM_RNG; 952 | #endif 953 | } 954 | 955 | 956 | /*! 957 | \brief Get the DTR's status (pin 4) 958 | DTR stands for Data Terminal Ready 959 | May behave abnormally on Windows 960 | \return Return true if CTS is set otherwise false 961 | */ 962 | bool serialib::isDTR() 963 | { 964 | #if defined (_WIN32) || defined( _WIN64) 965 | return currentStateDTR; 966 | #endif 967 | #if defined (__linux__) || defined(__APPLE__) 968 | int status=0; 969 | //Get the current status of the DTR bit 970 | ioctl(fd, TIOCMGET, &status); 971 | return status & TIOCM_DTR ; 972 | #endif 973 | } 974 | 975 | 976 | 977 | /*! 978 | \brief Get the RTS's status (pin 7) 979 | RTS stands for Request To Send 980 | May behave abnormally on Windows 981 | \return Return true if RTS is set otherwise false 982 | */ 983 | bool serialib::isRTS() 984 | { 985 | #if defined (_WIN32) || defined(_WIN64) 986 | return currentStateRTS; 987 | #endif 988 | #if defined (__linux__) || defined(__APPLE__) 989 | int status=0; 990 | //Get the current status of the CTS bit 991 | ioctl(fd, TIOCMGET, &status); 992 | return status & TIOCM_RTS; 993 | #endif 994 | } 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | // ****************************************** 1002 | // Class timeOut 1003 | // ****************************************** 1004 | 1005 | 1006 | /*! 1007 | \brief Constructor of the class timeOut. 1008 | */ 1009 | // Constructor 1010 | timeOut::timeOut() 1011 | {} 1012 | 1013 | 1014 | /*! 1015 | \brief Initialise the timer. It writes the current time of the day in the structure PreviousTime. 1016 | */ 1017 | //Initialize the timer 1018 | void timeOut::initTimer() 1019 | { 1020 | #if defined (NO_POSIX_TIME) 1021 | LARGE_INTEGER tmp; 1022 | QueryPerformanceFrequency(&tmp); 1023 | counterFrequency = tmp.QuadPart; 1024 | // Used to store the previous time (for computing timeout) 1025 | QueryPerformanceCounter(&tmp); 1026 | previousTime = tmp.QuadPart; 1027 | #else 1028 | gettimeofday(&previousTime, NULL); 1029 | #endif 1030 | } 1031 | 1032 | /*! 1033 | \brief Returns the time elapsed since initialization. It write the current time of the day in the structure CurrentTime. 1034 | Then it returns the difference between CurrentTime and PreviousTime. 1035 | \return The number of microseconds elapsed since the functions InitTimer was called. 1036 | */ 1037 | //Return the elapsed time since initialization 1038 | unsigned long int timeOut::elapsedTime_ms() 1039 | { 1040 | #if defined (NO_POSIX_TIME) 1041 | // Current time 1042 | LARGE_INTEGER CurrentTime; 1043 | // Number of ticks since last call 1044 | int sec; 1045 | 1046 | // Get current time 1047 | QueryPerformanceCounter(&CurrentTime); 1048 | 1049 | // Compute the number of ticks elapsed since last call 1050 | sec=CurrentTime.QuadPart-previousTime; 1051 | 1052 | // Return the elapsed time in milliseconds 1053 | return sec/(counterFrequency/1000); 1054 | #else 1055 | // Current time 1056 | struct timeval CurrentTime; 1057 | // Number of seconds and microseconds since last call 1058 | int sec,usec; 1059 | 1060 | // Get current time 1061 | gettimeofday(&CurrentTime, NULL); 1062 | 1063 | // Compute the number of seconds and microseconds elapsed since last call 1064 | sec=CurrentTime.tv_sec-previousTime.tv_sec; 1065 | usec=CurrentTime.tv_usec-previousTime.tv_usec; 1066 | 1067 | // If the previous usec is higher than the current one 1068 | if (usec<0) 1069 | { 1070 | // Recompute the microseonds and substract one second 1071 | usec=1000000-previousTime.tv_usec+CurrentTime.tv_usec; 1072 | sec--; 1073 | } 1074 | 1075 | // Return the elapsed time in milliseconds 1076 | return sec*1000+usec/1000; 1077 | #endif 1078 | } 1079 | -------------------------------------------------------------------------------- /Plugin/misc_modules/arduino_controller/src/serialib.h: -------------------------------------------------------------------------------- 1 | /*! 2 | \file serialib.h 3 | \brief Header file of the class serialib. This class is used for communication over a serial device. 4 | \author Philippe Lucidarme (University of Angers) 5 | \version 2.0 6 | \date december the 27th of 2019 7 | This Serial library is used to communicate through serial port. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 11 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, 12 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 13 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | This is a licence-free software, it can be used by anyone who try to build a better world. 16 | */ 17 | 18 | 19 | #ifndef SERIALIB_H 20 | #define SERIALIB_H 21 | 22 | #if defined(__CYGWIN__) 23 | // This is Cygwin special case 24 | #include 25 | #endif 26 | 27 | // Include for windows 28 | #if defined (_WIN32) || defined (_WIN64) 29 | #if defined(__GNUC__) 30 | // This is MinGW special case 31 | #include 32 | #else 33 | // sys/time.h does not exist on "actual" Windows 34 | #define NO_POSIX_TIME 35 | #endif 36 | // Accessing to the serial port under Windows 37 | #include 38 | #endif 39 | 40 | // Include for Linux 41 | #if defined (__linux__) || defined(__APPLE__) 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | // File control definitions 50 | #include 51 | #include 52 | #include 53 | #endif 54 | 55 | /*! To avoid unused parameters */ 56 | #define UNUSED(x) (void)(x) 57 | 58 | /** 59 | * number of serial data bits 60 | */ 61 | enum SerialDataBits { 62 | SERIAL_DATABITS_5, /**< 5 databits */ 63 | SERIAL_DATABITS_6, /**< 6 databits */ 64 | SERIAL_DATABITS_7, /**< 7 databits */ 65 | SERIAL_DATABITS_8, /**< 8 databits */ 66 | SERIAL_DATABITS_16, /**< 16 databits */ 67 | }; 68 | 69 | /** 70 | * number of serial stop bits 71 | */ 72 | enum SerialStopBits { 73 | SERIAL_STOPBITS_1, /**< 1 stop bit */ 74 | SERIAL_STOPBITS_1_5, /**< 1.5 stop bits */ 75 | SERIAL_STOPBITS_2, /**< 2 stop bits */ 76 | }; 77 | 78 | /** 79 | * type of serial parity bits 80 | */ 81 | enum SerialParity { 82 | SERIAL_PARITY_NONE, /**< no parity bit */ 83 | SERIAL_PARITY_EVEN, /**< even parity bit */ 84 | SERIAL_PARITY_ODD, /**< odd parity bit */ 85 | SERIAL_PARITY_MARK, /**< mark parity */ 86 | SERIAL_PARITY_SPACE /**< space bit */ 87 | }; 88 | 89 | /*! \class serialib 90 | \brief This class is used for communication over a serial device. 91 | */ 92 | class serialib 93 | { 94 | public: 95 | 96 | //_____________________________________ 97 | // ::: Constructors and destructors ::: 98 | 99 | 100 | 101 | // Constructor of the class 102 | serialib (); 103 | 104 | // Destructor 105 | ~serialib (); 106 | 107 | 108 | 109 | //_________________________________________ 110 | // ::: Configuration and initialization ::: 111 | 112 | 113 | // Open a device 114 | char openDevice(const char *Device, const unsigned int Bauds, 115 | SerialDataBits Databits = SERIAL_DATABITS_8, 116 | SerialParity Parity = SERIAL_PARITY_NONE, 117 | SerialStopBits Stopbits = SERIAL_STOPBITS_1); 118 | 119 | // Check device opening state 120 | bool isDeviceOpen(); 121 | 122 | // Close the current device 123 | void closeDevice(); 124 | 125 | 126 | 127 | 128 | //___________________________________________ 129 | // ::: Read/Write operation on characters ::: 130 | 131 | 132 | // Write a char 133 | char writeChar (char); 134 | 135 | // Read a char (with timeout) 136 | char readChar (char *pByte,const unsigned int timeOut_ms=0); 137 | 138 | 139 | 140 | 141 | //________________________________________ 142 | // ::: Read/Write operation on strings ::: 143 | 144 | 145 | // Write a string 146 | char writeString (const char *String); 147 | 148 | // Read a string (with timeout) 149 | int readString ( char *receivedString, 150 | char finalChar, 151 | unsigned int maxNbBytes, 152 | const unsigned int timeOut_ms=0); 153 | 154 | 155 | 156 | // _____________________________________ 157 | // ::: Read/Write operation on bytes ::: 158 | 159 | 160 | // Write an array of bytes 161 | char writeBytes (const void *Buffer, const unsigned int NbBytes); 162 | 163 | // Read an array of byte (with timeout) 164 | int readBytes (void *buffer,unsigned int maxNbBytes,const unsigned int timeOut_ms=0, unsigned int sleepDuration_us=100); 165 | 166 | 167 | 168 | 169 | // _________________________ 170 | // ::: Special operation ::: 171 | 172 | 173 | // Empty the received buffer 174 | char flushReceiver(); 175 | 176 | // Return the number of bytes in the received buffer 177 | int available(); 178 | 179 | 180 | 181 | 182 | // _________________________ 183 | // ::: Access to IO bits ::: 184 | 185 | 186 | // Set CTR status (Data Terminal Ready, pin 4) 187 | bool DTR(bool status); 188 | bool setDTR(); 189 | bool clearDTR(); 190 | 191 | // Set RTS status (Request To Send, pin 7) 192 | bool RTS(bool status); 193 | bool setRTS(); 194 | bool clearRTS(); 195 | 196 | // Get RI status (Ring Indicator, pin 9) 197 | bool isRI(); 198 | 199 | // Get DCD status (Data Carrier Detect, pin 1) 200 | bool isDCD(); 201 | 202 | // Get CTS status (Clear To Send, pin 8) 203 | bool isCTS(); 204 | 205 | // Get DSR status (Data Set Ready, pin 9) 206 | bool isDSR(); 207 | 208 | // Get RTS status (Request To Send, pin 7) 209 | bool isRTS(); 210 | 211 | // Get CTR status (Data Terminal Ready, pin 4) 212 | bool isDTR(); 213 | 214 | 215 | private: 216 | // Read a string (no timeout) 217 | int readStringNoTimeOut (char *String,char FinalChar,unsigned int MaxNbBytes); 218 | 219 | // Current DTR and RTS state (can't be read on WIndows) 220 | bool currentStateRTS; 221 | bool currentStateDTR; 222 | 223 | 224 | 225 | 226 | 227 | #if defined (_WIN32) || defined( _WIN64) 228 | // Handle on serial device 229 | HANDLE hSerial; 230 | // For setting serial port timeouts 231 | COMMTIMEOUTS timeouts; 232 | #endif 233 | #if defined (__linux__) || defined(__APPLE__) 234 | int fd; 235 | #endif 236 | 237 | }; 238 | 239 | 240 | 241 | /*! \class timeOut 242 | \brief This class can manage a timer which is used as a timeout. 243 | */ 244 | // Class timeOut 245 | class timeOut 246 | { 247 | public: 248 | 249 | // Constructor 250 | timeOut(); 251 | 252 | // Init the timer 253 | void initTimer(); 254 | 255 | // Return the elapsed time since initialization 256 | unsigned long int elapsedTime_ms(); 257 | 258 | private: 259 | #if defined (NO_POSIX_TIME) 260 | // Used to store the previous time (for computing timeout) 261 | LONGLONG counterFrequency; 262 | LONGLONG previousTime; 263 | #else 264 | // Used to store the previous time (for computing timeout) 265 | struct timeval previousTime; 266 | #endif 267 | }; 268 | 269 | #endif // serialib_H 270 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sdrppcontroller 2 | ## Arduino SDR++ Controller 3 | ### Arduino sketch + Linux and Windows plugin 4 | 5 | [![VIDEO](https://img.youtube.com/vi/_txrEIK9pqs/0.jpg)](https://youtu.be/_txrEIK9pqs) 6 | _Youtube video_ 7 | 8 | ## Introduction 9 | I was inspired by a question asked on RTL-SDR Facebook group. Although this question was 10 | about controlling SDR# with Arduino I decided to give a try with SDR++. I took a look at 11 | rig_ctrl plugin, but it was missing some gui functionalities. Also this plugin was 12 | designed to controll the software via network and its purpose in general was quite different 13 | from what I wanted to achieve. That's why I decided to try something by my own. 14 | Parts of the code were taken from Botland store and DRFrobot wiki. Serial initialization was 15 | googled and found in some Stackoverlow answer (hopefully). I just glued those pieces toghether. 16 | Please note: I'm not a software developer and I'm not a very big fan of C++. Yet, I manged to 17 | develop something functional,so here it is: "Arduino SDR++ Controller" 18 | 19 | ## Environment 20 | * [SDR++](https://github.com/AlexandreRouma/SDRPlusPlus) v1.0.6 21 | * [Ubuntu](https://ubuntu.com/) Linux 20.04 22 | * [Arduino UNO](https://www.arduino.cc/en/main/arduinoBoardUno) 23 | * [Arduino Leonardo](https://www.arduino.cc/en/main/arduinoBoardLeonardo) - preferred 24 | * DFRobot DFR0009 [LCD Keypad Shield](https://wiki.dfrobot.com/LCD_KeyPad_Shield_For_Arduino_SKU__DFR0009) 25 | * 2 * DFRobot [EC11 rotary encoders](https://wiki.dfrobot.com/EC11_Rotary_Encoder_Module_SKU__SEN0235) 26 | 27 | ## Building 28 | In order to build the plugin please follow the instructions from SDR++ README file. Use Arduino IDE to compile the sketch and upload to the board. 29 | * _Arduino/controller_uno/controller_uno.ino_ - Arduino UNO code 30 | * _Arduino/controller_leonardo/controller_leonardo.ino_ - Arduino Leonardo code 31 | * _Plugin/misc_modules/arduino_controller/src/main.cpp_ - plugin code 32 | 33 | ## Wiring for Arduino UNO 34 | 35 | Rotary encoders are connected to digitial pins: 36 | 37 | ### "Left" knob: 38 | - Hole A -> Pin 13 - left turn 39 | - Hole B -> Pin 12 - right turn 40 | - Hole C -> Pin 11 - button 41 | 42 | ### "Right" knob: 43 | - Hole A -> Pin 3 - left turn 44 | - Hole B -> Pin 2 - right turn 45 | - Hole C -> Unused - because the shield was out of digital pins 46 | 47 | ## Wiring for Arduino Leonardo 48 | 49 | Rotary encoders are connected to digitial pins: 50 | 51 | ### "Left" knob: 52 | - Hole A -> Pin 2 - left turn 53 | - Hole B -> Pin 3 - right turn 54 | - Hole C -> Pin 11 - button 55 | 56 | ### "Right" knob: 57 | - Hole A -> Pin 0 - left turn 58 | - Hole B -> Pin 1 - right turn 59 | - Hole C -> Unused (for now) 60 | 61 | 62 | ## Connecting to PC 63 | Connect Arduino with standard USB cable. It should be registered as a serial device, for example: `/dev/ttyACM0`. 64 | 65 | ## LCD Keys mapping 66 | - Left , Right - Waterfall Zoom In / Zoom Out 67 | - Up , Down - Tune with a step equals to device sample rate. 68 | - Select - Cycle demodulators. 69 | - Left knob - Tune with a step equals to "Snap interval" multiplied by 10. 70 | - Right knob - Tune with a step equals to "Snap interval". 71 | - Knob button - Center waterfall. 72 | 73 | ## Usage 74 | Enable the plugin with "Module Manager". Setup serial port (default: _/dev/ttyACM0_) and click "Start" button. 75 | 76 | ## License 77 | SDR++ is GPL3.0 so if someone decides to include this plugin within SDR++ then the same 78 | license should be applied. Keep in mind that I used some code snippets from Botland store and DFRobot wiki. 79 | I didn't notice any licenses there. So, to conclude: I really do not care. Take this code and Do whatever you want. 80 | 81 | ## Where to buy? 82 | I bought everything at [Botland](https://www.botland.store/). 83 | - [Arduino UNO](https://botland.com.pl/arduino-seria-podstawowa-oryginalne-plytki/1060-arduino-uno-rev3-a000066-7630049200050.html) 84 | - [Rotary encoder](https://botland.com.pl/enkodery/9533-czujnik-obrotu-impulsator-enkoder-obrotowy-dfrobot-ec11-5904422366674.html) 85 | - [LCD Keypad shield](https://botland.com.pl/arduino-shield-klawiatury-i-wyswietlacze/2729-dfrobot-lcd-keypad-shield-v11-wyswietlacz-dla-arduino-5903351243780.html) 86 | - [Case](https://botland.com.pl/obudowy-do-arduino/19020-obudowa-do-arduino-uno-z-lcd-keypad-shield-v11-czarno-przezroczysta-5904422362942.html) 87 | 88 | 89 | ## Multiplatform Serialib 90 | Multiplatform serial communication done with [Serialib](https://github.com/imabot2/serialib) by Philippe Lucidarme. Accoriding to author this library is license-free. 91 | 92 | ## Final words 93 | There is a lot of space for improments. For example I couldn't use interrupts for knobs because Arduino UNO has only two of them and I needed four. I read 94 | digital pins in a loop which is not the best practice I suppose. This issue doesn't affect Arduino Leonardo. Feel free to contribute. 95 | Greetings go to [RTL-SDR Polska](https://www.facebook.com/groups/2628926590655863) Facebook group and specially to Kacper for inspiring me! 96 | --------------------------------------------------------------------------------