saving config added
This commit is contained in:
+43
-23
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user