diff --git a/MCU Wrapper.mltbx b/MCU Wrapper.mltbx index 72aabd7..646b774 100644 Binary files a/MCU Wrapper.mltbx and b/MCU Wrapper.mltbx differ diff --git a/McuLib/lib/McuLib.slx b/McuLib/lib/McuLib.slx index 7c4a359..a091a4c 100644 Binary files a/McuLib/lib/McuLib.slx and b/McuLib/lib/McuLib.slx differ diff --git a/McuLib/m/appWrap.m b/McuLib/m/appWrap.m new file mode 100644 index 0000000..cfb7a94 --- /dev/null +++ b/McuLib/m/appWrap.m @@ -0,0 +1,154 @@ +classdef appWrap + + methods(Static) + function appWrapperFunc() + block = gcb; + % Получаем имя функции и путь к файлам + [filename, section, tool, example]= appWrap.getAppWrapperUserFile(block); + mcuMask.tool(tool, example); + + % Загружаем содержимое файла + set_param(block, 'appWrapperCode', ''); + try + code = fileread(filename); + code = regexprep(code, '\r\n?', '\n'); % нормализуем окончания строк + + includesText = editCode.extractSection(code, section); + set_param(block, 'appWrapperCode', includesText); + catch + end + % % Поиск тела обычной функции + % expr = sprintf('void %s()', sel); + % funcBody = editCode.extractSection(code, expr); + % set_param(block, 'wrapperCode', funcBody); + end + + function saveAppWrapperCode() + block = gcb; + + % Получаем имя функции и путь к файлам + [filename, section] = appWrap.getAppWrapperUserFile(block); + if ~isfile(filename) + errordlg(['Файл не найден: ', filename]); + return; + end + + sel = get_param(block, 'appWrapperFunc'); + basePath = get_param(block, 'appWrapperPath'); + if isempty(basePath) + errordlg('Не указан путь к файлам обёртки (wrapperPath).'); + return; + end + newBody = get_param(block, 'appWrapperCode'); + code = fileread(filename); + code = regexprep(code, '\r\n?', '\n'); + newBody = strrep(newBody, '\', '\\'); + 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 openAppWrapperCode() + block = gcb; + + % Получаем имя функции и путь к файлам + filename = mcuPath.getAbsolutePath(appWrap.getAppWrapperUserFile(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 + + +%% SPECIFIC TOOLS + function [filename, section, tool, example] = getAppWrapperUserFile(block, sel) + if (nargin < 2) + sel = get_param(block, 'appWrapperFunc'); + end + + basePath = mcuPath.get('appWrapperPath'); + 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 +end \ No newline at end of file diff --git a/McuLib/m/asynchManage.m b/McuLib/m/asynchManage.m index 591139d..71f1834 100644 --- a/McuLib/m/asynchManage.m +++ b/McuLib/m/asynchManage.m @@ -39,7 +39,7 @@ classdef asynchManage < handle methods (Access = private) function saveCallback(obj) try - mcuMask.saveAndClose(obj.maskBlockPath); + mcuMask.close(obj.maskBlockPath); save_system(obj.modelName); catch ME warning('progr:Nneg', 'Ошибка при сохранении модели: %s', ME.message); @@ -79,7 +79,7 @@ classdef asynchManage < handle function GUIconfigCallback(obj) try - mcuMask.saveAndClose(obj.maskBlockPath); + mcuMask.close(obj.maskBlockPath); mexing(0); catch ME warning('progr:Nneg', 'Ошибка при обновлении модели: %s', ME.message); diff --git a/McuLib/m/compiler.m b/McuLib/m/compiler.m new file mode 100644 index 0000000..2a0f39f --- /dev/null +++ b/McuLib/m/compiler.m @@ -0,0 +1,140 @@ +classdef compiler + methods(Static) + + function compile() + addpath(mcuPath.get('wrapperPath')); + mexing(1); + end + + function get_availbe() + addpath(mcuPath.get('wrapperPath')); + mexing(1); + end + + function choose() + addpath(mcuPath.get('wrapperPath')); + mexing(1); + end + + + + function updateRunBat() + sources = { + 'MCU.c' + 'mcu_wrapper.c' + }; + % Список заголовочных файлов (.h) + includes = { '.\' + }; + wrapperPath = mcuPath.get('wrapperPath'); + % [wrapperPath, ~, ~] = fileparts(wrapperPath); + % Формируем строки + wrapperSrcText = compiler.createSourcesBat('code_WRAPPER', sources, wrapperPath); + wrapperIncText = compiler.createIncludesBat('includes_WRAPPER', includes, wrapperPath); + + % Записываем результат + res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: WRAPPER BAT'); % Всё прошло успешно + + if res == 0 + return + end + + sources = { + 'app_wrapper.c' + 'app_init.c' + 'app_io.c' + }; + % Список заголовочных файлов (.h) + includes = { '.\' + }; + periphPath = mcuPath.get('appWrapperPath'); + % Формируем строки + wrapperSrcText = compiler.createSourcesBat('code_APP_WRAPPER', sources, periphPath); + wrapperIncText = compiler.createIncludesBat('includes_APP_WRAPPER', includes, periphPath); + + % Записываем результат + res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: APP WRAPPER BAT'); % Всё прошло успешно + + periphConfig.updatePeriphRunMexBat(); + end + + + function res = updateRunMexBat(srcText, incText, Section) + % Входные параметры: + % srcText - текст для записи set code_... + % incText - текст для записи set includes_... + % + % Возвращает: + % res - 0 при успехе, 1 при ошибке + periphBat = [srcText '\n\n' incText]; + batPath = fullfile(mcuPath.get('wrapperPath'), 'run_mex.bat'); + res = 1; + try + code = fileread(batPath); + code = regexprep(code, '\r\n?', '\n'); + + % Записываем строки srcText и incText с переносами строк + code = editCode.insertSection(code, Section, 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 srcText = createSourcesBat(prefix_name, sources, path) + srcList = {}; + if nargin >= 2 && iscell(sources) + for i = 1:numel(sources) + fullPath = fullfile(path, sources{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 + + % Добавляем префикс + srcText = ['set ' prefix_name '=' srcText]; + end + + function incText = createIncludesBat(prefix_name, includes, path) + incList = {}; + if nargin >= 2 && iscell(includes) + for i = 1:numel(includes) + fullPath = fullfile(path, includes{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 + + % Добавляем префикс + incText = ['set ' prefix_name '=' incText]; + end + + end +end \ No newline at end of file diff --git a/McuLib/m/configJs.m b/McuLib/m/configJs.m new file mode 100644 index 0000000..f2cde98 --- /dev/null +++ b/McuLib/m/configJs.m @@ -0,0 +1,143 @@ +classdef configJs + + methods(Static) + + function config = update(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}; + + % Проверяем есть ли 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(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) + 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 value = get_field(configStruct, targetConfig) + % получить targetConfig структуру из конфига (для глубоко вложенных) + value = []; + fields = fieldnames(configStruct); + + for i = 1:numel(fields) + key = fields{i}; + if strcmp(key, targetConfig) + value = configStruct.(key); + return; % нашли и возвращаем + elseif isstruct(configStruct.(key)) + value = configJs.get_field(configStruct.(key), targetConfig); + if ~isempty(value) + return; % нашли во вложенной структуре + end + end + end + + % Если не нашли, можно выбросить ошибку или вернуть пустое + if isempty(value) + % error('Поле "%s" не найдено в структуре.', targetConfig); + end + end + + function short = get_final_name_from_prefix(prefix) + % Берёт последнее имя после "_" (читаемое имя) + parts = strsplit(prefix, '_'); + short = parts{end}; + end + + function value = convert_code_value(codeField) + % Преобразует значение поля Options в строку + if ischar(codeField) + value = codeField; + elseif isstring(codeField) + value = char(codeField); + elseif iscell(codeField) + value = strjoin(codeField, newline); + else + % warning('Неподдерживаемый тип данных: сохранено как пустая строка'); + value = ''; + end + end + + + end + + + + methods(Static, Access=private) + + end +end diff --git a/McuLib/m/customtable.m b/McuLib/m/customtable.m index 43ab261..7915168 100644 --- a/McuLib/m/customtable.m +++ b/McuLib/m/customtable.m @@ -8,13 +8,15 @@ classdef customtable tableControl = mask.getDialogControl(table_name); tableParameter = mask.getParameter(table_name); nCols = tableControl.getNumberOfColumns; - % if nCols > 0 - % for i = 1:nCols - % tableControl.removeColumn(1); - % end - % end - % column = tableControl.addColumn(Name='Title', Type='edit'); - % tableControl.Sortable = 'on'; + % инициализация колонок если они пустые + % такое случается при removeParameter + if isempty(tableControl.Columns) || (nCols > 1) + for i = 1:nCols + tableControl.removeColumn(1); + end + column = tableControl.addColumn(Name='Title', Type='edit'); + tableControl.Sortable = 'on'; + end column.Name = tableParameter.Alias; end diff --git a/McuLib/m/mainConfig.m b/McuLib/m/mainConfig.m new file mode 100644 index 0000000..a588212 --- /dev/null +++ b/McuLib/m/mainConfig.m @@ -0,0 +1,274 @@ +classdef mainConfig + + methods(Static) + + function config = export() + blockPath = gcb; + mask = Simulink.Mask.get(blockPath); + wrapParamToExport = {'wrapperPath', 'enableDebug', 'mcuClk', ... + 'threadCycles', 'enableThreading', 'enableDeinit'}; + portParamToExport = {'inNumb', ... + 'in_port_1_name', 'in_port_1_width', ... + 'in_port_2_name', 'in_port_2_width', ... + 'in_port_3_name', 'in_port_3_width', ... + 'in_port_4_name', 'in_port_4_width', ... + 'in_port_5_name', 'in_port_5_width', ... + 'outNumb', ... + 'out_port_1_name', 'out_port_1_width', ... + 'out_port_2_name', 'out_port_2_width', ... + 'out_port_3_name', 'out_port_3_width', ... + 'out_port_4_name', 'out_port_4_width', ... + 'out_port_5_name', 'out_port_5_width',}; + appParamToExport = {'appWrapperPath', 'srcTable', 'incTable', 'userDefs'}; + + config = struct(); + for i = 1:numel(wrapParamToExport) + paramName = wrapParamToExport{i}; + def = mainConfig.exportParamToConfig(mask, paramName); + config.(paramName) = def; + end + + for i = 1:numel(portParamToExport) + paramName = portParamToExport{i}; + def = mainConfig.exportParamToConfig(mask, paramName); + config.(paramName) = def; + end + + for i = 1:numel(appParamToExport) + paramName = appParamToExport{i}; + def = mainConfig.exportParamToConfig(mask, paramName); + config.(paramName) = def; + end + + + + jsonStr = jsonencode(config, 'PrettyPrint', true); % 'PrettyPrint' для форматирования + + + % Диалог сохранения файла + [file, path] = uiputfile('*.json', 'Сохранить конфигурацию как', 'WrapperConfig.json'); + if isequal(file, 0) || isequal(path, 0) + disp('Сохранение отменено пользователем.'); + return; + end + + filepath = fullfile(path, file); + + + + % Сохраняем в файл + fid = fopen(filepath, 'w'); + if fid == -1 + mcuMask.disp(0, 'Не удалось открыть файл для записи: %s', filename); + end + + fwrite(fid, jsonStr, 'char'); + fclose(fid); + end + + + function import() + % Получаем путь к текущему блоку + blockPath = gcb; + mask = Simulink.Mask.get(blockPath); + + % Выбор JSON-файла через диалоговое окно + [file, path] = uigetfile('*.json', 'Выберите файл конфигурации'); + if isequal(file, 0) + mcuMask.disp(0, 'Импорт отменён пользователем.'); + return; + end + + fullpath = fullfile(path, file); + + % Чтение и декодирование JSON + try + jsonStr = fileread(fullpath); + config = jsondecode(jsonStr); + catch err + mcuMask.disp(0, 'Ошибка при чтении или разборе JSON: %s', err.message); + end + + % Применение параметров из конфигурации + paramNames = fieldnames(config); + for i = 1:numel(paramNames) + paramName = paramNames{i}; + def = config.(paramName); + + try + mainConfig.applyDefToMask(mask, paramName, def); + catch err + mcuMask.disp(0, 'Ошибка при применении параметра "%s": %s', paramName, err.message); + end + end + + mcuMask.disp(0, 'Конфигурация успешно импортирована.'); + end + + end + + + methods(Static, Access=private) + + + function def = exportParamToConfig(mask, paramName) + % mask — объект Simulink.Mask.get(blockPath) + % paramName — имя параметра (как в mask.Parameters.Name) + + param = mask.getParameter(paramName); + if isempty(param) + mcuMask.disp(0, 'Параметр "%s" не найден в маске.', paramName); + def = []; + return; + end + + def = struct(); + + % Prompt + def.Prompt = param.Prompt; + + % Тип параметра + def.Type = param.Type; + + % Значение по умолчанию + val = param.Value; + switch lower(param.Type) + case 'checkbox' + def.Default = strcmp(val, 'on'); + case {'edit', 'spinbox'} + num = str2double(val); + if ~isnan(num) + def.Default = num; + else + def.Default = val; + end + case 'customtable' + def.Default = customtable.parse(param.Name); % или можно попытаться распарсить значение позже + case 'text' + def.Default = ''; % или можно попытаться распарсить значение позже + otherwise + def.Default = val; + end + + % Alias, если есть + if ~isempty(param.Alias) + def.Def = param.Alias; + end + + % % Row (new/current) + % try + % rowSetting = param.DialogControl.Row; + % def.NewRow = strcmp(rowSetting, 'new'); + % catch + % def.NewRow = false; + % end + end + + + function applyDefToMask(mask, paramName, def) + % mask — объект Simulink.Mask.get(blockPath) + % paramName — имя параметра маски + % def — структура, полученная из extractDefFromMask + + % Получаем параметр + param = mask.getParameter(paramName); + if isempty(param) + mcuMask.disp(0, 'Параметр "%s" не найден в маске.', paramName); + return; + end + + if isempty(def) + mcuMask.disp(0, 'Параметр "%s" не найден в конфиге.', paramName); + return; + end + + switch lower(def.Type) + case 'checkbox' + % Логическое значение true/false + if islogical(def.Default) + param.Value = mainConfig.ternary(def.Default, 'on', 'off'); + else + mcuMask.disp(0, 'Ожидалось логическое значение для checkbox "%s".', paramName); + end + + case {'edit', 'spinbox'} + % Строка или число + if isnumeric(def.Default) + param.Value = num2str(def.Default); + elseif ischar(def.Default) || isstring(def.Default) + param.Value = char(def.Default); + else + mcuMask.disp(0, 'Некорректный формат значения для edit "%s".', paramName); + end + + case 'customtable' + % Массив строк + if iscell(def.Default) + customtable.collect(paramName, def.Default); + else + mcuMask.disp(0, 'customtable "%s" требует cell-массив строк.', paramName); + end + + case 'text' + % Просто текстовая строка + if ischar(def.Default) || isstring(def.Default) + param.Value = char(def.Default); + else + mcuMask.disp(0, 'text-параметр "%s" должен быть строкой.', paramName); + end + + case 'popup' + % popup — установить значение, если оно есть в списке + if ischar(def.Default) && isfield(def, 'Def') && any(strcmp(def.Default, def.Def)) + param.Value = def.Default; + else + mcuMask.disp(0, 'popup-параметр "%s" имеет неверное значение или список.', paramName); + end + + otherwise + % По умолчанию просто устанавливаем строковое значение + if ischar(def.Default) || isstring(def.Default) + param.Value = char(def.Default); + elseif isnumeric(def.Default) + param.Value = num2str(def.Default); + else + mcuMask.disp(0, 'Неизвестный формат значения параметра "%s".', paramName); + end + end + + % Применение Prompt, если нужно + if isfield(def, 'Prompt') + param.Prompt = def.Prompt; + end + + % Применение Alias, если есть + if isfield(def, 'Def') && ischar(def.Def) + param.Alias = def.Def; + end + + % % Установка Row, если поддерживается + % if isfield(def, 'NewRow') + % try + % if def.NewRow + % param.DialogControl.Row = 'new'; + % else + % param.DialogControl.Row = 'current'; + % end + % catch + % % Некоторым типам параметров Row может быть неприменим + % end + % end + end + + + function result = ternary(cond, a, b) + if cond + result = a; + else + result = b; + end + end + + end + +end \ No newline at end of file diff --git a/McuLib/m/mainWrap.m b/McuLib/m/mainWrap.m new file mode 100644 index 0000000..dcc3c17 --- /dev/null +++ b/McuLib/m/mainWrap.m @@ -0,0 +1,72 @@ +classdef mainWrap + + methods(Static) + function enableThreading() + 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() + 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() + 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 + + end + + +%% SPECIFIC TOOLS + methods(Static, Access = private) + + end + +end \ No newline at end of file diff --git a/McuLib/m/mcuMask.m b/McuLib/m/mcuMask.m index da7bd16..0947dcf 100644 --- a/McuLib/m/mcuMask.m +++ b/McuLib/m/mcuMask.m @@ -1,5 +1,5 @@ classdef mcuMask - +%% CALLBACKS methods(Static) % Following properties of 'maskInitContext' are avalaible to use: % - BlockHandle @@ -8,10 +8,10 @@ classdef mcuMask function MaskInitialization(maskInitContext) % Получаем хэндл текущего блока blk = gcbh; - set_param(blk,"MaskSelfModifiable","on") - set_param(blk, 'LinkStatus', 'none'); % Получаем объект маски текущего блока mask = Simulink.Mask.get(gcb); + set_param(blk,"MaskSelfModifiable","on") + set_param(blk, 'LinkStatus', 'none'); % mcuMask.disp(1,''); try % Проверка наличия findjobj @@ -19,8 +19,6 @@ classdef mcuMask catch findjobjAvailable = false; end - % Получаем объект маски текущего блока - mask = Simulink.Mask.get(gcb); % Имя checkbox-параметра (укажите точное имя из маски) checkboxParamName = 'extConsol'; % пример findjobjLinkName = 'findjobj_link'; % пример @@ -60,156 +58,37 @@ classdef mcuMask end %% WRAPPER PARAMS + function wrapperPath_add(callbackContext) + mcuPath.addPath('wrapperPath'); + end + 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); + mainWrap.enableThreading(); 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); + mainWrap.enableDeinit(); 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 - + mainWrap.extConsol(); 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', ''); - try - code = fileread(filename); - code = regexprep(code, '\r\n?', '\n'); % нормализуем окончания строк - - includesText = editCode.extractSection(code, section); - set_param(block, 'wrapperCode', includesText); - catch - end - % % Поиск тела обычной функции - % 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'); - newBody = strrep(newBody, '\', '\\'); - 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]); + function appWrapperPath_add(callbackContext) + mcuPath.addPath('appWrapperPath'); end - function openWrapperCode(callbackContext) - block = gcb; + function appWrapperFunc(callbackContext) + appWrap.appWrapperFunc(); + end + + function saveAppWrapperCode(callbackContext) + appWrap.saveAppWrapperCode(); + end - % Получаем имя функции и путь к файлам - 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 + function openAppWrapperCode(callbackContext) + appWrap.openAppWrapperCode(); end %% USER CODE @@ -222,106 +101,37 @@ classdef mcuMask 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); - + mcuPath.addSourceFileTable('srcTable', 'Выберите исходные файлы'); 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); + mcuPath.addPathTable('incTable', 'Выберите папку с заголовочными файлами'); 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; + mcuPath.addAnyFile('periphPath'); end - function compile(callbackContext) - addpath('MCU_Wrapper'); - mexing(1); - end - - - function updateModel(callbackContext) - addpath('MCU_Wrapper'); - res = mexing(1); - if res ~= 0 - return; - end - + function periphUpdate(callbackContext) modelName = bdroot(gcb); % получить имя верхнего уровня модели blockName = gcb; mgr = asynchManage(modelName, blockName); % создать объект класса - mgr.saveAndUpdateModel(); % запустить сохранение и обновление + mgr.updateGUIfromConfig(); % запустить сохранение и обновление end - - function findjobj_link(callbackContext) - web('https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects'); + %% COMPILE + function compile(callbackContext) + compiler.compile(); end - function set_name() + function setSFuncName(callbackContext) block = gcb; % Получаем параметр имени S-Function из маски блока newName = mcuMask.get_name(); % Путь к файлу, в котором надо заменить строку - cFilePath = fullfile(pwd, './MCU_Wrapper/MCU.c'); % <-- укажи правильный путь + cFilePath = fullfile(pwd, mcuPath.get('wrapperPath'), 'MCU.c'); % <-- укажи правильный путь % Считаем файл в память try @@ -349,93 +159,29 @@ classdef mcuMask fclose(fid); end - function name = get_name() - block = gcb; - % Получаем параметр имени S-Function из маски блока - name = get_param(block, 'sfuncName'); + %% LINK TO EXTERNAL CONSOLE + function findjobj_link(callbackContext) + web https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects; 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) + function updateModel() + addpath(mcuPath.get('wrapperPath')); + res = mexing(1); + if res ~= 0 + return; + end + + % modelName = bdroot(gcb); % получить имя верхнего уровня модели + % blockName = gcb; + % mgr = asynchManage(modelName, blockName); % создать объект класса + % mgr.saveAndUpdateModel(); % запустить сохранение и обновление + end + + function close(blockPath) try % Считываем текущее имя модели modelName = bdroot(blockPath); @@ -459,81 +205,13 @@ classdef mcuMask 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 + function name = get_name() + block = gcb; + % Получаем параметр имени S-Function из маски блока + name = get_param(block, 'sfuncName'); 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'); @@ -593,15 +271,7 @@ classdef mcuMask end end - function res = ternary(cond, valTrue, valFalse) - if cond - res = valTrue; - else - res = valFalse; - end - end - - + function tool(text, example) % Устанавливает заданный текст в параметр Text Area 'toolText' через объект маски @@ -630,9 +300,13 @@ classdef mcuMask out = sprintf(varargin{:}); end - out_now = get_param(gcb, 'consoleOutput'); + out_now = get_param(gcb, 'consoleOutput'); + if ~strcmp(out, '') && ~strcmp(out_now, '') + set_param(gcb, 'consoleOutput', [out_now newline out]); + else set_param(gcb, 'consoleOutput', [out_now out]); end + end function updateModelAsync() diff --git a/McuLib/m/mcuPath.m b/McuLib/m/mcuPath.m new file mode 100644 index 0000000..fefc8b8 --- /dev/null +++ b/McuLib/m/mcuPath.m @@ -0,0 +1,168 @@ +classdef mcuPath + methods(Static) + + %% GET PATH FROM PARAM + function path = get(paramName) + blockPath = gcb; + path = get_param(blockPath, paramName); + end + + %% ADD PATH TO TABLE + + function addSourceFileTable(targetParamName, message) + % Открываем проводник для выбора файлов + [files, pathstr] = uigetfile({ ... + '*.c;*.cpp', 'Исходные файлы (*.c, *.cpp)'; ... + '*.obj;*.lib', 'Библиотеки (*.obj, *.lib)'; ... + '*.*', 'Все файлы (*.*)'}, ... + message, ... + 'MultiSelect', 'on'); + + if isequal(files, 0) + return; % Отмена выбора + end + if ischar(files) + files = {files}; % Один файл — в cell + end + % Парсим строку в cell-массив + oldTable = customtable.parse(targetParamName); + + % Добавляем новые пути, проверяя уникальность + for i = 1:numel(files) + fullpath = fullfile(pathstr, files{i}); + rel = mcuPath.absoluteToRelativePath(fullpath); + if ~any(strcmp(rel, oldTable)) + oldTable{end+1, 1} = rel; + end + end + + % Парсим строку в cell-массив + customtable.collect(targetParamName, oldTable); + + end + + function addPathTable(targetParamName, message) + % Открываем проводник для выбора папок + pathstr = uigetdir(pwd, message); + if isequal(pathstr, 0) + return; % Отмена выбора + end + % Парсим таблицу + oldTable = customtable.parse(targetParamName); + + rel = mcuPath.absoluteToRelativePath(pathstr); + + % Проверяем наличие пути + if ~any(strcmp(rel, oldTable)) + oldTable{end+1, 1} = rel; + end + + % Собираем таблицу + customtable.collect(targetParamName, oldTable); + end + + %% ADD PATH TO EDIT + + function addPath(targetParamName, message) + block = gcb; + mask = Simulink.Mask.get(block); + % Открываем окно выбора папки + folderPath = uigetdir('', 'Выберите папку'); + % Проверка на отмену + if isequal(folderPath, 0) + return; + end + % Установка значения параметра маски + rel = mcuPath.absoluteToRelativePath(folderPath); + param = mask.getParameter(targetParamName); + param.Value = rel; + end + + function addAnyFile(targetParamName, message) + block = gcbh; + mask = Simulink.Mask.get(block); + [file, path] = uigetfile({'*.*','Все файлы (*.*)'}, 'Выберите файл'); + if isequal(file, 0) || isequal(path, 0) + % Отмена выбора — ничего не делаем + return; + end + fullFilePath = fullfile(path, file); + rel = mcuPath.absoluteToRelativePath(fullFilePath); + param = mask.getParameter(targetParamName); + param.Value = rel; + end + + %% GET PATH STRING + + 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 + + end +end \ No newline at end of file diff --git a/McuLib/m/mcuPorts.m b/McuLib/m/mcuPorts.m index 4135698..76770b7 100644 --- a/McuLib/m/mcuPorts.m +++ b/McuLib/m/mcuPorts.m @@ -5,8 +5,8 @@ classdef mcuPorts function write() block = gcb; mask = Simulink.Mask.get(block); - hPath = fullfile('.\MCU_Wrapper', 'mcu_wrapper_conf.h'); - cPath = fullfile('.\MCU_Wrapper', 'mcu_wrapper.c'); + hPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper_conf.h'); + cPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper.c'); mcuPorts.defaultUnused(); %% CREATE prefixNumb = 'IN'; diff --git a/McuLib/m/mexing.m b/McuLib/m/mexing.m index 5880b4a..1b42f89 100644 --- a/McuLib/m/mexing.m +++ b/McuLib/m/mexing.m @@ -4,10 +4,11 @@ function res = mexing(compile_mode) Ts = 0.00001; if compile_mode == 1 - delete("*.mexw64") - delete("*.mexw64.pdb") - delete(".\MCU_Wrapper\Outputs\*.*"); + delete('*.mexw64') + delete('*.mexw64.pdb') + delete([mcuPath.get('wrapperPath'), '\Outputs\*.*']); set_param(gcb, 'consoleOutput', ''); + compiler.updateRunBat(); % Дефайны definesUserArg = parseDefinesMaskText(); definesWrapperConfigArg = buildWrapperDefinesString(); @@ -31,7 +32,8 @@ function res = mexing(compile_mode) Name = mcuMask.get_name(); % Вызов батника с двумя параметрами: includes и code - cmd = sprintf('.\\MCU_Wrapper\\run_mex.bat %s "%s" "%s" "%s" "%s" %s %s', Name, includesArg, codeArg, definesUserArg, definesConfigArg, modeArg, echoArg); + run_bat_mex_path = fullfile(mcuPath.get('wrapperPath'), 'run_mex.bat'); + cmd = sprintf('%s %s "%s" "%s" "%s" "%s" %s %s', run_bat_mex_path, Name, includesArg, codeArg, definesUserArg, definesConfigArg, modeArg, echoArg); if mcuMask.read_checkbox('extConsol') cmdout = runBatAndShowOutput(cmd); @@ -64,10 +66,10 @@ function res = mexing(compile_mode) beep else blockPath = gcb; - config = periphConfig.read_config(blockPath); - config = periphConfig.update_config(blockPath, config); - periphConfig.write_config(config); - periphConfig.update(blockPath, config); + config = configJs.read(blockPath); + config = configJs.update(blockPath, config); + configJs.write(config); + periphConfig.updateMask(blockPath, config); end end @@ -200,6 +202,8 @@ function definesWrapperArg = buildConfigDefinesString() definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0); case 'edit' definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 1); + case 'popup' + definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0); otherwise % Необрабатываемые типы end @@ -228,7 +232,21 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_ end % Берём alias из маски - alias = param.Alias; + val = ''; + if ~strcmp(param.Type, 'popup') + def_name = param.Alias; + else + if strcmp(param.Alias, '') + def_name = param.Value; + else + def_name = param.Alias; + val = param.Value; + end + end + + if strcmp(def_name, '') + return; + end if val_define ~= 0 % Значение параметра @@ -238,14 +256,16 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_ val = num2str(val); % Преобразуем результат в строку end % Формируем define с кавычками и значением - newDefine = ['-D"' alias '__EQ__' val '"']; - else + newDefine = ['-D"' def_name '__EQ__' val '"']; + elseif ~strcmp(param.Type, 'popup') if mcuMask.read_checkbox(paramName) % Формируем define с кавычками без значения - newDefine = ['-D"' alias '"']; + newDefine = ['-D"' def_name '"']; else newDefine = ''; end + else + newDefine = ['-D"' def_name '__EQ__' val '"']; end diff --git a/McuLib/m/periphConfig.m b/McuLib/m/periphConfig.m index 16bd25c..ee6d26e 100644 --- a/McuLib/m/periphConfig.m +++ b/McuLib/m/periphConfig.m @@ -1,171 +1,424 @@ classdef periphConfig methods(Static) - function update(blockPath, config) + function updateMask(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) + try + rowWidth = str2double(get_param(blockPath, 'rowWidth')); - if isfield(config, 'Code') - res = periphConfig.addCodeConfig(config.Code, periphPath); - if res == 0 - error('Ошибка: неудачное добавление кода периферии. Проверьте корректность файлов и путей в конфигурационном файле') - end - else - error('Ошибка: в конфигурационном файле не задан исходный код для симуляции периферии') + containerName = 'configTabAll'; + periphConfig.clear_all_from_container(mask, containerName); + + % Ищем контейнер, в который будем добавлять вкладки + container = mask.getDialogControl(containerName); + if isempty(container) + error('Контейнер "%s" не найден в маске.', containerName); end + + if ~isempty(config) + % Проходим по каждому модулю (ADC, TIM...) + periphs = fieldnames(config); + for i = 1:numel(periphs) + periph = periphs{i}; + + % Сохраняем код, если он есть + periphConfig.store_single_periph_code(mask, periph, config.(periph)); + + % Проверяем наличие Defines + if ~isfield(config.(periph), 'Defines') + continue; + end + + defines = config.(periph).Defines; + defNames = fieldnames(defines); - 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); + % Создаём вкладку для модуля + tabCtrl = container.addDialogControl('tab', periph); + tabCtrl.Prompt = [periph ' Config']; + + rowCountMap = containers.Map(); + for j = 1:numel(defNames) + defPrompt = defNames{j}; + def = defines.(defPrompt); + + % Вызов функции добавления одного параметра + periphConfig.addConfig(mask, containerName, periph, defPrompt, def, rowCountMap, rowWidth); + end end end + periphConfig.create_all_code_storage_params(blockPath, config); + % periphConfig.cleanup_obsolete_code_params(blockPath, config); + + periphConfig.update(); + + % Восстанавлиperiph = allTabNamesваем таблицы + customtable.restore_all_tables(tableNames, columns_backup); + catch + % Восстанавливаем таблицы + customtable.restore_all_tables(tableNames, columns_backup); 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) + function update() + blockPath = gcb; + mask = Simulink.Mask.get(blockPath); + + config = configJs.read(blockPath); + containerName = 'configTabAll'; + container = mask.getDialogControl(containerName); + paramsAll = mcuMask.collect_all_parameters(container); + % Получаем все имена в кладок из container (у вас должен быть container) + allTabs = container.DialogControls; + allTabNames = arrayfun(@(t) t.Name, allTabs, 'UniformOutput', false); + + fieldsConfig = fieldnames(config); + % Цикл по всем вкладкам в контейнере + for i = 1:length(allTabNames) + periph = fieldsConfig{i}; + + % Попытка найти параметр чекбокса Tab__Enable + checkboxName = ['Tab_' periph '_Enable']; + if ismember(checkboxName, paramsAll) + % Чекбокс есть - проверяем его состояние + paramObj = mask.getParameter(checkboxName); + val = paramObj.Value; + + try + tab = container.getDialogControl(periph); + if strcmpi(val, 'off') + tab.Enabled = 'off'; + % Рекурсивно очищаем связанные скрытые параметры + periphConfig.clear_tab_params(mask, config.(periph), periph); + else + tab.Enabled = 'on'; + periphConfig.sync_tab_params(mask, config.(periph), periph); + end + catch + warning('Вкладка с именем "%s" не найдена.', periph); + end + + else + % Чекбокса нет — просто пытаемся включить вкладку, если она есть + try + tab = container.getDialogControl(periph); + tab.Enabled = 'on'; + % Опционально можно синхронизировать параметры, если есть config поле + if isfield(config, periph) + periphConfig.sync_tab_params(mask, config.(periph), periph); + end + catch + % Можно не выводить предупреждение — вкладка может быть необязательной + % warning('Вкладка с именем "%s" не найдена.', periph); + end + end + end + + end + + function periphParamCallback(paramName) + blockPath = gcb; + mask = Simulink.Mask.get(blockPath); + hObj = mask.getParameter(paramName); + config = configJs.read(blockPath); + container = mask.getDialogControl('configTabAll'); + + % === Проверка на Tab__Enable === + exprTab = '^Tab_(\w+)_Enable$'; + tokensTab = regexp(paramName, exprTab, 'tokens'); + + if ~isempty(tokensTab) + periph = tokensTab{1}{1}; + + try + tab = container.getDialogControl(periph); + paramVal = hObj.Value; + + if strcmpi(paramVal, 'off') + tab.Enabled = 'off'; + if isfield(config, periph) + periphConfig.clear_tab_params(mask, config.(periph), periph); + end + else + tab.Enabled = 'on'; + if isfield(config, periph) + periphConfig.sync_tab_params(mask, config.(periph), periph); + end + end + catch + warning('Ошибка обработки вкладки "%s".', periph); + end return; end - mask = Simulink.Mask.get(blockPath); - maskParams = mask.Parameters; - paramNames = arrayfun(@(p) p.Name, maskParams, 'UniformOutput', false); + % === Проверка на параметр, связанный с Sources/Includes === + % Проверка: это параметр, у которого есть соответствующий Hidden__Sources или Hidden__Includes + nameBase = paramName; + + paramNames = string({mask.Parameters.Name}); + hasSources = any(paramNames == "Hidden_" + nameBase + "_Sources"); + hasIncludes = any(paramNames == "Hidden_" + nameBase + "_Includes"); + + + if hasSources || hasIncludes + useVal = hObj.Value; + + % Получаем содержимое config по nameBase — возможно, вложенное + try + valueStruct = configJs.get_field(config, nameBase); + catch + warning('Не удалось найти путь %s в config.', nameBase); + return; + end + + if strcmpi(useVal, 'on') + try + periphConfig.store_single_periph_code(mask, nameBase, valueStruct); + catch + warning('Не удалось сохранить параметры для %s.', nameBase); + end + else + periphConfig.clear_single_periph_code_param(mask, nameBase); + end + + return; + end - % Обработка остальных секций (с дефайнами) - periphs = fieldnames(config); - for i = 1:numel(periphs) - periph = periphs{i}; - % Пропускаем Code и UserCode, они уже обработаны - if strcmp(periph, 'Code') || strcmp(periph, 'UserCode') - continue; - end + % === Если не подошло ни под одно из условий === + % warning('Объект "%s" не поддерживается универсальным коллбеком.', paramName); + end + + function updatePeriphRunMexBat() + % Запись run_mex.bat + blockPath = gcb; + CodeStruct = periphConfig.restore_periph_code_from_mask(blockPath); + periphPath = mcuPath.get('periphPath'); + [periphPath, ~, ~] = fileparts(periphPath); + + periphConfig.addCodeBat(CodeStruct, periphPath); + end - % Проверяем есть ли Defines - if ~isfield(config.(periph), 'Defines') - continue; - end + end + + + + methods(Static, Access=private) + + - defines = config.(periph).Defines; - defNames = fieldnames(defines); + function addHiddenParam(mask, containerName, nameBase, kind, existingParams) + % Преобразуем к красивому имени + prettyName = strrep(nameBase, '_', ' '); + paramName = ['Hidden_' char(nameBase) '_' kind]; + if ismember(paramName, existingParams) + return; + end - for j = 1:numel(defNames) - defPrompt = defNames{j}; - paramName = matlab.lang.makeValidName(defPrompt); + mask.addParameter( ... + 'Name', paramName, ... + 'Type', 'edit', ... + 'Prompt', ['Hidden ' prettyName ' ' kind], ... + 'Value', '', ... + 'Visible', 'off', ... + 'Container', containerName ... + ); + fprintf('Создан скрытый параметр: %s\n', paramName); + end + + function clear_tab_params(mask, configStruct, prefix, depth) + if nargin < 4 + depth = 0; + end + maxDepth = 3; % Максимальная глубина рекурсии + + fields = fieldnames(configStruct); - % Проверка, существует ли параметр с таким именем - if ismember(paramName, paramNames) - param = mask.getParameter(paramName); - valStr = param.Value; + for i = 1:numel(fields) + key = fields{i}; + value = configStruct.(key); + paramName = [prefix '_' key]; - % Проверяем, существует ли элемент 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 + if isstruct(value) + if depth < maxDepth + % Рекурсивный вызов для вложенных структур с увеличением глубины + periphConfig.clear_tab_params(mask, value, paramName, depth + 1); + end + else + if strcmp(key, 'Sources') || strcmp(key, 'Includes') + baseName = configJs.get_final_name_from_prefix(prefix); + periphConfig.clear_single_periph_code_param(mask, baseName); end end end end - - function config = read_config(blockPath) + + + function sync_tab_params(mask, configStruct, prefix, depth) + if nargin < 4 + depth = 0; + end + maxDepth = 3; % Максимальная глубина рекурсии + + fields = fieldnames(configStruct); + + for i = 1:numel(fields) + key = fields{i}; + value = configStruct.(key); + paramName = [prefix '_' key]; + + if isstruct(value) + if depth < maxDepth + % Рекурсивный вызов для вложенных структур с увеличением глубины + periphConfig.sync_tab_params(mask, value, paramName, depth + 1); + end + else + if strcmp(key, 'Sources') || strcmp(key, 'Includes') + baseName = configJs.get_final_name_from_prefix(prefix); + periphConfig.store_single_periph_code(mask, baseName, configStruct); + end + end + end + end + + function create_all_code_storage_params(blockPath, config) mask = Simulink.Mask.get(blockPath); - pathparam = mask.getParameter('periphPath'); - config_path = pathparam.Value; + containerName = 'configTabAll'; + container = mask.getDialogControl(containerName); + existingParams = mcuMask.collect_all_parameters(container); - if ~isempty(config_path) - jsonText = fileread(config_path); - config = jsondecode(jsonText); + % Убедимся, что контейнер существует + tabName = 'hiddenCodeTab'; + tab = mask.getDialogControl(tabName); + if isempty(tab) + tab = container.addDialogControl('tab', tabName); + tab.Prompt = 'Hidden Code Settings'; + tab.Visible = 'off'; else - config = []; + tab.Visible = 'off'; + end + + % Запуск рекурсивного обхода + periphConfig.process_struct_recursive(mask, tabName, config, {}, existingParams); + end + + function process_struct_recursive(mask, tabName, currentStruct, nameStack, existingParams) + fields = fieldnames(currentStruct); + for i = 1:numel(fields) + fieldName = fields{i}; + value = currentStruct.(fieldName); + newStack = [nameStack, fieldName]; % Добавляем уровень к имени + + % Если value — структура, обходим дальше + if isstruct(value) + % Проверяем: это структура с Sources/Includes или просто промежуточный узел? + hasSources = isfield(value, 'Sources'); + hasIncludes = isfield(value, 'Includes'); + + if hasSources + periphConfig.addHiddenParam(mask, tabName, fieldName, 'Sources', existingParams); + end + if hasIncludes + periphConfig.addHiddenParam(mask, tabName, fieldName, 'Includes', existingParams); + end + + % Рекурсивно продолжаем обход + periphConfig.process_struct_recursive(mask, tabName, value, newStack, existingParams); + end end end - function write_config(config) - if isempty(config) - return - end - blockHandle = gcbh; - mask = Simulink.Mask.get(blockHandle); + function cleanup_obsolete_code_params(blockPath, config) + mask = Simulink.Mask.get(blockPath); + maskParams = mask.Parameters; - pathparam = mask.getParameter('periphPath'); - config_path = pathparam.Value; + % Получаем список актуальных периферий + validPeriphs = fieldnames(config); - jsonText = jsonencode(config, 'PrettyPrint', true); - fid = fopen(config_path, 'w', 'n', 'UTF-8'); - if fid == -1 - error('Не удалось открыть файл periph_config.json для записи.'); + for i = 1:numel(maskParams) + paramName = maskParams(i).Name; + + % Проверяем, является ли параметром хранения Sources или Includes + expr = '^Hidden_(\w+)_(Sources|Includes)$'; + tokens = regexp(paramName, expr, 'tokens'); + if ~isempty(tokens) + periph = tokens{1}{1}; + + % Если периферии больше нет – удаляем параметр + if ~ismember(periph, validPeriphs) + mask.removeParameter(paramName); + fprintf('Удалён устаревший параметр: %s\n', paramName); + end + end end - fwrite(fid, jsonText, 'char'); - fclose(fid); end - + + function codeStruct = restore_periph_code_from_mask(blockPath) + mask = Simulink.Mask.get(blockPath); + maskParams = mask.Parameters; + + allSources = {}; + allIncludes = {}; + + for i = 1:numel(maskParams) + name = maskParams(i).Name; + + % Ищем параметры Sources + tokensSrc = regexp(name, '^Hidden_(\w+)_Sources$', 'tokens'); + if ~isempty(tokensSrc) + val = maskParams(i).Value; + + if ischar(val) || isstring(val) + valStr = strtrim(char(val)); + % Пропускаем пустые строки и '[]' + if isempty(valStr) || strcmp(valStr, '[]') + continue; + end + + lines = splitlines(valStr); + lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines)); + allSources = [allSources; lines]; %#ok + end + continue; + end + + % Ищем параметры Includes + tokensInc = regexp(name, '^Hidden_(\w+)_Includes$', 'tokens'); + if ~isempty(tokensInc) + val = maskParams(i).Value; + + if ischar(val) || isstring(val) + valStr = strtrim(char(val)); + if isempty(valStr) || strcmp(valStr, '[]') + continue; + end + + lines = splitlines(valStr); + lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines)); + allIncludes = [allIncludes; lines]; %#ok + end + continue; + end + end + + codeStruct = struct(); + codeStruct.Sources = allSources; + codeStruct.Includes = allIncludes; + end + + function clear_all_from_container(mask, containerName) % allControls = mask.getDialogControls(); container = mask.getDialogControl(containerName); @@ -190,69 +443,25 @@ classdef periphConfig mcuMask.delete_all_tabs(mask, container); end - end - - methods(Static, Access=private) - function res = addCodeConfig(codeConfig, periphPath) + function res = addCodeBat(codeConfig, codePath) + % Добавить сурсы и пути в батник % Возвращает 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]; + % Формируем строки + srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources, codePath); + incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes, codePath); % Записываем результат - res = periphConfig.updateRunMexBat(srcText, incText); % Всё прошло успешно + res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT'); % Всё прошло успешно catch % В случае ошибки просто возвращаем 1 res = 1; end end - - function res = addUserCodeConfig(userCodeConfig) + function res = addUserFunctions(userCodeConfig) + % Добавить функции и дефайны в исходный код wrapper % userCodeConfig — структура config.UserCode initFuncsText = ''; @@ -262,34 +471,33 @@ classdef periphConfig if isfield(userCodeConfig, 'Functions') funcs = userCodeConfig.Functions; - if isfield(funcs, 'PeriphInit') && isfield(funcs.PeriphInit, 'Options') - initFuncs = funcs.PeriphInit.Options; + if isfield(funcs, 'PeriphInit') + initFuncs = funcs.PeriphInit; initFuncsText = strjoin(strcat('\t', initFuncs, ';'), '\n'); end - if isfield(funcs, 'PeriphSimulation') && isfield(funcs.PeriphSimulation, 'Options') - simFuncs = funcs.PeriphSimulation.Options; + if isfield(funcs, 'PeriphSimulation') + simFuncs = funcs.PeriphSimulation; simFuncsText = strjoin(strcat('\t', simFuncs, ';'), '\n'); end - if isfield(funcs, 'PeriphDeinit') && isfield(funcs.PeriphDeinit, 'Options') - deinitFuncs = funcs.PeriphDeinit.Options; + if isfield(funcs, 'PeriphDeinit') + deinitFuncs = funcs.PeriphDeinit; deinitFuncsText = strjoin(strcat('\t', deinitFuncs, ';'), '\n'); end - res = periphConfig.updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText); + res = periphConfig.writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText); end end - - function res = updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText) + function res = writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText) % Входные параметры: % srcText - текст для записи set code_... % incText - текст для записи set includes_... % % Возвращает: % res - 0 при успехе, 1 при ошибке - wrapPath = fullfile('.\MCU_Wrapper', 'mcu_wrapper.c'); + wrapPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper.c'); res = 1; try code = fileread(wrapPath); @@ -311,47 +519,27 @@ classdef periphConfig 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) + + function addConfig(mask, containerName, periphName, defPrompt, def, rowCountMap, rowWidth) % mask — объект маски Simulink.Mask.get(blockPath) % containerName — имя контейнера, в который добавляем параметр (например, 'configTabAll') % periphName — имя вкладки / контейнера для текущего периферийного блока (например, 'ADC') % defPrompt — имя параметра в Defines (например, 'shift_enable') % def — структура с описанием параметра (Prompt, Def, Type, Default, NewRow и т.п.) + if ~isKey(rowCountMap, periphName) + rowCountMap(periphName) = 0; + end + rowCount = rowCountMap(periphName) + 1; + + % Устанавливаем NewRow, если он не задан + if ~isfield(def, 'NewRow') + def.NewRow = mod(rowCount - 1, rowWidth) == 0; + elseif def.NewRow == true + rowCount = 1; + end + rowCountMap(periphName) = rowCount; + % Найдем контейнер с таким именем container = mask.getDialogControl(containerName); if isempty(container) @@ -371,6 +559,8 @@ classdef periphConfig paramType = 'checkbox'; case 'edit' paramType = 'edit'; + case 'popup' + paramType = 'popup'; otherwise % Игнорируем остальные типы return; @@ -378,26 +568,48 @@ classdef periphConfig 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; + % Получаем значение + if strcmp(paramType, 'popup') + if isfield(def, 'Def') && iscell(def.Def) && ~isempty(def.Def) + choices = def.Def; + valStr = ''; % по умолчанию — ничего + elseif isfield(def, 'Options') + choices = def.Def; + valStr = ''; % по умолчанию — ничего + else + warning('Popout параметр "%s" не содержит допустимого списка в Def.', defPrompt); + return; + end else - error('Unsupported default value type for %s.%s', periphName, defPrompt); + val = def.Default; + if islogical(val) + valStr = periphConfig.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 end % Добавляем параметр в маску - param = mask.addParameter( ... - 'Type', paramType, ... - 'Prompt', def.Prompt, ... - 'Name', paramName, ... - 'Value', valStr, ... - 'Container', periphName ... - ); + if strcmp(paramType, 'popup') + param = mask.addParameter( ... + 'Type', paramType, ... + 'Prompt', def.Prompt, ... + 'Name', paramName, ... + 'Container', periphName ... + ); + else + param = mask.addParameter( ... + 'Type', paramType, ... + 'Prompt', def.Prompt, ... + 'Name', paramName, ... + 'Value', valStr, ... + 'Container', periphName ... + ); + end param.Evaluate = 'off'; @@ -406,9 +618,80 @@ classdef periphConfig else param.DialogControl.Row = 'current'; end - param.Alias = def.Def; + + if isfield(def, 'Def') + if strcmp(paramType, 'popup') + if iscell(def.Def) + param.TypeOptions = def.Def; + elseif isfield(def, 'Options') + param.Alias = def.Def; + param.TypeOptions = def.Options; + end + else + param.Alias = def.Def; + end + end + + callback = sprintf('periphConfig.periphParamCallback("%s");', paramName); + param.Callback = callback; end + + %% ELEMENTARY + + function clear_single_periph_code_param(mask, periph) + % Очистка кода одного поля конфига + paramNames = { + ['Hidden_' char(periph) '_Sources'], + ['Hidden_' char(periph) '_Includes'] + }; + + for i = 1:numel(paramNames) + paramName = paramNames{i}; + try + param = mask.getParameter(paramName); + param.Value = ''; + catch + % Параметр не существует — ничего не делаем + end + end + end + + function store_single_periph_code(mask, periph, code) + % Запись кода одного поля конфига + % Сохраняем Sources, если они есть + if isfield(code, 'Sources') + paramName = ['Hidden_' char(periph) '_Sources']; + try + param = mask.getParameter(paramName); + param.Value = configJs.convert_code_value(code.Sources); + catch + mcuMask.disp(0, ['Параметр ' paramName ' не найден']); + end + end + + % Сохраняем Includes, если они есть + if isfield(code, 'Includes') + paramName = ['Hidden_' char(periph) '_Includes']; + try + param = mask.getParameter(paramName); + param.Value = configJs.convert_code_value(code.Includes); + catch + mcuMask.disp(0, ['Параметр ' paramName ' не найден']); + end + end + end + + + + function res = ternary(cond, valTrue, valFalse) + if cond + res = valTrue; + else + res = valFalse; + end + end + end end diff --git a/McuLib/templates/MCU_Wrapper/run_mex.bat b/McuLib/templates/MCU_Wrapper/run_mex.bat index 5dcd00f..a635b0f 100644 --- a/McuLib/templates/MCU_Wrapper/run_mex.bat +++ b/McuLib/templates/MCU_Wrapper/run_mex.bat @@ -30,15 +30,17 @@ set defines_WRAPPER=-D"MATLAB"^ -D"__sizeof_ptr=8" :: -------------------------WRAPPER PATHS AND CODE--------------------------- :: оболочка, которая будет моделировать работу МК в симулинке -set includes_WRAPPER=-I"."^ - -I".\MCU_Wrapper"^ - -I".\app_wrapper" +:: WRAPPER BAT START + +:: WRAPPER BAT END + +:: APP WRAPPER BAT START + +:: APP WRAPPER BAT END + +set includes_WRAPPER= %includes_WRAPPER% %includes_APP_WRAPPER% +set code_WRAPPER= %code_WRAPPER% %code_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 diff --git a/mcuwrapper.prj b/mcuwrapper.prj index 9b198c6..f556f14 100644 --- a/mcuwrapper.prj +++ b/mcuwrapper.prj @@ -1,5 +1,5 @@ - + MCU Wrapper Razvalyaev wot890089@mail.ru @@ -7,7 +7,7 @@ Library for run MCU program in Simulink - 1.01 + 1.02 ${PROJECT_ROOT}\MCU Wrapper.mltbx @@ -27,7 +27,7 @@ false - findjobj - find java handles of Matlab graphic objects + findjobj - find java handles of Matlab graphic objects @@ -96,12 +96,49 @@ - E:\.WORK\MATLAB\mcu_matlab\MCU Wrapper.mltbx + F:\Work\Projects\MATLAB\mcu_matlab_lib\MCU Wrapper.mltbx - C:\Program Files\MyProgs\MATLAB\R2023a - + C:\Program Files\MATLAB\R2023a + + + + + + + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + false