big refactoring and features added

This commit is contained in:
Ayzen
2026-04-24 16:51:15 +03:00
parent eafc328caa
commit ea1fbb071d
184 changed files with 35336 additions and 75480 deletions

716
App/Devices/ad9102_device.c Normal file
View File

@ -0,0 +1,716 @@
/**
* @file ad9102_device.c
* @brief AD9102 waveform-generation device driver.
*/
#include "ad9102_device.h"
#include "board_io.h"
#include "main.h"
/* AD9102 register map used by the existing firmware. See ad9102.pdf. */
#define AD9102_REG_SPICONFIG 0x0000u
#define AD9102_REG_POWERCONFIG 0x0001u
#define AD9102_REG_CLOCKCONFIG 0x0002u
#define AD9102_REG_RAMUPDATE 0x001Du
#define AD9102_REG_PAT_STATUS 0x001Eu
#define AD9102_REG_PAT_TYPE 0x001Fu
#define AD9102_REG_WAV_CONFIG 0x0027u
#define AD9102_REG_PAT_TIMEBASE 0x0028u
#define AD9102_REG_PAT_PERIOD 0x0029u
#define AD9102_REG_DAC_PAT 0x002Bu
#define AD9102_REG_SAW_CONFIG 0x0037u
#define AD9102_REG_START_DLY 0x005Cu
#define AD9102_REG_START_ADDR 0x005Du
#define AD9102_REG_STOP_ADDR 0x005Eu
#define AD9102_REG_CFG_ERROR 0x0060u
#define AD9102_REG_SRAM_DATA_BASE 0x6000u
#define AD9102_PAT_STATUS_RUN (1u << 0)
#define AD9102_SAW_TYPE_UP 0u
#define AD9102_SAW_TYPE_TRIANGLE 2u
#define AD9102_SAW_STEP_DEFAULT 1u
#define AD9102_PAT_PERIOD_BASE_DEFAULT 0x2u
#define AD9102_START_DELAY_BASE_DEFAULT 0x1u
#define AD9102_PAT_TIMEBASE_HOLD_DEFAULT 0x1u
#define AD9102_PAT_PERIOD_DEFAULT 0xFFFFu
#define AD9102_EX4_WAV_CONFIG 0x3212u
#define AD9102_EX4_SAW_CONFIG 0x0606u
#define AD9102_EX2_WAV_CONFIG 0x3030u
#define AD9102_EX2_DAC_PAT 0x0101u
#define AD9102_EX2_SAW_CONFIG 0x0200u
#define AD9102_SRAM_PAT_PERIOD_BASE_DEFAULT 0x1u
#define AD9102_SRAM_START_DELAY_BASE_DEFAULT 0x1u
#define AD9102_SRAM_START_DLY_DEFAULT 0x0000u
#define AD9102_SRAM_RAMP_MIN (-8192)
#define AD9102_SRAM_RAMP_MAX (8191)
#define AD9102_REG_COUNT 66u
#define AD9102_WAVE_MAX_CHUNK_SAMPLES 12u
static const uint16_t g_ad9102_reg_addr[AD9102_REG_COUNT] = {
0x0000u, 0x0001u, 0x0002u, 0x0003u, 0x0004u, 0x0005u, 0x0006u, 0x0007u,
0x0008u, 0x0009u, 0x000Au, 0x000Bu, 0x000Cu, 0x000Du, 0x000Eu, 0x001Fu,
0x0020u, 0x0022u, 0x0023u, 0x0024u, 0x0025u, 0x0026u, 0x0027u, 0x0028u,
0x0029u, 0x002Au, 0x002Bu, 0x002Cu, 0x002Du, 0x002Eu, 0x002Fu, 0x0030u,
0x0031u, 0x0032u, 0x0033u, 0x0034u, 0x0035u, 0x0036u, 0x0037u, 0x003Eu,
0x003Fu, 0x0040u, 0x0041u, 0x0042u, 0x0043u, 0x0044u, 0x0045u, 0x0047u,
0x0050u, 0x0051u, 0x0052u, 0x0053u, 0x0054u, 0x0055u, 0x0056u, 0x0057u,
0x0058u, 0x0059u, 0x005Au, 0x005Bu, 0x005Cu, 0x005Du, 0x005Eu, 0x005Fu,
0x001Eu, 0x001Du
};
static const uint16_t g_ad9102_example4_regval[AD9102_REG_COUNT] = {
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x4000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x1F00u, 0x0000u, 0x0000u, 0x0000u,
0x000Eu, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x3212u, 0x0121u,
0xFFFFu, 0x0000u, 0x0101u, 0x0003u, 0x0000u, 0x0000u, 0x0000u, 0x0000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x4000u, 0x0000u, 0x0606u, 0x1999u,
0x9A00u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u,
0x0FA0u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x16FFu,
0x0001u, 0x0001u
};
static const uint16_t g_ad9102_example2_regval[AD9102_REG_COUNT] = {
0x0000u, 0x0E00u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x4000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x1F00u, 0x0000u, 0x0000u, 0x0000u,
0x000Eu, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x3030u, 0x0111u,
0xFFFFu, 0x0000u, 0x0101u, 0x0003u, 0x0000u, 0x0000u, 0x0000u, 0x0000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x4000u, 0x0000u, 0x0200u, 0x0000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u,
0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0FA0u, 0x0000u, 0x3FF0u, 0x0100u,
0x0001u, 0x0001u
};
typedef struct ad9102_upload_state_t {
uint8_t active;
uint16_t expected_samples;
uint16_t written_samples;
} ad9102_upload_state_t;
static ad9102_upload_state_t g_upload_state;
static void ad9102_write_reg(uint16_t address, uint16_t value)
{
uint16_t command = (uint16_t)(address & 0x7FFFu);
uint32_t timeout = 0u;
board_io_configure_spi2_mode(LL_SPI_POLARITY_LOW, LL_SPI_PHASE_1EDGE);
HAL_GPIO_WritePin(DAC_LD1_CS_GPIO_Port, DAC_LD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC1_CS_GPIO_Port, DAC_TEC1_CS_Pin, GPIO_PIN_SET);
if (!LL_SPI_IsEnabled(SPI2))
{
LL_SPI_Enable(SPI2);
}
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_RESET);
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout++ < 1000u))
{
}
LL_SPI_TransmitData16(SPI2, command);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout++ < 1000u))
{
}
(void)SPI2->DR;
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout++ < 1000u))
{
}
LL_SPI_TransmitData16(SPI2, value);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout++ < 1000u))
{
}
(void)SPI2->DR;
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_SET);
}
static uint16_t ad9102_read_reg(uint16_t address)
{
uint16_t command = (uint16_t)(0x8000u | (address & 0x7FFFu));
uint16_t value;
uint32_t timeout = 0u;
board_io_configure_spi2_mode(LL_SPI_POLARITY_LOW, LL_SPI_PHASE_1EDGE);
HAL_GPIO_WritePin(DAC_LD1_CS_GPIO_Port, DAC_LD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC1_CS_GPIO_Port, DAC_TEC1_CS_Pin, GPIO_PIN_SET);
if (!LL_SPI_IsEnabled(SPI2))
{
LL_SPI_Enable(SPI2);
}
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_RESET);
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout++ < 1000u))
{
}
LL_SPI_TransmitData16(SPI2, command);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout++ < 1000u))
{
}
(void)SPI2->DR;
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout++ < 1000u))
{
}
LL_SPI_TransmitData16(SPI2, 0x0000u);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout++ < 1000u))
{
}
value = LL_SPI_ReceiveData16(SPI2);
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_SET);
return value;
}
static void ad9102_write_reg_table(const uint16_t *values, uint16_t count)
{
uint16_t index;
for (index = 0u; index < count; ++index)
{
ad9102_write_reg(g_ad9102_reg_addr[index], values[index]);
}
}
static void ad9102_reset_upload_state(void)
{
g_upload_state.active = 0u;
g_upload_state.expected_samples = 0u;
g_upload_state.written_samples = 0u;
}
static void ad9102_start_output(void)
{
HAL_GPIO_WritePin(AD9102_TRIG_GPIO_Port, AD9102_TRIG_Pin, GPIO_PIN_SET);
ad9102_write_reg(AD9102_REG_PAT_STATUS, AD9102_PAT_STATUS_RUN);
ad9102_write_reg(AD9102_REG_RAMUPDATE, 0x0001u);
for (volatile uint32_t delay_counter = 0u; delay_counter < 1000u; ++delay_counter)
{
}
HAL_GPIO_WritePin(AD9102_TRIG_GPIO_Port, AD9102_TRIG_Pin, GPIO_PIN_RESET);
}
static void ad9102_configure_sram_playback(uint16_t sample_count, uint8_t hold_cycles)
{
uint16_t pat_timebase;
uint32_t pat_period;
if (sample_count < 2u)
{
sample_count = 2u;
}
if (sample_count > AD9102_SRAM_MAX_SAMPLE_COUNT)
{
sample_count = AD9102_SRAM_MAX_SAMPLE_COUNT;
}
if (hold_cycles == 0u)
{
hold_cycles = AD9102_SRAM_DEFAULT_HOLD;
}
if (hold_cycles > 0x0Fu)
{
hold_cycles = 0x0Fu;
}
pat_timebase = (uint16_t)(((uint16_t)(hold_cycles & 0x0Fu) << 8) |
((AD9102_SRAM_PAT_PERIOD_BASE_DEFAULT & 0x0Fu) << 4) |
(AD9102_SRAM_START_DELAY_BASE_DEFAULT & 0x0Fu));
pat_period = (uint32_t)sample_count * (uint32_t)(hold_cycles & 0x0Fu);
if (pat_period == 0u)
{
pat_period = sample_count;
}
if (pat_period > 0xFFFFu)
{
pat_period = 0xFFFFu;
}
ad9102_write_reg_table(g_ad9102_example2_regval, AD9102_REG_COUNT);
ad9102_stop_output();
ad9102_write_reg(AD9102_REG_WAV_CONFIG, AD9102_EX2_WAV_CONFIG);
ad9102_write_reg(AD9102_REG_SAW_CONFIG, AD9102_EX2_SAW_CONFIG);
ad9102_write_reg(AD9102_REG_DAC_PAT, AD9102_EX2_DAC_PAT);
ad9102_write_reg(AD9102_REG_PAT_TIMEBASE, pat_timebase);
ad9102_write_reg(AD9102_REG_PAT_PERIOD, (uint16_t)pat_period);
ad9102_write_reg(AD9102_REG_PAT_TYPE, 0x0000u);
ad9102_write_reg(AD9102_REG_START_DLY, AD9102_SRAM_START_DLY_DEFAULT);
ad9102_write_reg(AD9102_REG_START_ADDR, 0x0000u);
ad9102_write_reg(AD9102_REG_STOP_ADDR, (uint16_t)((sample_count - 1u) << 4));
ad9102_write_reg(AD9102_REG_RAMUPDATE, 0x0001u);
}
static void ad9102_load_sram_ramp(uint16_t sample_count, uint8_t triangle_mode, uint16_t amplitude)
{
uint16_t sample_index;
if (sample_count < 2u)
{
sample_count = 2u;
}
if (sample_count > AD9102_SRAM_MAX_SAMPLE_COUNT)
{
sample_count = AD9102_SRAM_MAX_SAMPLE_COUNT;
}
if (amplitude > AD9102_SRAM_DEFAULT_AMPLITUDE)
{
amplitude = AD9102_SRAM_DEFAULT_AMPLITUDE;
}
ad9102_write_reg(AD9102_REG_PAT_STATUS, 0x0004u);
for (sample_index = 0u; sample_index < sample_count; ++sample_index)
{
int32_t value;
int32_t min_value = -(int32_t)amplitude;
int32_t max_value = (int32_t)amplitude;
int32_t span = max_value - min_value;
if (triangle_mode != 0u)
{
uint16_t half = sample_count / 2u;
if (half == 0u)
{
half = 1u;
}
if (sample_index < half)
{
uint16_t denominator = (half > 1u) ? (uint16_t)(half - 1u) : 1u;
value = min_value + (span * (int32_t)sample_index) / (int32_t)denominator;
}
else
{
uint16_t tail = (uint16_t)(sample_count - half);
uint16_t denominator = (tail > 1u) ? (uint16_t)(tail - 1u) : 1u;
value = max_value - (span * (int32_t)(sample_index - half)) / (int32_t)denominator;
}
}
else
{
uint16_t denominator = (sample_count > 1u) ? (uint16_t)(sample_count - 1u) : 1u;
value = min_value + (span * (int32_t)sample_index) / (int32_t)denominator;
}
if (value < AD9102_SRAM_RAMP_MIN)
{
value = AD9102_SRAM_RAMP_MIN;
}
else if (value > AD9102_SRAM_RAMP_MAX)
{
value = AD9102_SRAM_RAMP_MAX;
}
ad9102_write_reg((uint16_t)(AD9102_REG_SRAM_DATA_BASE + sample_index),
(uint16_t)(((uint16_t)((int16_t)value) & 0x3FFFu) << 2));
}
ad9102_write_reg(AD9102_REG_PAT_STATUS, 0x0000u);
}
void ad9102_init(void)
{
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AD9102_RESET_GPIO_Port, AD9102_RESET_Pin, GPIO_PIN_RESET);
for (volatile uint32_t delay_counter = 0u; delay_counter < 1000u; ++delay_counter)
{
}
HAL_GPIO_WritePin(AD9102_RESET_GPIO_Port, AD9102_RESET_Pin, GPIO_PIN_SET);
ad9102_write_reg_table(g_ad9102_example4_regval, AD9102_REG_COUNT);
ad9102_write_reg(AD9102_REG_PAT_STATUS, 0x0000u);
ad9102_write_reg(AD9102_REG_RAMUPDATE, 0x0001u);
HAL_GPIO_WritePin(AD9102_TRIG_GPIO_Port, AD9102_TRIG_Pin, GPIO_PIN_SET);
ad9102_reset_upload_state();
}
void ad9102_stop_output(void)
{
ad9102_write_reg(AD9102_REG_PAT_STATUS, 0x0000u);
HAL_GPIO_WritePin(AD9102_TRIG_GPIO_Port, AD9102_TRIG_Pin, GPIO_PIN_SET);
}
uint16_t ad9102_apply_saw(uint8_t saw_type,
uint8_t enabled,
uint8_t saw_step,
uint8_t pat_period_base,
uint16_t pat_period)
{
uint16_t saw_config;
uint16_t pat_timebase;
ad9102_reset_upload_state();
if (enabled == 0u)
{
ad9102_stop_output();
return ad9102_read_reg(AD9102_REG_PAT_STATUS);
}
if (saw_step == 0u)
{
saw_step = AD9102_SAW_STEP_DEFAULT;
}
else if (saw_step > 63u)
{
saw_step = 63u;
}
if (pat_period == 0u)
{
pat_period = AD9102_PAT_PERIOD_DEFAULT;
}
saw_config = (uint16_t)(((uint16_t)(saw_step & 0x3Fu) << 2) | ((uint16_t)(saw_type & 0x3u)));
pat_timebase = (uint16_t)(((AD9102_PAT_TIMEBASE_HOLD_DEFAULT & 0x0Fu) << 8) |
((pat_period_base & 0x0Fu) << 4) |
(AD9102_START_DELAY_BASE_DEFAULT & 0x0Fu));
ad9102_write_reg(AD9102_REG_WAV_CONFIG, AD9102_EX4_WAV_CONFIG);
ad9102_write_reg(AD9102_REG_SAW_CONFIG, saw_config);
ad9102_write_reg(AD9102_REG_PAT_TIMEBASE, pat_timebase);
ad9102_write_reg(AD9102_REG_PAT_PERIOD, pat_period);
ad9102_write_reg(AD9102_REG_PAT_TYPE, 0x0000u);
ad9102_start_output();
return ad9102_read_reg(AD9102_REG_PAT_STATUS);
}
uint16_t ad9102_apply_generated_sram(uint8_t enabled,
uint16_t sample_count,
uint8_t hold_cycles,
uint8_t triangle_mode,
uint16_t amplitude)
{
ad9102_reset_upload_state();
if (sample_count == 0u)
{
sample_count = AD9102_SRAM_DEFAULT_SAMPLE_COUNT;
}
if (sample_count < 2u)
{
sample_count = 2u;
}
if (sample_count > AD9102_SRAM_MAX_SAMPLE_COUNT)
{
sample_count = AD9102_SRAM_MAX_SAMPLE_COUNT;
}
if (hold_cycles == 0u)
{
hold_cycles = AD9102_SRAM_DEFAULT_HOLD;
}
if (hold_cycles > 0x0Fu)
{
hold_cycles = 0x0Fu;
}
if (amplitude > AD9102_SRAM_DEFAULT_AMPLITUDE)
{
amplitude = AD9102_SRAM_DEFAULT_AMPLITUDE;
}
ad9102_configure_sram_playback(sample_count, hold_cycles);
ad9102_load_sram_ramp(sample_count, triangle_mode, amplitude);
if (enabled != 0u)
{
ad9102_start_output();
}
else
{
ad9102_stop_output();
}
return ad9102_read_reg(AD9102_REG_PAT_STATUS);
}
bool ad9102_begin_custom_upload(uint16_t sample_count)
{
if ((sample_count < 2u) || (sample_count > AD9102_SRAM_MAX_SAMPLE_COUNT))
{
return false;
}
ad9102_stop_output();
ad9102_reset_upload_state();
ad9102_configure_sram_playback(sample_count, AD9102_SRAM_DEFAULT_HOLD);
ad9102_write_reg(AD9102_REG_PAT_STATUS, 0x0004u);
g_upload_state.expected_samples = sample_count;
g_upload_state.written_samples = 0u;
g_upload_state.active = 1u;
return true;
}
bool ad9102_write_custom_chunk(const uint16_t *samples, uint16_t chunk_count)
{
uint16_t index;
if ((samples == NULL) || (g_upload_state.active == 0u))
{
return false;
}
if ((chunk_count == 0u) || (chunk_count > AD9102_WAVE_MAX_CHUNK_SAMPLES))
{
return false;
}
if (((uint32_t)g_upload_state.written_samples + (uint32_t)chunk_count) >
(uint32_t)g_upload_state.expected_samples)
{
return false;
}
for (index = 0u; index < chunk_count; ++index)
{
int16_t sample = (int16_t)samples[index];
if ((sample < AD9102_SRAM_RAMP_MIN) || (sample > AD9102_SRAM_RAMP_MAX))
{
return false;
}
ad9102_write_reg((uint16_t)(AD9102_REG_SRAM_DATA_BASE + g_upload_state.written_samples + index),
(uint16_t)(((uint16_t)sample & 0x3FFFu) << 2));
}
g_upload_state.written_samples = (uint16_t)(g_upload_state.written_samples + chunk_count);
return true;
}
uint16_t ad9102_commit_custom_upload(bool *out_ok)
{
uint16_t pat_status;
if (out_ok != NULL)
{
*out_ok = false;
}
if ((g_upload_state.active == 0u) ||
(g_upload_state.expected_samples < 2u) ||
(g_upload_state.written_samples != g_upload_state.expected_samples))
{
ad9102_cancel_custom_upload();
return ad9102_read_reg(AD9102_REG_PAT_STATUS);
}
ad9102_write_reg(AD9102_REG_PAT_STATUS, 0x0000u);
ad9102_write_reg(AD9102_REG_START_ADDR, 0x0000u);
ad9102_write_reg(AD9102_REG_STOP_ADDR, (uint16_t)((g_upload_state.expected_samples - 1u) << 4));
ad9102_write_reg(AD9102_REG_RAMUPDATE, 0x0001u);
ad9102_start_output();
pat_status = ad9102_read_reg(AD9102_REG_PAT_STATUS);
ad9102_reset_upload_state();
if (out_ok != NULL)
{
*out_ok = true;
}
return pat_status;
}
void ad9102_cancel_custom_upload(void)
{
if (g_upload_state.active != 0u)
{
ad9102_stop_output();
}
ad9102_reset_upload_state();
}
uint8_t ad9102_check_saw_configuration(uint16_t pat_status,
uint8_t expect_run,
uint8_t saw_type,
uint8_t saw_step,
uint8_t pat_period_base,
uint16_t pat_period)
{
uint16_t spiconfig = ad9102_read_reg(AD9102_REG_SPICONFIG);
uint16_t power_config = ad9102_read_reg(AD9102_REG_POWERCONFIG);
uint16_t clock_config = ad9102_read_reg(AD9102_REG_CLOCKCONFIG);
uint16_t config_error = ad9102_read_reg(AD9102_REG_CFG_ERROR);
uint16_t pat_timebase = (uint16_t)(((AD9102_PAT_TIMEBASE_HOLD_DEFAULT & 0x0Fu) << 8) |
((pat_period_base & 0x0Fu) << 4) |
(AD9102_START_DELAY_BASE_DEFAULT & 0x0Fu));
uint16_t expected_saw;
uint8_t ok = 1u;
if (saw_step == 0u)
{
saw_step = AD9102_SAW_STEP_DEFAULT;
}
if (saw_step > 63u)
{
saw_step = 63u;
}
if (pat_period == 0u)
{
pat_period = AD9102_PAT_PERIOD_DEFAULT;
}
expected_saw = (uint16_t)(((uint16_t)(saw_step & 0x3Fu) << 2) | ((uint16_t)(saw_type & 0x3u)));
if (spiconfig != 0x0000u)
{
ok = 0u;
}
if (power_config & ((1u << 8) | (1u << 7) | (1u << 6) | (1u << 5) | (1u << 3)))
{
ok = 0u;
}
if (clock_config & ((1u << 11) | (1u << 7) | (1u << 6) | (1u << 5)))
{
ok = 0u;
}
if (config_error & 0x003Fu)
{
ok = 0u;
}
if ((expect_run != 0u) && ((pat_status & AD9102_PAT_STATUS_RUN) == 0u))
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_WAV_CONFIG) != AD9102_EX4_WAV_CONFIG)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_PAT_TIMEBASE) != pat_timebase)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_PAT_PERIOD) != pat_period)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_PAT_TYPE) != 0x0000u)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_SAW_CONFIG) != expected_saw)
{
ok = 0u;
}
return (ok != 0u) ? 0u : 1u;
}
uint8_t ad9102_check_sram_configuration(uint16_t pat_status,
uint8_t expect_run,
uint16_t sample_count,
uint8_t hold_cycles)
{
uint16_t spiconfig = ad9102_read_reg(AD9102_REG_SPICONFIG);
uint16_t power_config = ad9102_read_reg(AD9102_REG_POWERCONFIG);
uint16_t clock_config = ad9102_read_reg(AD9102_REG_CLOCKCONFIG);
uint16_t config_error = ad9102_read_reg(AD9102_REG_CFG_ERROR);
uint16_t pat_timebase;
uint32_t pat_period;
uint16_t stop_address;
uint8_t ok = 1u;
if (sample_count == 0u)
{
sample_count = AD9102_SRAM_DEFAULT_SAMPLE_COUNT;
}
if (sample_count < 2u)
{
sample_count = 2u;
}
if (sample_count > AD9102_SRAM_MAX_SAMPLE_COUNT)
{
sample_count = AD9102_SRAM_MAX_SAMPLE_COUNT;
}
if (hold_cycles == 0u)
{
hold_cycles = AD9102_SRAM_DEFAULT_HOLD;
}
if (hold_cycles > 0x0Fu)
{
hold_cycles = 0x0Fu;
}
pat_timebase = (uint16_t)(((uint16_t)(hold_cycles & 0x0Fu) << 8) |
((AD9102_SRAM_PAT_PERIOD_BASE_DEFAULT & 0x0Fu) << 4) |
(AD9102_SRAM_START_DELAY_BASE_DEFAULT & 0x0Fu));
pat_period = (uint32_t)sample_count * (uint32_t)(hold_cycles & 0x0Fu);
if (pat_period == 0u)
{
pat_period = sample_count;
}
if (pat_period > 0xFFFFu)
{
pat_period = 0xFFFFu;
}
stop_address = (uint16_t)((sample_count - 1u) << 4);
if (spiconfig != 0x0000u)
{
ok = 0u;
}
if (power_config & ((1u << 8) | (1u << 7) | (1u << 6) | (1u << 5) | (1u << 3)))
{
ok = 0u;
}
if (clock_config & ((1u << 11) | (1u << 7) | (1u << 6) | (1u << 5)))
{
ok = 0u;
}
if (config_error & 0x003Fu)
{
ok = 0u;
}
if ((expect_run != 0u) && ((pat_status & AD9102_PAT_STATUS_RUN) == 0u))
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_WAV_CONFIG) != AD9102_EX2_WAV_CONFIG)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_PAT_TIMEBASE) != pat_timebase)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_PAT_PERIOD) != (uint16_t)pat_period)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_PAT_TYPE) != 0x0000u)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_START_ADDR) != 0x0000u)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_STOP_ADDR) != stop_address)
{
ok = 0u;
}
if (ad9102_read_reg(AD9102_REG_DAC_PAT) != AD9102_EX2_DAC_PAT)
{
ok = 0u;
}
return (ok != 0u) ? 0u : 1u;
}

