Andrew Wiens / Mbed 2 deprecated gps2rtty

Dependencies:   mbed-dsp mbed-rtos mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 // Author:  Andrew Wiens (adwiens.com)
00002 // Date:    11 Jan 2014
00003 //
00004 // ==========================================================================
00005 // 
00006 // GPS2RTTY
00007 //
00008 // Using the mbed RTOS and DSP libraries, GPS2RTTY generates RTTY messages
00009 // containing raw NMEA sentences from a GPS receiver.
00010 //
00011 // No extra hardware needed! Audio is generated with the mbed's built-in DAC.
00012 //
00013 // GPS ---> mbed ---> Radio
00014 //
00015 // ==========================================================================
00016 
00017 #include "mbed.h"
00018 #include "rtos.h" // mbed real time os library
00019 #include "dsp.h" // mbed digital signal processing library
00020 #include <cstring>
00021 #include <string>
00022 #include <iostream>
00023 #include <sstream>
00024 #include <map>
00025 
00026 #define GPS_CB_SIZE (16) /* size of gps circular buffer in characters */
00027 #define RTTY_CB_SIZE (2048) /* characters in rtty buffer */
00028 #define GPS_BAUD (9600) /* gps serial port speed in bps */
00029 #define RADIO_TX_WAIT (1000) /* time between radio transmissions in ms */
00030 #define PRINT_NMEA_WAIT (2000) /* time between console debug messages in ms */
00031 #define CALLSIGN_WAIT (600) /* time between sending amateur radio callsign in s */
00032 #define AUDIO_FS (8000) /* audio sample rate in hz */
00033 #define RTTY_BAUD (110) /* rtty bit rate in bps */
00034 #define MARK_FREQ (2850) /* mark frequency (1) in hz */
00035 #define SPACE_FREQ (2000) /* space frequency (0) in hz */
00036 #define AUDIO_VOL (1) /* range 0-1 */
00037 #define RTTY_NUM_ZEROS_BEFORE (25) /* number of empty characters to append before each rtty message. helps receivers sync. */
00038 #define RTTY_NUM_ZEROS_AFTER (8) /* number of empty characters to append after each rtty message. */
00039 #define CALLSIGN "KC0WYS" /* my amateur radio callsign */
00040 #define CALLSIGN_TAG "HAMID" /* 5-character tag used in the fake nmea sentence */
00041 
00042 using namespace std;
00043 
00044 // mbed ports:
00045 Serial pc(USBTX, USBRX); // pc serial port (via usb)
00046 Serial gps(p9, p10); // gps serial port (uart3)
00047 AnalogOut dac(p18); // mbed built-in digital to analog converter
00048 DigitalOut ptt(p17); // radio push to talk button
00049 
00050 // Status leds:
00051 DigitalOut gps_led(LED1); // gps status led
00052 DigitalOut rtty_led(LED2); // tx status led
00053 
00054 // GPS variables:
00055 char cb[GPS_CB_SIZE]; // c-string circular buffer for gps rx isr
00056 int cb_isr_i = 0; // gps isr index
00057 int cb_thr_i = 0; // gps thread index
00058 stringstream rxbuf; // gps receive buffer
00059 map<string,string> nmea_data; // most recent nmea sentences
00060 Mutex nmea_data_mutex; // nmea data lock
00061 
00062 // RTTY variables:
00063 char r_cb[RTTY_CB_SIZE]; // c-string circular buffer for rtty tx isr
00064 int r_cb_isr_i = 0; // rtty isr index
00065 int r_cb_thr_i = 0; // rtty thread index
00066 float angle = 0.0; // current sine angle
00067 int ifreq = MARK_FREQ; // instantaneous frequency
00068 int bitn = -1; // current bit number
00069 bool txen = false; // tx enable flag
00070 
00071 // Interrupt service routine for the GPS.
00072 // It is called when the serial port receives a character,
00073 // and it puts the character into a circular buffer for the gps thread.
00074 void gps_rx_isr()
00075 {
00076     cb[cb_isr_i] = LPC_UART3->RBR; // avoid mutex lockup (https://mbed.org/forum/bugs-suggestions/topic/4217/)
00077     if(++cb_isr_i >= GPS_CB_SIZE) cb_isr_i = 0; // loop circular buffer index
00078 }
00079 
00080 // Reads new characters from the GPS circular
00081 // buffer when the circular buffer is about 25 percent full
00082 // and adds the new characters to a string containing the current
00083 // nmea sentence. Each time a complete NMEA sentence is received
00084 // this function updates a map containing K,V pairs. (The sentence
00085 // type is the key and the value is the NMEA sentence.)
00086 void gps_rx_thread(void const *argument)
00087 {
00088     while(1) {
00089         gps_led = 0;
00090         Thread::wait((GPS_CB_SIZE)*1000*8/4/GPS_BAUD); // wait until cb 25% full
00091         gps_led = 1;
00092         while(cb_thr_i != cb_isr_i) {
00093             char c = cb[cb_thr_i];
00094             rxbuf << c; // add to string buffer
00095             if(c == '\n') { // clear string buffer
00096                 const char *c = rxbuf.str().c_str();
00097                 const char *l = strchr(c, '$'), *r = strchr(c, ',');
00098                 if(l != NULL && r != NULL && r > l) { // check valid limits
00099                     char ctype[6];
00100                     memcpy(ctype,l+1,5);
00101                     ctype[5] = 0;
00102                     string type = ctype;
00103                     nmea_data_mutex.lock();
00104                     nmea_data[type] = rxbuf.str(); // update map
00105                     nmea_data_mutex.unlock();
00106                 }
00107                 rxbuf.str("");
00108                 rxbuf.clear();
00109             }
00110             if(++cb_thr_i >= GPS_CB_SIZE) cb_thr_i = 0; // incr/loop circular buffer index
00111         }
00112     }
00113 }
00114 
00115 // Writes individual audio samples to the dac. It uses the
00116 // instantaneous frequency from the bit ticker.
00117 void rtty_sample_tick()
00118 {
00119     if(txen) {
00120         angle += 2 * PI * ifreq / AUDIO_FS;
00121         if(angle > 2 * PI) angle -= 2*PI;
00122         dac = (arm_sin_f32(angle) + 1.0) / 2.0 * AUDIO_VOL; // write sample to dac
00123         ptt = 0;
00124         rtty_led = !rtty_led;
00125     } else {
00126         dac = 0;
00127         ptt = 1;
00128         rtty_led = 0;
00129     }
00130 }
00131 
00132 // Handles whether the current rtty bit is a mark or a space.
00133 // It reads one character at a time from the rtty circular buffer and sets
00134 // the instantaneous frequency of the sample ticker for each bit. (1==mark,
00135 // 0==space.)
00136 void rtty_bit_tick()
00137 {
00138     if(bitn < 0) {
00139         txen = (r_cb_isr_i != r_cb_thr_i);
00140         if(txen) {
00141             ifreq = SPACE_FREQ; // start bit
00142             if(++r_cb_isr_i >= RTTY_CB_SIZE) r_cb_isr_i = 0; // incr/loop circular buffer index
00143             ++bitn;
00144         }
00145     } else if(bitn < 8 && txen) {
00146         ifreq = ((r_cb[r_cb_isr_i] & (1<<(bitn++))) == 0) ? SPACE_FREQ : MARK_FREQ; // data bit
00147     } else if(txen) {
00148         ifreq = MARK_FREQ; // stop bit
00149         bitn = -1;
00150     }
00151 }
00152 
00153 // Adds NMEA sentences periodically to a buffer for the other RTTY functions to process.
00154 void rtty_tx_thread(void const *argument)
00155 {
00156     while(1) {
00157         Thread::wait(RADIO_TX_WAIT); // wait for a certain amount of time between transmissions
00158         stringstream txbuf;
00159         nmea_data_mutex.lock();
00160         for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) {
00161             txbuf << (iter->second); // fill the packet with the most recent nmea sentences
00162         }
00163         nmea_data.clear(); // empty the map
00164         nmea_data_mutex.unlock();
00165         for(int i = 0; i < RTTY_NUM_ZEROS_BEFORE; ++i) { // add empty characters
00166             if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index
00167             r_cb[r_cb_thr_i] = 0; // append a zero character
00168         }
00169         for(const char* it = txbuf.str().c_str(); *it; ++it) { // add all characters to buffer
00170             if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index
00171             r_cb[r_cb_thr_i] = *it;
00172         }
00173         for(int i = 0; i < RTTY_NUM_ZEROS_AFTER; ++i) { // add empty characters
00174             if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index
00175             r_cb[r_cb_thr_i] = 0; // append a zero character
00176         }
00177         while(!txen) Thread::wait(100); // wait for transmission to start
00178         while( txen) Thread::wait(100); // wait for transmission to end
00179     }
00180 }
00181 
00182 // Periodically inserts HAM radio callsign into NMEA sentence map.
00183 // Required by FCC to send callsign at least every 30 minutes.
00184 void insert_callsign_thread(void const *argument)
00185 {
00186     while(1) {
00187         nmea_data_mutex.lock();
00188         nmea_data[CALLSIGN_TAG] = "$" CALLSIGN_TAG "," CALLSIGN "," CALLSIGN "," CALLSIGN ",*00\r\n"; // dummy nmea sentence
00189         nmea_data_mutex.unlock();
00190         for(int i = 0; i < CALLSIGN_WAIT; ++i) Thread::wait(1000); // wait for CALLSIGN_WAIT seconds
00191     }
00192 }
00193 
00194 // Writes NMEA sentences to the console. Useful for debug.
00195 void print_nmea_thread(void const *argument)
00196 {
00197     while(1) {
00198         nmea_data_mutex.lock();
00199         for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) {
00200             cout << (iter->second);
00201         }
00202         nmea_data_mutex.unlock();
00203         cout << endl;
00204         Thread::wait(PRINT_NMEA_WAIT);
00205     }
00206 }
00207 
00208 int main()
00209 {
00210     Ticker sample_tick, bit_tick;
00211     Thread gps_thread(gps_rx_thread); // gps receive thread
00212     Thread rtty_thread(rtty_tx_thread); // rtty transmit thread
00213     Thread print_thread(print_nmea_thread); // debug print thread
00214     Thread callsign_thread(insert_callsign_thread); // amateur callsign thread
00215     gps.baud(GPS_BAUD); // set gps bit rate
00216     gps.attach(&gps_rx_isr); // set up gps receive interrupt service routine
00217     sample_tick.attach_us(&rtty_sample_tick,1000000/AUDIO_FS); // begin generating audio
00218     bit_tick.attach_us(&rtty_bit_tick,1000000/RTTY_BAUD); // begin sending characters
00219     while(1); // idle forever
00220 }