From cc0301784156543c7c9bec36b6f42df1d2adba28 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sat, 29 Jun 2019 18:48:05 +0200 Subject: Change common/ folder structure --- src/common/Audio/audio.c | 50 +++ src/common/Audio/audio.h | 58 +++ src/common/Audio/audio_in.c | 24 ++ src/common/Audio/audio_in.h | 40 ++ src/common/Audio/cw.c | 605 +++++++++++++++++++++++++++ src/common/Audio/cw.h | 53 +++ src/common/Audio/tone.c | 358 ++++++++++++++++ src/common/Audio/tone.h | 53 +++ src/common/Core/FreeRTOSConfig.h | 164 ++++++++ src/common/Core/common.c | 265 ++++++++++++ src/common/Core/common.h | 82 ++++ src/common/Core/delay.h | 39 ++ src/common/Core/fsm.c | 679 ++++++++++++++++++++++++++++++ src/common/Core/fsm.h | 135 ++++++ src/common/Core/main.c | 710 ++++++++++++++++++++++++++++++++ src/common/GPIO/analog.c | 69 ++++ src/common/GPIO/analog.h | 50 +++ src/common/GPIO/i2c.h | 53 +++ src/common/GPIO/leds.h | 9 + src/common/GPIO/pio.h | 47 +++ src/common/GPIO/temperature.c | 62 +++ src/common/GPIO/temperature.h | 40 ++ src/common/GPIO/usart.c | 171 ++++++++ src/common/GPIO/usart.h | 78 ++++ src/common/GPS/gps.c | 166 ++++++++ src/common/GPS/gps.h | 46 +++ src/common/GPS/minmea.c | 597 +++++++++++++++++++++++++++ src/common/GPS/minmea.h | 252 ++++++++++++ src/common/includes/Audio/audio.h | 58 --- src/common/includes/Audio/audio_in.h | 40 -- src/common/includes/Audio/cw.h | 53 --- src/common/includes/Audio/tone.h | 53 --- src/common/includes/Core/common.h | 82 ---- src/common/includes/Core/delay.h | 39 -- src/common/includes/Core/fsm.h | 135 ------ src/common/includes/GPIO/analog.h | 50 --- src/common/includes/GPIO/i2c.h | 53 --- src/common/includes/GPIO/leds.h | 9 - src/common/includes/GPIO/pio.h | 47 --- src/common/includes/GPIO/temperature.h | 40 -- src/common/includes/GPIO/usart.h | 78 ---- src/common/includes/GPS/gps.h | 46 --- src/common/includes/GPS/minmea.h | 252 ------------ src/common/sourcelist.txt | 24 +- src/common/src/Audio/audio.c | 50 --- src/common/src/Audio/audio_in.c | 24 -- src/common/src/Audio/cw.c | 605 --------------------------- src/common/src/Audio/tone.c | 358 ---------------- src/common/src/Core/FreeRTOSConfig.h | 164 -------- src/common/src/Core/common.c | 265 ------------ src/common/src/Core/fsm.c | 679 ------------------------------ src/common/src/Core/main.c | 710 -------------------------------- src/common/src/GPIO/analog.c | 69 ---- src/common/src/GPIO/temperature.c | 62 --- src/common/src/GPIO/usart.c | 171 -------- src/common/src/GPS/gps.c | 166 -------- src/common/src/GPS/minmea.c | 597 --------------------------- src/glutt-o-logique/FreeRTOSConfig.h | 2 +- src/glutt-o-logique/Makefile | 2 +- src/glutt-o-logique/usart.c | 2 +- src/simulator/Makefile | 2 +- src/simulator/src/Core/FreeRTOSConfig.h | 3 +- 62 files changed, 4972 insertions(+), 4973 deletions(-) create mode 100644 src/common/Audio/audio.c create mode 100644 src/common/Audio/audio.h create mode 100644 src/common/Audio/audio_in.c create mode 100644 src/common/Audio/audio_in.h create mode 100644 src/common/Audio/cw.c create mode 100644 src/common/Audio/cw.h create mode 100644 src/common/Audio/tone.c create mode 100644 src/common/Audio/tone.h create mode 100644 src/common/Core/FreeRTOSConfig.h create mode 100644 src/common/Core/common.c create mode 100644 src/common/Core/common.h create mode 100644 src/common/Core/delay.h create mode 100644 src/common/Core/fsm.c create mode 100644 src/common/Core/fsm.h create mode 100644 src/common/Core/main.c create mode 100644 src/common/GPIO/analog.c create mode 100644 src/common/GPIO/analog.h create mode 100644 src/common/GPIO/i2c.h create mode 100644 src/common/GPIO/leds.h create mode 100644 src/common/GPIO/pio.h create mode 100644 src/common/GPIO/temperature.c create mode 100644 src/common/GPIO/temperature.h create mode 100644 src/common/GPIO/usart.c create mode 100644 src/common/GPIO/usart.h create mode 100644 src/common/GPS/gps.c create mode 100644 src/common/GPS/gps.h create mode 100644 src/common/GPS/minmea.c create mode 100644 src/common/GPS/minmea.h delete mode 100644 src/common/includes/Audio/audio.h delete mode 100644 src/common/includes/Audio/audio_in.h delete mode 100644 src/common/includes/Audio/cw.h delete mode 100644 src/common/includes/Audio/tone.h delete mode 100644 src/common/includes/Core/common.h delete mode 100644 src/common/includes/Core/delay.h delete mode 100644 src/common/includes/Core/fsm.h delete mode 100644 src/common/includes/GPIO/analog.h delete mode 100644 src/common/includes/GPIO/i2c.h delete mode 100644 src/common/includes/GPIO/leds.h delete mode 100644 src/common/includes/GPIO/pio.h delete mode 100644 src/common/includes/GPIO/temperature.h delete mode 100644 src/common/includes/GPIO/usart.h delete mode 100644 src/common/includes/GPS/gps.h delete mode 100644 src/common/includes/GPS/minmea.h delete mode 100644 src/common/src/Audio/audio.c delete mode 100644 src/common/src/Audio/audio_in.c delete mode 100644 src/common/src/Audio/cw.c delete mode 100644 src/common/src/Audio/tone.c delete mode 100644 src/common/src/Core/FreeRTOSConfig.h delete mode 100644 src/common/src/Core/common.c delete mode 100644 src/common/src/Core/fsm.c delete mode 100644 src/common/src/Core/main.c delete mode 100644 src/common/src/GPIO/analog.c delete mode 100644 src/common/src/GPIO/temperature.c delete mode 100644 src/common/src/GPIO/usart.c delete mode 100644 src/common/src/GPS/gps.c delete mode 100644 src/common/src/GPS/minmea.c diff --git a/src/common/Audio/audio.c b/src/common/Audio/audio.c new file mode 100644 index 0000000..f68f867 --- /dev/null +++ b/src/common/Audio/audio.c @@ -0,0 +1,50 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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 "Audio/audio.h" + +#include + +AudioCallbackFunction *callback_function; +void *callback_context; +int16_t * next_buffer_samples; +int next_buffer_length; +int buffer_number; +bool dma_running; + +void audio_initialize(int plln, int pllr, int i2sdiv, int i2sodd, int rate) { + + // Intitialize state. + callback_function = NULL; + callback_context = NULL; + next_buffer_samples = NULL; + next_buffer_length = 0; + buffer_number = 0; + dma_running = false; + + audio_initialize_platform(plln, pllr, i2sdiv, i2sodd, rate); + + audio_set_volume(0xff); + +} diff --git a/src/common/Audio/audio.h b/src/common/Audio/audio.h new file mode 100644 index 0000000..d48cbe5 --- /dev/null +++ b/src/common/Audio/audio.h @@ -0,0 +1,58 @@ +#ifndef __AUDIO_H__ +#define __AUDIO_H__ + +#include +#include + +typedef void AudioCallbackFunction(void *context,int buffer); + +// Variables used by both glutt-o-logique and simulator +extern AudioCallbackFunction *callback_function; +extern void *callback_context; +extern int16_t *next_buffer_samples; +extern int next_buffer_length; +extern int buffer_number; +extern bool dma_running; + +void audio_initialize_platform(int plln, int pllr, int i2sdiv, int i2sodd, int rate); +void audio_start_dma_and_request_buffers(void); +void audio_stop_dma(void); + +#define Audio8000HzSettings 256,5,12,1,8000 +#define Audio16000HzSettings 213,2,13,0,16000 +#define Audio32000HzSettings 213,2,6,1,32000 +#define Audio48000HzSettings 258,3,3,1,48000 +#define Audio96000HzSettings 344,2,3,1,96000 +#define Audio22050HzSettings 429,4,9,1,22050 +#define Audio44100HzSettings 271,2,6,0,44100 +#define AudioVGAHSyncSettings 419,2,13,0,31475 // 31475.3606. Actual VGA timer is 31472.4616. + +#define AUDIO_BUF_LEN 4096 + + +// Initialize and power up audio hardware. Use the above defines for the parameters. +// Can probably only be called once. +void audio_initialize(int plln, int pllr, int i2sdiv, int i2sodd, int rate); + +// Power up and down the audio hardware. +void audio_put_codec_in_reset(void); +void audio_reinit_codec(void); + +// Set audio volume in steps of 0.5 dB. 0xff is +12 dB. +void audio_set_volume(int volume); + +// Start and stop audio playback using DMA. +// Callback is optional, and called whenever a new buffer is needed. +void audio_play_with_callback(AudioCallbackFunction *callback,void *context); + +// Provide a new buffer to the audio DMA. Output is double buffered, so +// at least two buffers must be maintained by the program. It is not allowed +// to overwrite the previously provided buffer until after the next callback +// invocation. +// Buffers must reside in DMA1-accessible memory, that is, the 128k RAM bank, +// or flash. +bool audio_provide_buffer_without_blocking(void *samples,int numsamples); + +void DMA1_Stream7_IRQHandler(void); + +#endif diff --git a/src/common/Audio/audio_in.c b/src/common/Audio/audio_in.c new file mode 100644 index 0000000..dd1994e --- /dev/null +++ b/src/common/Audio/audio_in.c @@ -0,0 +1,24 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + diff --git a/src/common/Audio/audio_in.h b/src/common/Audio/audio_in.h new file mode 100644 index 0000000..97a0f29 --- /dev/null +++ b/src/common/Audio/audio_in.h @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + +#pragma once + +#include +#include "FreeRTOS.h" + +#define AUDIO_IN_RATE 16000 +#define AUDIO_IN_BUF_LEN 1600 + +void audio_in_initialize(void); + +/* Enable or disable the audio input */ +void audio_in_enable(int enable); + +/* The audio input layer must call tone_detect_push_sample() to + * send the samples + */ diff --git a/src/common/Audio/cw.c b/src/common/Audio/cw.c new file mode 100644 index 0000000..667d459 --- /dev/null +++ b/src/common/Audio/cw.c @@ -0,0 +1,605 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 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. +*/ + +/* CW and PSK31 generator + * + * Concept: + * + * +-------------------+ +----------------+ + * | cw_push_message() | -> cw_msg_queue -> | cw_psk31task() | -> cw_audio_queue + * +-------------------+ +----------------+ + * + * The cw_psk31_fill_buffer() function can be called to fetch audio from the + * audio_queue + */ + +#include "Audio/cw.h" +#include "Core/common.h" +#include "Audio/audio.h" +#include + +#ifdef SIMULATOR +#include +#define arm_cos_f32 cosf +#define arm_sin_f32 sinf +#else +#include "arm_math.h" +#endif + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include "semphr.h" + +#define MAX_MESSAGE_LEN 1024 +#define MAX_ON_BUFFER_LEN 8192 + +const uint8_t cw_mapping[60] = { // {{{ + // Read bits from right to left + + 0b110101, //+ ASCII 43 + 0b110101, //, ASCII 44 + 0b1011110, //- ASCII 45 + + 0b1010101, //., ASCII 46 + 0b110110, // / ASCII 47 + + 0b100000, // 0, ASCII 48 + 0b100001, // 1 + 0b100011, + 0b100111, + 0b101111, + 0b111111, + 0b111110, + 0b111100, + 0b111000, + 0b110000, // 9, ASCII 57 + + // The following are mostly invalid, but + // required to fill the gap in ASCII between + // numerals and capital letters + 0b10, // : + 0b10, // ; + 0b10, // < + 0b101110, // = + 0b10, // > + 0b1110011, // ? + 0b1101001, //@ + + 0b101, // A ASCII 65 + 0b11110, + 0b11010, + 0b1110, + 0b11, + 0b11011, + 0b1100, + 0b11111, + 0b111, + 0b10001, + 0b1010, + 0b11101, + 0b100, //M + 0b110, + 0b1000, + 0b11001, + 0b10100, + 0b1101, + 0b1111, + 0b10, + 0b1011, + 0b10111, + 0b1001, + 0b10110, + 0b10010, + 0b11100, // Z + + 0b101010, //Start, ASCII [ + 0b1010111, // SK , ASCII '\' +}; //}}} + +#if ENABLE_PSK31 +/* + * PSK31 Varicode + * http://aintel.bi.ehu.es/psk31.html + */ +const char *psk31_varicode[] = { // {{{ + "1010101011", + "1011011011", + "1011101101", + "1101110111", + "1011101011", + "1101011111", + "1011101111", + "1011111101", + "1011111111", + "11101111", + "11101", + "1101101111", + "1011011101", + "11111", + "1101110101", + "1110101011", + "1011110111", + "1011110101", + "1110101101", + "1110101111", + "1101011011", + "1101101011", + "1101101101", + "1101010111", + "1101111011", + "1101111101", + "1110110111", + "1101010101", + "1101011101", + "1110111011", + "1011111011", + "1101111111", + "1", + "111111111", + "101011111", + "111110101", + "111011011", + "1011010101", + "1010111011", + "101111111", + "11111011", + "11110111", + "101101111", + "111011111", + "1110101", + "110101", + "1010111", + "110101111", + "10110111", + "10111101", + "11101101", + "11111111", + "101110111", + "101011011", + "101101011", + "110101101", + "110101011", + "110110111", + "11110101", + "110111101", + "111101101", + "1010101", + "111010111", + "1010101111", + "1010111101", + "1111101", + "11101011", + "10101101", + "10110101", + "1110111", + "11011011", + "11111101", + "101010101", + "1111111", + "111111101", + "101111101", + "11010111", + "10111011", + "11011101", + "10101011", + "11010101", + "111011101", + "10101111", + "1101111", + "1101101", + "101010111", + "110110101", + "101011101", + "101110101", + "101111011", + "1010101101", + "111110111", + "111101111", + "111111011", + "1010111111", + "101101101", + "1011011111", + "1011", + "1011111", + "101111", + "101101", + "11", + "111101", + "1011011", + "101011", + "1101", + "111101011", + "10111111", + "11011", + "111011", + "1111", + "111", + "111111", + "110111111", + "10101", + "10111", + "101", + "110111", + "1111011", + "1101011", + "11011111", + "1011101", + "111010101", + "1010110111", + "110111011", + "1010110101", + "1011010111", + "1110110101", +}; //}}} +#endif + +// Function to display message in GUI +void cw_message_sent(const char* str); + +struct cw_message_s { + char message[MAX_MESSAGE_LEN]; + size_t message_len; + + int freq; + + // If dit_duration is 0, the message is sent in PSK31 + int dit_duration; +}; + +// The queue contains above structs +QueueHandle_t cw_msg_queue; + +// Queue that contains audio data +QueueHandle_t cw_audio_queue; +static int cw_psk31_samplerate; + +static int cw_transmit_ongoing; + +static void cw_psk31_task(void *pvParameters); + +void cw_psk31_init(unsigned int samplerate) +{ + cw_psk31_samplerate = samplerate; + cw_transmit_ongoing = 0; + + cw_msg_queue = xQueueCreate(10, sizeof(struct cw_message_s)); + if (cw_msg_queue == 0) { + while(1); /* fatal error */ + } + + cw_audio_queue = xQueueCreate(1, AUDIO_BUF_LEN * sizeof(int16_t)); + if (cw_audio_queue == 0) { + while(1); /* fatal error */ + } + + xTaskCreate( + cw_psk31_task, + "CWPSKTask", + 8*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 3UL, + NULL); +} + +/* Parse one CW letter/symbol, and fill in the on_buffer. + * Returns number of uint8_t written. + */ +size_t cw_symbol(uint8_t sym, uint8_t *on_buffer, size_t on_buffer_size) +{ + uint8_t p = 0; + uint8_t val = cw_mapping[sym]; + size_t pos = 0; + + while((val >> p) != 0b1) { + + if (((val >> p) & 0b1) == 0b1) { + if (pos + 2 < on_buffer_size) { + // tone(1) + on_buffer[pos++] = 1; + + // silence(1) + on_buffer[pos++] = 0; + } + } + else { + if (pos + 4 < on_buffer_size) { + // tone(3) + on_buffer[pos++] = 1; + on_buffer[pos++] = 1; + on_buffer[pos++] = 1; + + // silence(1) + on_buffer[pos++] = 0; + } + } + + p++; + } + + // silence(2) + if (pos + 2 < on_buffer_size) { + for (int i = 0; i < 2; i++) { + on_buffer[pos++] = 0; + } + } + + return pos; +} + +// Transmit a string in morse code or PSK31. +// Supported range for CW: +// All ASCII between '+' and '\', which includes +// numerals and capital letters. +// Distinction between CW and PSK31 is done on dit_duration==0 +int cw_psk31_push_message(const char* text, int dit_duration, int frequency) +{ + const int text_len = strlen(text); + + if (strlen(text) > MAX_MESSAGE_LEN) { + return 0; + } + + struct cw_message_s msg; + for (int i = 0; i < MAX_MESSAGE_LEN; i++) { + if (i < text_len) { + msg.message[i] = text[i]; + } + else { + msg.message[i] = '\0'; + } + } + msg.message_len = text_len; + msg.freq = frequency; + msg.dit_duration = dit_duration; + + xQueueSendToBack(cw_msg_queue, &msg, portMAX_DELAY); /* Send Message */ + + cw_message_sent(msg.message); + + return 1; +} + +/* Parse the message and fill the on_buffer with CW on/CW off information. + * Returns the number of on/off bits written. + */ +static size_t cw_text_to_on_buffer(const char *msg, uint8_t *on_buffer, size_t on_buffer_size) +{ + size_t pos = 0; + const char* sym = msg; + do { + if (*sym < '+' || *sym > '\\') { + if (pos + 3 < on_buffer_size) { + for (int i = 0; i < 3; i++) { + on_buffer[pos++] = 0; + } + } + } + else { + pos += cw_symbol(*sym - '+', on_buffer + pos, on_buffer_size - pos); + } + sym++; + } while (*sym != '\0'); + + return pos; +} + + +#if ENABLE_PSK31 +/* + * Turn a null terminated ASCII string into a uint8_t buffer + * of 0 and 1 representing the PSK31 varicode for the input. + * + * outstr must be at least size 20 + strlen(instr)*12 + 20 to accomodate + * the header and tail. + * + * Returns number of bytes written. + */ +static size_t psk31_text_to_phase_buffer(const char* instr, uint8_t* 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] == '1') ? 1 : 0; + } + outbits[i++] = 0; + outbits[i++] = 0; + } + + /* Tail of 0s */ + for (j=0; j < 20; j++) { + outbits[i++] = 0; + } + + return i; +} +#endif + + +size_t cw_psk31_fill_buffer(int16_t *buf, size_t bufsize) +{ + if (xQueueReceiveFromISR(cw_audio_queue, buf, NULL)) { + return bufsize; + } + else { + return 0; + } +} + +static int16_t cw_audio_buf[AUDIO_BUF_LEN]; +static uint8_t cw_psk31_buffer[MAX_ON_BUFFER_LEN]; +static struct cw_message_s cw_fill_msg_current; + +// Routine to generate CW audio +static float cw_generate_audio_ampl = 0.0f; +static float cw_generate_audio_nco = 0.0f; +static int16_t cw_generate_audio(float omega, int i, int __attribute__ ((unused))t) +{ + int16_t s = 0; + // Remove clicks from CW + if (cw_psk31_buffer[i]) { + const float remaining = 32768.0f - cw_generate_audio_ampl; + cw_generate_audio_ampl += remaining / 64.0f; + } + else { + cw_generate_audio_ampl -= cw_generate_audio_ampl / 64.0f; + + if (cw_generate_audio_ampl < 0) { + cw_generate_audio_ampl = 0; + } + } + + cw_generate_audio_nco += omega; + if (cw_generate_audio_nco > FLOAT_PI) { + cw_generate_audio_nco -= 2.0f * FLOAT_PI; + } + + s = cw_generate_audio_ampl * arm_sin_f32(cw_generate_audio_nco); + return s; +} + +#if ENABLE_PSK31 +static float psk31_generate_audio_nco = 0.0f; +static int psk31_current_psk_phase = 1; +static int16_t psk31_generate_audio(float omega, int i, int t, int samples_per_symbol) +{ + int16_t s = 0; + const float base_ampl = 20000.0f; + float psk31_generate_audio_ampl = 0.0f; + + if (cw_psk31_buffer[i] == 1) { + psk31_generate_audio_ampl = base_ampl; + } + else { + psk31_generate_audio_ampl = base_ampl * arm_cos_f32( + FLOAT_PI*(float)t/(float)samples_per_symbol); + } + + psk31_generate_audio_nco += omega; + if (psk31_generate_audio_nco > FLOAT_PI) { + psk31_generate_audio_nco -= 2.0f * FLOAT_PI; + } + + s = psk31_generate_audio_ampl * + arm_sin_f32(psk31_generate_audio_nco + + (psk31_current_psk_phase == 1 ? 0.0f : FLOAT_PI)); + + return s; +} +#endif + +static void cw_psk31_task(void __attribute__ ((unused))*pvParameters) +{ + int buf_pos = 0; + + while (1) { + int status = xQueueReceive(cw_msg_queue, &cw_fill_msg_current, portMAX_DELAY); + if (status == pdTRUE) { + + size_t cw_psk31_buffer_len = 0; + + cw_transmit_ongoing = 1; + + if (cw_fill_msg_current.dit_duration) { + cw_psk31_buffer_len = cw_text_to_on_buffer( + cw_fill_msg_current.message, + cw_psk31_buffer, + MAX_ON_BUFFER_LEN); + + } +#if ENABLE_PSK31 + else { + cw_psk31_buffer_len = psk31_text_to_phase_buffer( + cw_fill_msg_current.message, + cw_psk31_buffer); + } +#endif + + // Angular frequency of NCO + const float omega = 2.0f * FLOAT_PI * cw_fill_msg_current.freq / + (float)cw_psk31_samplerate; + + const int samples_per_symbol = (cw_fill_msg_current.dit_duration != 0) ? + (cw_psk31_samplerate * cw_fill_msg_current.dit_duration) / 1000 : + /* BPSK31 is at 31.25 symbols per second. */ + cw_psk31_samplerate * 100 / 3125; + +#if ENABLE_PSK31 + psk31_current_psk_phase = 1; +#endif + + for (int i = 0; i < cw_psk31_buffer_len; i++) { + for (int t = 0; t < samples_per_symbol; t++) { +#if ENABLE_PSK31 + int16_t s = (cw_fill_msg_current.dit_duration != 0) ? + cw_generate_audio(omega, i, t) : + psk31_generate_audio(omega, i, t, samples_per_symbol); +#else + int16_t s = cw_generate_audio(omega, i, t); +#endif + + // Stereo + for (int channel = 0; channel < 2; channel++) { + if (buf_pos == AUDIO_BUF_LEN) { + // It should take AUDIO_BUF_LEN/cw_psk31_samplerate seconds to send one buffer. + // If it takes more than 4 times as long, we think there is a problem. + const TickType_t reasonable_delay = pdMS_TO_TICKS(4000 * AUDIO_BUF_LEN / cw_psk31_samplerate); + if (xQueueSendToBack(cw_audio_queue, &cw_audio_buf, reasonable_delay) != pdTRUE) { + trigger_fault(FAULT_SOURCE_CW_AUDIO_QUEUE); + } + buf_pos = 0; + } + cw_audio_buf[buf_pos++] = s; + } + + } + +#if ENABLE_PSK31 + if (cw_psk31_buffer[i] == 0) { + psk31_current_psk_phase *= -1; + } +#endif + } + + // We have completed this message + + cw_transmit_ongoing = 0; + } + } +} + +int cw_psk31_busy(void) +{ + return cw_transmit_ongoing; +} + diff --git a/src/common/Audio/cw.h b/src/common/Audio/cw.h new file mode 100644 index 0000000..39be9c5 --- /dev/null +++ b/src/common/Audio/cw.h @@ -0,0 +1,53 @@ +/* + * 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. +*/ + +#ifndef __CW_H_ +#define __CW_H_ + +#include +#include + +// Setup the CW generator to create audio samples at the given +// samplerate. +void cw_psk31_init(unsigned int samplerate); + +// Append new CW or PSK31 text to transmit +// CW/PSK31 audio centre frequency in Hz +// if dit_duration == 0, message is sent in PSK31 +// otherwise it is sent in CW, with dit_duration in ms +// returns 0 on failure, 1 on success +int cw_psk31_push_message(const char* text, int frequency, int dit_duration); + +// Write the waveform into the buffer (stereo), both for cw and psk31 +size_t cw_psk31_fill_buffer(int16_t *buf, size_t bufsize); + +// Return 1 if the CW or PSK31 generator is running +int cw_psk31_busy(void); + +void cw_message_sent(const char*); + +size_t cw_symbol(uint8_t, uint8_t *, size_t); + +#endif // __CW_H_ + diff --git a/src/common/Audio/tone.c b/src/common/Audio/tone.c new file mode 100644 index 0000000..90a8394 --- /dev/null +++ b/src/common/Audio/tone.c @@ -0,0 +1,358 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Maximilien Cuony, 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 "Audio/tone.h" +#include "Core/common.h" +#include "GPIO/usart.h" + +#include +#include "queue.h" + +#ifdef SIMULATOR +#include +#define arm_cos_f32 cosf +#define arm_sin_f32 sinf +#else +#include "arm_math.h" +#endif + +#define TONE_N 800 + +/* DTMF frequencies + * COL_1 COL_2 COL_3 COL_A + * 1209 1336 1477 1633 + * 697 [1] [2] [3] [A] ROW_1 + * 770 [4] [5] [6] [B] ROW_4 + * 852 [7] [8] [9] [C] ROW_7 + * 941 [*] [0] [#] [D] ROW_STAR + * + * We need 0, 7, and 1750Hz for now, having detectors for + * 1209, 1336, 852, 941 and 1750 is sufficient. */ + +#define DET_COL_1 0 +#define DET_COL_2 1 +#define DET_ROW_7 2 +#define DET_ROW_STAR 3 +#define DET_1750 4 +#define NUM_DETECTORS 5 + +// Incomplete because not all frequencies decoded +enum dtmf_code { + DTMF_NONE = 0, + DTMF_0, + DTMF_7, + DTMF_8, + DTMF_STAR, +}; + +struct tone_detector { + float coef; + float Q1; + float Q2; +}; + +static struct tone_detector detectors[NUM_DETECTORS]; + +static int num_samples_analysed = 0; +static float prev_mean = 0; +static uint32_t accum = 0; +static int lost_results = 0; +static QueueHandle_t m_squared_queue; + +// Apply an IIR filter with alpha = 5/16 to smooth out variations. +// Values are normalised s.t. the mean corresponds to 100 +static int32_t normalised_results[NUM_DETECTORS]; + +static int num_tone_1750_detected = 0; +static uint64_t tone_1750_detected_since = 0; +static int detectors_enabled = 0; + +static enum dtmf_code dtmf_last_seen = 0; +static uint64_t dtmf_last_seen_at = 0; + +// Store the sequence of dtmf codes in this FIFO. If no DTMF code gets +// decoded in the interval, a NONE gets inserted into the sequence +#define NUM_DTMF_SEQ 3 +#define DTMF_MAX_TONE_INTERVAL 2500 +static enum dtmf_code dtmf_sequence[NUM_DTMF_SEQ]; + +static char* dtmf_to_str(enum dtmf_code code) +{ + char *codestr = "?"; + switch (code) { + case DTMF_0: codestr = "0"; break; + case DTMF_7: codestr = "7"; break; + case DTMF_8: codestr = "8"; break; + case DTMF_STAR: codestr = "*"; break; + case DTMF_NONE: codestr = "x"; break; + } + return codestr; +} + +static inline void push_dtmf_code(enum dtmf_code code) +{ + for (int i = 0; i < NUM_DTMF_SEQ-1; i++) { + dtmf_sequence[i] = dtmf_sequence[i+1]; + } + dtmf_sequence[NUM_DTMF_SEQ-1] = code; + + if (code != DTMF_NONE) { + usart_debug("DTMF: [%s, %s, %s]\r\n", + dtmf_to_str(dtmf_sequence[0]), + dtmf_to_str(dtmf_sequence[1]), + dtmf_to_str(dtmf_sequence[2])); + } +} + +// TODO: Does that depend on TONE_N? +const int thresh_dtmf = 200; +const int thresh_1750 = 300; +const int num_1750_required = 5; + +static void analyse_dtmf() +{ + // Bits 0 to 9 are numbers, bit 10 to 13 letters, bit 14 is star, 15 is hash + const uint16_t pattern = + ((normalised_results[DET_COL_1] > thresh_dtmf && + normalised_results[DET_ROW_7] > thresh_dtmf) ? (1 << 7) : 0) + + ((normalised_results[DET_COL_2] > thresh_dtmf && + normalised_results[DET_ROW_7] > thresh_dtmf) ? (1 << 8) : 0) + + ((normalised_results[DET_COL_1] > thresh_dtmf && + normalised_results[DET_ROW_STAR] > thresh_dtmf) ? (1 << 14) : 0) + + ((normalised_results[DET_COL_2] > thresh_dtmf && + normalised_results[DET_ROW_STAR] > thresh_dtmf) ? (1 << 0) : 0); + + // Match patterns exactly to exclude multiple simultaneous DTMF codes. + if (pattern == (1 << 0)) { + if (dtmf_last_seen != DTMF_0) { + push_dtmf_code(DTMF_0); + } + + dtmf_last_seen = DTMF_0; + dtmf_last_seen_at = timestamp_now(); + } + else if (pattern == (1 << 7)) { + if (dtmf_last_seen != DTMF_7) { + push_dtmf_code(DTMF_7); + } + + dtmf_last_seen = DTMF_7; + dtmf_last_seen_at = timestamp_now(); + } + else if (pattern == (1 << 8)) { + if (dtmf_last_seen != DTMF_8) { + push_dtmf_code(DTMF_8); + } + + dtmf_last_seen = DTMF_8; + dtmf_last_seen_at = timestamp_now(); + } + else if (pattern == (1 << 14)) { + if (dtmf_last_seen != DTMF_STAR) { + push_dtmf_code(DTMF_STAR); + } + + dtmf_last_seen = DTMF_STAR; + dtmf_last_seen_at = timestamp_now(); + } + else if (dtmf_last_seen_at + DTMF_MAX_TONE_INTERVAL < timestamp_now()) { + // Flush out all codes + push_dtmf_code(DTMF_NONE); + + dtmf_last_seen = DTMF_NONE; + dtmf_last_seen_at = timestamp_now(); + } +} + + +int tone_1750_status() +{ + return num_tone_1750_detected >= num_1750_required; +} + +int tone_1750_for_5_seconds() +{ + return (num_tone_1750_detected >= num_1750_required) && + tone_1750_detected_since + 5000 < timestamp_now(); +} + +int tone_fax_status() +{ + return dtmf_sequence[0] == DTMF_0 && + dtmf_sequence[1] == DTMF_7 && + dtmf_sequence[2] == DTMF_STAR; +} + +static inline void init_detector(struct tone_detector* detector, int freq) { + detector->coef = 2.0f * arm_cos_f32(2.0f * FLOAT_PI * freq / AUDIO_IN_RATE); + detector->Q1 = 0; + detector->Q2 = 0; +} + +void tone_init() { + m_squared_queue = xQueueCreate(2, NUM_DETECTORS * sizeof(float)); + if (m_squared_queue == 0) { + trigger_fault(FAULT_SOURCE_ADC2_QUEUE); + } + + for (int i = 0; i < NUM_DTMF_SEQ; i++) { + dtmf_sequence[i] = DTMF_NONE; + } + + init_detector(&detectors[DET_COL_1], 1209); + init_detector(&detectors[DET_COL_2], 1336); + init_detector(&detectors[DET_ROW_7], 852); + init_detector(&detectors[DET_ROW_STAR], 941); + init_detector(&detectors[DET_1750], 1750); +} + +void tone_detector_enable(int enable) +{ + if (enable && !detectors_enabled) { + num_samples_analysed = 0; + prev_mean = 0; + accum = 0; + for (int det = 0; det < NUM_DETECTORS; det++) { + detectors[det].Q1 = 0; + detectors[det].Q2 = 0; + } + audio_in_enable(1); + detectors_enabled = 1; + } + else if (!enable && detectors_enabled) { + audio_in_enable(0); + detectors_enabled = 0; + } +} + +void tone_detect_push_sample(const uint16_t sample, int is_irq) +{ + num_samples_analysed++; + + accum += sample; + + // Do not do tone detection before we have calculated a mean + if (prev_mean > 0) { + const float s = sample - prev_mean; + + for (int det = 0; det < NUM_DETECTORS; det++) { + struct tone_detector *detector = &detectors[det]; + + float Q0 = detector->coef * detector->Q1 - detector->Q2 + s; + detector->Q2 = detector->Q1; + detector->Q1 = Q0; + } + } + + if (num_samples_analysed == TONE_N) { + num_samples_analysed = 0; + + if (prev_mean > 0) { + float m_squared[NUM_DETECTORS]; + for (int det = 0; det < NUM_DETECTORS; det++) { + struct tone_detector *detector = &detectors[det]; + m_squared[det] = + detector->Q1 * detector->Q1 + + detector->Q2 * detector->Q2 - + detector->coef * detector->Q1 * detector->Q2; + detector->Q1 = 0; + detector->Q2 = 0; + } + + BaseType_t require_context_switch = 0; + int success; + if (is_irq) { + success = xQueueSendToBackFromISR( + m_squared_queue, + &m_squared, + &require_context_switch); + } + else { + success = xQueueSendToBack( + m_squared_queue, + &m_squared, + 0); + } + + if (success == pdFALSE) { + lost_results++; + } + } + + prev_mean = (float)accum / (float)TONE_N; + accum = 0; + } +} + +void tone_do_analysis() +{ + float m[NUM_DETECTORS]; + while (!xQueueReceive(m_squared_queue, &m, portMAX_DELAY)) {} + + float inv_mean = 0; + for (int det = 0; det < NUM_DETECTORS; det++) { + m[det] = sqrtf(m[det]); + inv_mean += m[det]; + } + inv_mean = NUM_DETECTORS / inv_mean; + + for (int det = 0; det < NUM_DETECTORS; det++) { + normalised_results[det] = + (11 * normalised_results[det] + + (int)(5 * 100 * m[det] * inv_mean)) + >> 4; // divide by 16 + } + + if (num_tone_1750_detected < num_1750_required && + normalised_results[DET_1750] > thresh_1750) { + num_tone_1750_detected++; + + if (num_tone_1750_detected == num_1750_required) { + tone_1750_detected_since = timestamp_now(); + } + } + else if (num_tone_1750_detected > 0 && + normalised_results[DET_1750] <= thresh_1750) { + num_tone_1750_detected--; + } + + analyse_dtmf(); + +#if PRINT_TONES_STATS + static int printcounter = 0; + if (++printcounter == 5) { + usart_debug("Tones: % 3d % 3d % 3d % 3d % 3d since %d\r\n", + normalised_results[0], + normalised_results[1], + normalised_results[2], + normalised_results[3], + normalised_results[4], + (int)(timestamp_now() - tone_1750_detected_since) + ); + + printcounter = 0; + } +#endif // PRINT_TONES_STATS +} + diff --git a/src/common/Audio/tone.h b/src/common/Audio/tone.h new file mode 100644 index 0000000..ba05304 --- /dev/null +++ b/src/common/Audio/tone.h @@ -0,0 +1,53 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + +#ifndef __TONE_H_ +#define __TONE_H_ + +#include +#include "Audio/audio_in.h" + +#define TONE_BUFFER_LEN AUDIO_IN_BUF_LEN + +void tone_init(void); + +void tone_detector_enable(int enable); + +/* Return 1 when 1750 detected, 0 otherwise */ +int tone_1750_status(void); + +/* Return 1 if 1750 is currently detected, and has been already for at + * least 5s */ +int tone_1750_for_5_seconds(void); + +/* The FAX status is 1 if the recently decoded DTMF is the 0-7-* sequence. */ +int tone_fax_status(void); + +/* Must be called by task to do the analysis */ +void tone_do_analysis(void); + +/* Push a sample from the ADC ISR or Pulseaudio task */ +void tone_detect_push_sample(const uint16_t sample, int is_irq); + +#endif diff --git a/src/common/Core/FreeRTOSConfig.h b/src/common/Core/FreeRTOSConfig.h new file mode 100644 index 0000000..b58e2a2 --- /dev/null +++ b/src/common/Core/FreeRTOSConfig.h @@ -0,0 +1,164 @@ +/* + FreeRTOS V8.0.0:rc2 - Copyright (C) 2014 Real Time Engineers Ltd. + All rights reserved + + VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION. + + *************************************************************************** + * * + * FreeRTOS provides completely free yet professionally developed, * + * robust, strictly quality controlled, supported, and cross * + * platform software that has become a de facto standard. * + * * + * Help yourself get started quickly and support the FreeRTOS * + * project by purchasing a FreeRTOS tutorial book, reference * + * manual, or both from: http://www.FreeRTOS.org/Documentation * + * * + * Thank you! * + * * + *************************************************************************** + + This file is part of the FreeRTOS distribution. + + FreeRTOS is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License (version 2) as published by the + Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception. + + >>! NOTE: The modification to the GPL is included to allow you to distribute + >>! a combined work that includes FreeRTOS without being obliged to provide + >>! the source code for proprietary components outside of the FreeRTOS + >>! kernel. + + FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. Full license text is available from the following + link: http://www.freertos.org/a00114.html + + 1 tab == 4 spaces! + + *************************************************************************** + * * + * Having a problem? Start by reading the FAQ "My application does * + * not run, what could be wrong?" * + * * + * http://www.FreeRTOS.org/FAQHelp.html * + * * + *************************************************************************** + + http://www.FreeRTOS.org - Documentation, books, training, latest versions, + license and Real Time Engineers Ltd. contact details. + + http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products, + including FreeRTOS+Trace - an indispensable productivity tool, a DOS + compatible FAT file system, and our tiny thread aware UDP/IP stack. + + http://www.OpenRTOS.com - Real Time Engineers ltd license FreeRTOS to High + Integrity Systems to sell under the OpenRTOS brand. Low cost OpenRTOS + licenses offer ticketed support, indemnification and middleware. + + http://www.SafeRTOS.com - High Integrity Systems also provide a safety + engineered and independently SIL3 certified version for use in safety and + mission critical applications that require provable dependability. + + 1 tab == 4 spaces! +*/ + + +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +/*----------------------------------------------------------- + * Application specific definitions. + * + * These definitions should be adjusted for your particular hardware and + * application requirements. + * + * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE + * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. + * + * See http://www.freertos.org/a00110.html. + *----------------------------------------------------------*/ + +/* Ensure stdint is only used by the compiler, and not the assembler. */ +//#ifdef __ICCARM__ + #include + extern uint32_t SystemCoreClock; +//#endif + +#define configUSE_PREEMPTION 1 +#define configUSE_IDLE_HOOK 0 // Default: 1 +#define configUSE_TICK_HOOK 0 // Default: 1 +#define configCPU_CLOCK_HZ ( SystemCoreClock ) +#define configTICK_RATE_HZ ( ( portTickType ) 250 ) +#define configMAX_PRIORITIES ( 5 ) +#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 ) +#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) ) +#define configMAX_TASK_NAME_LEN ( 10 ) +#define configUSE_TRACE_FACILITY 1 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_TICKLESS_IDLE 1 +#define configUSE_MUTEXES 1 +#define configQUEUE_REGISTRY_SIZE 8 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_MALLOC_FAILED_HOOK 0 // Default: 1 +#define configUSE_APPLICATION_TASK_TAG 0 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configGENERATE_RUN_TIME_STATS 0 + +/* Co-routine definitions. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) + +/* Software timer definitions. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY ( 2 ) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) + +/* Set the following definitions to 1 to include the API function, or zero +to exclude the API function. */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskCleanUpResources 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 + +/* Cortex-M specific definitions. */ +#ifdef __NVIC_PRIO_BITS + /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ + #define configPRIO_BITS __NVIC_PRIO_BITS +#else + #define configPRIO_BITS 4 /* 15 priority levels */ +#endif + +/* The lowest interrupt priority that can be used in a call to a "set priority" +function. */ +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf + +/* The highest interrupt priority that can be used by any interrupt service +routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL +INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER +PRIORITY THAN THIS! (higher priorities are lower numeric values. */ +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 + +/* Interrupt priorities used by the kernel port layer itself. These are generic +to all Cortex-M ports, and do not rely on any particular library functions. */ +#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) +/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! +See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ +#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) + +/* Normal assert() semantics without relying on the provision of an assert.h +header file. */ +#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); } + +/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS +standard names. */ +#define vPortSVCHandler SVC_Handler +#define xPortPendSVHandler PendSV_Handler +#define xPortSysTickHandler SysTick_Handler + +#endif /* FREERTOS_CONFIG_H */ diff --git a/src/common/Core/common.c b/src/common/Core/common.c new file mode 100644 index 0000000..7e19eaf --- /dev/null +++ b/src/common/Core/common.c @@ -0,0 +1,265 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 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 "Core/common.h" +#include "GPIO/usart.h" +#include "FreeRTOS.h" +#include "timers.h" +#include "GPS/gps.h" +#include +#include + +static uint64_t common_timestamp = 0; // milliseconds since startup +static TimerHandle_t common_timer; + +// The LFSR is used as random number generator +static const uint16_t lfsr_start_state = 0x12ABu; +static uint16_t lfsr; + +static void common_increase_timestamp(TimerHandle_t t); + +struct tm last_derived_time; +static uint64_t last_derived_time_timestamp = 0; +int last_derived_time_valid = 0; +int last_derived_time_delta_applied = 0; + + +#ifdef SIMULATOR +long timestamp_delta = 0; +#endif + +int find_last_sunday(const struct tm* time) { + struct tm t = *time; + + // the last sunday can never be before the 20th + t.tm_mday = 20; + + int last_sunday = 1; + + while (t.tm_mon == time->tm_mon) { + t.tm_mday++; + if (mktime(&t) == (time_t)-1) { + return -1; + } + + const int sunday = 0; + if (t.tm_wday == sunday) { + last_sunday = t.tm_mday; + } + } + + return last_sunday; +} + +/* Calculate if we are in daylight saving time. + * return 0 if false + * 1 if true + * -1 in case of error + */ +static int is_dst(const struct tm *time) { + /* DST from 01:00 UTC on last Sunday in March + * to 01:00 UTC on last Sunday in October + */ + const int march = 2; + const int october = 9; + if (time->tm_mon < march) { + return 0; + } + else if (time->tm_mon == march) { + int last_sunday = find_last_sunday(time); + if (last_sunday == -1) return -1; + + if (time->tm_mday < last_sunday) { + return 0; + } + else if (time->tm_mday == last_sunday) { + return (time->tm_hour < 1) ? 0 : 1; + } + else { + return 1; + } + } + else if (time->tm_mon > march && time->tm_mon < october) { + return 1; + } + else if (time->tm_mon == october) { + int last_sunday = find_last_sunday(time); + if (last_sunday == -1) return -1; + + if (time->tm_mday < last_sunday) { + return 1; + } + else if (time->tm_mday == last_sunday) { + return (time->tm_hour < 1) ? 1 : 0; + } + else { + return 0; + } + } + else { + return 0; + } +} + +int local_time(struct tm *time) { + const int local_time_offset=1; // hours + + int num_sv_used = 0; + int valid = gps_utctime(time, &num_sv_used); + + if (valid) { + time->tm_hour += local_time_offset; + + const int dst = is_dst(time); + if (dst == -1) { + usart_debug("mktime fail for dst %d-%d-%d %d:%d:%d " + "dst %d wday %d yday %d\r\n", + time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, + time->tm_hour, time->tm_min, time->tm_sec, + time->tm_isdst, time->tm_wday, time->tm_yday); + } + else if (dst == 1) { + time->tm_hour++; + time->tm_isdst = 1; + } + + // Let mktime fix the struct tm *time + if (mktime(time) == (time_t)-1) { + usart_debug("mktime fail for local_time %d-%d-%d %d:%d:%d " + "dst %d wday %d yday %d\r\n", + time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, + time->tm_hour, time->tm_min, time->tm_sec, + time->tm_isdst, time->tm_wday, time->tm_yday); + valid = 0; + } + + if (valid) { + last_derived_time = *time; + last_derived_time_timestamp = timestamp_now(); + last_derived_time_valid = 1; + last_derived_time_delta_applied = 0; + } + + } + + return valid; +} + +int local_derived_time(struct tm *time) { + + if (last_derived_time_valid == 0) { + return 0; + } + + // As there is a GPS timeout, local_time will think he has valid GPS data for GPS_MS_TIMEOUT. We need to remove it for better calculations. + if (last_derived_time_delta_applied == 0) { + last_derived_time_delta_applied = 1; + last_derived_time_timestamp -= GPS_MS_TIMEOUT; + } + + uint64_t new_timestamp = timestamp_now(); + + while (new_timestamp - last_derived_time_timestamp > 1000) { + last_derived_time.tm_sec += 1; + last_derived_time_timestamp += 1000; + } + + mktime(&last_derived_time); + + *time = last_derived_time; + + return 1; + +} + + +void common_init(void) +{ + common_timer = xTimerCreate("Timer", + pdMS_TO_TICKS(8), + pdTRUE, // Auto-reload + NULL, // No unique id + common_increase_timestamp + ); + + xTimerStart(common_timer, 0); + + lfsr = lfsr_start_state; +} + +static void common_increase_timestamp(TimerHandle_t __attribute__ ((unused))t) +{ + +#ifdef SIMULATOR + struct timespec ctime; + + clock_gettime(CLOCK_REALTIME, &ctime); + common_timestamp = ctime.tv_sec * 1000 + ctime.tv_nsec / 1.0e6 - timestamp_delta; + + if (timestamp_delta == 0) { + timestamp_delta = common_timestamp; + common_timestamp = 0; + } + +#else + common_timestamp += 8; +#endif +} + +uint64_t timestamp_now(void) +{ + return common_timestamp; // ms +} + + +// Return either 0 or 1, somewhat randomly +int random_bool(void) +{ + uint16_t bit; + + /* taps: 16 14 13 11; feedback polynomial: x^16 + x^14 + x^13 + x^11 + 1 */ + bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1; + lfsr = (lfsr >> 1) | (bit << 15); + + return bit; +} + +// For the debugger +static int faultsource = 0; +void trigger_fault(int source) +{ + usart_debug("Fatal: %d\r\n\r\n", source); + + __disable_irq(); + + faultsource = source; + + while (1) {} +} + +float round_float_to_half_steps(float value) +{ + return 0.5f * roundf(value * 2.0f); +} + diff --git a/src/common/Core/common.h b/src/common/Core/common.h new file mode 100644 index 0000000..4918a5b --- /dev/null +++ b/src/common/Core/common.h @@ -0,0 +1,82 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 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. +*/ + +/* A set of common routines for internal timekeeping and a LFSR to generate + * a random number + */ + +#pragma once + +#include +#include + +/* Feature defines */ +#define ENABLE_PSK31 0 + +#define FLOAT_PI 3.1415926535897932384f + +void common_init(void); + +// Return the current timestamp in milliseconds. Timestamps are monotonic, and not +// wall clock time. +uint64_t timestamp_now(void); + +// Calculate local time from GPS time, including daylight saving time +// Return 1 on success, 0 on failure +// A call to this function will invalidate the information inside 'time' +// regardless of return value. +int local_time(struct tm *time); + +// Try to calculate local time, based on a past valid local time and the current timestamp +// Return 1 on success, 0 on failure +int local_derived_time(struct tm *time); + +// Return either 0 or 1, somewhat randomly +int random_bool(void); + +// Fault handling mechanism +#define FAULT_SOURCE_MAIN 1 +#define FAULT_SOURCE_GPS 2 +#define FAULT_SOURCE_I2C 3 +#define FAULT_SOURCE_USART 4 +#define FAULT_SOURCE_TASK_OVERFLOW 5 +#define FAULT_SOURCE_CW_AUDIO_QUEUE 6 +#define FAULT_SOURCE_ADC1 7 +#define FAULT_SOURCE_TIM6_ISR 8 +#define FAULT_SOURCE_ADC2_QUEUE 9 +#define FAULT_SOURCE_ADC2_IRQ 10 +void trigger_fault(int source); + +int find_last_sunday(const struct tm*); + +#ifdef SIMULATOR +void __disable_irq(void); +#else +void hard_fault_handler_c(uint32_t *); +#endif + +// Round a value to the nearest 0.5 +float round_float_to_half_steps(float value); + +#define GPS_MS_TIMEOUT 2000ul diff --git a/src/common/Core/delay.h b/src/common/Core/delay.h new file mode 100644 index 0000000..4cc3f07 --- /dev/null +++ b/src/common/Core/delay.h @@ -0,0 +1,39 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + +#pragma once +#include + +// High-precisions delay (with approximations) + +// These functions only work if interupts are disabled +#ifdef SIMULATOR +void delay_us(int micros); +void delay_ms(int millis); +#else +void delay_us(uint32_t micros); +void delay_ms(uint32_t millis); +#endif + +void delay_init(void); diff --git a/src/common/Core/fsm.c b/src/common/Core/fsm.c new file mode 100644 index 0000000..9fe2c4c --- /dev/null +++ b/src/common/Core/fsm.c @@ -0,0 +1,679 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Matthias P. Braendli, Maximilien Cuony + * + * 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 +#include +#include +#include "Core/common.h" +#include "Core/fsm.h" +#include "GPIO/usart.h" +#include "GPIO/temperature.h" +#include "GPIO/analog.h" + +static struct fsm_input_signals_t fsm_in; +static struct fsm_output_signals_t fsm_out; + +static fsm_state_t current_state; +static balise_fsm_state_t balise_state; +static sstv_fsm_state_t sstv_state; + +// Keep track of when we last entered a given state, measured +// in ms using the timestamp_now() function +static uint64_t timestamp_state[_NUM_FSM_STATES]; + +static int last_supply_voltage_decivolts = 0; + +#define CW_MESSAGE_BALISE_LEN 64 +static char cw_message_balise[CW_MESSAGE_BALISE_LEN]; + + +// Each 20 minutes, send a SHORT_BEACON +#define SHORT_BEACON_MAX (60 * 20) +// Reset the counter if the QSO was 10m too long +#define SHORT_BEACON_RESET_IF_QSO (60 * 10) + +/* At least 1 second predelay for CW, ensures the receivers had enough time + * time to open their squelch before the first letter gets transmitted + */ +#define CW_PREDELAY " " + +// Some time to ensure we don't cut off the last letter +#define CW_POSTDELAY " " + +// The counter (up to 20 minutes) for the short balise +static int short_beacon_counter_s = 0; +static uint64_t short_beacon_counter_last_update = 0; + +// The last start of the last qso +static uint64_t last_qso_start_timestamp = 0; + +// Information from which we can calculate the QSO duration +static struct { + int qso_occurred; + uint64_t qso_start_time; +} qso_info; + +void fsm_init() { + memset(&fsm_in, 0, sizeof(fsm_in)); + memset(&fsm_out, 0, sizeof(fsm_out)); + + memset(timestamp_state, 0, _NUM_FSM_STATES * sizeof(*timestamp_state)); + timestamp_state[FSM_OISIF] = timestamp_now(); + + current_state = FSM_OISIF; + balise_state = BALISE_FSM_EVEN_HOUR; + sstv_state = SSTV_FSM_OFF; + + qso_info.qso_occurred = 0; + qso_info.qso_start_time = timestamp_now(); +} + +// Calculate the time spent in the current state +static uint64_t fsm_current_state_time_ms(void) { + return timestamp_now() - timestamp_state[current_state]; +} + +static uint64_t fsm_current_state_time_s(void) { + return fsm_current_state_time_ms() / 1000; +} + +static const char* state_name(fsm_state_t state) { + switch (state) { + case FSM_OISIF: return "FSM_OISIF"; + case FSM_OPEN1: return "FSM_OPEN1"; + case FSM_OPEN2: return "FSM_OPEN2"; + case FSM_LETTRE: return "FSM_LETTRE"; + case FSM_ECOUTE: return "FSM_ECOUTE"; + case FSM_ATTENTE: return "FSM_ATTENTE"; + case FSM_QSO: return "FSM_QSO"; + case FSM_ANTI_BAVARD: return "FSM_ANTI_BAVARD"; + case FSM_BLOQUE: return "FSM_BLOQUE"; + case FSM_TEXTE_73: return "FSM_TEXTE_73"; + case FSM_TEXTE_HB9G: return "FSM_TEXTE_HB9G"; + case FSM_TEXTE_LONG: return "FSM_TEXTE_LONG"; + case FSM_BALISE_LONGUE: return "FSM_BALISE_LONGUE"; + case FSM_BALISE_SPECIALE: return "FSM_BALISE_SPECIALE"; + case FSM_BALISE_COURTE: return "FSM_BALISE_COURTE"; + case FSM_BALISE_COURTE_OPEN: return "FSM_BALISE_COURTE_OPEN"; + default: return "ERROR!"; + } +} + +static const char* balise_state_name(balise_fsm_state_t state) { + switch (state) { + case BALISE_FSM_EVEN_HOUR: return "BALISE_FSM_EVEN_HOUR"; + case BALISE_FSM_ODD_HOUR: return "BALISE_FSM_ODD_HOUR"; + case BALISE_FSM_PENDING: return "BALISE_FSM_PENDING"; + default: return "ERROR!"; + } +} + +static const char* sstv_state_name(sstv_fsm_state_t state) { + switch (state) { + case SSTV_FSM_OFF: return "SSTV_FSM_OFF"; + case SSTV_FSM_ON: return "SSTV_FSM_ON"; + default: return "ERROR!"; + } +} + +static fsm_state_t select_grande_balise(void) { + if (fsm_in.qrp || fsm_in.swr_high) { + return FSM_BALISE_SPECIALE; + } + else { + return FSM_BALISE_LONGUE; + } +} + +static uint64_t qso_duration(void) { + return timestamp_state[current_state] - qso_info.qso_start_time; +} + +// Between turns in a QSO, the repeater sends a letter in CW, +// different messages are possible. They are sorted here from +// low to high priority. +const char* letter_all_ok = "K"; +const char* letter_sstv = "S"; +const char* letter_qrp = "G"; +const char* letter_freq_high = "U"; +const char* letter_freq_low = "D"; +const char* letter_swr_high = "R"; + +static const char* fsm_select_letter(void) { + if (fsm_in.swr_high) { + return letter_swr_high; + } + else if (fsm_in.discrim_d) { + return letter_freq_low; + } + else if (fsm_in.discrim_u) { + return letter_freq_high; + } + else if (fsm_in.qrp) { + return letter_qrp; + } + else if (sstv_state == SSTV_FSM_ON) { + return letter_sstv; + } + + return letter_all_ok; +} + + +void fsm_update() { + + fsm_state_t next_state = current_state; + + // Some defaults for the outgoing signals + fsm_out.tx_on = 0; + fsm_out.modulation = 0; + fsm_out.cw_psk31_trigger = 0; + fsm_out.cw_dit_duration = 50; + fsm_out.msg_frequency = 960; + fsm_out.require_tone_detector = 0; + // other output signals keep their value + + switch (current_state) { + case FSM_OISIF: + // Check the length of the last QSO, and reset the SHORT_BEACON counter if needed + if (last_qso_start_timestamp != 0) { + + if ((timestamp_now() - last_qso_start_timestamp) > 1000 * SHORT_BEACON_RESET_IF_QSO) { + short_beacon_counter_s = 0; + } + + last_qso_start_timestamp = 0; + } + + // Increment the SHORT_BEACON counter based on time spent in the state + while(short_beacon_counter_s < SHORT_BEACON_MAX && (fsm_current_state_time_s() - short_beacon_counter_last_update > 1)) { + short_beacon_counter_last_update++; + short_beacon_counter_s++; + } + + // SQ and button 1750 are debounced inside pio.c (300ms) + fsm_out.require_tone_detector = fsm_in.sq; + + if ( (fsm_in.sq && fsm_in.det_1750) || + (fsm_in.sq && sstv_state == SSTV_FSM_ON) || + (fsm_in.button_1750)) { + next_state = FSM_OPEN1; + } + else if (balise_state == BALISE_FSM_PENDING) { + short_beacon_counter_s = 0; + next_state = select_grande_balise(); + } + else if (!fsm_in.qrp && short_beacon_counter_s == SHORT_BEACON_MAX) { + short_beacon_counter_s = 0; + next_state = FSM_BALISE_COURTE; + } + + break; + + case FSM_OPEN1: + /* Do not enable TX_ON here, otherwise we could get stuck transmitting + * forever if SQ never goes low. + */ + fsm_out.require_tone_detector = 1; + if (!fsm_in.sq && !fsm_in.det_1750) { + next_state = FSM_OPEN2; + } + break; + + case FSM_OPEN2: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + qso_info.qso_occurred = 0; + qso_info.qso_start_time = timestamp_now(); + + if (fsm_current_state_time_ms() > 200) { + next_state = FSM_LETTRE; + } + break; + + case FSM_LETTRE: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + fsm_out.msg = fsm_select_letter(); + if (fsm_out.msg[0] == 'G') { + // The letter 'G' is a bit different + fsm_out.msg_frequency = 696; + } + fsm_out.cw_psk31_trigger = 1; + + if (fsm_in.cw_psk31_done) { + next_state = FSM_ECOUTE; + } + break; + + case FSM_ECOUTE: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + + /* Time checks: + * We need to check the total TX_ON duration to decide the text to + * send. This is the QSO duration. + * + * We also need to check if we actually entered the QSO state + * recently, otherwise we want to go to ATTENTE. That's why the + * additional field qso_occurred is required. + */ + + if (fsm_in.sq) { + next_state = FSM_QSO; + } + else { + if (fsm_current_state_time_s() > 5) { + if (balise_state == BALISE_FSM_PENDING) { + short_beacon_counter_s = 0; + next_state = select_grande_balise(); + } + else if (qso_info.qso_occurred) { + if (qso_duration() >= 1000ul * 15 * 60) { + next_state = FSM_TEXTE_LONG; + } + else if (qso_duration() >= 1000ul * 10 * 60) { + next_state = FSM_TEXTE_HB9G; + } + else if (qso_duration() >= 1000ul * 5 * 60) { + next_state = FSM_TEXTE_73; + } + else { + next_state = FSM_OISIF; + } + } + } + + if (fsm_current_state_time_s() > 6 && !qso_info.qso_occurred) { + next_state = FSM_ATTENTE; + } + + /* If everything fails and the state was not changed after 7 + * seconds, fall back to oisif + */ + if (fsm_current_state_time_s() > 7) { + next_state = FSM_OISIF; + } + } + break; + + case FSM_ATTENTE: + if (fsm_in.sq) { + fsm_out.require_tone_detector = 1; + next_state = FSM_ECOUTE; + } + else if (fsm_current_state_time_s() > 15) { + next_state = FSM_OISIF; + } + break; + + case FSM_QSO: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + qso_info.qso_occurred = 1; + + // Save the starting timestamp, if there is none + if (last_qso_start_timestamp == 0) { + last_qso_start_timestamp = timestamp_now(); + } + + if (!fsm_in.sq && fsm_current_state_time_s() < 3) { + /* To avoid that very short open squelch triggers + * transmit CW letters all the time. Some people + * enjoy doing that. + */ + next_state = FSM_ECOUTE; + } + else if (!fsm_in.sq && fsm_current_state_time_s() >= 3) { + next_state = FSM_LETTRE; + } + else if (fsm_current_state_time_s() > 5 * 60) { + next_state = FSM_ANTI_BAVARD; + } + break; + + case FSM_ANTI_BAVARD: + fsm_out.tx_on = 1; + // No modulation! + + // Short post-delay to underscore the fact that + // transmission was forcefully cut off. + fsm_out.msg = " HI HI "; + fsm_out.cw_psk31_trigger = 1; + + if (fsm_in.cw_psk31_done) { + next_state = FSM_BLOQUE; + } + break; + + case FSM_BLOQUE: + if (fsm_current_state_time_s() > 10) { + next_state = FSM_OISIF; + } + break; + + case FSM_TEXTE_73: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + fsm_out.msg_frequency = 696; + fsm_out.cw_dit_duration = 70; + fsm_out.msg = " 73" CW_POSTDELAY; + fsm_out.cw_psk31_trigger = 1; + + if (fsm_in.sq) { + next_state = FSM_QSO; + qso_info.qso_start_time = timestamp_now(); + } + else if (fsm_in.cw_psk31_done) { + next_state = FSM_OISIF; + } + break; + + case FSM_TEXTE_HB9G: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + fsm_out.msg_frequency = 696; + fsm_out.cw_dit_duration = 70; + // No need for CW_PREDELAY, since we are already transmitting + fsm_out.msg = " HB9G" CW_POSTDELAY; + fsm_out.cw_psk31_trigger = 1; + + if (fsm_in.sq) { + next_state = FSM_QSO; + qso_info.qso_start_time = timestamp_now(); + } + else if (fsm_in.cw_psk31_done) { + next_state = FSM_OISIF; + } + break; + + case FSM_TEXTE_LONG: + fsm_out.tx_on = 1; + fsm_out.modulation = 1; + fsm_out.require_tone_detector = 1; + + fsm_out.msg_frequency = 696; + fsm_out.cw_dit_duration = 70; + + // No need for CW_PREDELAY, since we are already transmitting + if (random_bool()) { + fsm_out.msg = " HB9G 1628M" CW_POSTDELAY; + } + else { + fsm_out.msg = " HB9G JN36BK" CW_POSTDELAY; + } + fsm_out.cw_psk31_trigger = 1; + + if (fsm_in.sq) { + next_state = FSM_QSO; + qso_info.qso_start_time = timestamp_now(); + } + else if (fsm_in.cw_psk31_done) { + next_state = FSM_OISIF; + } + break; + + case FSM_BALISE_LONGUE: + fsm_out.tx_on = 1; + fsm_out.msg_frequency = 588; + fsm_out.cw_dit_duration = 110; + + { + const float supply_voltage = round_float_to_half_steps(analog_measure_12v()); + const int supply_decivolts = supply_voltage * 10.0f; + + char *eol_info = "73"; + if (!fsm_in.wind_generator_ok) { + eol_info = "\\"; + // The backslash is the SK digraph + } + + char supply_trend = '='; + // = means same voltage as previous + // + means higher + // - means lower + if (last_supply_voltage_decivolts < supply_decivolts) { + supply_trend = '+'; + } + else if (last_supply_voltage_decivolts > supply_decivolts) { + supply_trend = '-'; + } + + if (temperature_valid()) { + snprintf(cw_message_balise, CW_MESSAGE_BALISE_LEN-1, + CW_PREDELAY "HB9G JN36BK 1628M U %dV%01d %c T %d %s" CW_POSTDELAY, + supply_decivolts / 10, + supply_decivolts % 10, + supply_trend, + (int)(round_float_to_half_steps(temperature_get())), + eol_info); + } + else { + snprintf(cw_message_balise, CW_MESSAGE_BALISE_LEN-1, + CW_PREDELAY "HB9G JN36BK 1628M U %dV%01d %c %s" CW_POSTDELAY, + supply_decivolts / 10, + supply_decivolts % 10, + supply_trend, + eol_info); + } + + fsm_out.msg = cw_message_balise; + + last_supply_voltage_decivolts = supply_decivolts; + + fsm_out.cw_psk31_trigger = 1; + } + + if (fsm_in.cw_psk31_done) { + next_state = FSM_OISIF; + } + break; + + case FSM_BALISE_SPECIALE: + fsm_out.tx_on = 1; + fsm_out.msg_frequency = 696; + fsm_out.cw_dit_duration = 70; + + { + const float supply_voltage = round_float_to_half_steps(analog_measure_12v()); + const int supply_decivolts = supply_voltage * 10.0f; + + char *eol_info = "73"; + if (!fsm_in.wind_generator_ok) { + eol_info = "\\"; + // The backslash is the SK digraph + } + + snprintf(cw_message_balise, CW_MESSAGE_BALISE_LEN-1, + CW_PREDELAY "HB9G U %dV%01d %s" CW_POSTDELAY, + supply_decivolts / 10, + supply_decivolts % 10, + eol_info); + + fsm_out.msg = cw_message_balise; + + fsm_out.cw_psk31_trigger = 1; + } + + if (fsm_in.cw_psk31_done) { + next_state = FSM_OISIF; + } + break; + + case FSM_BALISE_COURTE: + case FSM_BALISE_COURTE_OPEN: + + fsm_out.tx_on = 1; + + fsm_out.msg_frequency = 696; + fsm_out.cw_dit_duration = 70; + + { + int rand = random_bool() * 2 + random_bool(); + + if (rand == 0) { + fsm_out.msg = CW_PREDELAY "HB9G" CW_POSTDELAY; + } + else if (rand == 1) { + fsm_out.msg = CW_PREDELAY "HB9G JN36BK" CW_POSTDELAY; + } + else if (rand == 2) { + fsm_out.msg = CW_PREDELAY "HB9G 1628M" CW_POSTDELAY; + } + else { + fsm_out.msg = CW_PREDELAY "HB9G JN36BK 1628M" CW_POSTDELAY; + } + } + fsm_out.cw_psk31_trigger = 1; + + if (current_state == FSM_BALISE_COURTE) { + if (fsm_in.cw_psk31_done) { + if (fsm_in.sq) { + next_state = FSM_OPEN2; + } + else { + next_state = FSM_OISIF; + } + } + else if (fsm_in.sq) { + next_state = FSM_BALISE_COURTE_OPEN; + } + } + else { //FSM_BALISE_COURTE_OPEN + if (fsm_in.cw_psk31_done) { + next_state = FSM_OPEN2; + } + } + + break; + default: + // Should never happen + next_state = FSM_OISIF; + break; + } + + + if (next_state != current_state) { + timestamp_state[next_state] = timestamp_now(); + + short_beacon_counter_last_update = 0; + + fsm_state_switched(state_name(next_state)); + } + current_state = next_state; +} + +void fsm_update_inputs(struct fsm_input_signals_t* inputs) +{ + fsm_in = *inputs; +} + +void fsm_get_outputs(struct fsm_output_signals_t* out) +{ + *out = fsm_out; +} + +void fsm_balise_force() { + balise_state = BALISE_FSM_PENDING; +} + +void fsm_balise_update() { + + balise_fsm_state_t next_state = balise_state; + + switch (balise_state) { + case BALISE_FSM_EVEN_HOUR: + if (fsm_in.hour_is_even == 0) { + next_state = BALISE_FSM_ODD_HOUR; + } + break; + case BALISE_FSM_ODD_HOUR: + if (fsm_in.hour_is_even == 1) { + if (timestamp_now() > 1000 * 60) { // Does not start the balise at startup + next_state = BALISE_FSM_PENDING; + } + else { + next_state = BALISE_FSM_EVEN_HOUR; + } + } + break; + case BALISE_FSM_PENDING: + if (current_state == FSM_BALISE_SPECIALE || + current_state == FSM_BALISE_LONGUE) { + next_state = BALISE_FSM_EVEN_HOUR; + } + break; + default: + // Should never happen + next_state = BALISE_FSM_EVEN_HOUR; + break; + } + + if (next_state != balise_state) { + fsm_state_switched(balise_state_name(next_state)); + } + + balise_state = next_state; +} + +int fsm_sstv_update() { + + sstv_fsm_state_t next_state = sstv_state; + + switch (sstv_state) { + case SSTV_FSM_OFF: + if (fsm_in.sq && fsm_in.fax_mode) { + next_state = SSTV_FSM_ON; + } + break; + case SSTV_FSM_ON: + if (current_state == FSM_BALISE_LONGUE || + current_state == FSM_ANTI_BAVARD || + current_state == FSM_BALISE_SPECIALE || + fsm_in.long_1750 + ) { + next_state = SSTV_FSM_OFF; + } + break; + + default: + // Should never happen + next_state = SSTV_FSM_OFF; + break; + } + + if (next_state != sstv_state) { + fsm_state_switched(sstv_state_name(next_state)); + } + + sstv_state = next_state; + + return sstv_state == SSTV_FSM_ON; +} diff --git a/src/common/Core/fsm.h b/src/common/Core/fsm.h new file mode 100644 index 0000000..37ca386 --- /dev/null +++ b/src/common/Core/fsm.h @@ -0,0 +1,135 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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. + */ + +#pragma once +#include + +// List of all states the FSM of the relay can be in +enum fsm_state_e { + FSM_OISIF = 0, // Idle + FSM_OPEN1, // 1750 Hz received and squelch open + FSM_OPEN2, // Squelch closed + FSM_LETTRE, // Transmit single status letter + FSM_ECOUTE, // Repeater open, waiting for QSO + FSM_ATTENTE, // No QSO after a short while + FSM_QSO, // QSO ongoing + FSM_ANTI_BAVARD, // QSO too long, cut transmission + FSM_BLOQUE, // Backoff after ANTI_BAVARD + FSM_TEXTE_73, // Transmit 73 after QSO + FSM_TEXTE_HB9G, // Transmit HB9G after QSO + FSM_TEXTE_LONG, // Transmit either HB9G JN36BK or HB9G 1628M after QSO + FSM_BALISE_LONGUE, // Full-length 2-hour beacon + FSM_BALISE_SPECIALE, // 2-hour beacon when in QRP or with high power return mode + FSM_BALISE_COURTE, // Short intermittent beacon + FSM_BALISE_COURTE_OPEN, // Short intermittent beacon, need to switch to OPEN + _NUM_FSM_STATES // Dummy state to count the number of states +}; + +typedef enum fsm_state_e fsm_state_t; + +// List of all states the balise FSM of the relay can be in +enum balise_fsm_state_e { + BALISE_FSM_EVEN_HOUR = 0, // Even hours. + BALISE_FSM_ODD_HOUR, // Odd hours + BALISE_FSM_PENDING, // Waiting for transmission of balise +}; + +typedef enum balise_fsm_state_e balise_fsm_state_t; + + +// List of all states the SSTV FSM of the relay can be in +enum sstv_fsm_state_e { + SSTV_FSM_OFF = 0, + SSTV_FSM_ON, +}; + +typedef enum sstv_fsm_state_e sstv_fsm_state_t; + + +// All signals that the FSM can read, most of them are actually booleans +struct fsm_input_signals_t { + /* Signals coming from repeater electronics */ + int sq; // Squelch detection + int discrim_u; // FM discriminator says RX is too high in frequency + int qrp; // The relay is currently running with low power + int hour_is_even; // 1 if hour is even + float temp; // temperature in degrees C + float humidity; // relative humidity, range [0-100] % + int wind_generator_ok; // false if the generator is folded out of the wind + int discrim_d; // FM discriminator says RX is too low in frequency + int button_1750; // Front panel button 1750Hz + + /* Signals coming from FAX and 1750 detector */ + int fax_mode; // 1750Hz filter disabled for machine-generated modes + int det_1750; // 1750Hz detected + int long_1750; // 1750Hz detected for more than 5s + + /* Signals coming from CW and PSK generator */ + int cw_psk31_done; // The CW and PSK generator has finished transmitting the message + + /* Signal coming from the standing wave ratio meter */ + int swr_high; // We see a lot of return power + +}; + +// All signals the FSM has to control +struct fsm_output_signals_t { + /* Signals to the repeater electronics */ + int tx_on; // Enable TX circuitry + int modulation; // Enable repeater RX to TX modulation + + /* Signals to the CW and PSK generator */ + const char* msg; // The message to transmit + int msg_frequency; // What audio frequency for the CW or PSK message + int cw_dit_duration; // CW speed, dit duration in ms + int cw_psk31_trigger; // Set to true to trigger a CW or PSK31 transmission. + // PSK31 is sent if cw_dit_duration is 0 + + /* Tone detector */ + int require_tone_detector; // Enables audio input and detector for DTMF and 1750 +}; + +// Initialise local structures +void fsm_init(void); + +// Call the FSM once and update the internal state +void fsm_update(void); + +void fsm_balise_update(void); + +// Update the FAX/SSTV state machine and return 1 if the 1750 notch filter must be +// disabled +int fsm_sstv_update(void); + +// Force a BALISE +void fsm_balise_force(void); + +// Setter for inputs +void fsm_update_inputs(struct fsm_input_signals_t* inputs); + +// Getter for outputs +void fsm_get_outputs(struct fsm_output_signals_t* out); + +// Announce a state change +void fsm_state_switched(const char *new_state); diff --git a/src/common/Core/main.c b/src/common/Core/main.c new file mode 100644 index 0000000..07657fa --- /dev/null +++ b/src/common/Core/main.c @@ -0,0 +1,710 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony + * + * 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 +#include +#include +#include + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "task.h" +#include "timers.h" +#include "semphr.h" + +/* Includes */ +#include "Audio/audio.h" +#include "Audio/audio_in.h" +#include "Audio/tone.h" +#include "Audio/cw.h" +#include "GPIO/pio.h" +#include "GPIO/i2c.h" +#include "GPS/gps.h" +#include "Core/fsm.h" +#include "Core/common.h" +#include "GPIO/usart.h" +#include "Core/delay.h" +#include "GPIO/temperature.h" +#include "GPIO/leds.h" +#include "GPIO/analog.h" +#include "vc.h" + +#ifdef SIMULATOR +extern int gui_in_tone_1750; +#endif + +static void print_task_stats(void); + +static int tm_trigger_button = 0; + +static struct fsm_input_signals_t fsm_input; +static int hour_is_even = 0; + +/* Threshold for SWR measurement */ +const int swr_refl_threshold = 10; // mV + +/* Saturating counter for SWR measurement + * + * Hysteresis: + * If the counter reaches the max value, the + * SWR Error is triggered. + * + * The error can only be cleared through + * a full reset. + */ +#define SWR_ERROR_COUNTER_MAX 10 +static int swr_error_counter = 0; +static int swr_error_flag = 0; + +// Platform specific init function +void init(void); + +// Tasks +static void detect_button_press(void *pvParameters); +static void exercise_fsm(void *pvParameters); +static void nf_analyse(void *pvParameters); +static void gps_monit_task(void *pvParameters); +static void launcher_task(void *pvParameters); + +// Audio callback function +static void audio_callback(void* context, int select_buffer); +// Debugging +static uint64_t timestamp_last_audio_callback = 0; + +void vApplicationStackOverflowHook(TaskHandle_t, signed char *); + +void vApplicationStackOverflowHook(TaskHandle_t __attribute__ ((unused)) xTask, signed char *pcTaskName) { + usart_debug("TASK OVERFLOW %s\r\n", pcTaskName); + trigger_fault(FAULT_SOURCE_TASK_OVERFLOW); +} + +int main(void) { + init(); + delay_init(); + usart_init(); + usart_debug("\r\n******* glutt-o-matique version %s *******\r\n", vc_get_version()); + +#ifndef SIMULATOR + + if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) + { + usart_debug_puts("WARNING: A IWDG Reset occured!\r\n"); + } + RCC_ClearFlag(); + +#endif + + TaskHandle_t task_handle; + xTaskCreate( + launcher_task, + "Launcher", + 2*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + &task_handle); + + if (!task_handle) { + trigger_fault(FAULT_SOURCE_MAIN); + } + + vTaskStartScheduler(); + + /* HALT */ + while(1); +} + + +// Launcher task is here to make sure the scheduler is +// already running when calling the init functions. +static void launcher_task(void __attribute__ ((unused))*pvParameters) +{ + usart_debug_puts("CW init\r\n"); + cw_psk31_init(16000); + + usart_debug_puts("PIO init\r\n"); + pio_init(); + + usart_debug_puts("Analog init\r\n"); + analog_init(); + + usart_debug_puts("I2C init\r\n"); + i2c_init(); + + usart_debug_puts("common init\r\n"); + common_init(); + + usart_debug_puts("GPS init\r\n"); + gps_init(); + + usart_debug_puts("DS18B20 init\r\n"); + temperature_init(); + + usart_debug_puts("TaskButton init\r\n"); + + TaskHandle_t task_handle; + xTaskCreate( + detect_button_press, + "TaskButton", + 4*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + &task_handle); + + if (!task_handle) { + trigger_fault(FAULT_SOURCE_MAIN); + } + + usart_debug_puts("TaskFSM init\r\n"); + + xTaskCreate( + exercise_fsm, + "TaskFSM", + 4*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + &task_handle); + + if (!task_handle) { + trigger_fault(FAULT_SOURCE_MAIN); + } + + usart_debug_puts("TaskGPS init\r\n"); + + xTaskCreate( + gps_monit_task, + "TaskGPSMonit", + 4*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + &task_handle); + + if (!task_handle) { + trigger_fault(FAULT_SOURCE_MAIN); + } + + usart_debug_puts("Audio init\r\n"); + audio_initialize(Audio16000HzSettings); + + usart_debug_puts("Audio set volume\r\n"); + audio_set_volume(210); + + usart_debug_puts("Audio set callback\r\n"); + audio_play_with_callback(audio_callback, NULL); + + usart_debug_puts("Tone init\r\n"); + tone_init(); + + usart_debug_puts("Audio in init\r\n"); + audio_in_initialize(); + + usart_debug_puts("TaskNF init\r\n"); + + xTaskCreate( + nf_analyse, + "TaskNF", + 3*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 3UL, + &task_handle); + + if (!task_handle) { + trigger_fault(FAULT_SOURCE_MAIN); + } + + usart_debug_puts("Init done.\r\n"); + + int last_qrp_from_supply = 0; + int send_audio_callback_warning = 0; + + int i = 0; + + while(1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + + if (i == 0) { + i = 1; + leds_turn_on(LED_GREEN); + + print_task_stats(); + } + else { + i = 0; + leds_turn_off(LED_GREEN); + } + + struct fsm_output_signals_t fsm_out; + fsm_get_outputs(&fsm_out); + + tone_detector_enable(fsm_out.require_tone_detector); + + if (fsm_out.tx_on) { + int swr_fwd_mv, swr_refl_mv; + if (analog_measure_swr(&swr_fwd_mv, &swr_refl_mv)) { + if (swr_refl_mv > swr_refl_threshold) { + usart_debug("SWR meas %d mV\r\n", swr_refl_mv); + swr_error_counter++; + } + else { + swr_error_counter--; + } + + if (swr_error_counter > SWR_ERROR_COUNTER_MAX) { + swr_error_counter = SWR_ERROR_COUNTER_MAX; + if (!swr_error_flag) { + usart_debug("Set SWR error\r\n"); + } + swr_error_flag = 1; + } + } + } + else { + const int qrp_from_supply = analog_supply_too_low(); + if (swr_error_flag) { + pio_set_qrp(1); + } + else if (qrp_from_supply != last_qrp_from_supply) { + usart_debug("QRP = %d\r\n", qrp_from_supply); + last_qrp_from_supply = qrp_from_supply; + + pio_set_qrp(qrp_from_supply); + } + } + + if (timestamp_now() - timestamp_last_audio_callback > 1000) { + if (send_audio_callback_warning == 0) { + send_audio_callback_warning = 1; + usart_debug("[HOHO] timestamp_last_audio_callback > 1000\r\n"); + } + } + else { + if (send_audio_callback_warning == 1) { + send_audio_callback_warning = 0; + usart_debug("[HOHO] Fix ? Now timestamp_last_audio_callback < 1000\r\n"); + } + } + } +} + + +static void detect_button_press(void __attribute__ ((unused))*pvParameters) +{ + int pin_high_count = 0; + int last_pin_high_count = 0; + const int pin_high_thresh = 10; + while (1) { + if (pio_read_button()) { + if (pin_high_count < pin_high_thresh) { + pin_high_count++; + } + } + else { + if (pin_high_count > 0) { + pin_high_count--; + } + } + + vTaskDelay(pdMS_TO_TICKS(10)); /* Debounce Delay */ + + if (pin_high_count == pin_high_thresh && + last_pin_high_count != pin_high_count) { + tm_trigger_button = 1; + usart_debug_puts("Bouton bleu\r\n"); + } + else if (pin_high_count == 0 && + last_pin_high_count != pin_high_count) { + tm_trigger_button = 0; + } + + last_pin_high_count = pin_high_count; + } +} + +int only_zero_in_audio_buffer = 1; +int count_zero_audio_buffer = 0; + +static void audio_callback(void __attribute__ ((unused))*context, int select_buffer) { + static int16_t audio_buffer0[AUDIO_BUF_LEN]; + static int16_t audio_buffer1[AUDIO_BUF_LEN]; + int16_t *samples; + + if (select_buffer == 0) { + samples = audio_buffer0; + leds_turn_off(LED_RED); + } else { + samples = audio_buffer1; + leds_turn_on(LED_RED); + } + + size_t samples_len = cw_psk31_fill_buffer(samples, AUDIO_BUF_LEN); + + if (samples_len == 0) { + for (int i = 0; i < AUDIO_BUF_LEN; i++) { + samples[i] = 0; + } + + samples_len = AUDIO_BUF_LEN; + + if (count_zero_audio_buffer < 2) { + count_zero_audio_buffer++; + } else { + only_zero_in_audio_buffer = 1; + } + } else { + only_zero_in_audio_buffer = 0; + count_zero_audio_buffer = 0; + } + + if (!audio_provide_buffer_without_blocking(samples, samples_len)) { + usart_debug("[HOHO] audio_provide_buffer_without_blocking returned False.\r\n"); + } + + timestamp_last_audio_callback = timestamp_now(); + +} + +static struct tm gps_time; +static void gps_monit_task(void __attribute__ ((unused))*pvParameters) { + + /* There are two types of non GPS clocks: the DERIVED one which works if + * GPS time was known at some point, and the free-running that only depends + * on timestamp_now(). The free-running one is used to ensure 2h beacons are + * transmitted even if GPS never gave us time. The DERIVED kicks in when GPS + * fails after having output time information and tries to keep accurate absolute + * time. + */ + + pio_set_gps_epps(1); + + int t_gps_print_latch = 0; + int t_gps_hours_handeled = 0; + uint64_t last_hour_timestamp = 0; + + uint64_t last_volt_and_temp_timestamp, last_hour_is_even_change_timestamp; + last_volt_and_temp_timestamp = last_hour_is_even_change_timestamp = timestamp_now(); + + int last_even = -1; + + while (1) { + const uint64_t now = timestamp_now(); + + if (last_volt_and_temp_timestamp + 20000 < now) { + usart_debug("ALIM %d mV\r\n", (int)roundf(1000.0f * analog_measure_12v())); + + const float temp = temperature_get(); + usart_debug("TEMP %d.%02d\r\n", (int)temp, (int)(temp * 100.0f - (int)(temp) * 100.0f)); + + last_volt_and_temp_timestamp = now; + } + + struct tm time = {0}; + int time_valid = local_time(&time); + int derived_mode = 0; + + if (time_valid) { + if (time.tm_sec % 2) { + pio_set_gps_epps(1); + } + else { + pio_set_gps_epps(0); + } + + derived_mode = 0; + } + else { + time_valid = local_derived_time(&time); + + if (time_valid) { + if (time.tm_sec % 4 >= 2) { + pio_set_gps_epps(1); + } + else { + pio_set_gps_epps(0); + } + + derived_mode = 1; + } + } + + if (time_valid) { + hour_is_even = (time.tm_hour + 1) % 2; + + if (last_even != hour_is_even) { + last_even = hour_is_even; + + usart_debug("Even changed: %i %i %s\r\n", hour_is_even, time.tm_hour, derived_mode ? "DERIVED" : "GPS"); + } + } + else if (last_hour_is_even_change_timestamp + (2 * 3600 * 1000) < now) { + hour_is_even = (hour_is_even + 1) % 2; + last_even = hour_is_even; + + usart_debug("Even changed: %i %i FREE-RUNNING\r\n", hour_is_even, time.tm_hour); + last_hour_is_even_change_timestamp = now; + } + + int num_sv_used = 0; + gps_utctime(&gps_time, &num_sv_used); + + if (time.tm_sec % 30 == 0 && t_gps_print_latch == 0) { + usart_debug("T_GPS %04d-%02d-%02d %02d:%02d:%02d %d SV tracked\r\n", + gps_time.tm_year + 1900, gps_time.tm_mon + 1, gps_time.tm_mday, + gps_time.tm_hour, gps_time.tm_min, gps_time.tm_sec, + num_sv_used); + + char *mode = ""; + + if (derived_mode) { + mode = "Derived"; + } + else { + mode = "GPS"; + } + + usart_debug("TIME %04d-%02d-%02d %02d:%02d:%02d [%s]\r\n", + time.tm_year + 1900, + time.tm_mon + 1, time.tm_mday, + time.tm_hour, time.tm_min, time.tm_sec, + mode); + + t_gps_print_latch = 1; + } + + if (time.tm_sec % 30 > 0) { + t_gps_print_latch = 0; + } + + if (time_valid && derived_mode == 0 && gps_time.tm_sec == 0 && gps_time.tm_min == 0 && t_gps_hours_handeled == 0) { + if (last_hour_timestamp == 0) { + usart_debug("DERIV INIT TS=%lld\r\n", now); + } + else { + usart_debug("DERIV TS=%lld Excepted=%lld Delta=%lld\r\n", + now, + last_hour_timestamp + 3600000, + last_hour_timestamp + 3600000 - now + ); + } + + last_hour_timestamp = now; + + t_gps_hours_handeled = 1; + } + + if (gps_time.tm_sec != 0) { + t_gps_hours_handeled = 0; + } + + vTaskDelay(pdMS_TO_TICKS(100)); + + // Reload watchdog +#ifndef SIMULATOR + IWDG_ReloadCounter(); +#endif + } +} + +static void exercise_fsm(void __attribute__ ((unused))*pvParameters) +{ + fsm_init(); + + int cw_last_trigger = 0; + int last_tm_trigger_button = 0; + + int last_sq = 0; + int last_qrp = 0; + int last_cw_done = 0; + int last_discrim_d = 0; + int last_discrim_u = 0; + int last_wind_generator_ok = 0; + + fsm_input.humidity = 0; + fsm_input.temp = 15; + fsm_input.swr_high = 0; + fsm_input.fax_mode = 0; + fsm_input.wind_generator_ok = 1; + + while (1) { + vTaskDelay(pdMS_TO_TICKS(10)); + + pio_set_fsm_signals(&fsm_input); + + if (last_sq != fsm_input.sq) { + last_sq = fsm_input.sq; + usart_debug("In SQ %d\r\n", last_sq); + } + if (last_qrp != fsm_input.qrp) { + last_qrp = fsm_input.qrp; + usart_debug("In QRP %d\r\n", last_qrp); + } + if (last_discrim_d != fsm_input.discrim_d) { + last_discrim_d = fsm_input.discrim_d; + usart_debug("In D %d\r\n", last_discrim_d); + } + if (last_discrim_u != fsm_input.discrim_u) { + last_discrim_u = fsm_input.discrim_u; + usart_debug("In U %d\r\n", last_discrim_u); + } + if (last_wind_generator_ok != fsm_input.wind_generator_ok) { + last_wind_generator_ok = fsm_input.wind_generator_ok; + usart_debug("In eolienne %s\r\n", last_wind_generator_ok ? "vent" : "replie"); + } + + if (tm_trigger_button == 1 && last_tm_trigger_button == 0) { + fsm_balise_force(); + } + last_tm_trigger_button = tm_trigger_button; + + const int cw_psk31_done = !cw_psk31_busy(); + const int cw_done = cw_psk31_done && only_zero_in_audio_buffer; + + // Set the done flag to 1 only once, when cw_done switches from 0 to 1 + if (last_cw_done != cw_done) { + usart_debug("In cw_done change %d %d\r\n", cw_done, only_zero_in_audio_buffer); + + if (cw_done) { + fsm_input.cw_psk31_done = cw_done; + leds_turn_off(LED_ORANGE); + } + + last_cw_done = cw_done; + } + else { + fsm_input.cw_psk31_done = 0; + } + + + const int current_tone_1750_status = tone_1750_status(); +#ifdef SIMULATOR + gui_in_tone_1750 = current_tone_1750_status; +#endif + fsm_input.det_1750 = current_tone_1750_status; + pio_set_det_1750(current_tone_1750_status); + + fsm_input.long_1750 = tone_1750_for_5_seconds(); + + // TODO implement a DTMF controlled state machine for setting SQ2 + pio_set_sq2(0); + + fsm_input.fax_mode = tone_fax_status(); + fsm_input.swr_high = swr_error_flag; + fsm_input.hour_is_even = hour_is_even; + + fsm_update_inputs(&fsm_input); + fsm_update(); + fsm_balise_update(); + const int disable_1750_filter = fsm_sstv_update(); + pio_set_fax(disable_1750_filter); + + struct fsm_output_signals_t fsm_out; + fsm_get_outputs(&fsm_out); + + pio_set_tx(fsm_out.tx_on); + pio_set_mod_off(!fsm_out.modulation); + + // Add message to CW generator only on rising edge of trigger + if (fsm_out.cw_psk31_trigger && !cw_last_trigger) { + cw_psk31_push_message(fsm_out.msg, fsm_out.cw_dit_duration, fsm_out.msg_frequency); + + leds_turn_on(LED_ORANGE); + } + cw_last_trigger = fsm_out.cw_psk31_trigger; + + } +} + +const int BLUE_LED_INTVL = 4; +static int blue_led_phase = 0; +static void nf_analyse(void __attribute__ ((unused))*pvParameters) +{ + while (1) { + if (blue_led_phase == 0) { + leds_turn_on(LED_BLUE); + } + else if (blue_led_phase == BLUE_LED_INTVL) { + leds_turn_off(LED_BLUE); + } + + blue_led_phase++; + + if (blue_led_phase >= BLUE_LED_INTVL * 2) { + blue_led_phase = 0; + } + + tone_do_analysis(); + } +} + +#if configGENERATE_RUN_TIME_STATS +#include "stm32f4xx_conf.h" +#include "stm32f4xx_tim.h" + +void vConfigureTimerForRunTimeStats() +{ + TIM_TimeBaseInitTypeDef SetupTimer; + /* Enable timer 2, using the Reset and Clock Control register */ + RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); + SetupTimer.TIM_Prescaler = 0x0000; + SetupTimer.TIM_CounterMode = TIM_CounterMode_Up; + SetupTimer.TIM_Period = 0xFFFFFFFF; + SetupTimer.TIM_ClockDivision = TIM_CKD_DIV1; + TIM_TimeBaseInit(TIM2, &SetupTimer); + TIM_Cmd(TIM2, ENABLE); /* start counting by enabling CEN in CR1 */ +} + +unsigned long vGetTimerForRunTimeStats( void ) { + return TIM_GetCounter(TIM2); +} + +static TaskStatus_t taskstats[12]; +static void print_task_stats(void) { + uint32_t total_time; + int n_tasks = uxTaskGetSystemState(taskstats, 12, &total_time); + total_time /= 100UL; + for (int t = 0; t < n_tasks; t++) { + char status_indicator; + switch(taskstats[t].eCurrentState ) + { + case eReady: status_indicator = 'R'; break; + case eBlocked: status_indicator = 'B'; break; + case eSuspended: status_indicator = 'S'; break; + case eDeleted: status_indicator = 'D'; break; + case eRunning: status_indicator = 'R'; break; + } + + uint32_t task_time_percent = 0; + if (total_time > 0) { + task_time_percent = taskstats[t].ulRunTimeCounter / total_time; + } + + usart_debug("TASK %d %s %c [%d] %d\r\n", + taskstats[t].xTaskNumber, + taskstats[t].pcTaskName, + status_indicator, + taskstats[t].usStackHighWaterMark, + task_time_percent); + } +} +#else +static void print_task_stats(void) {} +#endif diff --git a/src/common/GPIO/analog.c b/src/common/GPIO/analog.c new file mode 100644 index 0000000..b6f75a6 --- /dev/null +++ b/src/common/GPIO/analog.c @@ -0,0 +1,69 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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 "GPIO/analog.h" + +#define SUPPLY_HISTORY_LEN 10 + +static float supply_history[SUPPLY_HISTORY_LEN]; +static int supply_history_ix = 0; +static int supply_history_ready = 0; +static int last_qrp = 0; + +// Return 1 if analog supply is too low +int analog_supply_too_low(void) +{ + + // Hysteresis: + // Lower voltage 12.5V + // High voltage 13V + const float measure = analog_measure_12v(); + + supply_history[supply_history_ix] = measure; + supply_history_ix++; + if (supply_history_ix >= SUPPLY_HISTORY_LEN) { + supply_history_ix = 0; + supply_history_ready = 1; + } + + if (supply_history_ready) { + float sum = 0.0f; + for (int i = 0; i < SUPPLY_HISTORY_LEN; i++) { + sum += supply_history[i]; + } + int above_lower_limit = (sum > 12.5f * SUPPLY_HISTORY_LEN); + int above_upper_limit = (sum > 13.0f * SUPPLY_HISTORY_LEN); + + if (!above_lower_limit) { + last_qrp = 1; + } + else if (above_upper_limit) { + last_qrp = 0; + } + + return last_qrp; + } + + return 0; +} diff --git a/src/common/GPIO/analog.h b/src/common/GPIO/analog.h new file mode 100644 index 0000000..ddc19ac --- /dev/null +++ b/src/common/GPIO/analog.h @@ -0,0 +1,50 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + +#pragma once +#include + +void analog_init(void); + +/* Measure the 12V supply voltage, in 0.5V increments. + * Returns 0.0f in case of error + * + * Warning, do not run from interrupt context! + */ +float analog_measure_12v(void); + +/* Measure SWR, and return voltages in mV. + * Returns 0 in case of error, 1 in case of success + * + * Warning, do not run from interrupt context! + */ +int analog_measure_swr(int *forward_mv, int* reflected_mv); + +/* Keep an average of measurements, and decide if the repeater should enter + * QRP. Returns 1 if low power must be activated + * + * Warning, do not run from interrupt context! + */ +int analog_supply_too_low(void); + diff --git a/src/common/GPIO/i2c.h b/src/common/GPIO/i2c.h new file mode 100644 index 0000000..69a4ad2 --- /dev/null +++ b/src/common/GPIO/i2c.h @@ -0,0 +1,53 @@ +/* + * 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. +*/ + +#ifndef __I2C_H_ +#define __I2C_H_ + +#include + +/* Initialise I2C on the board for both the audio codec and the GPS receiver */ +void i2c_init(void); + +/* Do an I2C write, return 1 on success, 0 on failure */ +int i2c_write(uint8_t device, const uint8_t *txbuf, int len); + +/* Do an I2C read into rxbuf. + * Returns number of bytes received, or 0 in case of failure + */ +int i2c_read(uint8_t device, uint8_t *rxbuf, int len); + +/* Do an I2C write for the address, and then a read into rxbuf. + * Returns number of bytes received, or 0 in case of failure + */ +int i2c_read_from(uint8_t device, uint8_t address, uint8_t *rxbuf, int len); + +/* Start an I2C transaction, keeping exclusive access to I2C */ +void i2c_transaction_start(void); + +/* End an I2C transaction, unlocking the exclusive access to I2C */ +void i2c_transaction_end(void); + +#endif // __I2C_H_ + diff --git a/src/common/GPIO/leds.h b/src/common/GPIO/leds.h new file mode 100644 index 0000000..70c57d2 --- /dev/null +++ b/src/common/GPIO/leds.h @@ -0,0 +1,9 @@ +#pragma once + +#define LED_GREEN 1 +#define LED_ORANGE 2 +#define LED_RED 3 +#define LED_BLUE 4 + +void leds_turn_off(int); +void leds_turn_on(int); diff --git a/src/common/GPIO/pio.h b/src/common/GPIO/pio.h new file mode 100644 index 0000000..e118fc8 --- /dev/null +++ b/src/common/GPIO/pio.h @@ -0,0 +1,47 @@ +/* + * 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. +*/ + +#ifndef _PIO_H_ +#define _PIO_H_ + +#include +#include +#include "Core/fsm.h" + +void pio_init(void); + +void pio_set_tx(int on); +void pio_set_mod_off(int mod_off); +void pio_set_qrp(int on); +void pio_set_gps_epps(int on); +void pio_set_fax(int on); +void pio_set_det_1750(int on); +void pio_set_sq2(int on); + +void pio_set_fsm_signals(struct fsm_input_signals_t* sig); + +int pio_read_button(void); + +#endif // _PIO_H_ + diff --git a/src/common/GPIO/temperature.c b/src/common/GPIO/temperature.c new file mode 100644 index 0000000..f9e43cb --- /dev/null +++ b/src/common/GPIO/temperature.c @@ -0,0 +1,62 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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 "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "task.h" +#include "Core/common.h" +#include "GPIO/temperature.h" + + +float _temperature_last_value; +int _temperature_valid; + + +void temperature_task(void *pvParameters); + + +void temperature_init() { + + xTaskCreate( + temperature_task, + "TaskTemperature", + 4*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + NULL); +} + +// Return the current temperature +float temperature_get() { + if (_temperature_valid) { + return _temperature_last_value; + } else { + return 0.0f; + } +} + +// Return 1 if the temperature is valid +int temperature_valid() { + return _temperature_valid; +} diff --git a/src/common/GPIO/temperature.h b/src/common/GPIO/temperature.h new file mode 100644 index 0000000..f49cc26 --- /dev/null +++ b/src/common/GPIO/temperature.h @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + +#pragma once +#include + +extern float _temperature_last_value; +extern int _temperature_valid; + +// Setup communication and temperature +void temperature_init(void); + +// Return 1 if the temperature is valid +int temperature_valid(void); + +// Get current temperature +float temperature_get(void); + +void temperature_task(void *); diff --git a/src/common/GPIO/usart.c b/src/common/GPIO/usart.c new file mode 100644 index 0000000..8bcd80a --- /dev/null +++ b/src/common/GPIO/usart.c @@ -0,0 +1,171 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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 +#include +#include +#include +#include "Core/common.h" +#include "GPIO/usart.h" +#include "GPIO/analog.h" +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" + +// The ISR writes into this buffer +static char nmea_sentence[MAX_NMEA_SENTENCE_LEN]; +static int nmea_sentence_last_written = 0; + + +// Once a completed NMEA sentence is received in the ISR, +// it is appended to this queue +static QueueHandle_t usart_nmea_queue; + +void usart_gps_init() { + usart_nmea_queue = xQueueCreate(15, MAX_NMEA_SENTENCE_LEN); + if (usart_nmea_queue == 0) { + while(1); /* fatal error */ + } + + usart_gps_specific_init(); + +} + +void usart_gps_puts(const char* str) { + vTaskSuspendAll(); + usart_puts(USART3, str); + xTaskResumeAll(); +} + +#define MAX_MSG_LEN 80 +static char usart_debug_message[MAX_MSG_LEN]; + +void usart_debug_timestamp() { + // Don't call printf here, to reduce stack usage + uint64_t now = timestamp_now(); + if (now == 0) { + usart_puts(USART2, "[0] "); + } + else { + char ts_str[64]; + int i = 63; + + ts_str[i--] = '\0'; + ts_str[i--] = ' '; + ts_str[i--] = ']'; + + while (now > 0 && i >= 0) { + ts_str[i--] = '0' + (now % 10); + now /= 10; + } + ts_str[i] = '['; + + usart_puts(USART2, &ts_str[i]); + } +} + +void usart_debug(const char *format, ...) { + va_list list; + va_start(list, format); + vsnprintf(usart_debug_message, MAX_MSG_LEN-1, format, list); + + vTaskSuspendAll(); + usart_debug_timestamp(); + usart_puts(USART2, usart_debug_message); + xTaskResumeAll(); + + va_end(list); +} + +void usart_debug_puts(const char* str) { +#ifdef SIMULATOR + fprintf(stderr, "DEBUG: %s", str); +#endif + vTaskSuspendAll(); + usart_debug_timestamp(); + usart_puts(USART2, str); + xTaskResumeAll(); +} + +void usart_debug_puts_header(const char* hdr, const char* str) { + vTaskSuspendAll(); + usart_debug_timestamp(); + usart_puts(USART2, hdr); + usart_puts(USART2, str); + usart_puts(USART2, "\r\n"); + xTaskResumeAll(); +} + +int usart_get_nmea_sentence(char* nmea) { + return xQueueReceive(usart_nmea_queue, nmea, portMAX_DELAY); +} + + +static void usart_clear_nmea_buffer(void) { + for (int i = 0; i < MAX_NMEA_SENTENCE_LEN; i++) { + nmea_sentence[i] = '\0'; + } + nmea_sentence_last_written = 0; +} + +void usart_process_char(char c) { +// Warning: running in interrupt context + usart_debug("Unknown command %c\r\n", c); +} + +void usart_gps_process_char(char c) +{ + BaseType_t require_context_switch = pdFALSE; + + if (nmea_sentence_last_written == 0) { + if (c == '$') { + // Likely new start of sentence + nmea_sentence[nmea_sentence_last_written] = c; + nmea_sentence_last_written++; + } + } + else if (nmea_sentence_last_written < MAX_NMEA_SENTENCE_LEN) { + nmea_sentence[nmea_sentence_last_written] = c; + nmea_sentence_last_written++; + + if (c == '\n') { + int success = xQueueSendToBackFromISR( + usart_nmea_queue, + nmea_sentence, + &require_context_switch); + + if (success == pdFALSE) { + trigger_fault(FAULT_SOURCE_USART); + } + + usart_clear_nmea_buffer(); + } + } + else { + // Buffer overrun without a meaningful NMEA message. + usart_clear_nmea_buffer(); + } + + portYIELD_FROM_ISR(require_context_switch); +} diff --git a/src/common/GPIO/usart.h b/src/common/GPIO/usart.h new file mode 100644 index 0000000..681d86f --- /dev/null +++ b/src/common/GPIO/usart.h @@ -0,0 +1,78 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony + * + * 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. +*/ + +/* This handles the USART 3 to the GPS receiver, and fills a queue of + * NMEA messages. + * + * It also handles the debug USART 2 and allows sending messages to the PC. + */ + +#ifndef __USART_H_ +#define __USART_H_ + +#ifdef STM32F4XX +# include +#else +# define USART_TypeDef int +# define USART2 ((USART_TypeDef*)2) +# define USART3 ((USART_TypeDef*)3) +#endif + +#define MAX_NMEA_SENTENCE_LEN 256 + +// Initialise USART2 for PC debugging +void usart_init(void); + +// Initialise USART3 for GPS and NMEA queue +// Needs running scheduler +void usart_gps_init(void); + +// Take GPS out of RESET +void usart_gps_remove_reset(void); + +// Send the str to the GPS receiver +void usart_gps_puts(const char* str); + +// a printf to send data to the PC +void usart_debug(const char *format, ...); + +// Send a string to the PC +void usart_debug_puts(const char* str); +void usart_debug_puts_header(const char* hdr, const char* str); + +// Get a MAX_NMEA_SENTENCE_LEN sized NMEA sentence +// Return 1 on success +int usart_get_nmea_sentence(char* nmea); + +void usart_debug_timestamp(void); + +void usart_gps_specific_init(void); + +void usart_process_char(char); +void usart_gps_process_char(char); + +void usart_puts(USART_TypeDef*, const char*); + +#endif //__USART_H_ + diff --git a/src/common/GPS/gps.c b/src/common/GPS/gps.c new file mode 100644 index 0000000..bfdc1c1 --- /dev/null +++ b/src/common/GPS/gps.c @@ -0,0 +1,166 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 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 "FreeRTOS.h" +#include "FreeRTOSConfig.h" +#include "task.h" +#include "semphr.h" +#include "Core/common.h" +#include "GPS/gps.h" +#include "GPS/minmea.h" +#include "GPIO/usart.h" + + +TickType_t gps_timeutc_last_updated = 0; +static struct tm gps_timeutc; +static int gps_num_sv_used; +static int gps_timeutc_valid; // Validity flag for both gps_timeutc and gps_num_sv_used + +const TickType_t gps_data_validity_timeout = GPS_MS_TIMEOUT / portTICK_PERIOD_MS; + +static void gps_task(void *pvParameters); + +SemaphoreHandle_t timeutc_semaphore; + +// Get current time from GPS +int gps_utctime(struct tm *timeutc, int *num_sv_used) +{ + int valid = 0; + + xSemaphoreTake(timeutc_semaphore, portMAX_DELAY); + if (xTaskGetTickCount() - gps_timeutc_last_updated < gps_data_validity_timeout) { + timeutc->tm_year = gps_timeutc.tm_year; + timeutc->tm_mon = gps_timeutc.tm_mon; + timeutc->tm_mday = gps_timeutc.tm_mday; + timeutc->tm_hour = gps_timeutc.tm_hour; + timeutc->tm_min = gps_timeutc.tm_min; + timeutc->tm_sec = gps_timeutc.tm_sec; + *num_sv_used = gps_num_sv_used; + valid = gps_timeutc_valid; + } + + xSemaphoreGive(timeutc_semaphore); + + return valid; +} + +#define RXBUF_LEN MAX_NMEA_SENTENCE_LEN +static char rxbuf[RXBUF_LEN]; + +static void gps_task(void __attribute__ ((unused))*pvParameters) { + + // The initialisation placed the GPS into reset + usart_gps_remove_reset(); + + while (1) { + taskYIELD(); + + int success = usart_get_nmea_sentence(rxbuf); + + if (success) { + const int strict = 1; + switch (minmea_sentence_id(rxbuf, strict)) { + case MINMEA_SENTENCE_RMC: + { + struct minmea_sentence_rmc frame; + if (minmea_parse_rmc(&frame, rxbuf)) { + xSemaphoreTake(timeutc_semaphore, portMAX_DELAY); + // tm_year is saved as Year - 1900 in struct tm + gps_timeutc.tm_year = 2000 + frame.date.year - 1900; + // struct tm months are zero-indexed + gps_timeutc.tm_mon = frame.date.month - 1; + gps_timeutc.tm_mday = frame.date.day; + gps_timeutc.tm_hour = frame.time.hours; + gps_timeutc.tm_min = frame.time.minutes; + gps_timeutc.tm_sec = frame.time.seconds; + gps_timeutc_valid = frame.valid; + gps_timeutc_last_updated = xTaskGetTickCount(); + xSemaphoreGive(timeutc_semaphore); + } + } break; + case MINMEA_SENTENCE_TXT: + { + struct minmea_sentence_txt frame; + if (minmea_parse_txt(&frame, rxbuf)) { + rxbuf[MINMEA_TXT_START_IX + frame.text_len] = '\0'; + + switch (frame.msgtype) { + case MINMEA_GPTXT_ERROR: + usart_debug_puts_header("GPS ERROR ", frame.text); + break; + case MINMEA_GPTXT_WARNING: + usart_debug_puts_header("GPS WARNING ", frame.text); + break; + default: + usart_debug_puts_header("GPS Message ", frame.text); + break; + } + } + } break; + case MINMEA_SENTENCE_GGA: + { + struct minmea_sentence_gga frame; + if (minmea_parse_gga(&frame, rxbuf)) { + xSemaphoreTake(timeutc_semaphore, portMAX_DELAY); + gps_num_sv_used = frame.satellites_tracked; + xSemaphoreGive(timeutc_semaphore); + } + } break; + default: + break; + } + } + } +} + +void gps_init() { + gps_timeutc_valid = 0; + + usart_gps_init(); + + timeutc_semaphore = xSemaphoreCreateBinary(); + + if (timeutc_semaphore == NULL) { + trigger_fault(FAULT_SOURCE_GPS); + } else { + xSemaphoreGive(timeutc_semaphore); + } + + xTaskCreate( + gps_task, + "TaskGPS", + 4*configMINIMAL_STACK_SIZE, + (void*) NULL, + tskIDLE_PRIORITY + 2UL, + NULL); +} + +// Return 1 of the GPS is receiving time +int gps_locked() { + if (xTaskGetTickCount() - gps_timeutc_last_updated < gps_data_validity_timeout) { + return gps_timeutc_valid; + } else { + return 0; + } +} diff --git a/src/common/GPS/gps.h b/src/common/GPS/gps.h new file mode 100644 index 0000000..8a2118f --- /dev/null +++ b/src/common/GPS/gps.h @@ -0,0 +1,46 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 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. +*/ + +#pragma once + +#include +#include + +/* Setup GPS receiver over USART and parse time */ + +/* USART connections: + * board TX to GPS RX on PD8 + * board RX to GPS TX on PD9 + */ + +// Setup communication and GPS receiver +void gps_init(void); + +// Return 1 of the GPS is receiving time +int gps_locked(void); + +// Get current time from GPS and number of satellite vehicles +// used for fix. +// Returns 1 if data is valid, 0 otherwise +int gps_utctime(struct tm *timeutc, int *num_sv_used); diff --git a/src/common/GPS/minmea.c b/src/common/GPS/minmea.c new file mode 100644 index 0000000..4d4aeb8 --- /dev/null +++ b/src/common/GPS/minmea.c @@ -0,0 +1,597 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +// We are lacking timegm, but we only handle UTC and use mktime instead. See +// https://github.com/cloudyourcar/minmea-t/ README.md +#define timegm mktime + +#include "GPS/minmea.h" + +#include +#include +#include +#include + +#define boolstr(s) ((s) ? "true" : "false") + +static int hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; +} + +uint8_t minmea_checksum(const char *sentence) +{ + // Support senteces with or without the starting dollar sign. + if (*sentence == '$') + sentence++; + + uint8_t checksum = 0x00; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while (*sentence && *sentence != '*') + checksum ^= *sentence++; + + return checksum; +} + +bool minmea_check(const char *sentence, bool strict) +{ + uint8_t checksum = 0x00; + + // Sequence length is limited. + if (strlen(sentence) > MINMEA_MAX_LENGTH + 3) + return false; + + // A valid sentence starts with "$". + if (*sentence++ != '$') + return false; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence)) + checksum ^= *sentence++; + + // If checksum is present... + if (*sentence == '*') { + // Extract checksum. + sentence++; + int upper = hex2int(*sentence++); + if (upper == -1) + return false; + int lower = hex2int(*sentence++); + if (lower == -1) + return false; + int expected = upper << 4 | lower; + + // Check for checksum mismatch. + if (checksum != expected) + return false; + } else if (strict) { + // Discard non-checksummed frames in strict mode. + return false; + } + + // The only stuff allowed at this point is a newline. + if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n")) + return false; + + return true; +} + +static inline bool minmea_isfield(char c) { + return isprint((unsigned char) c) && c != ',' && c != '*'; +} + +bool minmea_scan(const char *sentence, const char *format, ...) +{ + bool result = false; + bool optional = false; + va_list ap; + va_start(ap, format); + + const char *field = sentence; +#define next_field() \ + do { \ + /* Progress to the next field. */ \ + while (minmea_isfield(*sentence)) \ + sentence++; \ + /* Make sure there is a field there. */ \ + if (*sentence == ',') { \ + sentence++; \ + field = sentence; \ + } else { \ + field = NULL; \ + } \ + } while (0) + + while (*format) { + char type = *format++; + + if (type == ';') { + // All further fields are optional. + optional = true; + continue; + } + + if (!field && !optional) { + // Field requested but we ran out if input. Bail out. + goto parse_error; + } + + switch (type) { + case 'c': { // Single character field (char). + char value = '\0'; + + if (field && minmea_isfield(*field)) + value = *field; + + *va_arg(ap, char *) = value; + } break; + + case 'd': { // Single character direction field (int). + int value = 0; + + if (field && minmea_isfield(*field)) { + switch (*field) { + case 'N': + case 'E': + value = 1; + break; + case 'S': + case 'W': + value = -1; + break; + default: + goto parse_error; + } + } + + *va_arg(ap, int *) = value; + } break; + + case 'f': { // Fractional value with scale (struct minmea_float). + int sign = 0; + int_least32_t value = -1; + int_least32_t scale = 0; + + if (field) { + while (minmea_isfield(*field)) { + if (*field == '+' && !sign && value == -1) { + sign = 1; + } else if (*field == '-' && !sign && value == -1) { + sign = -1; + } else if (isdigit((unsigned char) *field)) { + int digit = *field - '0'; + if (value == -1) + value = 0; + if (value > (INT_LEAST32_MAX-digit) / 10) { + /* we ran out of bits, what do we do? */ + if (scale) { + /* truncate extra precision */ + break; + } else { + /* integer overflow. bail out. */ + goto parse_error; + } + } + value = (10 * value) + digit; + if (scale) + scale *= 10; + } else if (*field == '.' && scale == 0) { + scale = 1; + } else if (*field == ' ') { + /* Allow spaces at the start of the field. Not NMEA + * conformant, but some modules do this. */ + if (sign != 0 || value != -1 || scale != 0) + goto parse_error; + } else { + goto parse_error; + } + field++; + } + } + + if ((sign || scale) && value == -1) + goto parse_error; + + if (value == -1) { + /* No digits were scanned. */ + value = 0; + scale = 0; + } else if (scale == 0) { + /* No decimal point. */ + scale = 1; + } + if (sign) + value *= sign; + + *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale}; + } break; + + case 'i': { // Integer value, default 0 (int). + int value = 0; + + if (field) { + char *endptr; + value = strtol(field, &endptr, 10); + if (minmea_isfield(*endptr)) + goto parse_error; + } + + *va_arg(ap, int *) = value; + } break; + + case 's': { // String value (char *). + char *buf = va_arg(ap, char *); + + if (field) { + while (minmea_isfield(*field)) + *buf++ = *field++; + } + + *buf = '\0'; + } break; + + case 't': { // NMEA talker+sentence identifier (char *). + // This field is always mandatory. + if (!field) + goto parse_error; + + if (field[0] != '$') + goto parse_error; + for (int f=0; f<5; f++) + if (!minmea_isfield(field[1+f])) + goto parse_error; + + char *buf = va_arg(ap, char *); + memcpy(buf, field+1, 5); + buf[5] = '\0'; + } break; + + case 'D': { // Date (int, int, int), -1 if empty. + struct minmea_date *date = va_arg(ap, struct minmea_date *); + + int d = -1, m = -1, y = -1; + + if (field && minmea_isfield(*field)) { + // Always six digits. + for (int f=0; f<6; f++) + if (!isdigit((unsigned char) field[f])) + goto parse_error; + + d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10); + m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10); + y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10); + } + + date->day = d; + date->month = m; + date->year = y; + } break; + + case 'T': { // Time (int, int, int, int), -1 if empty. + struct minmea_time *time_ = va_arg(ap, struct minmea_time *); + + int h = -1, i = -1, s = -1, u = -1; + + if (field && minmea_isfield(*field)) { + // Minimum required: integer time. + for (int f=0; f<6; f++) + if (!isdigit((unsigned char) field[f])) + goto parse_error; + + h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10); + i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10); + s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10); + field += 6; + + // Extra: fractional time. Saved as microseconds. + if (*field++ == '.') { + int value = 0; + int scale = 1000000; + while (isdigit((unsigned char) *field) && scale > 1) { + value = (value * 10) + (*field++ - '0'); + scale /= 10; + } + u = value * scale; + } else { + u = 0; + } + } + + time_->hours = h; + time_->minutes = i; + time_->seconds = s; + time_->microseconds = u; + } break; + + case '_': { // Ignore the field. + } break; + + default: { // Unknown. + goto parse_error; + } break; + } + + next_field(); + } + + result = true; + +parse_error: + va_end(ap); + return result; +} + +bool minmea_talker_id(char talker[3], const char *sentence) +{ + char type[6]; + if (!minmea_scan(sentence, "t", type)) + return false; + + talker[0] = type[0]; + talker[1] = type[1]; + talker[2] = '\0'; + + return true; +} + +enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict) +{ + if (!minmea_check(sentence, strict)) + return MINMEA_INVALID; + + char type[6]; + if (!minmea_scan(sentence, "t", type)) + return MINMEA_INVALID; + + if (!strcmp(type+2, "RMC")) + return MINMEA_SENTENCE_RMC; + if (!strcmp(type+2, "GGA")) + return MINMEA_SENTENCE_GGA; + if (!strcmp(type+2, "GSA")) + return MINMEA_SENTENCE_GSA; + if (!strcmp(type+2, "GLL")) + return MINMEA_SENTENCE_GLL; + if (!strcmp(type+2, "GST")) + return MINMEA_SENTENCE_GST; + if (!strcmp(type+2, "GSV")) + return MINMEA_SENTENCE_GSV; + if (!strcmp(type+2, "TXT")) + return MINMEA_SENTENCE_TXT; + + return MINMEA_UNKNOWN; +} + +bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence) +{ + // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 + char type[6]; + char validity; + int latitude_direction; + int longitude_direction; + int variation_direction; + if (!minmea_scan(sentence, "tTcfdfdffDfd", + type, + &frame->time, + &validity, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->speed, + &frame->course, + &frame->date, + &frame->variation, &variation_direction)) + return false; + if (strcmp(type+2, "RMC")) + return false; + + frame->valid = (validity == 'A'); + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + frame->variation.value *= variation_direction; + + return true; +} + +bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence) +{ + // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + char type[6]; + int latitude_direction; + int longitude_direction; + + if (!minmea_scan(sentence, "tTfdfdiiffcfci_", + type, + &frame->time, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->fix_quality, + &frame->satellites_tracked, + &frame->hdop, + &frame->altitude, &frame->altitude_units, + &frame->height, &frame->height_units, + &frame->dgps_age)) + return false; + if (strcmp(type+2, "GGA")) + return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence) +{ + // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + char type[6]; + + if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff", + type, + &frame->mode, + &frame->fix_type, + &frame->sats[0], + &frame->sats[1], + &frame->sats[2], + &frame->sats[3], + &frame->sats[4], + &frame->sats[5], + &frame->sats[6], + &frame->sats[7], + &frame->sats[8], + &frame->sats[9], + &frame->sats[10], + &frame->sats[11], + &frame->pdop, + &frame->hdop, + &frame->vdop)) + return false; + if (strcmp(type+2, "GSA")) + return false; + + return true; +} + +bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence) +{ + // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$; + char type[6]; + int latitude_direction; + int longitude_direction; + + if (!minmea_scan(sentence, "tfdfdTc;c", + type, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->time, + &frame->status, + &frame->mode)) + return false; + if (strcmp(type+2, "GLL")) + return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence) +{ + // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58 + char type[6]; + + if (!minmea_scan(sentence, "tTfffffff", + type, + &frame->time, + &frame->rms_deviation, + &frame->semi_major_deviation, + &frame->semi_minor_deviation, + &frame->semi_major_orientation, + &frame->latitude_error_deviation, + &frame->longitude_error_deviation, + &frame->altitude_error_deviation)) + return false; + if (strcmp(type+2, "GST")) + return false; + + return true; +} + +bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence) +{ + // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 + // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D + // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75 + // $GPGSV,4,4,13,39,31,170,27*40 + // $GPGSV,4,4,13*7B + char type[6]; + + if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii", + type, + &frame->total_msgs, + &frame->msg_nr, + &frame->total_sats, + &frame->sats[0].nr, + &frame->sats[0].elevation, + &frame->sats[0].azimuth, + &frame->sats[0].snr, + &frame->sats[1].nr, + &frame->sats[1].elevation, + &frame->sats[1].azimuth, + &frame->sats[1].snr, + &frame->sats[2].nr, + &frame->sats[2].elevation, + &frame->sats[2].azimuth, + &frame->sats[2].snr, + &frame->sats[3].nr, + &frame->sats[3].elevation, + &frame->sats[3].azimuth, + &frame->sats[3].snr + )) { + return false; + } + if (strcmp(type+2, "GSV")) + return false; + + return true; +} + +bool minmea_parse_txt(struct minmea_sentence_txt *frame, const char *sentence) +{ + // Example + // $GPTXT,01,01,02,u-blox ag - www.u-blox.com*50 + char type[6]; + + if (!minmea_scan(sentence, "tiii", + type, + &frame->num_msg, + &frame->msg_num, + &frame->msgtype)) { + return false; + } + if (strcmp(type+2, "TXT")) { + return false; + } + + frame->text = sentence + MINMEA_TXT_START_IX; + frame->text_len = strlen(sentence) - MINMEA_TXT_START_IX - 5; + + return true; +} + +int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_) +{ + if (date->year == -1 || time_->hours == -1) + return -1; + + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_year = 2000 + date->year - 1900; + tm.tm_mon = date->month - 1; + tm.tm_mday = date->day; + tm.tm_hour = time_->hours; + tm.tm_min = time_->minutes; + tm.tm_sec = time_->seconds; + + time_t timestamp = timegm(&tm); /* See README.md if your system lacks timegm(). */ + if (timestamp != -1) { + ts->tv_sec = timestamp; + ts->tv_nsec = time_->microseconds * 1000; + return 0; + } else { + return -1; + } +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/common/GPS/minmea.h b/src/common/GPS/minmea.h new file mode 100644 index 0000000..ab770a6 --- /dev/null +++ b/src/common/GPS/minmea.h @@ -0,0 +1,252 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#ifndef MINMEA_H +#define MINMEA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#define MINMEA_MAX_LENGTH 80 + +enum minmea_sentence_id { + MINMEA_INVALID = -1, + MINMEA_UNKNOWN = 0, + MINMEA_SENTENCE_RMC, + MINMEA_SENTENCE_GGA, + MINMEA_SENTENCE_GSA, + MINMEA_SENTENCE_GLL, + MINMEA_SENTENCE_GST, + MINMEA_SENTENCE_GSV, + MINMEA_SENTENCE_TXT, +}; + +struct minmea_float { + int_least32_t value; + int_least32_t scale; +}; + +struct minmea_date { + int day; + int month; + int year; +}; + +struct minmea_time { + int hours; + int minutes; + int seconds; + int microseconds; +}; + +struct minmea_sentence_rmc { + struct minmea_time time; + bool valid; + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_float speed; + struct minmea_float course; + struct minmea_date date; + struct minmea_float variation; +}; + +struct minmea_sentence_gga { + struct minmea_time time; + struct minmea_float latitude; + struct minmea_float longitude; + int fix_quality; + int satellites_tracked; + struct minmea_float hdop; + struct minmea_float altitude; char altitude_units; + struct minmea_float height; char height_units; + int dgps_age; +}; + +enum minmea_gll_status { + MINMEA_GLL_STATUS_DATA_VALID = 'A', + MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V', +}; + +enum minmea_gll_mode { + MINMEA_GLL_MODE_AUTONOMOUS = 'A', + MINMEA_GLL_MODE_DPGS = 'D', + MINMEA_GLL_MODE_DR = 'E', +}; + +struct minmea_sentence_gll { + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_time time; + char status; + char mode; +}; + +struct minmea_sentence_gst { + struct minmea_time time; + struct minmea_float rms_deviation; + struct minmea_float semi_major_deviation; + struct minmea_float semi_minor_deviation; + struct minmea_float semi_major_orientation; + struct minmea_float latitude_error_deviation; + struct minmea_float longitude_error_deviation; + struct minmea_float altitude_error_deviation; +}; + +enum minmea_gsa_mode { + MINMEA_GPGSA_MODE_AUTO = 'A', + MINMEA_GPGSA_MODE_FORCED = 'M', +}; + +enum minmea_gsa_fix_type { + MINMEA_GPGSA_FIX_NONE = 1, + MINMEA_GPGSA_FIX_2D = 2, + MINMEA_GPGSA_FIX_3D = 3, +}; + +struct minmea_sentence_gsa { + char mode; + int fix_type; + int sats[12]; + struct minmea_float pdop; + struct minmea_float hdop; + struct minmea_float vdop; +}; + +struct minmea_sat_info { + int nr; + int elevation; + int azimuth; + int snr; +}; + +struct minmea_sentence_gsv { + int total_msgs; + int msg_nr; + int total_sats; + struct minmea_sat_info sats[4]; +}; + +enum minmea_txt_msgtype { + MINMEA_GPTXT_ERROR = 0, + MINMEA_GPTXT_WARNING = 1, + MINMEA_GPTXT_NOTICE = 2 +}; + +#define MINMEA_TXT_START_IX 16 + +struct minmea_sentence_txt { + int num_msg; + int msg_num; + enum minmea_txt_msgtype msgtype; + const char* text; + int text_len; +}; + +/** + * Calculate raw sentence checksum. Does not check sentence integrity. + */ +uint8_t minmea_checksum(const char *sentence); + +/** + * Check sentence validity and checksum. Returns true for valid sentences. + */ +bool minmea_check(const char *sentence, bool strict); + +/** + * Determine talker identifier. + */ +bool minmea_talker_id(char talker[3], const char *sentence); + +/** + * Determine sentence identifier. + */ +enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict); + +/** + * Scanf-like processor for NMEA sentences. Supports the following formats: + * c - single character (char *) + * d - direction, returned as 1/-1, default 0 (int *) + * f - fractional, returned as value + scale (int *, int *) + * i - decimal, default zero (int *) + * s - string (char *) + * t - talker identifier and type (char *) + * T - date/time stamp (int *, int *, int *) + * Returns true on success. See library source code for details. + */ +bool minmea_scan(const char *sentence, const char *format, ...); + +/* + * Parse a specific type of sentence. Return true on success. + */ +bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence); +bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence); +bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence); +bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence); +bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence); +bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence); +bool minmea_parse_txt(struct minmea_sentence_txt *frame, const char *sentence); + +/** + * Convert GPS UTC date/time representation to a UNIX timestamp. + */ +int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_); + +/** + * Rescale a fixed-point value to a different scale. Rounds towards zero. + */ +static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale) +{ + if (f->scale == 0) + return 0; + if (f->scale == new_scale) + return f->value; + if (f->scale > new_scale) + return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale); + else + return f->value * (new_scale/f->scale); +} + +/** + * Convert a fixed-point value to a floating-point value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tofloat(struct minmea_float *f) +{ + if (f->scale == 0) + return NAN; + return (float) f->value / (float) f->scale; +} + +/** + * Convert a raw coordinate to a floating point DD.DDD... value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tocoord(struct minmea_float *f) +{ + if (f->scale == 0) + return NAN; + int_least32_t degrees = f->value / (f->scale * 100); + int_least32_t minutes = f->value % (f->scale * 100); + return (float) degrees + (float) minutes / (60 * f->scale); +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINMEA_H */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/src/common/includes/Audio/audio.h b/src/common/includes/Audio/audio.h deleted file mode 100644 index d48cbe5..0000000 --- a/src/common/includes/Audio/audio.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef __AUDIO_H__ -#define __AUDIO_H__ - -#include -#include - -typedef void AudioCallbackFunction(void *context,int buffer); - -// Variables used by both glutt-o-logique and simulator -extern AudioCallbackFunction *callback_function; -extern void *callback_context; -extern int16_t *next_buffer_samples; -extern int next_buffer_length; -extern int buffer_number; -extern bool dma_running; - -void audio_initialize_platform(int plln, int pllr, int i2sdiv, int i2sodd, int rate); -void audio_start_dma_and_request_buffers(void); -void audio_stop_dma(void); - -#define Audio8000HzSettings 256,5,12,1,8000 -#define Audio16000HzSettings 213,2,13,0,16000 -#define Audio32000HzSettings 213,2,6,1,32000 -#define Audio48000HzSettings 258,3,3,1,48000 -#define Audio96000HzSettings 344,2,3,1,96000 -#define Audio22050HzSettings 429,4,9,1,22050 -#define Audio44100HzSettings 271,2,6,0,44100 -#define AudioVGAHSyncSettings 419,2,13,0,31475 // 31475.3606. Actual VGA timer is 31472.4616. - -#define AUDIO_BUF_LEN 4096 - - -// Initialize and power up audio hardware. Use the above defines for the parameters. -// Can probably only be called once. -void audio_initialize(int plln, int pllr, int i2sdiv, int i2sodd, int rate); - -// Power up and down the audio hardware. -void audio_put_codec_in_reset(void); -void audio_reinit_codec(void); - -// Set audio volume in steps of 0.5 dB. 0xff is +12 dB. -void audio_set_volume(int volume); - -// Start and stop audio playback using DMA. -// Callback is optional, and called whenever a new buffer is needed. -void audio_play_with_callback(AudioCallbackFunction *callback,void *context); - -// Provide a new buffer to the audio DMA. Output is double buffered, so -// at least two buffers must be maintained by the program. It is not allowed -// to overwrite the previously provided buffer until after the next callback -// invocation. -// Buffers must reside in DMA1-accessible memory, that is, the 128k RAM bank, -// or flash. -bool audio_provide_buffer_without_blocking(void *samples,int numsamples); - -void DMA1_Stream7_IRQHandler(void); - -#endif diff --git a/src/common/includes/Audio/audio_in.h b/src/common/includes/Audio/audio_in.h deleted file mode 100644 index 97a0f29..0000000 --- a/src/common/includes/Audio/audio_in.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - -#pragma once - -#include -#include "FreeRTOS.h" - -#define AUDIO_IN_RATE 16000 -#define AUDIO_IN_BUF_LEN 1600 - -void audio_in_initialize(void); - -/* Enable or disable the audio input */ -void audio_in_enable(int enable); - -/* The audio input layer must call tone_detect_push_sample() to - * send the samples - */ diff --git a/src/common/includes/Audio/cw.h b/src/common/includes/Audio/cw.h deleted file mode 100644 index 39be9c5..0000000 --- a/src/common/includes/Audio/cw.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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. -*/ - -#ifndef __CW_H_ -#define __CW_H_ - -#include -#include - -// Setup the CW generator to create audio samples at the given -// samplerate. -void cw_psk31_init(unsigned int samplerate); - -// Append new CW or PSK31 text to transmit -// CW/PSK31 audio centre frequency in Hz -// if dit_duration == 0, message is sent in PSK31 -// otherwise it is sent in CW, with dit_duration in ms -// returns 0 on failure, 1 on success -int cw_psk31_push_message(const char* text, int frequency, int dit_duration); - -// Write the waveform into the buffer (stereo), both for cw and psk31 -size_t cw_psk31_fill_buffer(int16_t *buf, size_t bufsize); - -// Return 1 if the CW or PSK31 generator is running -int cw_psk31_busy(void); - -void cw_message_sent(const char*); - -size_t cw_symbol(uint8_t, uint8_t *, size_t); - -#endif // __CW_H_ - diff --git a/src/common/includes/Audio/tone.h b/src/common/includes/Audio/tone.h deleted file mode 100644 index ba05304..0000000 --- a/src/common/includes/Audio/tone.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - -#ifndef __TONE_H_ -#define __TONE_H_ - -#include -#include "Audio/audio_in.h" - -#define TONE_BUFFER_LEN AUDIO_IN_BUF_LEN - -void tone_init(void); - -void tone_detector_enable(int enable); - -/* Return 1 when 1750 detected, 0 otherwise */ -int tone_1750_status(void); - -/* Return 1 if 1750 is currently detected, and has been already for at - * least 5s */ -int tone_1750_for_5_seconds(void); - -/* The FAX status is 1 if the recently decoded DTMF is the 0-7-* sequence. */ -int tone_fax_status(void); - -/* Must be called by task to do the analysis */ -void tone_do_analysis(void); - -/* Push a sample from the ADC ISR or Pulseaudio task */ -void tone_detect_push_sample(const uint16_t sample, int is_irq); - -#endif diff --git a/src/common/includes/Core/common.h b/src/common/includes/Core/common.h deleted file mode 100644 index 4918a5b..0000000 --- a/src/common/includes/Core/common.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 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. -*/ - -/* A set of common routines for internal timekeeping and a LFSR to generate - * a random number - */ - -#pragma once - -#include -#include - -/* Feature defines */ -#define ENABLE_PSK31 0 - -#define FLOAT_PI 3.1415926535897932384f - -void common_init(void); - -// Return the current timestamp in milliseconds. Timestamps are monotonic, and not -// wall clock time. -uint64_t timestamp_now(void); - -// Calculate local time from GPS time, including daylight saving time -// Return 1 on success, 0 on failure -// A call to this function will invalidate the information inside 'time' -// regardless of return value. -int local_time(struct tm *time); - -// Try to calculate local time, based on a past valid local time and the current timestamp -// Return 1 on success, 0 on failure -int local_derived_time(struct tm *time); - -// Return either 0 or 1, somewhat randomly -int random_bool(void); - -// Fault handling mechanism -#define FAULT_SOURCE_MAIN 1 -#define FAULT_SOURCE_GPS 2 -#define FAULT_SOURCE_I2C 3 -#define FAULT_SOURCE_USART 4 -#define FAULT_SOURCE_TASK_OVERFLOW 5 -#define FAULT_SOURCE_CW_AUDIO_QUEUE 6 -#define FAULT_SOURCE_ADC1 7 -#define FAULT_SOURCE_TIM6_ISR 8 -#define FAULT_SOURCE_ADC2_QUEUE 9 -#define FAULT_SOURCE_ADC2_IRQ 10 -void trigger_fault(int source); - -int find_last_sunday(const struct tm*); - -#ifdef SIMULATOR -void __disable_irq(void); -#else -void hard_fault_handler_c(uint32_t *); -#endif - -// Round a value to the nearest 0.5 -float round_float_to_half_steps(float value); - -#define GPS_MS_TIMEOUT 2000ul diff --git a/src/common/includes/Core/delay.h b/src/common/includes/Core/delay.h deleted file mode 100644 index 4cc3f07..0000000 --- a/src/common/includes/Core/delay.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - -#pragma once -#include - -// High-precisions delay (with approximations) - -// These functions only work if interupts are disabled -#ifdef SIMULATOR -void delay_us(int micros); -void delay_ms(int millis); -#else -void delay_us(uint32_t micros); -void delay_ms(uint32_t millis); -#endif - -void delay_init(void); diff --git a/src/common/includes/Core/fsm.h b/src/common/includes/Core/fsm.h deleted file mode 100644 index 37ca386..0000000 --- a/src/common/includes/Core/fsm.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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. - */ - -#pragma once -#include - -// List of all states the FSM of the relay can be in -enum fsm_state_e { - FSM_OISIF = 0, // Idle - FSM_OPEN1, // 1750 Hz received and squelch open - FSM_OPEN2, // Squelch closed - FSM_LETTRE, // Transmit single status letter - FSM_ECOUTE, // Repeater open, waiting for QSO - FSM_ATTENTE, // No QSO after a short while - FSM_QSO, // QSO ongoing - FSM_ANTI_BAVARD, // QSO too long, cut transmission - FSM_BLOQUE, // Backoff after ANTI_BAVARD - FSM_TEXTE_73, // Transmit 73 after QSO - FSM_TEXTE_HB9G, // Transmit HB9G after QSO - FSM_TEXTE_LONG, // Transmit either HB9G JN36BK or HB9G 1628M after QSO - FSM_BALISE_LONGUE, // Full-length 2-hour beacon - FSM_BALISE_SPECIALE, // 2-hour beacon when in QRP or with high power return mode - FSM_BALISE_COURTE, // Short intermittent beacon - FSM_BALISE_COURTE_OPEN, // Short intermittent beacon, need to switch to OPEN - _NUM_FSM_STATES // Dummy state to count the number of states -}; - -typedef enum fsm_state_e fsm_state_t; - -// List of all states the balise FSM of the relay can be in -enum balise_fsm_state_e { - BALISE_FSM_EVEN_HOUR = 0, // Even hours. - BALISE_FSM_ODD_HOUR, // Odd hours - BALISE_FSM_PENDING, // Waiting for transmission of balise -}; - -typedef enum balise_fsm_state_e balise_fsm_state_t; - - -// List of all states the SSTV FSM of the relay can be in -enum sstv_fsm_state_e { - SSTV_FSM_OFF = 0, - SSTV_FSM_ON, -}; - -typedef enum sstv_fsm_state_e sstv_fsm_state_t; - - -// All signals that the FSM can read, most of them are actually booleans -struct fsm_input_signals_t { - /* Signals coming from repeater electronics */ - int sq; // Squelch detection - int discrim_u; // FM discriminator says RX is too high in frequency - int qrp; // The relay is currently running with low power - int hour_is_even; // 1 if hour is even - float temp; // temperature in degrees C - float humidity; // relative humidity, range [0-100] % - int wind_generator_ok; // false if the generator is folded out of the wind - int discrim_d; // FM discriminator says RX is too low in frequency - int button_1750; // Front panel button 1750Hz - - /* Signals coming from FAX and 1750 detector */ - int fax_mode; // 1750Hz filter disabled for machine-generated modes - int det_1750; // 1750Hz detected - int long_1750; // 1750Hz detected for more than 5s - - /* Signals coming from CW and PSK generator */ - int cw_psk31_done; // The CW and PSK generator has finished transmitting the message - - /* Signal coming from the standing wave ratio meter */ - int swr_high; // We see a lot of return power - -}; - -// All signals the FSM has to control -struct fsm_output_signals_t { - /* Signals to the repeater electronics */ - int tx_on; // Enable TX circuitry - int modulation; // Enable repeater RX to TX modulation - - /* Signals to the CW and PSK generator */ - const char* msg; // The message to transmit - int msg_frequency; // What audio frequency for the CW or PSK message - int cw_dit_duration; // CW speed, dit duration in ms - int cw_psk31_trigger; // Set to true to trigger a CW or PSK31 transmission. - // PSK31 is sent if cw_dit_duration is 0 - - /* Tone detector */ - int require_tone_detector; // Enables audio input and detector for DTMF and 1750 -}; - -// Initialise local structures -void fsm_init(void); - -// Call the FSM once and update the internal state -void fsm_update(void); - -void fsm_balise_update(void); - -// Update the FAX/SSTV state machine and return 1 if the 1750 notch filter must be -// disabled -int fsm_sstv_update(void); - -// Force a BALISE -void fsm_balise_force(void); - -// Setter for inputs -void fsm_update_inputs(struct fsm_input_signals_t* inputs); - -// Getter for outputs -void fsm_get_outputs(struct fsm_output_signals_t* out); - -// Announce a state change -void fsm_state_switched(const char *new_state); diff --git a/src/common/includes/GPIO/analog.h b/src/common/includes/GPIO/analog.h deleted file mode 100644 index ddc19ac..0000000 --- a/src/common/includes/GPIO/analog.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - -#pragma once -#include - -void analog_init(void); - -/* Measure the 12V supply voltage, in 0.5V increments. - * Returns 0.0f in case of error - * - * Warning, do not run from interrupt context! - */ -float analog_measure_12v(void); - -/* Measure SWR, and return voltages in mV. - * Returns 0 in case of error, 1 in case of success - * - * Warning, do not run from interrupt context! - */ -int analog_measure_swr(int *forward_mv, int* reflected_mv); - -/* Keep an average of measurements, and decide if the repeater should enter - * QRP. Returns 1 if low power must be activated - * - * Warning, do not run from interrupt context! - */ -int analog_supply_too_low(void); - diff --git a/src/common/includes/GPIO/i2c.h b/src/common/includes/GPIO/i2c.h deleted file mode 100644 index 69a4ad2..0000000 --- a/src/common/includes/GPIO/i2c.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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. -*/ - -#ifndef __I2C_H_ -#define __I2C_H_ - -#include - -/* Initialise I2C on the board for both the audio codec and the GPS receiver */ -void i2c_init(void); - -/* Do an I2C write, return 1 on success, 0 on failure */ -int i2c_write(uint8_t device, const uint8_t *txbuf, int len); - -/* Do an I2C read into rxbuf. - * Returns number of bytes received, or 0 in case of failure - */ -int i2c_read(uint8_t device, uint8_t *rxbuf, int len); - -/* Do an I2C write for the address, and then a read into rxbuf. - * Returns number of bytes received, or 0 in case of failure - */ -int i2c_read_from(uint8_t device, uint8_t address, uint8_t *rxbuf, int len); - -/* Start an I2C transaction, keeping exclusive access to I2C */ -void i2c_transaction_start(void); - -/* End an I2C transaction, unlocking the exclusive access to I2C */ -void i2c_transaction_end(void); - -#endif // __I2C_H_ - diff --git a/src/common/includes/GPIO/leds.h b/src/common/includes/GPIO/leds.h deleted file mode 100644 index 70c57d2..0000000 --- a/src/common/includes/GPIO/leds.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#define LED_GREEN 1 -#define LED_ORANGE 2 -#define LED_RED 3 -#define LED_BLUE 4 - -void leds_turn_off(int); -void leds_turn_on(int); diff --git a/src/common/includes/GPIO/pio.h b/src/common/includes/GPIO/pio.h deleted file mode 100644 index e118fc8..0000000 --- a/src/common/includes/GPIO/pio.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. -*/ - -#ifndef _PIO_H_ -#define _PIO_H_ - -#include -#include -#include "Core/fsm.h" - -void pio_init(void); - -void pio_set_tx(int on); -void pio_set_mod_off(int mod_off); -void pio_set_qrp(int on); -void pio_set_gps_epps(int on); -void pio_set_fax(int on); -void pio_set_det_1750(int on); -void pio_set_sq2(int on); - -void pio_set_fsm_signals(struct fsm_input_signals_t* sig); - -int pio_read_button(void); - -#endif // _PIO_H_ - diff --git a/src/common/includes/GPIO/temperature.h b/src/common/includes/GPIO/temperature.h deleted file mode 100644 index f49cc26..0000000 --- a/src/common/includes/GPIO/temperature.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - -#pragma once -#include - -extern float _temperature_last_value; -extern int _temperature_valid; - -// Setup communication and temperature -void temperature_init(void); - -// Return 1 if the temperature is valid -int temperature_valid(void); - -// Get current temperature -float temperature_get(void); - -void temperature_task(void *); diff --git a/src/common/includes/GPIO/usart.h b/src/common/includes/GPIO/usart.h deleted file mode 100644 index 681d86f..0000000 --- a/src/common/includes/GPIO/usart.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - -/* This handles the USART 3 to the GPS receiver, and fills a queue of - * NMEA messages. - * - * It also handles the debug USART 2 and allows sending messages to the PC. - */ - -#ifndef __USART_H_ -#define __USART_H_ - -#ifdef STM32F4XX -# include -#else -# define USART_TypeDef int -# define USART2 ((USART_TypeDef*)2) -# define USART3 ((USART_TypeDef*)3) -#endif - -#define MAX_NMEA_SENTENCE_LEN 256 - -// Initialise USART2 for PC debugging -void usart_init(void); - -// Initialise USART3 for GPS and NMEA queue -// Needs running scheduler -void usart_gps_init(void); - -// Take GPS out of RESET -void usart_gps_remove_reset(void); - -// Send the str to the GPS receiver -void usart_gps_puts(const char* str); - -// a printf to send data to the PC -void usart_debug(const char *format, ...); - -// Send a string to the PC -void usart_debug_puts(const char* str); -void usart_debug_puts_header(const char* hdr, const char* str); - -// Get a MAX_NMEA_SENTENCE_LEN sized NMEA sentence -// Return 1 on success -int usart_get_nmea_sentence(char* nmea); - -void usart_debug_timestamp(void); - -void usart_gps_specific_init(void); - -void usart_process_char(char); -void usart_gps_process_char(char); - -void usart_puts(USART_TypeDef*, const char*); - -#endif //__USART_H_ - diff --git a/src/common/includes/GPS/gps.h b/src/common/includes/GPS/gps.h deleted file mode 100644 index 8a2118f..0000000 --- a/src/common/includes/GPS/gps.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 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. -*/ - -#pragma once - -#include -#include - -/* Setup GPS receiver over USART and parse time */ - -/* USART connections: - * board TX to GPS RX on PD8 - * board RX to GPS TX on PD9 - */ - -// Setup communication and GPS receiver -void gps_init(void); - -// Return 1 of the GPS is receiving time -int gps_locked(void); - -// Get current time from GPS and number of satellite vehicles -// used for fix. -// Returns 1 if data is valid, 0 otherwise -int gps_utctime(struct tm *timeutc, int *num_sv_used); diff --git a/src/common/includes/GPS/minmea.h b/src/common/includes/GPS/minmea.h deleted file mode 100644 index ab770a6..0000000 --- a/src/common/includes/GPS/minmea.h +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright © 2014 Kosma Moczek - * This program is free software. It comes without any warranty, to the extent - * permitted by applicable law. You can redistribute it and/or modify it under - * the terms of the Do What The Fuck You Want To Public License, Version 2, as - * published by Sam Hocevar. See the COPYING file for more details. - */ - -#ifndef MINMEA_H -#define MINMEA_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include -#include - -#define MINMEA_MAX_LENGTH 80 - -enum minmea_sentence_id { - MINMEA_INVALID = -1, - MINMEA_UNKNOWN = 0, - MINMEA_SENTENCE_RMC, - MINMEA_SENTENCE_GGA, - MINMEA_SENTENCE_GSA, - MINMEA_SENTENCE_GLL, - MINMEA_SENTENCE_GST, - MINMEA_SENTENCE_GSV, - MINMEA_SENTENCE_TXT, -}; - -struct minmea_float { - int_least32_t value; - int_least32_t scale; -}; - -struct minmea_date { - int day; - int month; - int year; -}; - -struct minmea_time { - int hours; - int minutes; - int seconds; - int microseconds; -}; - -struct minmea_sentence_rmc { - struct minmea_time time; - bool valid; - struct minmea_float latitude; - struct minmea_float longitude; - struct minmea_float speed; - struct minmea_float course; - struct minmea_date date; - struct minmea_float variation; -}; - -struct minmea_sentence_gga { - struct minmea_time time; - struct minmea_float latitude; - struct minmea_float longitude; - int fix_quality; - int satellites_tracked; - struct minmea_float hdop; - struct minmea_float altitude; char altitude_units; - struct minmea_float height; char height_units; - int dgps_age; -}; - -enum minmea_gll_status { - MINMEA_GLL_STATUS_DATA_VALID = 'A', - MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V', -}; - -enum minmea_gll_mode { - MINMEA_GLL_MODE_AUTONOMOUS = 'A', - MINMEA_GLL_MODE_DPGS = 'D', - MINMEA_GLL_MODE_DR = 'E', -}; - -struct minmea_sentence_gll { - struct minmea_float latitude; - struct minmea_float longitude; - struct minmea_time time; - char status; - char mode; -}; - -struct minmea_sentence_gst { - struct minmea_time time; - struct minmea_float rms_deviation; - struct minmea_float semi_major_deviation; - struct minmea_float semi_minor_deviation; - struct minmea_float semi_major_orientation; - struct minmea_float latitude_error_deviation; - struct minmea_float longitude_error_deviation; - struct minmea_float altitude_error_deviation; -}; - -enum minmea_gsa_mode { - MINMEA_GPGSA_MODE_AUTO = 'A', - MINMEA_GPGSA_MODE_FORCED = 'M', -}; - -enum minmea_gsa_fix_type { - MINMEA_GPGSA_FIX_NONE = 1, - MINMEA_GPGSA_FIX_2D = 2, - MINMEA_GPGSA_FIX_3D = 3, -}; - -struct minmea_sentence_gsa { - char mode; - int fix_type; - int sats[12]; - struct minmea_float pdop; - struct minmea_float hdop; - struct minmea_float vdop; -}; - -struct minmea_sat_info { - int nr; - int elevation; - int azimuth; - int snr; -}; - -struct minmea_sentence_gsv { - int total_msgs; - int msg_nr; - int total_sats; - struct minmea_sat_info sats[4]; -}; - -enum minmea_txt_msgtype { - MINMEA_GPTXT_ERROR = 0, - MINMEA_GPTXT_WARNING = 1, - MINMEA_GPTXT_NOTICE = 2 -}; - -#define MINMEA_TXT_START_IX 16 - -struct minmea_sentence_txt { - int num_msg; - int msg_num; - enum minmea_txt_msgtype msgtype; - const char* text; - int text_len; -}; - -/** - * Calculate raw sentence checksum. Does not check sentence integrity. - */ -uint8_t minmea_checksum(const char *sentence); - -/** - * Check sentence validity and checksum. Returns true for valid sentences. - */ -bool minmea_check(const char *sentence, bool strict); - -/** - * Determine talker identifier. - */ -bool minmea_talker_id(char talker[3], const char *sentence); - -/** - * Determine sentence identifier. - */ -enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict); - -/** - * Scanf-like processor for NMEA sentences. Supports the following formats: - * c - single character (char *) - * d - direction, returned as 1/-1, default 0 (int *) - * f - fractional, returned as value + scale (int *, int *) - * i - decimal, default zero (int *) - * s - string (char *) - * t - talker identifier and type (char *) - * T - date/time stamp (int *, int *, int *) - * Returns true on success. See library source code for details. - */ -bool minmea_scan(const char *sentence, const char *format, ...); - -/* - * Parse a specific type of sentence. Return true on success. - */ -bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence); -bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence); -bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence); -bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence); -bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence); -bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence); -bool minmea_parse_txt(struct minmea_sentence_txt *frame, const char *sentence); - -/** - * Convert GPS UTC date/time representation to a UNIX timestamp. - */ -int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_); - -/** - * Rescale a fixed-point value to a different scale. Rounds towards zero. - */ -static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale) -{ - if (f->scale == 0) - return 0; - if (f->scale == new_scale) - return f->value; - if (f->scale > new_scale) - return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale); - else - return f->value * (new_scale/f->scale); -} - -/** - * Convert a fixed-point value to a floating-point value. - * Returns NaN for "unknown" values. - */ -static inline float minmea_tofloat(struct minmea_float *f) -{ - if (f->scale == 0) - return NAN; - return (float) f->value / (float) f->scale; -} - -/** - * Convert a raw coordinate to a floating point DD.DDD... value. - * Returns NaN for "unknown" values. - */ -static inline float minmea_tocoord(struct minmea_float *f) -{ - if (f->scale == 0) - return NAN; - int_least32_t degrees = f->value / (f->scale * 100); - int_least32_t minutes = f->value % (f->scale * 100); - return (float) degrees + (float) minutes / (60 * f->scale); -} - -#ifdef __cplusplus -} -#endif - -#endif /* MINMEA_H */ - -/* vim: set ts=4 sw=4 et: */ diff --git a/src/common/sourcelist.txt b/src/common/sourcelist.txt index 9d6ec78..b0010be 100644 --- a/src/common/sourcelist.txt +++ b/src/common/sourcelist.txt @@ -1,12 +1,12 @@ -src/GPIO/analog.c -src/GPIO/usart.c -src/GPIO/temperature.c -src/GPS/gps.c -src/GPS/minmea.c -src/Core/common.c -src/Core/fsm.c -src/Core/main.c -src/Audio/cw.c -src/Audio/audio.c -src/Audio/audio_in.c -src/Audio/tone.c +GPIO/analog.c +GPIO/usart.c +GPIO/temperature.c +GPS/gps.c +GPS/minmea.c +Core/common.c +Core/fsm.c +Core/main.c +Audio/cw.c +Audio/audio.c +Audio/audio_in.c +Audio/tone.c diff --git a/src/common/src/Audio/audio.c b/src/common/src/Audio/audio.c deleted file mode 100644 index f68f867..0000000 --- a/src/common/src/Audio/audio.c +++ /dev/null @@ -1,50 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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 "Audio/audio.h" - -#include - -AudioCallbackFunction *callback_function; -void *callback_context; -int16_t * next_buffer_samples; -int next_buffer_length; -int buffer_number; -bool dma_running; - -void audio_initialize(int plln, int pllr, int i2sdiv, int i2sodd, int rate) { - - // Intitialize state. - callback_function = NULL; - callback_context = NULL; - next_buffer_samples = NULL; - next_buffer_length = 0; - buffer_number = 0; - dma_running = false; - - audio_initialize_platform(plln, pllr, i2sdiv, i2sodd, rate); - - audio_set_volume(0xff); - -} diff --git a/src/common/src/Audio/audio_in.c b/src/common/src/Audio/audio_in.c deleted file mode 100644 index dd1994e..0000000 --- a/src/common/src/Audio/audio_in.c +++ /dev/null @@ -1,24 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony - * - * 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. -*/ - diff --git a/src/common/src/Audio/cw.c b/src/common/src/Audio/cw.c deleted file mode 100644 index 667d459..0000000 --- a/src/common/src/Audio/cw.c +++ /dev/null @@ -1,605 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 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. -*/ - -/* CW and PSK31 generator - * - * Concept: - * - * +-------------------+ +----------------+ - * | cw_push_message() | -> cw_msg_queue -> | cw_psk31task() | -> cw_audio_queue - * +-------------------+ +----------------+ - * - * The cw_psk31_fill_buffer() function can be called to fetch audio from the - * audio_queue - */ - -#include "Audio/cw.h" -#include "Core/common.h" -#include "Audio/audio.h" -#include - -#ifdef SIMULATOR -#include -#define arm_cos_f32 cosf -#define arm_sin_f32 sinf -#else -#include "arm_math.h" -#endif - -/* Kernel includes. */ -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" -#include "semphr.h" - -#define MAX_MESSAGE_LEN 1024 -#define MAX_ON_BUFFER_LEN 8192 - -const uint8_t cw_mapping[60] = { // {{{ - // Read bits from right to left - - 0b110101, //+ ASCII 43 - 0b110101, //, ASCII 44 - 0b1011110, //- ASCII 45 - - 0b1010101, //., ASCII 46 - 0b110110, // / ASCII 47 - - 0b100000, // 0, ASCII 48 - 0b100001, // 1 - 0b100011, - 0b100111, - 0b101111, - 0b111111, - 0b111110, - 0b111100, - 0b111000, - 0b110000, // 9, ASCII 57 - - // The following are mostly invalid, but - // required to fill the gap in ASCII between - // numerals and capital letters - 0b10, // : - 0b10, // ; - 0b10, // < - 0b101110, // = - 0b10, // > - 0b1110011, // ? - 0b1101001, //@ - - 0b101, // A ASCII 65 - 0b11110, - 0b11010, - 0b1110, - 0b11, - 0b11011, - 0b1100, - 0b11111, - 0b111, - 0b10001, - 0b1010, - 0b11101, - 0b100, //M - 0b110, - 0b1000, - 0b11001, - 0b10100, - 0b1101, - 0b1111, - 0b10, - 0b1011, - 0b10111, - 0b1001, - 0b10110, - 0b10010, - 0b11100, // Z - - 0b101010, //Start, ASCII [ - 0b1010111, // SK , ASCII '\' -}; //}}} - -#if ENABLE_PSK31 -/* - * PSK31 Varicode - * http://aintel.bi.ehu.es/psk31.html - */ -const char *psk31_varicode[] = { // {{{ - "1010101011", - "1011011011", - "1011101101", - "1101110111", - "1011101011", - "1101011111", - "1011101111", - "1011111101", - "1011111111", - "11101111", - "11101", - "1101101111", - "1011011101", - "11111", - "1101110101", - "1110101011", - "1011110111", - "1011110101", - "1110101101", - "1110101111", - "1101011011", - "1101101011", - "1101101101", - "1101010111", - "1101111011", - "1101111101", - "1110110111", - "1101010101", - "1101011101", - "1110111011", - "1011111011", - "1101111111", - "1", - "111111111", - "101011111", - "111110101", - "111011011", - "1011010101", - "1010111011", - "101111111", - "11111011", - "11110111", - "101101111", - "111011111", - "1110101", - "110101", - "1010111", - "110101111", - "10110111", - "10111101", - "11101101", - "11111111", - "101110111", - "101011011", - "101101011", - "110101101", - "110101011", - "110110111", - "11110101", - "110111101", - "111101101", - "1010101", - "111010111", - "1010101111", - "1010111101", - "1111101", - "11101011", - "10101101", - "10110101", - "1110111", - "11011011", - "11111101", - "101010101", - "1111111", - "111111101", - "101111101", - "11010111", - "10111011", - "11011101", - "10101011", - "11010101", - "111011101", - "10101111", - "1101111", - "1101101", - "101010111", - "110110101", - "101011101", - "101110101", - "101111011", - "1010101101", - "111110111", - "111101111", - "111111011", - "1010111111", - "101101101", - "1011011111", - "1011", - "1011111", - "101111", - "101101", - "11", - "111101", - "1011011", - "101011", - "1101", - "111101011", - "10111111", - "11011", - "111011", - "1111", - "111", - "111111", - "110111111", - "10101", - "10111", - "101", - "110111", - "1111011", - "1101011", - "11011111", - "1011101", - "111010101", - "1010110111", - "110111011", - "1010110101", - "1011010111", - "1110110101", -}; //}}} -#endif - -// Function to display message in GUI -void cw_message_sent(const char* str); - -struct cw_message_s { - char message[MAX_MESSAGE_LEN]; - size_t message_len; - - int freq; - - // If dit_duration is 0, the message is sent in PSK31 - int dit_duration; -}; - -// The queue contains above structs -QueueHandle_t cw_msg_queue; - -// Queue that contains audio data -QueueHandle_t cw_audio_queue; -static int cw_psk31_samplerate; - -static int cw_transmit_ongoing; - -static void cw_psk31_task(void *pvParameters); - -void cw_psk31_init(unsigned int samplerate) -{ - cw_psk31_samplerate = samplerate; - cw_transmit_ongoing = 0; - - cw_msg_queue = xQueueCreate(10, sizeof(struct cw_message_s)); - if (cw_msg_queue == 0) { - while(1); /* fatal error */ - } - - cw_audio_queue = xQueueCreate(1, AUDIO_BUF_LEN * sizeof(int16_t)); - if (cw_audio_queue == 0) { - while(1); /* fatal error */ - } - - xTaskCreate( - cw_psk31_task, - "CWPSKTask", - 8*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 3UL, - NULL); -} - -/* Parse one CW letter/symbol, and fill in the on_buffer. - * Returns number of uint8_t written. - */ -size_t cw_symbol(uint8_t sym, uint8_t *on_buffer, size_t on_buffer_size) -{ - uint8_t p = 0; - uint8_t val = cw_mapping[sym]; - size_t pos = 0; - - while((val >> p) != 0b1) { - - if (((val >> p) & 0b1) == 0b1) { - if (pos + 2 < on_buffer_size) { - // tone(1) - on_buffer[pos++] = 1; - - // silence(1) - on_buffer[pos++] = 0; - } - } - else { - if (pos + 4 < on_buffer_size) { - // tone(3) - on_buffer[pos++] = 1; - on_buffer[pos++] = 1; - on_buffer[pos++] = 1; - - // silence(1) - on_buffer[pos++] = 0; - } - } - - p++; - } - - // silence(2) - if (pos + 2 < on_buffer_size) { - for (int i = 0; i < 2; i++) { - on_buffer[pos++] = 0; - } - } - - return pos; -} - -// Transmit a string in morse code or PSK31. -// Supported range for CW: -// All ASCII between '+' and '\', which includes -// numerals and capital letters. -// Distinction between CW and PSK31 is done on dit_duration==0 -int cw_psk31_push_message(const char* text, int dit_duration, int frequency) -{ - const int text_len = strlen(text); - - if (strlen(text) > MAX_MESSAGE_LEN) { - return 0; - } - - struct cw_message_s msg; - for (int i = 0; i < MAX_MESSAGE_LEN; i++) { - if (i < text_len) { - msg.message[i] = text[i]; - } - else { - msg.message[i] = '\0'; - } - } - msg.message_len = text_len; - msg.freq = frequency; - msg.dit_duration = dit_duration; - - xQueueSendToBack(cw_msg_queue, &msg, portMAX_DELAY); /* Send Message */ - - cw_message_sent(msg.message); - - return 1; -} - -/* Parse the message and fill the on_buffer with CW on/CW off information. - * Returns the number of on/off bits written. - */ -static size_t cw_text_to_on_buffer(const char *msg, uint8_t *on_buffer, size_t on_buffer_size) -{ - size_t pos = 0; - const char* sym = msg; - do { - if (*sym < '+' || *sym > '\\') { - if (pos + 3 < on_buffer_size) { - for (int i = 0; i < 3; i++) { - on_buffer[pos++] = 0; - } - } - } - else { - pos += cw_symbol(*sym - '+', on_buffer + pos, on_buffer_size - pos); - } - sym++; - } while (*sym != '\0'); - - return pos; -} - - -#if ENABLE_PSK31 -/* - * Turn a null terminated ASCII string into a uint8_t buffer - * of 0 and 1 representing the PSK31 varicode for the input. - * - * outstr must be at least size 20 + strlen(instr)*12 + 20 to accomodate - * the header and tail. - * - * Returns number of bytes written. - */ -static size_t psk31_text_to_phase_buffer(const char* instr, uint8_t* 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] == '1') ? 1 : 0; - } - outbits[i++] = 0; - outbits[i++] = 0; - } - - /* Tail of 0s */ - for (j=0; j < 20; j++) { - outbits[i++] = 0; - } - - return i; -} -#endif - - -size_t cw_psk31_fill_buffer(int16_t *buf, size_t bufsize) -{ - if (xQueueReceiveFromISR(cw_audio_queue, buf, NULL)) { - return bufsize; - } - else { - return 0; - } -} - -static int16_t cw_audio_buf[AUDIO_BUF_LEN]; -static uint8_t cw_psk31_buffer[MAX_ON_BUFFER_LEN]; -static struct cw_message_s cw_fill_msg_current; - -// Routine to generate CW audio -static float cw_generate_audio_ampl = 0.0f; -static float cw_generate_audio_nco = 0.0f; -static int16_t cw_generate_audio(float omega, int i, int __attribute__ ((unused))t) -{ - int16_t s = 0; - // Remove clicks from CW - if (cw_psk31_buffer[i]) { - const float remaining = 32768.0f - cw_generate_audio_ampl; - cw_generate_audio_ampl += remaining / 64.0f; - } - else { - cw_generate_audio_ampl -= cw_generate_audio_ampl / 64.0f; - - if (cw_generate_audio_ampl < 0) { - cw_generate_audio_ampl = 0; - } - } - - cw_generate_audio_nco += omega; - if (cw_generate_audio_nco > FLOAT_PI) { - cw_generate_audio_nco -= 2.0f * FLOAT_PI; - } - - s = cw_generate_audio_ampl * arm_sin_f32(cw_generate_audio_nco); - return s; -} - -#if ENABLE_PSK31 -static float psk31_generate_audio_nco = 0.0f; -static int psk31_current_psk_phase = 1; -static int16_t psk31_generate_audio(float omega, int i, int t, int samples_per_symbol) -{ - int16_t s = 0; - const float base_ampl = 20000.0f; - float psk31_generate_audio_ampl = 0.0f; - - if (cw_psk31_buffer[i] == 1) { - psk31_generate_audio_ampl = base_ampl; - } - else { - psk31_generate_audio_ampl = base_ampl * arm_cos_f32( - FLOAT_PI*(float)t/(float)samples_per_symbol); - } - - psk31_generate_audio_nco += omega; - if (psk31_generate_audio_nco > FLOAT_PI) { - psk31_generate_audio_nco -= 2.0f * FLOAT_PI; - } - - s = psk31_generate_audio_ampl * - arm_sin_f32(psk31_generate_audio_nco + - (psk31_current_psk_phase == 1 ? 0.0f : FLOAT_PI)); - - return s; -} -#endif - -static void cw_psk31_task(void __attribute__ ((unused))*pvParameters) -{ - int buf_pos = 0; - - while (1) { - int status = xQueueReceive(cw_msg_queue, &cw_fill_msg_current, portMAX_DELAY); - if (status == pdTRUE) { - - size_t cw_psk31_buffer_len = 0; - - cw_transmit_ongoing = 1; - - if (cw_fill_msg_current.dit_duration) { - cw_psk31_buffer_len = cw_text_to_on_buffer( - cw_fill_msg_current.message, - cw_psk31_buffer, - MAX_ON_BUFFER_LEN); - - } -#if ENABLE_PSK31 - else { - cw_psk31_buffer_len = psk31_text_to_phase_buffer( - cw_fill_msg_current.message, - cw_psk31_buffer); - } -#endif - - // Angular frequency of NCO - const float omega = 2.0f * FLOAT_PI * cw_fill_msg_current.freq / - (float)cw_psk31_samplerate; - - const int samples_per_symbol = (cw_fill_msg_current.dit_duration != 0) ? - (cw_psk31_samplerate * cw_fill_msg_current.dit_duration) / 1000 : - /* BPSK31 is at 31.25 symbols per second. */ - cw_psk31_samplerate * 100 / 3125; - -#if ENABLE_PSK31 - psk31_current_psk_phase = 1; -#endif - - for (int i = 0; i < cw_psk31_buffer_len; i++) { - for (int t = 0; t < samples_per_symbol; t++) { -#if ENABLE_PSK31 - int16_t s = (cw_fill_msg_current.dit_duration != 0) ? - cw_generate_audio(omega, i, t) : - psk31_generate_audio(omega, i, t, samples_per_symbol); -#else - int16_t s = cw_generate_audio(omega, i, t); -#endif - - // Stereo - for (int channel = 0; channel < 2; channel++) { - if (buf_pos == AUDIO_BUF_LEN) { - // It should take AUDIO_BUF_LEN/cw_psk31_samplerate seconds to send one buffer. - // If it takes more than 4 times as long, we think there is a problem. - const TickType_t reasonable_delay = pdMS_TO_TICKS(4000 * AUDIO_BUF_LEN / cw_psk31_samplerate); - if (xQueueSendToBack(cw_audio_queue, &cw_audio_buf, reasonable_delay) != pdTRUE) { - trigger_fault(FAULT_SOURCE_CW_AUDIO_QUEUE); - } - buf_pos = 0; - } - cw_audio_buf[buf_pos++] = s; - } - - } - -#if ENABLE_PSK31 - if (cw_psk31_buffer[i] == 0) { - psk31_current_psk_phase *= -1; - } -#endif - } - - // We have completed this message - - cw_transmit_ongoing = 0; - } - } -} - -int cw_psk31_busy(void) -{ - return cw_transmit_ongoing; -} - diff --git a/src/common/src/Audio/tone.c b/src/common/src/Audio/tone.c deleted file mode 100644 index 90a8394..0000000 --- a/src/common/src/Audio/tone.c +++ /dev/null @@ -1,358 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Maximilien Cuony, 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 "Audio/tone.h" -#include "Core/common.h" -#include "GPIO/usart.h" - -#include -#include "queue.h" - -#ifdef SIMULATOR -#include -#define arm_cos_f32 cosf -#define arm_sin_f32 sinf -#else -#include "arm_math.h" -#endif - -#define TONE_N 800 - -/* DTMF frequencies - * COL_1 COL_2 COL_3 COL_A - * 1209 1336 1477 1633 - * 697 [1] [2] [3] [A] ROW_1 - * 770 [4] [5] [6] [B] ROW_4 - * 852 [7] [8] [9] [C] ROW_7 - * 941 [*] [0] [#] [D] ROW_STAR - * - * We need 0, 7, and 1750Hz for now, having detectors for - * 1209, 1336, 852, 941 and 1750 is sufficient. */ - -#define DET_COL_1 0 -#define DET_COL_2 1 -#define DET_ROW_7 2 -#define DET_ROW_STAR 3 -#define DET_1750 4 -#define NUM_DETECTORS 5 - -// Incomplete because not all frequencies decoded -enum dtmf_code { - DTMF_NONE = 0, - DTMF_0, - DTMF_7, - DTMF_8, - DTMF_STAR, -}; - -struct tone_detector { - float coef; - float Q1; - float Q2; -}; - -static struct tone_detector detectors[NUM_DETECTORS]; - -static int num_samples_analysed = 0; -static float prev_mean = 0; -static uint32_t accum = 0; -static int lost_results = 0; -static QueueHandle_t m_squared_queue; - -// Apply an IIR filter with alpha = 5/16 to smooth out variations. -// Values are normalised s.t. the mean corresponds to 100 -static int32_t normalised_results[NUM_DETECTORS]; - -static int num_tone_1750_detected = 0; -static uint64_t tone_1750_detected_since = 0; -static int detectors_enabled = 0; - -static enum dtmf_code dtmf_last_seen = 0; -static uint64_t dtmf_last_seen_at = 0; - -// Store the sequence of dtmf codes in this FIFO. If no DTMF code gets -// decoded in the interval, a NONE gets inserted into the sequence -#define NUM_DTMF_SEQ 3 -#define DTMF_MAX_TONE_INTERVAL 2500 -static enum dtmf_code dtmf_sequence[NUM_DTMF_SEQ]; - -static char* dtmf_to_str(enum dtmf_code code) -{ - char *codestr = "?"; - switch (code) { - case DTMF_0: codestr = "0"; break; - case DTMF_7: codestr = "7"; break; - case DTMF_8: codestr = "8"; break; - case DTMF_STAR: codestr = "*"; break; - case DTMF_NONE: codestr = "x"; break; - } - return codestr; -} - -static inline void push_dtmf_code(enum dtmf_code code) -{ - for (int i = 0; i < NUM_DTMF_SEQ-1; i++) { - dtmf_sequence[i] = dtmf_sequence[i+1]; - } - dtmf_sequence[NUM_DTMF_SEQ-1] = code; - - if (code != DTMF_NONE) { - usart_debug("DTMF: [%s, %s, %s]\r\n", - dtmf_to_str(dtmf_sequence[0]), - dtmf_to_str(dtmf_sequence[1]), - dtmf_to_str(dtmf_sequence[2])); - } -} - -// TODO: Does that depend on TONE_N? -const int thresh_dtmf = 200; -const int thresh_1750 = 300; -const int num_1750_required = 5; - -static void analyse_dtmf() -{ - // Bits 0 to 9 are numbers, bit 10 to 13 letters, bit 14 is star, 15 is hash - const uint16_t pattern = - ((normalised_results[DET_COL_1] > thresh_dtmf && - normalised_results[DET_ROW_7] > thresh_dtmf) ? (1 << 7) : 0) + - ((normalised_results[DET_COL_2] > thresh_dtmf && - normalised_results[DET_ROW_7] > thresh_dtmf) ? (1 << 8) : 0) + - ((normalised_results[DET_COL_1] > thresh_dtmf && - normalised_results[DET_ROW_STAR] > thresh_dtmf) ? (1 << 14) : 0) + - ((normalised_results[DET_COL_2] > thresh_dtmf && - normalised_results[DET_ROW_STAR] > thresh_dtmf) ? (1 << 0) : 0); - - // Match patterns exactly to exclude multiple simultaneous DTMF codes. - if (pattern == (1 << 0)) { - if (dtmf_last_seen != DTMF_0) { - push_dtmf_code(DTMF_0); - } - - dtmf_last_seen = DTMF_0; - dtmf_last_seen_at = timestamp_now(); - } - else if (pattern == (1 << 7)) { - if (dtmf_last_seen != DTMF_7) { - push_dtmf_code(DTMF_7); - } - - dtmf_last_seen = DTMF_7; - dtmf_last_seen_at = timestamp_now(); - } - else if (pattern == (1 << 8)) { - if (dtmf_last_seen != DTMF_8) { - push_dtmf_code(DTMF_8); - } - - dtmf_last_seen = DTMF_8; - dtmf_last_seen_at = timestamp_now(); - } - else if (pattern == (1 << 14)) { - if (dtmf_last_seen != DTMF_STAR) { - push_dtmf_code(DTMF_STAR); - } - - dtmf_last_seen = DTMF_STAR; - dtmf_last_seen_at = timestamp_now(); - } - else if (dtmf_last_seen_at + DTMF_MAX_TONE_INTERVAL < timestamp_now()) { - // Flush out all codes - push_dtmf_code(DTMF_NONE); - - dtmf_last_seen = DTMF_NONE; - dtmf_last_seen_at = timestamp_now(); - } -} - - -int tone_1750_status() -{ - return num_tone_1750_detected >= num_1750_required; -} - -int tone_1750_for_5_seconds() -{ - return (num_tone_1750_detected >= num_1750_required) && - tone_1750_detected_since + 5000 < timestamp_now(); -} - -int tone_fax_status() -{ - return dtmf_sequence[0] == DTMF_0 && - dtmf_sequence[1] == DTMF_7 && - dtmf_sequence[2] == DTMF_STAR; -} - -static inline void init_detector(struct tone_detector* detector, int freq) { - detector->coef = 2.0f * arm_cos_f32(2.0f * FLOAT_PI * freq / AUDIO_IN_RATE); - detector->Q1 = 0; - detector->Q2 = 0; -} - -void tone_init() { - m_squared_queue = xQueueCreate(2, NUM_DETECTORS * sizeof(float)); - if (m_squared_queue == 0) { - trigger_fault(FAULT_SOURCE_ADC2_QUEUE); - } - - for (int i = 0; i < NUM_DTMF_SEQ; i++) { - dtmf_sequence[i] = DTMF_NONE; - } - - init_detector(&detectors[DET_COL_1], 1209); - init_detector(&detectors[DET_COL_2], 1336); - init_detector(&detectors[DET_ROW_7], 852); - init_detector(&detectors[DET_ROW_STAR], 941); - init_detector(&detectors[DET_1750], 1750); -} - -void tone_detector_enable(int enable) -{ - if (enable && !detectors_enabled) { - num_samples_analysed = 0; - prev_mean = 0; - accum = 0; - for (int det = 0; det < NUM_DETECTORS; det++) { - detectors[det].Q1 = 0; - detectors[det].Q2 = 0; - } - audio_in_enable(1); - detectors_enabled = 1; - } - else if (!enable && detectors_enabled) { - audio_in_enable(0); - detectors_enabled = 0; - } -} - -void tone_detect_push_sample(const uint16_t sample, int is_irq) -{ - num_samples_analysed++; - - accum += sample; - - // Do not do tone detection before we have calculated a mean - if (prev_mean > 0) { - const float s = sample - prev_mean; - - for (int det = 0; det < NUM_DETECTORS; det++) { - struct tone_detector *detector = &detectors[det]; - - float Q0 = detector->coef * detector->Q1 - detector->Q2 + s; - detector->Q2 = detector->Q1; - detector->Q1 = Q0; - } - } - - if (num_samples_analysed == TONE_N) { - num_samples_analysed = 0; - - if (prev_mean > 0) { - float m_squared[NUM_DETECTORS]; - for (int det = 0; det < NUM_DETECTORS; det++) { - struct tone_detector *detector = &detectors[det]; - m_squared[det] = - detector->Q1 * detector->Q1 + - detector->Q2 * detector->Q2 - - detector->coef * detector->Q1 * detector->Q2; - detector->Q1 = 0; - detector->Q2 = 0; - } - - BaseType_t require_context_switch = 0; - int success; - if (is_irq) { - success = xQueueSendToBackFromISR( - m_squared_queue, - &m_squared, - &require_context_switch); - } - else { - success = xQueueSendToBack( - m_squared_queue, - &m_squared, - 0); - } - - if (success == pdFALSE) { - lost_results++; - } - } - - prev_mean = (float)accum / (float)TONE_N; - accum = 0; - } -} - -void tone_do_analysis() -{ - float m[NUM_DETECTORS]; - while (!xQueueReceive(m_squared_queue, &m, portMAX_DELAY)) {} - - float inv_mean = 0; - for (int det = 0; det < NUM_DETECTORS; det++) { - m[det] = sqrtf(m[det]); - inv_mean += m[det]; - } - inv_mean = NUM_DETECTORS / inv_mean; - - for (int det = 0; det < NUM_DETECTORS; det++) { - normalised_results[det] = - (11 * normalised_results[det] + - (int)(5 * 100 * m[det] * inv_mean)) - >> 4; // divide by 16 - } - - if (num_tone_1750_detected < num_1750_required && - normalised_results[DET_1750] > thresh_1750) { - num_tone_1750_detected++; - - if (num_tone_1750_detected == num_1750_required) { - tone_1750_detected_since = timestamp_now(); - } - } - else if (num_tone_1750_detected > 0 && - normalised_results[DET_1750] <= thresh_1750) { - num_tone_1750_detected--; - } - - analyse_dtmf(); - -#if PRINT_TONES_STATS - static int printcounter = 0; - if (++printcounter == 5) { - usart_debug("Tones: % 3d % 3d % 3d % 3d % 3d since %d\r\n", - normalised_results[0], - normalised_results[1], - normalised_results[2], - normalised_results[3], - normalised_results[4], - (int)(timestamp_now() - tone_1750_detected_since) - ); - - printcounter = 0; - } -#endif // PRINT_TONES_STATS -} - diff --git a/src/common/src/Core/FreeRTOSConfig.h b/src/common/src/Core/FreeRTOSConfig.h deleted file mode 100644 index b58e2a2..0000000 --- a/src/common/src/Core/FreeRTOSConfig.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - FreeRTOS V8.0.0:rc2 - Copyright (C) 2014 Real Time Engineers Ltd. - All rights reserved - - VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION. - - *************************************************************************** - * * - * FreeRTOS provides completely free yet professionally developed, * - * robust, strictly quality controlled, supported, and cross * - * platform software that has become a de facto standard. * - * * - * Help yourself get started quickly and support the FreeRTOS * - * project by purchasing a FreeRTOS tutorial book, reference * - * manual, or both from: http://www.FreeRTOS.org/Documentation * - * * - * Thank you! * - * * - *************************************************************************** - - This file is part of the FreeRTOS distribution. - - FreeRTOS is free software; you can redistribute it and/or modify it under - the terms of the GNU General Public License (version 2) as published by the - Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception. - - >>! NOTE: The modification to the GPL is included to allow you to distribute - >>! a combined work that includes FreeRTOS without being obliged to provide - >>! the source code for proprietary components outside of the FreeRTOS - >>! kernel. - - FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. Full license text is available from the following - link: http://www.freertos.org/a00114.html - - 1 tab == 4 spaces! - - *************************************************************************** - * * - * Having a problem? Start by reading the FAQ "My application does * - * not run, what could be wrong?" * - * * - * http://www.FreeRTOS.org/FAQHelp.html * - * * - *************************************************************************** - - http://www.FreeRTOS.org - Documentation, books, training, latest versions, - license and Real Time Engineers Ltd. contact details. - - http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products, - including FreeRTOS+Trace - an indispensable productivity tool, a DOS - compatible FAT file system, and our tiny thread aware UDP/IP stack. - - http://www.OpenRTOS.com - Real Time Engineers ltd license FreeRTOS to High - Integrity Systems to sell under the OpenRTOS brand. Low cost OpenRTOS - licenses offer ticketed support, indemnification and middleware. - - http://www.SafeRTOS.com - High Integrity Systems also provide a safety - engineered and independently SIL3 certified version for use in safety and - mission critical applications that require provable dependability. - - 1 tab == 4 spaces! -*/ - - -#ifndef FREERTOS_CONFIG_H -#define FREERTOS_CONFIG_H - -/*----------------------------------------------------------- - * Application specific definitions. - * - * These definitions should be adjusted for your particular hardware and - * application requirements. - * - * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE - * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. - * - * See http://www.freertos.org/a00110.html. - *----------------------------------------------------------*/ - -/* Ensure stdint is only used by the compiler, and not the assembler. */ -//#ifdef __ICCARM__ - #include - extern uint32_t SystemCoreClock; -//#endif - -#define configUSE_PREEMPTION 1 -#define configUSE_IDLE_HOOK 0 // Default: 1 -#define configUSE_TICK_HOOK 0 // Default: 1 -#define configCPU_CLOCK_HZ ( SystemCoreClock ) -#define configTICK_RATE_HZ ( ( portTickType ) 250 ) -#define configMAX_PRIORITIES ( 5 ) -#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 ) -#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) ) -#define configMAX_TASK_NAME_LEN ( 10 ) -#define configUSE_TRACE_FACILITY 1 -#define configUSE_16_BIT_TICKS 0 -#define configIDLE_SHOULD_YIELD 1 -#define configUSE_TICKLESS_IDLE 1 -#define configUSE_MUTEXES 1 -#define configQUEUE_REGISTRY_SIZE 8 -#define configUSE_RECURSIVE_MUTEXES 1 -#define configUSE_MALLOC_FAILED_HOOK 0 // Default: 1 -#define configUSE_APPLICATION_TASK_TAG 0 -#define configUSE_COUNTING_SEMAPHORES 1 -#define configGENERATE_RUN_TIME_STATS 0 - -/* Co-routine definitions. */ -#define configUSE_CO_ROUTINES 0 -#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) - -/* Software timer definitions. */ -#define configUSE_TIMERS 1 -#define configTIMER_TASK_PRIORITY ( 2 ) -#define configTIMER_QUEUE_LENGTH 10 -#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) - -/* Set the following definitions to 1 to include the API function, or zero -to exclude the API function. */ -#define INCLUDE_vTaskPrioritySet 1 -#define INCLUDE_uxTaskPriorityGet 1 -#define INCLUDE_vTaskDelete 1 -#define INCLUDE_vTaskCleanUpResources 1 -#define INCLUDE_vTaskSuspend 1 -#define INCLUDE_vTaskDelayUntil 1 -#define INCLUDE_vTaskDelay 1 - -/* Cortex-M specific definitions. */ -#ifdef __NVIC_PRIO_BITS - /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ - #define configPRIO_BITS __NVIC_PRIO_BITS -#else - #define configPRIO_BITS 4 /* 15 priority levels */ -#endif - -/* The lowest interrupt priority that can be used in a call to a "set priority" -function. */ -#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf - -/* The highest interrupt priority that can be used by any interrupt service -routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL -INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER -PRIORITY THAN THIS! (higher priorities are lower numeric values. */ -#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 - -/* Interrupt priorities used by the kernel port layer itself. These are generic -to all Cortex-M ports, and do not rely on any particular library functions. */ -#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) -/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! -See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ -#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) - -/* Normal assert() semantics without relying on the provision of an assert.h -header file. */ -#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); } - -/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS -standard names. */ -#define vPortSVCHandler SVC_Handler -#define xPortPendSVHandler PendSV_Handler -#define xPortSysTickHandler SysTick_Handler - -#endif /* FREERTOS_CONFIG_H */ diff --git a/src/common/src/Core/common.c b/src/common/src/Core/common.c deleted file mode 100644 index 7e19eaf..0000000 --- a/src/common/src/Core/common.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 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 "Core/common.h" -#include "GPIO/usart.h" -#include "FreeRTOS.h" -#include "timers.h" -#include "GPS/gps.h" -#include -#include - -static uint64_t common_timestamp = 0; // milliseconds since startup -static TimerHandle_t common_timer; - -// The LFSR is used as random number generator -static const uint16_t lfsr_start_state = 0x12ABu; -static uint16_t lfsr; - -static void common_increase_timestamp(TimerHandle_t t); - -struct tm last_derived_time; -static uint64_t last_derived_time_timestamp = 0; -int last_derived_time_valid = 0; -int last_derived_time_delta_applied = 0; - - -#ifdef SIMULATOR -long timestamp_delta = 0; -#endif - -int find_last_sunday(const struct tm* time) { - struct tm t = *time; - - // the last sunday can never be before the 20th - t.tm_mday = 20; - - int last_sunday = 1; - - while (t.tm_mon == time->tm_mon) { - t.tm_mday++; - if (mktime(&t) == (time_t)-1) { - return -1; - } - - const int sunday = 0; - if (t.tm_wday == sunday) { - last_sunday = t.tm_mday; - } - } - - return last_sunday; -} - -/* Calculate if we are in daylight saving time. - * return 0 if false - * 1 if true - * -1 in case of error - */ -static int is_dst(const struct tm *time) { - /* DST from 01:00 UTC on last Sunday in March - * to 01:00 UTC on last Sunday in October - */ - const int march = 2; - const int october = 9; - if (time->tm_mon < march) { - return 0; - } - else if (time->tm_mon == march) { - int last_sunday = find_last_sunday(time); - if (last_sunday == -1) return -1; - - if (time->tm_mday < last_sunday) { - return 0; - } - else if (time->tm_mday == last_sunday) { - return (time->tm_hour < 1) ? 0 : 1; - } - else { - return 1; - } - } - else if (time->tm_mon > march && time->tm_mon < october) { - return 1; - } - else if (time->tm_mon == october) { - int last_sunday = find_last_sunday(time); - if (last_sunday == -1) return -1; - - if (time->tm_mday < last_sunday) { - return 1; - } - else if (time->tm_mday == last_sunday) { - return (time->tm_hour < 1) ? 1 : 0; - } - else { - return 0; - } - } - else { - return 0; - } -} - -int local_time(struct tm *time) { - const int local_time_offset=1; // hours - - int num_sv_used = 0; - int valid = gps_utctime(time, &num_sv_used); - - if (valid) { - time->tm_hour += local_time_offset; - - const int dst = is_dst(time); - if (dst == -1) { - usart_debug("mktime fail for dst %d-%d-%d %d:%d:%d " - "dst %d wday %d yday %d\r\n", - time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, - time->tm_hour, time->tm_min, time->tm_sec, - time->tm_isdst, time->tm_wday, time->tm_yday); - } - else if (dst == 1) { - time->tm_hour++; - time->tm_isdst = 1; - } - - // Let mktime fix the struct tm *time - if (mktime(time) == (time_t)-1) { - usart_debug("mktime fail for local_time %d-%d-%d %d:%d:%d " - "dst %d wday %d yday %d\r\n", - time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, - time->tm_hour, time->tm_min, time->tm_sec, - time->tm_isdst, time->tm_wday, time->tm_yday); - valid = 0; - } - - if (valid) { - last_derived_time = *time; - last_derived_time_timestamp = timestamp_now(); - last_derived_time_valid = 1; - last_derived_time_delta_applied = 0; - } - - } - - return valid; -} - -int local_derived_time(struct tm *time) { - - if (last_derived_time_valid == 0) { - return 0; - } - - // As there is a GPS timeout, local_time will think he has valid GPS data for GPS_MS_TIMEOUT. We need to remove it for better calculations. - if (last_derived_time_delta_applied == 0) { - last_derived_time_delta_applied = 1; - last_derived_time_timestamp -= GPS_MS_TIMEOUT; - } - - uint64_t new_timestamp = timestamp_now(); - - while (new_timestamp - last_derived_time_timestamp > 1000) { - last_derived_time.tm_sec += 1; - last_derived_time_timestamp += 1000; - } - - mktime(&last_derived_time); - - *time = last_derived_time; - - return 1; - -} - - -void common_init(void) -{ - common_timer = xTimerCreate("Timer", - pdMS_TO_TICKS(8), - pdTRUE, // Auto-reload - NULL, // No unique id - common_increase_timestamp - ); - - xTimerStart(common_timer, 0); - - lfsr = lfsr_start_state; -} - -static void common_increase_timestamp(TimerHandle_t __attribute__ ((unused))t) -{ - -#ifdef SIMULATOR - struct timespec ctime; - - clock_gettime(CLOCK_REALTIME, &ctime); - common_timestamp = ctime.tv_sec * 1000 + ctime.tv_nsec / 1.0e6 - timestamp_delta; - - if (timestamp_delta == 0) { - timestamp_delta = common_timestamp; - common_timestamp = 0; - } - -#else - common_timestamp += 8; -#endif -} - -uint64_t timestamp_now(void) -{ - return common_timestamp; // ms -} - - -// Return either 0 or 1, somewhat randomly -int random_bool(void) -{ - uint16_t bit; - - /* taps: 16 14 13 11; feedback polynomial: x^16 + x^14 + x^13 + x^11 + 1 */ - bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1; - lfsr = (lfsr >> 1) | (bit << 15); - - return bit; -} - -// For the debugger -static int faultsource = 0; -void trigger_fault(int source) -{ - usart_debug("Fatal: %d\r\n\r\n", source); - - __disable_irq(); - - faultsource = source; - - while (1) {} -} - -float round_float_to_half_steps(float value) -{ - return 0.5f * roundf(value * 2.0f); -} - diff --git a/src/common/src/Core/fsm.c b/src/common/src/Core/fsm.c deleted file mode 100644 index 9fe2c4c..0000000 --- a/src/common/src/Core/fsm.c +++ /dev/null @@ -1,679 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Matthias P. Braendli, Maximilien Cuony - * - * 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 -#include -#include -#include "Core/common.h" -#include "Core/fsm.h" -#include "GPIO/usart.h" -#include "GPIO/temperature.h" -#include "GPIO/analog.h" - -static struct fsm_input_signals_t fsm_in; -static struct fsm_output_signals_t fsm_out; - -static fsm_state_t current_state; -static balise_fsm_state_t balise_state; -static sstv_fsm_state_t sstv_state; - -// Keep track of when we last entered a given state, measured -// in ms using the timestamp_now() function -static uint64_t timestamp_state[_NUM_FSM_STATES]; - -static int last_supply_voltage_decivolts = 0; - -#define CW_MESSAGE_BALISE_LEN 64 -static char cw_message_balise[CW_MESSAGE_BALISE_LEN]; - - -// Each 20 minutes, send a SHORT_BEACON -#define SHORT_BEACON_MAX (60 * 20) -// Reset the counter if the QSO was 10m too long -#define SHORT_BEACON_RESET_IF_QSO (60 * 10) - -/* At least 1 second predelay for CW, ensures the receivers had enough time - * time to open their squelch before the first letter gets transmitted - */ -#define CW_PREDELAY " " - -// Some time to ensure we don't cut off the last letter -#define CW_POSTDELAY " " - -// The counter (up to 20 minutes) for the short balise -static int short_beacon_counter_s = 0; -static uint64_t short_beacon_counter_last_update = 0; - -// The last start of the last qso -static uint64_t last_qso_start_timestamp = 0; - -// Information from which we can calculate the QSO duration -static struct { - int qso_occurred; - uint64_t qso_start_time; -} qso_info; - -void fsm_init() { - memset(&fsm_in, 0, sizeof(fsm_in)); - memset(&fsm_out, 0, sizeof(fsm_out)); - - memset(timestamp_state, 0, _NUM_FSM_STATES * sizeof(*timestamp_state)); - timestamp_state[FSM_OISIF] = timestamp_now(); - - current_state = FSM_OISIF; - balise_state = BALISE_FSM_EVEN_HOUR; - sstv_state = SSTV_FSM_OFF; - - qso_info.qso_occurred = 0; - qso_info.qso_start_time = timestamp_now(); -} - -// Calculate the time spent in the current state -static uint64_t fsm_current_state_time_ms(void) { - return timestamp_now() - timestamp_state[current_state]; -} - -static uint64_t fsm_current_state_time_s(void) { - return fsm_current_state_time_ms() / 1000; -} - -static const char* state_name(fsm_state_t state) { - switch (state) { - case FSM_OISIF: return "FSM_OISIF"; - case FSM_OPEN1: return "FSM_OPEN1"; - case FSM_OPEN2: return "FSM_OPEN2"; - case FSM_LETTRE: return "FSM_LETTRE"; - case FSM_ECOUTE: return "FSM_ECOUTE"; - case FSM_ATTENTE: return "FSM_ATTENTE"; - case FSM_QSO: return "FSM_QSO"; - case FSM_ANTI_BAVARD: return "FSM_ANTI_BAVARD"; - case FSM_BLOQUE: return "FSM_BLOQUE"; - case FSM_TEXTE_73: return "FSM_TEXTE_73"; - case FSM_TEXTE_HB9G: return "FSM_TEXTE_HB9G"; - case FSM_TEXTE_LONG: return "FSM_TEXTE_LONG"; - case FSM_BALISE_LONGUE: return "FSM_BALISE_LONGUE"; - case FSM_BALISE_SPECIALE: return "FSM_BALISE_SPECIALE"; - case FSM_BALISE_COURTE: return "FSM_BALISE_COURTE"; - case FSM_BALISE_COURTE_OPEN: return "FSM_BALISE_COURTE_OPEN"; - default: return "ERROR!"; - } -} - -static const char* balise_state_name(balise_fsm_state_t state) { - switch (state) { - case BALISE_FSM_EVEN_HOUR: return "BALISE_FSM_EVEN_HOUR"; - case BALISE_FSM_ODD_HOUR: return "BALISE_FSM_ODD_HOUR"; - case BALISE_FSM_PENDING: return "BALISE_FSM_PENDING"; - default: return "ERROR!"; - } -} - -static const char* sstv_state_name(sstv_fsm_state_t state) { - switch (state) { - case SSTV_FSM_OFF: return "SSTV_FSM_OFF"; - case SSTV_FSM_ON: return "SSTV_FSM_ON"; - default: return "ERROR!"; - } -} - -static fsm_state_t select_grande_balise(void) { - if (fsm_in.qrp || fsm_in.swr_high) { - return FSM_BALISE_SPECIALE; - } - else { - return FSM_BALISE_LONGUE; - } -} - -static uint64_t qso_duration(void) { - return timestamp_state[current_state] - qso_info.qso_start_time; -} - -// Between turns in a QSO, the repeater sends a letter in CW, -// different messages are possible. They are sorted here from -// low to high priority. -const char* letter_all_ok = "K"; -const char* letter_sstv = "S"; -const char* letter_qrp = "G"; -const char* letter_freq_high = "U"; -const char* letter_freq_low = "D"; -const char* letter_swr_high = "R"; - -static const char* fsm_select_letter(void) { - if (fsm_in.swr_high) { - return letter_swr_high; - } - else if (fsm_in.discrim_d) { - return letter_freq_low; - } - else if (fsm_in.discrim_u) { - return letter_freq_high; - } - else if (fsm_in.qrp) { - return letter_qrp; - } - else if (sstv_state == SSTV_FSM_ON) { - return letter_sstv; - } - - return letter_all_ok; -} - - -void fsm_update() { - - fsm_state_t next_state = current_state; - - // Some defaults for the outgoing signals - fsm_out.tx_on = 0; - fsm_out.modulation = 0; - fsm_out.cw_psk31_trigger = 0; - fsm_out.cw_dit_duration = 50; - fsm_out.msg_frequency = 960; - fsm_out.require_tone_detector = 0; - // other output signals keep their value - - switch (current_state) { - case FSM_OISIF: - // Check the length of the last QSO, and reset the SHORT_BEACON counter if needed - if (last_qso_start_timestamp != 0) { - - if ((timestamp_now() - last_qso_start_timestamp) > 1000 * SHORT_BEACON_RESET_IF_QSO) { - short_beacon_counter_s = 0; - } - - last_qso_start_timestamp = 0; - } - - // Increment the SHORT_BEACON counter based on time spent in the state - while(short_beacon_counter_s < SHORT_BEACON_MAX && (fsm_current_state_time_s() - short_beacon_counter_last_update > 1)) { - short_beacon_counter_last_update++; - short_beacon_counter_s++; - } - - // SQ and button 1750 are debounced inside pio.c (300ms) - fsm_out.require_tone_detector = fsm_in.sq; - - if ( (fsm_in.sq && fsm_in.det_1750) || - (fsm_in.sq && sstv_state == SSTV_FSM_ON) || - (fsm_in.button_1750)) { - next_state = FSM_OPEN1; - } - else if (balise_state == BALISE_FSM_PENDING) { - short_beacon_counter_s = 0; - next_state = select_grande_balise(); - } - else if (!fsm_in.qrp && short_beacon_counter_s == SHORT_BEACON_MAX) { - short_beacon_counter_s = 0; - next_state = FSM_BALISE_COURTE; - } - - break; - - case FSM_OPEN1: - /* Do not enable TX_ON here, otherwise we could get stuck transmitting - * forever if SQ never goes low. - */ - fsm_out.require_tone_detector = 1; - if (!fsm_in.sq && !fsm_in.det_1750) { - next_state = FSM_OPEN2; - } - break; - - case FSM_OPEN2: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - qso_info.qso_occurred = 0; - qso_info.qso_start_time = timestamp_now(); - - if (fsm_current_state_time_ms() > 200) { - next_state = FSM_LETTRE; - } - break; - - case FSM_LETTRE: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - fsm_out.msg = fsm_select_letter(); - if (fsm_out.msg[0] == 'G') { - // The letter 'G' is a bit different - fsm_out.msg_frequency = 696; - } - fsm_out.cw_psk31_trigger = 1; - - if (fsm_in.cw_psk31_done) { - next_state = FSM_ECOUTE; - } - break; - - case FSM_ECOUTE: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - - /* Time checks: - * We need to check the total TX_ON duration to decide the text to - * send. This is the QSO duration. - * - * We also need to check if we actually entered the QSO state - * recently, otherwise we want to go to ATTENTE. That's why the - * additional field qso_occurred is required. - */ - - if (fsm_in.sq) { - next_state = FSM_QSO; - } - else { - if (fsm_current_state_time_s() > 5) { - if (balise_state == BALISE_FSM_PENDING) { - short_beacon_counter_s = 0; - next_state = select_grande_balise(); - } - else if (qso_info.qso_occurred) { - if (qso_duration() >= 1000ul * 15 * 60) { - next_state = FSM_TEXTE_LONG; - } - else if (qso_duration() >= 1000ul * 10 * 60) { - next_state = FSM_TEXTE_HB9G; - } - else if (qso_duration() >= 1000ul * 5 * 60) { - next_state = FSM_TEXTE_73; - } - else { - next_state = FSM_OISIF; - } - } - } - - if (fsm_current_state_time_s() > 6 && !qso_info.qso_occurred) { - next_state = FSM_ATTENTE; - } - - /* If everything fails and the state was not changed after 7 - * seconds, fall back to oisif - */ - if (fsm_current_state_time_s() > 7) { - next_state = FSM_OISIF; - } - } - break; - - case FSM_ATTENTE: - if (fsm_in.sq) { - fsm_out.require_tone_detector = 1; - next_state = FSM_ECOUTE; - } - else if (fsm_current_state_time_s() > 15) { - next_state = FSM_OISIF; - } - break; - - case FSM_QSO: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - qso_info.qso_occurred = 1; - - // Save the starting timestamp, if there is none - if (last_qso_start_timestamp == 0) { - last_qso_start_timestamp = timestamp_now(); - } - - if (!fsm_in.sq && fsm_current_state_time_s() < 3) { - /* To avoid that very short open squelch triggers - * transmit CW letters all the time. Some people - * enjoy doing that. - */ - next_state = FSM_ECOUTE; - } - else if (!fsm_in.sq && fsm_current_state_time_s() >= 3) { - next_state = FSM_LETTRE; - } - else if (fsm_current_state_time_s() > 5 * 60) { - next_state = FSM_ANTI_BAVARD; - } - break; - - case FSM_ANTI_BAVARD: - fsm_out.tx_on = 1; - // No modulation! - - // Short post-delay to underscore the fact that - // transmission was forcefully cut off. - fsm_out.msg = " HI HI "; - fsm_out.cw_psk31_trigger = 1; - - if (fsm_in.cw_psk31_done) { - next_state = FSM_BLOQUE; - } - break; - - case FSM_BLOQUE: - if (fsm_current_state_time_s() > 10) { - next_state = FSM_OISIF; - } - break; - - case FSM_TEXTE_73: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - fsm_out.msg_frequency = 696; - fsm_out.cw_dit_duration = 70; - fsm_out.msg = " 73" CW_POSTDELAY; - fsm_out.cw_psk31_trigger = 1; - - if (fsm_in.sq) { - next_state = FSM_QSO; - qso_info.qso_start_time = timestamp_now(); - } - else if (fsm_in.cw_psk31_done) { - next_state = FSM_OISIF; - } - break; - - case FSM_TEXTE_HB9G: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - fsm_out.msg_frequency = 696; - fsm_out.cw_dit_duration = 70; - // No need for CW_PREDELAY, since we are already transmitting - fsm_out.msg = " HB9G" CW_POSTDELAY; - fsm_out.cw_psk31_trigger = 1; - - if (fsm_in.sq) { - next_state = FSM_QSO; - qso_info.qso_start_time = timestamp_now(); - } - else if (fsm_in.cw_psk31_done) { - next_state = FSM_OISIF; - } - break; - - case FSM_TEXTE_LONG: - fsm_out.tx_on = 1; - fsm_out.modulation = 1; - fsm_out.require_tone_detector = 1; - - fsm_out.msg_frequency = 696; - fsm_out.cw_dit_duration = 70; - - // No need for CW_PREDELAY, since we are already transmitting - if (random_bool()) { - fsm_out.msg = " HB9G 1628M" CW_POSTDELAY; - } - else { - fsm_out.msg = " HB9G JN36BK" CW_POSTDELAY; - } - fsm_out.cw_psk31_trigger = 1; - - if (fsm_in.sq) { - next_state = FSM_QSO; - qso_info.qso_start_time = timestamp_now(); - } - else if (fsm_in.cw_psk31_done) { - next_state = FSM_OISIF; - } - break; - - case FSM_BALISE_LONGUE: - fsm_out.tx_on = 1; - fsm_out.msg_frequency = 588; - fsm_out.cw_dit_duration = 110; - - { - const float supply_voltage = round_float_to_half_steps(analog_measure_12v()); - const int supply_decivolts = supply_voltage * 10.0f; - - char *eol_info = "73"; - if (!fsm_in.wind_generator_ok) { - eol_info = "\\"; - // The backslash is the SK digraph - } - - char supply_trend = '='; - // = means same voltage as previous - // + means higher - // - means lower - if (last_supply_voltage_decivolts < supply_decivolts) { - supply_trend = '+'; - } - else if (last_supply_voltage_decivolts > supply_decivolts) { - supply_trend = '-'; - } - - if (temperature_valid()) { - snprintf(cw_message_balise, CW_MESSAGE_BALISE_LEN-1, - CW_PREDELAY "HB9G JN36BK 1628M U %dV%01d %c T %d %s" CW_POSTDELAY, - supply_decivolts / 10, - supply_decivolts % 10, - supply_trend, - (int)(round_float_to_half_steps(temperature_get())), - eol_info); - } - else { - snprintf(cw_message_balise, CW_MESSAGE_BALISE_LEN-1, - CW_PREDELAY "HB9G JN36BK 1628M U %dV%01d %c %s" CW_POSTDELAY, - supply_decivolts / 10, - supply_decivolts % 10, - supply_trend, - eol_info); - } - - fsm_out.msg = cw_message_balise; - - last_supply_voltage_decivolts = supply_decivolts; - - fsm_out.cw_psk31_trigger = 1; - } - - if (fsm_in.cw_psk31_done) { - next_state = FSM_OISIF; - } - break; - - case FSM_BALISE_SPECIALE: - fsm_out.tx_on = 1; - fsm_out.msg_frequency = 696; - fsm_out.cw_dit_duration = 70; - - { - const float supply_voltage = round_float_to_half_steps(analog_measure_12v()); - const int supply_decivolts = supply_voltage * 10.0f; - - char *eol_info = "73"; - if (!fsm_in.wind_generator_ok) { - eol_info = "\\"; - // The backslash is the SK digraph - } - - snprintf(cw_message_balise, CW_MESSAGE_BALISE_LEN-1, - CW_PREDELAY "HB9G U %dV%01d %s" CW_POSTDELAY, - supply_decivolts / 10, - supply_decivolts % 10, - eol_info); - - fsm_out.msg = cw_message_balise; - - fsm_out.cw_psk31_trigger = 1; - } - - if (fsm_in.cw_psk31_done) { - next_state = FSM_OISIF; - } - break; - - case FSM_BALISE_COURTE: - case FSM_BALISE_COURTE_OPEN: - - fsm_out.tx_on = 1; - - fsm_out.msg_frequency = 696; - fsm_out.cw_dit_duration = 70; - - { - int rand = random_bool() * 2 + random_bool(); - - if (rand == 0) { - fsm_out.msg = CW_PREDELAY "HB9G" CW_POSTDELAY; - } - else if (rand == 1) { - fsm_out.msg = CW_PREDELAY "HB9G JN36BK" CW_POSTDELAY; - } - else if (rand == 2) { - fsm_out.msg = CW_PREDELAY "HB9G 1628M" CW_POSTDELAY; - } - else { - fsm_out.msg = CW_PREDELAY "HB9G JN36BK 1628M" CW_POSTDELAY; - } - } - fsm_out.cw_psk31_trigger = 1; - - if (current_state == FSM_BALISE_COURTE) { - if (fsm_in.cw_psk31_done) { - if (fsm_in.sq) { - next_state = FSM_OPEN2; - } - else { - next_state = FSM_OISIF; - } - } - else if (fsm_in.sq) { - next_state = FSM_BALISE_COURTE_OPEN; - } - } - else { //FSM_BALISE_COURTE_OPEN - if (fsm_in.cw_psk31_done) { - next_state = FSM_OPEN2; - } - } - - break; - default: - // Should never happen - next_state = FSM_OISIF; - break; - } - - - if (next_state != current_state) { - timestamp_state[next_state] = timestamp_now(); - - short_beacon_counter_last_update = 0; - - fsm_state_switched(state_name(next_state)); - } - current_state = next_state; -} - -void fsm_update_inputs(struct fsm_input_signals_t* inputs) -{ - fsm_in = *inputs; -} - -void fsm_get_outputs(struct fsm_output_signals_t* out) -{ - *out = fsm_out; -} - -void fsm_balise_force() { - balise_state = BALISE_FSM_PENDING; -} - -void fsm_balise_update() { - - balise_fsm_state_t next_state = balise_state; - - switch (balise_state) { - case BALISE_FSM_EVEN_HOUR: - if (fsm_in.hour_is_even == 0) { - next_state = BALISE_FSM_ODD_HOUR; - } - break; - case BALISE_FSM_ODD_HOUR: - if (fsm_in.hour_is_even == 1) { - if (timestamp_now() > 1000 * 60) { // Does not start the balise at startup - next_state = BALISE_FSM_PENDING; - } - else { - next_state = BALISE_FSM_EVEN_HOUR; - } - } - break; - case BALISE_FSM_PENDING: - if (current_state == FSM_BALISE_SPECIALE || - current_state == FSM_BALISE_LONGUE) { - next_state = BALISE_FSM_EVEN_HOUR; - } - break; - default: - // Should never happen - next_state = BALISE_FSM_EVEN_HOUR; - break; - } - - if (next_state != balise_state) { - fsm_state_switched(balise_state_name(next_state)); - } - - balise_state = next_state; -} - -int fsm_sstv_update() { - - sstv_fsm_state_t next_state = sstv_state; - - switch (sstv_state) { - case SSTV_FSM_OFF: - if (fsm_in.sq && fsm_in.fax_mode) { - next_state = SSTV_FSM_ON; - } - break; - case SSTV_FSM_ON: - if (current_state == FSM_BALISE_LONGUE || - current_state == FSM_ANTI_BAVARD || - current_state == FSM_BALISE_SPECIALE || - fsm_in.long_1750 - ) { - next_state = SSTV_FSM_OFF; - } - break; - - default: - // Should never happen - next_state = SSTV_FSM_OFF; - break; - } - - if (next_state != sstv_state) { - fsm_state_switched(sstv_state_name(next_state)); - } - - sstv_state = next_state; - - return sstv_state == SSTV_FSM_ON; -} diff --git a/src/common/src/Core/main.c b/src/common/src/Core/main.c deleted file mode 100644 index 07657fa..0000000 --- a/src/common/src/Core/main.c +++ /dev/null @@ -1,710 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Matthias P. Braendli, Maximilien Cuony - * - * 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 -#include -#include -#include - -/* Kernel includes. */ -#include "FreeRTOS.h" -#include "task.h" -#include "timers.h" -#include "semphr.h" - -/* Includes */ -#include "Audio/audio.h" -#include "Audio/audio_in.h" -#include "Audio/tone.h" -#include "Audio/cw.h" -#include "GPIO/pio.h" -#include "GPIO/i2c.h" -#include "GPS/gps.h" -#include "Core/fsm.h" -#include "Core/common.h" -#include "GPIO/usart.h" -#include "Core/delay.h" -#include "GPIO/temperature.h" -#include "GPIO/leds.h" -#include "GPIO/analog.h" -#include "vc.h" - -#ifdef SIMULATOR -extern int gui_in_tone_1750; -#endif - -static void print_task_stats(void); - -static int tm_trigger_button = 0; - -static struct fsm_input_signals_t fsm_input; -static int hour_is_even = 0; - -/* Threshold for SWR measurement */ -const int swr_refl_threshold = 10; // mV - -/* Saturating counter for SWR measurement - * - * Hysteresis: - * If the counter reaches the max value, the - * SWR Error is triggered. - * - * The error can only be cleared through - * a full reset. - */ -#define SWR_ERROR_COUNTER_MAX 10 -static int swr_error_counter = 0; -static int swr_error_flag = 0; - -// Platform specific init function -void init(void); - -// Tasks -static void detect_button_press(void *pvParameters); -static void exercise_fsm(void *pvParameters); -static void nf_analyse(void *pvParameters); -static void gps_monit_task(void *pvParameters); -static void launcher_task(void *pvParameters); - -// Audio callback function -static void audio_callback(void* context, int select_buffer); -// Debugging -static uint64_t timestamp_last_audio_callback = 0; - -void vApplicationStackOverflowHook(TaskHandle_t, signed char *); - -void vApplicationStackOverflowHook(TaskHandle_t __attribute__ ((unused)) xTask, signed char *pcTaskName) { - usart_debug("TASK OVERFLOW %s\r\n", pcTaskName); - trigger_fault(FAULT_SOURCE_TASK_OVERFLOW); -} - -int main(void) { - init(); - delay_init(); - usart_init(); - usart_debug("\r\n******* glutt-o-matique version %s *******\r\n", vc_get_version()); - -#ifndef SIMULATOR - - if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) - { - usart_debug_puts("WARNING: A IWDG Reset occured!\r\n"); - } - RCC_ClearFlag(); - -#endif - - TaskHandle_t task_handle; - xTaskCreate( - launcher_task, - "Launcher", - 2*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 2UL, - &task_handle); - - if (!task_handle) { - trigger_fault(FAULT_SOURCE_MAIN); - } - - vTaskStartScheduler(); - - /* HALT */ - while(1); -} - - -// Launcher task is here to make sure the scheduler is -// already running when calling the init functions. -static void launcher_task(void __attribute__ ((unused))*pvParameters) -{ - usart_debug_puts("CW init\r\n"); - cw_psk31_init(16000); - - usart_debug_puts("PIO init\r\n"); - pio_init(); - - usart_debug_puts("Analog init\r\n"); - analog_init(); - - usart_debug_puts("I2C init\r\n"); - i2c_init(); - - usart_debug_puts("common init\r\n"); - common_init(); - - usart_debug_puts("GPS init\r\n"); - gps_init(); - - usart_debug_puts("DS18B20 init\r\n"); - temperature_init(); - - usart_debug_puts("TaskButton init\r\n"); - - TaskHandle_t task_handle; - xTaskCreate( - detect_button_press, - "TaskButton", - 4*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 2UL, - &task_handle); - - if (!task_handle) { - trigger_fault(FAULT_SOURCE_MAIN); - } - - usart_debug_puts("TaskFSM init\r\n"); - - xTaskCreate( - exercise_fsm, - "TaskFSM", - 4*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 2UL, - &task_handle); - - if (!task_handle) { - trigger_fault(FAULT_SOURCE_MAIN); - } - - usart_debug_puts("TaskGPS init\r\n"); - - xTaskCreate( - gps_monit_task, - "TaskGPSMonit", - 4*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 2UL, - &task_handle); - - if (!task_handle) { - trigger_fault(FAULT_SOURCE_MAIN); - } - - usart_debug_puts("Audio init\r\n"); - audio_initialize(Audio16000HzSettings); - - usart_debug_puts("Audio set volume\r\n"); - audio_set_volume(210); - - usart_debug_puts("Audio set callback\r\n"); - audio_play_with_callback(audio_callback, NULL); - - usart_debug_puts("Tone init\r\n"); - tone_init(); - - usart_debug_puts("Audio in init\r\n"); - audio_in_initialize(); - - usart_debug_puts("TaskNF init\r\n"); - - xTaskCreate( - nf_analyse, - "TaskNF", - 3*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 3UL, - &task_handle); - - if (!task_handle) { - trigger_fault(FAULT_SOURCE_MAIN); - } - - usart_debug_puts("Init done.\r\n"); - - int last_qrp_from_supply = 0; - int send_audio_callback_warning = 0; - - int i = 0; - - while(1) { - vTaskDelay(pdMS_TO_TICKS(1000)); - - if (i == 0) { - i = 1; - leds_turn_on(LED_GREEN); - - print_task_stats(); - } - else { - i = 0; - leds_turn_off(LED_GREEN); - } - - struct fsm_output_signals_t fsm_out; - fsm_get_outputs(&fsm_out); - - tone_detector_enable(fsm_out.require_tone_detector); - - if (fsm_out.tx_on) { - int swr_fwd_mv, swr_refl_mv; - if (analog_measure_swr(&swr_fwd_mv, &swr_refl_mv)) { - if (swr_refl_mv > swr_refl_threshold) { - usart_debug("SWR meas %d mV\r\n", swr_refl_mv); - swr_error_counter++; - } - else { - swr_error_counter--; - } - - if (swr_error_counter > SWR_ERROR_COUNTER_MAX) { - swr_error_counter = SWR_ERROR_COUNTER_MAX; - if (!swr_error_flag) { - usart_debug("Set SWR error\r\n"); - } - swr_error_flag = 1; - } - } - } - else { - const int qrp_from_supply = analog_supply_too_low(); - if (swr_error_flag) { - pio_set_qrp(1); - } - else if (qrp_from_supply != last_qrp_from_supply) { - usart_debug("QRP = %d\r\n", qrp_from_supply); - last_qrp_from_supply = qrp_from_supply; - - pio_set_qrp(qrp_from_supply); - } - } - - if (timestamp_now() - timestamp_last_audio_callback > 1000) { - if (send_audio_callback_warning == 0) { - send_audio_callback_warning = 1; - usart_debug("[HOHO] timestamp_last_audio_callback > 1000\r\n"); - } - } - else { - if (send_audio_callback_warning == 1) { - send_audio_callback_warning = 0; - usart_debug("[HOHO] Fix ? Now timestamp_last_audio_callback < 1000\r\n"); - } - } - } -} - - -static void detect_button_press(void __attribute__ ((unused))*pvParameters) -{ - int pin_high_count = 0; - int last_pin_high_count = 0; - const int pin_high_thresh = 10; - while (1) { - if (pio_read_button()) { - if (pin_high_count < pin_high_thresh) { - pin_high_count++; - } - } - else { - if (pin_high_count > 0) { - pin_high_count--; - } - } - - vTaskDelay(pdMS_TO_TICKS(10)); /* Debounce Delay */ - - if (pin_high_count == pin_high_thresh && - last_pin_high_count != pin_high_count) { - tm_trigger_button = 1; - usart_debug_puts("Bouton bleu\r\n"); - } - else if (pin_high_count == 0 && - last_pin_high_count != pin_high_count) { - tm_trigger_button = 0; - } - - last_pin_high_count = pin_high_count; - } -} - -int only_zero_in_audio_buffer = 1; -int count_zero_audio_buffer = 0; - -static void audio_callback(void __attribute__ ((unused))*context, int select_buffer) { - static int16_t audio_buffer0[AUDIO_BUF_LEN]; - static int16_t audio_buffer1[AUDIO_BUF_LEN]; - int16_t *samples; - - if (select_buffer == 0) { - samples = audio_buffer0; - leds_turn_off(LED_RED); - } else { - samples = audio_buffer1; - leds_turn_on(LED_RED); - } - - size_t samples_len = cw_psk31_fill_buffer(samples, AUDIO_BUF_LEN); - - if (samples_len == 0) { - for (int i = 0; i < AUDIO_BUF_LEN; i++) { - samples[i] = 0; - } - - samples_len = AUDIO_BUF_LEN; - - if (count_zero_audio_buffer < 2) { - count_zero_audio_buffer++; - } else { - only_zero_in_audio_buffer = 1; - } - } else { - only_zero_in_audio_buffer = 0; - count_zero_audio_buffer = 0; - } - - if (!audio_provide_buffer_without_blocking(samples, samples_len)) { - usart_debug("[HOHO] audio_provide_buffer_without_blocking returned False.\r\n"); - } - - timestamp_last_audio_callback = timestamp_now(); - -} - -static struct tm gps_time; -static void gps_monit_task(void __attribute__ ((unused))*pvParameters) { - - /* There are two types of non GPS clocks: the DERIVED one which works if - * GPS time was known at some point, and the free-running that only depends - * on timestamp_now(). The free-running one is used to ensure 2h beacons are - * transmitted even if GPS never gave us time. The DERIVED kicks in when GPS - * fails after having output time information and tries to keep accurate absolute - * time. - */ - - pio_set_gps_epps(1); - - int t_gps_print_latch = 0; - int t_gps_hours_handeled = 0; - uint64_t last_hour_timestamp = 0; - - uint64_t last_volt_and_temp_timestamp, last_hour_is_even_change_timestamp; - last_volt_and_temp_timestamp = last_hour_is_even_change_timestamp = timestamp_now(); - - int last_even = -1; - - while (1) { - const uint64_t now = timestamp_now(); - - if (last_volt_and_temp_timestamp + 20000 < now) { - usart_debug("ALIM %d mV\r\n", (int)roundf(1000.0f * analog_measure_12v())); - - const float temp = temperature_get(); - usart_debug("TEMP %d.%02d\r\n", (int)temp, (int)(temp * 100.0f - (int)(temp) * 100.0f)); - - last_volt_and_temp_timestamp = now; - } - - struct tm time = {0}; - int time_valid = local_time(&time); - int derived_mode = 0; - - if (time_valid) { - if (time.tm_sec % 2) { - pio_set_gps_epps(1); - } - else { - pio_set_gps_epps(0); - } - - derived_mode = 0; - } - else { - time_valid = local_derived_time(&time); - - if (time_valid) { - if (time.tm_sec % 4 >= 2) { - pio_set_gps_epps(1); - } - else { - pio_set_gps_epps(0); - } - - derived_mode = 1; - } - } - - if (time_valid) { - hour_is_even = (time.tm_hour + 1) % 2; - - if (last_even != hour_is_even) { - last_even = hour_is_even; - - usart_debug("Even changed: %i %i %s\r\n", hour_is_even, time.tm_hour, derived_mode ? "DERIVED" : "GPS"); - } - } - else if (last_hour_is_even_change_timestamp + (2 * 3600 * 1000) < now) { - hour_is_even = (hour_is_even + 1) % 2; - last_even = hour_is_even; - - usart_debug("Even changed: %i %i FREE-RUNNING\r\n", hour_is_even, time.tm_hour); - last_hour_is_even_change_timestamp = now; - } - - int num_sv_used = 0; - gps_utctime(&gps_time, &num_sv_used); - - if (time.tm_sec % 30 == 0 && t_gps_print_latch == 0) { - usart_debug("T_GPS %04d-%02d-%02d %02d:%02d:%02d %d SV tracked\r\n", - gps_time.tm_year + 1900, gps_time.tm_mon + 1, gps_time.tm_mday, - gps_time.tm_hour, gps_time.tm_min, gps_time.tm_sec, - num_sv_used); - - char *mode = ""; - - if (derived_mode) { - mode = "Derived"; - } - else { - mode = "GPS"; - } - - usart_debug("TIME %04d-%02d-%02d %02d:%02d:%02d [%s]\r\n", - time.tm_year + 1900, - time.tm_mon + 1, time.tm_mday, - time.tm_hour, time.tm_min, time.tm_sec, - mode); - - t_gps_print_latch = 1; - } - - if (time.tm_sec % 30 > 0) { - t_gps_print_latch = 0; - } - - if (time_valid && derived_mode == 0 && gps_time.tm_sec == 0 && gps_time.tm_min == 0 && t_gps_hours_handeled == 0) { - if (last_hour_timestamp == 0) { - usart_debug("DERIV INIT TS=%lld\r\n", now); - } - else { - usart_debug("DERIV TS=%lld Excepted=%lld Delta=%lld\r\n", - now, - last_hour_timestamp + 3600000, - last_hour_timestamp + 3600000 - now - ); - } - - last_hour_timestamp = now; - - t_gps_hours_handeled = 1; - } - - if (gps_time.tm_sec != 0) { - t_gps_hours_handeled = 0; - } - - vTaskDelay(pdMS_TO_TICKS(100)); - - // Reload watchdog -#ifndef SIMULATOR - IWDG_ReloadCounter(); -#endif - } -} - -static void exercise_fsm(void __attribute__ ((unused))*pvParameters) -{ - fsm_init(); - - int cw_last_trigger = 0; - int last_tm_trigger_button = 0; - - int last_sq = 0; - int last_qrp = 0; - int last_cw_done = 0; - int last_discrim_d = 0; - int last_discrim_u = 0; - int last_wind_generator_ok = 0; - - fsm_input.humidity = 0; - fsm_input.temp = 15; - fsm_input.swr_high = 0; - fsm_input.fax_mode = 0; - fsm_input.wind_generator_ok = 1; - - while (1) { - vTaskDelay(pdMS_TO_TICKS(10)); - - pio_set_fsm_signals(&fsm_input); - - if (last_sq != fsm_input.sq) { - last_sq = fsm_input.sq; - usart_debug("In SQ %d\r\n", last_sq); - } - if (last_qrp != fsm_input.qrp) { - last_qrp = fsm_input.qrp; - usart_debug("In QRP %d\r\n", last_qrp); - } - if (last_discrim_d != fsm_input.discrim_d) { - last_discrim_d = fsm_input.discrim_d; - usart_debug("In D %d\r\n", last_discrim_d); - } - if (last_discrim_u != fsm_input.discrim_u) { - last_discrim_u = fsm_input.discrim_u; - usart_debug("In U %d\r\n", last_discrim_u); - } - if (last_wind_generator_ok != fsm_input.wind_generator_ok) { - last_wind_generator_ok = fsm_input.wind_generator_ok; - usart_debug("In eolienne %s\r\n", last_wind_generator_ok ? "vent" : "replie"); - } - - if (tm_trigger_button == 1 && last_tm_trigger_button == 0) { - fsm_balise_force(); - } - last_tm_trigger_button = tm_trigger_button; - - const int cw_psk31_done = !cw_psk31_busy(); - const int cw_done = cw_psk31_done && only_zero_in_audio_buffer; - - // Set the done flag to 1 only once, when cw_done switches from 0 to 1 - if (last_cw_done != cw_done) { - usart_debug("In cw_done change %d %d\r\n", cw_done, only_zero_in_audio_buffer); - - if (cw_done) { - fsm_input.cw_psk31_done = cw_done; - leds_turn_off(LED_ORANGE); - } - - last_cw_done = cw_done; - } - else { - fsm_input.cw_psk31_done = 0; - } - - - const int current_tone_1750_status = tone_1750_status(); -#ifdef SIMULATOR - gui_in_tone_1750 = current_tone_1750_status; -#endif - fsm_input.det_1750 = current_tone_1750_status; - pio_set_det_1750(current_tone_1750_status); - - fsm_input.long_1750 = tone_1750_for_5_seconds(); - - // TODO implement a DTMF controlled state machine for setting SQ2 - pio_set_sq2(0); - - fsm_input.fax_mode = tone_fax_status(); - fsm_input.swr_high = swr_error_flag; - fsm_input.hour_is_even = hour_is_even; - - fsm_update_inputs(&fsm_input); - fsm_update(); - fsm_balise_update(); - const int disable_1750_filter = fsm_sstv_update(); - pio_set_fax(disable_1750_filter); - - struct fsm_output_signals_t fsm_out; - fsm_get_outputs(&fsm_out); - - pio_set_tx(fsm_out.tx_on); - pio_set_mod_off(!fsm_out.modulation); - - // Add message to CW generator only on rising edge of trigger - if (fsm_out.cw_psk31_trigger && !cw_last_trigger) { - cw_psk31_push_message(fsm_out.msg, fsm_out.cw_dit_duration, fsm_out.msg_frequency); - - leds_turn_on(LED_ORANGE); - } - cw_last_trigger = fsm_out.cw_psk31_trigger; - - } -} - -const int BLUE_LED_INTVL = 4; -static int blue_led_phase = 0; -static void nf_analyse(void __attribute__ ((unused))*pvParameters) -{ - while (1) { - if (blue_led_phase == 0) { - leds_turn_on(LED_BLUE); - } - else if (blue_led_phase == BLUE_LED_INTVL) { - leds_turn_off(LED_BLUE); - } - - blue_led_phase++; - - if (blue_led_phase >= BLUE_LED_INTVL * 2) { - blue_led_phase = 0; - } - - tone_do_analysis(); - } -} - -#if configGENERATE_RUN_TIME_STATS -#include "stm32f4xx_conf.h" -#include "stm32f4xx_tim.h" - -void vConfigureTimerForRunTimeStats() -{ - TIM_TimeBaseInitTypeDef SetupTimer; - /* Enable timer 2, using the Reset and Clock Control register */ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); - SetupTimer.TIM_Prescaler = 0x0000; - SetupTimer.TIM_CounterMode = TIM_CounterMode_Up; - SetupTimer.TIM_Period = 0xFFFFFFFF; - SetupTimer.TIM_ClockDivision = TIM_CKD_DIV1; - TIM_TimeBaseInit(TIM2, &SetupTimer); - TIM_Cmd(TIM2, ENABLE); /* start counting by enabling CEN in CR1 */ -} - -unsigned long vGetTimerForRunTimeStats( void ) { - return TIM_GetCounter(TIM2); -} - -static TaskStatus_t taskstats[12]; -static void print_task_stats(void) { - uint32_t total_time; - int n_tasks = uxTaskGetSystemState(taskstats, 12, &total_time); - total_time /= 100UL; - for (int t = 0; t < n_tasks; t++) { - char status_indicator; - switch(taskstats[t].eCurrentState ) - { - case eReady: status_indicator = 'R'; break; - case eBlocked: status_indicator = 'B'; break; - case eSuspended: status_indicator = 'S'; break; - case eDeleted: status_indicator = 'D'; break; - case eRunning: status_indicator = 'R'; break; - } - - uint32_t task_time_percent = 0; - if (total_time > 0) { - task_time_percent = taskstats[t].ulRunTimeCounter / total_time; - } - - usart_debug("TASK %d %s %c [%d] %d\r\n", - taskstats[t].xTaskNumber, - taskstats[t].pcTaskName, - status_indicator, - taskstats[t].usStackHighWaterMark, - task_time_percent); - } -} -#else -static void print_task_stats(void) {} -#endif diff --git a/src/common/src/GPIO/analog.c b/src/common/src/GPIO/analog.c deleted file mode 100644 index b6f75a6..0000000 --- a/src/common/src/GPIO/analog.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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 "GPIO/analog.h" - -#define SUPPLY_HISTORY_LEN 10 - -static float supply_history[SUPPLY_HISTORY_LEN]; -static int supply_history_ix = 0; -static int supply_history_ready = 0; -static int last_qrp = 0; - -// Return 1 if analog supply is too low -int analog_supply_too_low(void) -{ - - // Hysteresis: - // Lower voltage 12.5V - // High voltage 13V - const float measure = analog_measure_12v(); - - supply_history[supply_history_ix] = measure; - supply_history_ix++; - if (supply_history_ix >= SUPPLY_HISTORY_LEN) { - supply_history_ix = 0; - supply_history_ready = 1; - } - - if (supply_history_ready) { - float sum = 0.0f; - for (int i = 0; i < SUPPLY_HISTORY_LEN; i++) { - sum += supply_history[i]; - } - int above_lower_limit = (sum > 12.5f * SUPPLY_HISTORY_LEN); - int above_upper_limit = (sum > 13.0f * SUPPLY_HISTORY_LEN); - - if (!above_lower_limit) { - last_qrp = 1; - } - else if (above_upper_limit) { - last_qrp = 0; - } - - return last_qrp; - } - - return 0; -} diff --git a/src/common/src/GPIO/temperature.c b/src/common/src/GPIO/temperature.c deleted file mode 100644 index f9e43cb..0000000 --- a/src/common/src/GPIO/temperature.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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 "FreeRTOS.h" -#include "FreeRTOSConfig.h" -#include "task.h" -#include "Core/common.h" -#include "GPIO/temperature.h" - - -float _temperature_last_value; -int _temperature_valid; - - -void temperature_task(void *pvParameters); - - -void temperature_init() { - - xTaskCreate( - temperature_task, - "TaskTemperature", - 4*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 2UL, - NULL); -} - -// Return the current temperature -float temperature_get() { - if (_temperature_valid) { - return _temperature_last_value; - } else { - return 0.0f; - } -} - -// Return 1 if the temperature is valid -int temperature_valid() { - return _temperature_valid; -} diff --git a/src/common/src/GPIO/usart.c b/src/common/src/GPIO/usart.c deleted file mode 100644 index 8bcd80a..0000000 --- a/src/common/src/GPIO/usart.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 Matthias P. Braendli, Maximilien Cuony - * - * 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 -#include -#include -#include -#include "Core/common.h" -#include "GPIO/usart.h" -#include "GPIO/analog.h" -#include "FreeRTOS.h" -#include "task.h" -#include "queue.h" - -// The ISR writes into this buffer -static char nmea_sentence[MAX_NMEA_SENTENCE_LEN]; -static int nmea_sentence_last_written = 0; - - -// Once a completed NMEA sentence is received in the ISR, -// it is appended to this queue -static QueueHandle_t usart_nmea_queue; - -void usart_gps_init() { - usart_nmea_queue = xQueueCreate(15, MAX_NMEA_SENTENCE_LEN); - if (usart_nmea_queue == 0) { - while(1); /* fatal error */ - } - - usart_gps_specific_init(); - -} - -void usart_gps_puts(const char* str) { - vTaskSuspendAll(); - usart_puts(USART3, str); - xTaskResumeAll(); -} - -#define MAX_MSG_LEN 80 -static char usart_debug_message[MAX_MSG_LEN]; - -void usart_debug_timestamp() { - // Don't call printf here, to reduce stack usage - uint64_t now = timestamp_now(); - if (now == 0) { - usart_puts(USART2, "[0] "); - } - else { - char ts_str[64]; - int i = 63; - - ts_str[i--] = '\0'; - ts_str[i--] = ' '; - ts_str[i--] = ']'; - - while (now > 0 && i >= 0) { - ts_str[i--] = '0' + (now % 10); - now /= 10; - } - ts_str[i] = '['; - - usart_puts(USART2, &ts_str[i]); - } -} - -void usart_debug(const char *format, ...) { - va_list list; - va_start(list, format); - vsnprintf(usart_debug_message, MAX_MSG_LEN-1, format, list); - - vTaskSuspendAll(); - usart_debug_timestamp(); - usart_puts(USART2, usart_debug_message); - xTaskResumeAll(); - - va_end(list); -} - -void usart_debug_puts(const char* str) { -#ifdef SIMULATOR - fprintf(stderr, "DEBUG: %s", str); -#endif - vTaskSuspendAll(); - usart_debug_timestamp(); - usart_puts(USART2, str); - xTaskResumeAll(); -} - -void usart_debug_puts_header(const char* hdr, const char* str) { - vTaskSuspendAll(); - usart_debug_timestamp(); - usart_puts(USART2, hdr); - usart_puts(USART2, str); - usart_puts(USART2, "\r\n"); - xTaskResumeAll(); -} - -int usart_get_nmea_sentence(char* nmea) { - return xQueueReceive(usart_nmea_queue, nmea, portMAX_DELAY); -} - - -static void usart_clear_nmea_buffer(void) { - for (int i = 0; i < MAX_NMEA_SENTENCE_LEN; i++) { - nmea_sentence[i] = '\0'; - } - nmea_sentence_last_written = 0; -} - -void usart_process_char(char c) { -// Warning: running in interrupt context - usart_debug("Unknown command %c\r\n", c); -} - -void usart_gps_process_char(char c) -{ - BaseType_t require_context_switch = pdFALSE; - - if (nmea_sentence_last_written == 0) { - if (c == '$') { - // Likely new start of sentence - nmea_sentence[nmea_sentence_last_written] = c; - nmea_sentence_last_written++; - } - } - else if (nmea_sentence_last_written < MAX_NMEA_SENTENCE_LEN) { - nmea_sentence[nmea_sentence_last_written] = c; - nmea_sentence_last_written++; - - if (c == '\n') { - int success = xQueueSendToBackFromISR( - usart_nmea_queue, - nmea_sentence, - &require_context_switch); - - if (success == pdFALSE) { - trigger_fault(FAULT_SOURCE_USART); - } - - usart_clear_nmea_buffer(); - } - } - else { - // Buffer overrun without a meaningful NMEA message. - usart_clear_nmea_buffer(); - } - - portYIELD_FROM_ISR(require_context_switch); -} diff --git a/src/common/src/GPS/gps.c b/src/common/src/GPS/gps.c deleted file mode 100644 index bfdc1c1..0000000 --- a/src/common/src/GPS/gps.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2016 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 "FreeRTOS.h" -#include "FreeRTOSConfig.h" -#include "task.h" -#include "semphr.h" -#include "Core/common.h" -#include "GPS/gps.h" -#include "GPS/minmea.h" -#include "GPIO/usart.h" - - -TickType_t gps_timeutc_last_updated = 0; -static struct tm gps_timeutc; -static int gps_num_sv_used; -static int gps_timeutc_valid; // Validity flag for both gps_timeutc and gps_num_sv_used - -const TickType_t gps_data_validity_timeout = GPS_MS_TIMEOUT / portTICK_PERIOD_MS; - -static void gps_task(void *pvParameters); - -SemaphoreHandle_t timeutc_semaphore; - -// Get current time from GPS -int gps_utctime(struct tm *timeutc, int *num_sv_used) -{ - int valid = 0; - - xSemaphoreTake(timeutc_semaphore, portMAX_DELAY); - if (xTaskGetTickCount() - gps_timeutc_last_updated < gps_data_validity_timeout) { - timeutc->tm_year = gps_timeutc.tm_year; - timeutc->tm_mon = gps_timeutc.tm_mon; - timeutc->tm_mday = gps_timeutc.tm_mday; - timeutc->tm_hour = gps_timeutc.tm_hour; - timeutc->tm_min = gps_timeutc.tm_min; - timeutc->tm_sec = gps_timeutc.tm_sec; - *num_sv_used = gps_num_sv_used; - valid = gps_timeutc_valid; - } - - xSemaphoreGive(timeutc_semaphore); - - return valid; -} - -#define RXBUF_LEN MAX_NMEA_SENTENCE_LEN -static char rxbuf[RXBUF_LEN]; - -static void gps_task(void __attribute__ ((unused))*pvParameters) { - - // The initialisation placed the GPS into reset - usart_gps_remove_reset(); - - while (1) { - taskYIELD(); - - int success = usart_get_nmea_sentence(rxbuf); - - if (success) { - const int strict = 1; - switch (minmea_sentence_id(rxbuf, strict)) { - case MINMEA_SENTENCE_RMC: - { - struct minmea_sentence_rmc frame; - if (minmea_parse_rmc(&frame, rxbuf)) { - xSemaphoreTake(timeutc_semaphore, portMAX_DELAY); - // tm_year is saved as Year - 1900 in struct tm - gps_timeutc.tm_year = 2000 + frame.date.year - 1900; - // struct tm months are zero-indexed - gps_timeutc.tm_mon = frame.date.month - 1; - gps_timeutc.tm_mday = frame.date.day; - gps_timeutc.tm_hour = frame.time.hours; - gps_timeutc.tm_min = frame.time.minutes; - gps_timeutc.tm_sec = frame.time.seconds; - gps_timeutc_valid = frame.valid; - gps_timeutc_last_updated = xTaskGetTickCount(); - xSemaphoreGive(timeutc_semaphore); - } - } break; - case MINMEA_SENTENCE_TXT: - { - struct minmea_sentence_txt frame; - if (minmea_parse_txt(&frame, rxbuf)) { - rxbuf[MINMEA_TXT_START_IX + frame.text_len] = '\0'; - - switch (frame.msgtype) { - case MINMEA_GPTXT_ERROR: - usart_debug_puts_header("GPS ERROR ", frame.text); - break; - case MINMEA_GPTXT_WARNING: - usart_debug_puts_header("GPS WARNING ", frame.text); - break; - default: - usart_debug_puts_header("GPS Message ", frame.text); - break; - } - } - } break; - case MINMEA_SENTENCE_GGA: - { - struct minmea_sentence_gga frame; - if (minmea_parse_gga(&frame, rxbuf)) { - xSemaphoreTake(timeutc_semaphore, portMAX_DELAY); - gps_num_sv_used = frame.satellites_tracked; - xSemaphoreGive(timeutc_semaphore); - } - } break; - default: - break; - } - } - } -} - -void gps_init() { - gps_timeutc_valid = 0; - - usart_gps_init(); - - timeutc_semaphore = xSemaphoreCreateBinary(); - - if (timeutc_semaphore == NULL) { - trigger_fault(FAULT_SOURCE_GPS); - } else { - xSemaphoreGive(timeutc_semaphore); - } - - xTaskCreate( - gps_task, - "TaskGPS", - 4*configMINIMAL_STACK_SIZE, - (void*) NULL, - tskIDLE_PRIORITY + 2UL, - NULL); -} - -// Return 1 of the GPS is receiving time -int gps_locked() { - if (xTaskGetTickCount() - gps_timeutc_last_updated < gps_data_validity_timeout) { - return gps_timeutc_valid; - } else { - return 0; - } -} diff --git a/src/common/src/GPS/minmea.c b/src/common/src/GPS/minmea.c deleted file mode 100644 index 4d4aeb8..0000000 --- a/src/common/src/GPS/minmea.c +++ /dev/null @@ -1,597 +0,0 @@ -/* - * Copyright © 2014 Kosma Moczek - * This program is free software. It comes without any warranty, to the extent - * permitted by applicable law. You can redistribute it and/or modify it under - * the terms of the Do What The Fuck You Want To Public License, Version 2, as - * published by Sam Hocevar. See the COPYING file for more details. - */ - -// We are lacking timegm, but we only handle UTC and use mktime instead. See -// https://github.com/cloudyourcar/minmea-t/ README.md -#define timegm mktime - -#include "GPS/minmea.h" - -#include -#include -#include -#include - -#define boolstr(s) ((s) ? "true" : "false") - -static int hex2int(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - return -1; -} - -uint8_t minmea_checksum(const char *sentence) -{ - // Support senteces with or without the starting dollar sign. - if (*sentence == '$') - sentence++; - - uint8_t checksum = 0x00; - - // The optional checksum is an XOR of all bytes between "$" and "*". - while (*sentence && *sentence != '*') - checksum ^= *sentence++; - - return checksum; -} - -bool minmea_check(const char *sentence, bool strict) -{ - uint8_t checksum = 0x00; - - // Sequence length is limited. - if (strlen(sentence) > MINMEA_MAX_LENGTH + 3) - return false; - - // A valid sentence starts with "$". - if (*sentence++ != '$') - return false; - - // The optional checksum is an XOR of all bytes between "$" and "*". - while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence)) - checksum ^= *sentence++; - - // If checksum is present... - if (*sentence == '*') { - // Extract checksum. - sentence++; - int upper = hex2int(*sentence++); - if (upper == -1) - return false; - int lower = hex2int(*sentence++); - if (lower == -1) - return false; - int expected = upper << 4 | lower; - - // Check for checksum mismatch. - if (checksum != expected) - return false; - } else if (strict) { - // Discard non-checksummed frames in strict mode. - return false; - } - - // The only stuff allowed at this point is a newline. - if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n")) - return false; - - return true; -} - -static inline bool minmea_isfield(char c) { - return isprint((unsigned char) c) && c != ',' && c != '*'; -} - -bool minmea_scan(const char *sentence, const char *format, ...) -{ - bool result = false; - bool optional = false; - va_list ap; - va_start(ap, format); - - const char *field = sentence; -#define next_field() \ - do { \ - /* Progress to the next field. */ \ - while (minmea_isfield(*sentence)) \ - sentence++; \ - /* Make sure there is a field there. */ \ - if (*sentence == ',') { \ - sentence++; \ - field = sentence; \ - } else { \ - field = NULL; \ - } \ - } while (0) - - while (*format) { - char type = *format++; - - if (type == ';') { - // All further fields are optional. - optional = true; - continue; - } - - if (!field && !optional) { - // Field requested but we ran out if input. Bail out. - goto parse_error; - } - - switch (type) { - case 'c': { // Single character field (char). - char value = '\0'; - - if (field && minmea_isfield(*field)) - value = *field; - - *va_arg(ap, char *) = value; - } break; - - case 'd': { // Single character direction field (int). - int value = 0; - - if (field && minmea_isfield(*field)) { - switch (*field) { - case 'N': - case 'E': - value = 1; - break; - case 'S': - case 'W': - value = -1; - break; - default: - goto parse_error; - } - } - - *va_arg(ap, int *) = value; - } break; - - case 'f': { // Fractional value with scale (struct minmea_float). - int sign = 0; - int_least32_t value = -1; - int_least32_t scale = 0; - - if (field) { - while (minmea_isfield(*field)) { - if (*field == '+' && !sign && value == -1) { - sign = 1; - } else if (*field == '-' && !sign && value == -1) { - sign = -1; - } else if (isdigit((unsigned char) *field)) { - int digit = *field - '0'; - if (value == -1) - value = 0; - if (value > (INT_LEAST32_MAX-digit) / 10) { - /* we ran out of bits, what do we do? */ - if (scale) { - /* truncate extra precision */ - break; - } else { - /* integer overflow. bail out. */ - goto parse_error; - } - } - value = (10 * value) + digit; - if (scale) - scale *= 10; - } else if (*field == '.' && scale == 0) { - scale = 1; - } else if (*field == ' ') { - /* Allow spaces at the start of the field. Not NMEA - * conformant, but some modules do this. */ - if (sign != 0 || value != -1 || scale != 0) - goto parse_error; - } else { - goto parse_error; - } - field++; - } - } - - if ((sign || scale) && value == -1) - goto parse_error; - - if (value == -1) { - /* No digits were scanned. */ - value = 0; - scale = 0; - } else if (scale == 0) { - /* No decimal point. */ - scale = 1; - } - if (sign) - value *= sign; - - *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale}; - } break; - - case 'i': { // Integer value, default 0 (int). - int value = 0; - - if (field) { - char *endptr; - value = strtol(field, &endptr, 10); - if (minmea_isfield(*endptr)) - goto parse_error; - } - - *va_arg(ap, int *) = value; - } break; - - case 's': { // String value (char *). - char *buf = va_arg(ap, char *); - - if (field) { - while (minmea_isfield(*field)) - *buf++ = *field++; - } - - *buf = '\0'; - } break; - - case 't': { // NMEA talker+sentence identifier (char *). - // This field is always mandatory. - if (!field) - goto parse_error; - - if (field[0] != '$') - goto parse_error; - for (int f=0; f<5; f++) - if (!minmea_isfield(field[1+f])) - goto parse_error; - - char *buf = va_arg(ap, char *); - memcpy(buf, field+1, 5); - buf[5] = '\0'; - } break; - - case 'D': { // Date (int, int, int), -1 if empty. - struct minmea_date *date = va_arg(ap, struct minmea_date *); - - int d = -1, m = -1, y = -1; - - if (field && minmea_isfield(*field)) { - // Always six digits. - for (int f=0; f<6; f++) - if (!isdigit((unsigned char) field[f])) - goto parse_error; - - d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10); - m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10); - y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10); - } - - date->day = d; - date->month = m; - date->year = y; - } break; - - case 'T': { // Time (int, int, int, int), -1 if empty. - struct minmea_time *time_ = va_arg(ap, struct minmea_time *); - - int h = -1, i = -1, s = -1, u = -1; - - if (field && minmea_isfield(*field)) { - // Minimum required: integer time. - for (int f=0; f<6; f++) - if (!isdigit((unsigned char) field[f])) - goto parse_error; - - h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10); - i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10); - s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10); - field += 6; - - // Extra: fractional time. Saved as microseconds. - if (*field++ == '.') { - int value = 0; - int scale = 1000000; - while (isdigit((unsigned char) *field) && scale > 1) { - value = (value * 10) + (*field++ - '0'); - scale /= 10; - } - u = value * scale; - } else { - u = 0; - } - } - - time_->hours = h; - time_->minutes = i; - time_->seconds = s; - time_->microseconds = u; - } break; - - case '_': { // Ignore the field. - } break; - - default: { // Unknown. - goto parse_error; - } break; - } - - next_field(); - } - - result = true; - -parse_error: - va_end(ap); - return result; -} - -bool minmea_talker_id(char talker[3], const char *sentence) -{ - char type[6]; - if (!minmea_scan(sentence, "t", type)) - return false; - - talker[0] = type[0]; - talker[1] = type[1]; - talker[2] = '\0'; - - return true; -} - -enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict) -{ - if (!minmea_check(sentence, strict)) - return MINMEA_INVALID; - - char type[6]; - if (!minmea_scan(sentence, "t", type)) - return MINMEA_INVALID; - - if (!strcmp(type+2, "RMC")) - return MINMEA_SENTENCE_RMC; - if (!strcmp(type+2, "GGA")) - return MINMEA_SENTENCE_GGA; - if (!strcmp(type+2, "GSA")) - return MINMEA_SENTENCE_GSA; - if (!strcmp(type+2, "GLL")) - return MINMEA_SENTENCE_GLL; - if (!strcmp(type+2, "GST")) - return MINMEA_SENTENCE_GST; - if (!strcmp(type+2, "GSV")) - return MINMEA_SENTENCE_GSV; - if (!strcmp(type+2, "TXT")) - return MINMEA_SENTENCE_TXT; - - return MINMEA_UNKNOWN; -} - -bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence) -{ - // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 - char type[6]; - char validity; - int latitude_direction; - int longitude_direction; - int variation_direction; - if (!minmea_scan(sentence, "tTcfdfdffDfd", - type, - &frame->time, - &validity, - &frame->latitude, &latitude_direction, - &frame->longitude, &longitude_direction, - &frame->speed, - &frame->course, - &frame->date, - &frame->variation, &variation_direction)) - return false; - if (strcmp(type+2, "RMC")) - return false; - - frame->valid = (validity == 'A'); - frame->latitude.value *= latitude_direction; - frame->longitude.value *= longitude_direction; - frame->variation.value *= variation_direction; - - return true; -} - -bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence) -{ - // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 - char type[6]; - int latitude_direction; - int longitude_direction; - - if (!minmea_scan(sentence, "tTfdfdiiffcfci_", - type, - &frame->time, - &frame->latitude, &latitude_direction, - &frame->longitude, &longitude_direction, - &frame->fix_quality, - &frame->satellites_tracked, - &frame->hdop, - &frame->altitude, &frame->altitude_units, - &frame->height, &frame->height_units, - &frame->dgps_age)) - return false; - if (strcmp(type+2, "GGA")) - return false; - - frame->latitude.value *= latitude_direction; - frame->longitude.value *= longitude_direction; - - return true; -} - -bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence) -{ - // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 - char type[6]; - - if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff", - type, - &frame->mode, - &frame->fix_type, - &frame->sats[0], - &frame->sats[1], - &frame->sats[2], - &frame->sats[3], - &frame->sats[4], - &frame->sats[5], - &frame->sats[6], - &frame->sats[7], - &frame->sats[8], - &frame->sats[9], - &frame->sats[10], - &frame->sats[11], - &frame->pdop, - &frame->hdop, - &frame->vdop)) - return false; - if (strcmp(type+2, "GSA")) - return false; - - return true; -} - -bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence) -{ - // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$; - char type[6]; - int latitude_direction; - int longitude_direction; - - if (!minmea_scan(sentence, "tfdfdTc;c", - type, - &frame->latitude, &latitude_direction, - &frame->longitude, &longitude_direction, - &frame->time, - &frame->status, - &frame->mode)) - return false; - if (strcmp(type+2, "GLL")) - return false; - - frame->latitude.value *= latitude_direction; - frame->longitude.value *= longitude_direction; - - return true; -} - -bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence) -{ - // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58 - char type[6]; - - if (!minmea_scan(sentence, "tTfffffff", - type, - &frame->time, - &frame->rms_deviation, - &frame->semi_major_deviation, - &frame->semi_minor_deviation, - &frame->semi_major_orientation, - &frame->latitude_error_deviation, - &frame->longitude_error_deviation, - &frame->altitude_error_deviation)) - return false; - if (strcmp(type+2, "GST")) - return false; - - return true; -} - -bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence) -{ - // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 - // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D - // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75 - // $GPGSV,4,4,13,39,31,170,27*40 - // $GPGSV,4,4,13*7B - char type[6]; - - if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii", - type, - &frame->total_msgs, - &frame->msg_nr, - &frame->total_sats, - &frame->sats[0].nr, - &frame->sats[0].elevation, - &frame->sats[0].azimuth, - &frame->sats[0].snr, - &frame->sats[1].nr, - &frame->sats[1].elevation, - &frame->sats[1].azimuth, - &frame->sats[1].snr, - &frame->sats[2].nr, - &frame->sats[2].elevation, - &frame->sats[2].azimuth, - &frame->sats[2].snr, - &frame->sats[3].nr, - &frame->sats[3].elevation, - &frame->sats[3].azimuth, - &frame->sats[3].snr - )) { - return false; - } - if (strcmp(type+2, "GSV")) - return false; - - return true; -} - -bool minmea_parse_txt(struct minmea_sentence_txt *frame, const char *sentence) -{ - // Example - // $GPTXT,01,01,02,u-blox ag - www.u-blox.com*50 - char type[6]; - - if (!minmea_scan(sentence, "tiii", - type, - &frame->num_msg, - &frame->msg_num, - &frame->msgtype)) { - return false; - } - if (strcmp(type+2, "TXT")) { - return false; - } - - frame->text = sentence + MINMEA_TXT_START_IX; - frame->text_len = strlen(sentence) - MINMEA_TXT_START_IX - 5; - - return true; -} - -int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_) -{ - if (date->year == -1 || time_->hours == -1) - return -1; - - struct tm tm; - memset(&tm, 0, sizeof(tm)); - tm.tm_year = 2000 + date->year - 1900; - tm.tm_mon = date->month - 1; - tm.tm_mday = date->day; - tm.tm_hour = time_->hours; - tm.tm_min = time_->minutes; - tm.tm_sec = time_->seconds; - - time_t timestamp = timegm(&tm); /* See README.md if your system lacks timegm(). */ - if (timestamp != -1) { - ts->tv_sec = timestamp; - ts->tv_nsec = time_->microseconds * 1000; - return 0; - } else { - return -1; - } -} - -/* vim: set ts=4 sw=4 et: */ diff --git a/src/glutt-o-logique/FreeRTOSConfig.h b/src/glutt-o-logique/FreeRTOSConfig.h index 5a799e9..fdbb2e4 100644 --- a/src/glutt-o-logique/FreeRTOSConfig.h +++ b/src/glutt-o-logique/FreeRTOSConfig.h @@ -1,4 +1,4 @@ -#include "../common/src/Core/FreeRTOSConfig.h" +#include "../common/Core/FreeRTOSConfig.h" #define configCHECK_FOR_STACK_OVERFLOW 2 // Default: 2 diff --git a/src/glutt-o-logique/Makefile b/src/glutt-o-logique/Makefile index 0febb9c..3794856 100644 --- a/src/glutt-o-logique/Makefile +++ b/src/glutt-o-logique/Makefile @@ -22,7 +22,7 @@ COMMON_SOURCE_LIST=$(shell cat ../common/sourcelist.txt) CSOURCES+=$(COMMON_SOURCE_LIST:%.c=../common/%.c) INC=$(shell find -L $(SRCDIR) -name '*.h' -exec dirname {} \; | uniq) -INC+=$(COMMON_DIR)/includes +INC+=$(COMMON_DIR) INCLUDES=$(INC:%=-I%) # Create object list diff --git a/src/glutt-o-logique/usart.c b/src/glutt-o-logique/usart.c index 806505b..9ec5553 100644 --- a/src/glutt-o-logique/usart.c +++ b/src/glutt-o-logique/usart.c @@ -37,7 +37,7 @@ const uint16_t GPIOD_PIN_GPS_RESET_N = GPIO_Pin_10; const uint16_t GPIOA_PIN_USART2_RX = GPIO_Pin_3; const uint16_t GPIOA_PIN_USART2_TX = GPIO_Pin_2; -#include "../common/includes/GPIO/usart.h" +#include "GPIO/usart.h" #define USART2_RECEIVE_ENABLE 0 // TODO something is not working diff --git a/src/simulator/Makefile b/src/simulator/Makefile index 615765b..a93e741 100644 --- a/src/simulator/Makefile +++ b/src/simulator/Makefile @@ -43,7 +43,7 @@ C_FILES += $(SRC_SOURCES) INCLUDES += -I$(SRCROOT)/Source/include INCLUDES += -I$(SRCROOT)/Source/portable/GCC/POSIX/ INCLUDES += -I$(SRCROOT)/src/Core -INCLUDES += -I$(SRCROOT)/../common/includes/ +INCLUDES += -I$(SRCROOT)/../common/ INCLUDES += -I$(SRCROOT) # Generate OBJS names diff --git a/src/simulator/src/Core/FreeRTOSConfig.h b/src/simulator/src/Core/FreeRTOSConfig.h index 19086b7..a262c24 100644 --- a/src/simulator/src/Core/FreeRTOSConfig.h +++ b/src/simulator/src/Core/FreeRTOSConfig.h @@ -1,4 +1,3 @@ -#include "../../../common/src/Core/FreeRTOSConfig.h" - +#include "Core/FreeRTOSConfig.h" #define configCHECK_FOR_STACK_OVERFLOW 0 /* Do not use this option on the PC port. */ -- cgit v1.2.3