View File

@ -0,0 +1,52 @@
/**
* @file ad9102_device.h
* @brief AD9102 waveform-generation device driver.
*
* Architectural note:
* The driver owns all AD9102 register tables and safe mode switching rules.
* High-level services describe *what* waveform to apply, while this module
* owns the register-level details of *how* to enter, verify, and leave each
* mode without cross-coupling to UART or storage logic.
*/
#ifndef AD9102_DEVICE_H
#define AD9102_DEVICE_H
#include <stdbool.h>
#include <stdint.h>
#include "app_types.h"
#define AD9102_SRAM_DEFAULT_HOLD 1u
#define AD9102_SRAM_DEFAULT_AMPLITUDE 8191u
#define AD9102_SRAM_DEFAULT_SAMPLE_COUNT 16u
#define AD9102_SRAM_MAX_SAMPLE_COUNT 4096u
void ad9102_init(void);
void ad9102_stop_output(void);
uint16_t ad9102_apply_saw(uint8_t saw_type,
uint8_t enabled,
uint8_t saw_step,
uint8_t pat_period_base,
uint16_t pat_period);
uint16_t ad9102_apply_generated_sram(uint8_t enabled,
uint16_t sample_count,
uint8_t hold_cycles,
uint8_t triangle_mode,
uint16_t amplitude);
bool ad9102_begin_custom_upload(uint16_t sample_count);
bool ad9102_write_custom_chunk(const uint16_t *samples, uint16_t chunk_count);
uint16_t ad9102_commit_custom_upload(bool *out_ok);
void ad9102_cancel_custom_upload(void);
uint8_t ad9102_check_saw_configuration(uint16_t pat_status,
uint8_t expect_run,
uint8_t saw_type,
uint8_t saw_step,
uint8_t pat_period_base,
uint16_t pat_period);
uint8_t ad9102_check_sram_configuration(uint16_t pat_status,
uint8_t expect_run,
uint16_t sample_count,
uint8_t hold_cycles);
#endif /* AD9102_DEVICE_H */

