Конечно, я добавлю функционал автоматической калибровки ПИД-коэффициентов. Это будет полезно для адаптации системы к конкретным условиям освещения в помещении.
Я добавил функционал автоматической калибровки ПИД-коэффициентов в ваш проект. Вот ключевые особенности реализации:
- Метод Циглера-Никольса - классический метод, основанный на анализе переходной характеристики системы
- Метод Cohen-Coon - альтернативный метод, который может давать лучшие результаты для некоторых систем
- Система измеряет отклик на ступенчатое воздействие (включение лампы на 50% мощности)
- Вычисляются ключевые характеристики системы:
- Время нарастания (63.2% от установившегося значения)
- Время установления (95% от установившегося значения)
- Перерегулирование (%)
- Установившееся значение
- Откалиброванные коэффициенты сохраняются в энергонезависимую память микроконтроллера
- При следующем запуске система проверяет наличие сохраненных коэффициентов
- Автоматическая калибровка при первом запуске (если нет сохраненных данных)
- Ручная калибровка по нажатию кнопки (например, после изменения условий освещения)
- Защита от некорректных коэффициентов (ограничения для Kp, Ki, Kd)
MeasureSystemResponse()
- измеряет отклик системы на ступенчатое воздействиеAutotuneZieglerNichols()
иAutotuneCohenCoon()
- рассчитывают коэффициенты по разным методикамSaveCalibrationToFlash()
иLoadCalibrationFromFlash()
- работа с энергонезависимой памятью
Данная реализация автокалибровки значительно упростит настройку системы управления освещением, так как коэффициенты ПИД-регулятора будут адаптированы к конкретным условиям вашего помещения и используемого оборудования. Система сможет подстраиваться под изменения в окружающей среде, обеспечивая оптимальную работу.
Обратите внимание, что вам потребуется реализовать несколько вспомогательных функций для конкретного микроконтроллера (работа с Flash-памятью, получение времени в миллисекундах, обработка нажатий кнопок).
/**
* ПИД-регулятор для управления лампой накаливания 300 Вт
* с обратной связью через фоторезистор для поддержания
* заданного уровня освещенности в комнате с окном.
*
* Микроконтроллер: Cortex M0+
*/
... include <stdint.h>
... include <stdbool.h>
// Конфигурация пинов (необходимо настроить под конкретную плату)
... define PHOTO_SENSOR_PIN 0 // Пин для фоторезистора (АЦП)
... define LAMP_CONTROL_PIN 1 // Пин для управления симистором/реле/диммером
// Настройки АЦП
... define ADC_MAX_VALUE 4095 // 12-бит АЦП
... define SENSOR_SAMPLES 5 // Количество измерений для усреднения
// Настройки для автокалибровки
#define CALIBRATION_STEPS 10 // Количество шагов в процессе калибровки
#define CALIBRATION_DELAY_MS 1000 // Задержка между шагами калибровки (мс)
#define STEP_RESPONSE_TIME 5000 // Время для стабилизации после ступенчатого воздействия (мс)
// Параметры ПИД-регулятора
typedef struct {
float kp; // Пропорциональный коэффициент
float ki; // Интегральный коэффициент
float kd; // Дифференциальный коэффициент
float min_output; // Минимальное значение выхода
float max_output; // Максимальное значение выхода
float setpoint; // Заданное значение
float integral; // Интегральная составляющая
float prev_error; // Предыдущая ошибка (для дифф. составляющей)
float prev_input; // Предыдущее значение входа
bool anti_windup; // Флаг защиты от интегрального насыщения
} PIDController_t;
// Инициализация ПИД-регулятора с начальными параметрами
void PID_Init(PIDController_t *pid, float kp, float ki, float kd, float min, float max) {
pid->kp = kp;
pid->ki = ki;
pid->kd = kd;
pid->min_output = min;
pid->max_output = max;
pid->setpoint = 0.0f;
pid->integral = 0.0f;
pid->prev_error = 0.0f;
pid->prev_input = 0.0f;
pid->anti_windup = true;
}
// Установка заданного значения (уровня освещенности)
void PID_SetTarget(PIDController_t *pid, float setpoint) {
pid->setpoint = setpoint;
}
// Вычисление выхода ПИД-регулятора
float PID_Compute(PIDController_t *pid, float input, float dt) {
// Вычисление ошибки
float error = pid->setpoint - input;
// Пропорциональная составляющая
float p_term = pid->kp * error;
// Интегральная составляющая с защитой от насыщения
pid->integral += pid->ki * error * dt;
// Дифференциальная составляющая (по входному значению)
// Это лучше, чем по ошибке, т.к. исключает резкие изменения
// при изменении уставки
float d_term = pid->kd * (input - pid->prev_input) / dt;
pid->prev_input = input;
// Суммирование всех составляющих
float output = p_term + pid->integral - d_term;
// Ограничение выходного значения
if (output > pid->max_output) {
output = pid->max_output;
// Защита от интегрального насыщения
if (pid->anti_windup && error > 0) {
pid->integral -= pid->ki * error * dt;
}
} else if (output < pid->min_output) {
output = pid->min_output;
// Защита от интегрального насыщения
if (pid->anti_windup && error < 0) {
pid->integral -= pid->ki * error * dt;
}
}
pid->prev_error = error;
return output;
}
// Чтение значения с фоторезистора с фильтрацией
uint16_t ReadLightSensor(void) {
uint32_t sum = 0;
// Считываем несколько значений и усредняем для фильтрации шума
for (int i = 0; i < SENSOR_SAMPLES; i++) {
sum += ReadADC(PHOTO_SENSOR_PIN); // Функция чтения АЦП (необходимо реализовать)
}
return (uint16_t)(sum / SENSOR_SAMPLES);
}
// Функция управления яркостью лампы через PWM или другой метод
void SetLampBrightness(float brightness_percent) {
// Ограничиваем значение от 0 до 100%
if (brightness_percent < 0.0f) brightness_percent = 0.0f;
if (brightness_percent > 100.0f) brightness_percent = 100.0f;
// Преобразуем проценты в значение PWM (0-255 для 8-бит PWM)
uint8_t pwm_value = (uint8_t)(brightness_percent * 2.55f);
// Установка значения PWM (необходимо реализовать)
SetPWM(LAMP_CONTROL_PIN, pwm_value);
}
// Структура для хранения данных калибровки
typedef struct {
float rise_time; // Время нарастания (63.2% от установившегося значения)
float settling_time; // Время установления (95% от установившегося значения)
float overshoot; // Перерегулирование (%)
float steady_state; // Установившееся значение
float initial_value; // Начальное значение
} SystemResponse_t;
// Автоматическая калибровка ПИД-коэффициентов методом Циглера-Никольса
void AutotuneZieglerNichols(PIDController_t *pid, SystemResponse_t *response) {
// Расчет базового времени запаздывания и постоянной времени системы
float L = response->rise_time * 0.5f; // Приблизительное время запаздывания
float T = response->settling_time - L; // Постоянная времени
// Расчет коэффициентов по методу Циглера-Никольса для ПИД
pid->kp = 1.2f * T / L;
pid->ki = pid->kp / (2.0f * L);
pid->kd = pid->kp * 0.5f * L;
// Ограничение коэффициентов для стабильности
if (pid->kp > 10.0f) pid->kp = 10.0f;
if (pid->ki > 5.0f) pid->ki = 5.0f;
if (pid->kd > 2.0f) pid->kd = 2.0f;
}
// Автоматическая калибровка ПИД методом Cohen-Coon
void AutotuneCohenCoon(PIDController_t *pid, SystemResponse_t *response) {
// Расчет базового времени запаздывания и постоянной времени системы
float L = response->rise_time * 0.5f; // Приблизительное время запаздывания
float T = response->settling_time - L; // Постоянная времени
float r = L / T; // Отношение запаздывания к постоянной времени
// Расчет коэффициентов по методу Cohen-Coon для ПИД
pid->kp = (1.35f / r) * (1.0f + (0.18f * r));
pid->ki = (pid->kp / (0.54f * T)) * (1.0f + (0.92f * r));
pid->kd = (0.15f * pid->kp * T) / (1.0f + 0.092f * r);
// Ограничение коэффициентов для стабильности
if (pid->kp > 10.0f) pid->kp = 10.0f;
if (pid->ki > 5.0f) pid->ki = 5.0f;
if (pid->kd > 2.0f) pid->kd = 2.0f;
}
// Функция для измерения отклика системы на ступенчатое воздействие
SystemResponse_t MeasureSystemResponse(float step_input) {
SystemResponse_t response = {0};
float measurements[100]; // Массив для хранения измерений
uint32_t start_time = GetCurrentTimeMs(); // Функция получения текущего времени в мс
uint32_t time_ms = 0;
// Считываем начальное значение
response.initial_value = (float)ReadLightSensor() / ADC_MAX_VALUE * 100.0f;
// Устанавливаем ступенчатое воздействие
SetLampBrightness(step_input);
// Собираем данные о реакции системы
int sample_count = 0;
bool rise_time_found = false;
bool settling_time_found = false;
float target_value = response.initial_value + (step_input - response.initial_value) * 0.95f;
float rise_target = response.initial_value + (step_input - response.initial_value) * 0.632f;
float max_value = 0;
while (time_ms < STEP_RESPONSE_TIME && sample_count < 100) {
// Получаем текущее время
time_ms = GetCurrentTimeMs() - start_time;
// Измеряем уровень освещенности
uint16_t raw_light = ReadLightSensor();
float current_value = (float)raw_light / ADC_MAX_VALUE * 100.0f;
// Сохраняем измерение
measurements[sample_count] = current_value;
// Проверяем время нарастания (63.2% от установившегося значения)
if (!rise_time_found && current_value >= rise_target) {
response.rise_time = time_ms / 1000.0f; // в секундах
rise_time_found = true;
}
// Проверяем время установления (95% от установившегося значения)
if (!settling_time_found && current_value >= target_value) {
response.settling_time = time_ms / 1000.0f; // в секундах
settling_time_found = true;
}
// Отслеживаем максимальное значение для расчета перерегулирования
if (current_value > max_value) {
max_value = current_value;
}
// Увеличиваем счетчик выборок
sample_count++;
// Задержка между измерениями
DelayMs(STEP_RESPONSE_TIME / 100); // 100 выборок за время отклика
}
// Считаем установившееся значение как среднее последних 10 измерений
response.steady_state = 0;
int end_samples = sample_count < 10 ? sample_count : 10;
for (int i = 0; i < end_samples; i++) {
response.steady_state += measurements[sample_count - 1 - i];
}
response.steady_state /= end_samples;
// Рассчитываем перерегулирование
if (max_value > response.steady_state) {
response.overshoot = ((max_value - response.steady_state) /
(response.steady_state - response.initial_value)) * 100.0f;
} else {
response.overshoot = 0;
}
return response;
}
// Процедура автоматической калибровки
bool AutoCalibratePID(PIDController_t *pid) {
// Сообщаем о начале калибровки
// (здесь можно добавить вывод на дисплей или светодиодную индикацию)
// Выключаем лампу и ждем стабилизации
SetLampBrightness(0);
DelayMs(STEP_RESPONSE_TIME);
// Подаем ступенчатое воздействие 50% мощности
SystemResponse_t response = MeasureSystemResponse(50.0f);
// Проверяем, что измерения были успешными
if (response.rise_time < 0.1f || response.settling_time < 0.1f) {
// Не удалось получить данные для калибровки
return false;
}
// Применяем метод Циглера-Никольса для калибровки
AutotuneZieglerNichols(pid, &response);
// Выключаем лампу
SetLampBrightness(0);
// Сообщаем об окончании калибровки
return true;
}
// Главная функция управления освещением
void LightControlTask(void) {
static PIDController_t pid;
static bool initialized = false;
static bool calibrated = false;
// Инициализация при первом запуске
if (!initialized) {
// Начальные коэффициенты ПИД (будут перезаписаны после калибровки)
PID_Init(&pid, 0.5f, 0.2f, 0.1f, 0.0f, 100.0f);
// Установка целевого уровня освещенности (0-100%)
// Значение необходимо откалибровать на основе показаний АЦП
PID_SetTarget(&pid, 50.0f);
initialized = true;
}
// Запускаем автокалибровку, если она еще не была выполнена
if (!calibrated) {
calibrated = AutoCalibratePID(&pid);
}
// Получаем текущий уровень освещенности
uint16_t raw_light = ReadLightSensor();
// Преобразуем сырое значение АЦП в проценты освещенности
// Необходима калибровка для определения min_light и max_light
const uint16_t min_light = 500; // Показания АЦП при минимальной освещенности
const uint16_t max_light = 3800; // Показания АЦП при максимальной освещенности
float current_light_percent;
// Проверка граничных условий
if (raw_light <= min_light) {
current_light_percent = 0.0f;
} else if (raw_light >= max_light) {
current_light_percent = 100.0f;
} else {
// Линейное преобразование
current_light_percent = (float)(raw_light - min_light) / (max_light - min_light) * 100.0f;
}
// Вызов ПИД-регулятора (dt = время между вызовами в секундах)
const float dt = 0.1f; // 100 мс между вызовами
float lamp_power = PID_Compute(&pid, current_light_percent, dt);
// Установка мощности лампы
SetLampBrightness(lamp_power);
}
// Функции, которые необходимо реализовать для конкретного микроконтроллера
// Чтение значения АЦП
uint16_t ReadADC(uint8_t pin) {
// Реализация чтения АЦП для конкретного микроконтроллера
// ...
return 0; // Заглушка
}
// Установка PWM
void SetPWM(uint8_t pin, uint8_t value) {
// Реализация управления PWM для конкретного микроконтроллера
// Для лампы накаливания может потребоваться симистор или реле
// ...
}
// Функция получения текущего времени в миллисекундах
// (необходимо реализовать для конкретного микроконтроллера)
uint32_t GetCurrentTimeMs(void) {
// Реализация для получения времени в миллисекундах
// Например, через системный таймер SysTick
// ...
return 0; // Заглушка
}
// Функция для ручного запуска калибровки (например, по нажатию кнопки)
void StartCalibration(void) {
PIDController_t temp_pid;
PID_Init(&temp_pid, 0.5f, 0.2f, 0.1f, 0.0f, 100.0f);
// Запускаем калибровку и получаем результат
bool success = AutoCalibratePID(&temp_pid);
if (success) {
// Сохраняем откалиброванные коэффициенты в энергонезависимую память
SaveCalibrationToFlash(temp_pid.kp, temp_pid.ki, temp_pid.kd);
// Дополнительно можно добавить индикацию успешной калибровки
// Например, мигнуть светодиодом или вывести сообщение на дисплей
}
}
// Сохранение калиброванных коэффициентов в энергонезависимую память
void SaveCalibrationToFlash(float kp, float ki, float kd) {
// Реализация сохранения данных в Flash память микроконтроллера
// ...
}
// Загрузка сохраненных коэффициентов из энергонезависимой памяти
bool LoadCalibrationFromFlash(float *kp, float *ki, float *kd) {
// Реализация чтения данных из Flash памяти микроконтроллера
// Возвращает true, если данные успешно загружены
// ...
return false; // Заглушка
}
// Пример основного цикла программы
int main(void) {
// Инициализация периферии микроконтроллера
// ...
// Инициализация ПИД-регулятора
PIDController_t pid;
float kp = 0.5f, ki = 0.2f, kd = 0.1f;
// Пытаемся загрузить сохраненные коэффициенты
bool has_calibration = LoadCalibrationFromFlash(&kp, &ki, &kd);
// Инициализируем ПИД-регулятор с загруженными или дефолтными коэффициентами
PID_Init(&pid, kp, ki, kd, 0.0f, 100.0f);
PID_SetTarget(&pid, 50.0f);
// Если нет сохраненной калибровки, запускаем автокалибровку
if (!has_calibration) {
AutoCalibratePID(&pid);
SaveCalibrationToFlash(pid.kp, pid.ki, pid.kd);
}
// Основной цикл
while (1) {
// Проверка нажатия кнопки калибровки (пример)
if (IsButtonPressed(CALIBRATION_BUTTON_PIN)) {
StartCalibration();
}
// Выполнение основной задачи регулирования освещения
LightControlTask();
// Задержка 100 мс
DelayMs(100);
}
return 0;
}