├── arduino+linux ├── readme.txt ├── rf22_pocsag.ino └── sendpoctxt.c ├── arduino ├── readme.txt └── rf22_pocsag_arduino.ino └── readme.txt /arduino+linux/readme.txt: -------------------------------------------------------------------------------- 1 | POCSAG via si4432-based ISM modules 2 | ----------------------------------- 3 | 4 | 5 | This application-set is designed to send 512 bps POCSAG-messages 6 | using si4432-based ISM transceivers. 7 | 8 | It uses a linux device to generate the POCSAG (alphanumeric) messages 9 | and a arduino to interface to the si4432 chipset. 10 | 11 | 12 | The package uses two applications: 13 | - sendpoctxt.c: 14 | C application that creates the POCSAG-message and sends the 15 | message (plus callsign and configuration data) over serial port 16 | to the arduino 17 | 18 | 19 | - rf22_pocsag.ino: 20 | Arduino application that receives POCSAG-message from the serial 21 | port and transmits it via the si4432 module. 22 | 23 | rf22_pocsag.ino uses the "arduino rf22"-library found at 24 | http://www.airspayce.com/mikem/arduino/RF22/ 25 | 26 | 27 | 28 | As POCSAG-message natively do not contain an identification of the 29 | transmitting station, a small FSK-based and a FM-modulated CW 30 | identification can be added to the transmission. 31 | 32 | 33 | 34 | Compile / install: 35 | gcc -Wall -o sendpoctxt sendpoctxt.c 36 | 37 | 38 | connect arduino to serial port of linux board (/dev/ttyAMA0) 39 | 40 | ./sendpoctxt MYCALL
"POCSAG is good for you" 41 | 42 | 43 | 44 | 45 | These programs are free software; you can redistribute it and/or 46 | modify it under the terms of the GNU General Public License as 47 | published by the Free Software Foundation; either version 2 of the 48 | License, or (at your option) any later version. 49 | 50 | 51 | 52 | Version 0.1.0 (20140330) initial version 53 | -------------------------------------------------------------------------------- /arduino+linux/rf22_pocsag.ino: -------------------------------------------------------------------------------- 1 | // rf22_sendpocsag 2 | 3 | // receive POCSAG-message + callsign + configuration data from serial port (raspberry pi) 4 | // send message on 439.9875 Mhz using si4432-based "RF22" modules 5 | 6 | 7 | // this program uses arduino rf22 libraries: 8 | // http://www.airspayce.com/mikem/arduino/RF22/ 9 | 10 | // (c) 2014 Kristoff Bonne (ON1ARF) 11 | 12 | /* 13 | * This program is free software; you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public License as 15 | * published by the Free Software Foundation; either version 2 of the 16 | * License, or (at your option) any later version. 17 | * 18 | * This application is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | * General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program; if not, write to the Free Software 25 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 26 | * 27 | */ 28 | 29 | // Version 0.1.0 (20140330) Initial release 30 | 31 | 32 | // increase maximum packet site 33 | #define RF22_MAX_MESSAGE_LEN 255 34 | 35 | #define MAXSYNCFAIL 40 36 | 37 | #define CWID_SHORT 33 // 106 ms @ 1200 bps 38 | #define CWID_LONG 90 // 320ms @ 1200 bps 39 | 40 | #include 41 | #include 42 | 43 | // Singleton instance of the radio 44 | RF22 rf22; 45 | 46 | uint8_t pocsagmsg[208]; // 208 = 1 sync-patterns (576 bits/72 octets) + 47 | // 2 batches (544 bits/68 octets) 48 | 49 | uint8_t synctxt[6]; // contains either "POCGO1" or "POCEND" 50 | 51 | uint8_t call[8]; // callsign 52 | uint8_t config[6]; // 6 octets of configuration data 53 | // currently used: config[0]:0 send/do not send FSK ID 54 | // config[0]:1 send/do not send CW ID 55 | 56 | 57 | RF22::ModemConfig MC; 58 | 59 | 60 | uint8_t cwidmsg[CWID_LONG]; 61 | 62 | 63 | // data-structure for cwid 64 | struct t_mtab { 65 | char c; 66 | int pat; 67 | }; 68 | 69 | // table morse chars, read from left to right, ignore first bit (used to determine size) 70 | struct t_mtab morsetab[] = { 71 | {'.', 106}, 72 | {',', 115}, 73 | {'?', 76}, 74 | {'/', 41}, 75 | {'A', 6}, 76 | {'B', 17}, 77 | {'C', 21}, 78 | {'D', 9}, 79 | {'E', 2}, 80 | {'F', 20}, 81 | {'G', 11}, 82 | {'H', 16}, 83 | {'I', 4}, 84 | {'J', 30}, 85 | {'K', 13}, 86 | {'L', 18}, 87 | {'M', 7}, 88 | {'N', 5}, 89 | {'O', 15}, 90 | {'P', 22}, 91 | {'Q', 27}, 92 | {'R', 10}, 93 | {'S', 8}, 94 | {'T', 3}, 95 | {'U', 12}, 96 | {'V', 24}, 97 | {'W', 14}, 98 | {'X', 25}, 99 | {'Y', 29}, 100 | {'Z', 19}, 101 | {'1', 62}, 102 | {'2', 60}, 103 | {'3', 56}, 104 | {'4', 48}, 105 | {'5', 32}, 106 | {'6', 33}, 107 | {'7', 35}, 108 | {'8', 39}, 109 | {'9', 47}, 110 | {'0', 63}, 111 | {' ', 0} 112 | } ; 113 | 114 | 115 | // functions defined below 116 | void sendcwid(unsigned char * idtxt); 117 | 118 | 119 | 120 | void setup() 121 | { 122 | 123 | Serial.begin(9600); 124 | if (rf22.init()) { 125 | Serial.println("RF22 init OK"); 126 | } else { 127 | Serial.println("RF22 init FAILED"); 128 | }; // end if 129 | 130 | // rf22.setFrequency(439.9875, 0.00); 131 | rf22.setFrequency(439.9625, 0.00); // for some reason, I need to tune to 439.9625 to transmit on 439.9875 Mhz 132 | 133 | 134 | // 512 baud, FSK, 4.5 Khz fd 135 | MC.reg_6e=0x04; MC.reg_6f=0x32; MC.reg_70=0x2c; MC.reg_58=0x80; 136 | MC.reg_72=0x04; MC.reg_71=0x22; 137 | 138 | MC.reg_1c=0x2B; MC.reg_20=0xa1; MC.reg_21=0xE0; MC.reg_22=0x10; 139 | MC.reg_23=0xc7; MC.reg_24=0x00; MC.reg_25=0x09; MC.reg_1f=0x03; 140 | MC.reg_69=0x60; 141 | 142 | // MC.reg_1d=0x3c; MC.reg_1e=0x02; MC.reg_2a=0xff; 143 | 144 | rf22.setModemRegisters(&MC); 145 | 146 | 147 | // 100 mW TX power 148 | rf22.setTxPower(RF22_TXPOW_20DBM); 149 | 150 | 151 | // create packet 152 | // first 576 bits are sync pattern 153 | memset(&pocsagmsg[0],0x55,72); // pattern 0x55 is invers of 0xaa 154 | 155 | 156 | // both batches begin with sync codewords 157 | // 0111 1100 1101 0010 0001 0101 1101 1000 158 | pocsagmsg[72]=0x7c; pocsagmsg[73]=0xd2; 159 | pocsagmsg[74]=0x15; pocsagmsg[75]=0xd8; 160 | 161 | pocsagmsg[140]=0x7c; pocsagmsg[141]=0xd2; 162 | pocsagmsg[142]=0x15; pocsagmsg[143]=0xd8; 163 | 164 | // invert bits 165 | pocsagmsg[72] ^= 0xff; pocsagmsg[73] ^= 0xff; 166 | pocsagmsg[74] ^= 0xff; pocsagmsg[75] ^= 0xff; 167 | 168 | pocsagmsg[140] ^= 0xff; pocsagmsg[141] ^= 0xff; 169 | pocsagmsg[142] ^= 0xff; pocsagmsg[143] ^= 0xff; 170 | 171 | 172 | // the rest of the two batches are read from the serial port 173 | // see loop function below 174 | 175 | // message = 0b01010101 176 | memset(cwidmsg,0x55,CWID_LONG); 177 | 178 | } 179 | 180 | void loop() 181 | { 182 | //vars 183 | int syncfail; 184 | 185 | 186 | 187 | // print "READY" message 188 | Serial.println("POCSAG READY"); 189 | 190 | 191 | 192 | // fake loop. allows breaking out on error 193 | while (1) { 194 | int l; // loop 195 | 196 | // part 1: receive 6 bytes from the serial port 197 | // to fill up 198 | 199 | for (l=0; l < 6; l++) { 200 | // loop until data on serial port 201 | while (Serial.available() == 0) { 202 | }; // end empty loop 203 | 204 | synctxt[l]=Serial.read(); 205 | }; // end for 206 | 207 | 208 | syncfail=0; 209 | // loop until we receive the text "POCBEG" 210 | while (! ((synctxt[0] == 'P') && (synctxt[1] == 'O') && 211 | (synctxt[2] == 'C') && (synctxt[3] == 'G') && 212 | (synctxt[4] == 'O') && (synctxt[5] == '1'))) { 213 | syncfail++; 214 | 215 | // break out if more then MAXSYNCFAIL sync-failed 216 | if (syncfail > MAXSYNCFAIL) { 217 | break; 218 | }; // end if 219 | 220 | // move up char, and get next char 221 | synctxt[0]=synctxt[1]; synctxt[1]=synctxt[2]; 222 | synctxt[2]=synctxt[3]; synctxt[3]=synctxt[4]; 223 | synctxt[4]=synctxt[5]; 224 | 225 | // get next char from serial port 226 | while (Serial.available() == 0) { 227 | }; // end empty loop 228 | 229 | synctxt[5]=Serial.read(); 230 | 231 | }; // end while (sync pattern) 232 | 233 | // completely break out after too many syncfail 234 | if (syncfail > MAXSYNCFAIL) { 235 | Serial.println("POCSAG SYNCFAILED"); 236 | break; 237 | }; // end if 238 | 239 | Serial.println("POCSAG START"); 240 | 241 | Serial.println("POCSAG READ BATCH1"); 242 | 243 | // part 2a, get 16 * 2 codewords (64 octets) for 1st batch 244 | for (l=76; l < 140; l++) { 245 | // get char from serial port 246 | while (Serial.available() == 0) { 247 | }; // end empty loop 248 | 249 | pocsagmsg[l]=Serial.read(); 250 | // invert bits 251 | pocsagmsg[l] ^= 0xff; 252 | }; // end for 253 | 254 | Serial.println("POCSAG READ BATCH2"); 255 | 256 | // part 2b, get 16 * 2 codewords (64 octets) for 2nd batch 257 | for (l=144; l < 208; l++) { 258 | // get char from serial port 259 | while (Serial.available() == 0) { 260 | }; // end empty loop 261 | 262 | pocsagmsg[l]=Serial.read(); 263 | 264 | // invert bits 265 | pocsagmsg[l] ^= 0xff; 266 | }; // end for 267 | 268 | 269 | Serial.println("POCSAG READ CALL"); 270 | 271 | // part 2c: read callsign (8 chars) 272 | for (l=0; l < 8; l++) { 273 | // get char from serial port 274 | while (Serial.available() == 0) { 275 | }; // end empty loop 276 | 277 | call[l]=Serial.read(); 278 | }; // end for 279 | 280 | 281 | Serial.println("POCSAG READ CONFIG"); 282 | 283 | // part 2d: get config data (6 octets) 284 | for (l=0; l < 6; l++) { 285 | // get char from serial port 286 | while (Serial.available() == 0) { 287 | }; // end empty loop 288 | 289 | config[l]=Serial.read(); 290 | }; // end for 291 | 292 | 293 | Serial.println("POCSAG READ ENDMARKER"); 294 | 295 | // part 3: get end sync message (6 octets) 296 | for (l=0; l < 6; l++) { 297 | // get char from serial port 298 | while (Serial.available() == 0) { 299 | }; // end empty loop 300 | 301 | synctxt[l]=Serial.read(); 302 | }; // end for 303 | 304 | 305 | Serial.println("POCSAG ALLREAD"); 306 | 307 | 308 | // do we have a correct end sync ("POCEND") 309 | if ((synctxt[0] == 'P') && (synctxt[1] == 'O') && 310 | (synctxt[2] == 'C') && (synctxt[3] == 'E') && 311 | (synctxt[4] == 'N') && (synctxt[5] == 'D')) { 312 | Serial.println("POCSAG END"); 313 | 314 | 315 | // FSK ID. Just a FSK packet with your callsign 316 | // Fixed-length: 8 chars 317 | 318 | if (config[0] & 0x01) { 319 | Serial.println("POCSAG ID-FSK"); 320 | 321 | rf22.send(call, 8); 322 | rf22.waitPacketSent(); 323 | 324 | delay(500); 325 | }; // end if 326 | 327 | 328 | // send POCSAG message 3 times 329 | for (l=0; l < 3; l++) { 330 | Serial.println("POCSAG SEND"); 331 | 332 | rf22.send(pocsagmsg, 208); 333 | rf22.waitPacketSent(); 334 | 335 | delay(3000); 336 | }; // end for 337 | 338 | 339 | 340 | if (config[0] & 0x02) { 341 | // send CW ID: switch to 2000 bps GMSK and send short or long 342 | // dummy packets to create FM-modulated CW 343 | 344 | // switch to 2000 bps GMSK 345 | rf22.setModemConfig(RF22::GFSK_Rb2Fd5); 346 | 347 | 348 | sendcwid(call); 349 | 350 | // switch back to 512 bps FSK 351 | rf22.setModemRegisters(&MC); 352 | }; // end if 353 | 354 | 355 | 356 | 357 | } else { 358 | Serial.println("POCSAG INVALID"); 359 | }; // end else - if 360 | 361 | // done, break out of fake endless loop 362 | break; 363 | }; // end fake endless loop 364 | 365 | }; // end main function 366 | 367 | 368 | ////////////////////////////////////////////////////////////// 369 | // //////////////// 370 | // End of main function 371 | /////// 372 | 373 | 374 | 375 | void sendcwid (unsigned char *msg) { 376 | int l,l2; 377 | int n_morse; 378 | 379 | n_morse=sizeof(morsetab) / sizeof(morsetab[0]); 380 | 381 | // the id message should be 8 char long. 382 | // some more validity checks to be added later 383 | 384 | 385 | for (l=0; l < 8; l++) { 386 | 387 | char c; 388 | int m; 389 | int state; 390 | 391 | // init as invalid 392 | c='?'; m=0; 393 | 394 | for (l2=0; l2>= 1; 437 | }; // end for (morse bits) 438 | }; // end else - if (space ?) 439 | 440 | // wait for 350 ms between letters 441 | delay(350); 442 | 443 | }; // end for 444 | 445 | 446 | }; // end function cwidmsg 447 | -------------------------------------------------------------------------------- /arduino+linux/sendpoctxt.c: -------------------------------------------------------------------------------- 1 | // SENDPOCSAG TEXT-message version 2 | // creates pocsag message and send the 2 batches over the serial port 3 | // to the atmega to play out 4 | 5 | 6 | // (c) 2014 Kristoff Bonne (ON1ARF) 7 | 8 | /* 9 | * This program is free software; you can redistribute it and/or 10 | * modify it under the terms of the GNU General Public License as 11 | * published by the Free Software Foundation; either version 2 of the 12 | * License, or (at your option) any later version. 13 | * 14 | * This application is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | * General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program; if not, write to the Free Software 21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22 | * 23 | */ 24 | 25 | // Version 0.1.0 (20140330) Initial release 26 | 27 | 28 | 29 | 30 | // for write 31 | #include 32 | 33 | // for open 34 | #include 35 | #include 36 | #include 37 | 38 | // for memset 39 | #include 40 | 41 | // term io: serial port 42 | #include 43 | 44 | // for EXIT 45 | #include 46 | #include 47 | 48 | // for errno 49 | #include 50 | 51 | // for flock 52 | #include 53 | 54 | // for uint32_t 55 | #include 56 | 57 | 58 | 59 | // functions defined below 60 | uint32_t createcrc (uint32_t); 61 | void replaceline (int, uint32_t); 62 | unsigned char invertbits (unsigned char); 63 | 64 | 65 | // some constants 66 | const unsigned char size2mask[7]={ 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f }; 67 | 68 | 69 | // global data (shared between functions) 70 | // memory to store two pocsag batches 71 | uint32_t batch1[16]; 72 | uint32_t batch2[16]; 73 | 74 | 75 | 76 | 77 | /////////////////////////////////////////// 78 | ///////// MAIN APPLICATION 79 | ///// 80 | 81 | 82 | int main (int argc, char ** argv) { 83 | 84 | unsigned char txtin[41]; // text can be up to 40 chars, add one for EOT 85 | unsigned char txtcoded[42]; // coded version of text message (reserve 42 chars, a multiple of 3) 86 | 87 | int txtlen; 88 | int txtcodedlen; 89 | 90 | int address; 91 | int addresssource; 92 | 93 | 94 | 95 | 96 | 97 | // serial device 98 | // termios: structure to configure serial port 99 | struct termios tio; 100 | char * serialdevice = "/dev/ttyAMA0"; 101 | int serialfd; 102 | 103 | 104 | // input to read 105 | char buffin[64]; 106 | 107 | char * pocgo1 = "POCGO1"; 108 | char * pocend = "POCEND"; 109 | 110 | 111 | char callsign[9]; // callsign: up to 8 chars + null 112 | unsigned char config[6]; 113 | // configuration currently used: 114 | // config[0]:0 send FSK id 115 | // config[0]:1 send CW id 116 | 117 | // other vars 118 | int ret; 119 | int l; // loop 120 | 121 | int currentframe; 122 | int addressline; 123 | 124 | 125 | // parse parameters 126 | if (argc < 5) { 127 | fprintf(stderr,"Missing paramter: need at least 4 parameters: callsign, address, address-source, text\n"); 128 | exit(-1); 129 | }; // end if 130 | 131 | // copy up to 40 chars 132 | txtlen=strlen(argv[4]); 133 | 134 | // max 40 chars 135 | if (txtlen > 40) { 136 | txtlen=40; 137 | }; // end if 138 | 139 | // copy text 140 | memcpy(txtin,argv[4],txtlen); 141 | 142 | // replace terminating \0 by EOT 143 | txtin[txtlen]=0x04; // EOT (end of transmission) 144 | txtlen++; // increase size by 1 (for EOT) 145 | 146 | 147 | 148 | address = atoi(argv[2]); 149 | 150 | // sanity check: address is 21 bits 151 | if ((address >= (2^21)) || (address <= 0)) { 152 | fprintf(stderr,"Error: invalid address, should be between 1 and (2^21)-1, got %d\n",address); 153 | exit(-1); 154 | }; // end if 155 | 156 | 157 | 158 | 159 | addresssource = atoi(argv[3]); 160 | 161 | // sanity check: address is 2 bits 162 | if ((addresssource > 3) || (address < 0)) { 163 | fprintf(stderr,"Error: invalid address-source, should be between 0 and 3, got %d\n",addresssource); 164 | exit(-1); 165 | }; // end if 166 | 167 | 168 | 169 | // init config 170 | memset(config,0x0,6); 171 | config[0] |= 0x03; 172 | 173 | // copy callsign from CLI param 1, up to 8 chars allows 174 | memset(callsign,0x00,9); // init 175 | strncpy(callsign,argv[1],8); 176 | 177 | 178 | serialfd=open(serialdevice, O_RDWR | O_NONBLOCK); 179 | 180 | if (serialfd == 0) { 181 | fprintf(stderr,"Error: could not open serial device %s \n",serialdevice); 182 | }; // end if 183 | 184 | // make exclusive lock 185 | ret=flock(serialfd,LOCK_EX | LOCK_NB); 186 | 187 | if (ret != 0) { 188 | int thiserror=errno; 189 | 190 | fprintf(stderr,"Exclusive lock to serial port failed. (%s) \n",strerror(thiserror)); 191 | fprintf(stderr,"Serial device not found or already in use\n"); 192 | exit(-1); 193 | }; // end if 194 | 195 | // Serial port programming for Linux, see termios.h for more info 196 | memset(&tio,0,sizeof(tio)); 197 | tio.c_iflag=0; tio.c_oflag=0; tio.c_cflag=CS8|CREAD|CLOCAL; // 8n1 198 | tio.c_lflag=0; tio.c_cc[VMIN]=1; tio.c_cc[VTIME]=0; 199 | 200 | cfsetospeed(&tio,B9600); // 9.6 Kbps 201 | cfsetispeed(&tio,B9600); // 9.6 Kbps 202 | tcsetattr(serialfd,TCSANOW,&tio); 203 | 204 | 205 | // create packet 206 | // first fill up batch 1 with all idle 207 | 208 | for (l=0; l< 16; l++) { 209 | batch1[l]=0x7a89c197; 210 | batch2[l]=0x7a89c197; 211 | }; // end for 212 | 213 | 214 | 215 | // Convert text message 216 | // read from txtin, store in txtcoded 217 | 218 | { 219 | // local vars 220 | 221 | int stop; 222 | 223 | int bitcount_in, bitcount_out; 224 | int bytecount_in, bytecount_out; 225 | 226 | unsigned char c_in; // input char being processed 227 | unsigned char t; // tempory var 228 | 229 | 230 | // init some vars 231 | // clear all txtcodec 232 | for (l=0; l<42; l++) { 233 | txtcoded[l]=0x00; 234 | }; // end for 235 | 236 | // init bitcount_out at 7 (instead of normally 8) as we will fill up the first char with a leftmost 1 237 | bitcount_out=7; 238 | bytecount_out=0; 239 | 240 | bitcount_in=7; 241 | bytecount_in=0; 242 | 243 | // character is first char (from left) 244 | c_in=invertbits(txtin[bytecount_in]); 245 | 246 | txtcodedlen=0; 247 | txtcoded[0] = 0x80; // output, initialise as empty with leftmost bit="1" 248 | 249 | // loop for all data 250 | stop=0; 251 | 252 | while (stop == 0) { 253 | int bit2copy; 254 | 255 | 256 | // how many bits to copy? 257 | // look for smallest available 258 | if (bitcount_in > bitcount_out) { 259 | bit2copy = bitcount_out; 260 | } else { 261 | bit2copy = bitcount_in; 262 | }; // end if 263 | 264 | // maximum is 7 265 | if (bit2copy > 7) { 266 | bit2copy = 7; 267 | }; // end if 268 | 269 | // read input, copy "x" bits, shifted to left if needed 270 | t = c_in & (size2mask[bit2copy-1] << (bitcount_in - bit2copy) ); 271 | 272 | // where to place ? 273 | // move left to write if needed 274 | if (bitcount_in > bitcount_out) { 275 | // move to right and export 276 | t >>= (bitcount_in-bitcount_out); 277 | } else if (bitcount_in < bitcount_out) { 278 | // move to left 279 | t <<= (bitcount_out-bitcount_in); 280 | }; // end else - if 281 | 282 | // insert data in egress stream 283 | txtcoded[txtcodedlen] |= t; 284 | 285 | // decrease bit_counters 286 | bitcount_in -= bit2copy; 287 | bitcount_out -= bit2copy; 288 | 289 | // sanity checks 290 | if (bitcount_in < 0) { 291 | fprintf(stderr,"Error: negative bitcount_in: %d \n",bitcount_in); 292 | exit(-1); 293 | }; // end if 294 | 295 | 296 | if (bitcount_out < 0) { 297 | fprintf(stderr,"Error: negative bitcount_out: %d \n",bitcount_out); 298 | exit(-1); 299 | }; // end if 300 | 301 | 302 | // flush output if end reached 303 | if (bitcount_out == 0) { 304 | bytecount_out++; 305 | txtcodedlen++; 306 | 307 | // data is stored in codeblocks, each containing 20 bits with usefull data 308 | // The 1st octet of every codeblock always starts with a "1" (leftmost char) 309 | // to indicate the codeblock contains data 310 | // The 1st octet of a codeblock contains 8 bits: 1 fixed "1" + 7 databits 311 | 312 | // The 2nd octet of a codeblock contains 8 bits: 8 databits 313 | // the 3th octet of a codeblock contains 5 bits: 5 databits 314 | 315 | // the actual text to be transmitted is encoded in 7 bits + terminating "EOT" 316 | 317 | if (bytecount_out == 1) { 318 | // 2nd char of codeword: 319 | // all 8 bits, init with all 0 320 | txtcoded[txtcodedlen]=0x00; 321 | bitcount_out=8; 322 | } else if (bytecount_out == 2) { 323 | // 3th char of codeword: 324 | // only 5 bits used, init with all 0 325 | txtcoded[txtcodedlen]=0x00; 326 | bitcount_out=5; 327 | } else if (bytecount_out >= 3) { 328 | // 1st char of codeword (wrap around of "bytecount_out" value) 329 | // init with leftmost bit as "1" 330 | txtcoded[txtcodedlen]=0x80; 331 | bitcount_out = 7; 332 | 333 | // wrap around 334 | bytecount_out = 0; 335 | }; // end elsif - elsif - if 336 | 337 | }; // end if 338 | 339 | 340 | // read new input if ingress queue empty 341 | if (bitcount_in == 0) { 342 | bytecount_in++; 343 | 344 | // still data in txtbuffer 345 | if (bytecount_in < txtlen) { 346 | // reinit vars 347 | c_in=invertbits(txtin[bytecount_in]); 348 | bitcount_in=7; 349 | } else { 350 | // no more data in ingress queue 351 | // done, so ... stop!! 352 | stop=1; 353 | 354 | continue; 355 | }; // end else - if (still data in queue?) 356 | }; // end if (ingress queue empty?) 357 | 358 | }; // end while (stop?) 359 | 360 | // increase txtcodedlen. Was used as index (0 to ...) above. Add one to 361 | // make it indicate a lenth 362 | txtcodedlen++; 363 | 364 | 365 | }; // end subblock convert 366 | 367 | 368 | 369 | 370 | // address field: 371 | // lowest 3 bits of address determine startframe 372 | currentframe = ((address & 0x7)<<1); 373 | 374 | 375 | addressline=address >> 3; 376 | 377 | // add address source 378 | addressline<<=2; 379 | addressline += addresssource; 380 | 381 | // replace address line 382 | replaceline(currentframe,createcrc(addressline<<11)); 383 | 384 | 385 | // now insert text message 386 | for (l=0; l < txtcodedlen; l+= 3) { 387 | uint32_t t; 388 | 389 | // move up to next frame 390 | currentframe++; 391 | 392 | t = (uint32_t) txtcoded[l] << 24; // copy octet to bits 24 to 31 in 32 bit struct 393 | t |= (uint32_t) txtcoded[l+1] << 16; // copy octet to bits 16 to 23 in 32 bit struct 394 | t |= (uint32_t) txtcoded[l+2] << 11; // copt octet to bits 11 to 15 in 32 bit struct 395 | 396 | replaceline(currentframe,createcrc(t)); 397 | 398 | // note: move up 3 chars at a time (see "for" above) 399 | }; // end for 400 | 401 | 402 | 403 | // part 1: send "POCGO1" 404 | 405 | ret=write(serialfd,pocgo1,6); 406 | if (ret != 6) { 407 | printf("write did not return correct value, got %d \n",ret); 408 | }; // end if 409 | 410 | 411 | // write batches 412 | 413 | for (l=0; l<16; l++) { 414 | // copy octet per octet to be platform / endian endependant 415 | 416 | char c; 417 | c=(batch1[l] >> 24) & 0xff; ret=write(serialfd,&c,1); 418 | c=(batch1[l] >> 16) & 0xff; ret=write(serialfd,&c,1); 419 | c=(batch1[l] >> 8) & 0xff; ret=write(serialfd,&c,1); 420 | c=(batch1[l]) & 0xff; ret=write(serialfd,&c,1); 421 | }; // end for 422 | 423 | 424 | 425 | // 2nd batch 426 | for (l=0; l<16; l++) { 427 | // copy octet per octet to be platform / endian endependant 428 | 429 | char c; 430 | c=(batch2[l] >> 24) & 0xff; ret=write(serialfd,&c,1); 431 | c=(batch2[l] >> 16) & 0xff; ret=write(serialfd,&c,1); 432 | c=(batch2[l] >> 8) & 0xff; ret=write(serialfd,&c,1); 433 | c=(batch2[l]) & 0xff; ret=write(serialfd,&c,1); 434 | }; // end for 435 | 436 | // callsign 437 | ret=write(serialfd,callsign,8); 438 | if (ret != 8) { 439 | printf("write did not return correct value, got %d \n",ret); 440 | }; // end if 441 | 442 | // config 443 | ret=write(serialfd,config,6); 444 | if (ret != 6) { 445 | printf("write did not return correct value, got %d \n",ret); 446 | }; // end if 447 | 448 | 449 | // done 450 | ret=write(serialfd,pocend,6); 451 | if (ret != 6) { 452 | printf("write did not return correct value, got %d \n",ret); 453 | }; // end if 454 | 455 | 456 | 457 | ret=read (serialfd,buffin,64); 458 | if (ret < 0) { 459 | int thiserror=errno; 460 | 461 | fprintf(stderr,"read from serial failed: (%s) \n",strerror(thiserror)); 462 | exit(-1); 463 | }; // end if 464 | 465 | while (ret > 0) { 466 | // add terminating \n 467 | buffin[ret]=0; 468 | printf("%s\n",buffin); 469 | 470 | // read next 471 | ret=read (serialfd,buffin,64); 472 | }; // end while 473 | 474 | 475 | exit(0); 476 | }; // end MAIN 477 | 478 | 479 | 480 | /////////////////////////////////////////////////////// 481 | //////// END MAIN APPLICATION ////////////////////// 482 | //////////////////////////////////////////////// 483 | 484 | 485 | 486 | //// function createcrc 487 | uint32_t createcrc (uint32_t val) { 488 | 489 | // local vars 490 | uint32_t cw; // codeword 491 | 492 | int bit=0; 493 | int local_cw = 0; 494 | int parity = 0; 495 | 496 | 497 | // init cw 498 | cw=val; 499 | 500 | // move up 11 bits to make place for crc + parity 501 | local_cw=val; /* bch */ 502 | 503 | for (bit=1; bit<=21; bit++, cw <<= 1) { 504 | if (cw & 0x80000000) { 505 | cw ^= 0xED200000; 506 | }; // end if 507 | }; // end for 508 | 509 | local_cw |= (cw >> 21); 510 | 511 | cw =local_cw; /* parity */ 512 | 513 | for (bit=1; bit<=32; bit++, cw<<=1) { 514 | if (cw & 0x80000000) { 515 | parity++; 516 | }; // end if 517 | }; // end for 518 | 519 | 520 | // make even parity 521 | if (parity % 2) { 522 | local_cw++; 523 | }; // end if 524 | 525 | 526 | // done 527 | return(local_cw); 528 | 529 | }; // end function createcrc 530 | 531 | 532 | 533 | 534 | 535 | //// function replaceline 536 | void replaceline (int line, uint32_t val) { 537 | 538 | // sanity checks 539 | if ((line < 0) || (line > 32)) { 540 | return; 541 | }; // end if 542 | 543 | 544 | if (line < 16) { 545 | batch1[line]=val; 546 | } else { 547 | batch2[line-16]=val; 548 | }; // end if 549 | 550 | 551 | }; // end function replaceline; 552 | 553 | 554 | unsigned char invertbits (unsigned char c_in) { 555 | int l; 556 | char c_out; 557 | 558 | // reduce to 7 bits 559 | c_in &= 0x7f; 560 | 561 | 562 | // init 563 | c_out=0x00; 564 | 565 | // copy and shift 6 bits 566 | for (l=0; l<6; l++) { 567 | if (c_in & 0x01) { 568 | c_out |= 1; 569 | }; // end if 570 | c_out <<= 1; 571 | c_in >>= 1; 572 | }; // end for 573 | 574 | // copy 7th bit, do not shift 575 | if (c_in & 0x01) { 576 | c_out |= 1; 577 | }; // end if 578 | 579 | return(c_out); 580 | 581 | }; // end function invertbits 582 | 583 | -------------------------------------------------------------------------------- /arduino/readme.txt: -------------------------------------------------------------------------------- 1 | POCSAG via si4432-based ISM modules 2 | ----------------------------------- 3 | 4 | 5 | This application is designed to send 512 bps POCSAG-messages 6 | using si4432-based ISM transceivers. 7 | 8 | As of version 0.1.2 (20140517), part of the pocsag-code has been 9 | moved to a dedicated arduino-class and has -therefor- been moved here: 10 | https://github.com/on1arf/pocsag 11 | 12 | Kristoff Bonne (ON1ARF) 13 | -------------------------------------------------------------------------------- /arduino/rf22_pocsag_arduino.ino: -------------------------------------------------------------------------------- 1 | // rf22_pocsag_arduino 2 | 3 | // receive POCSAG-message + callsign + configuration data from serial port 4 | // send message on RF (70 cm ham-band) using si4432-based "RF22" ISM modules 5 | 6 | 7 | // this program uses the arduino rf22 library 8 | // http://www.airspayce.com/mikem/arduino/RF22/ 9 | 10 | // (c) 2014 Kristoff Bonne (ON1ARF) 11 | 12 | /* 13 | * This program is free software; you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public License as 15 | * published by the Free Software Foundation; either version 2 of the 16 | * License, or (at your option) any later version. 17 | * 18 | * This application is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | * General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program; if not, write to the Free Software 25 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 26 | * 27 | */ 28 | 29 | // Version 0.1.0 (20140330) Initial release 30 | // Version 0.1.1 (20140501) pocsag message creation now done on arduino 31 | 32 | 33 | |||////////////////////////////////////////////////// 34 | ||| IMPORTANT /// 35 | ||| /// 36 | ||| As of version 0.1.2. (20140517) part /// 37 | ||| of this code has been moved to a /// 38 | ||| dedicated arduino-class. See the new /// 39 | ||| code here: /// 40 | ||| https://github.com/on1arf/pocsag /// 41 | ||| /// 42 | ||| The code below is the old version /// 43 | ||| 0.1.1 that is not maintained /// 44 | ||| anymore! /// 45 | |||////////////////////////////////////// 46 | 47 | 48 | 49 | 50 | 51 | // increase maximum packet site 52 | #define RF22_MAX_MESSAGE_LEN 255 53 | 54 | #define CWID_SHORT 33 // 106 ms @ 1200 bps 55 | #define CWID_LONG 90 // 320ms @ 1200 bps 56 | 57 | #include 58 | #include 59 | 60 | 61 | // global data 62 | 63 | // RF22 class 64 | RF22 rf22; 65 | RF22::ModemConfig MC; 66 | 67 | uint8_t cwidmsg[CWID_LONG]; 68 | 69 | // frequency; 70 | float freq; 71 | 72 | 73 | 74 | 75 | // structure for pocsag message (containing two batches) 76 | struct pocsagmsg { 77 | unsigned char sync[72]; 78 | 79 | // batch 1 80 | unsigned char synccw1[4]; 81 | int32_t batch1[16]; 82 | 83 | // batch 2 84 | unsigned char synccw2[4]; 85 | int32_t batch2[16]; 86 | }; // end 87 | 88 | 89 | // structure to convert int32 to architecture / endian independant 4-char array 90 | struct int32_4char { 91 | union { 92 | int32_t i; 93 | unsigned char c[4]; 94 | }; 95 | }; 96 | 97 | 98 | 99 | // data-structure for cwid 100 | struct t_mtab { 101 | char c; 102 | int pat; 103 | }; 104 | 105 | // table morse chars, read from left to right, ignore first bit (used to determine size) 106 | struct t_mtab morsetab[] = { 107 | {'.', 106}, 108 | {',', 115}, 109 | {'?', 76}, 110 | {'/', 41}, 111 | {'A', 6}, 112 | {'B', 17}, 113 | {'C', 21}, 114 | {'D', 9}, 115 | {'E', 2}, 116 | {'F', 20}, 117 | {'G', 11}, 118 | {'H', 16}, 119 | {'I', 4}, 120 | {'J', 30}, 121 | {'K', 13}, 122 | {'L', 18}, 123 | {'M', 7}, 124 | {'N', 5}, 125 | {'O', 15}, 126 | {'P', 22}, 127 | {'Q', 27}, 128 | {'R', 10}, 129 | {'S', 8}, 130 | {'T', 3}, 131 | {'U', 12}, 132 | {'V', 24}, 133 | {'W', 14}, 134 | {'X', 25}, 135 | {'Y', 29}, 136 | {'Z', 19}, 137 | {'1', 62}, 138 | {'2', 60}, 139 | {'3', 56}, 140 | {'4', 48}, 141 | {'5', 32}, 142 | {'6', 33}, 143 | {'7', 35}, 144 | {'8', 39}, 145 | {'9', 47}, 146 | {'0', 63}, 147 | {' ', 0} 148 | } ; 149 | 150 | 151 | // table to convert size to n-times 1 bit mask 152 | const unsigned char size2mask[7] = {0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f}; 153 | 154 | // functions defined below 155 | void sendcwid(char * idtxt); 156 | uint32_t createcrc(uint32_t); 157 | 158 | 159 | void setup() 160 | { 161 | 162 | Serial.begin(9600); 163 | if (rf22.init()) { 164 | Serial.println("RF22 init OK"); 165 | } else { 166 | Serial.println("RF22 init FAILED"); 167 | }; // end if 168 | 169 | 170 | // initialise at 439.9875 171 | // you will probably need to change this to correct for crystal error 172 | 173 | freq=439.9875; 174 | rf22.setFrequency(freq, 0.00); 175 | 176 | 177 | // configure radio-module for 512 baud, FSK, 4.5 Khz fd 178 | MC.reg_6e=0x04; MC.reg_6f=0x32; MC.reg_70=0x2c; MC.reg_58=0x80; 179 | MC.reg_72=0x04; MC.reg_71=0x22; 180 | 181 | MC.reg_1c=0x2B; MC.reg_20=0xa1; MC.reg_21=0xE0; MC.reg_22=0x10; 182 | MC.reg_23=0xc7; MC.reg_24=0x00; MC.reg_25=0x09; MC.reg_1f=0x03; 183 | MC.reg_69=0x60; 184 | 185 | rf22.setModemRegisters(&MC); 186 | 187 | 188 | // 100 mW TX power 189 | rf22.setTxPower(RF22_TXPOW_20DBM); 190 | 191 | 192 | 193 | // the rest of the two batches are read from the serial port 194 | // see loop function below 195 | 196 | // CW: dummy packet containing 0b01010101 197 | memset(cwidmsg,0x55,CWID_LONG); 198 | 199 | } 200 | 201 | void loop() { 202 | //vars 203 | struct pocsagmsg msg; 204 | int size; 205 | int rc; 206 | 207 | int state; 208 | 209 | // bell = ascii 0x07 210 | char bell=0x07; 211 | 212 | // data 213 | long int address; 214 | int addresssource; 215 | int repeat; 216 | char call[8]; // callsign 217 | int callsize; 218 | char textmsg[42]; // to store text message; 219 | int msgsize; 220 | 221 | int freqsize; 222 | int freq1; // freq MHZ part (3 digits) 223 | int freq2; // freq. 100Hz part (4 digits) 224 | 225 | // read input: 226 | // format: "P
" 227 | Serial.println("POCSAG text-message tool v0.1"); 228 | Serial.println("https://github.com/on1arf/rf22_pocsag"); 229 | Serial.println(""); 230 | Serial.println("Format:"); 231 | Serial.println("P
"); 232 | Serial.println("F "); 233 | 234 | 235 | // init var 236 | state=0; 237 | address=0; 238 | addresssource=0; 239 | callsize=0; 240 | msgsize=0; 241 | 242 | freqsize=0; 243 | 244 | while (state >= 0) { 245 | char c; 246 | // loop until we get a character from serial input 247 | while (!Serial.available()) { 248 | }; // end while 249 | 250 | c=Serial.read(); 251 | 252 | // break out on ESC 253 | if (c == 0x1b) { 254 | state=-999; 255 | break; 256 | }; // end if 257 | 258 | // state machine 259 | 260 | if (state == 0) { 261 | // state 0: wait for command 262 | // P = PAGE 263 | // F = FREQUENCY 264 | 265 | if ((c == 'p') || (c == 'P')) { 266 | // ok, got our "p" -> go to state 1 267 | state=1; 268 | // echo back char 269 | Serial.write(c); 270 | } else if ((c == 'f') || (c == 'F')) { 271 | // ok, got our "f" -> go to state 1 272 | state=10; 273 | // echo back char 274 | Serial.write(c); 275 | } else { 276 | // error: echo "bell" 277 | Serial.write(bell); 278 | }; // end else - if 279 | 280 | // get next char 281 | continue; 282 | }; // end state 0 283 | 284 | // state 1: space (" ") or first digit of address ("0" to "9") 285 | if (state == 1) { 286 | if (c == ' ') { 287 | // space -> go to state 2 and get next char 288 | state=2; 289 | 290 | // echo back char 291 | Serial.write(c); 292 | 293 | // get next char 294 | continue; 295 | } else if ((c >= '0') && (c <= '9')) { 296 | // digit -> first digit of address. Go to state 2 and process 297 | state=2; 298 | 299 | // continue to state 2 without fetching next char 300 | } else { 301 | // error: echo "bell" 302 | Serial.write(bell); 303 | 304 | // get next char 305 | continue; 306 | }; // end else - if 307 | }; // end state 1 308 | 309 | // state 2: address ("0" to "9") 310 | if (state == 2) { 311 | if ((c >= '0') && (c <= '9')) { 312 | long int newaddress; 313 | 314 | newaddress = address * 10 + (c - '0'); 315 | 316 | if (newaddress <= 0x1FFFFF) { 317 | // valid address 318 | address=newaddress; 319 | 320 | Serial.write(c); 321 | } else { 322 | // address to high. Send "beep" 323 | Serial.write(bell); 324 | }; // end if 325 | 326 | } else if (c == ' ') { 327 | // received space, go to next field (address source) 328 | Serial.write(c); 329 | state=3; 330 | } else { 331 | // error: echo "bell" 332 | Serial.write(bell); 333 | }; // end else - elsif - if 334 | 335 | // get next char 336 | continue; 337 | }; // end state 2 338 | 339 | // state 3: address source: one single digit from 0 to 3 340 | if (state == 3) { 341 | if ((c >= '0') && (c <= '3')) { 342 | addresssource= c - '0'; 343 | Serial.write(c); 344 | 345 | state=4; 346 | } else { 347 | // invalid: sound bell 348 | Serial.write(bell); 349 | }; // end if 350 | 351 | // get next char 352 | continue; 353 | }; // end state 3 354 | 355 | 356 | // state 4: space between source and call 357 | if (state == 4) { 358 | if (c == ' ') { 359 | Serial.write(c); 360 | 361 | state=5; 362 | } else { 363 | // invalid: sound bell 364 | Serial.write(bell); 365 | }; // end if 366 | 367 | // get next char 368 | continue; 369 | }; // end state 4 370 | 371 | // state 5: callsign, minimal 4 chars, maximum 7 chars 372 | if (state == 5) { 373 | // make uppercase if possible 374 | if ( (c >= 'a') && (c <= 'z')) { 375 | // capical letters are 0x20 below small letters in ASCII table 376 | c -= 0x20; 377 | }; // end if 378 | 379 | // characters allowed are [A-Z] and [0-9] 380 | if ( ( (c >= 'A') && (c <= 'Z')) || 381 | ((c >= '0') && (c <= '9')) ) { 382 | // accept if not more then 7 chars 383 | if (callsize < 7) { 384 | // accept 385 | call[callsize]=c; 386 | Serial.write(c); 387 | callsize++; 388 | } else { 389 | // to long, error 390 | Serial.write(bell); 391 | }; // end else - if 392 | } else if (c == ' ' ) { 393 | // space, accept space if we have at least 4 chars 394 | if (callsize >= 4) { 395 | Serial.write(c); 396 | 397 | // terminate string with "0x00" 398 | call[callsize]=0x00; 399 | 400 | // go to next state 401 | state=6; 402 | } else { 403 | Serial.write(bell); 404 | }; // end else - if 405 | } else { 406 | // invalid char 407 | Serial.write(bell); 408 | }; // end else - elsif - if 409 | 410 | // get next char 411 | continue; 412 | }; // end state 5 413 | 414 | 415 | // state 6: repeat: 1-digit value between 0 and 9 416 | if (state == 6) { 417 | if ((c >= '0') && (c <= '9')) { 418 | Serial.write(c); 419 | repeat=c - '0'; 420 | 421 | // move to next state 422 | state=7; 423 | } else { 424 | Serial.write(bell); 425 | }; // end if 426 | 427 | // get next char 428 | continue; 429 | }; // end state 6 430 | 431 | 432 | // state 7: space between repeat and message 433 | if (state == 7) { 434 | if (c == ' ') { 435 | Serial.write(c); 436 | 437 | // move to next state 438 | state=8; 439 | } else { 440 | // invalid char 441 | Serial.write(bell); 442 | }; // end else - if 443 | 444 | // get next char 445 | continue; 446 | }; // end state 7 447 | 448 | 449 | // state 8: message, up to 40 chars, terminate with cr (0x0d) or lf (0x0a) 450 | if (state == 8) { 451 | // accepted is everything between space (ascii 0x20) and ~ (ascii 0x7e) 452 | if ((c >= 0x20) && (c <= 0x7e)) { 453 | // accept up to 40 chars 454 | if (msgsize < 40) { 455 | Serial.write(c); 456 | 457 | textmsg[msgsize]=c; 458 | msgsize++; 459 | } else { 460 | // to long 461 | Serial.write(bell); 462 | }; // end else - if 463 | 464 | } else if ((c == 0x0a) || (c == 0x0d)) { 465 | // done 466 | 467 | Serial.println(""); 468 | 469 | // add terminating NULL 470 | textmsg[msgsize]=0x00; 471 | 472 | // break out of loop 473 | state=-1; 474 | break; 475 | 476 | } else { 477 | // invalid char 478 | Serial.write(bell); 479 | }; // end else - elsif - if 480 | 481 | // get next char 482 | continue; 483 | }; // end state 8; 484 | 485 | 486 | 487 | 488 | // PART 2: frequency 489 | 490 | // state 1: space (" ") or first digit of address ("0" to "9") 491 | if (state == 10) { 492 | if (c == ' ') { 493 | // space -> go to state 11 and get next char 494 | state=11; 495 | 496 | // echo back char 497 | Serial.write(c); 498 | 499 | // get next char 500 | continue; 501 | } else if ((c >= '0') && (c <= '9')) { 502 | // digit -> first digit of address. Go to state 2 and process 503 | state=11; 504 | 505 | // init freqsize; 506 | freqsize=0; 507 | freq1=0; 508 | 509 | // continue to state 2 without fetching next char 510 | } else { 511 | // error: echo "bell" 512 | Serial.write(bell); 513 | 514 | // get next char 515 | continue; 516 | }; // end else - if 517 | }; // end state 1 518 | 519 | 520 | 521 | // state 11: freq. Mhz part: 3 digits needed 522 | if (state == 11) { 523 | if ((c >= '0') && (c <= '9')) { 524 | if (freqsize < 3) { 525 | freq1 *= 10; 526 | freq1 += (c - '0'); 527 | 528 | freqsize++; 529 | Serial.write(c); 530 | 531 | // go to state 12 (wait for space) if 3 digits received 532 | if (freqsize == 3) { 533 | state=12; 534 | }; // end if 535 | } else { 536 | // too large: error 537 | Serial.write(bell); 538 | }; // end else - if 539 | } else { 540 | // unknown char: error 541 | Serial.write(bell); 542 | }; // end else - if 543 | 544 | // get next char 545 | continue; 546 | 547 | }; // end state 11 548 | 549 | 550 | // state 12: space between freq part 1 and freq part 2 551 | if (state == 12) { 552 | if (c == ' ') { 553 | // space received, go to state 13 554 | state = 13; 555 | Serial.write(c); 556 | 557 | // init freq2; + freqsize; 558 | freq2=0; 559 | freqsize=0; 560 | } else { 561 | // unknown char 562 | Serial.write(bell); 563 | }; // end else - if 564 | 565 | // get next char 566 | continue; 567 | 568 | }; // end state 12; 569 | 570 | // state 13: part 2 of freq. (100 Hz part). 4 digits needed 571 | if (state == 13) { 572 | if ((c >= '0') && (c <= '9')) { 573 | if (freqsize < 4) { 574 | freq2 *= 10; 575 | freq2 += (c - '0'); 576 | 577 | freqsize++; 578 | Serial.write(c); 579 | } else { 580 | // too large: error 581 | Serial.write(bell); 582 | }; // end else - if 583 | 584 | // get next char 585 | continue; 586 | 587 | } else if ((c == 0x0a) || (c == 0x0d)) { 588 | if (freqsize == 4) { 589 | // 4 digits received, go to next state 590 | state = -2; 591 | Serial.println(""); 592 | 593 | // break out 594 | break; 595 | } else { 596 | // not yet 3 digits 597 | Serial.write(bell); 598 | 599 | // get next char; 600 | continue; 601 | }; // end else - if 602 | 603 | } else { 604 | // unknown char 605 | Serial.write(bell); 606 | 607 | // get next char 608 | continue; 609 | }; // end else - elsif - if 610 | 611 | }; // end state 12; 612 | 613 | }; // end while 614 | 615 | 616 | // Send PAGE 617 | if (state == -1) { 618 | Serial.print("address: "); 619 | Serial.println(address); 620 | 621 | Serial.print("addresssource: "); 622 | Serial.println(addresssource); 623 | 624 | Serial.print("callsign: "); 625 | Serial.println(call); 626 | 627 | Serial.print("repeat: "); 628 | Serial.println(repeat); 629 | 630 | Serial.print("message: "); 631 | Serial.println(textmsg); 632 | 633 | 634 | // create pocsag message 635 | // batch2 option 1 (duplicate message) 636 | // invert option 1 (invert) 637 | 638 | rc=create_pocsag(address,addresssource,textmsg,&msg,1,1); 639 | 640 | 641 | if ((rc != 1) && (rc != 2)) { 642 | Serial.print("Error in createpocsag: return-code: "); 643 | Serial.println(rc); 644 | 645 | } else { 646 | 647 | // pocsagmsg_correctendian(msg); 648 | 649 | if (rc == 1) { 650 | // 1 batch: sync (72 octets) + (17 * 4) 651 | size=140; 652 | } else { 653 | // 2 batches: sync (72 octets) + 2 * (17 * 4) 654 | size=208; 655 | }; // end if 656 | 657 | 658 | // send at least once + repeat 659 | for (int l=-1; l < repeat; l++) { 660 | Serial.println("POCSAG SEND"); 661 | 662 | rf22.send((uint8_t *)&msg, 208); 663 | rf22.waitPacketSent(); 664 | 665 | delay(3000); 666 | }; // end for 667 | 668 | }; // end else - if; 669 | 670 | 671 | // send CW ID: switch to 2000 bps GMSK and send short or long 672 | // dummy packets to create FM-modulated CW 673 | 674 | // switch to 2000 bps GMSK 675 | rf22.setModemConfig(RF22::GFSK_Rb2Fd5); 676 | 677 | sendcwid(call); 678 | 679 | // switch back to 512 bps FSK 680 | rf22.setModemRegisters(&MC); 681 | 682 | }; // end function P (page) 683 | 684 | 685 | 686 | // function "F": change frequency 687 | 688 | if (state == -2) { 689 | float newfreq; 690 | 691 | newfreq=(float)freq1+((float)freq2/10000.0F); // f1 = MHz, F2 = 100 Hz 692 | 693 | 694 | if ((newfreq >= 430) && (newfreq <= 440)) { 695 | Serial.print("switching to new frequency: "); 696 | Serial.println(newfreq); 697 | 698 | freq=newfreq; 699 | 700 | rf22.setFrequency(freq, 0.00); 701 | 702 | } else { 703 | Serial.print("Error: invalid frequency: "); 704 | Serial.println(newfreq); 705 | }; // end if 706 | }; // end function F (frequency) 707 | 708 | 709 | }; // end main loop function 710 | 711 | 712 | ////////////////////////////////////////////////////////////// 713 | // //////////////// 714 | // End of main loop 715 | /////// 716 | 717 | 718 | 719 | 720 | 721 | // create_pocsag 722 | 723 | // creates pocsag message in pocsagmsg structure 724 | // returns 1 or 2, if the message is one or two batches 725 | int create_pocsag (long int address, int source, char * text, struct pocsagmsg * pocsagmsg, int option_batch2, int option_invert) { 726 | int txtlen; 727 | unsigned char c; // tempory var for character being processed 728 | int stop; /// temp var 729 | 730 | unsigned char txtcoded[56]; // encoded text can be up to 56 octets 731 | int txtcodedlen; 732 | 733 | // local vars to encode address line 734 | int currentframe; 735 | uint32_t addressline; 736 | 737 | // counters to encode text 738 | int bitcount_in, bitcount_out, bytecount_in, bytecount_out; 739 | 740 | // some sanity checks 741 | txtlen=strlen(text); 742 | if (txtlen > 40) { 743 | // messages can be up to 40 chars (+ terminating EOT) 744 | txtlen=40; 745 | }; // end if 746 | 747 | // replace terminating \0 by EOT 748 | text[txtlen]=0x04; // EOT (end of transmission) 749 | txtlen++; // increase size by 1 (for EOT) 750 | 751 | // some sanity checks for the address 752 | // addreess are 21 bits 753 | if ((address > 0x1FFFFF) || (address <= 0)) { 754 | return(-1); 755 | }; // end if 756 | 757 | // source is 2 bits 758 | if ((source < 0) || (source > 3)) { 759 | return(-2); 760 | }; // end if 761 | 762 | // option "batch2" goes from 0 to 2 763 | if ((option_batch2 < 0) || (option_batch2 > 2)) { 764 | return(-3); 765 | }; // end if 766 | 767 | // option "invert" should be 0 or 1 768 | if ((option_invert < 0) || (option_invert > 1)) { 769 | return(-4); 770 | }; // end if 771 | 772 | // create packet 773 | 774 | // A POCSAG packet has the following structure: 775 | // - transmission syncronisation (576 bit "01" pattern) 776 | 777 | // one or multiple "batches". 778 | // - a batch starts with a 32 frame syncronisation pattern 779 | // - followed by 8 * 2 * 32 bits of actual data or "idle" information: 780 | // the "POCSAG message" 781 | 782 | 783 | // A POSAG message consists of one or multiple batches. A batch is 64 784 | // octets: 8 frames of 2 codewords of 32 bits each 785 | 786 | // This application can generate one pocsag-message containing one 787 | // text-message of up to 40 characters + a terminating EOT 788 | // (end-of-transmission, ASCII 0x04). 789 | 790 | // the message (i.e. the destination-address + the text messate itself) 791 | // always starts in the first batch but not necessairy at the beginning 792 | // of the batch. The start-position of message inside the batch is 793 | // determined by (the 3 lowest bits of) the address. 794 | 795 | // if the message reaches the end of the first batch, if overflows 796 | // into the 2nd batch. 797 | 798 | // As a result, we do not know beforehand if the POCSAG frame will be 799 | // one or two POCSAG message, so we will initialise both batches. If 800 | // -and the end of the process- it turns out that the message fits into 801 | // one single batch, there are three options: 802 | // - truncate the message and send a POCSAG-frame containing one POCSAG- 803 | // message 804 | // - repeat the first batch in the 2nd batch and send both batches 805 | // - leave the 2nd batch empty (i.e. filled with the "idle" pattern) and 806 | // send that 807 | 808 | // note that before actually transmitting the pattern, all bits are inverted 0-to-1 809 | // for the parts of the frame with a fixed text (syncronisation pattern), the 810 | // bits are inverted in the code 811 | 812 | // part 0.1: frame syncronisation pattern 813 | if (option_invert == 0) { 814 | memset(pocsagmsg->sync,0xaa,72); 815 | } else { 816 | memset(pocsagmsg->sync,0x55,72); // pattern 0x55 is invers of 0xaa 817 | }; // end else - if 818 | 819 | // part 0.2: batch syncronisation 820 | 821 | // a batch begins with a sync codeword 822 | // 0111 1100 1101 0010 0001 0101 1101 1000 823 | pocsagmsg->synccw1[0]=0x7c; pocsagmsg->synccw1[1]=0xd2; 824 | pocsagmsg->synccw1[2]=0x15; pocsagmsg->synccw1[3]=0xd8; 825 | 826 | pocsagmsg->synccw2[0]=0x7c; pocsagmsg->synccw2[1]=0xd2; 827 | pocsagmsg->synccw2[2]=0x15; pocsagmsg->synccw2[3]=0xd8; 828 | 829 | // invert bits if needed 830 | if (option_invert == 1) { 831 | pocsagmsg->synccw1[0] ^= 0xff; pocsagmsg->synccw1[1] ^= 0xff; 832 | pocsagmsg->synccw1[2] ^= 0xff; pocsagmsg->synccw1[3] ^= 0xff; 833 | 834 | pocsagmsg->synccw2[0] ^= 0xff; pocsagmsg->synccw2[1] ^= 0xff; 835 | pocsagmsg->synccw2[2] ^= 0xff; pocsagmsg->synccw2[3] ^= 0xff; 836 | }; 837 | 838 | // part 0.3: init batches with all "idle-pattern" 839 | for (int l=0; l< 16; l++) { 840 | pocsagmsg->batch1[l]=0x7a89c197; 841 | pocsagmsg->batch2[l]=0x7a89c197; 842 | }; // end for 843 | 844 | 845 | // part 1: 846 | // address line: 847 | // the format of a pocsag codeword containing an address is as follows: 848 | // 0aaaaaaa aaaaaaaa aaassccc ccccccp 849 | // "0" = fixed (indicating an codeword containing an address) 850 | // a[18] = 18 upper bits of the address 851 | // s[2] = 2 bits source (encoding 4 different "address-spaces") 852 | // c[10] = 10 bits of crc 853 | // p = parity bit 854 | 855 | // the lowest 3 bits of address are not encoded in the address line as 856 | // found in the POCSAG message. However, they are used to determine 857 | // where in the POCSAG-message, the message is started 858 | currentframe = ((address & 0x7)<<1); 859 | 860 | 861 | addressline=address >> 3; 862 | 863 | // add address source 864 | addressline<<=2; 865 | addressline += source; 866 | 867 | //// replace address line 868 | replaceline(pocsagmsg,currentframe,createcrc(addressline<<11)); 869 | 870 | 871 | 872 | 873 | 874 | // part 2.1: 875 | // convert text to podsag format 876 | 877 | // POCSAG codewords containing a message have the following format: 878 | // 1ttttttt tttttttt tttttccc ccccccp 879 | // "1" = fixed (indicating a codeword containing an encoded message) 880 | // t[20] = 20 bits of text 881 | // c[10] = 10 bits CRC 882 | // p = parity bit 883 | 884 | // text-messages are encoded using 7 bits ascii, IN INVERTED BITORDER (MSB first) 885 | 886 | // As up to 20 bits can be stored in a codeword, each codeword contains 3 chars 887 | // minus 1 bit. Hence, the text is "wrapped" around to the next codewords 888 | 889 | 890 | // The message is first encoded and stored into a tempory array 891 | 892 | // init vars 893 | // clear txtcoded 894 | memset(txtcoded,0x00,56); // 56 octets, all "0x00" 895 | 896 | bitcount_out = 7; // 1 char can contain up to 7 bits 897 | bytecount_out = 0; 898 | 899 | bitcount_in = 7; 900 | bytecount_in=0; 901 | c=flip7charbitorder(text[0]); // we start with first char 902 | 903 | 904 | txtcodedlen=0; 905 | txtcoded[0] = 0x80; // output, initialise as empty with leftmost bit="1" 906 | 907 | 908 | 909 | // loop for all chars 910 | stop=0; 911 | 912 | while (!stop) { 913 | int bits2copy; 914 | unsigned char t; // tempory var bits to be copied 915 | 916 | // how many bits to copy? 917 | // minimum of "bits available for input" and "bits that can be stored" 918 | if (bitcount_in > bitcount_out) { 919 | bits2copy = bitcount_out; 920 | } else { 921 | bits2copy = bitcount_in; 922 | }; // end if 923 | 924 | // copy "bits2copy" bits, shifted the left if needed 925 | t = c & (size2mask[bits2copy-1] << (bitcount_in - bits2copy) ); 926 | 927 | // where to place ? 928 | // move left to write if needed 929 | if (bitcount_in > bitcount_out) { 930 | // move to right and export 931 | t >>= (bitcount_in-bitcount_out); 932 | } else if (bitcount_in < bitcount_out) { 933 | // move to left 934 | t <<= (bitcount_out-bitcount_in); 935 | }; // end else - if 936 | 937 | // now copy bits 938 | txtcoded[txtcodedlen] |= t; 939 | 940 | // decrease bit counters 941 | bitcount_in -= bits2copy; 942 | bitcount_out -= bits2copy; 943 | 944 | 945 | // outbound queue is full 946 | if (bitcount_out == 0) { 947 | // go to next position in txtcoded 948 | bytecount_out++; 949 | txtcodedlen++; 950 | 951 | // data is stored in codeblocks, each containing 20 bits with usefull data 952 | // The 1st octet of every codeblock always starts with a "1" (leftmost char) 953 | // to indicate the codeblock contains data 954 | // The 1st octet of a codeblock contains 8 bits: "1" + 7 databits 955 | // The 2nd octet of a codeblock contains 8 bits: 8 databits 956 | // the 3th octet of a codeblock contains 5 bits: 5 databits 957 | 958 | if (bytecount_out == 1) { 959 | // 2nd char of codeword: 960 | // all 8 bits used, init with all 0 961 | txtcoded[txtcodedlen]=0x00; 962 | bitcount_out=8; 963 | } else if (bytecount_out == 2) { 964 | // 3th char of codeword: 965 | // only 5 bits used, init with all 0 966 | txtcoded[txtcodedlen]=0x00; 967 | bitcount_out=5; 968 | } else if (bytecount_out >= 3) { 969 | // 1st char of codeword (wrap around of "bytecount_out" value) 970 | // init with leftmost bit as "1" 971 | txtcoded[txtcodedlen]=0x80; 972 | bitcount_out = 7; 973 | 974 | // wrap around "bytecount_out" value 975 | bytecount_out = 0; 976 | }; // end elsif - elsif - if 977 | 978 | }; // end if 979 | 980 | // ingress queue is empty 981 | if (bitcount_in == 0) { 982 | bytecount_in++; 983 | 984 | // any more text ? 985 | if (bytecount_in < txtlen) { 986 | // reinit vars 987 | c=flip7charbitorder(text[bytecount_in]); 988 | bitcount_in=7; // only use 7 bits of every char 989 | } else { 990 | // no more text 991 | // done, so ... stop!! 992 | stop=1; 993 | 994 | continue; 995 | }; // end else - if 996 | }; // end if 997 | }; // end while (stop) 998 | 999 | // increase txtcodedlen. Was used as index (0 to ...) above. Add one to 1000 | // make it indicate a lenth 1001 | txtcodedlen++; 1002 | 1003 | 1004 | 1005 | // Part 2.2: copy coded message into pocsag message 1006 | 1007 | // now insert text message 1008 | for (int l=0; l < txtcodedlen; l+= 3) { 1009 | uint32_t t; 1010 | 1011 | // move up to next frame 1012 | currentframe++; 1013 | 1014 | // copying is done octet per octet to be architecture / endian independant 1015 | t = (uint32_t) txtcoded[l] << 24; // copy octet to bits 24 to 31 in 32 bit struct 1016 | t |= (uint32_t) txtcoded[l+1] << 16; // copy octet to bits 16 to 23 in 32 bit struct 1017 | t |= (uint32_t) txtcoded[l+2] << 11; // copy octet to bits 11 to 15 in 32 bit struct 1018 | 1019 | replaceline(pocsagmsg,currentframe,createcrc(t)); 1020 | 1021 | // note: move up 3 chars at a time (see "for" above) 1022 | }; // end for 1023 | 1024 | 1025 | // invert bits if needed 1026 | if (option_invert) { 1027 | for (int l=0; l<16; l++) { 1028 | pocsagmsg->batch1[l] ^= 0xffffffff; 1029 | }; // end for 1030 | 1031 | for (int l=0; l<16; l++) { 1032 | pocsagmsg->batch2[l] ^= 0xffffffff; 1033 | }; // end for 1034 | 1035 | }; 1036 | 1037 | 1038 | /// convert to make endian/architecture independant 1039 | 1040 | 1041 | for (int l=0; l<16; l++) { 1042 | int32_t t1; 1043 | 1044 | // structure to convert int32 to architecture / endian independant 4-char array 1045 | struct int32_4char t2; 1046 | 1047 | // batch 1 1048 | t1=pocsagmsg->batch1[l]; 1049 | 1050 | // left more octet 1051 | t2.c[0]=(t1>>24)&0xff; t2.c[1]=(t1>>16)&0xff; 1052 | t2.c[2]=(t1>>8)&0xff; t2.c[3]=t1&0xff; 1053 | 1054 | // copy back 1055 | pocsagmsg->batch1[l]=t2.i; 1056 | 1057 | // batch 2 1058 | t1=pocsagmsg->batch2[l]; 1059 | 1060 | // left more octet 1061 | t2.c[0]=(t1>>24)&0xff; t2.c[1]=(t1>>16)&0xff; 1062 | t2.c[2]=(t1>>8)&0xff; t2.c[3]=t1&0xff; 1063 | 1064 | // copy back 1065 | pocsagmsg->batch2[l]=t2.i; 1066 | 1067 | }; // end for 1068 | 1069 | 1070 | 1071 | // done 1072 | // return 1073 | 1074 | // If only one single batch used 1075 | if (currentframe < 16) { 1076 | // batch2 option: 1077 | // 0: truncate to one batch 1078 | // 1: copy batch1 to batch2 1079 | // 2: leave batch2 as "idle" 1080 | 1081 | if (option_batch2 == 0) { 1082 | // done. Just return indication length 1 batch 1083 | return(1); 1084 | } else if (option_batch2 == 1) { 1085 | memcpy(pocsagmsg->batch2,pocsagmsg->batch1,64); // 16 codewords of 32 bits 1086 | }; // end of 1087 | 1088 | // return for option_batch2 == 1 or 2 1089 | // return indication length 2 batches 1090 | return(2); 1091 | }; 1092 | 1093 | 1094 | // return indication length 2 batches 1095 | return(2); 1096 | 1097 | 1098 | }; // end function create_pocsag 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | ///////////////////////////////////////////// 1106 | // function sendcwid 1107 | 1108 | void sendcwid (char *msg) { 1109 | int l,l2, s; 1110 | int n_morse; 1111 | 1112 | n_morse=sizeof(morsetab) / sizeof(morsetab[0]); 1113 | 1114 | // the id message should be 8 char long. 1115 | // some more validity checks to be added later 1116 | 1117 | s=strlen(msg); 1118 | for (l=0; l < s; l++) { 1119 | 1120 | char c; 1121 | int m; 1122 | int state; 1123 | 1124 | // init as invalid 1125 | c='?'; m=0; 1126 | 1127 | for (l2=0; l2>= 1; 1170 | }; // end for (morse bits) 1171 | }; // end else - if (space ?) 1172 | 1173 | // wait for 350 ms between letters 1174 | delay(350); 1175 | 1176 | }; // end for 1177 | 1178 | 1179 | }; // end function sendcwid 1180 | 1181 | 1182 | ///////////////////////////// 1183 | /// function replaceline 1184 | 1185 | void replaceline (struct pocsagmsg * msg, int line, uint32_t val) { 1186 | 1187 | // sanity checks 1188 | if ((line < 0) || (line > 32)) { 1189 | return; 1190 | }; // end if 1191 | 1192 | 1193 | if (line < 16) { 1194 | msg->batch1[line]=val; 1195 | } else { 1196 | msg->batch2[line-16]=val; 1197 | }; // end if 1198 | 1199 | 1200 | }; // end function replaceline 1201 | 1202 | 1203 | ////////////////////////// 1204 | // function flip7charbitorder 1205 | 1206 | unsigned char flip7charbitorder (unsigned char c_in) { 1207 | int i; 1208 | char c_out; 1209 | 1210 | // init 1211 | c_out=0x00; 1212 | 1213 | // copy and shift 6 bits 1214 | for (int l=0; l<6; l++) { 1215 | if (c_in & 0x01) { 1216 | c_out |= 1; 1217 | }; // end if 1218 | 1219 | // shift data 1220 | c_out <<= 1; c_in >>= 1; 1221 | }; // end for 1222 | 1223 | // copy 7th bit, do not shift 1224 | if (c_in & 0x01) { 1225 | c_out |= 1; 1226 | }; // end if 1227 | 1228 | return(c_out); 1229 | 1230 | }; // end function flip2charbitorder 1231 | 1232 | ////////////////////////////// 1233 | /// function createcrc 1234 | 1235 | uint32_t createcrc(uint32_t in) { 1236 | 1237 | // local vars 1238 | uint32_t cw; // codeword 1239 | 1240 | uint32_t local_cw = 0; 1241 | uint32_t parity = 0; 1242 | 1243 | // init cw 1244 | cw=in; 1245 | 1246 | // move up 11 bits to make place for crc + parity 1247 | local_cw=in; /* bch */ 1248 | 1249 | // calculate crc 1250 | for (int bit=1; bit<=21; bit++, cw <<= 1) { 1251 | if (cw & 0x80000000) { 1252 | cw ^= 0xED200000; 1253 | }; // end if 1254 | }; // end for 1255 | 1256 | local_cw |= (cw >> 21); 1257 | 1258 | // parity 1259 | cw=local_cw; 1260 | 1261 | for (int bit=1; bit<=32; bit++, cw<<=1) { 1262 | if (cw & 0x80000000) { 1263 | parity++; 1264 | }; // end if 1265 | }; // end for 1266 | 1267 | 1268 | // make even parity 1269 | if (parity % 2) { 1270 | local_cw++; 1271 | }; // end if 1272 | 1273 | // done 1274 | return(local_cw); 1275 | 1276 | 1277 | }; // end function createcrc 1278 | 1279 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | POCSAG via si4432-based ISM modules 2 | ----------------------------------- 3 | 4 | 5 | This application is designed to send 512 bps POCSAG-messages 6 | using si4432-based ISM transceivers. 7 | 8 | 9 | There are currently two versions of this application: 10 | - arduino+linux: 11 | This uses a linux-based application to create the pocsag-message 12 | and an arduino to interface with the si4432 tranceiver module 13 | 14 | Version: 0.1.0 (2014/03/30) 15 | 16 | 17 | - arduino 18 | This uses an arduino to both create POCSAG-message and to interface 19 | with the si4432 based ISM-tranceiver for broadcasting the message. 20 | 21 | As the "create pocsag message" code has now been moved to a dedicated 22 | arduino class, the code has been moved to a new location: 23 | https://github.com/on1arf/pocsag 24 | 25 | 26 | Have fun! 27 | 28 | 73 29 | Kristoff - ON1ARF 30 | --------------------------------------------------------------------------------