diff options
Diffstat (limited to 'src/common/Core')
-rw-r--r-- | src/common/Core/FreeRTOSConfig.h | 164 | ||||
-rw-r--r-- | src/common/Core/common.c | 265 | ||||
-rw-r--r-- | src/common/Core/common.h | 82 | ||||
-rw-r--r-- | src/common/Core/delay.h | 39 | ||||
-rw-r--r-- | src/common/Core/fsm.c | 679 | ||||
-rw-r--r-- | src/common/Core/fsm.h | 135 | ||||
-rw-r--r-- | src/common/Core/main.c | 710 |
7 files changed, 2074 insertions, 0 deletions
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 <stdint.h>
+ 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 <time.h> +#include <math.h> + +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 <stdint.h> +#include <time.h> + +/* 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 <stdint.h> + +// 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 <string.h> +#include <stdio.h> +#include <stdint.h> +#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 <stdint.h> + +// 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 <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <math.h> + +/* 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 |