Generates RTTY from a GPS receiver
Dependencies: mbed-dsp mbed-rtos mbed
main.cpp@0:dbb85bfd22fd, 2014-01-11 (annotated)
- Committer:
- adwiens
- Date:
- Sat Jan 11 01:55:42 2014 +0000
- Revision:
- 0:dbb85bfd22fd
- Child:
- 1:07d7070e9252
First commit
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
adwiens | 0:dbb85bfd22fd | 1 | #include "mbed.h" |
adwiens | 0:dbb85bfd22fd | 2 | #include "rtos.h" // mbed real time os library |
adwiens | 0:dbb85bfd22fd | 3 | #include "dsp.h" // mbed digital signal processing library |
adwiens | 0:dbb85bfd22fd | 4 | #include <cstring> |
adwiens | 0:dbb85bfd22fd | 5 | #include <string> |
adwiens | 0:dbb85bfd22fd | 6 | #include <iostream> |
adwiens | 0:dbb85bfd22fd | 7 | #include <sstream> |
adwiens | 0:dbb85bfd22fd | 8 | #include <map> |
adwiens | 0:dbb85bfd22fd | 9 | |
adwiens | 0:dbb85bfd22fd | 10 | #define GPS_CB_SIZE (16) /* size of gps circular buffer in characters */ |
adwiens | 0:dbb85bfd22fd | 11 | #define GPS_BAUD (9600) /* gps serial port speed in bps */ |
adwiens | 0:dbb85bfd22fd | 12 | #define RADIO_TX_WAIT (2000) /* time between radio transmissions in ms */ |
adwiens | 0:dbb85bfd22fd | 13 | #define RADIO_KEYUP_DELAY (1000) /* time to wait for radio transmitter to turn on */ |
adwiens | 0:dbb85bfd22fd | 14 | #define AUDIO_FS (22050) /* audio sample rate in hz */ |
adwiens | 0:dbb85bfd22fd | 15 | #define RTTY_BAUD (45.45) /* rtty bit rate in bps */ |
adwiens | 0:dbb85bfd22fd | 16 | #define MARK_FREQ (2125) /* mark frequency (1) in hz */ |
adwiens | 0:dbb85bfd22fd | 17 | #define SPACE_FREQ (2295) /* space frequency (0) in hz */ |
adwiens | 0:dbb85bfd22fd | 18 | #define AUDIO_VOL (0.25) /* range 0-1 */ |
adwiens | 0:dbb85bfd22fd | 19 | |
adwiens | 0:dbb85bfd22fd | 20 | using namespace std; |
adwiens | 0:dbb85bfd22fd | 21 | |
adwiens | 0:dbb85bfd22fd | 22 | Serial pc(USBTX, USBRX); // pc serial port (via usb) |
adwiens | 0:dbb85bfd22fd | 23 | |
adwiens | 0:dbb85bfd22fd | 24 | // GPS variables: |
adwiens | 0:dbb85bfd22fd | 25 | char cb[GPS_CB_SIZE]; // c-string circular buffer for gps rx isr |
adwiens | 0:dbb85bfd22fd | 26 | int cb_isr_i = 0; // isr index |
adwiens | 0:dbb85bfd22fd | 27 | int cb_thr_i = 0; // thread index |
adwiens | 0:dbb85bfd22fd | 28 | DigitalOut gps_led(LED1); // gps status led |
adwiens | 0:dbb85bfd22fd | 29 | Serial gps(p9, p10); // gps serial port (uart3) |
adwiens | 0:dbb85bfd22fd | 30 | stringstream rxbuf; // gps receive buffer |
adwiens | 0:dbb85bfd22fd | 31 | map<string,string> nmea_data; // most recent nmea sentences |
adwiens | 0:dbb85bfd22fd | 32 | Mutex nmea_data_mutex; // nmea data lock |
adwiens | 0:dbb85bfd22fd | 33 | |
adwiens | 0:dbb85bfd22fd | 34 | // RTTY variables: |
adwiens | 0:dbb85bfd22fd | 35 | AnalogOut dac(p18); // mbed built-in digital to analog converter |
adwiens | 0:dbb85bfd22fd | 36 | DigitalOut ptt(p17); // radio push to talk button |
adwiens | 0:dbb85bfd22fd | 37 | DigitalOut tx_led(LED2); // tx status led |
adwiens | 0:dbb85bfd22fd | 38 | stringstream txbuf; // rtty tx buffer |
adwiens | 0:dbb85bfd22fd | 39 | string txbufstr; |
adwiens | 0:dbb85bfd22fd | 40 | const char *txchar = NULL; // current character to transmit |
adwiens | 0:dbb85bfd22fd | 41 | float angle = 0.0; // current sine angle |
adwiens | 0:dbb85bfd22fd | 42 | int ifreq = MARK_FREQ; // instantaneous frequency |
adwiens | 0:dbb85bfd22fd | 43 | int bitn = -1; // current bit number |
adwiens | 0:dbb85bfd22fd | 44 | bool txen = false; // tx enable flag |
adwiens | 0:dbb85bfd22fd | 45 | |
adwiens | 0:dbb85bfd22fd | 46 | // This function is the interrupt service routine for the gps. |
adwiens | 0:dbb85bfd22fd | 47 | // It is called when the serial port receives a character, |
adwiens | 0:dbb85bfd22fd | 48 | // and it puts the character into a circular buffer for the gps thread. |
adwiens | 0:dbb85bfd22fd | 49 | void gps_rx_isr() |
adwiens | 0:dbb85bfd22fd | 50 | { |
adwiens | 0:dbb85bfd22fd | 51 | cb[cb_isr_i] = LPC_UART3->RBR; // avoid mutex lockup (https://mbed.org/forum/bugs-suggestions/topic/4217/) |
adwiens | 0:dbb85bfd22fd | 52 | if(++cb_isr_i >= GPS_CB_SIZE) cb_isr_i = 0; // loop circular buffer index |
adwiens | 0:dbb85bfd22fd | 53 | } |
adwiens | 0:dbb85bfd22fd | 54 | |
adwiens | 0:dbb85bfd22fd | 55 | // This function reads new characters from the gps circular |
adwiens | 0:dbb85bfd22fd | 56 | // buffer when the circular buffer is about 25 percent full |
adwiens | 0:dbb85bfd22fd | 57 | // and adds the new characters to a string containing the current |
adwiens | 0:dbb85bfd22fd | 58 | // nmea sentence. Each time a complete NMEA sentence is received |
adwiens | 0:dbb85bfd22fd | 59 | // this function updates a map containing K,V pairs. (The sentence |
adwiens | 0:dbb85bfd22fd | 60 | // type is the key and the value is the NMEA sentence.) |
adwiens | 0:dbb85bfd22fd | 61 | void gps_rx_thread(void const *argument) |
adwiens | 0:dbb85bfd22fd | 62 | { |
adwiens | 0:dbb85bfd22fd | 63 | while(1) { |
adwiens | 0:dbb85bfd22fd | 64 | gps_led = 0; |
adwiens | 0:dbb85bfd22fd | 65 | Thread::wait((GPS_CB_SIZE)*1000*8/4/GPS_BAUD); // wait until cb 25% full |
adwiens | 0:dbb85bfd22fd | 66 | gps_led = 1; |
adwiens | 0:dbb85bfd22fd | 67 | while(cb_thr_i != cb_isr_i) { |
adwiens | 0:dbb85bfd22fd | 68 | char c = cb[cb_thr_i]; |
adwiens | 0:dbb85bfd22fd | 69 | rxbuf << c; // add to string buffer |
adwiens | 0:dbb85bfd22fd | 70 | if(c == '\n') { // clear string buffer |
adwiens | 0:dbb85bfd22fd | 71 | const char *c = rxbuf.str().c_str(); |
adwiens | 0:dbb85bfd22fd | 72 | const char *l = strchr(c, '$'), *r = strchr(c, ','); |
adwiens | 0:dbb85bfd22fd | 73 | if(l != NULL && r != NULL && r > l) { // check valid limits |
adwiens | 0:dbb85bfd22fd | 74 | char ctype[6]; |
adwiens | 0:dbb85bfd22fd | 75 | memcpy(ctype,l+1,5); |
adwiens | 0:dbb85bfd22fd | 76 | ctype[5] = 0; |
adwiens | 0:dbb85bfd22fd | 77 | string type = ctype; |
adwiens | 0:dbb85bfd22fd | 78 | nmea_data_mutex.lock(); |
adwiens | 0:dbb85bfd22fd | 79 | nmea_data[type] = rxbuf.str(); // update map |
adwiens | 0:dbb85bfd22fd | 80 | nmea_data_mutex.unlock(); |
adwiens | 0:dbb85bfd22fd | 81 | } |
adwiens | 0:dbb85bfd22fd | 82 | rxbuf.str(""); |
adwiens | 0:dbb85bfd22fd | 83 | rxbuf.clear(); |
adwiens | 0:dbb85bfd22fd | 84 | } |
adwiens | 0:dbb85bfd22fd | 85 | if(++cb_thr_i >= GPS_CB_SIZE) cb_thr_i = 0; // loop circular buffer index |
adwiens | 0:dbb85bfd22fd | 86 | } |
adwiens | 0:dbb85bfd22fd | 87 | } |
adwiens | 0:dbb85bfd22fd | 88 | } |
adwiens | 0:dbb85bfd22fd | 89 | |
adwiens | 0:dbb85bfd22fd | 90 | // This function writes individual audio samples to the dac. |
adwiens | 0:dbb85bfd22fd | 91 | void rtty_sample_tick() |
adwiens | 0:dbb85bfd22fd | 92 | { |
adwiens | 0:dbb85bfd22fd | 93 | if(txen) |
adwiens | 0:dbb85bfd22fd | 94 | { |
adwiens | 0:dbb85bfd22fd | 95 | angle += 2 * PI * ifreq / AUDIO_FS; |
adwiens | 0:dbb85bfd22fd | 96 | if(angle > 2 * PI) angle -= 2*PI; |
adwiens | 0:dbb85bfd22fd | 97 | dac = (arm_sin_f32(angle) + 1.0) / 2.0 * AUDIO_VOL; // write sample to dac |
adwiens | 0:dbb85bfd22fd | 98 | } |
adwiens | 0:dbb85bfd22fd | 99 | else |
adwiens | 0:dbb85bfd22fd | 100 | { |
adwiens | 0:dbb85bfd22fd | 101 | dac = 0; |
adwiens | 0:dbb85bfd22fd | 102 | } |
adwiens | 0:dbb85bfd22fd | 103 | } |
adwiens | 0:dbb85bfd22fd | 104 | |
adwiens | 0:dbb85bfd22fd | 105 | // This function controls whether the current rtty bit is a mark or a space. |
adwiens | 0:dbb85bfd22fd | 106 | // Format is 1 start bit, 1 stop bit, 8 bit ascii |
adwiens | 0:dbb85bfd22fd | 107 | void rtty_bit_tick() |
adwiens | 0:dbb85bfd22fd | 108 | { |
adwiens | 0:dbb85bfd22fd | 109 | if(txen) { |
adwiens | 0:dbb85bfd22fd | 110 | if(bitn < 0) { |
adwiens | 0:dbb85bfd22fd | 111 | ifreq = SPACE_FREQ; // start bit (space/0) |
adwiens | 0:dbb85bfd22fd | 112 | ++bitn; |
adwiens | 0:dbb85bfd22fd | 113 | } else if(bitn > 7) { |
adwiens | 0:dbb85bfd22fd | 114 | ifreq = MARK_FREQ; // stop bit (mark/1) |
adwiens | 0:dbb85bfd22fd | 115 | bitn = -1; |
adwiens | 0:dbb85bfd22fd | 116 | if(txchar != NULL && *txchar != NULL) |
adwiens | 0:dbb85bfd22fd | 117 | ++txchar; // go to next character |
adwiens | 0:dbb85bfd22fd | 118 | } else { // data bit |
adwiens | 0:dbb85bfd22fd | 119 | if(txchar != NULL && *txchar != NULL) |
adwiens | 0:dbb85bfd22fd | 120 | ifreq = ((*txchar & (1<<bitn)) == 0) ? SPACE_FREQ : MARK_FREQ; |
adwiens | 0:dbb85bfd22fd | 121 | else |
adwiens | 0:dbb85bfd22fd | 122 | ifreq = MARK_FREQ; |
adwiens | 0:dbb85bfd22fd | 123 | ++bitn; |
adwiens | 0:dbb85bfd22fd | 124 | } |
adwiens | 0:dbb85bfd22fd | 125 | } |
adwiens | 0:dbb85bfd22fd | 126 | } |
adwiens | 0:dbb85bfd22fd | 127 | |
adwiens | 0:dbb85bfd22fd | 128 | void rtty_tx_thread(void const *argument) |
adwiens | 0:dbb85bfd22fd | 129 | { |
adwiens | 0:dbb85bfd22fd | 130 | while(1) { |
adwiens | 0:dbb85bfd22fd | 131 | txen = false; |
adwiens | 0:dbb85bfd22fd | 132 | ptt = 1; // turn off transmitter |
adwiens | 0:dbb85bfd22fd | 133 | tx_led = 0; |
adwiens | 0:dbb85bfd22fd | 134 | Thread::wait(RADIO_TX_WAIT); // wait for a certain amount of time between transmissions |
adwiens | 0:dbb85bfd22fd | 135 | txbuf.str(""); // empty tx buffer |
adwiens | 0:dbb85bfd22fd | 136 | txbuf.clear(); |
adwiens | 0:dbb85bfd22fd | 137 | nmea_data_mutex.lock(); |
adwiens | 0:dbb85bfd22fd | 138 | for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) { |
adwiens | 0:dbb85bfd22fd | 139 | txbuf << (iter->second); // fill the packet with the most recent nmea sentences |
adwiens | 0:dbb85bfd22fd | 140 | } |
adwiens | 0:dbb85bfd22fd | 141 | nmea_data_mutex.unlock(); |
adwiens | 0:dbb85bfd22fd | 142 | txbufstr = txbuf.str(); |
adwiens | 0:dbb85bfd22fd | 143 | txchar = txbufstr.c_str(); |
adwiens | 0:dbb85bfd22fd | 144 | ptt = 0; // key up the radio |
adwiens | 0:dbb85bfd22fd | 145 | tx_led = 1; |
adwiens | 0:dbb85bfd22fd | 146 | txen = true; |
adwiens | 0:dbb85bfd22fd | 147 | Thread::wait(RADIO_KEYUP_DELAY); // wait for radio to key up |
adwiens | 0:dbb85bfd22fd | 148 | while(txchar != NULL && *txchar != NULL); // wait for rtty to finish |
adwiens | 0:dbb85bfd22fd | 149 | Thread::wait(RADIO_KEYUP_DELAY); |
adwiens | 0:dbb85bfd22fd | 150 | txen = false; |
adwiens | 0:dbb85bfd22fd | 151 | } |
adwiens | 0:dbb85bfd22fd | 152 | } |
adwiens | 0:dbb85bfd22fd | 153 | |
adwiens | 0:dbb85bfd22fd | 154 | void print_nmea_data() // useful for debug |
adwiens | 0:dbb85bfd22fd | 155 | { |
adwiens | 0:dbb85bfd22fd | 156 | nmea_data_mutex.lock(); |
adwiens | 0:dbb85bfd22fd | 157 | for (map<string,string>::iterator iter = nmea_data.begin(); iter != nmea_data.end(); ++iter) { |
adwiens | 0:dbb85bfd22fd | 158 | cout << (iter->second); |
adwiens | 0:dbb85bfd22fd | 159 | } |
adwiens | 0:dbb85bfd22fd | 160 | nmea_data_mutex.unlock(); |
adwiens | 0:dbb85bfd22fd | 161 | cout << endl; |
adwiens | 0:dbb85bfd22fd | 162 | } |
adwiens | 0:dbb85bfd22fd | 163 | |
adwiens | 0:dbb85bfd22fd | 164 | int main() |
adwiens | 0:dbb85bfd22fd | 165 | { |
adwiens | 0:dbb85bfd22fd | 166 | Ticker sample_tick, bit_tick; |
adwiens | 0:dbb85bfd22fd | 167 | Thread gps_thread(gps_rx_thread); // gps receive thread |
adwiens | 0:dbb85bfd22fd | 168 | Thread rtty_thread(rtty_tx_thread); // rtty transmit thread |
adwiens | 0:dbb85bfd22fd | 169 | gps.baud(GPS_BAUD); // set gps bit rate |
adwiens | 0:dbb85bfd22fd | 170 | gps.attach(&gps_rx_isr); // set up gps receive interrupt service routine |
adwiens | 0:dbb85bfd22fd | 171 | sample_tick.attach_us(&rtty_sample_tick,1000000/AUDIO_FS); // begin generating audio |
adwiens | 0:dbb85bfd22fd | 172 | bit_tick.attach_us(&rtty_bit_tick,1000000/RTTY_BAUD); // begin sending characters |
adwiens | 0:dbb85bfd22fd | 173 | while(1) { |
adwiens | 0:dbb85bfd22fd | 174 | Thread::wait(2000); |
adwiens | 0:dbb85bfd22fd | 175 | print_nmea_data(); // debug |
adwiens | 0:dbb85bfd22fd | 176 | } |
adwiens | 0:dbb85bfd22fd | 177 | } |