View File

@ -0,0 +1,64 @@
/**
* @file ad9833_device.c
* @brief AD9833 waveform-generator control helpers.
*/
#include "ad9833_device.h"
#include "board_io.h"
#include "main.h"
static void ad9833_write_word(uint16_t word)
{
uint32_t timeout = 0u;
board_io_configure_spi2_mode(LL_SPI_POLARITY_HIGH, LL_SPI_PHASE_1EDGE);
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_LD1_CS_GPIO_Port, DAC_LD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC1_CS_GPIO_Port, DAC_TEC1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AD9833_CS_GPIO_Port, AD9833_CS_Pin, GPIO_PIN_RESET);
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout++ < 1000u))
{
}
LL_SPI_TransmitData16(SPI2, word);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout++ < 1000u))
{
}
(void)SPI2->DR;
HAL_GPIO_WritePin(AD9833_CS_GPIO_Port, AD9833_CS_Pin, GPIO_PIN_SET);
}
void ad9833_apply(uint8_t enabled, uint8_t triangle_mode, uint32_t frequency_word)
{
uint16_t control = 0x2000u;
uint16_t lsw;
uint16_t msw;
if (triangle_mode != 0u)
{
control |= 0x0002u;
}
control |= 0x0100u;
frequency_word &= 0x0FFFFFFFu;
lsw = (uint16_t)(0x4000u | (frequency_word & 0x3FFFu));
msw = (uint16_t)(0x4000u | ((frequency_word >> 14) & 0x3FFFu));
ad9833_write_word(control);
ad9833_write_word(lsw);
ad9833_write_word(msw);
ad9833_write_word(0xC000u);
if (enabled != 0u)
{
control &= (uint16_t)(~0x0100u);
}
ad9833_write_word(control);
}

