aboutsummaryrefslogtreecommitdiffstats
path: root/src/common/Core
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/Core')
-rw-r--r--src/common/Core/FreeRTOSConfig.h164
-rw-r--r--src/common/Core/common.c265
-rw-r--r--src/common/Core/common.h82
-rw-r--r--src/common/Core/delay.h39
-rw-r--r--src/common/Core/fsm.c679
-rw-r--r--src/common/Core/fsm.h135
-rw-r--r--src/common/Core/main.c710
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