diff --git a/MATLAB/MCU.mexw64 b/MATLAB/MCU.mexw64 index f4ade44..6d90845 100644 Binary files a/MATLAB/MCU.mexw64 and b/MATLAB/MCU.mexw64 differ diff --git a/MATLAB/upp_demo.m b/MATLAB/upp_demo.m new file mode 100644 index 0000000..c299d55 --- /dev/null +++ b/MATLAB/upp_demo.m @@ -0,0 +1,390 @@ +%% Скрипт визуализации тиристорного устройства плавного пуска +% Автоматически создает графики входных и выходных напряжений +% с возможностью изменения угла открытия тиристоров + +%% Основные параметры системы +clear; close all; clc; + +% Параметры сети +f = 50; % Частота, Гц +T = 1/f; % Период, с +U_phase = 220; % Фазное напряжение (действующее), В +U_line = U_phase*sqrt(3); % Линейное напряжение (действующее), В + +% Временной вектор +t = linspace(0, 3*T, 1000); % 3 периода + +% Начальный угол открытия (градусы) +alpha_deg = 30; +alpha_rad = deg2rad(alpha_deg); + +%% Создание графического интерфейса +fig = figure('Name', 'Тиристорное устройство плавного пуска', ... + 'Position', [100, 100, 1200, 800], ... + 'NumberTitle', 'off'); + +% Создание панели для ползунка +control_panel = uipanel('Title', 'Управление', ... + 'Position', [0.75, 0.85, 0.23, 0.14]); + +% Текст с текущим значением угла +angle_text = uicontrol('Parent', control_panel, ... % Добавляем Parent + 'Style', 'text', ... + 'String', sprintf('Угол α = %d°', alpha_deg), ... + 'Units', 'normalized', ... + 'Position', [0.05, 0.6, 0.9, 0.3], ... % Относительно панели + 'FontSize', 12, 'FontWeight', 'bold'); + +% Ползунок для управления углом +slider = uicontrol('Parent', control_panel, ... % Добавляем Parent + 'Style', 'slider', ... + 'Min', -30, 'Max', 180, 'Value', alpha_deg, ... + 'Units', 'normalized', ... + 'Position', [0.05, 0.1, 0.9, 0.4], ... % Относительно панели + 'Callback', @set_alpha); + +%% Создание осей для графиков +% Входные напряжения (фазные и линейные на одном графике) +ax1 = subplot(3, 1, 1); % Было (3, 2, 1) +title('Входные напряжения'); +xlabel('Время, с'); +ylabel('Напряжение, В'); +grid on; hold on; + +% Выходные линейные напряжения +ax3 = subplot(3, 1, 3); % Было (3, 2, [3, 4]) +title('Выходные линейные напряжения (после тиристоров)'); +xlabel('Время, с'); +ylabel('Напряжение, В'); +grid on; hold on; + +% Визуализация открытия тиристоров +ax4 = subplot(3, 1, 2); % Было (3, 2, [5, 6]) +title('Состояние тиристоров'); +xlabel('Время, с'); +ylabel('Тиристор'); +grid on; hold on; +ylim([0.5, 6.5]); +yticks(1:6); +yticklabels({'VS1+', 'VS1-', 'VS2+', 'VS2-', 'VS3+', 'VS3-'}); + +%% Инициализация графиков +% Обновляем графики с начальными значениями +update_plots(alpha_deg, alpha_rad, t, f, U_phase, U_line, angle_text); + +%% Пояснительная информация +disp('==============================================='); +disp('ТИРИСТОРНОЕ УСТРОЙСТВО ПЛАВНОГО ПУСКА'); +disp('==============================================='); +disp('Управление:'); +disp(' - Используйте ползунок для изменения угла α от 0° до 180°'); +disp(' - α = 0° - полное напряжение на выходе'); +disp(' - α = 90° - половина напряжения на выходе'); +disp(' - α = 180° - напряжение отключено'); +disp(' '); +disp('Обозначения тиристоров:'); +disp(' VS1+ - тиристор положительной полуволны фазы A'); +disp(' VS1- - тиристор отрицательной полуволны фазы A'); +disp(' VS2+ - тиристор положительной полуволны фазы B'); +disp(' VS2- - тиристор отрицательной полуволны фазы B'); +disp(' VS3+ - тиристор положительной полуволны фазы C'); +disp(' VS3- - тиристор отрицательной полуволны фазы C'); + + +%% Функция обновления графиков +function update_plots(alpha_deg, alpha_rad, t, f, U_phase, U_line, angle_text) + % Расчет синусоидальных напряжений + omega = 2*pi*f; + + % Входные фазные напряжения + Ua = U_phase*sqrt(2)*sin(omega*t); + Ub = U_phase*sqrt(2)*sin(omega*t - 2*pi/3); + Uc = U_phase*sqrt(2)*sin(omega*t + 2*pi/3); + + % Входные линейные напряжения + Uab_in = Ua - Ub; + Ubc_in = Ub - Uc; + Uca_in = Uc - Ua; + + % Выходные линейные напряжения (после тиристоров) + Uab_out = zeros(size(t)); + Ubc_out = zeros(size(t)); + Uca_out = zeros(size(t)); + + % Состояния тиристоров (1 - открыт, 0 - закрыт) + thyristor_state = zeros(6, length(t)); + + % Физическое состояние тиристоров (1 - открыт, 0 - закрыт) + thyristor_conducting = zeros(6, length(t)); + + % Для каждого момента времени определяем состояние тиристоров + for i = 1:length(t) + % Фазовый угол для каждой фазы + theta = omega*t(i); + theta_a = theta; + theta_b = theta - 2*pi/3; + theta_c = theta + 2*pi/3; + + % Модулированные углы (для определения полуволны) + theta_a_mod = mod(theta_a, 2*pi); + theta_b_mod = mod(theta_b, 2*pi); + theta_c_mod = mod(theta_c, 2*pi); + + %% 1. Определяем, когда ПОДАНЫ управляющие импульсы + % Исправление для поддержки отрицательных углов + + % VS1+ (положительная полуволна фазы A) + % Нормализуем угол: если alpha_rad < 0, добавляем 2π для корректной работы + alpha_norm = mod(alpha_rad, 2*pi); + pulse_start = alpha_norm; + pulse_end = pulse_start + 3*pi/4; + + % Проверяем два случая: импульс в пределах одного периода + % и импульс, пересекающий границу 2π + if pulse_end <= 2*pi + % Импульс не пересекает границу периода + if theta_a_mod >= pulse_start && theta_a_mod <= pulse_end + thyristor_state(1, i) = 1; + end + else + % Импульс пересекает границу периода (pulse_end > 2π) + % Импульс состоит из двух частей: [pulse_start, 2π) и [0, pulse_end-2π] + if theta_a_mod >= pulse_start || theta_a_mod <= pulse_end - 2*pi + thyristor_state(1, i) = 1; + end + end + + % VS1- (отрицательная полуволна фазы A) + pulse_start_neg = pi + alpha_norm; + pulse_end_neg = pulse_start_neg + 3*pi/4; + + % Нормализуем start_neg и end_neg к диапазону [0, 4π) + if pulse_start_neg >= 2*pi + pulse_start_neg = pulse_start_neg - 2*pi; + pulse_end_neg = pulse_end_neg - 2*pi; + end + + if pulse_end_neg <= 2*pi + if theta_a_mod >= pulse_start_neg && theta_a_mod <= pulse_end_neg + thyristor_state(2, i) = 1; + end + else + % Импульс пересекает границу периода + if theta_a_mod >= pulse_start_neg || theta_a_mod <= pulse_end_neg - 2*pi + thyristor_state(2, i) = 1; + end + end + + % VS2+ (положительная полуволна фазы B) + pulse_start = alpha_norm; + pulse_end = pulse_start + 3*pi/4; + + if pulse_end <= 2*pi + if theta_b_mod >= pulse_start && theta_b_mod <= pulse_end + thyristor_state(3, i) = 1; + end + else + if theta_b_mod >= pulse_start || theta_b_mod <= pulse_end - 2*pi + thyristor_state(3, i) = 1; + end + end + + % VS2- (отрицательная полуволна фазы B) + pulse_start_neg = pi + alpha_norm; + pulse_end_neg = pulse_start_neg + 3*pi/4; + + % Нормализуем start_neg и end_neg к диапазону [0, 4π) + if pulse_start_neg >= 2*pi + pulse_start_neg = pulse_start_neg - 2*pi; + pulse_end_neg = pulse_end_neg - 2*pi; + end + + if pulse_end_neg <= 2*pi + if theta_b_mod >= pulse_start_neg && theta_b_mod <= pulse_end_neg + thyristor_state(4, i) = 1; + end + else + if theta_b_mod >= pulse_start_neg || theta_b_mod <= pulse_end_neg - 2*pi + thyristor_state(4, i) = 1; + end + end + + % VS3+ (положительная полуволна фазы C) + pulse_start = alpha_norm; + pulse_end = pulse_start + 3*pi/4; + + if pulse_end <= 2*pi + if theta_c_mod >= pulse_start && theta_c_mod <= pulse_end + thyristor_state(5, i) = 1; + end + else + if theta_c_mod >= pulse_start || theta_c_mod <= pulse_end - 2*pi + thyristor_state(5, i) = 1; + end + end + + % VS3- (отрицательная полуволна фазы C) + pulse_start_neg = pi + alpha_norm; + pulse_end_neg = pulse_start_neg + 3*pi/4; + + % Нормализуем start_neg и end_neg к диапазону [0, 4π) + if pulse_start_neg >= 2*pi + pulse_start_neg = pulse_start_neg - 2*pi; + pulse_end_neg = pulse_end_neg - 2*pi; + end + + if pulse_end_neg <= 2*pi + if theta_c_mod >= pulse_start_neg && theta_c_mod <= pulse_end_neg + thyristor_state(6, i) = 1; + end + else + if theta_c_mod >= pulse_start_neg || theta_c_mod <= pulse_end_neg - 2*pi + thyristor_state(6, i) = 1; + end + end + + %% 2. Определяем, когда тиристоры ФИЗИЧЕСКИ открыты + + % VS1+ (A+): открыт, если есть импульс и положительная полуволна фазы A + if thyristor_state(1, i) == 1 && (theta_a_mod >= 0 && theta_a_mod <= pi) && (Ua(i) > Ub(i) && Ua(i) > Uc(i)) + thyristor_conducting(1, i) = 1; + end + + % VS1- (A-): открыт, если есть импульс и отрицательная полуволна фазы A + if thyristor_state(2, i) == 1 && (theta_a_mod >= pi && theta_a_mod <= 2*pi) && (Ua(i) < Ub(i) && Ua(i) < Uc(i)) + thyristor_conducting(2, i) = 1; + end + + % VS2+ (B+): открыт, если есть импульс и положительная полуволна фазы B + if thyristor_state(3, i) == 1 && (theta_b_mod >= 0 && theta_b_mod <= pi) && (Ub(i) > Ua(i) && Ub(i) > Uc(i)) + thyristor_conducting(3, i) = 1; + end + + % VS2- (B-): открыт, если есть импульс и отрицательная полуволна фазы B + if thyristor_state(4, i) == 1 && (theta_b_mod >= pi && theta_b_mod <= 2*pi) && (Ub(i) < Ua(i) && Ub(i) < Uc(i)) + thyristor_conducting(4, i) = 1; + end + + % VS3+ (C+): открыт, если есть импульс и положительная полуволна фазы C + if thyristor_state(5, i) == 1 && (theta_c_mod >= 0 && theta_c_mod <= pi) && (Uc(i) > Ub(i) && Uc(i) > Ua(i)) + thyristor_conducting(5, i) = 1; + end + + % VS3- (C-): открыт, если есть импульс и отрицательная полуволна фазы C + if thyristor_state(6, i) == 1 && (theta_c_mod >= pi && theta_c_mod <= 2*pi) && (Uc(i) < Ub(i) && Uc(i) < Ua(i)) + thyristor_conducting(6, i) = 1; + end + + %% 3. Расчет выходных напряжений + + % Фазные выходные напряжения + Ua_out = 0; + if thyristor_conducting(1, i) || thyristor_conducting(2, i) + Ua_out = Ua(i); + end + + Ub_out = 0; + if thyristor_conducting(3, i) || thyristor_conducting(4, i) + Ub_out = Ub(i); + end + + Uc_out = 0; + if thyristor_conducting(5, i) || thyristor_conducting(6, i) + Uc_out = Uc(i); + end + + % Линейные выходные напряжения + Uab_out(i) = Ua_out - Ub_out; + Ubc_out(i) = Ub_out - Uc_out; + Uca_out(i) = Uc_out - Ua_out; + end + + %% Обновление графиков (ВАЖНО: без изменений!) + % Входные фазные напряжения + subplot(3, 1, 1); + cla; grid on; hold on; + plot(t, zeros(size(t)), 'k', 'LineWidth', 0.5); + plot(t, Ua, 'r', 'LineWidth', 2, 'DisplayName', 'Фаза A'); + plot(t, Ub, 'g', 'LineWidth', 2, 'DisplayName', 'Фаза B'); + plot(t, Uc, 'b', 'LineWidth', 2, 'DisplayName', 'Фаза C'); + plot(t, Uab_in, 'r--', 'LineWidth', 0.5, 'DisplayName', 'Uab'); + plot(t, Ubc_in, 'g--', 'LineWidth', 0.5, 'DisplayName', 'Ubc'); + plot(t, Uca_in, 'b--', 'LineWidth', 0.5, 'DisplayName', 'Uca'); + title('Входные напряжения (сплошные - фазные, пунктир - линейные)'); + xlabel('Время, с'); + ylabel('Напряжение, В'); + legend('Location', 'best'); + xlim([0, max(t)]); + ylim([-700, 700]); + + % Выходные линейные напряжения + subplot(3, 1, 3); + cla; grid on; hold on; + plot(t, zeros(size(t)), 'k', 'LineWidth', 0.5); + plot(t, Uab_out, 'r', 'LineWidth', 1.5, 'DisplayName', 'Uab вых'); + plot(t, Ubc_out, 'g', 'LineWidth', 1.5, 'DisplayName', 'Ubc вых'); + plot(t, Uca_out, 'b', 'LineWidth', 1.5, 'DisplayName', 'Uca вых'); + title(sprintf('Выходные линейные напряжения (α = %d°)', alpha_deg)); + xlabel('Время, с'); + ylabel('Напряжение, В'); + legend('Location', 'best'); + xlim([0, max(t)]); + ylim([-700, 700]); + + % Визуализация состояния тиристоров - показываем управляющие импульсы + subplot(3, 1, 2); + cla; grid on; hold on; + + % Цвета для тиристоров + colors = {'r', 'r', 'g', 'g', 'b', 'b'}; + line_styles = {'-', '--', '-', '--', '-', '--'}; + + for k = 1:6 + % Находим интервалы, где подан управляющий импульс + state = thyristor_state(k, :); + diff_state = diff([0, state, 0]); + start_idx = find(diff_state == 1); + end_idx = find(diff_state == -1) - 1; + + % Рисуем горизонтальные линии для каждого интервала + for m = 1:length(start_idx) + plot(t(start_idx(m):end_idx(m)), k*ones(1, length(start_idx(m):end_idx(m))), ... + 'Color', colors{k}, 'LineStyle', line_styles{k}, 'LineWidth', 2); + end + end + + title(sprintf('Управляющие импульсы тиристоров (α = %d°)', alpha_deg)); + xlabel('Время, с'); + ylabel('Тиристор'); + ylim([0.5, 6.5]); + yticks(1:6); + yticklabels({'VS1+', 'VS1-', 'VS2+', 'VS2-', 'VS3+', 'VS3-'}); + xlim([0, max(t)]); + + % Обновление текста с текущим углом + set(angle_text, 'String', sprintf('Угол α = %d°', alpha_deg)); + + % Добавление пояснений к тиристорам + legend({'Фаза A (+)', 'Фаза A (-)', 'Фаза B (+)', 'Фаза B (-)', ... + 'Фаза C (+)', 'Фаза C (-)'}, 'Location', 'best'); +end + +%% Функция обратного вызова для ползунка +function set_alpha(source, ~) + % Получаем значение из ползунка + alpha_deg = round(get(source, 'Value')); + + % Обновляем глобальные переменные + evalin('base', sprintf('alpha_deg = %d;', alpha_deg)); + evalin('base', 'alpha_rad = deg2rad(alpha_deg);'); + + % Получаем ссылки на переменные из базового рабочего пространства + t = evalin('base', 't'); + f = evalin('base', 'f'); + U_phase = evalin('base', 'U_phase'); + U_line = evalin('base', 'U_line'); + angle_text = evalin('base', 'angle_text'); + + % Обновляем графики + update_plots(alpha_deg, deg2rad(alpha_deg), t, f, U_phase, U_line, angle_text); +end diff --git a/MATLAB/upp_r2023.slx b/MATLAB/upp_r2023.slx index 6c4f228..7b1d2e6 100644 Binary files a/MATLAB/upp_r2023.slx and b/MATLAB/upp_r2023.slx differ diff --git a/UPP/Core/Configs/modbus_config.h b/UPP/Core/Configs/modbus_config.h index 27434b6..806e78d 100644 --- a/UPP/Core/Configs/modbus_config.h +++ b/UPP/Core/Configs/modbus_config.h @@ -59,7 +59,7 @@ * @details Терминалка от двигателей использует для чтения регистров комманду R_HOLD_REGS вместо R_IN_REGS * Поэтому чтобы считывать Input Regs - надо поменять их местами. */ -#define MODBUS_SWITCH_COMMAND_R_IN_REGS_AND_R_HOLD_REGS +//#define MODBUS_SWITCH_COMMAND_R_IN_REGS_AND_R_HOLD_REGS ///////////////////////////////////////////////////////////////////// /////////////////////////---CALC DEFINES---////////////////////////// diff --git a/UPP/Core/Configs/modbus_data.h b/UPP/Core/Configs/modbus_data.h index 34d385c..289053c 100644 --- a/UPP/Core/Configs/modbus_data.h +++ b/UPP/Core/Configs/modbus_data.h @@ -67,7 +67,7 @@ // DEFINES FOR HOLDING REGISTERS ARRAYS #define R_HOLDING_ADDR 0 ///< Начальный адрес регистров хранения -#define R_HOLDING_QNT (sizeof(UPP_PUI_Params_t)/sizeof(uint16_t)) ///< Количество регистров хранения +#define R_HOLDING_QNT (2000) ///< Количество регистров хранения // DEFINES FOR COIL ARRAYS #define C_COILS_ADDR 0 ///< Начальный адрес коилов @@ -157,10 +157,18 @@ typedef struct // tester modbus data extern MB_DataStructureTypeDef MB_DATA; + +typedef struct +{ + uint8_t data[240]; +}MB_OscilTypeDef; + typedef struct { UPP_FuncCalls_t FuncCalls; UPP_PrvtParams_t param; + + MB_OscilTypeDef oscil; }MB_DataInternalTypeDef; extern MB_DataInternalTypeDef MB_INTERNAL; diff --git a/UPP/Core/Configs/upp_config.h b/UPP/Core/Configs/upp_config.h index e4c2ee4..f2996dd 100644 --- a/UPP/Core/Configs/upp_config.h +++ b/UPP/Core/Configs/upp_config.h @@ -108,9 +108,8 @@ /* Параметры регулятора угла */ -#define ANGLE_PULSE_LENGTH_RESERVE_PERCENT_DEFAULT 1.0 -#define ANGLE_MAX_PERCENT_DEFAULT 0.80 -#define ANGLE_MIN_PERCENT_DEFAULT 0.1 +#define ANGLE_MAX_PERCENT_DEFAULT (140.0f/180.0f) // note: не больше 150! +#define ANGLE_MIN_PERCENT_DEFAULT (10.0f/180.0f) #define ANGLE_PID_KP_COEF_DEFAULT 0.0001 #define ANGLE_PID_KI_COEF_DEFAULT 0.0001 #define ANGLE_PID_KD_COEF_DEFAULT 0 diff --git a/UPP/Core/UPP/upp_params.c b/UPP/Core/UPP/upp_params.c index a18430b..8e71f10 100644 --- a/UPP/Core/UPP/upp_params.c +++ b/UPP/Core/UPP/upp_params.c @@ -419,9 +419,6 @@ void UPP_Params_Saturate(void) SATURATE_U16(PARAM_INTERNAL->zc.Hysteresis, 0, 0.1*100); SATURATE_U16(PARAM_INTERNAL->zc.DebouneCouner, 0, 1000); - - - SATURATE_U16(PARAM_INTERNAL->angle.PulseLengthReserve, 50, 1000); } /** @@ -487,7 +484,6 @@ void UPP_Params_SetDefault(int pui_default, int internal_default) PARAM_INTERNAL->angle.PID_Kd = ANGLE_PID_KD_COEF_DEFAULT*10000; PARAM_INTERNAL->angle.Angle_Max = ANGLE_MAX_PERCENT_DEFAULT*65535; PARAM_INTERNAL->angle.Angle_Min = ANGLE_MIN_PERCENT_DEFAULT*65535; - PARAM_INTERNAL->angle.PulseLengthReserve = ANGLE_PULSE_LENGTH_RESERVE_PERCENT_DEFAULT*100; //__AngleSetLimit(); } } diff --git a/UPP/Core/UPP/upp_params.h b/UPP/Core/UPP/upp_params.h index 5daa4bb..df58690 100644 --- a/UPP/Core/UPP/upp_params.h +++ b/UPP/Core/UPP/upp_params.h @@ -96,8 +96,7 @@ typedef struct /* Параметры Угла */ struct { - uint16_t PulseLengthReserve;/*!< @brief Адрес 1070: Сколько запаса закладывать на длительность пачки импульсов [Проценты] @ref __AngleSetLimit - @details Пример: 100% - будет запас в одну пачку импульсов */ + uint16_t reserved0; ///< Адрес 1070: Резерв uint16_t Angle_Max; ///< Адрес 1071: Максимальный угол открытия тиристора [0..1 x 65535] uint16_t Angle_Min; ///< Адрес 1072: Минимальный угол открытия тиристора [0..1 x 65535] uint16_t PID_Kp; ///< Адрес 1073: Пропорциональный коэфициент ПИ регулятора угла [x 10000] diff --git a/UPP/Core/UPP/upp_status.c b/UPP/Core/UPP/upp_status.c index 31d99bc..76e567a 100644 --- a/UPP/Core/UPP/upp_status.c +++ b/UPP/Core/UPP/upp_status.c @@ -44,6 +44,17 @@ void UPP_Status_Handler(void) MB_DATA.InRegs.pui.Warnings.warn.UnderVoltage = !upp.errors->pui.err.UnderVoltage; + static int mb_u_cnt = 0; + if(mb_u_cnt+4 >= 240) + { + mb_u_cnt = 0; + } + MB_INTERNAL.oscil.data[mb_u_cnt++] = (upp.pm.measured.slow.U[U_AB]+1)*127; + MB_INTERNAL.oscil.data[mb_u_cnt++] = (upp.pm.measured.slow.U[U_BC]+1)*127; + MB_INTERNAL.oscil.data[mb_u_cnt++] = (upp.pm.measured.slow.U[U_CA]+1)*127; + MB_INTERNAL.oscil.data[mb_u_cnt++] = local_time(); + + if(GPIO_Read_Switch(&UPP_DIN.Pusk)) { upp.call->go = 1; diff --git a/UPP/MDK-ARM/UPP.uvoptx b/UPP/MDK-ARM/UPP.uvoptx index 9e0ba89..93f9c3a 100644 --- a/UPP/MDK-ARM/UPP.uvoptx +++ b/UPP/MDK-ARM/UPP.uvoptx @@ -332,7 +332,22 @@ 16 2 - MB_INTERNAL.param.nominal.U + MB_INTERNAL + + + 17 + 2 + upp.hangle + + + 18 + 2 + MB_INTERNAL.oscil.data,0x0A + + + 19 + 2 + hmodbus1