View File

@ -0,0 +1,13 @@
/**
* @file ad9833_device.h
* @brief AD9833 waveform generator control helpers.
*/
#ifndef AD9833_DEVICE_H
#define AD9833_DEVICE_H
#include <stdint.h>
void ad9833_apply(uint8_t enabled, uint8_t triangle_mode, uint32_t frequency_word);
#endif /* AD9833_DEVICE_H */

135
App/Devices/adc_mux.c Normal file
View File

@ -0,0 +1,135 @@
/**
* @file adc_mux.c
* @brief External photodiode ADC and internal STM32 ADC helpers.
*/
#include "adc_mux.h"
#include "board_handles.h"
#include "main.h"
uint16_t adc_mux_read_external_channel(uint8_t channel_index)
{
uint16_t result = 0u;
uint32_t delay_counter;
HAL_GPIO_WritePin(SPI4_CNV_GPIO_Port, SPI4_CNV_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SPI5_CNV_GPIO_Port, SPI5_CNV_Pin, GPIO_PIN_RESET);
for (delay_counter = 0u; delay_counter < 500u; ++delay_counter)
{
}
HAL_GPIO_WritePin(SPI4_CNV_GPIO_Port, SPI4_CNV_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(SPI5_CNV_GPIO_Port, SPI5_CNV_Pin, GPIO_PIN_SET);
for (delay_counter = 0u; delay_counter < 500u; ++delay_counter)
{
}
if (channel_index == 1u)
{
HAL_GPIO_WritePin(ADC_ThrLD1_CS_GPIO_Port, ADC_ThrLD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ADC_MPD1_CS_GPIO_Port, ADC_MPD1_CS_Pin, GPIO_PIN_RESET);
for (delay_counter = 0u; delay_counter < 500u; ++delay_counter)
{
}
LL_SPI_Enable(SPI4);
delay_counter = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI4)) && (delay_counter <= 1000u))
{
++delay_counter;
}
LL_SPI_Disable(SPI4);
HAL_GPIO_WritePin(ADC_MPD1_CS_GPIO_Port, ADC_MPD1_CS_Pin, GPIO_PIN_SET);
result = LL_SPI_ReceiveData16(SPI4);
}
else if (channel_index == 2u)
{
HAL_GPIO_WritePin(ADC_ThrLD2_CS_GPIO_Port, ADC_ThrLD2_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ADC_MPD2_CS_GPIO_Port, ADC_MPD2_CS_Pin, GPIO_PIN_RESET);
for (delay_counter = 0u; delay_counter < 500u; ++delay_counter)
{
}
LL_SPI_Enable(SPI5);
delay_counter = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI5)) && (delay_counter <= 1000u))
{
++delay_counter;
}
LL_SPI_Disable(SPI5);
HAL_GPIO_WritePin(ADC_MPD2_CS_GPIO_Port, ADC_MPD2_CS_Pin, GPIO_PIN_SET);
result = LL_SPI_ReceiveData16(SPI5);
}
else if (channel_index == 3u)
{
HAL_GPIO_WritePin(ADC_MPD1_CS_GPIO_Port, ADC_MPD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ADC_ThrLD1_CS_GPIO_Port, ADC_ThrLD1_CS_Pin, GPIO_PIN_RESET);
for (delay_counter = 0u; delay_counter < 500u; ++delay_counter)
{
}
LL_SPI_Enable(SPI4);
delay_counter = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI4)) && (delay_counter <= 1000u))
{
++delay_counter;
}
LL_SPI_Disable(SPI4);
HAL_GPIO_WritePin(ADC_ThrLD1_CS_GPIO_Port, ADC_ThrLD1_CS_Pin, GPIO_PIN_SET);
result = LL_SPI_ReceiveData16(SPI4);
}
else if (channel_index == 4u)
{
HAL_GPIO_WritePin(ADC_MPD2_CS_GPIO_Port, ADC_MPD2_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ADC_ThrLD2_CS_GPIO_Port, ADC_ThrLD2_CS_Pin, GPIO_PIN_RESET);
for (delay_counter = 0u; delay_counter < 500u; ++delay_counter)
{
}
LL_SPI_Enable(SPI5);
delay_counter = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI5)) && (delay_counter <= 1000u))
{
++delay_counter;
}
LL_SPI_Disable(SPI5);
HAL_GPIO_WritePin(ADC_ThrLD2_CS_GPIO_Port, ADC_ThrLD2_CS_Pin, GPIO_PIN_SET);
result = LL_SPI_ReceiveData16(SPI5);
}
return result;
}
uint16_t adc_mux_process_internal_adc_step(uint8_t step)
{
uint16_t output = 0u;
switch (step)
{
case 0u:
HAL_ADC_Start(&hadc1);
break;
case 1u:
HAL_ADC_PollForConversion(&hadc1, 100u);
output = HAL_ADC_GetValue(&hadc1);
break;
case 2u:
HAL_ADC_Stop(&hadc1);
break;
case 3u:
HAL_ADC_Start(&hadc3);
break;
case 4u:
HAL_ADC_PollForConversion(&hadc3, 100u);
output = HAL_ADC_GetValue(&hadc3);
break;
case 5u:
HAL_ADC_Stop(&hadc3);
break;
default:
break;
}
return output;
}

