From b0813fb7e86df590dca5aefaa26d4115f3d724af Mon Sep 17 00:00:00 2001 From: Razvalyaev Date: Sun, 16 Nov 2025 19:40:40 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D0=BE=D1=86=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=D0=BE=D1=81=D1=8C=20=D0=B2=20=D0=BC=D0=B0=D1=82=D0=BB?= =?UTF-8?q?=D0=B0=D0=B1=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD?= =?UTF-8?q?=D0=BE.=20=D0=A3=D0=9F=D0=9F=20=D0=BC=D0=BE=D0=B6=D0=B5=D1=82?= =?UTF-8?q?=20=D0=BF=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D1=82=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=80=D1=8B=20=D0=BF=D0=B0=D1=87=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=B8=D0=BC=D0=BF=D1=83=D0=BB=D1=8C=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Начата работа над управлением УПП по командам --- MATLAB/MCU_STM32_Matlab/stm32_matlab_conf.c | 1 + .../stm32f4xx_matlab_conf.json | 1 - MATLAB/MCU_Wrapper/mcu_wrapper_conf.h | 2 +- MATLAB/app_wrapper/app_init.c | 1 + MATLAB/app_wrapper/app_io.c | 21 +- MATLAB/app_wrapper/app_wrapper.c | 8 + MATLAB/upp_r2023.slx | Bin 65974 -> 66601 bytes UPP/AllLibs/Modbus | 2 +- UPP/AllLibs/PeriphGeneral | 2 +- UPP/Core/Configs/memspi_config.h | 2 +- UPP/Core/Configs/modbus_config.h | 2 +- UPP/Core/Configs/modbus_data.c | 7 + UPP/Core/Configs/modbus_data.h | 21 +- UPP/Core/Configs/mylibs_config.h | 2 +- UPP/Core/Configs/upp_config.h | 77 +++-- UPP/Core/Configs/upp_defs.h | 178 ++++++++++++ UPP/Core/Inc/main.h | 7 +- UPP/Core/PowerMonitor/adc_tools.c | 34 +-- UPP/Core/PowerMonitor/adc_tools.h | 2 +- UPP/Core/PowerMonitor/power_monitor.c | 32 ++- UPP/Core/PowerMonitor/zero_cross.c | 38 +-- UPP/Core/PowerMonitor/zero_cross.h | 17 +- UPP/Core/Src/main.c | 2 +- UPP/Core/Src/stm32f4xx_it.c | 1 + UPP/Core/UPP/angle_control.c | 97 +++++-- UPP/Core/UPP/angle_control.h | 26 +- UPP/Core/UPP/pwm_thyristors.c | 50 +++- UPP/Core/UPP/pwm_thyristors.h | 28 +- UPP/Core/UPP/upp_control.c | 267 ++++++++++++++++++ UPP/Core/UPP/upp_control.h | 67 +++++ UPP/Core/UPP/upp_errors.c | 44 +++ UPP/Core/UPP/upp_errors.h | 124 ++++++++ UPP/Core/UPP/upp_main.c | 150 ++++++++-- UPP/Core/UPP/upp_main.h | 31 +- UPP/Core/UPP/upp_status.c | 12 + UPP/Core/UPP/upp_status.h | 13 + UPP/MDK-ARM/UPP.uvoptx | 254 +++++++++++------ UPP/MDK-ARM/UPP.uvprojx | 39 ++- 38 files changed, 1374 insertions(+), 288 deletions(-) create mode 100644 UPP/Core/Configs/upp_defs.h create mode 100644 UPP/Core/UPP/upp_control.c create mode 100644 UPP/Core/UPP/upp_control.h create mode 100644 UPP/Core/UPP/upp_errors.c create mode 100644 UPP/Core/UPP/upp_errors.h create mode 100644 UPP/Core/UPP/upp_status.c create mode 100644 UPP/Core/UPP/upp_status.h diff --git a/MATLAB/MCU_STM32_Matlab/stm32_matlab_conf.c b/MATLAB/MCU_STM32_Matlab/stm32_matlab_conf.c index 4ace5cb..546224f 100644 --- a/MATLAB/MCU_STM32_Matlab/stm32_matlab_conf.c +++ b/MATLAB/MCU_STM32_Matlab/stm32_matlab_conf.c @@ -16,6 +16,7 @@ MCU_CortexMemoryTypeDef MCU_CORTEX_MEM; // MCU PERIPH INIT void Initialize_Periph_Sim(void) { + uwTick = hmcu.SystemClock / (MCU_CORE_CLOCK / 1000); Init_TIM_SIM(); Init_ADC_SIM(); } diff --git a/MATLAB/MCU_STM32_Matlab/stm32f4xx_matlab_conf.json b/MATLAB/MCU_STM32_Matlab/stm32f4xx_matlab_conf.json index b1970f0..489a2f4 100644 --- a/MATLAB/MCU_STM32_Matlab/stm32f4xx_matlab_conf.json +++ b/MATLAB/MCU_STM32_Matlab/stm32f4xx_matlab_conf.json @@ -15,7 +15,6 @@ "Initialize_Periph_Sim()" ], "PeriphSimulation": [ - "uwTick = hmcu.SystemClock / (MCU_CORE_CLOCK / 1000)", "Simulate_Periph_Sim()" ], "PeriphDeinit": [ diff --git a/MATLAB/MCU_Wrapper/mcu_wrapper_conf.h b/MATLAB/MCU_Wrapper/mcu_wrapper_conf.h index 8f592c0..ab4f3d5 100644 --- a/MATLAB/MCU_Wrapper/mcu_wrapper_conf.h +++ b/MATLAB/MCU_Wrapper/mcu_wrapper_conf.h @@ -55,7 +55,7 @@ // INPUT/OUTPUTS PARAMS START #define IN_PORT_NUMB 2 #define ADC_PORT_1_WIDTH 6 -#define IN_PORT_2_WIDTH 1 +#define IN_PORT_2_WIDTH 16 #define OUT_PORT_NUMB 3 #define THYR_PORT_1_WIDTH 6 diff --git a/MATLAB/app_wrapper/app_init.c b/MATLAB/app_wrapper/app_init.c index 705b1e2..c399266 100644 --- a/MATLAB/app_wrapper/app_init.c +++ b/MATLAB/app_wrapper/app_init.c @@ -24,6 +24,7 @@ void app_init(void) { MX_TIM8_Init(); MX_TIM5_Init(); MX_ADC3_Init(); + UPP_SetDefault(1, 1); UPP_Init(); UPP_PreWhile(); diff --git a/MATLAB/app_wrapper/app_io.c b/MATLAB/app_wrapper/app_io.c index 6a06c4e..97e301b 100644 --- a/MATLAB/app_wrapper/app_io.c +++ b/MATLAB/app_wrapper/app_io.c @@ -8,7 +8,7 @@ float dbg[16]; -#define PIN_READ(_verbname_) (_verbname_##_GPIO_Port->ODR & (_verbname_##_Pin)) ? 0 : 1 +#define PIN_READ(_verbname_) (_verbname_##_GPIO_Port->ODR & (_verbname_##_Pin)) ? 1 : 0 void Write_Thyristors(real_T* Buffer, int ind_port) { @@ -66,6 +66,7 @@ void app_readInputs(const real_T* Buffer) { ADC_Set_Channel_Value(ADC3, 10, ReadInputArray(0,5)); alpha_dbg = ReadInputArray(1, 0); + upp.call->go = ReadInputArray(1, 1); // USER APP INPUT END } @@ -83,19 +84,19 @@ void app_writeOutputBuffer(real_T* Buffer) { int nn = 0; //WriteOutputArray(upp.hangle.htim->Instance->CNT, 2, nn++); - //WriteOutputArray((long long)(upp.hangle.htim->Instance->CCR1) - upp.hangle.htim->Instance->CNT, 2, nn++); - //WriteOutputArray((long long)(upp.hangle.htim->Instance->CCR2) - upp.hangle.htim->Instance->CNT, 2, nn++); - //WriteOutputArray((long long)(upp.hangle.htim->Instance->CCR3) - upp.hangle.htim->Instance->CNT, 2, nn++); + WriteOutputArray((long long)(upp.hangle.htim->Instance->CCR1) - upp.hangle.htim->Instance->CNT, 2, nn++); + WriteOutputArray((long long)(upp.hangle.htim->Instance->CCR2) - upp.hangle.htim->Instance->CNT, 2, nn++); + WriteOutputArray((long long)(upp.hangle.htim->Instance->CCR3) - upp.hangle.htim->Instance->CNT, 2, nn++); //WriteOutputArray(dbg[0], 2, nn++); //WriteOutputArray(dbg[1], 2, nn++); //WriteOutputArray(dbg[2], 2, nn++); - //WriteOutputArray(upp.hpwm.AllPhases[PHASE_A_POS].State, 2, nn++); - //WriteOutputArray(upp.hpwm.AllPhases[PHASE_A_NEG].State, 2, nn++); - //WriteOutputArray(upp.hpwm.AllPhases[PHASE_B_POS].State, 2, nn++); - //WriteOutputArray(upp.hpwm.AllPhases[PHASE_B_NEG].State, 2, nn++); - //WriteOutputArray(upp.hpwm.AllPhases[PHASE_C_POS].State, 2, nn++); - //WriteOutputArray(upp.hpwm.AllPhases[PHASE_C_NEG].State, 2, nn++); + WriteOutputArray(upp.hpwm.AllPhases[PHASE_A_POS].State, 2, nn++); + WriteOutputArray(upp.hpwm.AllPhases[PHASE_A_NEG].State, 2, nn++); + WriteOutputArray(upp.hpwm.AllPhases[PHASE_B_POS].State, 2, nn++); + WriteOutputArray(upp.hpwm.AllPhases[PHASE_B_NEG].State, 2, nn++); + WriteOutputArray(upp.hpwm.AllPhases[PHASE_C_POS].State, 2, nn++); + WriteOutputArray(upp.hpwm.AllPhases[PHASE_C_NEG].State, 2, nn++); // USER APP OUTPUT END } \ No newline at end of file diff --git a/MATLAB/app_wrapper/app_wrapper.c b/MATLAB/app_wrapper/app_wrapper.c index 78882d0..3941387 100644 --- a/MATLAB/app_wrapper/app_wrapper.c +++ b/MATLAB/app_wrapper/app_wrapper.c @@ -9,7 +9,15 @@ void app_step(void) { // USER APP STEP START + UPP_While(); + + static uint32_t uwTickPrev = 0; + if (uwTickPrev != uwTick) + { + uwTickPrev = uwTick; + UPP_Tick(); + } // USER APP STEP END } diff --git a/MATLAB/upp_r2023.slx b/MATLAB/upp_r2023.slx index 2f48cc2e843785be8983b5a580b535800f37d51a..1685338da56c07177a2992097c19506c408cb81d 100644 GIT binary patch delta 49996 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 delta 49192 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 M=Wl!9@n2v69|tI{N&o-= diff --git a/UPP/AllLibs/Modbus b/UPP/AllLibs/Modbus index 3aa2797..e0ce0e6 160000 --- a/UPP/AllLibs/Modbus +++ b/UPP/AllLibs/Modbus @@ -1 +1 @@ -Subproject commit 3aa279736d9383a06613f72d7b7462f0f23d7d4d +Subproject commit e0ce0e6dbf35d035175d30971442bc8d99f3bdd7 diff --git a/UPP/AllLibs/PeriphGeneral b/UPP/AllLibs/PeriphGeneral index 1cc886a..272642b 160000 --- a/UPP/AllLibs/PeriphGeneral +++ b/UPP/AllLibs/PeriphGeneral @@ -1 +1 @@ -Subproject commit 1cc886aa8dc5c2fcfdad93a21207793b2cd85fab +Subproject commit 272642b310043355b0af95e0410afbbcb74d17b7 diff --git a/UPP/Core/Configs/memspi_config.h b/UPP/Core/Configs/memspi_config.h index 54ed13b..aa1cde8 100644 --- a/UPP/Core/Configs/memspi_config.h +++ b/UPP/Core/Configs/memspi_config.h @@ -16,7 +16,7 @@ ******************************************************************************/ #ifndef __SPI_MEMORY_CONFIG_H_ #define __SPI_MEMORY_CONFIG_H_ -#include "upp_config.h" +#include "upp_defs.h" /////////////////////////---USER SETTINGS---///////////////////////// diff --git a/UPP/Core/Configs/modbus_config.h b/UPP/Core/Configs/modbus_config.h index dcae48e..0851d68 100644 --- a/UPP/Core/Configs/modbus_config.h +++ b/UPP/Core/Configs/modbus_config.h @@ -19,7 +19,7 @@ ******************************************************************************/ #ifndef _MODBUS_CONFIG_H_ #define _MODBUS_CONFIG_H_ -#include "upp_config.h" +#include "upp_defs.h" // Общие параметры #define MODBUS_DEVICE_ID 1 ///< Адрес устройства в сети Modbus diff --git a/UPP/Core/Configs/modbus_data.c b/UPP/Core/Configs/modbus_data.c index 1626ce8..b1cb836 100644 --- a/UPP/Core/Configs/modbus_data.c +++ b/UPP/Core/Configs/modbus_data.c @@ -22,6 +22,9 @@ #include "modbus_inputregs.h" #include "modbus_devid.h" +/* DEFINE DATA FOR MODBUS */ +MB_DataStructureTypeDef MB_DATA = {0};; ///< Coils & Registers +MB_DataInternalTypeDef MB_INTERNAL; /** * @brief Check is address valid for certain array. @@ -78,6 +81,10 @@ MB_ExceptionTypeDef MB_DefineRegistersAddress(uint16_t **pRegs, uint16_t Addr, u { *pRegs = MB_Set_Register_Ptr(&MB_DATA.HoldRegs, Addr - R_HOLDING_ADDR); // указатель на выбранный по Addr регистр } + else if(MB_Check_Address_For_Arr(Addr, Qnt, R_HOLDING_PRVT_PARAMS_ADR, R_HOLDING_PRVT_PARAMS_QNT) == ET_NO_ERRORS) + { + *pRegs = MB_Set_Register_Ptr(&MB_INTERNAL, Addr - R_HOLDING_PRVT_PARAMS_ADR); // указатель на выбранный по Addr регистр + } // if address doesnt match any array - return illegal data address response else { diff --git a/UPP/Core/Configs/modbus_data.h b/UPP/Core/Configs/modbus_data.h index 848006b..985af13 100644 --- a/UPP/Core/Configs/modbus_data.h +++ b/UPP/Core/Configs/modbus_data.h @@ -50,20 +50,24 @@ #ifndef _MODBUS_DATA_H_ #define _MODBUS_DATA_H_ - + +#include "upp_defs.h" +#include "upp_control.h" #include "stdint.h" //--------------SIZES OF DATA--------------- +#define R_HOLDING_PRVT_PARAMS_ADR 555 +#define R_HOLDING_PRVT_PARAMS_QNT (sizeof(UPP_PrvtParams_t)/sizeof(uint16_t)) // DEFINES FOR INPUT REGISTERS ARRAYS #define R_INPUT_ADDR 0 ///< Начальный адрес входных регистров -#define R_INPUT_QNT 16 ///< Количество входных регистров +#define R_INPUT_QNT (sizeof(UPP_PUI_Values_t)/sizeof(uint16_t)) ///< Количество входных регистров // DEFINES FOR HOLDING REGISTERS ARRAYS #define R_HOLDING_ADDR 0 ///< Начальный адрес регистров хранения -#define R_HOLDING_QNT 16 ///< Количество регистров хранения +#define R_HOLDING_QNT (sizeof(UPP_PUI_Params_t)/sizeof(uint16_t)) ///< Количество регистров хранения // DEFINES FOR COIL ARRAYS #define C_COILS_ADDR 0 ///< Начальный адрес коилов @@ -89,7 +93,7 @@ */ typedef struct //MB_DataInRegsTypeDef { - uint16_t in[16]; + UPP_PUI_Values_t pui; }MB_DataInRegsTypeDef; @@ -98,7 +102,7 @@ typedef struct //MB_DataInRegsTypeDef */ typedef struct //MB_DataInRegsTypeDef { - uint16_t out[16]; + UPP_PUI_Params_t pui_params; }MB_DataHoldRegsTypeDef; @@ -153,6 +157,13 @@ typedef struct // tester modbus data extern MB_DataStructureTypeDef MB_DATA; +typedef struct +{ + UPP_FuncCalls_t FuncCalls; + UPP_PrvtParams_t param; +}MB_DataInternalTypeDef; +extern MB_DataInternalTypeDef MB_INTERNAL; + #endif //_MODBUS_DATA_H_ ///////////////////////////////////////////////////////////// diff --git a/UPP/Core/Configs/mylibs_config.h b/UPP/Core/Configs/mylibs_config.h index 89c6755..a9dbd71 100644 --- a/UPP/Core/Configs/mylibs_config.h +++ b/UPP/Core/Configs/mylibs_config.h @@ -10,7 +10,7 @@ *************************************************************************/ #ifndef __MYLIBS_CONFIG_H_ #define __MYLIBS_CONFIG_H_ -#include "upp_config.h" +#include "upp_defs.h" // user includes /** diff --git a/UPP/Core/Configs/upp_config.h b/UPP/Core/Configs/upp_config.h index 1208474..706225f 100644 --- a/UPP/Core/Configs/upp_config.h +++ b/UPP/Core/Configs/upp_config.h @@ -27,39 +27,58 @@ #define TEMP_1 0 #define TEMP_2 1 -/* Дефайны для базовых величин */ -#define PM_U_BASE 1216.0 -#define PM_I_BASE 53.0 -/* Дефайны для настройки мониторинга питания */ -#define PM_ADC_PERIOD_MKS 10 -#define PM_ZERO_CROSS_HYSTERESIS_V 10 -#define PM_ZERO_CROSS_DEBOUNCE_10US 2.5*100 // (2.5 * 100 = 2.5 мс) - - - - -/* Рассчитанные дефайны */ -#define PM_ADC_PERIOD (180*PM_ADC_PERIOD_MKS)-1 - -/* Общие для всего проекта структуры*/ /** - * @brief Состояния полуволны + * @addtogroup UPP_DEFAULT_PARAMS Default params for external flash + * @ingroup UPP_CONFIG + * @brief Дефолтные параметры для внешней памяти. Они применятся по команде или по ошибке + * @{ */ -typedef enum { - UPP_WAVE_UNKNOWED = 0, - UPP_WAVE_POSITIVE, - UPP_WAVE_NEGATIVE -} UPP_HalfWave_t; - +/* Параметры ПУИ */ +#define PUI_Iref_PERCENT_DEFAULT 150 +#define PUI_Tnt_MS_DEFAULT 300 +#define PUI_Umin_PERCENT_DEFAULT 80 +#define PUI_Umax_PERCENT_DEFAULT 120 +#define PUI_Imax_PERCENT_DEFAULT 99 +#define PUI_Imin_PERCENT_DEFAULT 7 +#define PUI_TiMax_US_DEFAULT 5000 +#define PUI_Tdelay_SECONDS_DEFAULT 30 +#define PUI_Interlace_EN_DEFAULT 5000 + +/* Параметры АЦП */ +#define ADC_U_MAX_V_DEFAULT 1216.0 +#define ADC_I_MAX_A_DEFAULT 53.0 +#define ADC_U_ZERO_DEFAULT 2048 +#define ADC_I_ZERO_DEFAULT 2048 + +/* Параметры определения перехода через ноль */ +#define ZERO_CROSS_HYSTERESIS_V_DEFAULT 10.0 +#define ZERO_CROSS_DEBOUNCE_10US_DEFAULT 2.5*100 // (2.5 * 100 = 2.5 мс) + +/* Параметры ШИМ для тиристоров */ +#define PWM_THYR_FREQUENCY_HZ_DEFAULT 20000 +#define PWM_THYR_PULSE_NUMBER_DEFAULT 20 + +/** //UPP_DEFAULT_PARAMS + * @} + */ + + /** - * @brief Названия Фаз + * @addtogroup UPP_COMPILED_PARAMS Fixed params + * @ingroup UPP_CONFIG + * @brief Параметры устанавливаемые на этапе компиляции. Без перепрошивки их не поменять + * @{ */ -typedef enum { - UPP_PHASE_A = 0, - UPP_PHASE_B = 1, - UPP_PHASE_C = 2, - UPP_PHASE_UNKNOWN = 3 -} UPP_Phase_t; +#define PM_U_NOM_V 690 +#define PM_I_NOM_V 5 +#define PM_F_NOM_HZ 50 + +#define PM_ADC_PERIOD_MKS 10 ///< Период опроса АЦП в мкс +/* Частоты таймеров в МГц*/ +#define ADC_TIM8_FREQ_MZH 180 ///< Частота тиков таймера АЦП +#define PWM_TIM1_FREQ_MHZ 180 ///< Частота тиков таймера ШИМ (1-4 каналы) +#define PWM_TIM3_FREQ_MHZ 90 ///< Частота тиков таймера ШИМ (5-6 каналы) +#define ANGLE_TIM2_FREQ_MHZ 90 ///< Частота тиков таймера отсчета угла открытия тиристоров #endif //_UPP_CONFIG_H_ diff --git a/UPP/Core/Configs/upp_defs.h b/UPP/Core/Configs/upp_defs.h new file mode 100644 index 0000000..5d9ae19 --- /dev/null +++ b/UPP/Core/Configs/upp_defs.h @@ -0,0 +1,178 @@ +/** +****************************************************************************** +* @file upp_defs.h +* @brief Общие дефайны УПП +****************************************************************************** +@addtogroup UPP_DEFS UPP defines +@ingroup UPP_MAIN +@brief Общие дефайны для всего проекта УПП +@{ +****************************************************************************** +* @details +******************************************************************************/ +#ifndef _UPP_DEFS_H_ +#define _UPP_DEFS_H_ +#include "upp_config.h" + +/** + * @addtogroup UPP_HIGHLEVEL Defines for high-level + * @ingroup UPP_DEFS + * @brief Дефайны УПП которые определяют коды ошибок и параметры для общения с верхним уровнем + * @{ + */ + + +/** + * @brief Список аварий УПП + */ +typedef enum { + WM_Not_Init = 0, ///< УПП не инициализирован + WM_Ready = 1, ///< УПП в готовности + WM_Running = 2, ///< УПП в работе, управляет тиристорами + WM_Error = 3, ///< УПП в аварии +} UPP_WorkModeType_t; + +/** + * @brief Список аварий УПП + */ +typedef enum { + Err_None = 0, ///< Неисправность отсутствует + + /* Програмные ошибки */ + Err_Internal_1 = 1, ///< Внутренняя неисправность УПП 1 + Err_Internal_2 = 2, ///< Внутренняя неисправность УПП 2 + Err_Internal_3 = 3, ///< Внутренняя неисправность УПП 3 + Err_Internal_4 = 4, ///< Внутренняя неисправность УПП 4 + Err_Internal_5 = 5, ///< Внутренняя неисправность УПП 5 + Err_Internal_6 = 6, ///< Внутренняя неисправность УПП 6 + + /* Ошибки по питанию */ + Err_Power_Digit_5V = 7, ///< Неисправность цифрового источника питания (5 В) + Err_Power_24V = 8, ///< Неисправность источника питания 24 В + Err_Power_Analog_5V = 9, ///< Неисправность аналогового источника питания микроконтроллера (± 5 В) + Err_Power_SCI_5V = 10, ///< Неисправность источника питания последовательных интерфейсов микроконтроллера (5 В) + Err_Power_DIO_24V = 11, ///< Неисправность источника питания дискретных входов/выходов (24 В) + + /* Ошибки по допустимым пределам Наряжений/Токов/Температуры */ + Err_OverCurrent = 12, ///< Ток выше допустимого (см. Imax и TiMax в @ref UPP_PUI_Params_t) + Err_OverVoltage = 13, ///< Напряжение сети выше допустимого (см. Umах в @ref UPP_PUI_Params_t) + Err_OverTemperature = 14, ///< Температура выше допустимой (плюс 85 °C) + Err_UnderVoltage = 15, ///< Напряжение сети ниже допустимого (см. Umin в @ref UPP_PUI_Params_t) + + /* Ошибки по обрывам фаз */ + Err_LossPhaseAll = 16, ///< Обрыв трёх фаз (см. Imin в @ref UPP_PUI_Params_t) + Err_LossPhaseA = 17, ///< Обрыв фазы A (см. Imin в @ref UPP_PUI_Params_t) + Err_LossPhaseB = 18, ///< Обрыв фазы B (см. Imin в @ref UPP_PUI_Params_t) + Err_LossPhaseC = 19, ///< Обрыв фазы C (см. Imin в @ref UPP_PUI_Params_t) + + /* Другие ошибки */ + Err_LongStart = 20, ///< Затянутый пуск (ток не спадает за установленное время) (см. Tdelay в @ref UPP_PUI_Params_t) + Err_Interlace = 21, ///< Неправильный порядок чередования фаз (см. Interlace в @ref UPP_PUI_Params_t) + Err_OverFrequency = 22, ///< Частота сети выше допустимой + Err_UnderFrequency = 23, ///< Частота сети ниже допустимой +} UPP_ErrorType_t; + + +/** + * @brief Структура данных от УПП передаваемых в ПУИ + */ +typedef struct { + uint16_t Error; /** @brief Текущая авария + @details В случае срабатывания защиты */ + + uint16_t WorkMode; /** @brief Режим работы + @details «Готовность» или «Работа» */ + + uint16_t Voltage; /** @brief Напряжение на входе УПП, Вольты */ + + uint16_t Current; /** @brief Ток на входе УПП, Амперы */ + + uint16_t Frequency; /** @brief Частота напряжения на входе УПП */ + + uint16_t Temperature; /** @brief Температура радиатора тиристоров, Градусы Цельсия */ +} UPP_PUI_Values_t; + +/** + * @brief Структура параметров УПП от ПУИ + * @details Параметры по умолчанию приведены в @ref UPP_DEFAULT_PARAMS + * @note Защиты №X приведены в @ref UPP_ErrorType_t + */ +typedef struct { + uint16_t Iref; /** @brief Уставка ограничения тока УПП + @details В диапазоне от 100 % до 500 % от Iн = 5 А + По умолчанию – @ref PUI_Iref_PERCENT_DEFAULT */ + + uint16_t Tnt; /** @brief Уставка времени нарастания заданного тока + @details В диапазоне от 50 до 5000 мс. + По умолчанию – @ref PUI_Tnt_MS_DEFAULT */ + + uint16_t Umin; /** @brief Уставка защиты №15 от понижения напряжения входной сети + @details В диапазоне от 5% до 99 % от Uн = 690 В. + По умолчанию – @ref PUI_Umin_PERCENT_DEFAULT */ + + uint16_t Umax; /** @brief Уставка защиты №13 от повышения напряжения входной сети + @details В диапазоне от 100% до 120 % от Uн = 690 В. + По умолчанию – @ref PUI_Umax_PERCENT_DEFAULT */ + + uint16_t Imax; /** @brief Уставка защиты №12 по току + @details В диапазоне от 5 % до 99 % от 50 А. + По умолчанию – @ref PUI_Imax_PERCENT_DEFAULT */ + + uint16_t Imin; /** @brief Уставка защит № 16 – №19 от обрыва фаз(ы) + @details В диапазоне от 0 % до 40 % от Iн = 5 А. При задании нуля эти защиты отключаются. + По умолчанию – @ref PUI_Imin_PERCENT_DEFAULT */ + + uint16_t TiMax; /** @brief Выдержка времени на срабатывание защиты № 12 по току + @details В диапазоне от 500 до 10000 мкс. + По умолчанию – @ref PUI_TiMax_US_DEFAULT */ + + uint16_t Tdelay; /** @brief Выдержка времени защиты № 20 от затянутого пуска + @details В диапазоне от 5 до 60 с. + По умолчанию – @ref PUI_Tdelay_SECONDS_DEFAULT */ + + uint16_t Interlace; /** @brief Разрешение защиты № 21 от обратного порядка чередования фаз; + @details 0 – нет защиты, 1 – есть защита. + По умолчанию – @ref PUI_Interlace_EN_DEFAULT */ +} UPP_PUI_Params_t; + + +/** //UPP_HIGHLEVEL + * @} + */ + + +/** + * @addtogroup UPP_INTERNAL_DEFS Defines for internal use + * @ingroup UPP_DEFS + * @brief Дефайны УПП которые используютяс исключительно внутри программы + * @{ + */ +#define ANGLE_PERIOD_MS (((float)1/(PM_F_NOM_HZ*2))*1000) +/** + * @brief Состояния полуволны + */ +typedef enum { + UPP_WAVE_UNKNOWED = 0, + UPP_WAVE_POSITIVE, + UPP_WAVE_NEGATIVE +} UPP_HalfWave_t; + +/** + * @brief Названия Фаз + */ +typedef enum { + UPP_PHASE_A = 0, + UPP_PHASE_B = 1, + UPP_PHASE_C = 2, + UPP_PHASE_UNKNOWN = 3 +} UPP_Phase_t; + +/** //UPP_INTERNAL_DEFS + * @} + */ +#endif //_UPP_DEFS_H_ + + +/** //UPP_DEFS + * @} + */ diff --git a/UPP/Core/Inc/main.h b/UPP/Core/Inc/main.h index 704767c..ad65e1f 100644 --- a/UPP/Core/Inc/main.h +++ b/UPP/Core/Inc/main.h @@ -31,11 +31,10 @@ extern "C" { /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ -#include "upp_config.h" +#include "upp_defs.h" +#include "upp_errors.h" #include "mylibs_include.h" -#ifndef MATLAB #include "modbus.h" -#endif /* USER CODE END Includes */ /* Exported types ------------------------------------------------------------*/ @@ -56,7 +55,7 @@ extern "C" { void Error_Handler(void); /* USER CODE BEGIN EFP */ -extern __IO uint32_t micros; + /* USER CODE END EFP */ /* Private defines -----------------------------------------------------------*/ diff --git a/UPP/Core/PowerMonitor/adc_tools.c b/UPP/Core/PowerMonitor/adc_tools.c index 5129c70..2a8b7eb 100644 --- a/UPP/Core/PowerMonitor/adc_tools.c +++ b/UPP/Core/PowerMonitor/adc_tools.c @@ -7,32 +7,6 @@ ******************************************************************************/ #include "adc_tools.h" -//Полосовой фильтр 45-55 Гц -static float coefs_biquad_U[5] = { - 0.000010f, // b0 - 0.000020f, // b1 - 0.000010f, // b2 - -1.900000f, // a1 - 0.950000f // a2 -}; - -// ФНЧ 100 Гц -static float coefs_biquad_I[5] = { - 0.000010f, // b0 - 0.000020f, // b1 - 0.000010f, // b2 - -1.900000f, // a1 - 0.950000f // a2 -}; -// ФНЧ 10 Гц -static float coefs_biquad_T[5] = { - 0.0002f, // b0 - 0.0004f, // b1 - 0.0002f, // b2 - -1.98f, // a1 - 0.980f // a2 -}; - static void ADC_EnableAllFilters(ADC_Periodic_t *adc) { for(int i = 0; i < ADC_NUMB_OF_CHANNELS; i++) @@ -106,6 +80,8 @@ HAL_StatusTypeDef ADC_ConfigChannel(ADC_Periodic_t *adc, int ChNumb, uint16_t le adc->Coefs[ChNumb].lMax = levelMax; adc->Coefs[ChNumb].vMax = valueMax; adc->Coefs[ChNumb].lZero = levelZero; + + ADC_ResetStatistics(adc, ChNumb); return HAL_OK; } @@ -116,16 +92,16 @@ HAL_StatusTypeDef ADC_ConfigChannel(ADC_Periodic_t *adc, int ChNumb, uint16_t le * @return HAL Status. * @details Запускает АЦП с частотой дискретизации на которую настроен таймер adc_tim. */ -HAL_StatusTypeDef ADC_Start(ADC_Periodic_t *adc, uint16_t Period) +HAL_StatusTypeDef ADC_Start(ADC_Periodic_t *adc, float PeriodUs) { HAL_StatusTypeDef res; if(assert_upp(adc)) return HAL_ERROR; - if(Period == 0) + if(PeriodUs == 0) return HAL_ERROR; // Запускаем таймер который будет запускать опрос АЦП с заданным периодом - __HAL_TIM_SET_AUTORELOAD(adc->htim, Period); + __HAL_TIM_SET_AUTORELOAD(adc->htim, TIM_MicrosToTick(PeriodUs, ADC_TIM8_FREQ_MZH)); res = HAL_TIM_Base_Start(adc->htim); if(res != HAL_OK) { diff --git a/UPP/Core/PowerMonitor/adc_tools.h b/UPP/Core/PowerMonitor/adc_tools.h index e61e16b..7a47e57 100644 --- a/UPP/Core/PowerMonitor/adc_tools.h +++ b/UPP/Core/PowerMonitor/adc_tools.h @@ -123,7 +123,7 @@ HAL_StatusTypeDef ADC_Init(ADC_Periodic_t *adc, TIM_HandleTypeDef *htim, ADC_Han /* Конфигуарция канала АЦП. */ HAL_StatusTypeDef ADC_ConfigChannel(ADC_Periodic_t *adc, int ChNumb, uint16_t levelZero, float valueMax, uint16_t levelMax); /* Запуск АЦП. */ -HAL_StatusTypeDef ADC_Start(ADC_Periodic_t *adc, uint16_t Period); +HAL_StatusTypeDef ADC_Start(ADC_Periodic_t *adc, float PeriodUs); /* Остановка АЦП. */ HAL_StatusTypeDef ADC_Stop(ADC_Periodic_t *adc); diff --git a/UPP/Core/PowerMonitor/power_monitor.c b/UPP/Core/PowerMonitor/power_monitor.c index f4dcf07..ff8c982 100644 --- a/UPP/Core/PowerMonitor/power_monitor.c +++ b/UPP/Core/PowerMonitor/power_monitor.c @@ -15,17 +15,32 @@ HAL_StatusTypeDef PowerMonitor_Init(PowerMonitor_t *hpm) if(ADC_Init(&hpm->adc, &adc_tim, &hadc3) != HAL_OK) return HAL_ERROR; - if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_UBA, 2048, PM_U_BASE, 4095) != HAL_OK) - return HAL_ERROR; - if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_UAC, 2048, PM_U_BASE, 4095) != HAL_OK) - return HAL_ERROR; - if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_IC, 2048, PM_I_BASE, 4095) != HAL_OK) + if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_UBA, + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_UBA], + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_UBA], + 4095) != HAL_OK) return HAL_ERROR; - if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_IA, 2048, PM_I_BASE, 4095) != HAL_OK) + + if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_UAC, + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_UAC], + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_UAC], + 4095) != HAL_OK) + return HAL_ERROR; + + if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_IC, + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_IC], + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_IC], + 4095) != HAL_OK) + return HAL_ERROR; + + if(ADC_ConfigChannel(&hpm->adc, ADC_CHANNEL_IA, + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_IA], + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_IA], + 4095) != HAL_OK) return HAL_ERROR; - if(ZC_Init(&hpm->zc, 3, PM_ZERO_CROSS_HYSTERESIS_V, PM_ZERO_CROSS_DEBOUNCE_10US) != HAL_OK) + if(ZC_Init(&hpm->zc, 3, (float)MB_INTERNAL.param.zc.Hysteresis/100, MB_INTERNAL.param.zc.DebouneCouner) != HAL_OK) return HAL_ERROR; if(ZC_ConfigChannel(&hpm->zc, U_BA, ZC_BOTH_EDGES) != HAL_OK) @@ -41,7 +56,7 @@ HAL_StatusTypeDef PowerMonitor_Init(PowerMonitor_t *hpm) HAL_StatusTypeDef PowerMonitor_Start(PowerMonitor_t *hpm) { - if(ADC_Start(&hpm->adc, PM_ADC_PERIOD) != HAL_OK) + if(ADC_Start(&hpm->adc, PM_ADC_PERIOD_MKS) != HAL_OK) return HAL_ERROR; return HAL_OK; @@ -51,6 +66,7 @@ HAL_StatusTypeDef PowerMonitor_Start(PowerMonitor_t *hpm) void PowerMonitor_Handle(PowerMonitor_t *hpm) { + /* Считываем АЦП */ static uint32_t last_zc_cnt[ADC_NUMB_OF_U_CHANNELS] = {0}; ADC_Handle(&hpm->adc); diff --git a/UPP/Core/PowerMonitor/zero_cross.c b/UPP/Core/PowerMonitor/zero_cross.c index d587251..621f08e 100644 --- a/UPP/Core/PowerMonitor/zero_cross.c +++ b/UPP/Core/PowerMonitor/zero_cross.c @@ -28,9 +28,9 @@ HAL_StatusTypeDef ZC_Init(ZeroCross_Handle_t *zc, uint8_t num_channels, memset(zc, 0, sizeof(ZeroCross_Handle_t)); // Установка параметров хендла - zc->NumChannels = num_channels; - zc->Hysteresis = hysteresis; - zc->DebounceSamples = debounce_samples; + zc->Config.NumChannels = num_channels; + zc->Config.Hysteresis = hysteresis; + zc->Config.DebounceSamples = debounce_samples; // Инициализация каналов for (int i = 0; i < num_channels; i++) { @@ -63,7 +63,7 @@ HAL_StatusTypeDef ZC_ConfigChannel(ZeroCross_Handle_t *zc, uint8_t channel, if (assert_upp(zc)){ return HAL_ERROR; } - if (channel >= zc->NumChannels) { + if (channel >= zc->Config.NumChannels) { return HAL_ERROR; } @@ -94,7 +94,7 @@ void ZC_ProcessChannel(ZeroCross_Handle_t *zc, uint8_t channel, float value, uin if (!zc->f.Monitoring) { return; } - if (channel >= zc->NumChannels) { + if (channel >= zc->Config.NumChannels) { return; } int zc_detected = 0; @@ -111,16 +111,16 @@ void ZC_ProcessChannel(ZeroCross_Handle_t *zc, uint8_t channel, float value, uin } // Детектирование rising edge (отрицательное -> положительное) - if ((zc_ch->LastValue <= -zc->Hysteresis) && - (value >= zc->Hysteresis)) + if ((zc_ch->LastValue <= -zc->Config.Hysteresis) && + (value >= zc->Config.Hysteresis)) { if (zc_ch->EdgeType == ZC_RISING_EDGE || zc_ch->EdgeType == ZC_BOTH_EDGES) { - if(zc->DebounceSamples) + if(zc->Config.DebounceSamples) { if(zc_ch->DebounceCounter == 0) { - zc_ch->DebounceCounter = zc->DebounceSamples; + zc_ch->DebounceCounter = zc->Config.DebounceSamples; } } @@ -128,16 +128,16 @@ void ZC_ProcessChannel(ZeroCross_Handle_t *zc, uint8_t channel, float value, uin } } // Детектирование falling edge (положительное -> отрицательное) - else if ((zc_ch->LastValue >= zc->Hysteresis) && - (value <= -zc->Hysteresis)) + else if ((zc_ch->LastValue >= zc->Config.Hysteresis) && + (value <= -zc->Config.Hysteresis)) { if (zc_ch->EdgeType == ZC_FALLING_EDGE || zc_ch->EdgeType == ZC_BOTH_EDGES) { - if(zc->DebounceSamples) + if(zc->Config.DebounceSamples) { if(zc_ch->DebounceCounter == 0) { - zc_ch->DebounceCounter = zc->DebounceSamples; + zc_ch->DebounceCounter = zc->Config.DebounceSamples; } } @@ -149,7 +149,7 @@ void ZC_ProcessChannel(ZeroCross_Handle_t *zc, uint8_t channel, float value, uin { zc_ch->HalfWave = zc_detected == 1 ? UPP_WAVE_POSITIVE : UPP_WAVE_NEGATIVE; zc_ch->Occurred = 1; - uint32_t RealTimeShift = 0;//zc->DebounceSamples*(timestamp - zc->LastTick); // коло-во тиков * период вызова функции + uint32_t RealTimeShift = 0;//zc->Config.DebounceSamples*(timestamp - zc->LastTick); // коло-во тиков * период вызова функции // Переход подтвержден сразу uint32_t RealTimestamp = timestamp-RealTimeShift; @@ -166,7 +166,7 @@ void ZC_ProcessChannel(ZeroCross_Handle_t *zc, uint8_t channel, float value, uin } // Сохраняем текущее значение для следующей итерации в случае если оно не в мертвой зоне - if((value > zc->Hysteresis) || (value < -zc->Hysteresis)) + if((value > zc->Config.Hysteresis) || (value < -zc->Config.Hysteresis)) { zc_ch->LastValue = value; } @@ -188,7 +188,7 @@ void ZC_ProcessAllChannels(ZeroCross_Handle_t *zc, float *values, uint32_t times return; } - for (int ch = 0; ch < zc->NumChannels; ch++) { + for (int ch = 0; ch < zc->Config.NumChannels; ch++) { ZC_ProcessChannel(zc, ch, values[ch], timestamp); } @@ -238,7 +238,7 @@ float ZC_GetFrequency(ZeroCross_Handle_t *zc, uint8_t channel) if (assert_upp(zc)){ return 0.0f; } - if (channel >= zc->NumChannels) { + if (channel >= zc->Config.NumChannels) { return 0.0f; } @@ -273,7 +273,7 @@ void ZC_Reset(ZeroCross_Handle_t *zc, uint8_t channel) return; } - if (channel < zc->NumChannels) { + if (channel < zc->Config.NumChannels) { zc->Channel[channel].LastValue = 0.0f; zc->Channel[channel].CurrentValue = 0.0f; zc->Channel[channel].DebounceCounter = 0; @@ -284,7 +284,7 @@ void ZC_Reset(ZeroCross_Handle_t *zc, uint8_t channel) } else { // Сброс всех каналов - for (int i = 0; i < zc->NumChannels; i++) { + for (int i = 0; i < zc->Config.NumChannels; i++) { zc->Channel[i].LastValue = 0.0f; zc->Channel[i].CurrentValue = 0.0f; zc->Channel[i].DebounceCounter = 0; diff --git a/UPP/Core/PowerMonitor/zero_cross.h b/UPP/Core/PowerMonitor/zero_cross.h index ba22f79..cb75130 100644 --- a/UPP/Core/PowerMonitor/zero_cross.h +++ b/UPP/Core/PowerMonitor/zero_cross.h @@ -100,15 +100,22 @@ typedef struct { ZC_EdgeType_t EdgeType; ///< Тип детектируемого перехода } ZC_Channel_t; + +/** + * @brief Параметры перехода через ноль + */ +typedef struct { + uint8_t NumChannels; ///< Количество используемых каналов для этого хендла + float Hysteresis; ///< Гистерезис для избежания дребезга + uint16_t DebounceSamples; ///< Количество samples для антидребезга +} ZC_Config_t; + /** * @brief Хендл детектора нуля */ typedef struct { - ZC_Channel_t Channel[ZC_MAX_CHANNELS]; ///< Каналы @ref ZC_Channel_t - uint8_t NumChannels; ///< Количество используемых каналов для этого хендла - float Hysteresis; ///< Гистерезис для избежания дребезга - uint16_t DebounceSamples; ///< Количество samples для антидребезга - + ZC_Channel_t Channel[ZC_MAX_CHANNELS]; ///< Каналы @ref ZC_Channel_t + ZC_Config_t Config; struct { unsigned Initialized:1; ///< Флаг инициализации unsigned Monitoring:1; ///< Флаг активности мониторинга diff --git a/UPP/Core/Src/main.c b/UPP/Core/Src/main.c index 9096b23..076b2e2 100644 --- a/UPP/Core/Src/main.c +++ b/UPP/Core/Src/main.c @@ -34,7 +34,7 @@ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ -__IO uint32_t micros; + /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ diff --git a/UPP/Core/Src/stm32f4xx_it.c b/UPP/Core/Src/stm32f4xx_it.c index f151479..cf5eee0 100644 --- a/UPP/Core/Src/stm32f4xx_it.c +++ b/UPP/Core/Src/stm32f4xx_it.c @@ -246,6 +246,7 @@ void TIM8_TRG_COM_TIM14_IRQHandler(void) HAL_TIM_IRQHandler(&htim14); /* USER CODE BEGIN TIM8_TRG_COM_TIM14_IRQn 1 */ #endif + UPP_Tick(); /* USER CODE END TIM8_TRG_COM_TIM14_IRQn 1 */ } diff --git a/UPP/Core/UPP/angle_control.c b/UPP/Core/UPP/angle_control.c index 767edaf..781a050 100644 --- a/UPP/Core/UPP/angle_control.c +++ b/UPP/Core/UPP/angle_control.c @@ -1,7 +1,7 @@ /** ****************************************************************************** -* @file pwm_thyristors.c -* @brief Модуль для управления тиристорами +* @file angle_control.c +* @brief Модуль для формирования и отсчитывания угла открытия ****************************************************************************** * @details ******************************************************************************/ @@ -10,22 +10,16 @@ /** - * @brief Инициализация ШИМ тиристоров. + * @brief Инициализация таймера для расчета угла открытия. * @param hangle Указатель на таймер * @return HAL Status. */ -HAL_StatusTypeDef Angle_Init(Angle_Handle_t *hangle, float AngleMin, float AngleMax) +HAL_StatusTypeDef Angle_Init(Angle_Handle_t *hangle) { if(hangle == NULL) return HAL_ERROR; - if(AngleMax < 0 || AngleMax > 1) - return HAL_ERROR; - if(AngleMin < 0 || AngleMin > 1) - return HAL_ERROR; hangle->htim = &angletim; - hangle->AngleMax = AngleMax; - hangle->AngleMin = AngleMin; HAL_TIM_OC_Start_IT(hangle->htim, ANGLE_CHANNEL_1); @@ -36,9 +30,65 @@ HAL_StatusTypeDef Angle_Init(Angle_Handle_t *hangle, float AngleMin, float Angle Angle_Reset(hangle, UPP_PHASE_B); Angle_Reset(hangle, UPP_PHASE_C); + hangle->f.Initialized = 1; return HAL_OK; } +/** + * @brief Инициализация углов открытия. + * @param hangle Указатель на таймер + * @param AngleLimit Лимит AngleMax, рассчитывается от параметров ШИМ + * @param AngleMin Минимально возможный угол открытия + * @param AngleMax Максимально возможный угол открытия + * @return HAL Status. + */ +HAL_StatusTypeDef Angle_SetRange(Angle_Handle_t *hangle, float AngleMin, float AngleMax) +{ + if(assert_upp(hangle)) + return HAL_ERROR; + if(hangle->f.Running) + return HAL_BUSY; + if(AngleMax < 0 || AngleMax > 1) + return HAL_ERROR; + if(AngleMin < 0 || AngleMin > 1) + return HAL_ERROR; + + if(AngleMax > hangle->Config.AngleLimit) + AngleMax = hangle->Config.AngleLimit; + if(AngleMin > hangle->Config.AngleLimit) + AngleMin = hangle->Config.AngleLimit; + + if(AngleMin >= AngleMax) + return HAL_ERROR; + + hangle->Config.AngleMax = AngleMax; + hangle->Config.AngleMin = AngleMin; + + return HAL_OK; +} + +/** + * @brief Инициализация предельного угла открытия. + * @param hangle Указатель на таймер + * @param AngleLimit Лимит AngleMax, рассчитывается от параметров ШИМ + * @param AngleMin Минимально возможный угол открытия + * @param AngleMax Максимально возможный угол открытия + * @return HAL Status. + */ +HAL_StatusTypeDef Angle_SetLimit(Angle_Handle_t *hangle, float AngleLimit) +{ + if(assert_upp(hangle)) + return HAL_ERROR; + if(hangle->f.Running) + return HAL_BUSY; + + if(AngleLimit < 0 || AngleLimit > 1) + return HAL_ERROR; + + hangle->Config.AngleLimit = AngleLimit; + + return HAL_OK; +} /** * @brief Хендл таймера для рассчета угла открытия. * @param hangle Указатель на таймер @@ -46,7 +96,7 @@ HAL_StatusTypeDef Angle_Init(Angle_Handle_t *hangle, float AngleMin, float Angle */ UPP_Phase_t Angle_Handle(Angle_Handle_t *hangle) { - if(hangle == NULL) + if(assert_upp(hangle)) return UPP_PHASE_UNKNOWN; @@ -79,9 +129,9 @@ UPP_Phase_t Angle_Handle(Angle_Handle_t *hangle) * @param Phase Для какой фазы надо установить угол открытия * @return HAL Status. */ -HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float Angle, float Freq) +HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float Angle, float PeriodMs) { - if(hangle == NULL) + if(assert_upp(hangle)) return HAL_ERROR; // Если канал дурацкий - возвращаем ошибку @@ -89,16 +139,16 @@ HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float A { return HAL_ERROR; } - if(Angle > hangle->AngleMax) + if(Angle > hangle->Config.AngleMax) { - Angle = hangle->AngleMax; + Angle = hangle->Config.AngleMax; } - if(Angle < hangle->AngleMin) + if(Angle < hangle->Config.AngleMin) { - Angle = hangle->AngleMin; + Angle = hangle->Config.AngleMin; } - uint32_t timer_ticks = TIM_FreqToTick(Freq/Angle, ANGLE_TIM2_FREQ_MHZ); + uint32_t timer_ticks = TIM_MillisToTick(PeriodMs*Angle, ANGLE_TIM2_FREQ_MHZ); uint32_t ccr_ticks = __HAL_TIM_GET_COUNTER(hangle->htim) + timer_ticks; switch(Phase) @@ -106,16 +156,19 @@ HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float A case UPP_PHASE_A: __HAL_TIM_SET_COMPARE(hangle->htim, ANGLE_CHANNEL_1, ccr_ticks); __HAL_TIM_ENABLE_IT(hangle->htim, TIM_IT_CC1); + hangle->f.Running++; break; case UPP_PHASE_B: __HAL_TIM_SET_COMPARE(hangle->htim, ANGLE_CHANNEL_2, ccr_ticks); __HAL_TIM_ENABLE_IT(hangle->htim, TIM_IT_CC2); + hangle->f.Running++; break; case UPP_PHASE_C: __HAL_TIM_SET_COMPARE(hangle->htim, ANGLE_CHANNEL_3, ccr_ticks); __HAL_TIM_ENABLE_IT(hangle->htim, TIM_IT_CC3); + hangle->f.Running++; break; case UPP_PHASE_UNKNOWN: @@ -136,7 +189,7 @@ HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float A */ HAL_StatusTypeDef Angle_Reset(Angle_Handle_t *hangle, UPP_Phase_t Phase) { - if(hangle == NULL) + if(assert_upp(hangle)) return HAL_ERROR; // Если канал дурацкий - возвращаем ошибку @@ -149,14 +202,20 @@ HAL_StatusTypeDef Angle_Reset(Angle_Handle_t *hangle, UPP_Phase_t Phase) { case UPP_PHASE_A: __HAL_TIM_DISABLE_IT(hangle->htim, TIM_IT_CC1); + if(hangle->f.Running) + hangle->f.Running--; break; case UPP_PHASE_B: __HAL_TIM_DISABLE_IT(hangle->htim, TIM_IT_CC2); + if(hangle->f.Running) + hangle->f.Running--; break; case UPP_PHASE_C: __HAL_TIM_DISABLE_IT(hangle->htim, TIM_IT_CC3); + if(hangle->f.Running) + hangle->f.Running--; break; case UPP_PHASE_UNKNOWN: diff --git a/UPP/Core/UPP/angle_control.h b/UPP/Core/UPP/angle_control.h index 4da20e1..2b62a01 100644 --- a/UPP/Core/UPP/angle_control.h +++ b/UPP/Core/UPP/angle_control.h @@ -1,27 +1,39 @@ /** ****************************************************************************** -* @file pwm_thyristors.h -* @brief Модуль для управления тиристорами (объединённый 6-канальный) +* @file angle_control.h +* @brief Модуль для формирования и отсчитывания угла открытия ****************************************************************************** */ #ifndef _ANGLE_CONTROL_H_ #define _ANGLE_CONTROL_H_ #include "main.h" -#define ANGLE_TIM2_FREQ_MHZ 90 +typedef struct +{ + float AngleLimit; ///< Лимит AngleMax, рассчитывается от параметров ШИМ + float AngleMin; ///< Минимально возможный угол открытия + float AngleMax; ///< Максимально возможный угол открытия +}Angle_Config_t; typedef struct { TIM_HandleTypeDef *htim; + Angle_Config_t Config; - float AngleMin; - float AngleMax; + struct { + unsigned Initialized : 1; + unsigned Running : 3; ///< Сколько каналов запущено сейчас + } f; }Angle_Handle_t; /* Инициализация Таймера для рассчета угла открытия. */ -HAL_StatusTypeDef Angle_Init(Angle_Handle_t *hangle, float AngleMin, float AngleMax); +HAL_StatusTypeDef Angle_Init(Angle_Handle_t *hangle); +/* Инициализация углов открытия. */ +HAL_StatusTypeDef Angle_SetRange(Angle_Handle_t *hangle, float AngleMin, float AngleMax); +/* Инициализация предельного угла открытия. */ +HAL_StatusTypeDef Angle_SetLimit(Angle_Handle_t *hangle, float AngleLimit); /* Установка угла открытия в таймер. */ -HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float Angle, float Freq); +HAL_StatusTypeDef Angle_Start(Angle_Handle_t *hangle, UPP_Phase_t Phase, float Angle, float PeriodMs); /* Сброс угла открытия у таймера. */ HAL_StatusTypeDef Angle_Reset(Angle_Handle_t *hangle, UPP_Phase_t Phase); diff --git a/UPP/Core/UPP/pwm_thyristors.c b/UPP/Core/UPP/pwm_thyristors.c index 20455c8..2d6daba 100644 --- a/UPP/Core/UPP/pwm_thyristors.c +++ b/UPP/Core/UPP/pwm_thyristors.c @@ -56,7 +56,7 @@ HAL_StatusTypeDef PWM_Init(PWM_Handle_t *hpwm) PWM_SetHalfWave(hpwm, UPP_PHASE_B, UPP_WAVE_UNKNOWED); PWM_SetHalfWave(hpwm, UPP_PHASE_C, UPP_WAVE_UNKNOWED); - PWM_SetFrequency(hpwm, hpwm->Config.Frequency); + PWM_SetConfig(hpwm, MB_INTERNAL.param.pwm.PhaseMask, MB_INTERNAL.param.pwm.Frequency, MB_INTERNAL.param.pwm.PulseNumber); HAL_TIM_PWM_Start(&hpwm1, PWM_CHANNEL_1); HAL_TIM_PWM_Start(&hpwm1, PWM_CHANNEL_2); @@ -115,7 +115,7 @@ HAL_StatusTypeDef PWM_Start(PWM_Handle_t *hpwm, UPP_Phase_t Phase) * @param Phase На какой фазе надо остановить ШИМ * @return HAL Status. */ -HAL_StatusTypeDef PWM_Stop(PWM_Handle_t *hpwm, UPP_Phase_t Phase, uint8_t force_stop) +HAL_StatusTypeDef PWM_Stop(PWM_Handle_t *hpwm, UPP_Phase_t Phase, uint8_t force_stop_all) { if(assert_upp(hpwm)) return HAL_ERROR; @@ -129,10 +129,18 @@ HAL_StatusTypeDef PWM_Stop(PWM_Handle_t *hpwm, UPP_Phase_t Phase, uint8_t force_ hpwm->Phase[Phase]->State = PWM_THYR_DISABLED; - if(force_stop) + // Если не force_stop_all - сбрасываем только текущий канал + if (!force_stop_all) { __PWM_SetOutputState(hpwm->Phase[Phase], PWM_DISABLE); } + // Если force_stop_all - сбрасываем ВСЕ КАНАЛЫ + else { + for(int ch = 0; ch < 6; ch++) + { + __PWM_SetOutputState(&hpwm->AllPhases[ch], PWM_DISABLE); + } + } return HAL_OK; } @@ -169,6 +177,7 @@ HAL_StatusTypeDef PWM_Handle(PWM_Handle_t *hpwm) __PWM_SetOutputState(hPhase, PWM_ENABLE); hPhase->PulseCnt = hpwm->Config.PulseNumber; hPhase->State = PWM_THYR_TIM_ACTIVE; + hpwm->f.Running++; break; case PWM_THYR_TIM_ACTIVE: @@ -182,6 +191,8 @@ HAL_StatusTypeDef PWM_Handle(PWM_Handle_t *hpwm) case PWM_THYR_TIM_DONE: hPhase->State = PWM_THYR_TIM_WAIT; + if(hpwm->f.Running) + hpwm->f.Running--; break; default: @@ -192,6 +203,37 @@ HAL_StatusTypeDef PWM_Handle(PWM_Handle_t *hpwm) return HAL_OK; } +/** + * @brief Установка частоты ШИМ. + * @param hpwm Указатель на хендл ШИМ тиристоров + * @param Frequency Частота в ГЦ + * @return HAL Status. + */ +HAL_StatusTypeDef PWM_SetConfig(PWM_Handle_t *hpwm, uint8_t PhaseMask, uint16_t Frequency, uint8_t PulseNumber) +{ + if(assert_upp(hpwm)) + return HAL_ERROR; + if(hpwm->f.Running) + return HAL_BUSY; + + HAL_TIM_Base_Stop(&hpwm1); + + hpwm->Config.PhaseMask.all = PhaseMask; + hpwm->Config.PulseNumber = PulseNumber; + hpwm->Config.Frequency = Frequency; + __HAL_TIM_SET_AUTORELOAD(&hpwm1, TIM_FreqToTick(Frequency, PWM_TIM1_FREQ_MHZ)); + __HAL_TIM_SET_AUTORELOAD(&hpwm2, TIM_FreqToTick(Frequency, PWM_TIM3_FREQ_MHZ)); + __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_1, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); + __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_2, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); + __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_3, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); + __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_4, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); + __HAL_TIM_SET_COMPARE(&hpwm2, PWM_CHANNEL_5, __HAL_TIM_GET_AUTORELOAD(&hpwm2)/2); + __HAL_TIM_SET_COMPARE(&hpwm2, PWM_CHANNEL_6, __HAL_TIM_GET_AUTORELOAD(&hpwm2)/2); + + return HAL_TIM_Base_Start_IT(&hpwm1); +} + + /** * @brief Установка полуволны для слежения. * @param hpwm Указатель на хендл ШИМ тиристоров @@ -206,7 +248,7 @@ HAL_StatusTypeDef PWM_SetHalfWave(PWM_Handle_t *hpwm, UPP_Phase_t Phase, UPP_Hal // Сбрасываем текущий канал - PWM_Stop(hpwm, Phase, 1); + PWM_Stop(hpwm, Phase, 0); // Если канал дурацкий - выставляем заглушку if(Phase >= 3) diff --git a/UPP/Core/UPP/pwm_thyristors.h b/UPP/Core/UPP/pwm_thyristors.h index fa56447..5908225 100644 --- a/UPP/Core/UPP/pwm_thyristors.h +++ b/UPP/Core/UPP/pwm_thyristors.h @@ -1,7 +1,7 @@ /** ****************************************************************************** * @file pwm_thyristors.h -* @brief Модуль для управления тиристорами (объединённый 6-канальный) +* @brief Модуль для управления тиристорами ****************************************************************************** */ #ifndef _PWM_THYRISTORS_H @@ -12,23 +12,11 @@ -#define PWM_TIM1_FREQ_MHZ 180 -#define PWM_TIM3_FREQ_MHZ 90 -#define PWM_ENABLE TIM_OCMODE_PWM1 -#define PWM_DISABLE TIM_OCMODE_FORCED_ACTIVE +#define PWM_ENABLE TIM_OCMODE_PWM2 +#define PWM_DISABLE TIM_OCMODE_FORCED_INACTIVE -#define PWM_SetFrequency(_hpwm_, _freq_) \ -do { _hpwm_->Config.Frequency = _freq_; \ - __HAL_TIM_SET_AUTORELOAD(&hpwm1, TIM_FreqToTick(_freq_, PWM_TIM1_FREQ_MHZ)); \ - __HAL_TIM_SET_AUTORELOAD(&hpwm2, TIM_FreqToTick(_freq_, PWM_TIM3_FREQ_MHZ)); \ - __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_1, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); \ - __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_2, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); \ - __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_3, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); \ - __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_4, __HAL_TIM_GET_AUTORELOAD(&hpwm1)/2); \ - __HAL_TIM_SET_COMPARE(&hpwm2, PWM_CHANNEL_5, __HAL_TIM_GET_AUTORELOAD(&hpwm2)/2); \ - __HAL_TIM_SET_COMPARE(&hpwm2, PWM_CHANNEL_6, __HAL_TIM_GET_AUTORELOAD(&hpwm2)/2); }while(0); // Индексы для структур каналов @ref PWM_Handle_t @@ -67,7 +55,7 @@ typedef struct { } PWM_Channel_t; /** - * @brief Канал PWM (один тиристор) + * @brief Параметры ШИМ */ typedef struct { union @@ -81,7 +69,7 @@ typedef struct { }; }PhaseMask; ///< Какими каналами управлять uint8_t PulseNumber; ///< Сколько импульсов отправить в пакете для открытия тиристоров - uint32_t Frequency; ///< Частота импульсов + uint16_t Frequency; ///< Частота импульсов } PWM_ThyrConfig_t; /** @@ -93,7 +81,7 @@ typedef struct { struct { unsigned Initialized : 1; - unsigned Running : 1; ///< true если оба таймера запущены + unsigned Running : 3; ///< Сколько каналов запущено сейчас } f; } PWM_Handle_t; @@ -103,7 +91,9 @@ HAL_StatusTypeDef PWM_Init(PWM_Handle_t *hpwm); /* Запуск ШИМ. */ HAL_StatusTypeDef PWM_Start(PWM_Handle_t *hpwm, UPP_Phase_t Phase); /* Остановить ШИМ. */ -HAL_StatusTypeDef PWM_Stop(PWM_Handle_t *hpwm, UPP_Phase_t Phase, uint8_t force_stop); +HAL_StatusTypeDef PWM_Stop(PWM_Handle_t *hpwm, UPP_Phase_t Phase, uint8_t force_stop_all); +/* Установка частоты ШИМ. */ +HAL_StatusTypeDef PWM_SetConfig(PWM_Handle_t *hpwm, uint8_t PhaseMask, uint16_t Frequency, uint8_t PulseNumber); /* Установка полуволны для слежения. */ HAL_StatusTypeDef PWM_SetHalfWave(PWM_Handle_t *hpwm, UPP_Phase_t Phase, UPP_HalfWave_t halfwave); /* Установка полуволны для слежения. */ diff --git a/UPP/Core/UPP/upp_control.c b/UPP/Core/UPP/upp_control.c new file mode 100644 index 0000000..dec4727 --- /dev/null +++ b/UPP/Core/UPP/upp_control.c @@ -0,0 +1,267 @@ +/** +****************************************************************************** +* @file upp_control.c +* @brief Модуль определябщий поведение УПП +****************************************************************************** +* @details +******************************************************************************/ +#include "upp_main.h" // всё остальное по работе с УПП + + +static int __CheckSimpleParamF(float *paramDist, uint16_t paramSrc, float Coef); +static int __CheckSimpleParamU32(uint32_t *paramDist, uint16_t paramSrc, float Coef); +static int __CheckSimpleParamU16(uint16_t *paramDist, uint16_t paramSrc); +static int __CheckSimpleParamU8(uint8_t *paramDist, uint16_t paramSrc, float Coef); +static int __AngleSetLimit(void); + +/** + * @brief Контроль внутренних параметров УПП. + * @return HAL Status. + */ +void UPP_Control_InternalParams(void) +{ + if(upp.call->go) // при запущеном УПП ничего не меняем + return; + + // флаги обновились ли конфиги + static int adc_channel_update[ADC_NUMB_OF_REGULAR_CHANNELS] = {0}; + static int zc_update = 0; + static int pwm_update = 0; + + // временная переменная для параметров каналов АЦП + float adc_channel_max[ADC_NUMB_OF_REGULAR_CHANNELS] = {0}; + uint16_t adc_channel_zero[ADC_NUMB_OF_REGULAR_CHANNELS] = {0}; + // временная переменная для параметров перехода через ноль + float zc_hysteresis = upp.pm.zc.Config.Hysteresis; + uint16_t zc_debounce = upp.pm.zc.Config.DebounceSamples; + // временная переменная для параметров ШИМ + uint8_t pwm_phase_mask = upp.hpwm.Config.PhaseMask.all; + uint16_t pwm_freq = upp.hpwm.Config.Frequency; + uint8_t pwm_pulse_num = upp.hpwm.Config.PulseNumber; + + + for(int i = 0; i < ADC_NUMB_OF_REGULAR_CHANNELS; i++) + { + adc_channel_max[i] = upp.pm.adc.Coefs[i].vMax; + adc_channel_zero[i] = upp.pm.adc.Coefs[i].lZero; + + // Максимальное измеряемое напряжение + if(__CheckSimpleParamF(&adc_channel_max[i], MB_INTERNAL.param.adc.ADC_Max[i], 10)) + { + adc_channel_update[i] = 1; + } + // Значение АЦП при нулевом входе + if(__CheckSimpleParamU16(&adc_channel_zero[i], MB_INTERNAL.param.adc.ADC_Zero[i])) + { + adc_channel_update[i] = 1; + } + } + + // Параметры алгоритма перехода через ноль + if(__CheckSimpleParamF(&zc_hysteresis, MB_INTERNAL.param.zc.Hysteresis, 100)) + { + zc_update = 1; + } + if(__CheckSimpleParamU16(&zc_debounce, MB_INTERNAL.param.zc.Hysteresis)) + { + zc_update = 1; + } + + // Параметры ШИМ токов + if(__CheckSimpleParamU8(&pwm_phase_mask, MB_INTERNAL.param.pwm.PhaseMask, 1)) + { + pwm_update = 1; + } + if(__CheckSimpleParamU16(&pwm_freq, MB_INTERNAL.param.pwm.Frequency)) + { + pwm_update = 1; + } + if(__CheckSimpleParamU8(&pwm_pulse_num, MB_INTERNAL.param.pwm.PulseNumber, 1)) + { + pwm_update = 1; + } + + + // Обновление АЦП конфигов + for(int i = 0; i < ADC_NUMB_OF_REGULAR_CHANNELS; i++) + { + if(adc_channel_update[i]) + { + if(ADC_ConfigChannel(&upp.pm.adc, i, adc_channel_zero[i], adc_channel_max[i], 4095) == HAL_OK) + adc_channel_update[i] = 0; + else + errors.prvt.cnt.adc_reinit_err++; + } + } + // Обновление Zero-Cross конфигов + if(zc_update) + { + if(ZC_Init(&upp.pm.zc, upp.pm.zc.Config.NumChannels, zc_hysteresis, zc_debounce) == HAL_OK) + zc_update = 0; + else + errors.prvt.cnt.zc_reinit_err++; + } + // Обновление ШИМ конфигов + if(pwm_update) + { + if(PWM_SetConfig(&upp.hpwm, pwm_phase_mask, pwm_freq, pwm_pulse_num) == HAL_OK) + { + pwm_update = 0; + __AngleSetLimit(); + } + else + errors.prvt.cnt.pwm_reinit_err++; + } + if (upp.hangle.Config.AngleLimit == 0) + { + __AngleSetLimit(); + } +} + + +/** + * @brief Установка параметров на дефолтные значения @ref UPP_DEFAULT_PARAMS. + * @param pui_default Сбросить параметры ПУИ + * @param internal_default Сбросить внутренние параметры + * @return HAL Status. + */ +void UPP_SetDefault(int pui_default, int internal_default) +{ + if(pui_default) + { + MB_DATA.HoldRegs.pui_params.Iref = PUI_Iref_PERCENT_DEFAULT; + MB_DATA.HoldRegs.pui_params.Tnt = PUI_Tnt_MS_DEFAULT; + MB_DATA.HoldRegs.pui_params.Umin = PUI_Umin_PERCENT_DEFAULT; + MB_DATA.HoldRegs.pui_params.Umax = PUI_Umax_PERCENT_DEFAULT; + MB_DATA.HoldRegs.pui_params.Imax = PUI_Imax_PERCENT_DEFAULT; + MB_DATA.HoldRegs.pui_params.Imin = PUI_Imin_PERCENT_DEFAULT; + MB_DATA.HoldRegs.pui_params.TiMax = PUI_TiMax_US_DEFAULT; + MB_DATA.HoldRegs.pui_params.Tdelay = PUI_Tdelay_SECONDS_DEFAULT; + MB_DATA.HoldRegs.pui_params.Interlace = PUI_Interlace_EN_DEFAULT; + } + + if(internal_default) + { + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_UBA] = ADC_U_MAX_V_DEFAULT*10; + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_UAC] = ADC_U_MAX_V_DEFAULT*10; + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_IC] = ADC_I_MAX_A_DEFAULT*10; + MB_INTERNAL.param.adc.ADC_Max[ADC_CHANNEL_IA] = ADC_I_MAX_A_DEFAULT*10; + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_UBA] = ADC_U_ZERO_DEFAULT; + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_UAC] = ADC_U_ZERO_DEFAULT; + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_IC] = ADC_I_ZERO_DEFAULT; + MB_INTERNAL.param.adc.ADC_Zero[ADC_CHANNEL_IA] = ADC_I_ZERO_DEFAULT; + + MB_INTERNAL.param.pwm.PhaseMask = 0x7; // (все три фазы) + MB_INTERNAL.param.pwm.Frequency = (float)PWM_THYR_FREQUENCY_HZ_DEFAULT; + MB_INTERNAL.param.pwm.PulseNumber = PWM_THYR_PULSE_NUMBER_DEFAULT; + + MB_INTERNAL.param.zc.Hysteresis = ZERO_CROSS_HYSTERESIS_V_DEFAULT*100; + MB_INTERNAL.param.zc.DebouneCouner = ZERO_CROSS_DEBOUNCE_10US_DEFAULT; + //__AngleSetLimit(); + } +} + + + +static int __AngleSetLimit(void) +{ + // Перерасчет максимально допустимого угла + float pulses_percent_of_period = ((MB_INTERNAL.param.pwm.PulseNumber / MB_INTERNAL.param.pwm.Frequency) * 1000) / ANGLE_PERIOD_MS; + float angle_limit = 1 - pulses_percent_of_period; + Angle_SetLimit(&upp.hangle, angle_limit); +} + + + +/** + * @brief Сверить и обновить float параметр из uint16_t. + * @param paramDist Указатель на float параметр + * @param paramSrc Значение для сравнения с float параметром + * @param Coef Коэффициент для приведения float к uint16_t: uint16_t = float*coef, float = uint16_t/coef + * @return 0 - параметры совпадают, 1 - параметр был обновлен на paramSrc. + */ +static int __CheckSimpleParamF(float *paramDist, uint16_t paramSrc, float Coef) +{ + if(paramDist == NULL) + return 0; + + uint16_t expected_mb_param = *paramDist*Coef; + if(expected_mb_param != paramSrc) + { + *paramDist = (float)paramSrc/Coef; + return 1; + } + else + { + return 0; + } +} +/** + * @brief Сверить и обновить uint32_t параметр из uint16_t. + * @param paramDist Указатель на uint32_t параметр + * @param paramSrc Значение для сравнения с uint32_t параметром + * @param Coef Коэффициент для приведения uint32_t к uint16_t: uint16_t = uint32_t*coef, uint32_t = uint16_t/coef + * @return 0 - параметры совпадают, 1 - параметр был обновлен на paramSrc. + */ +static int __CheckSimpleParamU32(uint32_t *paramDist, uint16_t paramSrc, float Coef) +{ + if(paramDist == NULL) + return 0; + + uint16_t expected_mb_param = *paramDist*Coef; + if(expected_mb_param != paramSrc) + { + *paramDist = (uint32_t)paramSrc/Coef; + return 1; + } + else + { + return 0; + } +} +/** + * @brief Сверить и обновить uint16_t параметр из uint16_t. + * @param paramDist Указатель на uint16_t параметр + * @param paramSrc Значение для сравнения с uint16_t параметром + * @return 0 - параметры совпадают, 1 - параметр был обновлен на paramSrc. + */ +static int __CheckSimpleParamU16(uint16_t *paramDist, uint16_t paramSrc) +{ + if(paramDist == NULL) + return 0; + + uint16_t expected_mb_param = *paramDist; + if(expected_mb_param != paramSrc) + { + *paramDist = (uint16_t)paramSrc; + return 1; + } + else + { + return 0; + } +} + +/** + * @brief Сверить и обновить uint8_t параметр из uint16_t. + * @param paramDist Указатель на uint8_t параметр + * @param paramSrc Значение для сравнения с uint32_t параметром + * @param Coef Коэффициент для приведения uint32_t к uint16_t: uint16_t = uint8_t*coef, uint8_t = uint16_t/coef + * @return 0 - параметры совпадают, 1 - параметр был обновлен на paramSrc. + */ +static int __CheckSimpleParamU8(uint8_t *paramDist, uint16_t paramSrc, float Coef) +{ + if(paramDist == NULL) + return 0; + + uint16_t expected_mb_param = *paramDist*Coef; + if(expected_mb_param != paramSrc) + { + *paramDist = (uint8_t)paramSrc/Coef; + return 1; + } + else + { + return 0; + } +} \ No newline at end of file diff --git a/UPP/Core/UPP/upp_control.h b/UPP/Core/UPP/upp_control.h new file mode 100644 index 0000000..6404411 --- /dev/null +++ b/UPP/Core/UPP/upp_control.h @@ -0,0 +1,67 @@ +/** +****************************************************************************** +* @file upp_control.h +* @brief Модуль определябщий поведение УПП +****************************************************************************** +* @details +******************************************************************************/ + +#ifndef _UPP_CONTROL_H +#define _UPP_CONTROL_H +#include "upp_defs.h" + +typedef struct +{ + unsigned set_default_pui:1; + unsigned set_default_internal:1; + unsigned go:1; + unsigned stop:1; + + + unsigned reserved:11; + unsigned reset_mcu:1; +}UPP_FuncCalls_t; + + +typedef struct +{ + /* Параметры АЦП */ + struct + { + uint16_t ADC_Max[4]; + uint16_t ADC_Zero[4]; + }adc; + + /* Параметры ШИМ */ + struct + { + uint16_t PhaseMask; + uint16_t Frequency; + uint16_t PulseNumber; + }pwm; + + /* Параметры Угла */ + struct + { + uint16_t Hysteresis; + uint16_t DebouneCouner; + }zc; + + /* Параметры Угла */ + struct + { + uint16_t Angle_Max; + uint16_t Angle_Min; + }angle; + + + +}UPP_PrvtParams_t; + + +/* Контроль внутренних параметров УПП. */ +void UPP_Control_InternalParams(void); +/* Установка параметров на дефолтные значения */ +void UPP_SetDefault(int pui_default, int internal_default); + +#endif //_UPP_CONTROL_H \ No newline at end of file diff --git a/UPP/Core/UPP/upp_errors.c b/UPP/Core/UPP/upp_errors.c new file mode 100644 index 0000000..02ebc29 --- /dev/null +++ b/UPP/Core/UPP/upp_errors.c @@ -0,0 +1,44 @@ +/** +****************************************************************************** +* @file upp_errors.c +* @brief Ошибки УПП и их обработка +****************************************************************************** +* @details +******************************************************************************/ +#include "upp_errors.h" // всё остальное по работе с УПП +UPP_Errors_t errors; + +static UPP_ErrorType_t UPP_SelectCommonError(void) +{ + // Пока нет ошибки + UPP_ErrorType_t best = Err_None; + // Приоритет отсутствия ошибок + uint8_t best_prio = UPP_ErrorPriority[Err_None]; + + // Перебираем все возможные ошибки по enum + for (int e = Err_None + 1; e <= Err_UnderFrequency; e++) + { + // Проверяем: установлен ли соответствующий бит в pui.all + // e-1, потому что ошибка №1 = бит 0, ошибка №2 = бит 1 и т.д. + if (errors.pui.all & (1u << (e - 1))) + { + // Получаем приоритет этой ошибки + uint8_t pr = UPP_ErrorPriority[e]; + + // Если её приоритет лучше (меньше число) — запоминаем + if (pr < best_prio) + { + best_prio = pr; + best = (UPP_ErrorType_t)e; + } + } + } + + // Возвращаем самую важную (наивысшего приоритета) ошибку + return best; +} + +void UPP_ErrorsHandle(void) +{ + errors.common = UPP_SelectCommonError(); +} \ No newline at end of file diff --git a/UPP/Core/UPP/upp_errors.h b/UPP/Core/UPP/upp_errors.h new file mode 100644 index 0000000..9bed663 --- /dev/null +++ b/UPP/Core/UPP/upp_errors.h @@ -0,0 +1,124 @@ +/** +****************************************************************************** +* @file upp_errors.h +* @brief Ошибки УПП и их обработка +****************************************************************************** +* @details +******************************************************************************/ + +#ifndef _UPP_ERRORS_H +#define _UPP_ERRORS_H +#include "upp_defs.h" + + +/** + * @brief Приоритет ПУИ ошибок + */ +static const uint8_t UPP_ErrorPriority[] = +{ + [Err_None] = 255, + + /* Фатальные ошибки */ + [Err_LossPhaseAll] = 1, + [Err_OverCurrent] = 2, + [Err_OverVoltage] = 3, + [Err_OverTemperature] = 4, + [Err_Power_24V] = 5, + [Err_Power_Digit_5V] = 6, + [Err_Power_DIO_24V] = 7, + [Err_Power_Analog_5V] = 8, + + /* Критичные */ + [Err_LossPhaseA] = 10, + [Err_LossPhaseB] = 11, + [Err_LossPhaseC] = 12, + [Err_LongStart] = 13, + [Err_Interlace] = 14, + + /* Пограничные параметры */ + [Err_UnderVoltage] = 20, + [Err_OverFrequency] = 21, + [Err_UnderFrequency] = 22, + + /* Внутренние */ + [Err_Internal_1] = 40, + [Err_Internal_2] = 41, + [Err_Internal_3] = 42, + [Err_Internal_4] = 43, + [Err_Internal_5] = 44, + [Err_Internal_6] = 45, +}; + + + +/** + * @brief Структура с всеми ошибками УПП + */ +typedef struct +{ + UPP_ErrorType_t common; ///< Общая ошибка @ref UPP_ErrorType_t; + + union + { + uint32_t all; + struct + { + /* Програмные ошибки */ + unsigned Internal_1:1; ///< Внутренняя неисправность УПП 1 + unsigned Internal_2:1; ///< Внутренняя неисправность УПП 2 + unsigned Internal_3:1; ///< Внутренняя неисправность УПП 3 + unsigned Internal_4:1; ///< Внутренняя неисправность УПП 4 + unsigned Internal_5:1; ///< Внутренняя неисправность УПП 5 + unsigned Internal_6:1; ///< Внутренняя неисправность УПП 6 + + /* Ошибки по питанию */ + unsigned Power_Digit_5V:1; ///< Неисправность цифрового источника питания (5 В) + unsigned Power_24V:1; ///< Неисправность источника питания 24 В + unsigned Power_Analog_5V:1; ///< Неисправность аналогового источника питания микроконтроллера (± 5 В) + unsigned Power_SCI_5V:1; ///< Неисправность источника питания последовательных интерфейсов микроконтроллера (5 В) + unsigned Power_DIO_24V:1; ///< Неисправность источника питания дискретных входов/выходов (24 В) + + /* Ошибки по допустимым пределам Наряжений/Токов/Температуры */ + unsigned OverCurrent:1; ///< Ток выше допустимого (см. Imax и TiMax в @ref UPP_PUI_Params_t) + unsigned OverVoltage:1; ///< Напряжение сети выше допустимого (см. Umах в @ref UPP_PUI_Params_t) + unsigned OverTemperature:1; ///< Температура выше допустимой (плюс 85 °C) + unsigned UnderVoltage:1; ///< Напряжение сети ниже допустимого (см. Umin в @ref UPP_PUI_Params_t) + + /* Ошибки по обрывам фаз */ + unsigned LossPhaseA:1; ///< Обрыв фазы A (см. Imin в @ref UPP_PUI_Params_t) + unsigned LossPhaseB:1; ///< Обрыв фазы B (см. Imin в @ref UPP_PUI_Params_t) + unsigned LossPhaseC:1; ///< Обрыв фазы C (см. Imin в @ref UPP_PUI_Params_t) + + /* Другие ошибки */ + unsigned LongStart:1; ///< Затянутый пуск (ток не спадает за установленное время) (см. Tdelay в @ref UPP_PUI_Params_t) + unsigned Interlace:1; ///< Неправильный порядок чередования фаз (см. Interlace в @ref UPP_PUI_Params_t) + unsigned OverFrequency:1; ///< Частота сети выше допустимой + unsigned UnderFrequency:1; ///< Частота сети ниже допустимой + }err; + }pui; + + struct + { + union + { + uint64_t all; + struct + { + unsigned :1; + }err; + }f; + + struct + { + uint16_t adc_reinit_err; + uint16_t zc_reinit_err; + uint16_t pwm_reinit_err; + }cnt; + + }prvt; ///< Приватные ошибки, не идущие напрямую в ПУИ +}UPP_Errors_t; +extern UPP_Errors_t errors; + +void UPP_ErrorsHandle(void); + +#endif //_UPP_ERRORS_H \ No newline at end of file diff --git a/UPP/Core/UPP/upp_main.c b/UPP/Core/UPP/upp_main.c index a6a3571..9356f6e 100644 --- a/UPP/Core/UPP/upp_main.c +++ b/UPP/Core/UPP/upp_main.c @@ -6,6 +6,7 @@ * @details ******************************************************************************/ #include "upp_main.h" // всё остальное по работе с УПП + UPP_t upp; float alpha_dbg = 0.5; // ОСНОВНОЙ ЦИКЛ main.c @@ -16,12 +17,18 @@ float alpha_dbg = 0.5; */ int UPP_Init(void) { + // Подключение указателей + upp.errors = &errors; + upp.PUI.params = &MB_DATA.HoldRegs.pui_params; + upp.PUI.values = &MB_DATA.InRegs.pui; + upp.call = &MB_INTERNAL.FuncCalls; + HAL_TIM_Base_Start(&ustim); PowerMonitor_Init(&upp.pm); - upp.hpwm.Config.Frequency = 20000; - upp.hpwm.Config.PulseNumber = 20; PWM_Init(&upp.hpwm); - Angle_Init(&upp.hangle, 0, 0.8); + Angle_Init(&upp.hangle); + + upp.workmode = WM_Ready; return 0; } @@ -31,8 +38,9 @@ int UPP_Init(void) */ int UPP_PreWhile(void) { + UPP_Control_InternalParams(); + Angle_SetRange(&upp.hangle, 0.0, 0.8); PowerMonitor_Start(&upp.pm); - upp.hpwm.Config.PhaseMask.all = 0x7; return 0; } @@ -41,11 +49,85 @@ int UPP_PreWhile(void) * @return 0 - если ОК, >1 если ошибка. */ int UPP_While(void) -{ +{ + // если ошибка вызываем СТОП + if(errors.pui.all) + { + upp.call->stop = 1; + } + // иначе снимаем СТОП + else + { + upp.call->stop = 0; + } + + + // Сброс на дефолтные по запросу + if(upp.call->set_default_pui) + { + UPP_SetDefault(1, 0); + } + if(upp.call->set_default_internal) + { + UPP_SetDefault(0, 1); + } + + + + // Если СТОП - переходим в ошибку + if (upp.call->stop) + upp.workmode = WM_Error; + // Автомат состояний УПП + switch(upp.workmode) + { + case WM_Ready: + // если пришла команда на запуск + if (upp.call->go) + { + upp.workmode = WM_Running; + upp.StartTick = local_time(); + } + break; + + case WM_Running: + // если пришла команда на остановку + if (!upp.call->go) + upp.workmode = WM_Ready; + + // если слишком долгий запуск + if((local_time() - upp.StartTick) > (upp.PUI.params->Tdelay*1000)) + { + errors.pui.err.LongStart = 1; + } + break; + + case WM_Error: + if(errors.common == Err_None) + upp.workmode = WM_Ready; + + PWM_Stop(&upp.hpwm, 0, 1); // Останавливаем ВЕСЬ ШИМ + break; + } + return 0; } + + +/** + * @brief Всякое что будет делатся каждую 1 мс. + */ +void UPP_Tick(void) +{ + if(upp.workmode == WM_Not_Init) + return; + UPP_ErrorsHandle(); + UPP_Control_InternalParams(); +} + + + // ПРЕРЫВАНИЯ stm32f4xx_it.c void UPP_ADC_Handle(void) @@ -57,41 +139,53 @@ void UPP_ADC_Handle(void) // Если произошел Zero Cross if(ZC_isOccurred(&upp.pm.zc, phase)) { - // Меняем полуволну тиристора - UPP_HalfWave_t curr_halfwave = ZC_GetHalfWave(&upp.pm.zc, phase); - PWM_SetHalfWave(&upp.hpwm, phase, curr_halfwave); - Angle_Start(&upp.hangle, phase, alpha_dbg, 100); + // Если УПП в работе + if(upp.workmode == WM_Running) + { + // Меняем полуволну тиристора + UPP_HalfWave_t curr_halfwave = ZC_GetHalfWave(&upp.pm.zc, phase); + PWM_SetHalfWave(&upp.hpwm, phase, curr_halfwave); + // Начинаем отсчитывать угол + Angle_Start(&upp.hangle, phase, alpha_dbg, 10); + } } } + + // Проверяем на ошибки } void UPP_PWM_Handle(void) { PWM_Handle(&upp.hpwm); } +void UPP_Angle_Handle(void) +{ + UPP_Phase_t phase = Angle_Handle(&upp.hangle); + Angle_Reset(&upp.hangle, phase); + // Если УПП в работе + if(upp.workmode == WM_Running) + { + switch(phase) + { + case UPP_PHASE_A: + PWM_Start(&upp.hpwm, UPP_PHASE_A); + break; + case UPP_PHASE_B: + PWM_Start(&upp.hpwm, UPP_PHASE_B); + break; + case UPP_PHASE_C: + PWM_Start(&upp.hpwm, UPP_PHASE_C); + break; + default: + break; + } + } +} + void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef* htim) { if (htim == upp.hangle.htim) { UPP_Angle_Handle(); } -} -void UPP_Angle_Handle(void) -{ - UPP_Phase_t phase = Angle_Handle(&upp.hangle); - Angle_Reset(&upp.hangle, phase); - switch(phase) - { - case UPP_PHASE_A: - PWM_Start(&upp.hpwm, UPP_PHASE_A); - break; - case UPP_PHASE_B: - PWM_Start(&upp.hpwm, UPP_PHASE_B); - break; - case UPP_PHASE_C: - PWM_Start(&upp.hpwm, UPP_PHASE_C); - break; - default: - break; - } } \ No newline at end of file diff --git a/UPP/Core/UPP/upp_main.h b/UPP/Core/UPP/upp_main.h index 54b3f56..62bc657 100644 --- a/UPP/Core/UPP/upp_main.h +++ b/UPP/Core/UPP/upp_main.h @@ -1,6 +1,6 @@ /** ****************************************************************************** -* @file modbus_data.h +* @file upp_main.h * @brief Определения структур данных Modbus устройства ****************************************************************************** * @details @@ -9,19 +9,35 @@ #ifndef _UPP_MAIN_H #define _UPP_MAIN_H -#include "main.h" // либы из AllLibs и вербальные имена из CubeMX -#include "upp_config.h" +#include "main.h" // общие библиотеки, конфигурации и вербальные имена из CubeMX #include "power_monitor.h" // статистика сети и АЦП #include "pwm_thyristors.h" // Управление тиристорами #include "angle_control.h" // Управление углом открытия +#include "upp_status.h" // статус упп +#include "upp_control.h" // управление упп + extern float alpha_dbg; + typedef struct { - PowerMonitor_t pm; - PWM_Handle_t hpwm; - Angle_Handle_t hangle; + UPP_WorkModeType_t workmode; ///< Режим УПП + UPP_FuncCalls_t *call; ///< Вызов функций УПП + + PowerMonitor_t pm; ///< Мониторинг сети + PWM_Handle_t hpwm; ///< Хендл ШИМ тиристоров + Angle_Handle_t hangle; ///< Хендл отсчитывания угла открытия + + struct + { + UPP_PUI_Params_t *params; ///< Параметры от ПУИ + UPP_PUI_Values_t *values; ///< Данные для ПУИ + } PUI; ///< Общение с ПУИ + + + UPP_Errors_t *errors; ///< Ошибки УПП + uint32_t StartTick; }UPP_t; extern UPP_t upp; @@ -32,7 +48,10 @@ int UPP_Init(void); int UPP_PreWhile(void); /* Основной цикл УПП. */ int UPP_While(void); +/* Всякое что будет делатся каждую 1 мс. */ +void UPP_Tick(void); +// Прерывания void UPP_ADC_Handle(void); void UPP_PWM_Handle(void); void UPP_Angle_Handle(void); diff --git a/UPP/Core/UPP/upp_status.c b/UPP/Core/UPP/upp_status.c new file mode 100644 index 0000000..ed27e20 --- /dev/null +++ b/UPP/Core/UPP/upp_status.c @@ -0,0 +1,12 @@ +/** +****************************************************************************** +* @file upp_status.c +* @brief Модуль определяющий состояние УПП +****************************************************************************** +* @details +******************************************************************************/ +#include "upp_main.h" // всё остальное по работе с УПП + +void UPP_Status_Handler(void) +{ +} \ No newline at end of file diff --git a/UPP/Core/UPP/upp_status.h b/UPP/Core/UPP/upp_status.h new file mode 100644 index 0000000..e33d67f --- /dev/null +++ b/UPP/Core/UPP/upp_status.h @@ -0,0 +1,13 @@ +/** +****************************************************************************** +* @file upp_status.h +* @brief Модуль определяющий состояние УПП +****************************************************************************** +* @details +******************************************************************************/ + +#ifndef _UPP_STATUS_H +#define _UPP_STATUS_H + + +#endif //_UPP_STATUS_H \ No newline at end of file diff --git a/UPP/MDK-ARM/UPP.uvoptx b/UPP/MDK-ARM/UPP.uvoptx index 7736db4..a8f9a56 100644 --- a/UPP/MDK-ARM/UPP.uvoptx +++ b/UPP/MDK-ARM/UPP.uvoptx @@ -206,6 +206,18 @@ 0 0 0 + ..\Core\Configs\upp_defs.h + upp_defs.h + 0 + 0 + + + 1 + 3 + 5 + 0 + 0 + 0 ..\Core\Inc\main.h main.h 0 @@ -213,7 +225,7 @@ 1 - 3 + 4 5 0 0 @@ -225,7 +237,7 @@ 1 - 4 + 5 1 0 0 @@ -237,7 +249,7 @@ 1 - 5 + 6 5 0 0 @@ -249,7 +261,7 @@ 1 - 6 + 7 5 0 0 @@ -261,7 +273,7 @@ 1 - 7 + 8 5 0 0 @@ -273,7 +285,7 @@ 1 - 8 + 9 5 0 0 @@ -285,7 +297,7 @@ 1 - 9 + 10 5 0 0 @@ -305,7 +317,7 @@ 0 2 - 10 + 11 1 0 0 @@ -317,7 +329,7 @@ 2 - 11 + 12 5 0 0 @@ -329,7 +341,7 @@ 2 - 12 + 13 1 0 0 @@ -341,7 +353,7 @@ 2 - 13 + 14 5 0 0 @@ -353,7 +365,7 @@ 2 - 14 + 15 1 0 0 @@ -365,7 +377,7 @@ 2 - 15 + 16 5 0 0 @@ -375,6 +387,78 @@ 0 0 + + 2 + 17 + 1 + 0 + 0 + 0 + ..\Core\UPP\upp_control.c + upp_control.c + 0 + 0 + + + 2 + 18 + 5 + 0 + 0 + 0 + ..\Core\UPP\upp_control.h + upp_control.h + 0 + 0 + + + 2 + 19 + 1 + 0 + 0 + 0 + ..\Core\UPP\upp_status.c + upp_status.c + 0 + 0 + + + 2 + 20 + 5 + 0 + 0 + 0 + ..\Core\UPP\upp_status.h + upp_status.h + 0 + 0 + + + 2 + 21 + 1 + 0 + 0 + 0 + ..\Core\UPP\upp_errors.c + upp_errors.c + 0 + 0 + + + 2 + 22 + 5 + 0 + 0 + 0 + ..\Core\UPP\upp_errors.h + upp_errors.h + 0 + 0 + @@ -385,7 +469,7 @@ 0 3 - 16 + 23 1 0 0 @@ -397,7 +481,7 @@ 3 - 17 + 24 5 0 0 @@ -409,7 +493,7 @@ 3 - 18 + 25 1 0 0 @@ -421,7 +505,7 @@ 3 - 19 + 26 5 0 0 @@ -433,7 +517,7 @@ 3 - 20 + 27 1 0 0 @@ -445,7 +529,7 @@ 3 - 21 + 28 5 0 0 @@ -465,7 +549,7 @@ 0 4 - 22 + 29 1 0 0 @@ -477,7 +561,7 @@ 4 - 23 + 30 1 0 0 @@ -489,7 +573,7 @@ 4 - 24 + 31 1 0 0 @@ -501,7 +585,7 @@ 4 - 25 + 32 1 0 0 @@ -513,7 +597,7 @@ 4 - 26 + 33 1 0 0 @@ -525,7 +609,7 @@ 4 - 27 + 34 1 0 0 @@ -537,7 +621,7 @@ 4 - 28 + 35 1 0 0 @@ -549,7 +633,7 @@ 4 - 29 + 36 1 0 0 @@ -561,7 +645,7 @@ 4 - 30 + 37 1 0 0 @@ -573,7 +657,7 @@ 4 - 31 + 38 1 0 0 @@ -585,7 +669,7 @@ 4 - 32 + 39 1 0 0 @@ -597,7 +681,7 @@ 4 - 33 + 40 1 0 0 @@ -609,7 +693,7 @@ 4 - 34 + 41 1 0 0 @@ -629,7 +713,7 @@ 0 5 - 35 + 42 5 0 0 @@ -641,7 +725,7 @@ 5 - 36 + 43 5 0 0 @@ -653,7 +737,7 @@ 5 - 37 + 44 5 0 0 @@ -665,7 +749,7 @@ 5 - 38 + 45 5 0 0 @@ -677,7 +761,7 @@ 5 - 39 + 46 5 0 0 @@ -689,7 +773,7 @@ 5 - 40 + 47 5 0 0 @@ -701,7 +785,7 @@ 5 - 41 + 48 1 0 0 @@ -713,7 +797,7 @@ 5 - 42 + 49 5 0 0 @@ -733,7 +817,7 @@ 0 6 - 43 + 50 1 0 0 @@ -745,7 +829,7 @@ 6 - 44 + 51 1 0 0 @@ -757,7 +841,7 @@ 6 - 45 + 52 1 0 0 @@ -769,7 +853,7 @@ 6 - 46 + 53 1 0 0 @@ -781,7 +865,7 @@ 6 - 47 + 54 1 0 0 @@ -793,7 +877,7 @@ 6 - 48 + 55 1 0 0 @@ -805,7 +889,7 @@ 6 - 49 + 56 1 0 0 @@ -817,7 +901,7 @@ 6 - 50 + 57 1 0 0 @@ -829,7 +913,7 @@ 6 - 51 + 58 1 0 0 @@ -841,7 +925,7 @@ 6 - 52 + 59 1 0 0 @@ -853,7 +937,7 @@ 6 - 53 + 60 1 0 0 @@ -865,7 +949,7 @@ 6 - 54 + 61 1 0 0 @@ -885,7 +969,7 @@ 0 7 - 55 + 62 1 0 0 @@ -897,7 +981,7 @@ 7 - 56 + 63 1 0 0 @@ -917,7 +1001,7 @@ 0 8 - 57 + 64 1 0 0 @@ -929,7 +1013,7 @@ 8 - 58 + 65 1 0 0 @@ -941,7 +1025,7 @@ 8 - 59 + 66 1 0 0 @@ -953,7 +1037,7 @@ 8 - 60 + 67 1 0 0 @@ -965,7 +1049,7 @@ 8 - 61 + 68 1 0 0 @@ -985,7 +1069,7 @@ 0 9 - 62 + 69 1 0 0 @@ -997,7 +1081,7 @@ 9 - 63 + 70 1 0 0 @@ -1009,7 +1093,7 @@ 9 - 64 + 71 1 0 0 @@ -1021,7 +1105,7 @@ 9 - 65 + 72 1 0 0 @@ -1033,7 +1117,7 @@ 9 - 66 + 73 1 0 0 @@ -1045,7 +1129,7 @@ 9 - 67 + 74 1 0 0 @@ -1057,7 +1141,7 @@ 9 - 68 + 75 1 0 0 @@ -1069,7 +1153,7 @@ 9 - 69 + 76 1 0 0 @@ -1081,7 +1165,7 @@ 9 - 70 + 77 1 0 0 @@ -1093,7 +1177,7 @@ 9 - 71 + 78 1 0 0 @@ -1105,7 +1189,7 @@ 9 - 72 + 79 1 0 0 @@ -1117,7 +1201,7 @@ 9 - 73 + 80 1 0 0 @@ -1129,7 +1213,7 @@ 9 - 74 + 81 1 0 0 @@ -1141,7 +1225,7 @@ 9 - 75 + 82 1 0 0 @@ -1153,7 +1237,7 @@ 9 - 76 + 83 1 0 0 @@ -1165,7 +1249,7 @@ 9 - 77 + 84 1 0 0 @@ -1177,7 +1261,7 @@ 9 - 78 + 85 1 0 0 @@ -1189,7 +1273,7 @@ 9 - 79 + 86 1 0 0 @@ -1201,7 +1285,7 @@ 9 - 80 + 87 1 0 0 @@ -1213,7 +1297,7 @@ 9 - 81 + 88 1 0 0 @@ -1225,7 +1309,7 @@ 9 - 82 + 89 1 0 0 @@ -1237,7 +1321,7 @@ 9 - 83 + 90 1 0 0 @@ -1249,7 +1333,7 @@ 9 - 84 + 91 1 0 0 @@ -1261,7 +1345,7 @@ 9 - 85 + 92 1 0 0 @@ -1281,7 +1365,7 @@ 0 10 - 86 + 93 1 0 0 @@ -1301,7 +1385,7 @@ 0 11 - 87 + 94 2 0 0 diff --git a/UPP/MDK-ARM/UPP.uvprojx b/UPP/MDK-ARM/UPP.uvprojx index 585e041..ff631f5 100644 --- a/UPP/MDK-ARM/UPP.uvprojx +++ b/UPP/MDK-ARM/UPP.uvprojx @@ -17,8 +17,8 @@ STM32F427ZGTx STMicroelectronics - Keil.STM32F4xx_DFP.2.17.1 - https://www.keil.com/pack/ + Keil.STM32F4xx_DFP.2.16.0 + http://www.keil.com/pack/ IRAM(0x20000000-0x2002FFFF) IRAM2(0x10000000-0x1000FFFF) IROM(0x8000000-0x80FFFFF) CLOCK(25000000) FPU2 CPUTYPE("Cortex-M4") TZ @@ -390,6 +390,11 @@ 5 ..\Core\Configs\upp_config.h + + upp_defs.h + 5 + ..\Core\Configs\upp_defs.h + main.h 5 @@ -465,6 +470,36 @@ 5 ..\Core\UPP\angle_control.h + + upp_control.c + 1 + ..\Core\UPP\upp_control.c + + + upp_control.h + 5 + ..\Core\UPP\upp_control.h + + + upp_status.c + 1 + ..\Core\UPP\upp_status.c + + + upp_status.h + 5 + ..\Core\UPP\upp_status.h + + + upp_errors.c + 1 + ..\Core\UPP\upp_errors.c + + + upp_errors.h + 5 + ..\Core\UPP\upp_errors.h +