saving config added

This commit is contained in:
Ayzen
2026-10-13 17:49:53 +03:00
parent 4548c1f2a0
commit f7d9d1ce0e
7 changed files with 167 additions and 50 deletions
+43 -23
View File
@@ -202,6 +202,7 @@ static void app_write_tec_modulation_sample(void);
static void app_set_stm32_dac(uint16_t code, uint8_t enabled);
static void app_update_sweep_timer(void);
static void app_stop_sweeps(void);
static bool app_apply_sweep_config(sweep_t *sweep, const sweep_config_t *config);
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);
@@ -594,6 +595,34 @@ static void app_stop_sweeps(void)
app_update_sweep_timer();
}
/**
* @brief Stop @p sweep and restart it with @p config when enabled.
*
* @return false if an enabled configuration could not be started.
*/
static bool app_apply_sweep_config(sweep_t *sweep, const sweep_config_t *config)
{
const bool dac_target = (sweep == &g_app.stm32_dac_sweep);
bool started = true;
sweep_stop(sweep);
if (config->enabled != 0u)
{
/* The laser current is only driven in WORK mode; idle entry stops the sweep again. */
started = dac_target ? (config->max_code <= STM32_DAC_MAX_CODE) : (g_app.mode == APP_MODE_WORK);
__disable_irq();
started = started && sweep_start(sweep, config, APP_SWEEP_TICK_US);
__enable_irq();
}
if (dac_target && (sweep->enabled == 0u))
{
stm32_dac_output_set(g_app.stm32_dac.code, g_app.stm32_dac.enabled);
}
app_update_sweep_timer();
return started;
}
static void app_apply_profile_auxiliary_outputs(const profile_t *profile)
{
if (profile == NULL)
@@ -603,6 +632,10 @@ static void app_apply_profile_auxiliary_outputs(const profile_t *profile)
ad9833_apply(profile->ad9833.enabled, profile->ad9833.triangle, profile->ad9833.frequency_word);
app_set_stm32_dac(profile->stm32_dac.code, profile->stm32_dac.enabled);
if (!app_apply_sweep_config(&g_app.stm32_dac_sweep, &profile->stm32_dac_sweep))
{
app_set_error(APP_STATUS_FLAG_SD_ERROR);
}
}
static void app_prepare_status_response(void)
@@ -676,6 +709,10 @@ static bool app_activate_profile(const profile_t *profile, uint16_t index, bool
app_apply_work_config_to_board();
app_set_mode(APP_MODE_WORK);
g_app.resume_mode = APP_MODE_WORK;
if (!app_apply_sweep_config(&g_app.laser2_current_sweep, &profile->laser2_current_sweep))
{
app_set_error(APP_STATUS_FLAG_SD_ERROR);
}
}
if (profile->ds1809.apply_position)
@@ -1386,8 +1423,7 @@ static void app_handle_sweep_control_packet(const app_packet_t *packet)
{
sweep_config_t config;
sweep_t *sweep;
bool dac_target;
bool started = true;
uint16_t flags;
if ((packet == NULL) || !packet->checksum_valid ||
((packet->words[0] & ~APP_SWEEP_ALLOWED_FLAGS) != 0u))
@@ -1397,32 +1433,16 @@ static void app_handle_sweep_control_packet(const app_packet_t *packet)
return;
}
dac_target = (packet->words[0] & APP_SWEEP_FLAG_TARGET_STM32_DAC) != 0u;
sweep = dac_target ? &g_app.stm32_dac_sweep : &g_app.laser2_current_sweep;
sweep_stop(sweep);
if ((packet->words[0] & APP_SWEEP_FLAG_ENABLE) != 0u)
{
flags = packet->words[0];
sweep = ((flags & APP_SWEEP_FLAG_TARGET_STM32_DAC) != 0u) ? &g_app.stm32_dac_sweep
: &g_app.laser2_current_sweep;
config.enabled = ((flags & APP_SWEEP_FLAG_ENABLE) != 0u) ? 1u : 0u;
config.min_code = packet->words[1];
config.max_code = packet->words[2];
config.step_code = packet->words[3];
config.step_period_us = packet->words[4];
config.pause_ms = packet->words[5];
/* The laser current is only driven in WORK mode; idle entry stops the sweep again. */
started = dac_target ? (config.max_code <= STM32_DAC_MAX_CODE) : (g_app.mode == APP_MODE_WORK);
__disable_irq();
started = started && sweep_start(sweep, &config, APP_SWEEP_TICK_US);
__enable_irq();
}
if (dac_target && (sweep->enabled == 0u))
{
stm32_dac_output_set(g_app.stm32_dac.code, g_app.stm32_dac.enabled);
}
app_update_sweep_timer();
if (!started)
if (!app_apply_sweep_config(sweep, &config))
{
app_set_error(APP_STATUS_FLAG_UART_DECODE_ERROR);
}
+18
View File
@@ -347,6 +347,22 @@ typedef struct tec_modulation_config_t {
uint16_t amplitude_code;
} tec_modulation_config_t;
/**
* @brief Periodic staircase sweep of one output code.
*
* The output steps from `min_code` up to `max_code` (inclusive) every
* `step_period_us`, then holds `min_code` for an extra `pause_ms` before the
* next sweep starts.
*/
typedef struct sweep_config_t {
uint8_t enabled;
uint16_t min_code;
uint16_t max_code;
uint16_t step_code;
uint16_t step_period_us;
uint16_t pause_ms;
} sweep_config_t;
/**
* @brief Absolute DS1809 target expressed as a number of steps above the minimum tap.
*/
@@ -371,6 +387,8 @@ typedef struct profile_t {
stm32_dac_config_t stm32_dac;
tec_modulation_config_t tec_modulation;
ds1809_config_t ds1809;
sweep_config_t laser2_current_sweep;
sweep_config_t stm32_dac_sweep;
} profile_t;
/**
+60 -5
View File
@@ -13,7 +13,8 @@
#include "ad9102_device.h"
#include "storage_sd.h"
#define PROFILE_REPOSITORY_BUFFER_SIZE 1024u
#define PROFILE_REPOSITORY_INDEX_BUFFER_SIZE 1024u
#define PROFILE_REPOSITORY_INI_BUFFER_SIZE 4096u
static char *profile_repository_trim(char *text)
{
@@ -114,6 +115,52 @@ static void profile_repository_reset_profile(profile_t *profile)
profile->ds1809.apply_position = false;
}
/**
* @brief Apply a `<prefix><field>=value` sweep key.
*
* @return true when @p key carries @p prefix, i.e. belongs to this sweep.
*/
static bool profile_repository_apply_sweep_key(sweep_config_t *sweep,
const char *prefix,
const char *key,
const char *value)
{
const size_t prefix_length = strlen(prefix);
const char *field;
if (strncmp(key, prefix, prefix_length) != 0)
{
return false;
}
field = key + prefix_length;
if (strcmp(field, "enable") == 0)
{
sweep->enabled = (uint8_t)profile_repository_parse_u16(value);
}
else if (strcmp(field, "min_code") == 0)
{
sweep->min_code = profile_repository_parse_u16(value);
}
else if (strcmp(field, "max_code") == 0)
{
sweep->max_code = profile_repository_parse_u16(value);
}
else if (strcmp(field, "step_code") == 0)
{
sweep->step_code = profile_repository_parse_u16(value);
}
else if (strcmp(field, "step_period_us") == 0)
{
sweep->step_period_us = profile_repository_parse_u16(value);
}
else if (strcmp(field, "pause_ms") == 0)
{
sweep->pause_ms = profile_repository_parse_u16(value);
}
return true;
}
static void profile_repository_apply_key_value(profile_t *profile, const char *key, const char *value)
{
bool bool_value;
@@ -123,6 +170,12 @@ static void profile_repository_apply_key_value(profile_t *profile, const char *k
return;
}
if (profile_repository_apply_sweep_key(&profile->laser2_current_sweep, "laser2_current_sweep_", key, value) ||
profile_repository_apply_sweep_key(&profile->stm32_dac_sweep, "stm32_dac_sweep_", key, value))
{
return;
}
if (strcmp(key, "profile_name") == 0)
{
profile_repository_copy_string(profile->display_name, sizeof(profile->display_name), value);
@@ -351,13 +404,15 @@ static void profile_repository_apply_key_value(profile_t *profile, const char *k
static bool profile_repository_load_ini_file(const char *profile_path, profile_t *profile)
{
char buffer[PROFILE_REPOSITORY_BUFFER_SIZE];
/* Static: profiles are parsed only from the main loop, and the stack already holds FatFs' FIL. */
static char buffer[PROFILE_REPOSITORY_INI_BUFFER_SIZE];
UINT bytes_read = 0u;
char *cursor;
FRESULT result;
result = storage_sd_read_bytes(profile_path, 0u, buffer, sizeof(buffer) - 1u, &bytes_read);
if (result != FR_OK)
result = storage_sd_read_bytes(profile_path, 0u, buffer, sizeof(buffer), &bytes_read);
/* A file that fills the whole buffer leaves no room for the terminator: reject it rather than drop keys. */
if ((result != FR_OK) || (bytes_read >= sizeof(buffer)))
{
return false;
}
@@ -412,7 +467,7 @@ static bool profile_repository_load_by_valid_line_index(uint16_t target_index,
profile_t *out_profile,
uint16_t *out_total_valid_lines)
{
char buffer[PROFILE_REPOSITORY_BUFFER_SIZE];
char buffer[PROFILE_REPOSITORY_INDEX_BUFFER_SIZE];
UINT bytes_read = 0u;
char *cursor;
uint16_t valid_line_index = 0u;
+5 -16
View File
@@ -1,10 +1,6 @@
/**
* @file sweep.h
* @brief Hardware-agnostic periodic staircase sweep between two output codes.
*
* The output steps from `min_code` up to `max_code` (inclusive) every step
* period, then returns to `min_code` and holds it for an extra pause before
* the next sweep starts.
* @brief Hardware-agnostic periodic staircase sweep (see sweep_config_t).
*/
#ifndef SWEEP_H
@@ -13,16 +9,7 @@
#include <stdbool.h>
#include <stdint.h>
/**
* @brief Host-supplied sweep parameters.
*/
typedef struct sweep_config_t {
uint16_t min_code;
uint16_t max_code;
uint16_t step_code;
uint16_t step_period_us;
uint16_t pause_ms;
} sweep_config_t;
#include "app_types.h"
/**
* @brief Sweep state advanced from the periodic tick interrupt.
@@ -39,7 +26,9 @@ typedef struct sweep_t {
} sweep_t;
/**
* @brief Validate the configuration and start sweeping from `min_code` on the next tick.
* @brief Validate the sweep parameters and start from `min_code` on the next tick.
*
* The `enabled` field of @p config is not consulted: whether to start is the caller's decision.
*
* @return false (sweep left stopped) if bounds, step or period are invalid.
*/
+8 -1
View File
@@ -35,6 +35,7 @@ Profile `.ini` format:
- Empty lines are ignored.
- Lines starting with `#` or `;` are ignored.
- Unknown keys are ignored.
- The whole file must be smaller than 4096 bytes; a larger profile fails to load instead of being truncated.
Additional profile-controlled devices:
@@ -44,12 +45,18 @@ Additional profile-controlled devices:
- TEC drive modulation is stored through `tec_modulation_enable`, `tec_modulation_laser`,
`tec_modulation_frequency_hz`, and `tec_modulation_amplitude_code`.
- `DS1809` is stored as an absolute target above the minimum tap using `ds1809_apply` and `ds1809_position_from_min`.
- Laser 2 current and STM32 DAC sweeps are stored through the `laser2_current_sweep_*` and `stm32_dac_sweep_*` keys:
`_enable`, `_min_code`, `_max_code`, `_step_code`, `_step_period_us`, and `_pause_ms`.
Notes:
- `ad9833_frequency_word` uses the same raw 28-bit tuning word that the old serial command accepted.
- `tec_modulation_laser` is `1` or `2`; accepted TEC modulation frequency is `50..2000 Hz`,
and peak amplitude is `0..4096` external TEC DAC codes.
and peak amplitude is `0..27900` external TEC DAC codes (further limited by the headroom around the PID output).
- A sweep steps from `_min_code` up to `_max_code` (inclusive) by `_step_code` every `_step_period_us`
(at least `10`, rounded down to a multiple of 10 us), then holds `_min_code` for `_pause_ms` before repeating.
- Sweep codes are raw DAC codes: the laser current DAC uses the same units as `laser2_current`, the STM32 DAC `0..4095`.
- The laser 2 current sweep only runs in `auto_run=true` profiles, because it needs the laser to be driven.
- `ds1809_position_from_min` is clamped to the valid `0..63` range by the firmware.
- When `ds1809_apply=true`, the firmware first drives the potentiometer fully down and then steps it up to the requested absolute position.
+14
View File
@@ -50,3 +50,17 @@ tec_modulation_amplitude_code=0
ds1809_apply=true
ds1809_position_from_min=0
laser2_current_sweep_enable=0
laser2_current_sweep_min_code=29490
laser2_current_sweep_max_code=39321
laser2_current_sweep_step_code=49
laser2_current_sweep_step_period_us=50
laser2_current_sweep_pause_ms=5
stm32_dac_sweep_enable=0
stm32_dac_sweep_min_code=0
stm32_dac_sweep_max_code=4095
stm32_dac_sweep_step_code=16
stm32_dac_sweep_step_period_us=50
stm32_dac_sweep_pause_ms=5
+14
View File
@@ -54,3 +54,17 @@ tec_modulation_amplitude_code=0
ds1809_apply=true
ds1809_position_from_min=18
laser2_current_sweep_enable=0
laser2_current_sweep_min_code=29490
laser2_current_sweep_max_code=39321
laser2_current_sweep_step_code=49
laser2_current_sweep_step_period_us=50
laser2_current_sweep_pause_ms=5
stm32_dac_sweep_enable=0
stm32_dac_sweep_min_code=0
stm32_dac_sweep_max_code=4095
stm32_dac_sweep_step_code=16
stm32_dac_sweep_step_period_us=50
stm32_dac_sweep_pause_ms=5