diff options
Diffstat (limited to 'src/fsm/psk31.c')
-rw-r--r-- | src/fsm/psk31.c | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/fsm/psk31.c b/src/fsm/psk31.c new file mode 100644 index 0000000..6eafa53 --- /dev/null +++ b/src/fsm/psk31.c @@ -0,0 +1,278 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Matthias P. Braendli + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +*/ + +#include "psk31.h" +#include "common.h" +#include "audio.h" +#include <string.h> +#include "arm_math.h" + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "semphr.h" + +/* PSK31 generator + * + * Concept: + * + * +----------------------+ +--------------+ + * | psk31_push_message() | -> psk31_msg_queue -> | psk31_task() | + * +----------------------+ +--------------+ + * | + * _________________________________________________/ + * / + * | + * \|/ + * V + * + * psk31_audio_queue + * + * The psk31_fill_buffer() function can be called to fetch audio from the audio_queue + */ + +#define PSK31_MAX_MESSAGE_LEN 10240 +#define PHASE_BUFFER_SIZE (20 + PSK31_MAX_MESSAGE_LEN + 20) + +struct psk31_out_message_s { + // Contains a sequence of ones and zeros corresponding to + // the BPSK31 phase + char phase_buffer[PHASE_BUFFER_SIZE]; + size_t phase_buffer_end; + + int freq; // Audio frequency for signal center +}; + +// The queue contains above structs +QueueHandle_t psk31_msg_queue; + +// Queue that contains audio data +QueueHandle_t psk31_audio_queue; +static int psk31_samplerate; + +static int psk31_transmit_ongoing; + +static void psk31_task(void *pvParameters); +static void psk31_str_to_bits(const char* instr, char* outbits); + + +void psk31_init(unsigned int samplerate) +{ + psk31_samplerate = samplerate; + psk31_transmit_ongoing = 0; + + psk31_msg_queue = xQueueCreate(15, sizeof(struct psk31_out_message_s)); + if (psk31_msg_queue == 0) { + while(1); /* fatal error */ + } + + psk31_audio_queue = xQueueCreate(2, AUDIO_BUF_LEN * sizeof(int16_t)); + if (psk31_audio_queue == 0) { + while(1); /* fatal error */ + } + + xTaskCreate( + psk31_task, + "TaskPSK31", + 8*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + NULL); +} + +int psk31_push_message(const char* text, int frequency) +{ + if (strlen(text) > PSK31_MAX_MESSAGE_LEN) { + return 0; + } + + struct psk31_out_message_s msg; + msg.phase_buffer_end = 0; + msg.freq = frequency; + + psk31_str_to_bits(text, msg.phase_buffer); + + xQueueSendToBack(psk31_msg_queue, &msg, portMAX_DELAY); + + return 1; +} + +// Write the waveform into the buffer (stereo) +size_t psk31_fill_buffer(int16_t *buf, size_t bufsize); + +// Return 1 if the psk31 generator has completed transmission +int psk31_busy(void); + +static int16_t psk31_audio_buf[AUDIO_BUF_LEN]; +static void psk31_task(void *pvParameters) +{ + struct psk31_out_message_s psk31_fill_msg_current; + + float nco_phase = 0.0f; + float ampl = 0.0f; + + int buf_pos = 0; + + while (1) { + int status = xQueueReceive(psk31_msg_queue, &psk31_fill_msg_current, portMAX_DELAY); + if (status == pdTRUE) { + + psk31_transmit_ongoing = 1; + + /* BPSK31 is at 31.25 symbols per second. */ + const int samples_per_symbol = psk31_samplerate * 100 / 3125; + + // Angular frequency of NCO + const float omega = 2.0f * FLOAT_PI * psk31_fill_msg_current.freq / + (float)psk31_samplerate; + + int current_psk_phase = 0; + + for (int i = 0; i < psk31_fill_msg_current.phase_buffer_end; i++) { + for (int t = 0; t < samples_per_symbol; t++) { + int16_t s = 0; + + if (psk31_fill_msg_current.phase_buffer[i]) { + ampl = 32000.0f; + } + else { + ampl = + (float)current_psk_phase * + 32000.0f * + arm_cos_f32( + FLOAT_PI*(float)t/(float)samples_per_symbol); + } + + nco_phase += omega; + if (nco_phase > FLOAT_PI) { + nco_phase -= 2.0f * FLOAT_PI; + } + + s = ampl * arm_sin_f32(nco_phase); + + if (buf_pos == AUDIO_BUF_LEN) { + xQueueSendToBack(psk31_audio_queue, &psk31_audio_buf, portMAX_DELAY); + buf_pos = 0; + } + psk31_audio_buf[buf_pos++] = s; + + // Stereo + if (buf_pos == AUDIO_BUF_LEN) { + xQueueSendToBack(psk31_audio_queue, &psk31_audio_buf, portMAX_DELAY); + buf_pos = 0; + } + psk31_audio_buf[buf_pos++] = s; + } + + + if (! psk31_fill_msg_current.phase_buffer[i]) { + current_psk_phase *= -1; + } + } + + // We have completed this message + + psk31_transmit_ongoing = 0; + } + } +} + +/* + * Turn a null terminated ASCII string into a null terminated + * string of '0's and '1's representing the PSK31 varicode for the input. + * + * outstr must be at least size 20 + strlen(instr)*12 + 20 to accomodate + * the header and tail + */ +static void psk31_str_to_bits(const char* instr, char* outbits) +{ + int i=0, j, k; + + /* Header of 0s */ + for (j=0; j < 20; j++) { + outbits[i++] = '0'; + } + + /* Encode the message, with 00 between letters */ + for (j=0; j < strlen(instr); j++) { + const char* varicode_bits = psk31_varicode[(int)instr[j]]; + for(k=0; k < strlen(varicode_bits); k++) { + outbits[i++] = varicode_bits[k]; + } + outbits[i++] = '0'; + outbits[i++] = '0'; + } + + /* Tail of 0s */ + for (j=0; j < 20; j++) { + outbits[i++] = '0'; + } + + /* NULL terminate */ + outbits[i] = 0; +} + +#if 0 +/* + * Turn a null terminated string `bits` containing '0's and '1's + * into `outlen` IQ samples for BPSK, `outbuf`. + * Note that `outlen` is set to the number of IQ samples, i.e. half the + * number of bytes in `outbuf`. + * Allocates memory (possibly lots of memory) for the IQ samples, which + * should be freed elsewhere. + * Modulation: + * '0': swap phase, smoothed by a cosine + * '1': maintain phase + * Output: I carries data, Q constantly 0 + */ +void bits_to_iq(char* bits, uint8_t** outbuf, int* outlen) +{ + *outlen = strlen(bits) * 256000 * 2; + *outbuf = malloc(*outlen); + int8_t *buf = (int8_t*)(*outbuf); + if(*outbuf == NULL) { + fprintf(stderr, "Could not allocate memory for IQ buffer\n"); + exit(EXIT_FAILURE); + } + + int i, j, phase = 1; + for(i=0; i<strlen(bits); i++) { + if(bits[i] == '1') { + for(j=0; j<256000; j++) { + buf[i*256000*2 + 2*j] = phase*50; + buf[i*256000*2 + 2*j + 1] = 0; + } + } else { + for(j=0; j<256000; j++) { + buf[i*256000*2 + 2*j] = phase * + (int8_t)(50.0f * cosf(M_PI*(float)j/256000.0f)); + buf[i*256000*2 + 2*j + 1] = 0; + } + phase *= -1; + } + } +} +#endif + |