% Компилирует S-function function 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', ''); % Дефайны definesWrapperArg = buildWrapperDefinesString(); definesUserArg = parseDefinesMaskText(); definesConfigArg = buildConfigDefinesString(); definesAllArg = [definesUserArg + " " + definesWrapperArg + " " + definesConfigArg]; %режимы компиляции if read_checkbox('enableDebug') modeArg = "debug"; else modeArg = "release"; end if read_checkbox('fullOutput') || 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', includesArg, codeArg, definesAllArg, modeArg, echoArg); if read_checkbox('extConsol') cmdout = runBatAndShowOutput(cmd); else [status, cmdout]= system(cmd); end % Сохраним вывод в параметр маски с именем 'consoleOutput' set_param(gcb, 'consoleOutput', cmdout); beep else blockPath = bdroot; config = read_periph_config(); config = update_config_from_mask(blockPath, config); write_periph_config(config); update_mask_from_config(blockPath, config); % 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"" % Здесь пример получения из маски текущего блока (замени по своему) blockHandle = gcbh; % или замени на нужный блок includesCell = parseCellString(get_param(blockHandle, incTableName)); codeCell = parseCellString(get_param(blockHandle, 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) allControls = mask.getDialogControls(); tabCtrl = find_container_by_name(allControls, tabName); if isempty(tabCtrl) error('Вкладка с названием "%s" не найдена в маске', tabName); end params = 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 out = parseCellString(str) str = strtrim(str); 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 str = cellArrayToString(cellArray) quoted = cellfun(@(s) ['''' s ''''], cellArray, 'UniformOutput', false); str = ['{' strjoin(quoted, ';') '}']; end 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}; % Формируем define с кавычками и значением newDefine = ['-D"' alias '__EQ__' val '"']; else if 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 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 %% 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 config = read_periph_config() blockHandle = gcbh; mask = Simulink.Mask.get(blockHandle); pathparam = mask.getParameter('periphPath'); config_path = pathparam.Value; jsonText = fileread(config_path); config = jsondecode(jsonText); end function write_periph_config(config) 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'); if fid == -1 error('Не удалось открыть файл periph_config.json для записи.'); end fwrite(fid, jsonText, 'char'); fclose(fid); end %% CONFIG MASK TOOLS function update_mask_from_config(blockPath, config) blockPath = [blockPath '/MCU']; % Проверяем, была ли маска открыта wasOpen = isMaskDialogOpen(blockPath); close_system(blockPath, 0); mask = Simulink.Mask.get(blockPath); tableNames = {'incTable', 'srcTable'}; columns_backup = clear_tables(blockPath, tableNames); containerName = 'configTabAll'; clear_all_from_container(mask, containerName); % Ищем контейнер, в который будем добавлять вкладки allControls = mask.getDialogControls(); container = find_container_by_name(allControls, containerName); if isempty(container) error('Контейнер "%s" не найден в маске.', containerName); end % Проходим по каждому модулю (ADC, TIM...) periphs = fieldnames(config); for i = 1:numel(periphs) periph = periphs{i}; 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); prompt = def.Prompt; % Только checkbox и edit switch lower(def.Type) case 'checkbox' paramType = 'checkbox'; case 'edit' paramType = 'edit'; otherwise continue; end paramName = matlab.lang.makeValidName(defPrompt); % Преобразуем значение по типу val = def.Default; if islogical(val) if val valStr = 'on'; else valStr = 'off'; end elseif isnumeric(val) valStr = num2str(val); elseif ischar(val) valStr = val; else error('Unsupported default value type for %s.%s', periph, defPrompt); end % Добавляем параметр в соответствующую вкладку param = mask.addParameter( ... 'Type', paramType, ... 'Prompt', prompt, ... 'Name', paramName, ... 'Value', valStr, ... 'Container', periph ... ); param.Alias = def.Def; if def.NewRow row_param = 'new'; else row_param = 'current'; end param.DialogControl.Row = row_param; end end % Восстанавливаем таблицы restore_tables(blockPath, tableNames, columns_backup); % Повторно открываем маску, если она была открыта if wasOpen open_system(blockPath, 'mask'); end end function config = update_config_from_mask(blockPath, config) blockPath = [blockPath '/MCU']; 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 = 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 clear_all_from_container(mask, containerName) allControls = mask.getDialogControls(); container = find_container_by_name(allControls, containerName); if isempty(container) warning('Контейнер "%s" не найден.', containerName); return; end % Рекурсивно собрать все параметры (не вкладки) paramsToDelete = collect_all_parameters(container); % Удаляем все параметры for i = 1:numel(paramsToDelete) try mask.removeParameter(paramsToDelete{i}); catch warning('Не удалось удалить параметр %s', paramsToDelete{i}); end end % Рекурсивно удалить все вкладки внутри контейнера delete_all_tabs(mask, container); 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, 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') % Сначала рекурсивно удаляем вкладки внутри текущей вкладки delete_all_tabs(mask, ctrl); try container.removeDialogControl(ctrl.Name); catch ME warning('Не удалось удалить вкладку %s: %s', ctrl.Name, ME.message); end end end end 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 function column_titles = clear_tables(block, table_names) % Очищает столбцы в каждой таблице из массива имен table_names % Возвращает cell-массив с названиями первых столбцов каждой таблицы % Получить объект маски блока 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_tables(block, table_names, column_titles) % Восстанавливает первый столбец в каждой таблице из массива имен % Использует массив column_titles для установки имени столбца % Получить объект маски блока 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 tab = find_tab_by_name(controls, targetName) tab = []; for i = 1:numel(controls) ctrl = controls(i); % Проверяем, вкладка ли это и совпадает ли имя if isa(ctrl, 'Simulink.dialog.Tab') && strcmp(ctrl.Name, targetName) tab = ctrl; return; end % Если это контейнер — обходим его детей children = get_children(ctrl); if ~isempty(children) tab = find_tab_by_name(children, targetName); if ~isempty(tab) return; end end end end function container = find_container_by_name(controls, targetName) container = []; for i = 1:numel(controls) ctrl = controls(i); % Проверяем, контейнер ли это и совпадает ли имя if isa(ctrl, 'Simulink.dialog.Container') && strcmp(ctrl.Name, targetName) container = ctrl; return; end % Если это вложенный контрол — обходим его детей children = get_children(ctrl); if ~isempty(children) container = find_container_by_name(children, targetName); if ~isempty(container) return; end end 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