├── 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 |
--------------------------------------------------------------------------------