diff options
Diffstat (limited to 'src/common/Core/main.c')
-rw-r--r-- | src/common/Core/main.c | 710 |
1 files changed, 710 insertions, 0 deletions
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 |