
Generates RTTY from a GPS receiver
Dependencies: mbed-dsp mbed-rtos mbed
main.cpp
- Committer:
- adwiens
- Date:
- 2014-01-11
- Revision:
- 5:dcf952c7e6b3
- Parent:
- 4:ed77fd381cc5
File content as of revision 5:dcf952c7e6b3:
// Author: Andrew Wiens (adwiens.com) // Date: 11 Jan 2014 // // ========================================================================== // // GPS2RTTY // // Using the mbed RTOS and DSP libraries, GPS2RTTY generates RTTY messages // containing raw NMEA sentences from a GPS receiver. // // No extra hardware needed! Audio is generated with the mbed's built-in DAC. // // GPS ---> mbed ---> Radio // // ========================================================================== #include "mbed.h" #include "rtos.h" // mbed real time os library #include "dsp.h" // mbed digital signal processing library #include <cstring> #include <string> #include <iostream> #include <sstream> #include <map> #define GPS_CB_SIZE (16) /* size of gps circular buffer in characters */ #define RTTY_CB_SIZE (2048) /* characters in rtty buffer */ #define GPS_BAUD (9600) /* gps serial port speed in bps */ #define RADIO_TX_WAIT (1000) /* time between radio transmissions in ms */ #define PRINT_NMEA_WAIT (2000) /* time between console debug messages in ms */ #define CALLSIGN_WAIT (600) /* time between sending amateur radio callsign in s */ #define AUDIO_FS (8000) /* audio sample rate in hz */ #define RTTY_BAUD (110) /* rtty bit rate in bps */ #define MARK_FREQ (2850) /* mark frequency (1) in hz */ #define SPACE_FREQ (2000) /* space frequency (0) in hz */ #define AUDIO_VOL (1) /* range 0-1 */ #define RTTY_NUM_ZEROS_BEFORE (25) /* number of empty characters to append before each rtty message. helps receivers sync. */ #define RTTY_NUM_ZEROS_AFTER (8) /* number of empty characters to append after each rtty message. */ #define CALLSIGN "KC0WYS" /* my amateur radio callsign */ #define CALLSIGN_TAG "HAMID" /* 5-character tag used in the fake nmea sentence */ using namespace std; // mbed ports: Serial pc(USBTX, USBRX); // pc serial port (via usb) Serial gps(p9, p10); // gps serial port (uart3) AnalogOut dac(p18); // mbed built-in digital to analog converter DigitalOut ptt(p17); // radio push to talk button // Status leds: DigitalOut gps_led(LED1); // gps status led DigitalOut rtty_led(LED2); // tx status led // GPS variables: char cb[GPS_CB_SIZE]; // c-string circular buffer for gps rx isr int cb_isr_i = 0; // gps isr index int cb_thr_i = 0; // gps thread index stringstream rxbuf; // gps receive buffer map<string,string> nmea_data; // most recent nmea sentences Mutex nmea_data_mutex; // nmea data lock // RTTY variables: char r_cb[RTTY_CB_SIZE]; // c-string circular buffer for rtty tx isr int r_cb_isr_i = 0; // rtty isr index int r_cb_thr_i = 0; // rtty thread index float angle = 0.0; // current sine angle int ifreq = MARK_FREQ; // instantaneous frequency int bitn = -1; // current bit number bool txen = false; // tx enable flag // Interrupt service routine for the GPS. // It is called when the serial port receives a character, // and it puts the character into a circular buffer for the gps thread. void gps_rx_isr() { cb[cb_isr_i] = LPC_UART3->RBR; // avoid mutex lockup (https://mbed.org/forum/bugs-suggestions/topic/4217/) if(++cb_isr_i >= GPS_CB_SIZE) cb_isr_i = 0; // loop circular buffer index } // Reads new characters from the GPS circular // buffer when the circular buffer is about 25 percent full // and adds the new characters to a string containing the current // nmea sentence. Each time a complete NMEA sentence is received // this function updates a map containing K,V pairs. (The sentence // type is the key and the value is the NMEA sentence.) void gps_rx_thread(void const *argument) { while(1) { gps_led = 0; Thread::wait((GPS_CB_SIZE)*1000*8/4/GPS_BAUD); // wait until cb 25% full gps_led = 1; while(cb_thr_i != cb_isr_i) { char c = cb[cb_thr_i]; rxbuf << c; // add to string buffer if(c == '\n') { // clear string buffer const char *c = rxbuf.str().c_str(); const char *l = strchr(c, '$'), *r = strchr(c, ','); if(l != NULL && r != NULL && r > l) { // check valid limits char ctype[6]; memcpy(ctype,l+1,5); ctype[5] = 0; string type = ctype; nmea_data_mutex.lock(); nmea_data[type] = rxbuf.str(); // update map nmea_data_mutex.unlock(); } rxbuf.str(""); rxbuf.clear(); } if(++cb_thr_i >= GPS_CB_SIZE) cb_thr_i = 0; // incr/loop circular buffer index } } } // Writes individual audio samples to the dac. It uses the // instantaneous frequency from the bit ticker. void rtty_sample_tick() { if(txen) { angle += 2 * PI * ifreq / AUDIO_FS; if(angle > 2 * PI) angle -= 2*PI; dac = (arm_sin_f32(angle) + 1.0) / 2.0 * AUDIO_VOL; // write sample to dac ptt = 0; rtty_led = !rtty_led; } else { dac = 0; ptt = 1; rtty_led = 0; } } // Handles whether the current rtty bit is a mark or a space. // It reads one character at a time from the rtty circular buffer and sets // the instantaneous frequency of the sample ticker for each bit. (1==mark, // 0==space.) void rtty_bit_tick() { if(bitn < 0) { txen = (r_cb_isr_i != r_cb_thr_i); if(txen) { ifreq = SPACE_FREQ; // start bit if(++r_cb_isr_i >= RTTY_CB_SIZE) r_cb_isr_i = 0; // incr/loop circular buffer index ++bitn; } } else if(bitn < 8 && txen) { ifreq = ((r_cb[r_cb_isr_i] & (1<<(bitn++))) == 0) ? SPACE_FREQ : MARK_FREQ; // data bit } else if(txen) { ifreq = MARK_FREQ; // stop bit bitn = -1; } } // Adds NMEA sentences periodically to a buffer for the other RTTY functions to process. void rtty_tx_thread(void const *argument) { while(1) { Thread::wait(RADIO_TX_WAIT); // wait for a certain amount of time between transmissions stringstream txbuf; nmea_data_mutex.lock(); for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) { txbuf << (iter->second); // fill the packet with the most recent nmea sentences } nmea_data.clear(); // empty the map nmea_data_mutex.unlock(); for(int i = 0; i < RTTY_NUM_ZEROS_BEFORE; ++i) { // add empty characters if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index r_cb[r_cb_thr_i] = 0; // append a zero character } for(const char* it = txbuf.str().c_str(); *it; ++it) { // add all characters to buffer if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index r_cb[r_cb_thr_i] = *it; } for(int i = 0; i < RTTY_NUM_ZEROS_AFTER; ++i) { // add empty characters if(++r_cb_thr_i >= RTTY_CB_SIZE) r_cb_thr_i = 0; // incr/loop circular buffer index r_cb[r_cb_thr_i] = 0; // append a zero character } while(!txen) Thread::wait(100); // wait for transmission to start while( txen) Thread::wait(100); // wait for transmission to end } } // Periodically inserts HAM radio callsign into NMEA sentence map. // Required by FCC to send callsign at least every 30 minutes. void insert_callsign_thread(void const *argument) { while(1) { nmea_data_mutex.lock(); nmea_data[CALLSIGN_TAG] = "$" CALLSIGN_TAG "," CALLSIGN "," CALLSIGN "," CALLSIGN ",*00\r\n"; // dummy nmea sentence nmea_data_mutex.unlock(); for(int i = 0; i < CALLSIGN_WAIT; ++i) Thread::wait(1000); // wait for CALLSIGN_WAIT seconds } } // Writes NMEA sentences to the console. Useful for debug. void print_nmea_thread(void const *argument) { while(1) { nmea_data_mutex.lock(); for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) { cout << (iter->second); } nmea_data_mutex.unlock(); cout << endl; Thread::wait(PRINT_NMEA_WAIT); } } int main() { Ticker sample_tick, bit_tick; Thread gps_thread(gps_rx_thread); // gps receive thread Thread rtty_thread(rtty_tx_thread); // rtty transmit thread Thread print_thread(print_nmea_thread); // debug print thread Thread callsign_thread(insert_callsign_thread); // amateur callsign thread gps.baud(GPS_BAUD); // set gps bit rate gps.attach(&gps_rx_isr); // set up gps receive interrupt service routine sample_tick.attach_us(&rtty_sample_tick,1000000/AUDIO_FS); // begin generating audio bit_tick.attach_us(&rtty_bit_tick,1000000/RTTY_BAUD); // begin sending characters while(1); // idle forever }