wrapper init

This commit is contained in:
Razvalyaev 2025-01-19 12:19:24 +03:00
parent 24fa4d17ec
commit aa27973f61
13 changed files with 1188 additions and 1 deletions

63
.gitignore vendored Normal file
View File

@ -0,0 +1,63 @@
# ---> MATLAB
# Windows default autosave extension
*.asv
# OSX / *nix default autosave extension
*.m~
# Compiled MEX binaries (all platforms)
*.mex*
# Packaged app and toolbox files
*.mlappinstall
*.mltbx
# Generated helpsearch folders
helpsearch*/
# Simulink code generation folders
slprj/
sccprj/
# Matlab code generation folders
codegen/
# Simulink autosave extension
*.autosave
# Simulink cache files
*.slxc
# Octave session info
octave-workspace
/.vs/Inu_im_2wnd_3lvl_23550/v16/.suo
/.vs/Inu_im_2wnd_3lvl_23550/v16/Browse.VC.db
/.vs/Inu_im_2wnd_3lvl_23550/v16/Browse.VC.db-shm
/.vs/Inu_im_2wnd_3lvl_23550/v16/Browse.VC.db-wal
/.vs/Inu_im_2wnd_3lvl_23550/v16/Browse.VC.opendb
/.vs/ProjectSettings.json
/.vs/slnx.sqlite
/slprj/_cgxe/inu_im_2wnd_3lvl/inu_im_2wnd_3lvl_Cache.mat
/slprj/_jitprj/4jSHq6tpkAOru8OWjpPNFB.l
/slprj/_jitprj/4jSHq6tpkAOru8OWjpPNFB.mat
/slprj/accel/inu_im_2wnd_3lvl/amsi_serial.mat
/slprj/accel/inu_im_2wnd_3lvl/inu_im_2wnd_3lvl_instrumentation_settings.mat
/slprj/accel/inu_im_2wnd_3lvl/inu_im_2wnd_3lvl_SolverChangeInfo.mat
/slprj/accel/inu_im_2wnd_3lvl/inu_im_2wnd_3lvl_top_vm.bc
/slprj/accel/inu_im_2wnd_3lvl/inu_im_2wnd_3lvl_top_vm.ll
/slprj/accel/inu_im_2wnd_3lvl/tmwinternal/simulink_cache.xml
/slprj/sim/varcache/inu_im_2wnd_3lvl/checksumOfCache.mat
/slprj/sim/varcache/inu_im_2wnd_3lvl/tmwinternal/simulink_cache.xml
/slprj/sim/varcache/inu_im_2wnd_3lvl/varInfo.mat
/slprj/sl_proj.tmw
/slprj/
/.vs/
init.m
inu_23550.slx

205
Inu/MCU.c Normal file
View File

@ -0,0 +1,205 @@
/**
**************************************************************************
* @file MCU.c
* @brief Исходный код S-Function.
**************************************************************************
@details
Данный файл содержит функции S-Function, который вызывает MATLAB.
**************************************************************************
@note
Описание функций по большей части сгенерировано MATLAB'ом, поэтому на английском
**************************************************************************/
/**
* @addtogroup WRAPPER_SFUNC S-Function funtions
* @ingroup MCU_WRAPPER
* @brief Дефайны и функции блока S-Function
* @details Здесь собраны функции, с которыми непосредственно работает S-Function
* @note Описание функций по большей части сгенерировано MATLAB'ом, поэтому на английском
* @{
*/
#define S_FUNCTION_NAME wrapper_inu
#define S_FUNCTION_LEVEL 2
#include "mcu_wrapper_conf.h"
#define MDL_UPDATE ///< для подключения mdlUpdate()
/**
* @brief Update S-Function at every step of simulation
* @param S - pointer to S-Function (library struct from "simstruc.h")
* @details Abstract:
* This function is called once for every major integration time step.
* Discrete states are typically updated here, but this function is useful
* for performing any tasks that should only take place once per
* integration step.
*/
static void mdlUpdate(SimStruct *S)
{
// get time of simulation
time_T TIME = ssGetT(S);
//---------------SIMULATE MCU---------------
MCU_Step_Simulation(S, TIME); // SIMULATE MCU
//------------------------------------------
}//end mdlUpdate
/**
* @brief Writting outputs of S-Function
* @param S - pointer to S-Function (library struct from "simstruc.h")
* @details Abstract:
* In this function, you compute the outputs of your S-function
* block. Generally outputs are placed in the output vector(s),
* ssGetOutputPortSignal.
*/
static void mdlOutputs(SimStruct *S)
{
SIM_writeOutputs(S);
}//end mdlOutputs
#define MDL_CHECK_PARAMETERS /* Change to #undef to remove function */
#if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)
static void mdlCheckParameters(SimStruct *S)
{
int i;
// Проверяем и принимаем параметры и разрешаем или запрещаем их менять
// в процессе моделирования
for (i=0; i<1; i++)
{
// Input parameter must be scalar or vector of type double
if (!mxIsDouble(ssGetSFcnParam(S,i)) || mxIsComplex(ssGetSFcnParam(S,i)) ||
mxIsEmpty(ssGetSFcnParam(S,i)))
{
ssSetErrorStatus(S,"Input parameter must be of type double");
return;
}
// Параметр м.б. только скаляром, вектором или матрицей
if (mxGetNumberOfDimensions(ssGetSFcnParam(S,i)) > 2)
{
ssSetErrorStatus(S,"Параметр м.б. только скаляром, вектором или матрицей");
return;
}
// sim_dt = mxGetPr(ssGetSFcnParam(S,0))[0];
// Parameter not tunable
// ssSetSFcnParamTunable(S, i, SS_PRM_NOT_TUNABLE);
// Parameter tunable (we must create a corresponding run-time parameter)
ssSetSFcnParamTunable(S, i, SS_PRM_TUNABLE);
// Parameter tunable only during simulation
// ssSetSFcnParamTunable(S, i, SS_PRM_SIM_ONLY_TUNABLE);
}//for (i=0; i<NPARAMS; i++)
}//end mdlCheckParameters
#endif //MDL_CHECK_PARAMETERS
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 1);
// Кол-во ожидаемых и фактических параметров должно совпадать
if(ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S))
{
// Проверяем и принимаем параметры
mdlCheckParameters(S);
}
else
{
return;// Parameter mismatch will be reported by Simulink
}
// set up discrete states
ssSetNumContStates(S, 0); // number of continuous states
ssSetNumDiscStates(S, DISC_STATES_WIDTH); // number of discrete states
// set up input port
if (!ssSetNumInputPorts(S, 1)) return;
for (int i = 0; i < IN_PORT_NUMB; i++)
ssSetInputPortWidth(S, i, IN_PORT_WIDTH);
ssSetInputPortDirectFeedThrough(S, 0, 0);
ssSetInputPortRequiredContiguous(S, 0, 1); // direct input signal access
// set up output port
if (!ssSetNumOutputPorts(S, OUT_PORT_NUMB)) return;
for (int i = 0; i < OUT_PORT_NUMB; i++)
ssSetOutputPortWidth(S, i, OUT_PORT_WIDTH);
ssSetNumSampleTimes(S, 1);
ssSetNumRWork( S, 5); // number of real work vector elements
ssSetNumIWork( S, 5); // number of integer work vector elements
ssSetNumPWork( S, 0); // number of pointer work vector elements
ssSetNumModes( S, 0); // number of mode work vector elements
ssSetNumNonsampledZCs( S, 0); // number of nonsampled zero crossings
ssSetRuntimeThreadSafetyCompliance(S, RUNTIME_THREAD_SAFETY_COMPLIANCE_TRUE);
/* Take care when specifying exception free code - see sfuntmpl.doc */
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}
#define MDL_START /* Change to #undef to remove function */
#if defined(MDL_START)
/**
* @brief Initialize S-Function at start of simulation
* @param S - pointer to S-Function (library struct from "simstruc.h")
* @details Abstract:
* This function is called once at start of model execution. If you
* have states that should be initialized once, this is the place
* to do it.
*/
static void mdlStart(SimStruct *S)
{
SIM_Initialize_Simulation();
}
#endif // MDL_START
/**
* @brief Initialize Sample Time of Simulation
* @param S - pointer to S-Function (library struct from "simstruc.h")
* @details Abstract:
* This function is used to specify the sample time(s) for your
* S-function. You must register the same number of sample times as
* specified in ssSetNumSampleTimes.
*/
static void mdlInitializeSampleTimes(SimStruct *S)
{
// Шаг дискретизации
hmcu.sSimSampleTime = mxGetPr(ssGetSFcnParam(S,NPARAMS-1))[0];
// Register one pair for each sample time
ssSetSampleTime(S, 0, hmcu.sSimSampleTime);
ssSetOffsetTime(S, 0, 0.0);
}
/**
* @brief Terminate S-Function at the end of simulation
* @param S - pointer to S-Function (library struct from "simstruc.h")
* @details Abstract:
* In this function, you should perform any actions that are necessary
* at the termination of a simulation. For example, if memory was
* allocated in mdlStart, this is the place to free it.
*/
static void mdlTerminate(SimStruct *S)
{
hmcu.fMCU_Stop = 1;
ResumeThread(hmcu.hMCUThread);
WaitForSingleObject(hmcu.hMCUThread, 10000);
SIM_deInitialize_Simulation();
mexUnlock();
}
/** WRAPPER_SFUNC
* @}
*/
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a
MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration
function */
#endif

