Generates RTTY from a GPS receiver

Dependencies:   mbed-dsp mbed-rtos mbed

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?

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