commit 1bd5009b9d2de267a28a0e5d6fe8dec28fbf01d8 Author: Razvalyaev Date: Sat Jun 14 19:51:05 2025 +0300 release 1.0 diff --git a/McuLib/install_my_library.m b/McuLib/install_my_library.m new file mode 100644 index 0000000..2c4a542 --- /dev/null +++ b/McuLib/install_my_library.m @@ -0,0 +1,47 @@ +% install_my_library.m +function install_my_library() + libDir = fileparts(mfilename('fullpath')); + + % 1. Добавляем библиотеку и m-файлы в путь + addpath(fullfile(libDir, 'lib')); + addpath(fullfile(libDir, 'm')); + savepath; + + % 2. Диалог выбора папки для копирования шаблонов + defaultTargetDir = pwd; + answer = questdlg(['Выберите папку для копирования шаблонов кода. ', ... + 'По умолчанию текущая папка: ' defaultTargetDir], ... + 'Выбор папки', ... + 'Текущая папка', 'Выбрать другую', 'Текущая папка'); + + switch answer + case 'Выбрать другую' + targetDir = uigetdir(defaultTargetDir, 'Выберите папку для шаблонов'); + if isequal(targetDir,0) + disp('Копирование шаблонов отменено пользователем.'); + targetDir = ''; + end + case 'Текущая папка' + targetDir = defaultTargetDir; + otherwise + targetDir = defaultTargetDir; + end + + if ~isempty(targetDir) + templatesDir = fullfile(libDir, 'templates'); + templateFiles = dir(fullfile(templatesDir, '*.*')); + for k = 1:numel(templateFiles) + if ~templateFiles(k).isdir + copyfile(fullfile(templatesDir, templateFiles(k).name), ... + fullfile(targetDir, templateFiles(k).name)); + end + end + fprintf('Шаблоны кода скопированы в папку:\n%s\n', targetDir); + end + + % 3. Обновляем Library Browser + rehash; + sl_refresh_customizations; + + disp('Библиотека успешно установлена и добавлена в Library Browser.'); +end diff --git a/McuLib/lib/MCU.c b/McuLib/lib/MCU.c new file mode 100644 index 0000000..7eb0c5b --- /dev/null +++ b/McuLib/lib/MCU.c @@ -0,0 +1,209 @@ +/** +************************************************************************** +* @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 MCU +#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, int_T tid) +{ + // 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, int_T tid) +{ + 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 0 + for i = 1:nCols + tableControl.removeColumn(1); + end + end + column = tableControl.addColumn(Name='Title', Type='edit'); + tableControl.Sortable = 'on'; + column.Name = tableParameter.Alias; + end + + + function update(tableName) + block = gcb; + mask = Simulink.Mask.get(block); + Table = mask.getParameter(tableName); + + cellArray = customtable.parse(tableName); + cleaned = customtable.removeEmptyRows(cellArray); + + if numel(cleaned) ~= numel(cellArray) + quoted = cellfun(@(s) ['''' s ''''], cleaned, 'UniformOutput', false); + newStr = ['{' strjoin(quoted, ';') '}']; + Table.Value = newStr; + end + end + + function column_titles = save_all_tables(table_names) + % Очищает столбцы в каждой таблице из массива имен table_names + % Возвращает cell-массив с названиями первых столбцов каждой таблицы + block = gcb; + + % Получить объект маски блока + maskObj = Simulink.Mask.get(block); + + % Инициализировать cell-массив для хранения названий столбцов + column_titles = cell(size(table_names)); + + for k = 1:numel(table_names) + table_name = table_names{k}; + + % Получить объект управления таблицей + tableControl = maskObj.getDialogControl(table_name); + + % Получить количество столбцов + nCols = tableControl.getNumberOfColumns; + + if nCols > 0 + % Получить первый столбец (который будем удалять) + column = tableControl.getColumn(1); + column_titles{k} = column.Name; + + % Удаляем все столбцы + % Важно: при удалении столбцов индексы меняются, + % поэтому удаляем всегда первый столбец nCols раз + for i = 1:nCols + tableControl.removeColumn(1); + end + else + % Если столбцов нет, возвращаем пустую строку + column_titles{k} = ''; + end + end + end + + function restore_all_tables(table_names, column_titles) + % Восстанавливает первый столбец в каждой таблице из массива имен + % Использует массив column_titles для установки имени столбца + block = gcb; + + % Получить объект маски блока + maskObj = Simulink.Mask.get(block); + + for k = 1:numel(table_names) + table_name = table_names{k}; + title = column_titles{k}; + + % Получить объект управления таблицей + tableControl = maskObj.getDialogControl(table_name); + + % Добавить новый столбец + column = tableControl.addColumn(Name='title', Type='edit'); + column.Name = title; + end + end + + + function out = parse(tableName) + block = gcb; + TableStr = get_param(block, tableName); + out = customtable.parse__(TableStr); + end + + function collect(tableName, cellArray) + block = gcb; + newTableStr = customtable.collect__(cellArray); + % Записываем обратно в параметр маски + set_param(block, tableName, newTableStr); + end + end + + + + + + methods(Static, Access=private) + + function out = parse__(tableStr) + str = strtrim(tableStr); + if startsWith(str, '{') && endsWith(str, '}') + str = str(2:end-1); + end + + parts = split(str, ';'); + out = cell(numel(parts), 1); + for i = 1:numel(parts) + el = strtrim(parts{i}); + if startsWith(el, '''') && endsWith(el, '''') + el = el(2:end-1); + end + out{i} = el; + end + + if isempty(out) || (numel(out) == 1 && isempty(out{1})) + out = {}; + end + end + + + function tableStr = collect__(cellArray) + quoted = cellfun(@(s) ['''' s ''''], cellArray, 'UniformOutput', false); + tableStr = ['{' strjoin(quoted, ';') '}']; + end + + + function cleaned = removeEmptyRows(cellArray) + if isempty(cellArray) + cleaned = {}; + else + % Проверяем каждую строку, есть ли в ней содержимое (не пустая строка) + isEmptyRow = cellfun(@(s) isempty(strtrim(s)), cellArray); + cleaned = cellArray(~isEmptyRow); + end + end + + end + +end \ No newline at end of file diff --git a/McuLib/m/editCode.m b/McuLib/m/editCode.m new file mode 100644 index 0000000..5780303 --- /dev/null +++ b/McuLib/m/editCode.m @@ -0,0 +1,172 @@ +classdef editCode + + methods(Static) + + function newCode = insertSection(code, sectionName, newText) + % insertSection – вставка или замена содержимого секции или тела функции + % Аргументы: + % code – исходный текст (строка) + % sectionName – имя секции (например, 'MY_SECTION') или заголовок функции (например, 'void myFunc(...)') + % newText – новый текст, который будет вставлен внутрь секции или функции + % + % Возвращает: + % newCode – обновлённый текст с подставленным содержимым + % + % Особенности: + % - Если sectionName начинается с 'void ', считается что это функция, и вставка происходит внутрь её тела. + % - В остальных случаях ищется секция между маркерами " START" и " END", и она заменяется на newText. + + newCode = code; + + % Если это функция + if startsWith(strtrim(sectionName), 'void ') + tokens = regexp(sectionName, 'void\s+(\w+)\s*\(', 'tokens'); + if isempty(tokens) + return; + end + funcName = tokens{1}{1}; + expr = sprintf('void\\s+%s\\s*\\(.*?\\)\\s*\\{', funcName); + startIdx = regexp(code, expr, 'start'); + if isempty(startIdx) + return; + end + + % Найдём тело функции с учётом вложенных скобок + from = startIdx(1); + braceCount = 0; + i = from; + while i <= length(code) + if code(i) == '{' + braceCount = braceCount + 1; + if braceCount == 1 + bodyStart = i + 1; + end + elseif code(i) == '}' + braceCount = braceCount - 1; + if braceCount == 0 + bodyEnd = i - 1; + break; + end + end + i = i + 1; + end + + if braceCount ~= 0 + return; + end + + newCode = [ ... + code(1:bodyStart-1), ... + newline, newText, newline, ... + code(bodyEnd+1:end) ... + ]; + return; + end + + % Иначе это обычная секция + % Формируем шаблон с группами для поиска нужного блока + pattern = sprintf('(%s START\\s*\\n)(.*?)(\\s*%s END)', sectionName, sectionName); + + % Проверяем, есть ли совпадение + startIdx = regexp(code, pattern, 'start', 'once'); + if isempty(startIdx) + error('Секция "%s" не найдена в тексте.', sectionName); + end + + % Формируем новую секцию с нужным текстом + replacement = sprintf('%s START\n%s\n%s END', sectionName, newText, sectionName); + + % Заменяем всю найденную секцию на новую + newCode = regexprep(code, pattern, replacement, 'dotall'); + end + + + + function result = extractSection(code, sectionName) + % extractSection – извлечение содержимого секции или тела функции + % Аргументы: + % code – исходный текст (строка) + % sectionName – имя секции (например, 'MY_SECTION') или заголовок функции (например, 'void myFunc(...)') + % + % Возвращает: + % result – извлечённый текст из секции или тела функции + % + % Особенности: + % - Если sectionName начинается с 'void ', считается что это функция, и извлекается содержимое её тела. + % - В остальных случаях ищется секция между маркерами " START" и " END". + + result = ''; + % Если это функция (начинается с 'void ') + if startsWith(strtrim(sectionName), 'void ') + % Получаем имя функции из заголовка + tokens = regexp(sectionName, 'void\s+(\w+)\s*\(', 'tokens'); + if isempty(tokens) + return; + end + funcName = tokens{1}{1}; + + % Строим шаблон начала функции + expr = sprintf('void\\s+%s\\s*\\(.*?\\)\\s*\\{', funcName); + startIdx = regexp(code, expr, 'start'); + if isempty(startIdx) + return; + end + + % Поиск тела функции с учётом вложенных скобок + from = startIdx(1); + braceCount = 0; + i = from; + while i <= length(code) + if code(i) == '{' + braceCount = braceCount + 1; + if braceCount == 1 + % Найдём первую новую строку после { + braceOpenIdx = i; + end + elseif code(i) == '}' + braceCount = braceCount - 1; + if braceCount == 0 + braceCloseIdx = i; + break; + end + end + i = i + 1; + end + + if braceCount ~= 0 + return; + end + + % Найдём \n после { + newlineAfterOpen = regexp(code(braceOpenIdx:end), '\n', 'once'); + if isempty(newlineAfterOpen) + return; + end + bodyStart = braceOpenIdx + newlineAfterOpen; + + % Найдём \n до } + newlinesBeforeClose = regexp(code(1:braceCloseIdx), '\n'); + if isempty(newlinesBeforeClose) + return; + end + bodyEnd = newlinesBeforeClose(end) - 1; + + % Извлекаем блок как есть, включая отступы + result = code(bodyStart:bodyEnd); + return; + end + + % Иначе считаем, что это секция вида // NAME START ... // NAME END + pattern = sprintf('%s START\\s*\\n(.*?)\n%s END', sectionName, sectionName); + + match = regexp(code, pattern, 'tokens', 'dotall'); + if ~isempty(match) + result = match{1}{1}; + else + mcuMask.disp(0, 'Ошибка: cекция "%s" не найдена в тексте.', sectionName); + end + end + + + end +end \ No newline at end of file diff --git a/McuLib/m/init.m b/McuLib/m/init.m new file mode 100644 index 0000000..8106cc0 --- /dev/null +++ b/McuLib/m/init.m @@ -0,0 +1,120 @@ +% C + +clear;% + +%% + +addpath('MCU_Wrapper'); +addpath('motor'); +Ts = 10e-6;% +Decim = 1;% +DisableScope = { + "Idc"; + "Udc"; + }; + +GED = "23550"; +% GED = "22220"; + +% , NmNom +w0 = 0;%0.5;%-0.75;% +% , .. +Mst = 0.6;%0.6; + +% / / +changingLoadEnable = 0;%1 +% / +noiseEnable = 0;%1;% +% ... +NP = 0.08; + + +%% +% ... , +Pnom = 6300e3; +% ... , (rms) +Unom = 3300; +% ... , / +NmNom = 180; +% ... +Pp = 6; +% ... +CosFi = 0.87; +% ... +Eff = 0.968; +% ... , *^2 +J = 87e3*0.1; + + +%% +% + +modelName = [bdroot '/Measurements']; +blocks = find_system(modelName, ... + 'IncludeCommented', 'on', ... + 'FollowLinks', 'on', ... + 'LookUnderMasks', 'all', ... + 'BlockType', 'Scope'); +for i = 1:length(blocks) + set_param(blocks{i}, 'Commented', 'off'); +end +% +for i = 1:length(DisableScope) + set_param([modelName '/'] + DisableScope{i}, 'Commented', 'on'); +end + +% +SQRT2 = sqrt(2); +SQRT3 = sqrt(3); +PI2 = pi*2; + +% ... , +Snom = Pnom/CosFi/Eff; +% ... , / +WmNom = NmNom/60*PI2; +% ... , * +Mnom = Pnom/WmNom; +% ... . , / +WeNom = WmNom*Pp; +% ... . , +FeNom = WeNom/PI2; +% ... , +PsiNom = Unom*SQRT2/(WeNom*SQRT3); +% ... , B +UdcNom = Unom*SQRT2; +% ... , (ampl) +Inom = Snom/(Unom*SQRT3)*SQRT2*0.5;%0.5 - .. + +% +if GED == "22220" + GED + Rs = 11.8e-3;% + Xls = 72.7e-3;%72.7e-3;% + Rr = 11.1e-3*2.0;%*0.8;% + Xlr = 85.5e-3;% + Xm = 2.9322;%2.87;% + Fe = 18;% + Lls = Xls/(Fe*PI2);% + Llr = Xlr/(Fe*PI2);% + Lm = Xm/(Fe*PI2);% +elseif GED == "23550" + GED + Rs = 0.0282;% + Xls = 0.4016;% + Rr = 0.139;% + Xlr = 0.2006;% + Xm = 5.2796;% + Fe = 18.2;% + Lls = Xls/(Fe*PI2);% + Llr = Xlr/(Fe*PI2);% + Lm = Xm/(Fe*PI2);% +end + +% INU, +Cdc = 50e-3; +% INU +Csn = Pnom/(1000*WeNom*Unom^2)/10;% (0.5 - .. ) +Rsn = 2*Ts/Csn*10;% + +% , c +Tiac = 30e-6; diff --git a/McuLib/m/mcuMask.m b/McuLib/m/mcuMask.m new file mode 100644 index 0000000..7755161 --- /dev/null +++ b/McuLib/m/mcuMask.m @@ -0,0 +1,641 @@ +classdef mcuMask + + methods(Static) + % Following properties of 'maskInitContext' are avalaible to use: + % - BlockHandle + % - MaskObject + % - MaskWorkspace - Use get/set APIs to work with mask workspace. + function MaskInitialization(maskInitContext) + % Получаем хэндл текущего блока + blk = gcbh; + % Получаем объект маски текущего блока + mask = Simulink.Mask.get(gcb); + % mcuMask.disp(1,''); + try + % Проверка наличия findjobj + findjobjAvailable = exist('findjobj', 'file') == 2; + catch + findjobjAvailable = false; + end + % Получаем объект маски текущего блока + mask = Simulink.Mask.get(gcb); + % Имя checkbox-параметра (укажите точное имя из маски) + checkboxParamName = 'extConsol'; % пример + findjobjLinkName = 'findjobj_link'; % пример + % Получаем параметр по имени + checkboxParam = mask.getParameter(checkboxParamName); + findjobjLink = mask.getDialogControl(findjobjLinkName); + if isempty(findjobjLink) + error('Параметр %s не найден в маске.', findjobjLinkName); + end + if isempty(checkboxParam) + error('Параметр %s не найден в маске.', checkboxParamName); + end + % Блокируем чекбокс, если findjobj не найден + if ~findjobjAvailable + checkboxParam.Enabled = 'off'; + checkboxParam.Value = 'off'; % и на всякий случай снимаем галочку + checkboxParam.Prompt = 'External Console (requires findjobj)'; + findjobjLink.Visible = 'on'; + else + checkboxParam.Enabled = 'on'; + checkboxParam.Prompt = 'External Console'; + findjobjLink.Visible = 'off'; + end + % формирование таблицы на всю ширину + table_names = {'srcTable', 'incTable'}; + for k = 1:numel(table_names) + table_name = table_names{k}; + % customtable.format(table_name); + end + % запись описания блока + textDesc = ['Блок для настройки параметров симуляции микроконтроллера. ' newline ... + 'Позволяет задавать параметры оболочки, приложения МК и периферии']; + + % Получаем объект описания + toolTextArea = mask.getDialogControl('BlockDesc'); + toolTextArea.Prompt = textDesc; + end + + %% WRAPPER PARAMS + function enableThreading(callbackContext) + block = gcb; + maskNames = get_param(block, 'MaskNames'); + maskValues = get_param(block, 'MaskValues'); + maskEnables = get_param(block, 'MaskEnables'); + idxEnable = find(strcmp(maskNames, 'enableThreading')); + idxEdit = find(strcmp(maskNames, 'threadCycles')); + if isempty(idxEnable) || isempty(idxEdit) + error('Параметры enableThreading или threadCycles не найдены в маске'); + end + val = maskValues{idxEnable}; + if strcmp(val, 'on') + maskEnables{idxEdit} = 'on'; + else + maskEnables{idxEdit} = 'off'; + end + set_param(block, 'MaskEnables', maskEnables); + end + + function enableDeinit(callbackContext) + block = gcb; + maskNames = get_param(block, 'MaskNames'); + maskValues = get_param(block, 'MaskValues'); + maskEnables = get_param(block, 'MaskEnables'); + idxEnable = find(strcmp(maskNames, 'enableThreading')); + idxEdit = find(strcmp(maskNames, 'threadCycles')); + if isempty(idxEnable) || isempty(idxEdit) + error('Параметры enableThreading или threadCycles не найдены в маске'); + end + val = maskValues{idxEnable}; + if strcmp(val, 'on') + maskEnables{idxEdit} = 'on'; + else + maskEnables{idxEdit} = 'off'; + end + set_param(block, 'MaskEnables', maskEnables); + end + + function extConsol(callbackContext) + block = gcb; + mask = Simulink.Mask.get(block); + fullOut = mask.getParameter('fullOutput'); + extCons = mask.getParameter('extConsol'); + if isempty(extCons) || isempty(fullOut) + error('Параметры fullOutput или extConsol не найдены в маске'); + end + + if(strcmp(extCons.Enabled, 'on')) + if strcmp(extCons.Value, 'on') + fullOut.Enabled = 'off'; + fullOut.Value = 'on'; + else + fullOut.Enabled = 'on'; + end + else + fullOut.Enabled = 'on'; + end + + end + + function wrapperPath_add(callbackContext) + block = gcb; + mask = Simulink.Mask.get(block); + % Открываем окно выбора папки + folderPath = uigetdir('', 'Выберите папку'); + % Проверка на отмену + if isequal(folderPath, 0) + return; + end + % Установка значения параметра маски + rel = mcuMask.absoluteToRelativePath(folderPath); + param = mask.getParameter('wrapperPath'); + param.Value = rel; + + end + %% USER WRAPPER CODE + + function wrapperFunc(callbackContext) + block = gcb; + % Получаем имя функции и путь к файлам + [filename, section, tool, example]= mcuMask.getWrapperUserFile(block); + mcuMask.tool(tool, example); + + % Загружаем содержимое файла + set_param(block, 'wrapperCode', ''); + code = fileread(filename); + code = regexprep(code, '\r\n?', '\n'); % нормализуем окончания строк + + includesText = editCode.extractSection(code, section); + set_param(block, 'wrapperCode', includesText); + % % Поиск тела обычной функции + % expr = sprintf('void %s()', sel); + % funcBody = editCode.extractSection(code, expr); + % set_param(block, 'wrapperCode', funcBody); + end + + function saveWrapperCode(callbackContext) + block = gcb; + + % Получаем имя функции и путь к файлам + [filename, section] = mcuMask.getWrapperUserFile(block); + if ~isfile(filename) + errordlg(['Файл не найден: ', filename]); + return; + end + + sel = get_param(block, 'wrapperFunc'); + basePath = get_param(block, 'wrapperPath'); + if isempty(basePath) + errordlg('Не указан путь к файлам обёртки (wrapperPath).'); + return; + end + newBody = get_param(block, 'wrapperCode'); + code = fileread(filename); + code = regexprep(code, '\r\n?', '\n'); + code = editCode.insertSection(code, section, newBody); + % else + % % Обновляем тело функции + % expr = sprintf('void %s()', sel); + % code = editCode.insertSection(code, expr, newBody); + % end + fid = fopen(filename, 'w', 'n', 'UTF-8'); + if fid == -1 + errordlg('Не удалось открыть файл для записи'); + return; + end + fwrite(fid, code); + fclose(fid); + mcuMask.disp(1, ['Обновлено: ' sel]); + end + + function openWrapperCode(callbackContext) + block = gcb; + + % Получаем имя функции и путь к файлам + filename = mcuMask.getAbsolutePath(mcuMask.getWrapperUserFile(block)); + if exist(filename, 'file') == 2 + % Формируем команду без кавычек + cmd = sprintf('rundll32.exe shell32.dll,OpenAs_RunDLL %s', filename); + status = system(cmd); + if status ~= 0 + errordlg('Не удалось открыть окно выбора приложения.'); + end + else + errordlg('Файл не найден'); + end + end + + %% USER CODE + function srcTable(callbackContext) + customtable.update('srcTable'); + end + + function incTable(callbackContext) + customtable.update('incTable'); + end + + function btnAddSrc(callbackContext) + blockHandle = gcb; + % Открываем проводник для выбора файлов + [files, pathstr] = uigetfile({ ... + '*.c;*.cpp', 'Исходные файлы (*.c, *.cpp)'; ... + '*.obj;*.lib', 'Библиотеки (*.obj, *.lib)'; ... + '*.*', 'Все файлы (*.*)'}, ... + 'Выберите файлы', ... + 'MultiSelect', 'on'); + + if isequal(files, 0) + return; % Отмена выбора + end + if ischar(files) + files = {files}; % Один файл — в cell + end + % Парсим строку в cell-массив + oldTable = customtable.parse('srcTable'); + + % Добавляем новые пути, проверяя уникальность + for i = 1:numel(files) + fullpath = fullfile(pathstr, files{i}); + rel = mcuMask.absoluteToRelativePath(fullpath); + if ~any(strcmp(rel, oldTable)) + oldTable{end+1, 1} = rel; + end + end + + % Парсим строку в cell-массив + customtable.collect('srcTable', oldTable); + + end + + function btnAddInc(callbackContext) + blockHandle = gcb; + % Открываем проводник для выбора папок + pathstr = uigetdir(pwd, 'Выберите папку с заголовочными файлами'); + if isequal(pathstr, 0) + return; % Отмена выбора + end + % Парсим таблицу + oldTable = customtable.parse('incTable'); + + rel = mcuMask.absoluteToRelativePath(pathstr); + + % Проверяем наличие пути + if ~any(strcmp(rel, oldTable)) + oldTable{end+1, 1} = rel; + end + + % Собираем таблицу + customtable.collect('incTable', oldTable); + end + + %% PERIPH CONFIG + + function periphPath_add(callbackContext) + block = gcbh; + mask = Simulink.Mask.get(block); + [file, path] = uigetfile({'*.*','Все файлы (*.*)'}, 'Выберите файл'); + if isequal(file, 0) || isequal(path, 0) + % Отмена выбора — ничего не делаем + return; + end + fullFilePath = fullfile(path, file); + rel = mcuMask.absoluteToRelativePath(fullFilePath); + param = mask.getParameter('periphPath'); + param.Value = rel; + end + + function compile(callbackContext) + addpath('MCU_Wrapper'); + mexing(1); + end + + + function updateModel(callbackContext) + addpath('MCU_Wrapper'); + res = mexing(1); + if res ~= 0 + return; + end + + modelName = bdroot(gcb); % получить имя верхнего уровня модели + blockName = gcb; + mgr = asynchManage(modelName, blockName); % создать объект класса + mgr.saveAndUpdateModel(); % запустить сохранение и обновление + end + + + function findjobj_link(callbackContext) + web('https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects'); + end + + function set_name() + block = gcb; + % Получаем параметр имени S-Function из маски блока + newName = get_param(block, 'sfuncName'); + + % Путь к файлу, в котором надо заменить строку + cFilePath = fullfile(pwd, './MCU_Wrapper/MCU.c'); % <-- укажи правильный путь + + % Считаем файл в память + fileText = fileread(cFilePath); + + % Регулярное выражение для поиска строки с define + % Заменим строку вида: #define S_FUNCTION_NAME old_name + pattern = '#define\s+S_FUNCTION_NAME\s+\w+'; + + % Новая строка + newLine = ['#define S_FUNCTION_NAME ', newName]; + + % Замена + updatedText = regexprep(fileText, pattern, newLine); + + % Записываем обратно в файл + fid = fopen(cFilePath, 'w', 'n', 'UTF-8'); + if fid == -1 + error('Не удалось открыть файл для записи.'); + end + fwrite(fid, updatedText); + fclose(fid); + + end + + end + + +%% SPECIFIC TOOLS + methods(Static, Access = private) + + function [filename, section, tool, example] = getWrapperUserFile(block) + sel = get_param(block, 'wrapperFunc'); + basePath = get_param(block, 'wrapperPath'); + if isempty(basePath) + errordlg('Не указан путь к файлам обёртки (wrapperPath).'); + return; + end + % Формируем путь к файлу в зависимости от типа запроса + if strcmp(sel, 'Includes') + filename = fullfile(basePath, 'app_includes.h'); + section = '// INCLUDES'; + tool = 'Инклюды для доступа к коду МК в коде оболочке'; + example = '#include "main.h"'; + elseif strcmp(sel, 'Dummy') + filename = fullfile(basePath, 'app_wrapper.c'); + section = '// DUMMY'; + tool = 'Заглушки для различных функций и переменных'; + example = ['CAN_HandleTypeDef hcan = {0};' newline... + 'void hardware_func(handle *huart) {}' newline... + 'int wait_for_hardware_flag(int *flag) {' newline... + ' return 1;' newline... + '}' newline... + '']; + elseif strcmp(sel, 'App Init') + filename = fullfile(basePath, 'app_init.c'); + section = '// USER APP INIT'; + tool = ['Код для инициализации приложения МК.' newline newline... + 'Вызов функций инициализации, если не используется отдельный поток для main().']; + example = 'init_func();'; + elseif strcmp(sel, 'App Step') + filename = fullfile(basePath, 'app_wrapper.c'); + section = '// USER APP STEP'; + tool = ['Код приложения МК для вызова в шаге симуляции.' newline newline ... + 'Вызов функций программы МК, если не используется отдельный поток для main().']; + example = 'step_func();'; + elseif strcmp(sel, 'App Inputs') + filename = fullfile(basePath, 'app_io.c'); + section = '// USER APP INPUT'; + tool = ['Работа с буффером для портов S-Function' newline newline ... + 'Буфер в начале хранит входные порты S-Function, далее идут выходные порты:' newline ... + 'Buffer[0:15] - входной порт, Buffer[16:31] - входной 1 порт, ' newline ... + 'Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт']; + example = ['// чтение 1-го элемента 0-го входного массива' newline... + 'app_variable_2 = ReadInputArray(0, 1);' newline newline... + '// запись в буфер выходов' newline ... + 'app_variable_2 = Buffer[10];']; + elseif strcmp(sel, 'App Outputs') + filename = fullfile(basePath, 'app_io.c'); + section = '// USER APP OUTPUT'; + tool = ['Работа с буффером для портов S-Function' newline newline ... + 'Буфер в начале хранит входные порты S-Function, далее идут выходные порты:' newline ... + 'Buffer[0:15] - входной порт, Buffer[16:31] - входной 1 порт, ' newline ... + 'Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт']; + example = ['// запись в 1-й элемент 0-го выходного массива' newline... + 'WriteOutputArray(app_variable, 0, 1);' newline newline ... + '// запись в буфер выходов' newline ... + 'Buffer[XD_OUTPUT_START + 10] = app_variable_2;']; + elseif strcmp(sel, 'App Deinit') + filename = fullfile(basePath, 'app_init.c'); + section = '// USER APP DEINIT'; + tool = ['Код для деинициализации приложения МК.' newline newline ... + 'Можно деинициализировать приложение МК, для повторного запуска.']; + example = 'memset(&htim1, sizeof(htim1), 0;'; + else + tool = ''; + mcuMask.disp(0, '\nОшибка выбора типа секции кода: неизвестное значение'); + end + + end + + end + +%% GENERAL TOOLS + methods(Static, Access = public) + + function saveAndClose(blockPath) + try + % Считываем текущее имя модели + modelName = bdroot(blockPath); + % Включаем возможность изменения маски + set_param(blockPath, 'MaskSelfModifiable', 'on'); + + % Считываем текущие значения параметров маски + currentMaskValues = get_param(blockPath, 'MaskValues'); + + % Применяем текущие значения заново, чтобы "применить" маску + set_param(blockPath, 'MaskValues', currentMaskValues); + save_system(modelName); + catch ME + warning('progr:Nneg', 'Ошибка при сохранении маски: %s', ME.message); + end + close_system(blockPath, 0); + end + + function open(blockPath, clear_flag) + open_system(blockPath, 'mask'); + mcuMask.disp(clear_flag, ''); + end + + + function absPath = getAbsolutePath(relPath) + % relativeToAbsolutePath — преобразует относительный путь в абсолютный. + % + % Если путь уже абсолютный — возвращается он же, приведённый к канонической форме. + % Если путь относительный — преобразуется относительно текущей директории. + + % Проверка: абсолютный ли путь + if ispc + isAbsolute = ~isempty(regexp(relPath, '^[a-zA-Z]:[\\/]', 'once')) || startsWith(relPath, '\\'); + else + isAbsolute = startsWith(relPath, '/'); + end + + if isAbsolute + % Канонизируем абсолютный путь (убираем ./, ../ и т.п.) + absPath = char(java.io.File(relPath).getCanonicalPath()); + else + % Строим абсолютный путь от текущей директории + cwd = pwd; + combined = fullfile(cwd, relPath); + absPath = char(java.io.File(combined).getCanonicalPath()); + end + end + + + + function rel = absoluteToRelativePath(pathstr) + % absoluteToRelativePath — преобразует абсолютный путь в относительный от текущей директории. + % + % Если путь находится в текущей директории или вложенной в неё — добавляется префикс './' + % Если выше — формируются переходы '..' + % Если путь совпадает с текущей директорией — возвращается '.' + + % Получаем текущую рабочую директорию + cwd = pwd; + + % Преобразуем пути в канонические абсолютные пути + fullpath = char(java.io.File(pathstr).getCanonicalPath()); + cwd = char(java.io.File(cwd).getCanonicalPath()); + + % Разбиваем пути на части + targetParts = strsplit(fullpath, filesep); + baseParts = strsplit(cwd, filesep); + + % Находим длину общего префикса + j = 1; + while j <= min(length(targetParts), length(baseParts)) && strcmpi(targetParts{j}, baseParts{j}) + j = j + 1; + end + + % Формируем количество подъемов ".." из cwd + numUps = length(baseParts) - (j - 1); + ups = repmat({'..'}, 1, numUps); + + % Оставшаяся часть пути после общего префикса + rest = targetParts(j:end); + + % Объединяем для получения относительного пути + relParts = [ups, rest]; + rel = fullfile(relParts{:}); + + % Если путь пустой — это текущая директория + if isempty(rel) + rel = '.'; + end + + % Если путь не содержит ".." и начинается внутри текущей директории — добавим './' + if ~isempty(rest) && isempty(ups) + rel = fullfile('.', rel); + end + end + + + + function checkbox_state = read_checkbox(checkboxName) + maskValues = get_param(gcbh, 'MaskValues'); + paramNames = get_param(gcbh, 'MaskNames'); + + inxCheckBox = find(strcmp(paramNames, checkboxName)); + + checkbox_state_str = maskValues{inxCheckBox}; + if strcmpi(checkbox_state_str, 'on') + checkbox_state = 1; + else + checkbox_state = 0; + end + end + + + function children = get_children(ctrl) + if isprop(ctrl, 'DialogControls') + children = ctrl.DialogControls; + elseif isprop(ctrl, 'Controls') + children = ctrl.Controls; + elseif isprop(ctrl, 'Children') + children = ctrl.Children; + else + children = []; + end + end + + function params = collect_all_parameters(container) + params = {}; + children = container.DialogControls; + for i = 1:numel(children) + ctrl = children(i); + if isa(ctrl, 'Simulink.dialog.Tab') + % Если вкладка — рекурсивно собираем параметры внутри неё + params = [params, mcuMask.collect_all_parameters(ctrl)]; + else + % Иначе это параметр — добавляем имя + params{end+1} = ctrl.Name; %#ok + end + end + end + + function delete_all_tabs(mask, container) + children = container.DialogControls; + % Идём в обратном порядке, чтобы безопасно удалять + for i = numel(children):-1:1 + ctrl = children(i); + if isa(ctrl, 'Simulink.dialog.Tab') + % Сначала рекурсивно удаляем вкладки внутри текущей вкладки + mcuMask.delete_all_tabs(mask, ctrl); + try + container.removeDialogControl(ctrl.Name); + catch ME + warning('Не удалось удалить вкладку %s: %s', ctrl.Name, ME.message); + end + end + end + end + + function res = ternary(cond, valTrue, valFalse) + if cond + res = valTrue; + else + res = valFalse; + end + end + + + function tool(text, example) + % Устанавливает заданный текст в параметр Text Area 'toolText' через объект маски + + % Получаем ссылку на текущий блок + block = gcb; + + % Получаем объект маски + mask = Simulink.Mask.get(block); + + toolTextArea = mask.getDialogControl('toolText'); + exampleTextArea = mask.getDialogControl('exampleText'); + toolTextArea.Prompt = text; + exampleTextArea.Prompt = example; + end + + function disp(clcFlag, varargin) + if clcFlag + set_param(gcb, 'consoleOutput', ''); + end + + if length(varargin) == 1 && ischar(varargin{1}) + % Если передан один аргумент — просто строка, передаем напрямую + out = varargin{1}; + else + % Иначе считаем, что первый аргумент — формат, остальные — параметры + out = sprintf(varargin{:}); + end + + out_now = get_param(gcb, 'consoleOutput'); + set_param(gcb, 'consoleOutput', [out_now out]); + end + + + function updateModelAsync() + mdl = bdroot(gcb); + + try + % Применить изменения, если есть + set_param(mdl, 'ApplyChanges', 'on'); + catch + beep + % Игнорировать, если не удалось + end + + t = timer('StartDelay', 0.01, 'TimerFcn', @(~,~) set_param(mdl, 'SimulationCommand', 'update')); + start(t); + end + + + end +end \ No newline at end of file diff --git a/McuLib/m/mcuPorts.m b/McuLib/m/mcuPorts.m new file mode 100644 index 0000000..315871e --- /dev/null +++ b/McuLib/m/mcuPorts.m @@ -0,0 +1,284 @@ +classdef mcuPorts + + methods(Static) + + function write() + block = gcb; + mask = Simulink.Mask.get(block); + hPath = fullfile('.\MCU_Wrapper', 'mcu_wrapper_conf.h'); + cPath = fullfile('.\MCU_Wrapper', 'mcu_wrapper.c'); + mcuPorts.defaultUnused(); + %% CREATE + prefixNumb = 'IN'; + [widths, portPrefixes] = mcuPorts.getMaskNames('in'); + + headerText = mcuPorts.addPortHeaderDefines('', widths, prefixNumb, portPrefixes); + cText = mcuPorts.addPortCDefines('', widths, prefixNumb, portPrefixes); + + prefixNumb = 'OUT'; + [widths, portPrefixes] = mcuPorts.getMaskNames('out'); + + headerText = mcuPorts.addPortHeaderDefines(headerText, widths, prefixNumb, portPrefixes); + cText = mcuPorts.addPortCDefines(cText, widths, prefixNumb, portPrefixes); + + %% WRITE + + hCode = fileread(hPath); + hCode = regexprep(hCode, '\r\n?', '\n'); + cCode = fileread(cPath); + cCode = regexprep(cCode, '\r\n?', '\n'); + + code = editCode.insertSection(hCode, '// INPUT/OUTPUTS PARAMS', headerText.PARAMS); + code = editCode.insertSection(code, '// INPUT/OUTPUTS AUTO-PARAMS', headerText.AUTO_PARAMS); + + fid = fopen(hPath, 'w', 'n', 'UTF-8'); + if fid == -1 + error('Не удалось открыть файл для записи'); + end + fwrite(fid, code); + fclose(fid); + + code = editCode.insertSection(cCode, '// INPUT/OUTPUTS AUTO-PARAMS', cText); + fid = fopen(cPath, 'w', 'n', 'UTF-8'); + if fid == -1 + error('Не удалось открыть файл для записи'); + end + fwrite(fid, code); + fclose(fid); + end + + + function [portwidth, defnames] = getMaskNames(port_prefix) + block = gcb; + % Получаем значение из спиннера + mask = Simulink.Mask.get(block); + paramName = sprintf('%sNumb', port_prefix); + param = mask.getParameter(paramName); + numb = str2double(param.Value); + + + % Инициализируем массив для значений + defnames = strings(1, numb); + portwidth = []; + + % Чтение значений edit-параметров + for i = 1:numb + paramName = sprintf('%s_port_%d_name', port_prefix, i); + param = mask.getParameter(paramName); + defnames(i) = param.Value; + paramName = sprintf('%s_port_%d_width', port_prefix, i); + param = mask.getParameter(paramName); + portwidth(i) = str2double(param.Value); + end + end + + + function updateMask() + mcuPorts.updateMask_prefix('in'); + mcuPorts.updateMask_prefix('out'); + end + + + function defaultUnused() + mcuPorts.defaultUnused_prefix('in'); + mcuPorts.defaultUnused_prefix('out'); + end + end + + methods(Static, Access=private) + + + + function updateMask_prefix(port_prefix) + block = gcb; + % Получаем значение из спиннера + mask = Simulink.Mask.get(block); + paramName = sprintf('%sNumb', port_prefix); + param = mask.getParameter(paramName); + n = str2double(param.Value); + + % Максимальное количество портов + maxPorts = param.Range(2); + + % Проходим по всем edit-полям + for i = 1:maxPorts + % Формируем имя параметра + paramDefName = sprintf('%s_port_%d_name', port_prefix, i); + paramWidthName = sprintf('%s_port_%d_width', port_prefix, i); + paramDef = mask.getParameter(paramDefName); + paramWidth = mask.getParameter(paramWidthName); + + if i <= n + % Показываем параметр + paramDef.Visible = 'on'; + paramWidth.Visible = 'on'; + + % Если значение пустое — задаём дефолтное + if isempty(strtrim(paramDef.Value)) + paramDef.Value = upper(port_prefix); + end + % Если значение пустое — задаём дефолтное + if isempty(strtrim(paramWidth.Value)) || strcmp(paramWidth.Value, '0') + paramWidth.Value = '16'; + end + else + % Скрываем параметр + paramDef.Visible = 'off'; + paramWidth.Visible = 'off'; + end + end + end + + + function defaultUnused_prefix(port_prefix) + block = gcb; + % Получаем значение из спиннера + mask = Simulink.Mask.get(block); + paramName = sprintf('%sNumb', port_prefix); + param = mask.getParameter(paramName); + numb = str2double(param.Value); + + % Максимальное количество портов + maxPorts = param.Range(2); + % Чтение значений edit-параметров + for i = numb+1:maxPorts + paramName = sprintf('%s_port_%d_name', port_prefix, i); + param = mask.getParameter(paramName); + param.Value = upper(port_prefix); + paramName = sprintf('%s_port_%d_width', port_prefix, i); + param = mask.getParameter(paramName); + param.Value = '16'; + end + end + + + function headerText = addPortHeaderDefines(existingText, widths, prefixNumb, portPrefixes) + % existingText — структура с полями PARAMS, AUTO_PARAMS + % widths — вектор ширин портов + % prefixNumb — префикс общего счётчика (например, 'OUT') + % portPrefixes — {'OUT', 'OUT', ...} + + n = numel(widths); + upperPrefix = upper(prefixNumb); + + % === PARAMS === + lines = { + sprintf('#define %s_PORT_NUMB %d', upperPrefix, n) + }; + for i = 1:n + lines{end+1} = sprintf('#define %s_PORT_%d_WIDTH %d', ... + upper(portPrefixes{i}), i, widths(i)); + end + newParams = strjoin(lines, newline); + newParams = [newParams, newline]; + + % === AUTO-PARAMS === + lines = {}; + + % Формируем выражение суммы ширин с добавлением PORT + sumExprParts = cell(1,n); + for i = 1:n + sumExprParts{i} = sprintf('%s_PORT_%d_WIDTH', upper(portPrefixes{i}), i); + end + sumExpr = strjoin(sumExprParts, ' + '); + + lines{end+1} = sprintf('/// === Полный размер буфера ==='); + lines{end+1} = sprintf('#define TOTAL_%s_SIZE (%s)', upperPrefix, sumExpr); + + lines{end+1} = ''; + lines{end+1} = sprintf('/// === Смещения массивов (внутри общего буфера) ==='); + + for i = 1:n + if i == 1 + lines{end+1} = '#define OFFSET_ARRAY_1 0'; + else + lines{end+1} = sprintf('#define OFFSET_ARRAY_%d (OFFSET_ARRAY_%d + %s_PORT_%d_WIDTH)', ... + i, i - 1, upper(portPrefixes{i - 1}), i - 1); + end + end + newAuto = strjoin(lines, newline); + + % === Добавление к существующему === + if isfield(existingText, 'PARAMS') + headerText.PARAMS = [existingText.PARAMS, newline, newParams]; + else + headerText.PARAMS = newParams; + end + + if isfield(existingText, 'AUTO_PARAMS') + headerText.AUTO_PARAMS = [existingText.AUTO_PARAMS, newline, newAuto, newline]; + else + headerText.AUTO_PARAMS = [newAuto, newline]; + end + end + + + function cText = addPortCDefines(existingText, widths, prefixNumb, portPrefixes) + % existingText — существующий текст .c + % widths — вектор ширин портов + % prefixNumb — общий префикс ('OUT') + % portPrefixes — {'OUT', 'LOG', ...} + + n = numel(widths); + upperPrefix = upper(prefixNumb); + lowerPrefix = lower(prefixNumb); + + lines = {}; + + % === Таблица длин === + lines{end+1} = '/**'; + lines{end+1} = sprintf(' * @brief Таблица длин массивов %s', upperPrefix); + lines{end+1} = ' */'; + % Здесь используем общий префикс для количества портов + lines{end+1} = sprintf('const int %sLengths[%s_PORT_NUMB] = {', lowerPrefix, upperPrefix); + for i = 1:n + comma = ','; + if i == n + comma = ''; + end + % Используем макросы с портовыми префиксами из portPrefixes + lines{end+1} = sprintf(' %s_PORT_%d_WIDTH%s', upper(portPrefixes{i}), i, comma); + end + lines{end+1} = '};'; + + % === Таблица смещений === + lines{end+1} = '/**'; + lines{end+1} = sprintf(' * @brief Таблица смещений в выходном массиве %s', upperPrefix); + lines{end+1} = ' */'; + lines{end+1} = sprintf('const int %sOffsets[%s_PORT_NUMB] = {', lowerPrefix, upperPrefix); + for i = 1:n + comma = ','; + if i == n + comma = ''; + end + lines{end+1} = sprintf(' OFFSET_ARRAY_%d%s', i, comma); + end + lines{end+1} = '};'; + lines{end+1} = ''; + + newText = strjoin(lines, newline); + + if nargin < 1 || isempty(existingText) + cText = newText; + else + cText = [existingText, newline, newText]; + end + end + + + + + + function val = iff(cond, a, b) + % Условное выражение inline + if cond + val = a; + else + val = b; + end + end + + + end + +end \ No newline at end of file diff --git a/McuLib/m/mexing.m b/McuLib/m/mexing.m new file mode 100644 index 0000000..e91e644 --- /dev/null +++ b/McuLib/m/mexing.m @@ -0,0 +1,377 @@ +% Компилирует S-function +function res = mexing(compile_mode) + global Ts + Ts = 0.00001; + + if compile_mode == 1 + delete("*.mexw64") + delete("*.mexw64.pdb") + delete(".\MCU_Wrapper\Outputs\*.*"); + set_param(gcb, 'consoleOutput', ''); + % Порты S-Function + mcuPorts.write(); + % Дефайны + definesUserArg = parseDefinesMaskText(); + definesWrapperConfigArg = buildWrapperDefinesString(); + definesPeriphConfigArg = buildConfigDefinesString(); + definesConfigArg = [definesWrapperConfigArg + " " + definesPeriphConfigArg]; + + %режимы компиляции + if mcuMask.read_checkbox('enableDebug') + modeArg = "debug"; + else + modeArg = "release"; + end + if mcuMask.read_checkbox('fullOutput') || mcuMask.read_checkbox('extConsol') + echoArg = 'echo_enable'; + else + echoArg = 'echo_disable'; + end + + [includesArg, codeArg] = make_mex_arguments('incTable', 'srcTable'); + + % Вызов батника с двумя параметрами: includes и code + cmd = sprintf('.\\MCU_Wrapper\\run_mex.bat "%s" "%s" "%s" "%s" %s %s', includesArg, codeArg, definesUserArg, definesConfigArg, modeArg, echoArg); + + if mcuMask.read_checkbox('extConsol') + cmdout = runBatAndShowOutput(cmd); + else + [status, cmdout]= system(cmd); + end + + % Сохраним вывод в параметр маски с именем 'consoleOutput' + mcuMask.disp(1, cmdout); + + block = gcb; + + newName = get_param(block, 'sfuncName'); + oldName = get_param(block, 'FunctionName'); + if ~strcmp(newName, oldName) + set_param(block, 'FunctionName', newName); + end + + newParam = get_param(block, 'sfuncParam'); + oldParam = get_param(block, 'Parameters'); + if ~strcmp(newParam, oldParam) + set_param(block, 'Parameters', newParam); + end + + if status == 0 + res = 0; + else + res = 1; + end + beep + else + blockPath = gcb; + config = periphConfig.read_config(blockPath); + config = periphConfig.update_config(blockPath, config); + periphConfig.write_config(config); + periphConfig.update(blockPath, config); + % Порты S-Function + mcuPorts.write(); + % set_param(gcb, 'consoleOutput', 'Peripheral configuration file loaded. Re-open Block Parameters'); + end +end + +%% COMPILE PARAMS + + +function [includesArg, codeArg] = make_mex_arguments(incTableName, srcTableame) +%MAKE_MEX_ARGUMENTS Формирует строки аргументов для вызова mex-компиляции через батник +% +% [includesArg, codeArg] = make_mex_arguments(includesCell, codeCell) +% +% Вход: +% includesCell — ячейковый массив путей к директориям include +% codeCell — ячейковый массив исходных файлов +% +% Выход: +% includesArg — строка для передачи в батник, например: "-I"inc1" -I"inc2"" +% codeArg — строка с исходниками, например: ""src1.c" "src2.cpp"" + + + % Здесь пример получения из маски текущего блока (замени по своему) + includesCell = customtable.parse(incTableName); + codeCell = customtable.parse(srcTableame); + + % Оборачиваем пути в кавычки и добавляем -I + includesStr = strjoin(cellfun(@(f) ['-I"' f '"'], includesCell, 'UniformOutput', false), ' '); + + % Оборачиваем имена файлов в кавычки + codeStr = strjoin(cellfun(@(f) ['"' f '"'], codeCell, 'UniformOutput', false), ' '); + + % Удаляем символ переноса строки и пробел в конце, если вдруг попал + codeStr = strtrim(codeStr); + includesStr = strtrim(includesStr); + + % Оборачиваем всю строку в кавычки, чтобы батник корректно понял + % includesArg = ['"' includesStr '"']; + % codeArg = ['"' codeStr '"']; + includesArg = includesStr; + codeArg = codeStr; + +end + + +function definesWrapperArg = buildWrapperDefinesString() + + definesWrapperArg = ''; + definesWrapperArg = addDefineByParam(definesWrapperArg, 'enableThreading', 0); + definesWrapperArg = addDefineByParam(definesWrapperArg, 'enableDeinit', 0); + definesWrapperArg = addDefineByParam(definesWrapperArg, 'threadCycles', 1); + definesWrapperArg = addDefineByParam(definesWrapperArg, 'mcuClk', 1); +end + + +function definesUserArg = parseDefinesMaskText() + blockHandle = gcbh; + % Получаем MaskValues и MaskNames + maskValues = get_param(blockHandle, 'MaskValues'); + paramNames = get_param(blockHandle, 'MaskNames'); + + % Индекс параметра userDefs + idxUserDefs = find(strcmp(paramNames, 'userDefs')); + definesText = maskValues{idxUserDefs}; % Текст с пользовательскими определениями + + % Убираем буквальные символы \n и \r + definesText = strrep(definesText, '\n', ' '); + definesText = strrep(definesText, '\r', ' '); + + % Разбиваем по переносам строк + lines = split(definesText, {'\n', '\r\n', '\r'}); + + parts = strings(1,0); % пустой массив строк + + for k = 1:numel(lines) + line = strtrim(lines{k}); + if isempty(line) + continue; + end + + % Разбиваем по пробелам, чтобы получить отдельные определения в строке + tokens = split(line); + + for t = 1:numel(tokens) + token = strtrim(tokens{t}); + if isempty(token) + continue; + end + + eqIdx = strfind(token, '='); + if isempty(eqIdx) + % Просто ключ без значения + parts(end+1) = sprintf('-D"%s"', token); + else + key = strtrim(token(1:eqIdx(1)-1)); + val = strtrim(token(eqIdx(1)+1:end)); + parts(end+1) = sprintf('-D"%s__EQ__%s"', key, val); + end + end + end + + definesUserArg = strjoin(parts, ' '); +end + + + +function definesWrapperArg = buildConfigDefinesString() + blockHandle = gcbh; + mask = Simulink.Mask.get(blockHandle); + + tabName = 'configTabAll'; % Имя вкладки (Prompt) + + tabCtrl = mask.getDialogControl(tabName); + + if isempty(tabCtrl) + error('Вкладка с названием "%s" не найдена в маске', tabName); + end + + + params = mcuMask.collect_all_parameters(tabCtrl); + definesWrapperArg = ''; + for i = 1:numel(params) + % Получаем имя параметра из контрола + paramName = string(params(i)); + try + % Получаем объект параметра по имени + param = mask.getParameter(paramName); + + % Определяем тип параметра + switch lower(param.Type) + case 'checkbox' + definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0); + case 'edit' + definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 1); + otherwise + % Необрабатываемые типы + end + catch ME + % warning('Не удалось получить параметр "%s": %s', paramName, ME.message); + end + end +end + + +%% PARSE FUNCTIONS + +function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_define) + blockHandle = gcbh; + mask = Simulink.Mask.get(blockHandle); + + % Получаем MaskValues, MaskNames + maskValues = get_param(blockHandle, 'MaskValues'); + paramNames = get_param(blockHandle, 'MaskNames'); + param = mask.getParameter(paramName); % для alias + + % Найдём индекс нужного параметра + idxParam = find(strcmp(paramNames, paramName), 1); + if isempty(idxParam) + error('Parameter "%s" not found in block mask parameters.', paramName); + end + + % Берём alias из маски + alias = param.Alias; + + if val_define ~= 0 + % Значение параметра + val = maskValues{idxParam}; + if strcmp(param.Evaluate, 'on') + val = evalin('base', val); % Вычисляем выражение + val = num2str(val); % Преобразуем результат в строку + end + % Формируем define с кавычками и значением + newDefine = ['-D"' alias '__EQ__' val '"']; + else + if mcuMask.read_checkbox(paramName) + % Формируем define с кавычками без значения + newDefine = ['-D"' alias '"']; + else + newDefine = ''; + end + end + + + + % Добавляем новый define к существующему (string) + if isempty(definesWrapperArg) || strlength(strtrim(definesWrapperArg)) == 0 + definesWrapperArg = newDefine; + else + definesWrapperArg = definesWrapperArg + " " + newDefine; + end +end + + +%% CONSOLE FUNCTIONS +function cmdret = runBatAndShowOutput(cmd) + import java.io.*; + import java.lang.*; + cmdEnglish = ['chcp 437 > nul && ' cmd]; + pb = java.lang.ProcessBuilder({'cmd.exe', '/c', cmdEnglish}); + pb.redirectErrorStream(true); + process = pb.start(); + + reader = BufferedReader(InputStreamReader(process.getInputStream())); + + cmdret = ""; % Здесь будем накапливать весь вывод + + while true + if reader.ready() + line = char(reader.readLine()); + if isempty(line) + break; + end + cmdret = cmdret + string(line) + newline; % сохраняем вывод + % Здесь выводим только новую строку + safeLine = strrep(line, '''', ''''''); % Экранируем апострофы + logWindow_append(safeLine); + drawnow; % обновляем GUI + else + if ~process.isAlive() + % дочитываем оставшиеся строки + while reader.ready() + line = char(reader.readLine()); + if isempty(line) + break; + end + cmdret = cmdret + string(line) + newline; % сохраняем вывод + safeLine = strrep(line, '''', ''''''); + logWindow_append(safeLine); + drawnow; + end + break; + end + pause(0.2); + end + end + process.waitFor(); +end + + +function logWindow_append(line) + persistent fig hEdit jScrollPane jTextArea + + if isempty(fig) || ~isvalid(fig) + fig = figure('Name', 'Log Window', 'Position', [100 100 600 400]); + hEdit = uicontrol('Style', 'edit', ... + 'Max', 2, 'Min', 0, ... + 'Enable', 'on', ... + 'FontName', 'Courier New', ... + 'Position', [10 10 580 380], ... + 'HorizontalAlignment', 'left', ... + 'BackgroundColor', 'white', ... + 'Tag', 'LogWindowFigure'); + + jScrollPane = findjobj(hEdit); % JScrollPane + jTextArea = jScrollPane.getViewport.getView; % JTextArea внутри JScrollPane + end + + oldText = get(hEdit, 'String'); + if ischar(oldText) + oldText = {oldText}; + end + + set(hEdit, 'String', [oldText; {line}]); + drawnow; + % Автоскролл вниз: + jTextArea.setCaretPosition(jTextArea.getDocument.getLength); + drawnow; +end + + +%% READ CONFIGS + +function isOpen = isMaskDialogOpen(blockPath) + isOpen = false; + + try + % Получаем имя блока + blockName = get_param(blockPath, 'Name'); + + % Получаем список окон MATLAB GUI + jWindows = java.awt.Window.getWindows(); + + for i = 1:numel(jWindows) + win = jWindows(i); + + % Проверка, что окно видимое и активно + if win.isShowing() + try + title = char(win.getTitle()); + % Проверка по ключевому слову, соответствующему заголовку маски + if contains(title, ['Mask Editor: ' blockName]) || ... + contains(title, ['Mask: ' blockName]) || ... + contains(title, blockName) + isOpen = true; + return; + end + catch + % Окно не имеет заголовка — пропускаем + end + end + end + catch + isOpen = false; + end +end + diff --git a/McuLib/m/periphConfig.m b/McuLib/m/periphConfig.m new file mode 100644 index 0000000..01e3061 --- /dev/null +++ b/McuLib/m/periphConfig.m @@ -0,0 +1,414 @@ +classdef periphConfig + + methods(Static) + function update(blockPath, config) + % blockPath = [blockPath '/MCU']; + + % Проверяем, была ли маска открыта +% wasOpen = isMaskDialogOpen(blockPath); + mask = Simulink.Mask.get(blockPath); + periphPath = get_param(blockPath, 'periphPath'); + [periphPath, ~, ~] = fileparts(periphPath); + + tableNames = {'incTable', 'srcTable'}; + columns_backup = customtable.save_all_tables(tableNames); + + containerName = 'configTabAll'; + periphConfig.clear_all_from_container(mask, containerName); + + % Ищем контейнер, в который будем добавлять вкладки + container = mask.getDialogControl(containerName); + if isempty(container) + error('Контейнер "%s" не найден в маске.', containerName); + end + + if ~isempty(config) + + if isfield(config, 'Code') + res = periphConfig.addCodeConfig(config.Code, periphPath); + if res == 0 + error('Ошибка: неудачное добавление кода периферии. Проверьте корректность файлов и путей в конфигурационном файле') + end + else + error('Ошибка: в конфигурационном файле не задан исходный код для симуляции периферии') + end + + if isfield(config, 'UserCode') + res = periphConfig.addUserCodeConfig(config.UserCode); + if res == 0 + error('Ошибка: неудачное добавление функций для симуляции. Проверьте корректность названий функций в конфигурационном файле') + end + else + error('Ошибка: в конфигурационном файле не заданы функции для симуляции периферии') + end + + % Проходим по каждому модулю (ADC, TIM...) + periphs = fieldnames(config); + for i = 1:numel(periphs) + periph = periphs{i}; + + % Пропускаем Code и UserCode, они уже обработаны + if strcmp(periph, 'Code') || strcmp(periph, 'UserCode') + continue; + end + + defines = config.(periph).Defines; + defNames = fieldnames(defines); + + % Создаём вкладку для модуля + tabCtrl = container.addDialogControl('tab', periph); + tabCtrl.Prompt = [periph ' Config']; + + for j = 1:numel(defNames) + defPrompt = defNames{j}; + def = defines.(defPrompt); + + % Вызов функции добавления одного параметра + periphConfig.addDefineConfig(mask, containerName, periph, defPrompt, def); + end + end + end + % Восстанавливаем таблицы + customtable.restore_all_tables(tableNames, columns_backup); + + % % Повторно открываем маску, если она была открыта + % if wasOpen + % open_system(blockPath, 'mask'); + % end + end + + function config = update_config(blockPath, config) + if isempty(config) + return; + end + + mask = Simulink.Mask.get(blockPath); + maskParams = mask.Parameters; + paramNames = arrayfun(@(p) p.Name, maskParams, 'UniformOutput', false); + + % Обработка остальных секций (с дефайнами) + periphs = fieldnames(config); + for i = 1:numel(periphs) + periph = periphs{i}; + + % Пропускаем Code и UserCode, они уже обработаны + if strcmp(periph, 'Code') || strcmp(periph, 'UserCode') + continue; + end + + % Проверяем есть ли Defines + if ~isfield(config.(periph), 'Defines') + continue; + end + + defines = config.(periph).Defines; + defNames = fieldnames(defines); + + for j = 1:numel(defNames) + defPrompt = defNames{j}; + paramName = matlab.lang.makeValidName(defPrompt); + + % Проверка, существует ли параметр с таким именем + if ismember(paramName, paramNames) + param = mask.getParameter(paramName); + valStr = param.Value; + + % Проверяем, существует ли элемент defPrompt в структуре defines + if isfield(defines, defPrompt) + % Преобразуем строку в соответствующий тип + if strcmpi(defines.(defPrompt).Type, 'checkbox') + config.(periph).Defines.(defPrompt).Default = strcmpi(valStr, 'on'); + elseif strcmpi(defines.(defPrompt).Type, 'edit') + valNum = str2double(valStr); + if isnan(valNum) + config.(periph).Defines.(defPrompt).Default = valStr; + else + config.(periph).Defines.(defPrompt).Default = valNum; + end + end + end + end + end + end + end + + function config = read_config(blockPath) + mask = Simulink.Mask.get(blockPath); + + pathparam = mask.getParameter('periphPath'); + config_path = pathparam.Value; + + if ~isempty(config_path) + jsonText = fileread(config_path); + config = jsondecode(jsonText); + else + config = []; + end + end + + function write_config(config) + if isempty(config) + return + end + + blockHandle = gcbh; + mask = Simulink.Mask.get(blockHandle); + + pathparam = mask.getParameter('periphPath'); + config_path = pathparam.Value; + + jsonText = jsonencode(config, 'PrettyPrint', true); + fid = fopen(config_path, 'w', 'n', 'UTF-8'); + if fid == -1 + error('Не удалось открыть файл periph_config.json для записи.'); + end + fwrite(fid, jsonText, 'char'); + fclose(fid); + end + + function clear_all_from_container(mask, containerName) + % allControls = mask.getDialogControls(); + container = mask.getDialogControl(containerName); + if isempty(container) + warning('Контейнер "%s" не найден.', containerName); + return; + end + + % Рекурсивно собрать все параметры (не вкладки) + paramsToDelete = mcuMask.collect_all_parameters(container); + + % Удаляем все параметры + for i = 1:numel(paramsToDelete) + try + mask.removeParameter(paramsToDelete{i}); + catch + warning('Не удалось удалить параметр %s', paramsToDelete{i}); + end + end + + % Рекурсивно удалить все вкладки внутри контейнера + mcuMask.delete_all_tabs(mask, container); + end + + end + + methods(Static, Access=private) + + function res = addCodeConfig(codeConfig, periphPath) + % Возвращает 0 при успехе, 1 при ошибке + try + % Источники + srcList = {}; + if isfield(codeConfig, 'Sources') && isfield(codeConfig.Sources, 'Options') + srcFiles = codeConfig.Sources.Options; + for i = 1:numel(srcFiles) + fullPath = fullfile(periphPath, srcFiles{i}); + srcList{end+1} = [strrep(fullPath, '\', '\\')]; + end + end + + % Формируем srcText с переносами строк и ^ + srcText = ''; + for i = 1:numel(srcList) + if i < numel(srcList) + srcText = [srcText srcList{i} '^' newline ' ']; + else + srcText = [srcText srcList{i}]; + end + end + + % Инклуды + incList = {}; + if isfield(codeConfig, 'Includes') && isfield(codeConfig.Includes, 'Options') + incPaths = codeConfig.Includes.Options; + for i = 1:numel(incPaths) + fullPath = fullfile(periphPath, incPaths{i}); + incList{end+1} = ['-I"' strrep(fullPath, '\', '\\') '"']; + end + end + + % Формируем incText с переносами строк и ^ + incText = ''; + for i = 1:numel(incList) + if i == 1 && numel(incList) ~= 1 + incText = [incText incList{i} '^' newline]; + elseif i < numel(incList) + incText = [incText ' ' incList{i} '^' newline]; + else + incText = [incText ' ' incList{i}]; + end + end + + % Добавляем префиксы + srcText = ['set code_PERIPH' '=' srcText]; + incText = ['set includes_PERIPH' '=' incText]; + + % Записываем результат + res = periphConfig.updateRunMexBat(srcText, incText); % Всё прошло успешно + catch + % В случае ошибки просто возвращаем 1 + res = 1; + end + end + + + function res = addUserCodeConfig(userCodeConfig) + % userCodeConfig — структура config.UserCode + + initFuncsText = ''; + simFuncsText = ''; + deinitFuncsText = ''; + + if isfield(userCodeConfig, 'Functions') + funcs = userCodeConfig.Functions; + + if isfield(funcs, 'PeriphInit') && isfield(funcs.PeriphInit, 'Options') + initFuncs = funcs.PeriphInit.Options; + initFuncsText = strjoin(strcat('\t', initFuncs, ';'), '\n'); + end + + if isfield(funcs, 'PeriphSimulation') && isfield(funcs.PeriphSimulation, 'Options') + simFuncs = funcs.PeriphSimulation.Options; + simFuncsText = strjoin(strcat('\t', simFuncs, ';'), '\n'); + end + + if isfield(funcs, 'PeriphDeinit') && isfield(funcs.PeriphDeinit, 'Options') + deinitFuncs = funcs.PeriphDeinit.Options; + deinitFuncsText = strjoin(strcat('\t', deinitFuncs, ';'), '\n'); + end + + res = periphConfig.updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText); + end + end + + + function res = updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText) + % Входные параметры: + % srcText - текст для записи set code_... + % incText - текст для записи set includes_... + % + % Возвращает: + % res - 0 при успехе, 1 при ошибке + wrapPath = fullfile('.\MCU_Wrapper', 'mcu_wrapper.c'); + res = 1; + try + code = fileread(wrapPath); + code = regexprep(code, '\r\n?', '\n'); + + % Записываем строки initFuncsText и simFuncsText + code = editCode.insertSection(code, '// PERIPH INIT', initFuncsText); + code = editCode.insertSection(code, '// PERIPH SIM', simFuncsText); + code = editCode.insertSection(code, '// PERIPH DEINIT', deinitFuncsText); + + fid = fopen(wrapPath, 'w', 'n', 'UTF-8'); + if fid == -1 + error('Не удалось открыть файл для записи'); + end + fwrite(fid, code); + fclose(fid); + res = 1; + catch ME + error('Ошибка: неудачная запись в файл при записи файла: %s', ME.message); + end + end + + + + function res = updateRunMexBat(srcText, incText) + % Входные параметры: + % srcText - текст для записи set code_... + % incText - текст для записи set includes_... + % + % Возвращает: + % res - 0 при успехе, 1 при ошибке + periphBat = [srcText '\n\n' incText]; + batPath = fullfile('.\MCU_Wrapper', 'run_mex.bat'); + res = 1; + try + code = fileread(batPath); + code = regexprep(code, '\r\n?', '\n'); + + % Записываем строки srcText и incText с переносами строк + code = editCode.insertSection(code, ':: PERIPH BAT', periphBat); + + fid = fopen(batPath, 'w', 'n', 'UTF-8'); + if fid == -1 + error('Не удалось открыть файл для записи'); + end + fwrite(fid, code); + fclose(fid); + res = 1; + catch ME + mcuMask.disp(0, '\nОшибка: неудачная запись в файл при записи файла: %s', ME.message); + end + end + + + + function addDefineConfig(mask, containerName, periphName, defPrompt, def) + % mask — объект маски Simulink.Mask.get(blockPath) + % containerName — имя контейнера, в который добавляем параметр (например, 'configTabAll') + % periphName — имя вкладки / контейнера для текущего периферийного блока (например, 'ADC') + % defPrompt — имя параметра в Defines (например, 'shift_enable') + % def — структура с описанием параметра (Prompt, Def, Type, Default, NewRow и т.п.) + + % Найдем контейнер с таким именем + container = mask.getDialogControl(containerName); + if isempty(container) + error('Контейнер "%s" не найден в маске.', containerName); + end + + % Проверим, есть ли вкладка с именем periphName, если нет — создадим + tabCtrl = mask.getDialogControl(periphName); + if isempty(tabCtrl) + tabCtrl = container.addDialogControl('tab', periphName); + tabCtrl.Prompt = [periphName ' Config']; + end + + % Определяем тип параметра (checkbox или edit) + switch lower(def.Type) + case 'checkbox' + paramType = 'checkbox'; + case 'edit' + paramType = 'edit'; + otherwise + % Игнорируем остальные типы + return; + end + + paramName = matlab.lang.makeValidName(defPrompt); + + % Преобразуем значение Default в строку для Value + val = def.Default; + if islogical(val) + valStr = mcuMask.ternary(val, 'on', 'off'); + elseif isnumeric(val) + valStr = num2str(val); + elseif ischar(val) + valStr = val; + else + error('Unsupported default value type for %s.%s', periphName, defPrompt); + end + + % Добавляем параметр в маску + param = mask.addParameter( ... + 'Type', paramType, ... + 'Prompt', def.Prompt, ... + 'Name', paramName, ... + 'Value', valStr, ... + 'Container', periphName ... + ); + + param.Alias = def.Def; + param.Evaluate = 'off'; + + if def.NewRow + param.DialogControl.Row = 'new'; + else + param.DialogControl.Row = 'current'; + end + end + + + end +end diff --git a/McuLib/mcuwrapper.prj b/McuLib/mcuwrapper.prj new file mode 100644 index 0000000..e886622 --- /dev/null +++ b/McuLib/mcuwrapper.prj @@ -0,0 +1,123 @@ + + + mcuwrapper + Razvalyaev + wot890089@mail.ru + NIO-12 + Library for run MCU program in Simulink + + + 1.0 + ${PROJECT_ROOT}\mcuwrapper.mltbx + + + + + e7dd2564-e462-4878-b445-45763482263f + + true + + + + + + + + + false + + + findjobj - find java handles of Matlab graphic objects + + + + + + + false + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${PROJECT_ROOT} + + + ${PROJECT_ROOT}\install_my_library.m + ${PROJECT_ROOT}\lib + ${PROJECT_ROOT}\m + ${PROJECT_ROOT}\sl_customization.m + ${PROJECT_ROOT}\slblocks.m + ${PROJECT_ROOT}\startup.m + ${PROJECT_ROOT}\templates + + + + + + E:\.WORK\MATLAB\matlab_23550\McuLib\mcuwrapper.mltbx + + + + C:\Program Files\MyProgs\MATLAB\R2023a + + + + false + false + true + false + false + false + false + false + 10.0 + false + true + win64 + true + + + \ No newline at end of file diff --git a/McuLib/sl_customization.m b/McuLib/sl_customization.m new file mode 100644 index 0000000..028950e --- /dev/null +++ b/McuLib/sl_customization.m @@ -0,0 +1,5 @@ +function sl_customization(cm) + % Добавим путь к коллбекам + addpath(fullfile(fileparts(mfilename('fullpath')), 'm')); + addpath(fullfile(fileparts(mfilename('fullpath')), 'lib')); +end \ No newline at end of file diff --git a/McuLib/slblocks.m b/McuLib/slblocks.m new file mode 100644 index 0000000..a3c0eb9 --- /dev/null +++ b/McuLib/slblocks.m @@ -0,0 +1,5 @@ +function blkStruct = slblocks + blkStruct.Browser.Library = 'McuLib'; + blkStruct.Browser.Name = 'MCU Wrapper'; + blkStruct.Browser.IsFlat = false; +end \ No newline at end of file diff --git a/McuLib/startup.m b/McuLib/startup.m new file mode 100644 index 0000000..640df74 --- /dev/null +++ b/McuLib/startup.m @@ -0,0 +1,2 @@ +% startup.m +install_my_library; diff --git a/McuLib/templates/MCU_Wrapper/mcu_wrapper.c b/McuLib/templates/MCU_Wrapper/mcu_wrapper.c new file mode 100644 index 0000000..16014bc --- /dev/null +++ b/McuLib/templates/MCU_Wrapper/mcu_wrapper.c @@ -0,0 +1,250 @@ +/** +************************************************************************** +* @file mcu_wrapper.c +* @brief Исходный код оболочки МК. +************************************************************************** +@details +Данный файл содержит функции для симуляции МК в Simulink (S-Function). +**************************************************************************/ +#include "mcu_wrapper_conf.h" +#include "app_wrapper.h" + +/** + * @addtogroup WRAPPER_CONF + * @{ + */ + +SIM__MCUHandleTypeDef hmcu; ///< Хендл для управления потоком программы МК + +// INPUT/OUTPUTS AUTO-PARAMS START + +// INPUT/OUTPUTS AUTO-PARAMS END + +/** 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(S); // 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); +#else + app_step(); +#endif //RUN_APP_MAIN_FUNC_THREAD + + MCU_writeOutputs(S); // запись портов (по факту запись в буфер. запись в порты в mdlOutputs) +} + +/* SIMULATE MCU PERIPHERAL */ +/** + * @brief Симуляция периферии МК + * @details Пользовательский код, который симулирует работу периферии МК. + */ +void MCU_Periph_Simulation(SimStruct* S) +{ +// PERIPH SIM START + +// PERIPH SIM END +} + +/* READ INPUTS S-FUNCTION TO MCU REGS */ +/** + * @brief Считывание входов S-Function в порты ввода-вывода. + * @param S - указатель на структуру S-Function из "simstruc.h" + * @details Пользовательский код, который записывает входы МК из входов S-Function. + */ +void MCU_readInputs(SimStruct* S) +{ + SIM_readInputs(S); + /* Get S-Function descrete array (IO buffer) */ + real_T* In_Buff = ssGetDiscStates(S); + app_readInputs(In_Buff); +} + +/* 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 (IO buffer) */ + real_T* Out_Buff = ssGetDiscStates(S); + + app_writeOutputBuffer(Out_Buff); +} +//-----------------CONTROLLER SIMULATE FUNCTIONS---------------// +//-------------------------------------------------------------// + + + +//-------------------------------------------------------------// +//----------------------SIMULINK FUNCTIONS---------------------// +/* MCU WRAPPER DEINITIALIZATION */ +/** + * @brief Инициализация симуляции МК. + * @details Пользовательский код, который создает поток для приложения МК + и настраивает симулятор МК для симуляции. + */ +void SIM_Initialize_Simulation(SimStruct* S) +{ +#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 */ + app_init(); +// PERIPH INIT START + +// PERIPH INIT END + + /* clock step initialization */ + hmcu.sSystemClock_step = MCU_CORE_CLOCK * hmcu.sSimSampleTime; // set system clock step + hmcu.fInitDone = 1; +} +/* MCU WRAPPER DEINITIALIZATION */ +/** + * @brief Деинициализация симуляции МК. + * @details Пользовательский код, который будет очищать все структуры после окончания симуляции. + */ +void SIM_deInitialize_Simulation(SimStruct* S) +{ +#ifdef DEINITIALIZE_AFTER_SIM + // deinitialize app + app_deinit(); +// PERIPH DEINIT START + +// PERIPH DEINIT END +#endif// DEINITIALIZE_AFTER_SIM +} +/* WORK WITH IN/OUT BUFFER OF S-BLOCK */ + +/** + * @brief Функция для записи переменной в буфер выходов в определенный массив + * @param xD - указатель на буфер состояний + * @param value - значение для записи + * @param array_index - индекс выходного массива + * @param value_index - индекс внутри массива + */ +void __WriteOutputArray(real_T* xD, float value, int array_index, int value_index) +{ + if (array_index >= OUT_PORT_NUMB) + return; + + if (value_index >= outLengths[array_index]) + return; + + int global_index = XD_OUTPUT_START + outOffsets[array_index] + value_index; + xD[global_index] = value; +} + +/** + * @brief Функция для чтения значения из буфера входов из определенного массива + * @param xD - указатель на буфер состояний + * @param array_index - индекс входного массива + * @param value_index - индекс внутри массива + * @return - считанное значение или 0.0 при выходе за границы + */ +float __ReadInputArray(const real_T* xD, int array_index, int value_index) +{ + if (array_index >= IN_PORT_NUMB) + return 0.0f; + + if (value_index >= inLengths[array_index]) + return 0.0f; + + int global_index = XD_INPUT_START + inOffsets[array_index] + value_index; + return xD[global_index]; +} + +/** + * @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); + int global_index; + + //-------------WRITTING OUTPUT-------------- + for (int j = 0; j < OUT_PORT_NUMB; j++) + { + Output = ssGetOutputPortRealSignal(S, j); + for (int i = 0; i < outLengths[i]; i++) + { + global_index = XD_OUTPUT_START + outOffsets[j] + i; + Output[i] = Out_Buff[global_index]; + Out_Buff[global_index] = 0; + } + } + //------------------------------------------ +} +/** + * @brief Формирование входов S-Function. + * @param S - указатель на структуру S-Function из "simstruc.h" + * @details Пользовательский код, который считывает входы S-Function в буфер дискретных состояний. + */ +void SIM_readInputs(SimStruct* S) +{ + real_T* Input = ssGetInputPortRealSignal(S, 0); + real_T* In_Buff = ssGetDiscStates(S); + int global_index; + + //-------------READING INPUTS--------------- + for (int j = 0; j < IN_PORT_NUMB; j++) + { + Input = ssGetInputPortRealSignal(S, j); + for (int i = 0; i < inLengths[j]; i++) + { + global_index = XD_INPUT_START + inOffsets[j] + i; + In_Buff[global_index] = Input[i]; + } + } + //------------------------------------------ +} +//-------------------------------------------------------------// diff --git a/McuLib/templates/MCU_Wrapper/mcu_wrapper_conf.h b/McuLib/templates/MCU_Wrapper/mcu_wrapper_conf.h new file mode 100644 index 0000000..a40d8fb --- /dev/null +++ b/McuLib/templates/MCU_Wrapper/mcu_wrapper_conf.h @@ -0,0 +1,219 @@ +/** +************************************************************************** +* @dir ../MCU_Wrapper +* @brief Папка с исходным кодом оболочки МК. +* @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" +- для потоков +**************************************************************************/ +#ifndef _WRAPPER_CONF_H_ +#define _WRAPPER_CONF_H_ + +// Includes +#include "simstruc.h" // For S-Function variables +#include // 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 +// INPUT/OUTPUTS PARAMS START + +// INPUT/OUTPUTS PARAMS END +/** WRAPPER_CONF + * @} + */ + + + +/** + * @addtogroup MCU_WRAPPER + * @{ + */ + +/** @brief Записывает значение в выходной массив блока S-Function + * @param _var_ Значение, которое необходимо записать (будет преобразовано в float) + * @param _arr_ind_ Индекс выходного порта + * @param _val_ind_ Индекс элемента в выходном массиве + */ +#define WriteOutputArray(_var_, _arr_ind_, _val_ind_) __WriteOutputArray(Buffer, (float)_var_, _arr_ind_, _val_ind_) + +/** @brief Считывает значение из входного массива блока S-Function + * @param _var_ Значение, которое необходимо записать (будет преобразовано в float) + * @param _arr_ind_ Индекс входного порта + * @param _val_ind_ Индекс элемента во входном массиве + */ +#define ReadInputArray(_arr_ind_, _val_ind_) __ReadInputArray(Buffer, _arr_ind_, _val_ind_) + + + +// INPUT/OUTPUTS AUTO-PARAMS START + +// INPUT/OUTPUTS AUTO-PARAMS END + +extern const int outLengths[OUT_PORT_NUMB]; +extern const int outOffsets[OUT_PORT_NUMB]; +extern const int inLengths[IN_PORT_NUMB]; +extern const int inOffsets[IN_PORT_NUMB]; +#define TOTAL_XD_SIZE (TOTAL_IN_SIZE + TOTAL_OUT_SIZE) +#define XD_INPUT_START 0 +#define XD_OUTPUT_START (XD_INPUT_START + TOTAL_IN_SIZE) + + + + + +// Fixed parameters(?) of S_Function +#define NPARAMS 1 ///< number of input parametrs (only Ts) +#define DISC_STATES_WIDTH TOTAL_XD_SIZE ///< 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; ///< флаг для выхода из потока программы МК + unsigned fInitDone : 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(SimStruct* S); + +/* Initialize MCU simulation */ +void SIM_Initialize_Simulation(SimStruct* S); + +/* Deinitialize MCU simulation */ +void SIM_deInitialize_Simulation(SimStruct* S); + +/* 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_writeOutputs(SimStruct* S); + +/* Write inputs of block of S-Function*/ +void SIM_readInputs(SimStruct* S); + +/* Set output of block of S-Function*/ +void __WriteOutputArray(real_T* xD, float value, int array_index, int value_index); + +/* Get input of block of S-Function*/ +float __ReadInputArray(const real_T* xD, int array_index, int value_index); +//---------------- SIMULATE FUNCTIONS PROTOTYPES -------------// +//-------------------------------------------------------------// + +/** MCU_WRAPPER + * @} + */ +#endif // _WRAPPER_CONF_H_ + + +//-------------------------------------------------------------// +//---------------------BAT FILE DESCRIBTION--------------------// +/** + * @file run_mex.bat + * @brief Батник для компиляции оболочки МК. + * @details + * Вызывается в матлабе из allmex.m. + * + * Исходный код батника: + * @include run_mex.bat + */ \ No newline at end of file diff --git a/McuLib/templates/MCU_Wrapper/run_mex.bat b/McuLib/templates/MCU_Wrapper/run_mex.bat new file mode 100644 index 0000000..7c6316c --- /dev/null +++ b/McuLib/templates/MCU_Wrapper/run_mex.bat @@ -0,0 +1,115 @@ +@echo off +:: Получаем аргументы из командной строки +:: %1 - includes_USER +:: %2 - code_USER +:: %3 - режим (например, debug) + +:: Аргументы: +:: %1 — includes строка (в кавычках) +:: %2 — sources строка +:: %3 — defines строка +:: %4 — режим компиляции (debug/release) + +:: Сохраняем как переменные +set includes_USER=%~1 +set code_USER=%~2 +set defines_USER=%~3 +set defines_CONFIG=%~4 +set compil_mode=%~5 + +:: Заменяем __EQ__ на = +set defines_USER=%defines_USER:__EQ__==% +set defines_CONFIG=%defines_CONFIG:__EQ__==% + + +set defines_WRAPPER=-D"MATLAB"^ -D"__sizeof_ptr=8" +:: -------------------------USERS PATHS AND CODE--------------------------- +::------------------------------------------------------------------------- + + +:: -------------------------WRAPPER PATHS AND CODE--------------------------- +:: оболочка, которая будет моделировать работу МК в симулинке +set includes_WRAPPER=-I"."^ + -I".\MCU_Wrapper"^ + -I".\app_wrapper" + +set code_WRAPPER= .\MCU_Wrapper\MCU.c^ + .\MCU_Wrapper\mcu_wrapper.c^ + .\app_wrapper\app_init.c^ + .\app_wrapper\app_io.c^ + .\app_wrapper\app_wrapper.c + +:: PERIPH BAT START + +:: PERIPH BAT END +::------------------------------------------------------------------------- + + +:: ---------------------SET PARAMS FOR MEX COMPILING----------------------- +:: -------------ALL------------ +set includes= %includes_WRAPPER% %includes_PERIPH% %includes_USER% +set codes= %code_WRAPPER% %code_PERIPH% %code_USER% +set defines= %defines_WRAPPER% %defines_CONFIG% %defines_USER% +:: -------OUTPUT FOLDER-------- +set output= -outdir "." + +:: если нужен дебаг, до запускаем run_mex с припиской debug +IF [%1]==[debug] (set debug= -g) +::------------------------------------------------------------------------- + + +::------START COMPILING------- +if "%6"=="echo_enable" ( + echo Compiling... + + echo =========================== + echo =========INCLUDES========== + echo USER: + for %%f in (%includes_USER%) do ( + echo %%f + ) + echo INTERNAL: + for %%f in (%includes_WRAPPER%) do ( + echo %%f + ) + echo PERIPH: + for %%f in (%includes_PERIPH%) do ( + echo %%f + ) + + echo =========================== + echo ==========SOURCES========== + echo USER: + for %%f in (%code_USER%) do ( + echo %%f + ) + echo INTERNAL: + for %%f in (%code_WRAPPER%) do ( + echo %%f + ) + echo PERIPH: + for %%f in (%code_PERIPH%) do ( + echo %%f + ) + + echo =========================== + echo ==========DEFINES========== + echo USER: + for %%d in (%defines_USER%) do ( + echo %%d + ) + echo CONFIG: + for %%f in (%defines_CONFIG%) do ( + echo %%f + ) + echo INTERNAL: + for %%f in (%defines_WRAPPER%) do ( + echo %%f + ) +) +echo =========================== +echo MODE: %compil_mode% +echo =========================== +mex %output% %defines% %includes% %codes% %debug% +echo %DATE% %TIME% +exit /b %ERRORLEVEL% \ No newline at end of file diff --git a/McuLib/templates/app_wrapper/app_configs.h b/McuLib/templates/app_wrapper/app_configs.h new file mode 100644 index 0000000..5665abe --- /dev/null +++ b/McuLib/templates/app_wrapper/app_configs.h @@ -0,0 +1,10 @@ +/** +************************************************************************** +* @file app_config.h +* @brief . +**************************************************************************/ +#ifndef _APP_CONFIG +#define _APP_CONFIG + + +#endif //_APP_CONFIG diff --git a/McuLib/templates/app_wrapper/app_includes.h b/McuLib/templates/app_wrapper/app_includes.h new file mode 100644 index 0000000..1d729f1 --- /dev/null +++ b/McuLib/templates/app_wrapper/app_includes.h @@ -0,0 +1,15 @@ +/** +************************************************************************** +* @file app_includes.h +* @brief Заголовочный файл для подключаения заголовочных файлов программы МК. +**************************************************************************/ +#ifndef _APP_INCLUDES_H_ +#define _APP_INCLUDES_H_ + +#include "app_configs.h" + +// INCLUDES START +// Инклюды для доступа к коду МК в коде оболочке +// INCLUDES END + +#endif //_APP_INCLUDES_H_ \ No newline at end of file diff --git a/McuLib/templates/app_wrapper/app_init.c b/McuLib/templates/app_wrapper/app_init.c new file mode 100644 index 0000000..2509cf7 --- /dev/null +++ b/McuLib/templates/app_wrapper/app_init.c @@ -0,0 +1,33 @@ +/** +************************************************************************** +* @file app_init.h +* @brief Файл с функцией инициализации программы МК @ref app_init. +**************************************************************************/ +#include "mcu_wrapper_conf.h" +#include "app_wrapper.h" + +/** + * @brief Функция для инициализации приложения МК + * @details Используется в случае симуляции без отдельного потока для main(). + */ +void app_init(void) { +// USER APP INIT START +// Код для инициализации приложения МК +// +// Вызов разных функций в случае, +// если не используется отдельный поток для main(). +// USER APP INIT END +} + + +/** + * @brief Функция для деинициализации приложения МК + */ +void app_deinit(void) { +// USER APP DEINIT START +// Код для деинициализации приложения МК +// +// Структуры, переменные и так далее, которые надо очистить, +// для повторного запуска симуляции. +// USER APP DEINIT END +} \ No newline at end of file diff --git a/McuLib/templates/app_wrapper/app_io.c b/McuLib/templates/app_wrapper/app_io.c new file mode 100644 index 0000000..40c7a6f --- /dev/null +++ b/McuLib/templates/app_wrapper/app_io.c @@ -0,0 +1,50 @@ +/** +************************************************************************** +* @file app_init.h +* @brief Файл с функциями записи входов/выходов программы МК @ref app_init. +**************************************************************************/ +#include "mcu_wrapper_conf.h" +#include "app_wrapper.h" + +/** + * @brief Функция для записи входов в приложение МК + * @param u - массив входных значений + */ +void app_readInputs(const real_T* Buffer) { +// USER APP INPUT START +// Код для записи считывания входов из IO буфера +// Буфер в начале хранит входные порты S-Function, далее идут выходные порты: +// Buffer[0:15] - входной 1 порт, Buffer[16:31] - входной 2 порт, +// Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт +// +// Note: используте для чтения: +// val = ReadInputArray(arr_ind, val_ind) +// Пример: +// // запись в второй элемент первого массива +// app_variable = ReadInputArray(0, 1); +// // чтение из IO буфера напрямую +// app_variable_2 = Buffer[10]; +// USER APP INPUT END +} + +/** + * @brief Функция для записи выходов приложения МК + * @param xD - массив буффера выходов(дискретных выходов) + * @details Используте WriteOutputArray(val, arr_ind, val_ind) для записи + */ +void app_writeOutputBuffer(real_T* Buffer) { +// USER APP OUTPUT START +// Код для записи выходов в IO буфер +// Буфер в начале хранит входные порты S-Function, далее идут выходные порты: +// Buffer[0:15] - входной 1 порт, Buffer[16:31] - входной 2 порт, +// Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт +// +// Note: используте для записи: +// WriteOutputArray(val, arr_ind, val_ind) +// Пример: +// // запись в второй элемент первого массива +// WriteOutputArray(app_variable, 0, 1); +// // запись в IO буфер напрямую +// Buffer[XD_OUTPUT_START + 10] = app_variable_2; +// USER APP OUTPUT END +} \ No newline at end of file diff --git a/McuLib/templates/app_wrapper/app_wrapper.c b/McuLib/templates/app_wrapper/app_wrapper.c new file mode 100644 index 0000000..add68e4 --- /dev/null +++ b/McuLib/templates/app_wrapper/app_wrapper.c @@ -0,0 +1,22 @@ +#include "mcu_wrapper_conf.h" +#include "app_wrapper.h" + + +/** + * @brief Функция для симуляции шага приложения МК + * @details Используется в случае симуляции без отдельного потока для main(). + */ +void app_step(void) +{ +// USER APP STEP START +// Код приложения МК для вызова в шаге симуляции +// +// Вызов разных функций на шаге симуляции в случае, +// если не используется отдельный поток для main(). +// USER APP STEP END +} + + +// DUMMY START +// Заглушки для различных функций и переменных +// DUMMY END \ No newline at end of file diff --git a/McuLib/templates/app_wrapper/app_wrapper.h b/McuLib/templates/app_wrapper/app_wrapper.h new file mode 100644 index 0000000..7a8022a --- /dev/null +++ b/McuLib/templates/app_wrapper/app_wrapper.h @@ -0,0 +1,12 @@ +#ifndef _APP_WRAPPER_H_ +#define _APP_WRAPPER_H_ + +#include "app_includes.h" + +void app_step(void); +void app_init(void); +void app_deinit(void); +void app_readInputs(const real_T* u); +void app_writeOutputBuffer(real_T* xD); + +#endif //_APP_WRAPPER_H_ diff --git a/mcuwrapper.mltbx b/mcuwrapper.mltbx new file mode 100644 index 0000000..584d945 Binary files /dev/null and b/mcuwrapper.mltbx differ