14
App/Devices/adc_mux.h Normal file
View File

@ -0,0 +1,14 @@
/**
* @file adc_mux.h
* @brief Helpers for external photodiode ADCs and internal STM32 ADC channels.
*/
#ifndef ADC_MUX_H
#define ADC_MUX_H
#include <stdint.h>
uint16_t adc_mux_read_external_channel(uint8_t channel_index);
uint16_t adc_mux_process_internal_adc_step(uint8_t step);
#endif /* ADC_MUX_H */

View File

@ -0,0 +1,22 @@
/**
* @file board_handles.h
* @brief Extern declarations for CubeMX-generated peripheral handles.
*
* Architectural note:
* Device and service modules depend on hardware handles owned by `Src/main.c`.
* This header isolates those extern declarations so the App layer never has to
* pull application types back into the CubeMX-generated file.
*/
#ifndef BOARD_HANDLES_H
#define BOARD_HANDLES_H
#include "main.h"
extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc3;
extern SD_HandleTypeDef hsd1;
extern TIM_HandleTypeDef htim1;
extern UART_HandleTypeDef huart8;
#endif /* BOARD_HANDLES_H */

257
App/Devices/board_io.c Normal file
View File

@ -0,0 +1,257 @@
/**
* @file board_io.c
* @brief Board-specific GPIO and shared low-level control helpers.
*/
#include "board_io.h"
#define UI_LCD_CONTRAST_PWM_FREQUENCY_HZ 20000u
#define UI_LCD_CONTRAST_PWM_PERIOD_COUNTS 1000u
#define UI_LCD_CONTRAST_PWM_DUTY_PERMILLE 300u
static TIM_HandleTypeDef g_ui_lcd_contrast_pwm_timer;
static uint32_t board_io_get_apb1_timer_clock_hz(void);
static void board_io_init_lcd_contrast_pwm(void);
void board_io_enable_uart_rx_irq(void)
{
LL_USART_EnableIT_PE(USART1);
LL_USART_EnableIT_RXNE(USART1);
LL_USART_EnableIT_ERROR(USART1);
NVIC_SetPriority(USART1_IRQn, 0);
NVIC_EnableIRQ(USART1_IRQn);
}
void board_io_init_standalone_ui(void)
{
GPIO_InitTypeDef gpio_init = {0};
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
HAL_GPIO_WritePin(UI_LCD_RS_GPIO_Port,
UI_LCD_RS_Pin | UI_LCD_E_Pin | UI_LCD_D4_Pin | UI_LCD_D5_Pin | UI_LCD_D6_Pin | UI_LCD_D7_Pin,
GPIO_PIN_RESET);
/*
* A fixed contrast pin is only a compromise until a potentiometer is
* added. Driving V0 low is the safest default for HD44780/SPLC780D-style
* modules because it makes characters visible instead of appearing blank.
*/
HAL_GPIO_WritePin(UI_LCD_V0_REF_GPIO_Port, UI_LCD_V0_REF_Pin, GPIO_PIN_RESET);
gpio_init.Pin = UI_LCD_RS_Pin | UI_LCD_E_Pin | UI_LCD_D4_Pin | UI_LCD_D5_Pin | UI_LCD_D6_Pin | UI_LCD_D7_Pin;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOG, &gpio_init);
gpio_init.Pin = UI_LCD_V0_REF_Pin;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(UI_LCD_V0_REF_GPIO_Port, &gpio_init);
gpio_init.Pin = UI_BUTTON_Pin;
gpio_init.Mode = GPIO_MODE_INPUT;
gpio_init.Pull = GPIO_PULLUP;
HAL_GPIO_Init(UI_BUTTON_GPIO_Port, &gpio_init);
board_io_init_lcd_contrast_pwm();
}
static uint32_t board_io_get_apb1_timer_clock_hz(void)
{
uint32_t pclk1_hz = HAL_RCC_GetPCLK1Freq();
if ((RCC->CFGR & RCC_CFGR_PPRE1) == RCC_CFGR_PPRE1_DIV1)
{
return pclk1_hz;
}
return pclk1_hz * 2u;
}
static void board_io_init_lcd_contrast_pwm(void)
{
GPIO_InitTypeDef gpio_init = {0};
TIM_OC_InitTypeDef pwm_channel = {0};
uint32_t timer_clock_hz = board_io_get_apb1_timer_clock_hz();
uint32_t prescaler_divisor = timer_clock_hz / (UI_LCD_CONTRAST_PWM_FREQUENCY_HZ * UI_LCD_CONTRAST_PWM_PERIOD_COUNTS);
uint32_t pulse_counts = ((UI_LCD_CONTRAST_PWM_PERIOD_COUNTS * UI_LCD_CONTRAST_PWM_DUTY_PERMILLE) + 999u) / 1000u;
if (prescaler_divisor == 0u)
{
prescaler_divisor = 1u;
}
if (pulse_counts >= UI_LCD_CONTRAST_PWM_PERIOD_COUNTS)
{
pulse_counts = UI_LCD_CONTRAST_PWM_PERIOD_COUNTS - 1u;
}
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_TIM4_CLK_ENABLE();
/*
* This PWM output is intended for the LCD contrast input (V0) through a
* simple RC low-pass filter. Keeping it local to board_io.c avoids
* spreading board wiring assumptions into higher-level UI code.
*/
gpio_init.Pin = UI_LCD_CONTRAST_PWM_Pin;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Pull = GPIO_NOPULL;
gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
gpio_init.Alternate = GPIO_AF2_TIM4;
HAL_GPIO_Init(UI_LCD_CONTRAST_PWM_GPIO_Port, &gpio_init);
g_ui_lcd_contrast_pwm_timer.Instance = TIM4;
g_ui_lcd_contrast_pwm_timer.Init.Prescaler = (uint32_t)(prescaler_divisor - 1u);
g_ui_lcd_contrast_pwm_timer.Init.CounterMode = TIM_COUNTERMODE_UP;
g_ui_lcd_contrast_pwm_timer.Init.Period = UI_LCD_CONTRAST_PWM_PERIOD_COUNTS - 1u;
g_ui_lcd_contrast_pwm_timer.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
g_ui_lcd_contrast_pwm_timer.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&g_ui_lcd_contrast_pwm_timer) != HAL_OK)
{
return;
}
pwm_channel.OCMode = TIM_OCMODE_PWM1;
pwm_channel.Pulse = pulse_counts;
pwm_channel.OCPolarity = TIM_OCPOLARITY_HIGH;
pwm_channel.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&g_ui_lcd_contrast_pwm_timer, &pwm_channel, TIM_CHANNEL_3) != HAL_OK)
{
return;
}
(void)HAL_TIM_PWM_Start(&g_ui_lcd_contrast_pwm_timer, TIM_CHANNEL_3);
}
void board_io_reset_runtime_outputs(void)
{
HAL_GPIO_WritePin(EN_5V1_GPIO_Port, EN_5V1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(EN_5V2_GPIO_Port, EN_5V2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LD1_EN_GPIO_Port, LD1_EN_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LD2_EN_GPIO_Port, LD2_EN_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(REF0_EN_GPIO_Port, REF0_EN_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(REF2_ON_GPIO_Port, REF2_ON_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(TECEN1_GPIO_Port, TECEN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(TECEN2_GPIO_Port, TECEN2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(TEC1_PD_GPIO_Port, TEC1_PD_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(TEC2_PD_GPIO_Port, TEC2_PD_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(ADC_MPD1_CS_GPIO_Port, ADC_MPD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ADC_MPD2_CS_GPIO_Port, ADC_MPD2_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(SPI4_CNV_GPIO_Port, SPI4_CNV_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(SPI5_CNV_GPIO_Port, SPI5_CNV_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_LD1_CS_GPIO_Port, DAC_LD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_LD2_CS_GPIO_Port, DAC_LD2_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC1_CS_GPIO_Port, DAC_TEC1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC2_CS_GPIO_Port, DAC_TEC2_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AD9833_CS_GPIO_Port, AD9833_CS_Pin, GPIO_PIN_SET);
}
void board_io_configure_spi2_mode(uint32_t polarity, uint32_t phase)
{
if (LL_SPI_IsEnabled(SPI2))
{
LL_SPI_Disable(SPI2);
}
LL_SPI_SetClockPolarity(SPI2, polarity);
LL_SPI_SetClockPhase(SPI2, phase);
if (!LL_SPI_IsEnabled(SPI2))
{
LL_SPI_Enable(SPI2);
}
}
bool board_io_is_usb_connected(void)
{
return HAL_GPIO_ReadPin(USB_FLAG_GPIO_Port, USB_FLAG_Pin) == GPIO_PIN_SET;
}
bool board_io_is_sd_card_present(void)
{
return HAL_GPIO_ReadPin(SDMMC1_EN_GPIO_Port, SDMMC1_EN_Pin) == GPIO_PIN_RESET;
}
bool board_io_is_standalone_ui_button_pressed(void)
{
return HAL_GPIO_ReadPin(UI_BUTTON_GPIO_Port, UI_BUTTON_Pin) == GPIO_PIN_RESET;
}
void board_io_set_supply_enabled(uint8_t supply_index, bool enabled)
{
GPIO_PinState pin_state = enabled ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (supply_index == 1u)
{
HAL_GPIO_WritePin(EN_5V1_GPIO_Port, EN_5V1_Pin, pin_state);
}
else if (supply_index == 2u)
{
HAL_GPIO_WritePin(EN_5V2_GPIO_Port, EN_5V2_Pin, pin_state);
}
}
void board_io_set_laser_enabled(uint8_t laser_index, bool enabled)
{
GPIO_PinState pin_state = enabled ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (laser_index == 1u)
{
HAL_GPIO_WritePin(LD1_EN_GPIO_Port, LD1_EN_Pin, pin_state);
}
else if (laser_index == 2u)
{
HAL_GPIO_WritePin(LD2_EN_GPIO_Port, LD2_EN_Pin, pin_state);
}
}
void board_io_set_reference_enabled(uint8_t reference_index, bool enabled)
{
GPIO_PinState pin_state = enabled ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (reference_index == 1u)
{
HAL_GPIO_WritePin(REF0_EN_GPIO_Port, REF0_EN_Pin, pin_state);
}
else if (reference_index == 2u)
{
HAL_GPIO_WritePin(REF2_ON_GPIO_Port, REF2_ON_Pin, pin_state);
}
}
void board_io_set_tec_channel_enabled(uint8_t tec_index, bool enabled)
{
GPIO_PinState pin_state = enabled ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (tec_index == 1u)
{
HAL_GPIO_WritePin(TEC1_PD_GPIO_Port, TEC1_PD_Pin, pin_state);
HAL_GPIO_WritePin(TECEN1_GPIO_Port, TECEN1_Pin, pin_state);
}
else if (tec_index == 2u)
{
HAL_GPIO_WritePin(TEC2_PD_GPIO_Port, TEC2_PD_Pin, pin_state);
HAL_GPIO_WritePin(TECEN2_GPIO_Port, TECEN2_Pin, pin_state);
}
}
void board_io_write_signal(GPIO_TypeDef *port, uint16_t pin, bool level_high)
{
HAL_GPIO_WritePin(port, pin, level_high ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
void board_io_toggle_debug_pin(void)
{
HAL_GPIO_TogglePin(TEST_01_GPIO_Port, TEST_01_Pin);
}

33
App/Devices/board_io.h Normal file
View File

@ -0,0 +1,33 @@
/**
* @file board_io.h
* @brief Board-specific GPIO and low-level control helpers.
*
* Architectural note:
* This module groups direct GPIO manipulations that are shared across several
* devices and services. Doing so keeps register-driving code localised and
* prevents high-level services from depending on scattered pin knowledge.
*/
#ifndef BOARD_IO_H
#define BOARD_IO_H
#include <stdbool.h>
#include <stdint.h>
#include "main.h"
void board_io_enable_uart_rx_irq(void);
void board_io_init_standalone_ui(void);
void board_io_reset_runtime_outputs(void);
void board_io_configure_spi2_mode(uint32_t polarity, uint32_t phase);
bool board_io_is_usb_connected(void);
bool board_io_is_sd_card_present(void);
bool board_io_is_standalone_ui_button_pressed(void);
void board_io_set_supply_enabled(uint8_t supply_index, bool enabled);
void board_io_set_laser_enabled(uint8_t laser_index, bool enabled);
void board_io_set_reference_enabled(uint8_t reference_index, bool enabled);
void board_io_set_tec_channel_enabled(uint8_t tec_index, bool enabled);
void board_io_toggle_debug_pin(void);
void board_io_write_signal(GPIO_TypeDef *port, uint16_t pin, bool level_high);
#endif /* BOARD_IO_H */

View File

@ -0,0 +1,83 @@
/**
* @file ds1809_device.c
* @brief DS1809 pulse-based control helper.
*/
#include "ds1809_device.h"
#include "main.h"
#define DS1809_POSITION_COUNT 64u
#define DS1809_CPU_PULSE_WIDTH_MS 2u
#define DS1809_CPU_INTER_PULSE_HIGH_MS 2u
#define DS1809_CONTROL_PORT_READY_DELAY_MS 10u
static void ds1809_drive_idle_high(void)
{
HAL_GPIO_WritePin(DS1809_UC_GPIO_Port, DS1809_UC_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DS1809_DC_GPIO_Port, DS1809_DC_Pin, GPIO_PIN_SET);
}
static void ds1809_pulse_direction(uint8_t increment,
uint8_t decrement,
uint16_t count,
uint16_t low_time_ms,
uint16_t high_time_ms)
{
uint16_t pulse_index;
if ((count == 0u) || ((increment == 0u) && (decrement == 0u)))
{
ds1809_drive_idle_high();
return;
}
ds1809_drive_idle_high();
for (pulse_index = 0u; pulse_index < count; ++pulse_index)
{
if (increment != 0u)
{
HAL_GPIO_WritePin(DS1809_UC_GPIO_Port, DS1809_UC_Pin, GPIO_PIN_RESET);
}
if (decrement != 0u)
{
HAL_GPIO_WritePin(DS1809_DC_GPIO_Port, DS1809_DC_Pin, GPIO_PIN_RESET);
}
HAL_Delay(low_time_ms);
ds1809_drive_idle_high();
HAL_Delay(high_time_ms);
}
}
void ds1809_pulse(uint8_t increment, uint8_t decrement, uint16_t count, uint16_t pulse_ms)
{
if (pulse_ms < DS1809_CPU_PULSE_WIDTH_MS)
{
pulse_ms = DS1809_CPU_PULSE_WIDTH_MS;
}
ds1809_pulse_direction(increment, decrement, count, pulse_ms, pulse_ms);
}
void ds1809_apply_position_from_min(uint8_t position_from_min)
{
/*
* DS1809 CPU-driven timing requirements from the local datasheet:
* - a pulse must stay low for longer than 1 ms to count as a step;
* - repetitive pulses need at least 1 ms of high time between steps;
* - the control inputs are locked out for at least 10 ms after power-up.
*
* Use 2 ms low/high timing for margin, always drive to the RL end first,
* then walk back up to the requested absolute position.
*/
if (position_from_min >= DS1809_POSITION_COUNT)
{
position_from_min = (uint8_t)(DS1809_POSITION_COUNT - 1u);
}
HAL_Delay(DS1809_CONTROL_PORT_READY_DELAY_MS);
ds1809_pulse_direction(0u, 1u, DS1809_POSITION_COUNT, DS1809_CPU_PULSE_WIDTH_MS, DS1809_CPU_INTER_PULSE_HIGH_MS);
ds1809_pulse_direction(1u, 0u, position_from_min, DS1809_CPU_PULSE_WIDTH_MS, DS1809_CPU_INTER_PULSE_HIGH_MS);
}

View File

@ -0,0 +1,32 @@
/**
* @file ds1809_device.h
* @brief DS1809 pulse-based control helper.
*/
#ifndef DS1809_DEVICE_H
#define DS1809_DEVICE_H
#include <stdint.h>
/**
* @brief Send relative UC/DC pulses using the legacy serial-command semantics.
*
* @param increment Non-zero to pulse the UC pin.
* @param decrement Non-zero to pulse the DC pin.
* @param count Number of pulses to issue.
* @param pulse_ms Low time and inter-pulse high time, in milliseconds.
*/
void ds1809_pulse(uint8_t increment, uint8_t decrement, uint16_t count, uint16_t pulse_ms);
/**
* @brief Force the DS1809 to a known absolute position above the minimum tap.
*
* The helper first overdrives the wiper all the way down to the RL terminal and
* then steps back up to the requested tap. This makes the standalone profile
* path deterministic even though the DS1809 serial command itself is relative.
*
* @param position_from_min Number of upward steps above the minimum tap.
*/
void ds1809_apply_position_from_min(uint8_t position_from_min);
#endif /* DS1809_DEVICE_H */

103
App/Devices/laser_dac.c Normal file
View File

@ -0,0 +1,103 @@
/**
* @file laser_dac.c
* @brief External DAC access for laser-current and TEC channels.
*/
#include "laser_dac.h"
#include "board_io.h"
#include "main.h"
void laser_dac_write_channel(uint8_t channel, uint16_t value)
{
uint32_t timeout;
if ((channel == 1u) || (channel == 3u))
{
board_io_configure_spi2_mode(LL_SPI_POLARITY_HIGH, LL_SPI_PHASE_2EDGE);
HAL_GPIO_WritePin(AD9102_CS_GPIO_Port, AD9102_CS_Pin, GPIO_PIN_SET);
}
switch (channel)
{
case 1u:
HAL_GPIO_WritePin(DAC_LD1_CS_GPIO_Port, DAC_LD1_CS_Pin, GPIO_PIN_RESET);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout <= 500u))
{
++timeout;
}
LL_SPI_TransmitData16(SPI2, value);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout <= 500u))
{
++timeout;
}
(void)SPI2->DR;
break;
case 2u:
if (!LL_SPI_IsEnabled(SPI6))
{
LL_SPI_Enable(SPI6);
}
HAL_GPIO_WritePin(DAC_LD2_CS_GPIO_Port, DAC_LD2_CS_Pin, GPIO_PIN_RESET);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_TXE(SPI6)) && (timeout <= 500u))
{
++timeout;
}
LL_SPI_TransmitData16(SPI6, value);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI6)) && (timeout <= 500u))
{
++timeout;
}
(void)SPI6->DR;
break;
case 3u:
HAL_GPIO_WritePin(DAC_TEC1_CS_GPIO_Port, DAC_TEC1_CS_Pin, GPIO_PIN_RESET);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_TXE(SPI2)) && (timeout <= 500u))
{
++timeout;
}
LL_SPI_TransmitData16(SPI2, value);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI2)) && (timeout <= 500u))
{
++timeout;
}
(void)SPI2->DR;
break;
case 4u:
if (!LL_SPI_IsEnabled(SPI6))
{
LL_SPI_Enable(SPI6);
}
HAL_GPIO_WritePin(DAC_TEC2_CS_GPIO_Port, DAC_TEC2_CS_Pin, GPIO_PIN_RESET);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_TXE(SPI6)) && (timeout <= 500u))
{
++timeout;
}
LL_SPI_TransmitData16(SPI6, value);
timeout = 0u;
while ((!LL_SPI_IsActiveFlag_RXNE(SPI6)) && (timeout <= 500u))
{
++timeout;
}
(void)SPI6->DR;
break;
default:
break;
}
HAL_GPIO_WritePin(DAC_LD1_CS_GPIO_Port, DAC_LD1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_LD2_CS_GPIO_Port, DAC_LD2_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC1_CS_GPIO_Port, DAC_TEC1_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(DAC_TEC2_CS_GPIO_Port, DAC_TEC2_CS_Pin, GPIO_PIN_SET);
}