1
Inu/app_wrapper Submodule

@ -0,0 +1 @@
Subproject commit 876b8d9f9a896d524b3945ec346f2708a627feba

176
Inu/mcu_wrapper.c Normal file
View File

@ -0,0 +1,176 @@
/**
**************************************************************************
* @file mcu_wrapper.c
* @brief Исходный код оболочки МК.
**************************************************************************
@details
Данный файл содержит функции для симуляции МК в Simulink (S-Function).
**************************************************************************/
#include "mcu_wrapper_conf.h"
#include "app_init.h"
#include "app_io.h"
#include "pwm_sim.h"
/**
* @addtogroup WRAPPER_CONF
* @{
*/
SIM__MCUHandleTypeDef hmcu; ///< Хендл для управления потоком программы МК
/** MCU_WRAPPER
* @}
*/
//-------------------------------------------------------------//
//-----------------CONTROLLER SIMULATE FUNCTIONS---------------//
/* THREAD FOR MCU APP */
#ifdef RUN_APP_MAIN_FUNC_THREAD
/**
* @brief Главная функция приложения МК.
* @details Функция с которой начинается выполнение кода МК. Выход из данной функции происходит только в конце симуляции @ref mdlTerminate
*/
extern int main(void); // extern while from main.c
/**
* @brief Поток приложения МК.
* @details Поток, который запускает и выполняет код МК (@ref main).
*/
unsigned __stdcall MCU_App_Thread(void) {
main(); // run MCU code
return 0; // end thread
// note: this return will reached only at the end of simulation, when all whiles will be skipped due to @ref sim_while
}
#endif //RUN_APP_MAIN_FUNC_THREAD
/* SIMULATE MCU FOR ONE SIMULATION STEP */
/**
* @brief Симуляция МК на один такт симуляции.
* @param S - указатель на структуру S-Function из "simstruc.h"
* @param time - текущее время симуляции.
* @details Запускает поток, который выполняет код МК и управляет ходом потока:
* Если прошел таймаут, поток прерывается, симулируется периферия
* и на следующем шаге поток возобнавляется.
*
* Вызывается из mdlUpdate()
*/
void MCU_Step_Simulation(SimStruct* S, time_T time)
{
hmcu.SystemClockDouble += hmcu.sSystemClock_step; // emulate core clock
hmcu.SystemClock = hmcu.SystemClockDouble;
hmcu.SimTime = time;
MCU_readInputs(S); // считывание портов
MCU_Periph_Simulation(); // simulate peripheral
#ifdef RUN_APP_MAIN_FUNC_THREAD
ResumeThread(hmcu.hMCUThread);
for (int i = DEKSTOP_CYCLES_FOR_MCU_APP; i > 0; i--)
{
}
SuspendThread(hmcu.hMCUThread);
#endif //RUN_APP_MAIN_FUNC_THREAD
MCU_writeOutputs(S); // запись портов (по факту запись в буфер. запись в порты в mdlOutputs)
}
/* SIMULATE MCU PERIPHERAL */
/**
* @brief Симуляция периферии МК
* @details Пользовательский код, который симулирует работу периферии МК.
*/
void MCU_Periph_Simulation(void)
{
Simulate_PWM();
}
/* READ INPUTS S-FUNCTION TO MCU REGS */
/**
* @brief Считывание входов S-Function в порты ввода-вывода.
* @param S - указатель на структуру S-Function из "simstruc.h"
* @details Пользовательский код, который записывает входы МК из входов S-Function.
*/
void MCU_readInputs(SimStruct* S)
{
/* Get S-Function inputs */
real_T* IN = ssGetInputPortRealSignal(S, 0);
readInputParameters(IN);
}
/* WRITE OUTPUTS BUFFER S-FUNCTION FROM MCU REGS*/
/**
* @brief Запись портов ввода-вывода в буфер выхода S-Function
* @param S - указатель на структуру S-Function из "simstruc.h"
* @details Пользовательский код, который записывает буфер выходов S-Function из портов ввода-вывода.
*/
void MCU_writeOutputs(SimStruct* S)
{
/* Get S-Function descrete array */
real_T* Out_Buff = ssGetDiscStates(S);
writeOutputParameters(Out_Buff);
}
//-----------------CONTROLLER SIMULATE FUNCTIONS---------------//
//-------------------------------------------------------------//
//-------------------------------------------------------------//
//----------------------SIMULINK FUNCTIONS---------------------//
/* MCU WRAPPER DEINITIALIZATION */
/**
* @brief Инициализация симуляции МК.
* @details Пользовательский код, который создает поток для приложения МК
и настраивает симулятор МК для симуляции.
*/
void SIM_Initialize_Simulation(void)
{
#ifdef RUN_APP_MAIN_FUNC_THREAD
// инициализация потока, который будет выполнять код МК
hmcu.hMCUThread = (HANDLE)CreateThread(NULL, 0, MCU_App_Thread, 0, CREATE_SUSPENDED, &hmcu.idMCUThread);
#endif //RUN_APP_MAIN_FUNC_THREAD
/* user initialization */
Init_PWM_Simulation();
app_init();
/* clock step initialization */
hmcu.sSystemClock_step = MCU_CORE_CLOCK * hmcu.sSimSampleTime; // set system clock step
}
/* MCU WRAPPER DEINITIALIZATION */
/**
* @brief Деинициализация симуляции МК.
* @details Пользовательский код, который будет очищать все структуры после окончания симуляции.
*/
void SIM_deInitialize_Simulation(void)
{
//// simulate structures of peripheral deinitialization
//deInitialize_Periph_Sim();
//// mcu peripheral memory deinitialization
//deInitialize_MCU();
}
/* WRITE OUTPUTS OF S-BLOCK */
/**
* @brief Формирование выходов S-Function.
* @param S - указатель на структуру S-Function из "simstruc.h"
* @details Пользовательский код, который записывает выходы S-Function из буфера.
*/
void SIM_writeOutputs(SimStruct* S)
{
real_T* Output = ssGetOutputPortRealSignal(S,0);
real_T* Out_Buff = ssGetDiscStates(S);
//-------------WRITTING OUTPUT--------------
for (int i = 0; i < OUT_PORT_WIDTH; i++)
Output[i] = Out_Buff[i];
//for (int j = 0; j < OUT_PORT_NUMB; j++)
//{
// GPIO = ssGetOutputPortRealSignal(S, j);
// for (int i = 0; i < OUT_PORT_WIDTH; i++)
// {
// GPIO[i] = Out_Buff[j * OUT_PORT_WIDTH + i];
// Out_Buff[j * OUT_PORT_WIDTH + i] = 0;
// }
//}
//------------------------------------------
}
//-------------------------------------------------------------//

