Generates RTTY from a GPS receiver

Dependencies:   mbed-dsp mbed-rtos mbed

Committer:
adwiens
Date:
Sat Jan 11 19:09:46 2014 +0000
Revision:
5:dcf952c7e6b3
Parent:
4:ed77fd381cc5
again

Who changed what in which revision?

UserRevisionLine numberNew 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 }