added tec modulation

This commit is contained in:
Ayzen
2026-04-27 18:16:39 +03:00
parent d32db245fa
commit d3d48a5255
60 changed files with 13021 additions and 12456 deletions

View File

@ -37,6 +37,15 @@
#define APP_UART_TIMEOUT_TICKS_10MS 100u
#define APP_STATUS_BYTE_COUNT 2u
#define APP_CUSTOM_WAVE_BUFFER_SIZE 1024u
#define APP_TEC_CHANNEL_COUNT 2u
#define APP_TEC_DRIVE_MIN_CODE 1000u
#define APP_TEC_DRIVE_MAX_CODE 56800u
#define APP_TEC_MODULATION_UPDATE_RATE_HZ 16000u
#define APP_TEC_MODULATION_LUT_BITS 5u
#define APP_TEC_MODULATION_LUT_SIZE (1u << APP_TEC_MODULATION_LUT_BITS)
#define APP_TEC_MODULATION_PHASE_SHIFT (32u - APP_TEC_MODULATION_LUT_BITS)
#define APP_TEC_MODULATION_ALLOWED_FLAGS \
(APP_TEC_MODULATION_FLAG_ENABLE | APP_TEC_MODULATION_FLAG_CHANNEL_2)
/*
* Boot-diagnostic codes exposed through QUERY_STATE (0x6666).
@ -54,6 +63,13 @@
#define APP_BOOT_DIAG_PROFILE_READY_IDLE 0x08u
#define APP_BOOT_DIAG_PROFILE_RUNNING 0x09u
static const int16_t g_tec_modulation_sine_lut[APP_TEC_MODULATION_LUT_SIZE] = {
0, 6393, 12540, 18205, 23170, 27245, 30273, 32138,
32767, 32138, 30273, 27245, 23170, 18205, 12540, 6393,
0, -6393, -12540, -18205, -23170, -27245, -30273, -32138,
-32767, -32138, -30273, -27245, -23170, -18205, -12540, -6393
};
typedef struct app_context_t {
/* Written by TIM6 IRQ. Read by telemetry and UART timeout logic. Purpose: coarse 10 ms system timebase. */
volatile uint32_t tick_10ms;
@ -123,6 +139,21 @@ typedef struct app_context_t {
/* Written by ADC sampling and PID helpers. Read by telemetry and PID logic. Purpose: measured live state for both laser channels. */
laser_runtime_t laser_runtime[2];
/* Written by TEC modulation command and TIM5. Read by PID output glue. Purpose: one fast zero-mean TEC drive modulation channel. */
tec_modulation_config_t tec_modulation;
/* Written by TEC modulation command and TIM5. Read by TIM5. Purpose: fixed-point sine phase state. */
uint32_t tec_modulation_phase;
/* Written by TEC modulation command. Read by TIM5. Purpose: fixed-point phase increment per modulation tick. */
uint32_t tec_modulation_phase_step;
/* Written by PID loop. Read by TIM5. Purpose: latest slow TEC drive centers for both channels. */
uint16_t tec_base_code[APP_TEC_CHANNEL_COUNT];
/* Written by main-loop SPI sections. Read by TIM5. Purpose: skip modulation writes while shared SPI is busy. */
volatile uint8_t tec_modulation_spi_suspended;
/* Written when a profile is activated. Read mainly for future profile cycling/debug visibility. Purpose: snapshot of the currently selected standalone profile. */
profile_t active_profile;
@ -143,6 +174,17 @@ static void app_reset_uart_parser(void);
static void app_set_mode(app_mode_t mode);
static void app_apply_work_config_to_board(void);
static void app_enter_idle_state(bool reset_ticks);
static void app_reset_tec_modulation(void);
static bool app_is_tec_channel_active(uint8_t channel_index);
static uint16_t app_clamp_tec_drive_code(int32_t value);
static bool app_normalize_tec_modulation_config(const tec_modulation_config_t *input,
tec_modulation_config_t *output);
static void app_store_tec_modulation_config(const tec_modulation_config_t *config);
static void app_suspend_tec_modulation_spi(void);
static void app_resume_tec_modulation_spi(void);
static void app_write_laser_dac_channel(uint8_t channel, uint16_t value);
static void app_write_tec_pid_output(uint8_t channel_index, uint16_t pid_code);
static void app_write_tec_modulation_sample(void);
static void app_prepare_status_response(void);
static void app_capture_live_frame(bool update_temperature_loops, bool drive_laser_currents);
static void app_decode_work_packet(const uint16_t *packet_words);
@ -161,6 +203,7 @@ static void app_handle_ad9102_control_packet(const app_packet_t *packet);
static void app_handle_ad9833_control_packet(const app_packet_t *packet);
static void app_handle_ds1809_control_packet(const app_packet_t *packet);
static void app_handle_stm32_dac_control_packet(const app_packet_t *packet);
static void app_handle_tec_modulation_control_packet(const app_packet_t *packet);
static void app_handle_ad9102_wave_control_packet(const app_packet_t *packet);
static void app_handle_ad9102_wave_data_packet(const app_packet_t *packet);
static void app_handle_profile_save_data_packet(const app_packet_t *packet);
@ -201,6 +244,7 @@ static void app_reset_runtime_state(bool reset_ticks)
g_app.last_processed_tick_1ms = 0u;
g_app.pending_event = APP_EVENT_NONE;
memset(&g_app.laser_runtime, 0, sizeof(g_app.laser_runtime));
app_reset_tec_modulation();
app_clear_active_profile();
ui_status_set_error(0u);
@ -266,8 +310,9 @@ static void app_apply_work_config_to_board(void)
if ((g_app.work_config.temp_sensor1_enabled != 0u) && (g_app.work_config.tec1_enabled != 0u))
{
laser_dac_write_channel(3u, APP_TEC_PRECHARGE_CODE);
laser_dac_write_channel(3u, APP_TEC_PRECHARGE_CODE);
g_app.tec_base_code[0] = APP_TEC_PRECHARGE_CODE;
app_write_laser_dac_channel(3u, APP_TEC_PRECHARGE_CODE);
app_write_laser_dac_channel(3u, APP_TEC_PRECHARGE_CODE);
board_io_set_tec_channel_enabled(1u, true);
}
else
@ -277,8 +322,9 @@ static void app_apply_work_config_to_board(void)
if ((g_app.work_config.temp_sensor2_enabled != 0u) && (g_app.work_config.tec2_enabled != 0u))
{
laser_dac_write_channel(4u, APP_TEC_PRECHARGE_CODE);
laser_dac_write_channel(4u, APP_TEC_PRECHARGE_CODE);
g_app.tec_base_code[1] = APP_TEC_PRECHARGE_CODE;
app_write_laser_dac_channel(4u, APP_TEC_PRECHARGE_CODE);
app_write_laser_dac_channel(4u, APP_TEC_PRECHARGE_CODE);
board_io_set_tec_channel_enabled(2u, true);
}
else
@ -314,6 +360,190 @@ static void app_enter_idle_state(bool reset_ticks)
app_reset_runtime_state(reset_ticks);
}
static void app_reset_tec_modulation(void)
{
memset(&g_app.tec_modulation, 0, sizeof(g_app.tec_modulation));
g_app.tec_modulation_phase = 0u;
g_app.tec_modulation_phase_step = 0u;
g_app.tec_base_code[0] = APP_TEC_PRECHARGE_CODE;
g_app.tec_base_code[1] = APP_TEC_PRECHARGE_CODE;
g_app.tec_modulation_spi_suspended = 0u;
}
static bool app_is_tec_channel_active(uint8_t channel_index)
{
if (channel_index == 0u)
{
return (g_app.work_config.tec1_enabled != 0u) &&
(g_app.work_config.temp_sensor1_enabled != 0u);
}
if (channel_index == 1u)
{
return (g_app.work_config.tec2_enabled != 0u) &&
(g_app.work_config.temp_sensor2_enabled != 0u);
}
return false;
}
static uint16_t app_clamp_tec_drive_code(int32_t value)
{
if (value < (int32_t)APP_TEC_DRIVE_MIN_CODE)
{
return APP_TEC_DRIVE_MIN_CODE;
}
if (value > (int32_t)APP_TEC_DRIVE_MAX_CODE)
{
return APP_TEC_DRIVE_MAX_CODE;
}
return (uint16_t)value;
}
static bool app_normalize_tec_modulation_config(const tec_modulation_config_t *input,
tec_modulation_config_t *output)
{
if ((input == NULL) || (output == NULL))
{
return false;
}
*output = *input;
if (output->channel_index >= APP_TEC_CHANNEL_COUNT)
{
return false;
}
if (output->enabled == 0u)
{
output->frequency_hz = 0u;
output->amplitude_code = 0u;
return true;
}
if ((output->frequency_hz < APP_TEC_MODULATION_MIN_FREQUENCY_HZ) ||
(output->frequency_hz > APP_TEC_MODULATION_MAX_FREQUENCY_HZ) ||
(output->amplitude_code > APP_TEC_MODULATION_MAX_AMPLITUDE_CODE))
{
return false;
}
output->enabled = 1u;
return true;
}
static void app_store_tec_modulation_config(const tec_modulation_config_t *config)
{
uint32_t phase_step = 0u;
if (config == NULL)
{
return;
}
if (config->enabled != 0u)
{
phase_step = (uint32_t)(((uint64_t)config->frequency_hz << 32) /
APP_TEC_MODULATION_UPDATE_RATE_HZ);
}
__disable_irq();
g_app.tec_modulation = *config;
g_app.tec_modulation_phase = 0u;
g_app.tec_modulation_phase_step = phase_step;
__enable_irq();
}
static void app_suspend_tec_modulation_spi(void)
{
__disable_irq();
if (g_app.tec_modulation_spi_suspended < 0xFFu)
{
++g_app.tec_modulation_spi_suspended;
}
__enable_irq();
}
static void app_resume_tec_modulation_spi(void)
{
__disable_irq();
if (g_app.tec_modulation_spi_suspended > 0u)
{
--g_app.tec_modulation_spi_suspended;
}
__enable_irq();
}
static void app_write_laser_dac_channel(uint8_t channel, uint16_t value)
{
app_suspend_tec_modulation_spi();
laser_dac_write_channel(channel, value);
app_resume_tec_modulation_spi();
}
static void app_write_tec_pid_output(uint8_t channel_index, uint16_t pid_code)
{
uint8_t dac_channel;
if (channel_index >= APP_TEC_CHANNEL_COUNT)
{
return;
}
g_app.tec_base_code[channel_index] = app_clamp_tec_drive_code(pid_code);
if ((g_app.tec_modulation.enabled != 0u) &&
(g_app.tec_modulation.channel_index == channel_index))
{
return;
}
dac_channel = (uint8_t)(3u + channel_index);
app_write_laser_dac_channel(dac_channel, g_app.tec_base_code[channel_index]);
}
static void app_write_tec_modulation_sample(void)
{
uint8_t channel_index;
uint16_t base_code;
uint16_t low_headroom;
uint16_t high_headroom;
uint16_t amplitude;
uint8_t lut_index;
int32_t offset;
uint8_t dac_channel;
if ((g_app.tec_modulation.enabled == 0u) || (g_app.mode != APP_MODE_WORK))
{
return;
}
if (g_app.tec_modulation_spi_suspended != 0u)
{
g_app.tec_modulation_phase += g_app.tec_modulation_phase_step;
return;
}
channel_index = g_app.tec_modulation.channel_index;
if (!app_is_tec_channel_active(channel_index))
{
return;
}
base_code = g_app.tec_base_code[channel_index];
base_code = app_clamp_tec_drive_code(base_code);
low_headroom = (uint16_t)(base_code - APP_TEC_DRIVE_MIN_CODE);
high_headroom = (uint16_t)(APP_TEC_DRIVE_MAX_CODE - base_code);
amplitude = (low_headroom < high_headroom) ? low_headroom : high_headroom;
if (amplitude > g_app.tec_modulation.amplitude_code)
{
amplitude = g_app.tec_modulation.amplitude_code;
}
lut_index = (uint8_t)(g_app.tec_modulation_phase >> APP_TEC_MODULATION_PHASE_SHIFT);
offset = ((int32_t)g_tec_modulation_sine_lut[lut_index] * (int32_t)amplitude) / 32767;
dac_channel = (uint8_t)(3u + channel_index);
laser_dac_write_channel(dac_channel, app_clamp_tec_drive_code((int32_t)base_code + offset));
g_app.tec_modulation_phase += g_app.tec_modulation_phase_step;
}
static void app_apply_profile_auxiliary_outputs(const profile_t *profile)
{
if (profile == NULL)
@ -356,6 +586,7 @@ static void app_request_transient_mode(app_mode_t transient_mode)
static bool app_activate_profile(const profile_t *profile, uint16_t index, bool allow_auto_run)
{
bool should_auto_run;
tec_modulation_config_t tec_modulation;
if (profile == NULL)
{
@ -372,6 +603,16 @@ static bool app_activate_profile(const profile_t *profile, uint16_t index, bool
g_app.laser_config[0] = profile->laser_channels[0];
g_app.laser_config[1] = profile->laser_channels[1];
tec_modulation.enabled = 0u;
tec_modulation.channel_index = 0u;
tec_modulation.frequency_hz = 0u;
tec_modulation.amplitude_code = 0u;
if (!app_normalize_tec_modulation_config(&profile->tec_modulation, &tec_modulation))
{
app_set_error(APP_STATUS_FLAG_SD_ERROR);
}
app_store_tec_modulation_config(&tec_modulation);
if (!app_apply_waveform_config(&profile->waveform))
{
app_enter_idle_state(false);
@ -422,20 +663,20 @@ static void app_capture_live_frame(bool update_temperature_loops, bool drive_las
1u,
g_app.tick_1ms,
&g_app.pid_reference_tick_1ms);
laser_dac_write_channel(3u, tec_drive_code);
app_write_tec_pid_output(0u, tec_drive_code);
tec_drive_code = temperature_control_compute_pid(&g_app.laser_config[1],
&g_app.laser_runtime[1],
2u,
g_app.tick_1ms,
&g_app.pid_reference_tick_1ms);
laser_dac_write_channel(4u, tec_drive_code);
app_write_tec_pid_output(1u, tec_drive_code);
}
if (drive_laser_currents)
{
laser_dac_write_channel(1u, g_app.laser_config[0].current_raw);
laser_dac_write_channel(2u, g_app.laser_config[1].current_raw);
app_write_laser_dac_channel(1u, g_app.laser_config[0].current_raw);
app_write_laser_dac_channel(2u, g_app.laser_config[1].current_raw);
}
(void)adc_mux_process_internal_adc_step(0u);
@ -882,6 +1123,7 @@ static void app_handle_ad9102_control_packet(const app_packet_t *packet)
triangle = (flags & APP_AD9102_FLAG_TRIANGLE) ? 1u : 0u;
sram_mode = (flags & APP_AD9102_FLAG_SRAM) ? 1u : 0u;
app_suspend_tec_modulation_spi();
if (sram_mode != 0u)
{
uint16_t sample_count;
@ -954,6 +1196,7 @@ static void app_handle_ad9102_control_packet(const app_packet_t *packet)
app_set_error(APP_STATUS_FLAG_AD9102_ERROR);
}
}
app_resume_tec_modulation_spi();
g_app.status_bytes[1] = (uint8_t)(pat_status & 0x00FFu);
app_prepare_status_response();
@ -977,9 +1220,11 @@ static void app_handle_ad9833_control_packet(const app_packet_t *packet)
lsw = (uint16_t)(packet->words[1] & 0x3FFFu);
msw = (uint16_t)(packet->words[2] & 0x3FFFu);
frequency_word = ((uint32_t)msw << 14) | (uint32_t)lsw;
app_suspend_tec_modulation_spi();
ad9833_apply((flags & APP_AD9833_FLAG_ENABLE) ? 1u : 0u,
(flags & APP_AD9833_FLAG_TRIANGLE) ? 1u : 0u,
frequency_word);
app_resume_tec_modulation_spi();
app_prepare_status_response();
}
@ -1046,6 +1291,55 @@ static void app_handle_stm32_dac_control_packet(const app_packet_t *packet)
app_prepare_status_response();
}
static void app_handle_tec_modulation_control_packet(const app_packet_t *packet)
{
tec_modulation_config_t config;
tec_modulation_config_t normalized_config;
uint16_t flags;
uint8_t previous_enabled;
uint8_t previous_channel_index;
if ((packet == NULL) || !packet->checksum_valid)
{
app_set_error(APP_STATUS_FLAG_UART_DECODE_ERROR);
app_prepare_status_response();
return;
}
flags = packet->words[0];
if ((flags & ~APP_TEC_MODULATION_ALLOWED_FLAGS) != 0u)
{
app_set_error(APP_STATUS_FLAG_UART_DECODE_ERROR);
app_prepare_status_response();
return;
}
config.enabled = (flags & APP_TEC_MODULATION_FLAG_ENABLE) ? 1u : 0u;
config.channel_index = (flags & APP_TEC_MODULATION_FLAG_CHANNEL_2) ? 1u : 0u;
config.frequency_hz = packet->words[1];
config.amplitude_code = packet->words[2];
if (!app_normalize_tec_modulation_config(&config, &normalized_config))
{
app_set_error(APP_STATUS_FLAG_UART_DECODE_ERROR);
app_prepare_status_response();
return;
}
previous_enabled = g_app.tec_modulation.enabled;
previous_channel_index = g_app.tec_modulation.channel_index;
app_store_tec_modulation_config(&normalized_config);
if ((previous_enabled != 0u) &&
((normalized_config.enabled == 0u) || (previous_channel_index != normalized_config.channel_index)) &&
app_is_tec_channel_active(previous_channel_index))
{
app_write_laser_dac_channel((uint8_t)(3u + previous_channel_index),
g_app.tec_base_code[previous_channel_index]);
}
app_prepare_status_response();
}
static void app_handle_ad9102_wave_control_packet(const app_packet_t *packet)
{
uint16_t opcode;
@ -1066,6 +1360,7 @@ static void app_handle_ad9102_wave_control_packet(const app_packet_t *packet)
param0 = packet->words[1];
param1 = packet->words[2];
app_suspend_tec_modulation_spi();
switch (opcode)
{
case APP_AD9102_WAVE_OPCODE_BEGIN:
@ -1105,6 +1400,7 @@ static void app_handle_ad9102_wave_control_packet(const app_packet_t *packet)
app_set_error(APP_STATUS_FLAG_AD9102_ERROR);
break;
}
app_resume_tec_modulation_spi();
app_prepare_status_response();
}
@ -1119,11 +1415,13 @@ static void app_handle_ad9102_wave_data_packet(const app_packet_t *packet)
return;
}
app_suspend_tec_modulation_spi();
if (!ad9102_write_custom_chunk(&packet->words[1], packet->words[0]))
{
ad9102_cancel_custom_upload();
app_set_error(APP_STATUS_FLAG_AD9102_ERROR);
}
app_resume_tec_modulation_spi();
app_prepare_status_response();
}
@ -1213,6 +1511,10 @@ static void app_handle_packet(const app_packet_t *packet)
app_handle_stm32_dac_control_packet(packet);
break;
case APP_PACKET_KIND_TEC_MODULATION_CONTROL:
app_handle_tec_modulation_control_packet(packet);
break;
case APP_PACKET_KIND_AD9102_WAVE_CONTROL:
app_handle_ad9102_wave_control_packet(packet);
break;
@ -1359,6 +1661,8 @@ void app_init(void)
board_io_reset_runtime_outputs();
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
LL_TIM_EnableIT_UPDATE(TIM5);
LL_TIM_EnableCounter(TIM5);
LL_TIM_EnableIT_UPDATE(TIM6);
LL_TIM_EnableCounter(TIM6);
LL_TIM_EnableIT_UPDATE(TIM7);
@ -1469,6 +1773,11 @@ void app_on_tim7_tick(void)
++g_app.tick_1ms;
}
void app_on_tim5_tick(void)
{
app_write_tec_modulation_sample();
}
void app_on_dma_tx_complete(void)
{
uart_transport_mark_dma_complete();

View File

@ -53,6 +53,11 @@ void app_on_tim6_tick(void);
*/
void app_on_tim7_tick(void);
/**
* @brief Notify the application core about the fast TEC modulation tick.
*/
void app_on_tim5_tick(void);
/**
* @brief Notify the application core that the USART DMA transmit finished.
*/