180
Inu/mcu_wrapper_conf.h Normal file
View File

@ -0,0 +1,180 @@
/**
**************************************************************************
* @dir ../MCU_Wrapper
* @brief <b> Папка с исходным кодом оболочки МК. </b>
* @details
В этой папке содержаться оболочка(англ. wrapper) для запуска и контроля
эмуляции микроконтроллеров в MATLAB (любого МК, не только STM).
Оболочка представляет собой S-Function - блок в Simulink, который работает
по скомпилированому коду. Компиляция происходит с помощью MSVC-компилятора.
**************************************************************************/
/**
**************************************************************************
* @file mcu_wrapper_conf.h
* @brief Заголовочный файл для оболочки МК.
**************************************************************************
@details
Главный заголовочный файл для матлаба. Включает дейфайны для S-Function,
объявляет базовые функции для симуляции МК и подключает базовые библиотеки:
- для симуляции "stm32fxxx_matlab_conf.h"
- для S-Function "simstruc.h"
- для потоков <process.h>
**************************************************************************/
#ifndef _WRAPPER_CONF_H_
#define _WRAPPER_CONF_H_
// Includes
#include "simstruc.h" // For S-Function variables
#include <process.h> // For threads
#include "app_includes.h"
/**
* @defgroup MCU_WRAPPER MCU Wrapper
* @brief Всякое для оболочки МК
*/
/**
* @addtogroup WRAPPER_CONF Wrapper Configuration
* @ingroup MCU_WRAPPER
* @brief Параметры конфигурации для оболочки МК
* @details Здесь дефайнами задается параметры оболочки, которые определяют как она будет работать
* @{
*/
// Parametrs of MCU simulator
//#define RUN_APP_MAIN_FUNC_THREAD ///< Enable using thread for MCU main() func
#define DEKSTOP_CYCLES_FOR_MCU_APP 0xFFFF ///< number of for() cycles after which MCU thread would be suspended
#define MCU_CORE_CLOCK 150000000 ///< MCU clock rate for simulation
// Parameters of S_Function
#define IN_PORT_WIDTH 20 ///< width of input ports
#define IN_PORT_NUMB 1 ///< number of input ports
#define OUT_PORT_WIDTH 51 ///< width of output ports
#define OUT_PORT_NUMB 1 ///< number of output ports
#define RWORK_0_WIDTH 5 //width of the real-work vector
#define IWORK_0_WIDTH 5 //width of the integer-work vector
/** WRAPPER_CONF
* @}
*/
/**
* @addtogroup MCU_WRAPPER
* @{
*/
// Fixed parameters(?) of S_Function
#define NPARAMS 1 ///< number of input parametrs (only Ts)
#define DISC_STATES_WIDTH OUT_PORT_WIDTH*OUT_PORT_NUMB ///< width of discrete states array (outbup buffer)
/**
* @brief Define for creating thread in suspended state.
* @details Define from WinBase.h. We dont wanna include "Windows.h" or smth like this, because of HAL there are a lot of redefine errors.
*/
#define CREATE_SUSPENDED 0x00000004
typedef void* HANDLE; ///< MCU handle typedef
/**
* @brief MCU handle Structure definition.
* @note Prefixes: h - handle, s - settings, f - flag
*/
typedef struct {
// MCU Thread
HANDLE hMCUThread; ///< Хендл для потока МК
int idMCUThread; ///< id потока МК (unused)
// Flags
unsigned fMCU_Stop : 1; ///< флаг для выхода из потока программы МК
double SimTime; ///< Текущее время симуляции
long SystemClock; ///< Счетчик тактов для симуляции системных тиков (в целочисленном формате)
double SystemClockDouble; ///< Счетчик в формате double для точной симуляции системных тиков С промежуточными значений
double sSystemClock_step; ///< Шаг тиков для их симуляции, в формате double
double sSimSampleTime; ///< Период дискретизации симуляции
}SIM__MCUHandleTypeDef;
extern SIM__MCUHandleTypeDef hmcu; // extern для видимости переменной во всех файлах
//-------------------------------------------------------------//
//------------------ SIMULINK WHILE DEFINES -----------------//
#ifdef RUN_APP_MAIN_FUNC_THREAD
/* DEFINE TO WHILE WITH SIMULINK WHILE */
/**
* @brief Redefine C while statement with sim_while() macro.
* @param _expression_ - expression for while.
* @details Это while который будет использоваться в симулинке @ref sim_while для подробностей.
*/
#define while(_expression_) sim_while(_expression_)
#endif
/* SIMULINK WHILE */
/**
* @brief While statement for emulate MCU code in Simulink.
* @param _expression_ - expression for while.
* @details Данный while необходим, чтобы в конце симуляции, завершить поток МК:
* При выставлении флага окончания симуляции, все while будут пропускаться
* и поток сможет дойти до конца функции main и завершить себя.
*/
#define sim_while(_expression_) while((_expression_)&&(hmcu.fMCU_Stop == 0))
/* DEFAULT WHILE */
/**
* @brief Default/Native C while statement.
* @param _expression_ - expression for while.
* @details Данный while - аналог обычного while, без дополнительного функционала.
*/
#define native_while(_expression_) for(; (_expression_); )
/***************************************************************/
//------------------ SIMULINK WHILE DEFINES -----------------//
//-------------------------------------------------------------//
//-------------------------------------------------------------//
//---------------- SIMULATE FUNCTIONS PROTOTYPES -------------//
/* Step simulation */
void MCU_Step_Simulation(SimStruct *S, time_T time);
/* MCU peripheral simulation */
void MCU_Periph_Simulation(void);
/* Initialize MCU simulation */
void SIM_Initialize_Simulation(void);
/* Deinitialize MCU simulation */
void SIM_deInitialize_Simulation(void);
/* Read inputs S-function */
void MCU_readInputs(SimStruct* S);
/* Write pre-outputs S-function (out_buff states) */
void MCU_writeOutputs(SimStruct* S);
/* Write outputs of block of S-Function*/
void SIM_writeOutput(SimStruct* S);
//---------------- SIMULATE FUNCTIONS PROTOTYPES -------------//
//-------------------------------------------------------------//
/** MCU_WRAPPER
* @}
*/
#endif // _WRAPPER_CONF_H_
//-------------------------------------------------------------//
//---------------------BAT FILE DESCRIBTION--------------------//
/**
* @file run_mex.bat
* @brief Батник для компиляции оболочки МК.
* @details
* Вызывается в матлабе из allmex.m.
*
* Исходный код батника:
* @include run_mex.bat
*/