13
App/Devices/laser_dac.h Normal file
View File

@ -0,0 +1,13 @@
/**
* @file laser_dac.h
* @brief Drivers for the external laser-current and TEC DAC channels.
*/
#ifndef LASER_DAC_H
#define LASER_DAC_H
#include <stdint.h>
void laser_dac_write_channel(uint8_t channel, uint16_t value);
#endif /* LASER_DAC_H */

View File

@ -0,0 +1,199 @@
/**
* @file lcd1602_display.c
* @brief Minimal SPLC780D/HD44780-compatible LCD1602 driver in 4-bit mode.
*
* Architectural note:
* The chosen wiring keeps the whole standalone UI on GPIOG. This driver uses
* write-only transfers with fixed delays, which keeps the implementation
* compact and avoids a bidirectional R/W pin or extra state.
*/
#include "lcd1602_display.h"
#include <stdbool.h>
#include <stddef.h>
#include "main.h"
#define LCD1602_COLUMNS 16u
#define LCD1602_LINE_1_ADDRESS 0x00u
#define LCD1602_LINE_2_ADDRESS 0x40u
#define LCD1602_COMMAND_DELAY_US 50u
#define LCD1602_CLEAR_DELAY_US 2000u
#define LCD1602_ENABLE_PULSE_US 1u
#define LCD1602_POWER_ON_DELAY_US 50000u
#define LCD1602_DATA_MASK (UI_LCD_D4_Pin | UI_LCD_D5_Pin | UI_LCD_D6_Pin | UI_LCD_D7_Pin)
static uint8_t g_cycle_counter_available = 0u;
static void lcd1602_enable_cycle_counter(void);
static void lcd1602_delay_us(uint32_t delay_us);
static void lcd1602_write_nibble(uint8_t nibble);
static void lcd1602_pulse_enable(void);
static void lcd1602_write_byte(bool is_data, uint8_t value);
static void lcd1602_write_command(uint8_t command);
static void lcd1602_write_data(uint8_t value);
static void lcd1602_set_cursor(uint8_t address);
static void lcd1602_write_padded_line(const char *text);
void lcd1602_display_init(void)
{
lcd1602_enable_cycle_counter();
HAL_GPIO_WritePin(UI_LCD_RS_GPIO_Port, UI_LCD_RS_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(UI_LCD_E_GPIO_Port, UI_LCD_E_Pin, GPIO_PIN_RESET);
UI_LCD_D4_GPIO_Port->BSRR = ((uint32_t)LCD1602_DATA_MASK) << 16u;
lcd1602_delay_us(LCD1602_POWER_ON_DELAY_US);
lcd1602_write_nibble(0x03u);
lcd1602_delay_us(5000u);
lcd1602_write_nibble(0x03u);
lcd1602_delay_us(150u);
lcd1602_write_nibble(0x03u);
lcd1602_delay_us(150u);
lcd1602_write_nibble(0x02u);
lcd1602_delay_us(LCD1602_COMMAND_DELAY_US);
lcd1602_write_command(0x28u);
lcd1602_write_command(0x08u);
lcd1602_write_command(0x01u);
lcd1602_write_command(0x06u);
lcd1602_write_command(0x0Cu);
}
void lcd1602_display_set_lines(const char *line1, const char *line2)
{
lcd1602_set_cursor(LCD1602_LINE_1_ADDRESS);
lcd1602_write_padded_line(line1);
lcd1602_set_cursor(LCD1602_LINE_2_ADDRESS);
lcd1602_write_padded_line(line2);
}
static void lcd1602_enable_cycle_counter(void)
{
uint32_t start_cycles;
uint32_t spin;
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0u;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
start_cycles = DWT->CYCCNT;
for (spin = 0u; spin < 64u; ++spin)
{
__NOP();
}
/*
* DWT-based microsecond delays are convenient, but they are not
* guaranteed to be available in every production boot/debug state.
* Falling back to HAL_Delay() keeps the LCD optional rather than
* letting the standalone UI block the whole firmware startup.
*/
g_cycle_counter_available = (DWT->CYCCNT != start_cycles) ? 1u : 0u;
}
static void lcd1602_delay_us(uint32_t delay_us)
{
if (delay_us == 0u)
{
return;
}
if (g_cycle_counter_available != 0u)
{
uint32_t start_cycles = DWT->CYCCNT;
uint32_t target_cycles = delay_us * (SystemCoreClock / 1000000u);
while ((DWT->CYCCNT - start_cycles) < target_cycles)
{
}
return;
}
while (delay_us > 1000u)
{
HAL_Delay(1u);
delay_us -= 1000u;
}
HAL_Delay(1u);
}
static void lcd1602_write_nibble(uint8_t nibble)
{
uint32_t bsrr = ((uint32_t)LCD1602_DATA_MASK) << 16u;
if ((nibble & 0x01u) != 0u)
{
bsrr |= UI_LCD_D4_Pin;
}
if ((nibble & 0x02u) != 0u)
{
bsrr |= UI_LCD_D5_Pin;
}
if ((nibble & 0x04u) != 0u)
{
bsrr |= UI_LCD_D6_Pin;
}
if ((nibble & 0x08u) != 0u)
{
bsrr |= UI_LCD_D7_Pin;
}
UI_LCD_D4_GPIO_Port->BSRR = bsrr;
lcd1602_pulse_enable();
}
static void lcd1602_pulse_enable(void)
{
HAL_GPIO_WritePin(UI_LCD_E_GPIO_Port, UI_LCD_E_Pin, GPIO_PIN_SET);
lcd1602_delay_us(LCD1602_ENABLE_PULSE_US);
HAL_GPIO_WritePin(UI_LCD_E_GPIO_Port, UI_LCD_E_Pin, GPIO_PIN_RESET);
lcd1602_delay_us(LCD1602_COMMAND_DELAY_US);
}
static void lcd1602_write_byte(bool is_data, uint8_t value)
{
HAL_GPIO_WritePin(UI_LCD_RS_GPIO_Port, UI_LCD_RS_Pin, is_data ? GPIO_PIN_SET : GPIO_PIN_RESET);
lcd1602_write_nibble((uint8_t)(value >> 4u));
lcd1602_write_nibble((uint8_t)(value & 0x0Fu));
}
static void lcd1602_write_command(uint8_t command)
{
lcd1602_write_byte(false, command);
if ((command == 0x01u) || (command == 0x02u))
{
lcd1602_delay_us(LCD1602_CLEAR_DELAY_US);
}
}
static void lcd1602_write_data(uint8_t value)
{
lcd1602_write_byte(true, value);
}
static void lcd1602_set_cursor(uint8_t address)
{
lcd1602_write_command((uint8_t)(0x80u | address));
}
static void lcd1602_write_padded_line(const char *text)
{
uint8_t index;
uint8_t value;
for (index = 0u; index < LCD1602_COLUMNS; ++index)
{
value = ' ';
if ((text != NULL) && (text[index] != '\0'))
{
value = (uint8_t)text[index];
}
lcd1602_write_data(value);
}
}

View File

@ -0,0 +1,19 @@
/**
* @file lcd1602_display.h
* @brief Minimal SPLC780D/HD44780-compatible LCD1602 driver in 4-bit mode.
*
* Architectural note:
* This module owns the LCD bus timing and character writes for the standalone
* front panel. High-level services format text elsewhere and call this driver
* only with already-prepared 16-character lines.
*/
#ifndef LCD1602_DISPLAY_H
#define LCD1602_DISPLAY_H
#include <stdint.h>
void lcd1602_display_init(void);
void lcd1602_display_set_lines(const char *line1, const char *line2);
#endif /* LCD1602_DISPLAY_H */

View File

@ -0,0 +1,45 @@
/**
* @file stm32_dac_output.c
* @brief STM32 internal DAC helper for the PA4 analogue output.
*/
#include "stm32_dac_output.h"
#include "main.h"
#define STM32_DAC_MAX_CODE 4095u
void stm32_dac_output_init(void)
{
GPIO_InitTypeDef gpio_init = {0};
__HAL_RCC_DAC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
gpio_init.Pin = GPIO_PIN_4;
gpio_init.Mode = GPIO_MODE_ANALOG;
gpio_init.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &gpio_init);
DAC->CR &= ~(DAC_CR_EN1 | DAC_CR_TEN1 | DAC_CR_DMAEN1);
DAC->DHR12R1 = 0u;
}
void stm32_dac_output_set(uint16_t dac_code, uint8_t enabled)
{
if (dac_code > STM32_DAC_MAX_CODE)
{
dac_code = STM32_DAC_MAX_CODE;
}
DAC->DHR12R1 = dac_code;
if (enabled != 0u)
{
DAC->CR |= DAC_CR_EN1;
}
else
{
DAC->CR &= ~DAC_CR_EN1;
}
}

