/** @file dac_control.c @brief 直接调用 HAL_DAC 的 DAC 输出与 PI 控制实现。 */
#include "dac_control.h"
static uint8_t DAC_Control_IsNumber(float value)
{
return ((value >= 0.0f) || (value < 0.0f)) ? 1U : 0U;
}
static uint8_t DAC_Control_IsReady(const DAC_Control_t *control)
{
return ((control != NULL) && (control->initialized != 0U) &&
(control->hdac != NULL) && (control->hdac->Instance != NULL)) ? 1U : 0U;
}
static float DAC_Control_Clamp(float value, float min_value, float max_value, uint8_t *clamped)
{
if (value < min_value) { if (clamped != NULL) *clamped = 1U; return min_value; }
if (value > max_value) { if (clamped != NULL) *clamped = 1U; return max_value; }
return value;
}
/* 电压 -> 12位码值 -> HAL_DAC_SetValue;这是唯一真正写 PA4 的位置。 */
static DAC_ControlStatus_t DAC_Control_WriteOutput(DAC_Control_t *control, float voltage)
{
uint8_t clamped = 0U;
uint32_t raw_value;
voltage = DAC_Control_Clamp(voltage, control->output_min, control->output_max, &clamped);
raw_value = (uint32_t)((voltage * (float)control->full_scale / control->vref_voltage) + 0.5f);
if (raw_value > control->full_scale) raw_value = control->full_scale;
if (HAL_DAC_SetValue(control->hdac, control->channel, DAC_ALIGN_12B_R, raw_value) != HAL_OK)
return DAC_CONTROL_HAL_ERROR;
control->output_voltage = voltage;
return (clamped != 0U) ? DAC_CONTROL_CLAMPED : DAC_CONTROL_OK;
}
DAC_ControlStatus_t DAC_Control_Init(DAC_Control_t *control, DAC_HandleTypeDef *hdac,
uint32_t channel, float vref_voltage, uint32_t full_scale,
float output_min_voltage, float output_max_voltage)
{
if ((control == NULL) || (hdac == NULL) || (hdac->Instance == NULL) ||
((channel != DAC_CHANNEL_1) && (channel != DAC_CHANNEL_2)) ||
(DAC_Control_IsNumber(vref_voltage) == 0U) || !(vref_voltage > 0.0f) ||
(full_scale == 0U) || (DAC_Control_IsNumber(output_min_voltage) == 0U) ||
(DAC_Control_IsNumber(output_max_voltage) == 0U) || (output_min_voltage < 0.0f) ||
!(output_max_voltage > output_min_voltage) || (output_max_voltage > vref_voltage))
return DAC_CONTROL_INVALID_ARGUMENT;
control->hdac = hdac;
control->channel = channel;
control->vref_voltage = vref_voltage;
control->full_scale = full_scale;
control->target_voltage = 0.0f;
control->output_voltage = 0.0f;
control->output_min = output_min_voltage;
control->output_max = output_max_voltage;
control->kp = 0.0f;
control->ki = 0.0f;
control->integral = 0.0f;
control->closed_loop = 0U;
control->pi_first_update = 0U;
control->initialized = 1U;
return DAC_CONTROL_OK;
}
DAC_ControlStatus_t DAC_Control_Start(DAC_Control_t *control)
{
if (DAC_Control_IsReady(control) == 0U) return DAC_CONTROL_NOT_READY;
if (HAL_DAC_Start(control->hdac, control->channel) != HAL_OK) return DAC_CONTROL_HAL_ERROR;
return DAC_Control_WriteOutput(control, control->output_min);
}
DAC_ControlStatus_t DAC_Control_Stop(DAC_Control_t *control)
{
if (DAC_Control_IsReady(control) == 0U) return DAC_CONTROL_NOT_READY;
if (HAL_DAC_SetValue(control->hdac, control->channel, DAC_ALIGN_12B_R, 0U) != HAL_OK)
return DAC_CONTROL_HAL_ERROR;
if (HAL_DAC_Stop(control->hdac, control->channel) != HAL_OK) return DAC_CONTROL_HAL_ERROR;
control->output_voltage = 0.0f;
control->closed_loop = 0U;
control->pi_first_update = 0U;
control->integral = 0.0f;
return DAC_CONTROL_OK;
}
DAC_ControlStatus_t DAC_Control_SetPI(DAC_Control_t *control, float kp, float ki)
{
if (DAC_Control_IsReady(control) == 0U) return DAC_CONTROL_NOT_READY;
if ((DAC_Control_IsNumber(kp) == 0U) || (DAC_Control_IsNumber(ki) == 0U) ||
(kp < 0.0f) || (ki < 0.0f)) return DAC_CONTROL_INVALID_ARGUMENT;
control->kp = kp;
control->ki = ki;
return DAC_CONTROL_OK;
}
DAC_ControlStatus_t DAC_Control_SetTarget(DAC_Control_t *control, float target_voltage)
{
if (DAC_Control_IsReady(control) == 0U) return DAC_CONTROL_NOT_READY;
if (DAC_Control_IsNumber(target_voltage) == 0U) return DAC_CONTROL_INVALID_ARGUMENT;
control->target_voltage = target_voltage;
return DAC_CONTROL_OK;
}
DAC_ControlStatus_t DAC_Control_SetOpenLoopVoltage(DAC_Control_t *control, float voltage)
{
if (DAC_Control_IsReady(control) == 0U) return DAC_CONTROL_NOT_READY;
if (DAC_Control_IsNumber(voltage) == 0U) return DAC_CONTROL_INVALID_ARGUMENT;
control->closed_loop = 0U;
control->pi_first_update = 0U;
control->integral = 0.0f;
return DAC_Control_WriteOutput(control, voltage);
}
DAC_ControlStatus_t DAC_Control_EnableClosedLoop(DAC_Control_t *control, float target_voltage)
{
DAC_ControlStatus_t status = DAC_Control_SetTarget(control, target_voltage);
if (status != DAC_CONTROL_OK) return status;
control->integral = 0.0f;
control->closed_loop = 1U;
control->pi_first_update = 1U;
return DAC_CONTROL_OK;
}
void DAC_Control_DisableClosedLoop(DAC_Control_t *control)
{
if (DAC_Control_IsReady(control) == 0U) return;
control->closed_loop = 0U;
control->pi_first_update = 0U;
control->integral = 0.0f;
}
DAC_ControlStatus_t DAC_Control_Update(DAC_Control_t *control, float measured_voltage, float dt_seconds)
{
float error;
float integral_candidate;
float output_candidate;
uint8_t clamped = 0U;
if (DAC_Control_IsReady(control) == 0U) return DAC_CONTROL_NOT_READY;
if ((DAC_Control_IsNumber(measured_voltage) == 0U) ||
(DAC_Control_IsNumber(dt_seconds) == 0U) || !(dt_seconds > 0.0f))
return DAC_CONTROL_INVALID_ARGUMENT;
if (control->closed_loop == 0U) return DAC_CONTROL_OK;
error = control->target_voltage - measured_voltage;
/*
* main 先用开环把 DAC 调到安全工作点后再切闭环。若一切换就清积分并计算
* kp*error,输出会从原电压突然跳到一个很小值。这里首周期保持已有输出;
* 后续周期才按 PI 调节,因此示波器上不会看到切换瞬间的大幅跌落。
*/
if (control->pi_first_update != 0U)
{
/* 令 kp*error + ki*integral 恰好等于当前输出,实现无扰切换。 */
if (control->ki > 0.0f)
{
control->integral = (control->output_voltage - control->kp * error) / control->ki;
}
control->pi_first_update = 0U;
return DAC_Control_WriteOutput(control, control->output_voltage);
}
integral_candidate = control->integral + error * dt_seconds;
output_candidate = control->kp * error + control->ki * integral_candidate;
output_candidate = DAC_Control_Clamp(output_candidate, control->output_min,
control->output_max, &clamped);
/* 输出没有饱和时积分;若饱和但误差正把输出拉回范围,也允许积分。 */
if ((clamped == 0U) || ((output_candidate >= control->output_max) && (error < 0.0f)) ||
((output_candidate <= control->output_min) && (error > 0.0f)))
control->integral = integral_candidate;
return DAC_Control_WriteOutput(control, output_candidate);
}
float DAC_Control_GetOutputVoltage(const DAC_Control_t *control)
{
return (DAC_Control_IsReady(control) != 0U) ? control->output_voltage : 0.0f;
}
float DAC_Control_GetTargetVoltage(const DAC_Control_t *control)
{
return (DAC_Control_IsReady(control) != 0U) ? control->target_voltage : 0.0f;
}