View File

@ -0,0 +1,3 @@
#include "adc_sim.h"
AdcSimHandle adcsim;

View File

@ -0,0 +1,12 @@
#ifndef PWM_SIM
#define PWM_SIM
// Äëÿ ìîäåëèðîâàíèÿ ADC
typedef struct
{
int tAdc;
int Tadc;
int nAdc;
}AdcSimHandle;
#endif //PWM_SIM

View File

@ -0,0 +1,291 @@
#include "pwm_sim.h"
TimerSimHandle t1sim;
TimerSimHandle t2sim;
TimerSimHandle t3sim;
TimerSimHandle t4sim;
TimerSimHandle t5sim;
TimerSimHandle t6sim;
TimerSimHandle t7sim;
TimerSimHandle t8sim;
TimerSimHandle t9sim;
TimerSimHandle t10sim;
TimerSimHandle t11sim;
TimerSimHandle t12sim;
PWMPhaseSimHandle PWMPhaseA1;
PWMPhaseSimHandle PWMPhaseB1;
PWMPhaseSimHandle PWMPhaseC1;
PWMPhaseSimHandle PWMPhaseA2;
PWMPhaseSimHandle PWMPhaseB2;
PWMPhaseSimHandle PWMPhaseC2;
void Simulate_PWM(void)
{
Simulate_PWMPhase(&PWMPhaseA1, xpwm_time.Ta0_1, xpwm_time.Ta0_0);
Simulate_PWMPhase(&PWMPhaseB1, xpwm_time.Tb0_1, xpwm_time.Tb0_0);
Simulate_PWMPhase(&PWMPhaseC1, xpwm_time.Tc0_1, xpwm_time.Tc0_0);
Simulate_PWMPhase(&PWMPhaseA2, xpwm_time.Ta1_1, xpwm_time.Ta1_0);
Simulate_PWMPhase(&PWMPhaseB2, xpwm_time.Tb1_1, xpwm_time.Tb1_0);
Simulate_PWMPhase(&PWMPhaseC2, xpwm_time.Tc1_1, xpwm_time.Tc1_0);
}
void Init_PWM_Simulation(void)
{
Init_PWMPhase_Simulation(&PWMPhaseA1, &t1sim, &t2sim,
PWM_PERIOD, PWM_TICK_STEP);
Init_PWMPhase_Simulation(&PWMPhaseB1, &t3sim, &t4sim,
PWM_PERIOD, PWM_TICK_STEP);
Init_PWMPhase_Simulation(&PWMPhaseC1, &t5sim, &t6sim,
PWM_PERIOD, PWM_TICK_STEP);
Init_PWMPhase_Simulation(&PWMPhaseA2, &t7sim, &t8sim,
PWM_PERIOD, PWM_TICK_STEP);
Init_PWMPhase_Simulation(&PWMPhaseB2, &t9sim, &t10sim,
PWM_PERIOD, PWM_TICK_STEP);
Init_PWMPhase_Simulation(&PWMPhaseC2, &t11sim, &t12sim,
PWM_PERIOD, PWM_TICK_STEP);
t1sim.simulatePwm = (void (*)())Simulate_MainTIM;
}
void Simulate_PWMPhase(PWMPhaseSimHandle* tksim, int T1, int T0)
{
tksim->tsim1->simulatePwm(tksim->tsim1, T1);
tksim->tsim2->simulatePwm(tksim->tsim2, T0);
#ifdef PWM_SIMULATION_MODE_TK_LINES
convertSVGenTimesToTkLines(tksim);
xilinxPwm3LevelSimulation(tksim);
#endif
#ifdef PWM_SIMULATION_MODE_REGULAR_PWM
simulateActionActionQualifierSubmodule(tksim->tsim1);
simulateDeadBendSubmodule(tksim->tsim1, &tksim->pwmOut.ci1A, &tksim->pwmOut.ci1B);
simulateTripZoneSubmodule(tksim->tsim1);
simulateActionActionQualifierSubmodule(tksim->tsim2);
simulateDeadBendSubmodule(tksim->tsim2, &tksim->pwmOut.ci2A, &tksim->pwmOut.ci2B);
simulateTripZoneSubmodule(tksim->tsim2);
#endif
}
void Simulate_MainTIM(TimerSimHandle* tsim, int compare)
{
#ifdef ENABLE_UNITED_COUNTER_FOR_ALL_PWM
tsim->tcntAuxPrev = tsim->tcntAux;
tsim->tcntAux += tsim->TxCntPlus;
#endif
if (simulateTimAndGetCompare(tsim, compare))
mcu_simulate_step();
}
void Simulate_SimpleTIM(TimerSimHandle* tsim, int compare)
{
simulateTimAndGetCompare(tsim, compare);
}
int simulateTimAndGetCompare(TimerSimHandle* tsim, int compare)
{
int interruptflag = 0;
#ifdef ENABLE_UNITED_COUNTER_FOR_ALL_PWM
tsim->tcntAuxPrev = t1sim.tcntAuxPrev;
tsim->tcntAux = t1sim.tcntAux;
#else
tsim->tcntAuxPrev = tsim->tcntAux;
tsim->tcntAux += tsim->TxCntPlus;
#endif
if (tsim->tcntAux > tsim->TxPeriod) {
tsim->tcntAux -= tsim->TxPeriod * 2.;
tsim->cmpA = compare;
interruptflag = 1;
}
if ((tsim->tcntAuxPrev < 0) && (tsim->tcntAux >= 0)) {
tsim->cmpA = compare;
interruptflag = 1;
}
tsim->tcnt = fabs(tsim->tcntAux);
return interruptflag;
}
void convertSVGenTimesToTkLines(PWMPhaseSimHandle *tksim) {
TimerSimHandle* tsim1 = tksim->tsim1;
TimerSimHandle* tsim2 = tksim->tsim2;
//Phase Uni
if ((tsim1->cmpA < tsim1->tcnt) && (tsim2->cmpA < tsim2->tcnt)) {
tksim->tkLineA = 0;
tksim->tkLineB = 1;
}
else if ((tsim1->cmpA > tsim1->tcnt) && (tsim2->cmpA > tsim2->tcnt)) {
tksim->tkLineA = 1;
tksim->tkLineB = 0;
}
else if ((tsim1->cmpA < tsim1->tcnt) && (tsim2->cmpA > tsim2->tcnt)) {
//Îøèáêà. Çàäàíèå íà îòêðûòèå âåðõíèõ è íèæíèõ êëþ÷åé îäíîâðåìåííî. Çàêðûâàåì.
tksim->tkLineA = 1;
tksim->tkLineB = 1;
}
else {
tksim->tkLineA = 0;
tksim->tkLineB = 0;
}
}
void xilinxPwm3LevelSimulation(PWMPhaseSimHandle *tksim) {
TimerSimHandle* tsim1 = tksim->tsim1;
TimerSimHandle* tsim2 = tksim->tsim2;
DeadTimeSimHandle* deadtime = &tksim->deadtime;
PWMPhaseOutput* pwmOut = &tksim->pwmOut;
//Ïðåîáðàçóåì ñîñòîÿíèå ëèíèé ÒÊ â ñèãíàëû óïðàâëåíèÿ êëþ÷àìè
//PhaseA Uni1
if (tksim->tkLineB == 0 && tksim->tkLineA == 1) {
if ((pwmOut->ci1A == 0 || pwmOut->ci2A == 0) && deadtime->stateDt == stateDtReady) {
pwmOut->ci1B = 0;
pwmOut->ci2B = 0;
deadtime->dtcnt = deadtime->DtPeriod;
deadtime->stateDt = stateDtWait;
}
if (deadtime->stateDt == stateDtWait) {
if (deadtime->dtcnt > 0)
deadtime->dtcnt--;
else
deadtime->stateDt = stateDtReady;
}
if (deadtime->stateDt == stateDtReady) {
pwmOut->ci1A = 1;
pwmOut->ci2A = 1;
pwmOut->ci1B = 0;
pwmOut->ci2B = 0;
}
}
else if (tksim->tkLineB == 1 && tksim->tkLineA == 0) {
if ((pwmOut->ci1B == 0 || pwmOut->ci2B == 0) && deadtime->stateDt == stateDtReady) {
pwmOut->ci1A = 0;
pwmOut->ci2B = 0;
deadtime->dtcnt = deadtime->DtPeriod;
deadtime->stateDt = stateDtWait;
}
if (deadtime->stateDt == stateDtWait) {
if (deadtime->dtcnt > 0)
deadtime->dtcnt--;
else
deadtime->stateDt = stateDtReady;
}
if (deadtime->stateDt == stateDtReady) {
pwmOut->ci1A = 0;
pwmOut->ci2A = 0;
pwmOut->ci1B = 1;
pwmOut->ci2B = 1;
}
}
else if (tksim->tkLineA == 0 && tksim->tkLineB == 0) {
if ((pwmOut->ci1B == 0 || pwmOut->ci2A == 0) && deadtime->stateDt == stateDtReady) {
pwmOut->ci1A = 0;
pwmOut->ci2B = 0;
deadtime->dtcnt = deadtime->DtPeriod;
deadtime->stateDt = stateDtWait;
}
if (deadtime->stateDt == stateDtWait) {
if (deadtime->dtcnt > 0)
deadtime->dtcnt--;
else
deadtime->stateDt = stateDtReady;
}
if (deadtime->stateDt == stateDtReady) {
pwmOut->ci1A = 0;
pwmOut->ci2A = 1;
pwmOut->ci1B = 1;
pwmOut->ci2B = 0;
}
}
else {
pwmOut->ci1A = 0;
pwmOut->ci2A = 0;
pwmOut->ci1B = 0;
pwmOut->ci2B = 0;
}
}
void simulateActionActionQualifierSubmodule(TimerSimHandle* tsim)
{
// Ìîäåëèðóåì Action-Qualifier Submodule
if (tsim->cmpA > tsim->tcnt) {
tsim->deadtime.pre_ciA = 0;
tsim->deadtime.pre_ciB = 1;
}
else if (tsim->cmpA < tsim->tcnt) {
tsim->deadtime.pre_ciA = 1;
tsim->deadtime.pre_ciB = 0;
}
}
void simulateDeadBendSubmodule(TimerSimHandle* tsim, int* ciA, int* ciB)
{
// Ìîäåëèðóåì Dead-Band Submodule
if (tsim->deadtime.stateDt == 0) {
*ciA = tsim->deadtime.pre_ciA;
*ciB = 0;
if (tsim->deadtime.pre_ciA == 1)
tsim->deadtime.dtcnt = tsim->deadtime.DtPeriod;
if (tsim->deadtime.dtcnt > 0)
tsim->deadtime.dtcnt--;
else
tsim->deadtime.stateDt = 1;
}
else if (tsim->deadtime.stateDt == 1) {
*ciA = 0;
*ciB = tsim->deadtime.pre_ciB;
if (tsim->deadtime.pre_ciB == 1)
tsim->deadtime.dtcnt = tsim->deadtime.DtPeriod;
if (tsim->deadtime.dtcnt > 0)
tsim->deadtime.dtcnt--;
else
tsim->deadtime.stateDt = 0;
}
}
void simulateTripZoneSubmodule(TimerSimHandle* tsim)
{
// Ìîäåëèðóåì Trip-Zone Submodule
// ... clear flag for one-shot trip latch
// // ... clear flag for one-shot trip latch
//if (EPwm1Regs.TZCLR.all == 0x0004) {
// EPwm1Regs.TZCLR.all = 0x0000;
// EPwm1Regs.TZFRC.all = 0x0000;
//} // ... forces a one-shot trip event
//if (EPwm1Regs.TZFRC.all == 0x0004)
// ci1A_DT = ci1B_DT = 0;
}
void Init_PWMPhase_Simulation(PWMPhaseSimHandle* tksim, TimerSimHandle* tsim1, TimerSimHandle* tsim2, int period, double step)
{
tksim->tsim1 = tsim1;
tksim->tsim2 = tsim2;
Init_TIM_Simulation(tksim->tsim1, period, step);
Init_TIM_Simulation(tksim->tsim2, period, step);
}
void Init_TIM_Simulation(TimerSimHandle* tsim, int period, double step)
{
tsim->deadtime.stateDt = stateDtWait;
tsim->TxPeriod = period;
tsim->TxCntPlus = step * 2;
tsim->deadtime.DtPeriod = (int)(DT / hmcu.sSimSampleTime);
tsim->simulatePwm = (void (*)())Simulate_SimpleTIM;
}

