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