View File

@ -0,0 +1,14 @@
/**
* @file stm32_dac_output.h
* @brief STM32 internal DAC helper for PA4 output generation.
*/
#ifndef STM32_DAC_OUTPUT_H
#define STM32_DAC_OUTPUT_H
#include <stdint.h>
void stm32_dac_output_init(void);
void stm32_dac_output_set(uint16_t dac_code, uint8_t enabled);
#endif /* STM32_DAC_OUTPUT_H */

View File

@ -0,0 +1,68 @@
/**
* @file uart_transport.c
* @brief USART1 transmit helpers used by the application core.
*/
#include "uart_transport.h"
#include "main.h"
static volatile uint8_t g_dma_busy = 0u;
void uart_transport_init(void)
{
LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_7);
LL_DMA_ClearFlag_TC7(DMA2);
LL_DMA_ClearFlag_TE7(DMA2);
LL_USART_EnableDMAReq_TX(USART1);
LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_7);
LL_DMA_EnableIT_TE(DMA2, LL_DMA_STREAM_7);
g_dma_busy = 0u;
}
void uart_transport_reset(void)
{
g_dma_busy = 0u;
}
void uart_transport_send_blocking(const uint8_t *data, uint16_t size)
{
uint16_t index;
for (index = 0u; index < size; ++index)
{
while (!LL_USART_IsActiveFlag_TXE(USART1))
{
}
LL_USART_TransmitData8(USART1, data[index]);
}
}
void uart_transport_send_dma(const uint8_t *data, uint16_t size)
{
while (g_dma_busy != 0u)
{
}
LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_7);
LL_DMA_ClearFlag_TC7(DMA2);
LL_DMA_ClearFlag_TE7(DMA2);
LL_DMA_ConfigAddresses(DMA2,
LL_DMA_STREAM_7,
(uint32_t)data,
LL_USART_DMA_GetRegAddr(USART1, LL_USART_DMA_REG_DATA_TRANSMIT),
LL_DMA_GetDataTransferDirection(DMA2, LL_DMA_STREAM_7));
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_7, size);
g_dma_busy = 1u;
LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7);
}
void uart_transport_mark_dma_complete(void)
{
g_dma_busy = 0u;
}
bool uart_transport_is_dma_busy(void)
{
return g_dma_busy != 0u;
}

View File

@ -0,0 +1,19 @@
/**
* @file uart_transport.h
* @brief USART1 transmit helpers used by the application core.
*/
#ifndef UART_TRANSPORT_H
#define UART_TRANSPORT_H
#include <stdbool.h>
#include <stdint.h>
void uart_transport_init(void);
void uart_transport_reset(void);
void uart_transport_send_blocking(const uint8_t *data, uint16_t size);
void uart_transport_send_dma(const uint8_t *data, uint16_t size);
void uart_transport_mark_dma_complete(void);
bool uart_transport_is_dma_busy(void);
#endif /* UART_TRANSPORT_H */