View File

@ -0,0 +1,116 @@
#include "mcu_wrapper_conf.h"
#ifndef PWM_SIM
#define PWM_SIM
#define ENABLE_UNITED_COUNTER_FOR_ALL_PWM
#define PWM_SIMULATION_MODE_TK_LINES
//#define PWM_SIMULATION_MODE_REGULAR_PWM
#define PWM_PERIOD (FREQ_INTERNAL_GENERATOR_XILINX_TMS / FREQ_PWM)
#define PWM_TICK_STEP (FREQ_INTERNAL_GENERATOR_XILINX_TMS * hmcu.sSimSampleTime)
// Для моделирования ШИМ
/**
* @brief 3lvl PWM One Phase Simulation handle
*/
typedef struct
{
int ci1A;
int ci1B;
int ci2A;
int ci2B;
}PWMPhaseOutput;
/**
* @brief DeadTime Simulation Handle
*/
typedef struct
{
int DtPeriod;
int stateDt;
int dtcnt;
int pre_ciA;
int pre_ciB;
}DeadTimeSimHandle;
enum StateDeadTime {
stateDtWait = 0,
stateDtReady
};
/**
* @brief Tim Simulation Handle
*/
typedef struct
{
double TxCntPlus;
double TxPeriod;
double tcntAux;
double tcntAuxPrev;
double tcnt;
double cmpA;
double cmpB;
DeadTimeSimHandle deadtime;
void (*simulatePwm)();
}TimerSimHandle;
/**
* @brief PWM Phase Simulation Handle
*/
typedef struct
{
PWMPhaseOutput pwmOut;
TimerSimHandle* tsim1;
TimerSimHandle* tsim2;
int tkLineA;
int tkLineB;
DeadTimeSimHandle deadtime;
}PWMPhaseSimHandle;
extern TimerSimHandle t1sim;
extern TimerSimHandle t2sim;
extern TimerSimHandle t3sim;
extern TimerSimHandle t4sim;
extern TimerSimHandle t5sim;
extern TimerSimHandle t6sim;
extern TimerSimHandle t7sim;
extern TimerSimHandle t8sim;
extern TimerSimHandle t9sim;
extern TimerSimHandle t10sim;
extern TimerSimHandle t11sim;
extern TimerSimHandle t12sim;
extern PWMPhaseSimHandle PWMPhaseA1;
extern PWMPhaseSimHandle PWMPhaseB1;
extern PWMPhaseSimHandle PWMPhaseC1;
extern PWMPhaseSimHandle PWMPhaseA2;
extern PWMPhaseSimHandle PWMPhaseB2;
extern PWMPhaseSimHandle PWMPhaseC2;
void Simulate_PWM(void);
void Init_PWM_Simulation(void);
void Simulate_PWMPhase(PWMPhaseSimHandle* tksim, int T1, int T0);
void Simulate_MainTIM(TimerSimHandle* tsim, int compare);
void Simulate_SimpleTIM(TimerSimHandle* tsim, int compare);
int simulateTimAndGetCompare(TimerSimHandle* tsim, int compare);
void simulateActionActionQualifierSubmodule(TimerSimHandle* tsim);
void simulateDeadBendSubmodule(TimerSimHandle* tsim, int* ciA, int* ciB);
void simulateTripZoneSubmodule(TimerSimHandle* tsim);
void Init_TIM_Simulation(TimerSimHandle* tsim, int period, double step);
void Init_PWMPhase_Simulation(PWMPhaseSimHandle* tksim, TimerSimHandle* tsim1, TimerSimHandle* tsim2, int period, double step);
void convertSVGenTimesToTkLines(PWMPhaseSimHandle* tksim);
void xilinxPwm3LevelSimulation(PWMPhaseSimHandle* tksim);
#if defined(PWM_SIMULATION_MODE_REGULAR_PWM) && defined(PWM_SIMULATION_MODE_TK_LINES)
#error Choose only one PWM simulation mode!
#endif
#endif //PWM_SIM

