
Generates RTTY from a GPS receiver
Dependencies: mbed-dsp mbed-rtos mbed
main.cpp@5:dcf952c7e6b3, 2014-01-11 (annotated)
- Committer:
- adwiens
- Date:
- Sat Jan 11 19:09:46 2014 +0000
- Revision:
- 5:dcf952c7e6b3
- Parent:
- 4:ed77fd381cc5
again
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
adwiens | 4:ed77fd381cc5 | 1 | // Author: Andrew Wiens (adwiens.com) |
adwiens | 4:ed77fd381cc5 | 2 | // Date: 11 Jan 2014 |
adwiens | 4:ed77fd381cc5 | 3 | // |
adwiens | 5:dcf952c7e6b3 | 4 | // ========================================================================== |
adwiens | 4:ed77fd381cc5 | 5 | // |
adwiens | 4:ed77fd381cc5 | 6 | // GPS2RTTY |
adwiens | 4:ed77fd381cc5 | 7 | // |
adwiens | 4:ed77fd381cc5 | 8 | // Using the mbed RTOS and DSP libraries, GPS2RTTY generates RTTY messages |
adwiens | 4:ed77fd381cc5 | 9 | // containing raw NMEA sentences from a GPS receiver. |
adwiens | 4:ed77fd381cc5 | 10 | // |
adwiens | 5:dcf952c7e6b3 | 11 | // No extra hardware needed! Audio is generated with the mbed's built-in DAC. |
adwiens | 4:ed77fd381cc5 | 12 | // |
adwiens | 4:ed77fd381cc5 | 13 | // GPS ---> mbed ---> Radio |
adwiens | 4:ed77fd381cc5 | 14 | // |
adwiens | 5:dcf952c7e6b3 | 15 | // ========================================================================== |
adwiens | 4:ed77fd381cc5 | 16 | |
adwiens | 0:dbb85bfd22fd | 17 | #include "mbed.h" |
adwiens | 0:dbb85bfd22fd | 18 | #include "rtos.h" // mbed real time os library |
adwiens | 0:dbb85bfd22fd | 19 | #include "dsp.h" // mbed digital signal processing library |
adwiens | 0:dbb85bfd22fd | 20 | #include <cstring> |
adwiens | 0:dbb85bfd22fd | 21 | #include <string> |
adwiens | 0:dbb85bfd22fd | 22 | #include <iostream> |
adwiens | 0:dbb85bfd22fd | 23 | #include <sstream> |
adwiens | 0:dbb85bfd22fd | 24 | #include <map> |
adwiens | 0:dbb85bfd22fd | 25 | |
adwiens | 0:dbb85bfd22fd | 26 | #define GPS_CB_SIZE (16) /* size of gps circular buffer in characters */ |
adwiens | 1:07d7070e9252 | 27 | #define RTTY_CB_SIZE (2048) /* characters in rtty buffer */ |
adwiens | 0:dbb85bfd22fd | 28 | #define GPS_BAUD (9600) /* gps serial port speed in bps */ |
adwiens | 3:33d80761aa06 | 29 | #define RADIO_TX_WAIT (1000) /* time between radio transmissions in ms */ |
adwiens | 3:33d80761aa06 | 30 | #define PRINT_NMEA_WAIT (2000) /* time between console debug messages in ms */ |
adwiens | 3:33d80761aa06 | 31 | #define CALLSIGN_WAIT (600) /* time between sending amateur radio callsign in s */ |
adwiens | 3:33d80761aa06 | 32 | #define AUDIO_FS (8000) /* audio sample rate in hz */ |
adwiens | 3:33d80761aa06 | 33 | #define RTTY_BAUD (110) /* rtty bit rate in bps */ |
adwiens | 3:33d80761aa06 | 34 | #define MARK_FREQ (2850) /* mark frequency (1) in hz */ |
adwiens | 3:33d80761aa06 | 35 | #define SPACE_FREQ (2000) /* space frequency (0) in hz */ |
adwiens | 3:33d80761aa06 | 36 | #define AUDIO_VOL (1) /* range 0-1 */ |
adwiens | 3:33d80761aa06 | 37 | #define RTTY_NUM_ZEROS_BEFORE (25) /* number of empty characters to append before each rtty message. helps receivers sync. */ |
adwiens | 3:33d80761aa06 | 38 | #define RTTY_NUM_ZEROS_AFTER (8) /* number of empty characters to append after each rtty message. */ |
adwiens | 3:33d80761aa06 | 39 | #define CALLSIGN "KC0WYS" /* my amateur radio callsign */ |
adwiens | 3:33d80761aa06 | 40 | #define CALLSIGN_TAG "HAMID" /* 5-character tag used in the fake nmea sentence */ |
adwiens | 0:dbb85bfd22fd | 41 | |
adwiens | 0:dbb85bfd22fd | 42 | using namespace std; |
adwiens | 0:dbb85bfd22fd | 43 | |
adwiens | 4:ed77fd381cc5 | 44 | // mbed ports: |
adwiens | 0:dbb85bfd22fd | 45 | Serial pc(USBTX, USBRX); // pc serial port (via usb) |
adwiens | 4:ed77fd381cc5 | 46 | Serial gps(p9, p10); // gps serial port (uart3) |
adwiens | 4:ed77fd381cc5 | 47 | AnalogOut dac(p18); // mbed built-in digital to analog converter |
adwiens | 4:ed77fd381cc5 | 48 | DigitalOut ptt(p17); // radio push to talk button |
adwiens | 4:ed77fd381cc5 | 49 | |
adwiens | 4:ed77fd381cc5 | 50 | // Status leds: |
adwiens | 4:ed77fd381cc5 | 51 | DigitalOut gps_led(LED1); // gps status led |
adwiens | 4:ed77fd381cc5 | 52 | DigitalOut rtty_led(LED2); // tx status led |
adwiens | 0:dbb85bfd22fd | 53 | |
adwiens | 0:dbb85bfd22fd | 54 | // GPS variables: |
adwiens | 0:dbb85bfd22fd | 55 | char cb[GPS_CB_SIZE]; // c-string circular buffer for gps rx isr |
adwiens | 1:07d7070e9252 | 56 | int cb_isr_i = 0; // gps isr index |
adwiens | 1:07d7070e9252 | 57 | int cb_thr_i = 0; // gps thread index |
adwiens | 0:dbb85bfd22fd | 58 | stringstream rxbuf; // gps receive buffer |
adwiens | 0:dbb85bfd22fd | 59 | map<string,string> nmea_data; // most recent nmea sentences |
adwiens | 0:dbb85bfd22fd | 60 | Mutex nmea_data_mutex; // nmea data lock |
adwiens | 0:dbb85bfd22fd | 61 | |
adwiens | 0:dbb85bfd22fd | 62 | // RTTY variables: |
adwiens | 1:07d7070e9252 | 63 | char r_cb[RTTY_CB_SIZE]; // c-string circular buffer for rtty tx isr |
adwiens | 1:07d7070e9252 | 64 | int r_cb_isr_i = 0; // rtty isr index |
adwiens | 1:07d7070e9252 | 65 | int r_cb_thr_i = 0; // rtty thread index |
adwiens | 0:dbb85bfd22fd | 66 | float angle = 0.0; // current sine angle |
adwiens | 0:dbb85bfd22fd | 67 | int ifreq = MARK_FREQ; // instantaneous frequency |
adwiens | 0:dbb85bfd22fd | 68 | int bitn = -1; // current bit number |
adwiens | 0:dbb85bfd22fd | 69 | bool txen = false; // tx enable flag |
adwiens | 0:dbb85bfd22fd | 70 | |
adwiens | 4:ed77fd381cc5 | 71 | // Interrupt service routine for the GPS. |
adwiens | 0:dbb85bfd22fd | 72 | // It is called when the serial port receives a character, |
adwiens | 0:dbb85bfd22fd | 73 | // and it puts the character into a circular buffer for the gps thread. |
adwiens | 0:dbb85bfd22fd | 74 | void gps_rx_isr() |
adwiens | 0:dbb85bfd22fd | 75 | { |
adwiens | 0:dbb85bfd22fd | 76 | cb[cb_isr_i] = LPC_UART3->RBR; // avoid mutex lockup (https://mbed.org/forum/bugs-suggestions/topic/4217/) |
adwiens | 0:dbb85bfd22fd | 77 | if(++cb_isr_i >= GPS_CB_SIZE) cb_isr_i = 0; // loop circular buffer index |
adwiens | 0:dbb85bfd22fd | 78 | } |
adwiens | 0:dbb85bfd22fd | 79 | |
adwiens | 4:ed77fd381cc5 | 80 | // Reads new characters from the GPS circular |
adwiens | 0:dbb85bfd22fd | 81 | // buffer when the circular buffer is about 25 percent full |
adwiens | 0:dbb85bfd22fd | 82 | // and adds the new characters to a string containing the current |
adwiens | 0:dbb85bfd22fd | 83 | // nmea sentence. Each time a complete NMEA sentence is received |
adwiens | 0:dbb85bfd22fd | 84 | // this function updates a map containing K,V pairs. (The sentence |
adwiens | 0:dbb85bfd22fd | 85 | // type is the key and the value is the NMEA sentence.) |
adwiens | 0:dbb85bfd22fd | 86 | void gps_rx_thread(void const *argument) |
adwiens | 0:dbb85bfd22fd | 87 | { |
adwiens | 0:dbb85bfd22fd | 88 | while(1) { |
adwiens | 0:dbb85bfd22fd | 89 | gps_led = 0; |
adwiens | 0:dbb85bfd22fd | 90 | Thread::wait((GPS_CB_SIZE)*1000*8/4/GPS_BAUD); // wait until cb 25% full |
adwiens | 0:dbb85bfd22fd | 91 | gps_led = 1; |
adwiens | 0:dbb85bfd22fd | 92 | while(cb_thr_i != cb_isr_i) { |
adwiens | 0:dbb85bfd22fd | 93 | char c = cb[cb_thr_i]; |
adwiens | 0:dbb85bfd22fd | 94 | rxbuf << c; // add to string buffer |
adwiens | 0:dbb85bfd22fd | 95 | if(c == '\n') { // clear string buffer |
adwiens | 0:dbb85bfd22fd | 96 | const char *c = rxbuf.str().c_str(); |
adwiens | 0:dbb85bfd22fd | 97 | const char *l = strchr(c, '$'), *r = strchr(c, ','); |
adwiens | 0:dbb85bfd22fd | 98 | if(l != NULL && r != NULL && r > l) { // check valid limits |
adwiens | 0:dbb85bfd22fd | 99 | char ctype[6]; |
adwiens | 0:dbb85bfd22fd | 100 | memcpy(ctype,l+1,5); |
adwiens | 0:dbb85bfd22fd | 101 | ctype[5] = 0; |
adwiens | 0:dbb85bfd22fd | 102 | string type = ctype; |
adwiens | 0:dbb85bfd22fd | 103 | nmea_data_mutex.lock(); |
adwiens | 0:dbb85bfd22fd | 104 | nmea_data[type] = rxbuf.str(); // update map |
adwiens | 0:dbb85bfd22fd | 105 | nmea_data_mutex.unlock(); |
adwiens | 0:dbb85bfd22fd | 106 | } |
adwiens | 0:dbb85bfd22fd | 107 | rxbuf.str(""); |
adwiens | 0:dbb85bfd22fd | 108 | rxbuf.clear(); |
adwiens | 0:dbb85bfd22fd | 109 | } |
adwiens | 1:07d7070e9252 | 110 | if(++cb_thr_i >= GPS_CB_SIZE) cb_thr_i = 0; // incr/loop circular buffer index |
adwiens | 0:dbb85bfd22fd | 111 | } |
adwiens | 0:dbb85bfd22fd | 112 | } |
adwiens | 0:dbb85bfd22fd | 113 | } |
adwiens | 0:dbb85bfd22fd | 114 | |
adwiens | 1:07d7070e9252 | 115 | // Writes individual audio samples to the dac. It uses the |
adwiens | 1:07d7070e9252 | 116 | // instantaneous frequency from the bit ticker. |
adwiens | 0:dbb85bfd22fd | 117 | void rtty_sample_tick() |
adwiens | 0:dbb85bfd22fd | 118 | { |
adwiens | 1:07d7070e9252 | 119 | if(txen) { |
adwiens | 0:dbb85bfd22fd | 120 | angle += 2 * PI * ifreq / AUDIO_FS; |
adwiens | 0:dbb85bfd22fd | 121 | if(angle > 2 * PI) angle -= 2*PI; |
adwiens | 0:dbb85bfd22fd | 122 | dac = (arm_sin_f32(angle) + 1.0) / 2.0 * AUDIO_VOL; // write sample to dac |
adwiens | 2:1f19f8e52c75 | 123 | ptt = 0; |
adwiens | 3:33d80761aa06 | 124 | rtty_led = !rtty_led; |
adwiens | 1:07d7070e9252 | 125 | } else { |
adwiens | 0:dbb85bfd22fd | 126 | dac = 0; |
adwiens | 2:1f19f8e52c75 | 127 | ptt = 1; |
adwiens | 2:1f19f8e52c75 | 128 | rtty_led = 0; |
adwiens | 0:dbb85bfd22fd | 129 | } |
adwiens | 0:dbb85bfd22fd | 130 | } |
adwiens | 0:dbb85bfd22fd | 131 | |
adwiens | 1:07d7070e9252 | 132 | // Handles whether the current rtty bit is a mark or a space. |
adwiens | 1:07d7070e9252 | 133 | // It reads one character at a time from the rtty circular buffer and sets |
adwiens | 1:07d7070e9252 | 134 | // the instantaneous frequency of the sample ticker for each bit. (1==mark, |
adwiens | 1:07d7070e9252 | 135 | // 0==space.) |
adwiens | 0:dbb85bfd22fd | 136 | void rtty_bit_tick() |
adwiens | 0:dbb85bfd22fd | 137 | { |
adwiens | 1:07d7070e9252 | 138 | if(bitn < 0) { |
adwiens | 1:07d7070e9252 | 139 | txen = (r_cb_isr_i != r_cb_thr_i); |
adwiens | 1:07d7070e9252 | 140 | if(txen) { |
adwiens | 1:07d7070e9252 | 141 | ifreq = SPACE_FREQ; // start bit |
adwiens | 1:07d7070e9252 | 142 | if(++r_cb_isr_i >= RTTY_CB_SIZE) r_cb_isr_i = 0; // incr/loop circular buffer index |
adwiens | 0:dbb85bfd22fd | 143 | ++bitn; |
adwiens | 0:dbb85bfd22fd | 144 | } |
adwiens | 1:07d7070e9252 | 145 | } else if(bitn < 8 && txen) { |
adwiens | 1:07d7070e9252 | 146 | ifreq = ((r_cb[r_cb_isr_i] & (1<<(bitn++))) == 0) ? SPACE_FREQ : MARK_FREQ; // data bit |
adwiens | 1:07d7070e9252 | 147 | } else if(txen) { |
adwiens | 1:07d7070e9252 | 148 | ifreq = MARK_FREQ; // stop bit |
adwiens | 1:07d7070e9252 | 149 | bitn = -1; |
adwiens | 0:dbb85bfd22fd | 150 | } |
adwiens | 0:dbb85bfd22fd | 151 | } |
adwiens | 0:dbb85bfd22fd | 152 | |
adwiens | 3:33d80761aa06 | 153 | // Adds NMEA sentences periodically to a buffer for the other RTTY functions to process. |
adwiens | 0:dbb85bfd22fd | 154 | void rtty_tx_thread(void const *argument) |
adwiens | 0:dbb85bfd22fd | 155 | { |
adwiens | 0:dbb85bfd22fd | 156 | while(1) { |
adwiens | 0:dbb85bfd22fd | 157 | Thread::wait(RADIO_TX_WAIT); // wait for a certain amount of time between transmissions |
adwiens | 1:07d7070e9252 | 158 | stringstream txbuf; |
adwiens | 0:dbb85bfd22fd | 159 | nmea_data_mutex.lock(); |
adwiens | 0:dbb85bfd22fd | 160 | for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) { |
adwiens | 0:dbb85bfd22fd | 161 | txbuf << (iter->second); // fill the packet with the most recent nmea sentences |
adwiens | 0:dbb85bfd22fd | 162 | } |
adwiens | 3:33d80761aa06 | 163 | nmea_data.clear(); // empty the map |
adwiens | 0:dbb85bfd22fd | 164 | nmea_data_mutex.unlock(); |
adwiens | 3:33d80761aa06 | 165 | for(int i = 0; i < RTTY_NUM_ZEROS_BEFORE; ++i) { // add empty characters |
adwiens | 3:33d80761aa06 | 166 | if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index |
adwiens | 3:33d80761aa06 | 167 | r_cb[r_cb_thr_i] = 0; // append a zero character |
adwiens | 3:33d80761aa06 | 168 | } |
adwiens | 1:07d7070e9252 | 169 | for(const char* it = txbuf.str().c_str(); *it; ++it) { // add all characters to buffer |
adwiens | 1:07d7070e9252 | 170 | if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index |
adwiens | 1:07d7070e9252 | 171 | r_cb[r_cb_thr_i] = *it; |
adwiens | 1:07d7070e9252 | 172 | } |
adwiens | 3:33d80761aa06 | 173 | for(int i = 0; i < RTTY_NUM_ZEROS_AFTER; ++i) { // add empty characters |
adwiens | 3:33d80761aa06 | 174 | if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index |
adwiens | 3:33d80761aa06 | 175 | r_cb[r_cb_thr_i] = 0; // append a zero character |
adwiens | 3:33d80761aa06 | 176 | } |
adwiens | 2:1f19f8e52c75 | 177 | while(!txen) Thread::wait(100); // wait for transmission to start |
adwiens | 2:1f19f8e52c75 | 178 | while( txen) Thread::wait(100); // wait for transmission to end |
adwiens | 0:dbb85bfd22fd | 179 | } |
adwiens | 0:dbb85bfd22fd | 180 | } |
adwiens | 0:dbb85bfd22fd | 181 | |
adwiens | 3:33d80761aa06 | 182 | // Periodically inserts HAM radio callsign into NMEA sentence map. |
adwiens | 3:33d80761aa06 | 183 | // Required by FCC to send callsign at least every 30 minutes. |
adwiens | 3:33d80761aa06 | 184 | void insert_callsign_thread(void const *argument) |
adwiens | 3:33d80761aa06 | 185 | { |
adwiens | 3:33d80761aa06 | 186 | while(1) { |
adwiens | 3:33d80761aa06 | 187 | nmea_data_mutex.lock(); |
adwiens | 3:33d80761aa06 | 188 | nmea_data[CALLSIGN_TAG] = "$" CALLSIGN_TAG "," CALLSIGN "," CALLSIGN "," CALLSIGN ",*00\r\n"; // dummy nmea sentence |
adwiens | 3:33d80761aa06 | 189 | nmea_data_mutex.unlock(); |
adwiens | 3:33d80761aa06 | 190 | for(int i = 0; i < CALLSIGN_WAIT; ++i) Thread::wait(1000); // wait for CALLSIGN_WAIT seconds |
adwiens | 3:33d80761aa06 | 191 | } |
adwiens | 3:33d80761aa06 | 192 | } |
adwiens | 3:33d80761aa06 | 193 | |
adwiens | 4:ed77fd381cc5 | 194 | // Writes NMEA sentences to the console. Useful for debug. |
adwiens | 1:07d7070e9252 | 195 | void print_nmea_thread(void const *argument) |
adwiens | 0:dbb85bfd22fd | 196 | { |
adwiens | 1:07d7070e9252 | 197 | while(1) { |
adwiens | 1:07d7070e9252 | 198 | nmea_data_mutex.lock(); |
adwiens | 1:07d7070e9252 | 199 | for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) { |
adwiens | 1:07d7070e9252 | 200 | cout << (iter->second); |
adwiens | 1:07d7070e9252 | 201 | } |
adwiens | 1:07d7070e9252 | 202 | nmea_data_mutex.unlock(); |
adwiens | 1:07d7070e9252 | 203 | cout << endl; |
adwiens | 1:07d7070e9252 | 204 | Thread::wait(PRINT_NMEA_WAIT); |
adwiens | 0:dbb85bfd22fd | 205 | } |
adwiens | 0:dbb85bfd22fd | 206 | } |
adwiens | 0:dbb85bfd22fd | 207 | |
adwiens | 0:dbb85bfd22fd | 208 | int main() |
adwiens | 0:dbb85bfd22fd | 209 | { |
adwiens | 0:dbb85bfd22fd | 210 | Ticker sample_tick, bit_tick; |
adwiens | 0:dbb85bfd22fd | 211 | Thread gps_thread(gps_rx_thread); // gps receive thread |
adwiens | 0:dbb85bfd22fd | 212 | Thread rtty_thread(rtty_tx_thread); // rtty transmit thread |
adwiens | 1:07d7070e9252 | 213 | Thread print_thread(print_nmea_thread); // debug print thread |
adwiens | 3:33d80761aa06 | 214 | Thread callsign_thread(insert_callsign_thread); // amateur callsign thread |
adwiens | 0:dbb85bfd22fd | 215 | gps.baud(GPS_BAUD); // set gps bit rate |
adwiens | 0:dbb85bfd22fd | 216 | gps.attach(&gps_rx_isr); // set up gps receive interrupt service routine |
adwiens | 0:dbb85bfd22fd | 217 | sample_tick.attach_us(&rtty_sample_tick,1000000/AUDIO_FS); // begin generating audio |
adwiens | 0:dbb85bfd22fd | 218 | bit_tick.attach_us(&rtty_bit_tick,1000000/RTTY_BAUD); // begin sending characters |
adwiens | 1:07d7070e9252 | 219 | while(1); // idle forever |
adwiens | 0:dbb85bfd22fd | 220 | } |