126
README.md
View File

@ -1,2 +1,126 @@
# test_branches
# MATLAB 23550
**СОДЕРЖАНИЕ**
- [Общая структура симулятора](#общая-структура-симулятора)
- [Описание стуктуры симулятора](#описание-стуктуры-симулятора)
- [Оболочка МК](#оболочка-мк)
- [Оболочка программы](#оболочка-программы)
- [Код пользователя](#код-пользователя)
- [Симуляция плат](#симуляция-плат)
- [Инструкция](#инструкция)
- [Портирование кода](#портирование-кода)
- [Как скомпилировать код](#как-скомпилировать-код)
- [Как запустить отладку](#как-запустить-отладку)
- [Ошибки при портировании](#ошибки)
## Общая структура симулятора
Код для компиляции S-Function лежит в папке ***Inu***. В ней содержатся:
- **Оболочка МК ([файлы в корне](#оболочка-мк))**: код S-Function, который запускает код программы
- **Оболочка программы ([_app_wrapper_](#оболочка-программы))**: связывает программу и S-Function
- **Программа МК ([_Src_](#код-пользователя))**: программа для симуляции
- **Симуляция плат (beta) ([_xilinx_wrapper_](#симуляция-плат))**: моделирует внешние платы (пока только ШИМ)
Далее приведена структура симулятора. Инструкция для портирования другого кода в MATLAB приведена [ниже](#инструкция)
## Описание стуктуры симулятора
Здесь содержиться описание трех блоков симулятора:
- [Оболочка МК](#оболочка-мк)
- [Оболочка программы](#оболочка-программы)
- [Код пользователя](#код-пользователя)
- [Симуляция плат (beta)](#симуляция-плат)
### Оболочка МК
В этой папке содержиться оболочка (англ. wrapper) для запуска и контроля симуляции микроконтроллеров в MATLAB (любого МК, не только TMS). Оболочка представляет собой S-Function - блок в Simulink, который работает по скомпилированому коду. Компиляция происходит с помощью MSVC-компилятора.
S-Function работает особым образом: на шаге `n` она запускает скомпилированный код и ждет пока этот код выполниться. Только когда завершается выполнение кода, S-Function переходит на следующий шаг `n+1`.
Но программа МК это бесконечный цикл, который никогда не завершается. Поэтому есть несколько особенностей в выполнении такого кода в виде S-Function:
- Для симуляции создается отдельный поток для программы МК. Этот поток запускается в начале текущего шага симуляции, выполняется какое-то время, а потом приостанавливается. Это позволяет коду S-Function завершиться и перейти на следующий шаг.
- Необходимо закрыть поток программы МК в конце симуляции. Для этого используется особый дефайн для while. Этот дефайн помимо условия while, проверяет условие окончания симуляции. И если симуляцию надо завершить, все бесконечные циклы `while()` пропускаются и поток доходит до конца функции `main()` и завершает себя.
Всего оболочка содержит 4 файла:
- ***mcu_wrapper.c*** &emsp;&emsp;&emsp; - код, который запускает код МК и симулирует периферию. В нем содержаться функции для запуска/остановки потока программы МК, считывании входов и запись входов S-Function.
- ***MCU.c*** &nbsp;&ensp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; - код для компиляции S-Function в MATLAB. Вызывает функции из ***mcu_wrapper.c***
- ***mcu_wrapper_conf.h*** &ensp; - общий для ***mcu_wrapper.c*** и ***MCU.c*** заголовочный файл. Содержит настройки для блока S-Function, а также дефайны для управления потоком программы МК.
- ***run_mex.bat*** &nbsp;&emsp;&emsp;&emsp;&emsp; - скрипт для компиляции кода компилятором MSVC. В нем прописываются пути для заголовочных файлов ***.h***, указываются файлы исходного кода ***.c*** и прописываются дефайны для компиляции.
Конфигурации оболочки:
- `RUN_APP_MAIN_FUNC_THREAD` - создание и запуск отдельного потока для main()
- `DEKSTOP_CYCLES_FOR_MCU_APP` - количество циклов пустого for(;;), в течении которого будет работать поток main()
- `MCU_CORE_CLOCK` - частота симулируемого процессора (пока нигде не используется)
- `IN_PORT_WIDTH` - размерность входного вектора S-Function
- `IN_PORT_NUMB` - количество входных векторов S-Function
- `OUT_PORT_WIDTH` - размерность выходного вектора S-Function
- `OUT_PORT_NUMB` - количество выходных векторов S-Function
_Note: дефайн `RUN_APP_MAIN_FUNC_THREAD` пока выключен и поток для main() не используется)_
_Note for future: разные вектора можно использовать для разных плат_
### Оболочка программы
Оболочка для программы позволяет имитировать реальный алгоритм программы. Она инициализирует её, запускает необходимые для её работы функции и связывает её с входами/выходами S-Function
Ниже приведен перечень всех файлов и краткое описание зачем они нужны:
- ***app_includes.h*** &emsp;&emsp;&emsp; - Содержит ARM дефайны для компиляции в MSVC.
- ***app_init.c/.h*** &emsp;&emsp;&emsp;&emsp; - инициализация программы
- ***app_io.c/.h*** &nbsp;&ensp;&emsp;&emsp;&emsp;&emsp; - запись/считывание входов/выходов S-Function в программу
- ***app_wrapper.c/.h*** &nbsp;&nbsp;&ensp;&emsp; - вызов функций из программы и создание заглушек для ненужных функций
- ***def.h*** &ensp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; - дефайны для симуляции программы в симулинке (осталось от улитковского)
Также в папке ***\app_wrapper\device_support*** находяться стандартные библиотеки для TMS, но переделанные под компилятор MSVC (удален `volatile`, добавлены заглушки для `interrupt`, `asm`, `cregister`, добавлен код для симуляции IQlib).
### Код пользователя
Данная папка содержит исходный код приложения МК. При этом стандартные библиотеки, которые общие для всех проектов следует помещать в [папку с оболочкой программы](#оболочка-программы). Чтобы не редактировать исходники общих библиотек в каждом проекте.
### Симуляция плат
Модули в этой папке моделируют внешние платы. Пока более-менее сделан только ШИМ, но в будущем планируется расширение и вывод их в отельные S-Function. Чтобы сделать подобие корзины.
###### adc_sim
***adc_sim.c/.h*** - симуляция АЦП (пока просто заготовка)
###### pwm_sim
***pwm_sim.c/.h*** - симуляция ШИМ
Поддерживает два режимы формирования ШИМ:
- для каждого таймера отдельно (PWM_SIMULATION_MODE_REGULAR_PWM)
- через линии ТК для всей фазы разом (PWM_SIMULATION_MODE_TK_LINES).
## Инструкция
Общий алгоритм портирования кода для симуляции в MATLAB приведен ниже. В инструкции есть ссылки на более подробное описание действий.
1. [Портировать код для MATLAB](#портирование-кода) (можно начать с портирования без изменений и далее действовать от шага 2)
2. Проверить [компилируеться ли код](#как-скомпилировать-код). А далее:
- если есть ошибки при компиляции, [исправить их](#ошибки-при-компиляции) и вернуться на шаг 2.
- если ошибок нет, перейти на шаг 3.
3. Проверить нормально ли запускается и работает симуляция с МК. А далее:
- если симуляции вылетает, то необходимо [исправить ошибки](#ошибки-при-симуляции) в [режиме отладки](#как-запустить-отладку) и вернуться на шаг 3.
- если симуляция нормально запускается только один раз, и не завершается или не запускается второй раз, то необходимо [исправить ошибки](#ошибки-при-симуляции) в [режиме отладки](#как-запустить-отладку) и вернуться на шаг 3.
- если симуляция работает, не вылетает и перезапускается, то перейти на шаг 4.
4. Оценить результаты симуляции. А далее:
- если симуляция сходится с реальностью - то всё работает корректно.
- если нет - необходимо разбираться в алгоритме и после исправить его и перейти на шаг 2.
#### Портирование кода
Для начала необходимо весь пользовательский код портировать в отдельную папку для удобства. Например в "***\Src***".
Далее в "[run_bat.mex](#оболочка-мк)" надо прописать пути для заголовочных файлов (***\Includes***) и все необходимые для симуляции программы исходники. Все файлы исходников "***.c***" прописывать не обязательно. Если нужно отладить какой-то отдельный модуль программы, можно попробовать ограничиться им, и при возникновении ошибок подключать новые исходники.
#### Как скомпилировать код
Для компиляции кода необходимо открыть файл ***mexing.m***. Это MATLAB-скрипт, который запускает скрипт "[***run_bat.mex***](#оболочка-мк)" для компиляции. Есть возможность компиляции кода для [отладки](#как-запустить-отладку)
#### Как запустить отладку
Для отладки симуляции необходимо приписать в ***mexing.m*** в вызове ***run_mex.bat*** слово `debug`, тогда код скомпилируется для дебага. После этого необходимо открыть Visual Studio, открыть папку проекта (там должны быть все исходники программы и симулятора). И подключиться к ***MATLAB.exe***.
Теперь можно поставить точку в исходном коде симулятора или программы МК и запустить симуляцию. Когда MATLAB дойдет до этого места, симуляция остановиться и в Visual Studio можно будет посмотреть все переменные, пройти код по строкам и в общем делать всё то, что можно делать в режиме отладки.
Для отладки необходимо сначала подключится к процессу, а потом уже запускать симуляцию. Также отладка рабоатет только один раз. При повторном запуске симуляции остановки не произойдет. Поэтому перед каждой отладкой надо перекомпилировать код.
#### Ошибки
##### Ошибки при компиляции
Самые распространеные ошибки компилятора при портировании нового кода - это ошибки из-за неподключенных исходных файлов. Для исправления ошибок необходимо подключить требуемые модули или создавать заглушки.
Также еще могут быть ошибки связанные с разными компиляторами и отсутствия ключевых слов в MSVC, например, ошибки с `volatile`, `interrupt`, `asm`... в библиотеках DPS. Для исправления ошибок надо или удалять эти ключевые слова или создавать для них заглушки-define
##### Ошибки при симуляции
Обычно это исключения при чтении по недоступному адресу. Надо выяснить на какой строке произошло исключение и смотреть по какому адресу произошла попытка чтения и по какому адресу надо на самом деле считывать. И после этого скорректировать код так, чтобы адрес брался корректный.
Это обычно неинициализированные указатели или некоторые функции (напр. `ReadMemory`/`WriteMemory` когда пытаются считать что-то по адрессам "несуществующей" шины)

16
allmex.m Normal file
View File

@ -0,0 +1,16 @@
clc
% Êîìïèëèðóåò S-function
currFolder = cd;
%cd([currFolder, '\Inu']);
delete("wrapper_inu.mexw64")
delete("*.mexw64.pdb")
status=system('Inu\run_mex.bat debug')
if status==0
beep
else
error('Error!');
end

BIN
asynchronous_machine.p Normal file

Binary file not shown.

BIN
electr_mach.slx Normal file

Binary file not shown.