diff --git a/MCU Wrapper.mltbx b/MCU Wrapper.mltbx index b92d2d8..2b8619c 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 1432d09..b0f19a4 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 index cfb7a94..7b0f3da 100644 --- a/McuLib/m/appWrap.m +++ b/McuLib/m/appWrap.m @@ -1,100 +1,144 @@ classdef appWrap + % Класс для редактирования кода обёртки МК прямо внутри MATLAB/Simulink + % Позволяет работать с кодом микроконтроллера без переключения между IDE methods(Static) + function appWrapperFunc() - block = gcb; - % Получаем имя функции и путь к файлам - [filename, section, tool, example]= appWrap.getAppWrapperUserFile(block); + % Загружает код обёртки из файлов в интерфейс MATLAB для редактирования + % Автоматически вызывается при выборе типа кода в маске блока + + block = gcb; % Получаем текущий блок Simulink для редактирования + + % Определяем какой файл и секцию нужно редактировать + [filename, section, tool, example] = appWrap.getAppWrapperUserFile(block); + + % Показываем подсказку по выбранному типу кода прямо в MATLAB 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); + + % Загружаем код из файла в интерфейс MATLAB + try + % Читаем исходный код из файла + code = fileread(filename); + code = regexprep(code, '\r\n?', '\n'); % унифицируем переводы строк для MATLAB + + % Вырезаем только нужную секцию для редактирования + includesText = editCode.extractSection(code, section); + + % Загружаем код в текстовое поле маски для редактирования + set_param(block, 'appWrapperCode', includesText); + catch + % Если файл не найден или ошибка чтения - оставляем поле пустым + % Пользователь сможет написать код с нуля прямо в MATLAB + end end function saveAppWrapperCode() - block = gcb; + % Сохраняет отредактированный код из MATLAB обратно в файлы обёртки + % Вызывается при нажатии кнопки "Сохранить" в интерфейсе + + block = gcb; % Текущий редактируемый блок - % Получаем имя функции и путь к файлам + % Определяем куда сохранять based на выборе пользователя в MATLAB [filename, section] = appWrap.getAppWrapperUserFile(block); + + % Проверяем что файл существует перед сохранением if ~isfile(filename) - errordlg(['Файл не найден: ', filename]); + errordlg(['Файл не найден: ', filename], 'Ошибка сохранения в MATLAB'); return; end + % Получаем выбранный тип кода и путь к файлам sel = get_param(block, 'appWrapperFunc'); basePath = get_param(block, 'appWrapperPath'); + if isempty(basePath) - errordlg('Не указан путь к файлам обёртки (wrapperPath).'); + errordlg('Не указан путь к файлам обёртки.', 'Ошибка в MATLAB'); return; end + + % Получаем код который пользователь написал/отредактировал в MATLAB newBody = get_param(block, 'appWrapperCode'); + + % Читаем текущее содержимое файла чтобы сохранить структуру code = fileread(filename); code = regexprep(code, '\r\n?', '\n'); + + % Экранируем специальные символы для корректного сохранения из MATLAB newBody = strrep(newBody, '\', '\\'); + + % Вставляем отредактированную в MATLAB секцию обратно в файл code = editCode.insertSection(code, section, newBody); - % else - % % Обновляем тело функции - % expr = sprintf('void %s()', sel); - % code = editCode.insertSection(code, expr, newBody); - % end + + % Сохраняем изменения прямо из MATLAB без внешних редакторов fid = fopen(filename, 'w', 'n', 'UTF-8'); if fid == -1 - errordlg('Не удалось открыть файл для записи'); + errordlg('Не удалось сохранить файл из MATLAB', 'Ошибка записи'); return; end + + % Записываем обновленный код fwrite(fid, code); fclose(fid); - mcuMask.disp(1, ['Обновлено: ' sel]); + + % Подтверждаем успешное сохранение в интерфейсе MATLAB + mcuMask.disp(1, ['Сохранено в MATLAB: ' sel]); end function openAppWrapperCode() + % Альтернативный способ: открывает файл в системном редакторе + % Для случаев когда нужно работать во внешней IDE + block = gcb; - % Получаем имя функции и путь к файлам + % Получаем абсолютный путь к файлу для открытия filename = mcuPath.getAbsolutePath(appWrap.getAppWrapperUserFile(block)); + if exist(filename, 'file') == 2 - % Формируем команду без кавычек + % Открываем через системный диалог "Открыть с помощью" + % Полезно если нужно использовать привычную IDE вместо редактора MATLAB cmd = sprintf('rundll32.exe shell32.dll,OpenAs_RunDLL %s', filename); status = system(cmd); + if status ~= 0 - errordlg('Не удалось открыть окно выбора приложения.'); + errordlg('Не удалось открыть файл во внешнем редакторе'); end else - errordlg('Файл не найден'); + errordlg('Файл не найден для открытия'); end end -%% SPECIFIC TOOLS + %% SPECIFIC TOOLS function [filename, section, tool, example] = getAppWrapperUserFile(block, sel) + % Маппинг типов кода на файлы и секции для редактирования в MATLAB + % Центральное место настройки - здесь определяется где что редактировать + if (nargin < 2) + % Получаем какой тип кода пользователь выбрал в интерфейсе MATLAB sel = get_param(block, 'appWrapperFunc'); end + % Базовый путь к файлам обёртки (настраивается в маске) basePath = mcuPath.get('appWrapperPath'); if isempty(basePath) - errordlg('Не указан путь к файлам обёртки (wrapperPath).'); + errordlg('Не указан путь к файлам обёртки.'); return; end - % Формируем путь к файлу в зависимости от типа запроса + + % В зависимости от выбора в MATLAB - определяем что редактировать: if strcmp(sel, 'Includes') + % Редактирование подключения заголовочных файлов filename = fullfile(basePath, 'app_includes.h'); section = '// INCLUDES'; tool = 'Инклюды для доступа к коду МК в коде оболочке'; - example = '#include "main.h"'; + example = '#include "main.h"'; % Пример для подсказки в MATLAB + elseif strcmp(sel, 'Dummy') + % Редактирование заглушек для симуляции filename = fullfile(basePath, 'app_wrapper.c'); section = '// DUMMY'; tool = 'Заглушки для различных функций и переменных'; @@ -104,19 +148,25 @@ classdef appWrap ' 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') + % Редактирование обработки входных данных от Simulink filename = fullfile(basePath, 'app_io.c'); section = '// USER APP INPUT'; tool = ['Работа с буффером для портов S-Function' newline newline ... @@ -127,7 +177,9 @@ classdef appWrap 'app_variable_2 = ReadInputArray(0, 1);' newline newline... '// запись в буфер выходов' newline ... 'app_variable_2 = Buffer[10];']; + elseif strcmp(sel, 'App Outputs') + % Редактирование формирования выходных данных для Simulink filename = fullfile(basePath, 'app_io.c'); section = '// USER APP OUTPUT'; tool = ['Работа с буффером для портов S-Function' newline newline ... @@ -138,17 +190,20 @@ classdef appWrap '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 + % Неизвестный тип кода - выводим сообщение в консоль MATLAB 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 71f1834..157f30f 100644 --- a/McuLib/m/asynchManage.m +++ b/McuLib/m/asynchManage.m @@ -1,103 +1,124 @@ classdef asynchManage < handle + % Менеджер асинхронных операций для работы с моделью Simulink + % Решает проблемы блокировки модели при одновременном доступе из GUI + % Использует таймеры для отложенного выполнения операций с моделью + properties (Access = private) - modelName % Имя модели - maskBlockPath % Полный путь к блоку с маской - timerSave - timerUpdate - timerConfigUpdate + modelName % Имя модели Simulink для управления + maskBlockPath % Полный путь к блоку с маской (если нужен доступ к GUI) + timerSave % Таймер для операции сохранения модели + timerUpdate % Таймер для операции обновления модели + timerConfigUpdate % Таймер для обновления конфигурации end methods function obj = asynchManage(modelName, maskBlockPath) - % Конструктор принимает имя модели и путь к блоку с маской + % Конструктор - создает менеджер для асинхронных операций + % modelName - имя модели Simulink для управления + % maskBlockPath - путь к блоку с маской (опционально, для GUI) obj.modelName = modelName; if nargin < 2 - obj.maskBlockPath = ''; % если не передали, оставляем пустым + obj.maskBlockPath = ''; % Если не передали - работаем без GUI else obj.maskBlockPath = maskBlockPath; end end function saveAndUpdateModel(obj) + % Асинхронное сохранение и обновление модели + % Использует таймер чтобы избежать конфликтов доступа к модели obj.timerSave = timer(... - 'StartDelay', 0.01, ... - 'ExecutionMode', 'singleShot', ... - 'TimerFcn', @(~,~) obj.saveCallback()); + 'StartDelay', 0.01, ... % Короткая задержка чтобы освободить модель + 'ExecutionMode', 'singleShot', ... % Однократное выполнение + 'TimerFcn', @(~,~) obj.saveCallback()); % Колбэк сохранения start(obj.timerSave); end - function updateGUIfromConfig(obj) + % Асинхронное обновление GUI из конфигурации + % Используется когда нужно перезагрузить маску с новыми параметрами obj.timerConfigUpdate = timer(... - 'StartDelay', 0.01, ... + 'StartDelay', 0.01, ... % Задержка для стабилизации модели 'ExecutionMode', 'singleShot', ... - 'TimerFcn', @(~,~) obj.GUIconfigCallback()); + 'TimerFcn', @(~,~) obj.GUIconfigCallback()); % Колбэк обновления GUI start(obj.timerConfigUpdate); end end methods (Access = private) function saveCallback(obj) + % Колбэк для сохранения модели - вызывается через таймер try + % Закрываем маску чтобы разблокировать модель для сохранения mcuMask.close(obj.maskBlockPath); + % Сохраняем модель (это могло бы вызвать конфликт без таймера) save_system(obj.modelName); catch ME warning('progr:Nneg', 'Ошибка при сохранении модели: %s', ME.message); end + + % Очищаем таймер сохранения stop(obj.timerSave); delete(obj.timerSave); obj.timerSave = []; + % Запускаем таймер для обновления модели после сохранения obj.timerUpdate = timer(... - 'StartDelay', 0.05, ... + 'StartDelay', 0.05, ... % Большая задержка для полного сохранения 'ExecutionMode', 'singleShot', ... - 'TimerFcn', @(~,~) obj.updateCallback()); + 'TimerFcn', @(~,~) obj.updateCallback()); % Колбэк обновления start(obj.timerUpdate); end function updateCallback(obj) + % Колбэк для обновления модели - вызывается после сохранения try + % Команда обновления модели (перекомпиляция и т.д.) set_param(obj.modelName, 'SimulationCommand', 'update'); + % Повторное сохранение после обновления save_system(obj.modelName); catch ME warning('progr:Nneg', 'Ошибка при обновлении модели: %s', ME.message); end - % Открываем маску, если задан путь к блоку + % Переоткрываем маску если был указан путь к блоку if ~isempty(obj.maskBlockPath) try - mcuMask.open(obj.maskBlockPath, 1); + mcuMask.open(obj.maskBlockPath, 1); % Открываем маску снова catch ME warning('progr:Nneg', 'Не удалось открыть маску: %s', ME.message); end end + % Очищаем таймер обновления stop(obj.timerUpdate); delete(obj.timerUpdate); obj.timerUpdate = []; end + function GUIconfigCallback(obj) - + % Колбэк для обновления GUI из конфигурации try + % Закрываем маску и выполняем настройку (mexing) mcuMask.close(obj.maskBlockPath); - mexing(0); + mexing(0); % Функция настройки/компиляции catch ME warning('progr:Nneg', 'Ошибка при обновлении модели: %s', ME.message); end - % Открываем маску, если задан путь к блоку + % Переоткрываем маску с обновленными параметрами if ~isempty(obj.maskBlockPath) try - mcuMask.open(obj.maskBlockPath, 1); + mcuMask.open(obj.maskBlockPath, 1); % Открываем обновленную маску catch ME warning('progr:Nneg', 'Не удалось открыть маску: %s', ME.message); end end + % Очищаем таймер обновления конфигурации stop(obj.timerConfigUpdate); delete(obj.timerConfigUpdate); obj.timerConfigUpdate = []; end - end -end +end \ No newline at end of file diff --git a/McuLib/m/compiler.m b/McuLib/m/compiler.m index 2a0f39f..c33a4d6 100644 --- a/McuLib/m/compiler.m +++ b/McuLib/m/compiler.m @@ -1,138 +1,160 @@ classdef compiler + % Класс для компиляции бинарника S-Function в MATLAB + % Управляет процессом сборки mex-файлов для Simulink methods(Static) function compile() + % Основная функция компиляции S-Function + % Добавляет путь к файлам обёртки и запускает компиляцию addpath(mcuPath.get('wrapperPath')); - mexing(1); + mexing(1); % 1 - флаг компиляции end + % хз че это function get_availbe() addpath(mcuPath.get('wrapperPath')); mexing(1); end - function choose() addpath(mcuPath.get('wrapperPath')); mexing(1); end - - function updateRunBat() + % Обновление BAT-файла для компиляции S-Function + % Формирует списки исходных файлов и путей для включения + + % === КОМПИЛЯЦИЯ ОСНОВНОЙ ОБЁРТКИ === sources = { 'MCU.c' 'mcu_wrapper.c' - }; - % Список заголовочных файлов (.h) - includes = { '.\' }; + % Список заголовочных файлов (.h) + includes = { '.\' }; wrapperPath = mcuPath.get('wrapperPath'); - % [wrapperPath, ~, ~] = fileparts(wrapperPath); - % Формируем строки + + % Формируем строки для BAT-файла wrapperSrcText = compiler.createSourcesBat('code_WRAPPER', sources, wrapperPath); wrapperIncText = compiler.createIncludesBat('includes_WRAPPER', includes, wrapperPath); - % Записываем результат - res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: WRAPPER BAT'); % Всё прошло успешно - + % Обновляем BAT-файл для основной обёртки + res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: WRAPPER BAT'); + + % Если ошибка - прекращаем выполнение if res == 0 return end + % === КОМПИЛЯЦИЯ ПРИЛОЖЕНИЯ ОБЁРТКИ === sources = { 'app_wrapper.c' 'app_init.c' 'app_io.c' }; % Список заголовочных файлов (.h) - includes = { '.\' - }; + includes = { '.\' }; periphPath = mcuPath.get('appWrapperPath'); - % Формируем строки + + % Формируем строки для BAT-файла wrapperSrcText = compiler.createSourcesBat('code_APP_WRAPPER', sources, periphPath); wrapperIncText = compiler.createIncludesBat('includes_APP_WRAPPER', includes, periphPath); - % Записываем результат - res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: APP WRAPPER BAT'); % Всё прошло успешно + % Обновляем BAT-файл для обёртки приложения + res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: APP WRAPPER BAT'); + % Обновляем BAT-файл для периферии periphConfig.updatePeriphRunMexBat(); end - function res = updateRunMexBat(srcText, incText, Section) + % Обновляет секцию в BAT-файле для компиляции mex % Входные параметры: - % srcText - текст для записи set code_... - % incText - текст для записи set includes_... + % srcText - текст для записи set code_... (исходные файлы) + % incText - текст для записи set includes_... (пути включения) + % Section - секция в BAT-файле для обновления % % Возвращает: - % res - 0 при успехе, 1 при ошибке + % res - 1 при успехе, 0 при ошибке + % Объединяем исходные файлы и пути включения periphBat = [srcText '\n\n' incText]; batPath = fullfile(mcuPath.get('wrapperPath'), 'run_mex.bat'); - res = 1; + res = 1; % По умолчанию - ошибка + try + % Читаем текущее содержимое BAT-файла code = fileread(batPath); - code = regexprep(code, '\r\n?', '\n'); + code = regexprep(code, '\r\n?', '\n'); % Нормализуем переводы строк - % Записываем строки srcText и incText с переносами строк + % Вставляем новую секцию в BAT-файл code = editCode.insertSection(code, Section, periphBat); + % Записываем обновленный BAT-файл fid = fopen(batPath, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл для записи'); end fwrite(fid, code); fclose(fid); - res = 1; + res = 1; % Успех catch ME + % Ошибка записи в BAT-файл mcuMask.disp(0, '\nОшибка: неудачная запись в файл при записи файла: %s', ME.message); end end function srcText = createSourcesBat(prefix_name, sources, path) + % Создает строку с исходными файлами для BAT-файла + % Формат: set code_WRAPPER=file1.c file2.c ... + srcList = {}; if nargin >= 2 && iscell(sources) for i = 1:numel(sources) + % Формируем полный путь к исходному файлу fullPath = fullfile(path, sources{i}); - srcList{end+1} = strrep(fullPath, '\', '\\'); + srcList{end+1} = strrep(fullPath, '\', '\\'); % Экранирование слешей end end - % Формируем srcText с переносами строк и ^ + % Формируем srcText с переносами строк и символом продолжения ^ srcText = ''; for i = 1:numel(srcList) if i < numel(srcList) - srcText = [srcText srcList{i} '^' newline ' ']; + srcText = [srcText srcList{i} '^' newline ' ']; % ^ для продолжения строки в BAT else - srcText = [srcText srcList{i}]; + srcText = [srcText srcList{i}]; % Последний файл без продолжения end end - % Добавляем префикс + % Добавляем префикс переменной BAT srcText = ['set ' prefix_name '=' srcText]; end function incText = createIncludesBat(prefix_name, includes, path) + % Создает строку с путями включения для BAT-файла + % Формат: set includes_WRAPPER=-I"path1" -I"path2" ... + incList = {}; if nargin >= 2 && iscell(includes) for i = 1:numel(includes) + % Формируем полный путь для включения fullPath = fullfile(path, includes{i}); - incList{end+1} = ['-I"' strrep(fullPath, '\', '\\') '"']; + incList{end+1} = ['-I"' strrep(fullPath, '\', '\\') '"']; % Флаг include для компилятора end end - % Формируем incText с переносами строк и ^ + % Формируем incText с переносами строк и символом продолжения ^ incText = ''; for i = 1:numel(incList) if i == 1 && numel(incList) ~= 1 - incText = [incText incList{i} '^' newline]; + incText = [incText incList{i} '^' newline]; % Первый элемент с продолжением elseif i < numel(incList) - incText = [incText ' ' incList{i} '^' newline]; + incText = [incText ' ' incList{i} '^' newline]; % Средние элементы с продолжением else - incText = [incText ' ' incList{i}]; + incText = [incText ' ' incList{i}]; % Последний элемент без продолжения end end - % Добавляем префикс + % Добавляем префикс переменной BAT incText = ['set ' prefix_name '=' incText]; end diff --git a/McuLib/m/configJs.m b/McuLib/m/configJs.m index f2cde98..e50b495 100644 --- a/McuLib/m/configJs.m +++ b/McuLib/m/configJs.m @@ -1,48 +1,61 @@ classdef configJs + % Класс для работы с JSON конфигурацией периферии в масках Simulink + % Обеспечивает чтение, запись и обновление конфигов из/в JSON файлы methods(Static) function config = update(blockPath, config) + % Обновляет конфигурацию значениями из маски Simulink + % 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 + % Проверяем есть ли секция Defines в этой периферии if ~isfield(config.(periph), 'Defines') continue; end + % Получаем все определения (defines) для этой периферии defines = config.(periph).Defines; defNames = fieldnames(defines); + % Обходим все определения и обновляем их значения из маски for j = 1:numel(defNames) defPrompt = defNames{j}; - paramName = matlab.lang.makeValidName(defPrompt); + paramName = matlab.lang.makeValidName(defPrompt); % Создаем валидное имя параметра - % Проверка, существует ли параметр с таким именем + % Проверяем существует ли параметр с таким именем в маске if ismember(paramName, paramNames) param = mask.getParameter(paramName); - valStr = param.Value; + valStr = param.Value; % Значение как строка из маски - % Проверяем, существует ли элемент defPrompt в структуре defines + % Проверяем существует ли элемент defPrompt в структуре defines if isfield(defines, defPrompt) - % Преобразуем строку в соответствующий тип + % Преобразуем строку из маски в соответствующий тип данных if strcmpi(defines.(defPrompt).Type, 'checkbox') + % Для чекбоксов преобразуем 'on'/'off' в true/false 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 @@ -53,12 +66,18 @@ classdef configJs end function config = read(blockPath) + % Читает JSON конфигурацию из файла указанного в маске + % blockPath - путь к блоку с маской + % Возвращает структуру конфигурации или пустой массив + mask = Simulink.Mask.get(blockPath); + % Получаем путь к конфигурационному файлу из параметра маски pathparam = mask.getParameter('periphPath'); config_path = pathparam.Value; if ~isempty(config_path) + % Читаем и декодируем JSON файл jsonText = fileread(config_path); config = jsondecode(jsonText); else @@ -67,17 +86,25 @@ classdef configJs end function write(config) + % Записывает конфигурацию обратно в JSON файл + % config - структура конфигурации для записи + if isempty(config) return end + % Получаем handle текущего блока и его маску blockHandle = gcbh; mask = Simulink.Mask.get(blockHandle); + % Получаем путь к конфигурационному файлу из маски pathparam = mask.getParameter('periphPath'); config_path = pathparam.Value; + % Кодируем структуру в JSON с красивым форматированием jsonText = jsonencode(config, 'PrettyPrint', true); + + % Записываем JSON в файл fid = fopen(config_path, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл periph_config.json для записи.'); @@ -86,58 +113,65 @@ classdef configJs fclose(fid); end - - function value = get_field(configStruct, targetConfig) - % получить targetConfig структуру из конфига (для глубоко вложенных) + % Рекурсивно ищет поле в структуре конфигурации + % configStruct - структура для поиска + % targetConfig - имя целевого поля + % Возвращает значение поля или пустой массив если не найдено + value = []; fields = fieldnames(configStruct); for i = 1:numel(fields) key = fields{i}; if strcmp(key, targetConfig) + % Нашли прямое совпадение value = configStruct.(key); - return; % нашли и возвращаем + return; elseif isstruct(configStruct.(key)) + % Рекурсивно ищем во вложенной структуре value = configJs.get_field(configStruct.(key), targetConfig); if ~isempty(value) - return; % нашли во вложенной структуре + return; % Нашли во вложенной структуре end end end - % Если не нашли, можно выбросить ошибку или вернуть пустое + % Если не нашли - возвращаем пустой массив if isempty(value) + % Можно раскомментировать для отладки: % error('Поле "%s" не найдено в структуре.', targetConfig); end end function short = get_final_name_from_prefix(prefix) - % Берёт последнее имя после "_" (читаемое имя) + % Извлекает короткое имя из префикса (последняя часть после '_') + % prefix - строка с префиксом вида 'PREFIX_NAME' + % Возвращает последнюю часть после последнего '_' + parts = strsplit(prefix, '_'); - short = parts{end}; + short = parts{end}; % Берём последний элемент end function value = convert_code_value(codeField) - % Преобразует значение поля Options в строку + % Преобразует значение поля Options в строку для отображения + % Обрабатывает разные типы данных: char, string, cell array + 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 +end \ No newline at end of file diff --git a/McuLib/m/customtable.m b/McuLib/m/customtable.m index 7915168..87516b0 100644 --- a/McuLib/m/customtable.m +++ b/McuLib/m/customtable.m @@ -1,159 +1,182 @@ classdef customtable + % Класс для работы с таблицами в масках Simulink + % Обеспечивает форматирование, парсинг и управление табличными данными + % 1. Таблицы в масках Simulink хранятся как строки специального формата: + % {'строка1';'строка2';'строка3'} + % 2. Этот класс преобразует строковое представление в cell-массивы для удобной работы + % 3. Обеспечивает очистку от пустых строк, форматирование колонок и массовое обновление methods(Static) - % формирование таблицы на всю ширину function format(table_name) + % Форматирует таблицу в маске - настраивает колонки и внешний вид + % table_name - имя табличного параметра в маске + block = gcb; mask = Simulink.Mask.get(block); - tableControl = mask.getDialogControl(table_name); - tableParameter = mask.getParameter(table_name); + tableControl = mask.getDialogControl(table_name); % Элемент управления таблицей + tableParameter = mask.getParameter(table_name); % Параметр с данными таблицы nCols = tableControl.getNumberOfColumns; - % инициализация колонок если они пустые - % такое случается при removeParameter + + % Инициализация колонок если они пустые (после removeParameter) if isempty(tableControl.Columns) || (nCols > 1) - for i = 1:nCols - tableControl.removeColumn(1); - end + % Удаляем все существующие колонки + for i = 1:nCols + tableControl.removeColumn(1); + end + % Создаем одну колонку по умолчанию column = tableControl.addColumn(Name='Title', Type='edit'); - tableControl.Sortable = 'on'; + tableControl.Sortable = 'on'; % Разрешаем сортировку end + % Устанавливаем имя колонки равным алиасу параметра column.Name = tableParameter.Alias; end function update(tableName) + % Обновляет таблицу - удаляет пустые строки и сохраняет изменения + % tableName - имя табличного параметра для обновления + block = gcb; mask = Simulink.Mask.get(block); Table = mask.getParameter(tableName); + % Парсим текущее содержимое таблицы cellArray = customtable.parse(tableName); + % Удаляем пустые строки cleaned = customtable.removeEmptyRows(cellArray); + % Если были удалены пустые строки - сохраняем изменения if numel(cleaned) ~= numel(cellArray) + % Обрамляем каждую строку в кавычки и формируем новое значение quoted = cellfun(@(s) ['''' s ''''], cleaned, 'UniformOutput', false); newStr = ['{' strjoin(quoted, ';') '}']; - Table.Value = newStr; + Table.Value = newStr; % Сохраняем обратно в параметр end end function column_titles = save_all_tables(table_names) - % Очищает столбцы в каждой таблице из массива имен table_names - % Возвращает cell-массив с названиями первых столбцов каждой таблицы - block = gcb; + % Сохраняет и очищает несколько таблиц одновременно + % table_names - cell-массив с именами таблиц + % Возвращает массив с названиями первых колонок каждой таблицы - % Получить объект маски блока + block = gcb; maskObj = Simulink.Mask.get(block); - % Инициализировать cell-массив для хранения названий столбцов + % Инициализируем массив для хранения названий колонок column_titles = cell(size(table_names)); for k = 1:numel(table_names) table_name = table_names{k}; - - % Получить объект управления таблицей tableControl = maskObj.getDialogControl(table_name); - - % Получить количество столбцов nCols = tableControl.getNumberOfColumns; if nCols > 0 - % Получить первый столбец (который будем удалять) + % Сохраняем название первой колонки column = tableControl.getColumn(1); column_titles{k} = column.Name; - % Удаляем все столбцы - % Важно: при удалении столбцов индексы меняются, - % поэтому удаляем всегда первый столбец nCols раз + % Удаляем все колонки (всегда удаляем первую, т.к. индексы сдвигаются) for i = 1:nCols tableControl.removeColumn(1); end else - % Если столбцов нет, возвращаем пустую строку - column_titles{k} = ''; + column_titles{k} = ''; % Если колонок нет end end end function restore_all_tables(table_names, column_titles) - % Восстанавливает первый столбец в каждой таблице из массива имен - % Использует массив column_titles для установки имени столбца - block = gcb; + % Восстанавливает таблицы с сохраненными названиями колонок + % table_names - имена таблиц для восстановления + % column_titles - массив с названиями для первых колонок - % Получить объект маски блока + block = gcb; maskObj = Simulink.Mask.get(block); for k = 1:numel(table_names) table_name = table_names{k}; title = column_titles{k}; - % Получить объект управления таблицей tableControl = maskObj.getDialogControl(table_name); - % Добавить новый столбец + % Добавляем новую колонку с сохраненным названием column = tableControl.addColumn(Name='title', Type='edit'); column.Name = title; end end - function out = parse(tableName) + % Парсит содержимое таблицы из строкового параметра в cell-массив + % tableName - имя табличного параметра + % Возвращает cell-массив со строками таблицы + block = gcb; - TableStr = get_param(block, tableName); - out = customtable.parse__(TableStr); + TableStr = get_param(block, tableName); % Получаем строковое представление + out = customtable.parse__(TableStr); % Парсим во внутреннем методе end function collect(tableName, cellArray) + % Сохраняет cell-массив обратно в табличный параметр + % tableName - имя параметра для сохранения + % cellArray - cell-массив с данными таблицы + block = gcb; - newTableStr = customtable.collect__(cellArray); - % Записываем обратно в параметр маски - set_param(block, tableName, newTableStr); + newTableStr = customtable.collect__(cellArray); % Конвертируем в строку + set_param(block, tableName, newTableStr); % Сохраняем в параметр end end - - - - methods(Static, Access=private) - function out = parse__(tableStr) + % Внутренний метод для парсинга строки таблицы в cell-массив + % tableStr - строковое представление таблицы в формате {'str1';'str2'} + str = strtrim(tableStr); + % Удаляем обрамляющие фигурные скобки если есть if startsWith(str, '{') && endsWith(str, '}') str = str(2:end-1); end + % Разбиваем по разделителю строк ; parts = split(str, ';'); out = cell(numel(parts), 1); for i = 1:numel(parts) el = strtrim(parts{i}); + % Удаляем обрамляющие кавычки если есть if startsWith(el, '''') && endsWith(el, '''') el = el(2:end-1); end out{i} = el; end + % Обработка пустых таблиц if isempty(out) || (numel(out) == 1 && isempty(out{1})) out = {}; end end - function tableStr = collect__(cellArray) + % Внутренний метод для конвертации cell-массива в строку таблицы + % cellArray - входной cell-массив + % Возвращает строку в формате {'str1';'str2'} + + % Обрамляем каждую строку в кавычки quoted = cellfun(@(s) ['''' s ''''], cellArray, 'UniformOutput', false); - tableStr = ['{' strjoin(quoted, ';') '}']; + tableStr = ['{' strjoin(quoted, ';') '}']; % Объединяем в формат таблицы end - function cleaned = removeEmptyRows(cellArray) + % Удаляет пустые строки из cell-массива + % cellArray - входной массив данных таблицы + % Возвращает очищенный массив без пустых строк + if isempty(cellArray) cleaned = {}; else - % Проверяем каждую строку, есть ли в ней содержимое (не пустая строка) + % Определяем какие строки пустые (после удаления пробелов) isEmptyRow = cellfun(@(s) isempty(strtrim(s)), cellArray); - cleaned = cellArray(~isEmptyRow); + cleaned = cellArray(~isEmptyRow); % Фильтруем пустые строки end end - end - end \ No newline at end of file diff --git a/McuLib/m/editCode.m b/McuLib/m/editCode.m index c7388ea..918cea1 100644 --- a/McuLib/m/editCode.m +++ b/McuLib/m/editCode.m @@ -1,60 +1,71 @@ classdef editCode + % Класс для редактирования кода C/C++ в текстовых файлах + % Обеспечивает извлечение и вставку кода в секции и функции methods(Static) function newCode = insertSection(code, sectionName, newText) - % insertSection – вставка или замена содержимого секции или тела функции + % Вставка или замена содержимого секции или тела функции + % % Аргументы: - % code – исходный текст (строка) - % sectionName – имя секции (например, 'MY_SECTION') или заголовок функции (например, 'void myFunc(...)') - % newText – новый текст, который будет вставлен внутрь секции или функции + % code - исходный текст кода как строка + % sectionName - имя секции (например, 'MY_SECTION') или + % заголовок функции (например, 'void myFunc(...)') + % newText - новый текст для вставки внутрь секции или функции % % Возвращает: - % newCode – обновлённый текст с подставленным содержимым + % newCode - обновлённый текст с подставленным содержимым % % Особенности: - % - Если sectionName начинается с 'void ', считается что это функция, и вставка происходит внутрь её тела. - % - В остальных случаях ищется секция между маркерами " START" и " END", и она заменяется на newText. + % - Если sectionName начинается с 'void ', считается что это функция, + % и вставка происходит внутрь её тела + % - Для секций ищется блок между маркерами "NAME START" и "NAME END" + % - Поддерживает функции с параметрами и вложенными скобками newCode = code; - % Если это функция + % Обработка функций (если sectionName начинается с 'void ') if startsWith(strtrim(sectionName), 'void ') + % Извлекаем имя функции из заголовка tokens = regexp(sectionName, 'void\s+(\w+)\s*\(', 'tokens'); if isempty(tokens) - return; + return; % Не удалось извлечь имя функции end funcName = tokens{1}{1}; + + % Ищем начало функции в коде expr = sprintf('void\\s+%s\\s*\\(.*?\\)\\s*\\{', funcName); startIdx = regexp(code, expr, 'start'); if isempty(startIdx) - return; + return; % Функция не найдена end - % Найдём тело функции с учётом вложенных скобок + % Находим тело функции с учётом вложенных фигурных скобок from = startIdx(1); - braceCount = 0; + braceCount = 0; % Счётчик уровня вложенности скобок i = from; while i <= length(code) if code(i) == '{' braceCount = braceCount + 1; if braceCount == 1 - bodyStart = i + 1; + bodyStart = i + 1; % Начало тела функции (после {) end elseif code(i) == '}' braceCount = braceCount - 1; if braceCount == 0 - bodyEnd = i - 1; + bodyEnd = i - 1; % Конец тела функции (перед }) break; end end i = i + 1; end + % Проверяем что все скобки закрыты if braceCount ~= 0 - return; + return; % Несбалансированные скобки end + % Собираем новый код: начало до тела + новый текст + конец после тела newCode = [ ... code(1:bodyStart-1), ... newline, newText, newline, ... @@ -63,111 +74,113 @@ classdef editCode return; end - % Иначе это обычная секция - % Формируем шаблон с группами для поиска нужного блока + % Обработка обычных секций (не функций) + % Формируем шаблон для поиска секции: начало, содержимое, конец pattern = sprintf('(%s START\\s*\\n)(.*?)(\\s*%s END)', sectionName, sectionName); - % Проверяем, есть ли совпадение + % Проверяем наличие секции в коде startIdx = regexp(code, pattern, 'start', 'once'); if isempty(startIdx) error('Секция "%s" не найдена в тексте.', sectionName); end - % Формируем новую секцию с нужным текстом - % newText = strrep(newText, '\', '\\'); % экранируем для корректной вставки + % Формируем новую секцию с переданным текстом replacement = sprintf('%s START\n%s\n%s END', sectionName, newText, sectionName); % Заменяем всю найденную секцию на новую newCode = regexprep(code, pattern, replacement, 'dotall'); end - - function result = extractSection(code, sectionName) - % extractSection – извлечение содержимого секции или тела функции + % Извлечение содержимого секции или тела функции + % % Аргументы: - % code – исходный текст (строка) - % sectionName – имя секции (например, 'MY_SECTION') или заголовок функции (например, 'void myFunc(...)') + % code - исходный текст кода как строка + % sectionName - имя секции (например, 'MY_SECTION') или + % заголовок функции (например, 'void myFunc(...)') % % Возвращает: - % result – извлечённый текст из секции или тела функции + % result - извлечённый текст из секции или тела функции % % Особенности: - % - Если sectionName начинается с 'void ', считается что это функция, и извлекается содержимое её тела. - % - В остальных случаях ищется секция между маркерами " START" и " END". + % - Для функций извлекает содержимое между { и } с учётом вложенности + % - Для секций извлекает текст между маркерами START и END + % - Сохращает оригинальные отступы и форматирование result = ''; - % Если это функция (начинается с 'void ') + + % Обработка функций (если sectionName начинается с 'void ') if startsWith(strtrim(sectionName), 'void ') - % Получаем имя функции из заголовка + % Извлекаем имя функции из заголовка tokens = regexp(sectionName, 'void\s+(\w+)\s*\(', 'tokens'); if isempty(tokens) - return; + return; % Не удалось извлечь имя функции end funcName = tokens{1}{1}; - % Строим шаблон начала функции + % Ищем начало функции в коде expr = sprintf('void\\s+%s\\s*\\(.*?\\)\\s*\\{', funcName); startIdx = regexp(code, expr, 'start'); if isempty(startIdx) - return; + return; % Функция не найдена end - % Поиск тела функции с учётом вложенных скобок + % Находим тело функции с учётом вложенных фигурных скобок from = startIdx(1); - braceCount = 0; + braceCount = 0; % Счётчик уровня вложенности скобок i = from; while i <= length(code) if code(i) == '{' braceCount = braceCount + 1; if braceCount == 1 - % Найдём первую новую строку после { - braceOpenIdx = i; + braceOpenIdx = i; % Позиция открывающей скобки end elseif code(i) == '}' braceCount = braceCount - 1; if braceCount == 0 - braceCloseIdx = i; + braceCloseIdx = i; % Позиция закрывающей скобки break; end end i = i + 1; end + % Проверяем что все скобки закрыты if braceCount ~= 0 - return; + return; % Несбалансированные скобки end - % Найдём \n после { + % Находим первую новую строку после открывающей скобки { newlineAfterOpen = regexp(code(braceOpenIdx:end), '\n', 'once'); if isempty(newlineAfterOpen) - return; + return; % Не найден перевод строки end - bodyStart = braceOpenIdx + newlineAfterOpen; + bodyStart = braceOpenIdx + newlineAfterOpen; % Начало тела функции - % Найдём \n до } + % Находим последнюю новую строку перед закрывающей скобкой } newlinesBeforeClose = regexp(code(1:braceCloseIdx), '\n'); if isempty(newlinesBeforeClose) - return; + return; % Не найдены переводы строк end - bodyEnd = newlinesBeforeClose(end) - 1; + bodyEnd = newlinesBeforeClose(end) - 1; % Конец тела функции - % Извлекаем блок как есть, включая отступы + % Извлекаем тело функции с сохранением форматирования result = code(bodyStart:bodyEnd); return; end - % Иначе считаем, что это секция вида // NAME START ... // NAME END + % Обработка обычных секций (не функций) + % Ищем секцию между маркерами START и END pattern = sprintf('%s START\\s*\\n(.*?)\n%s END', sectionName, sectionName); + % Извлекаем содержимое секции match = regexp(code, pattern, 'tokens', 'dotall'); if ~isempty(match) - result = match{1}{1}; + result = match{1}{1}; % Возвращаем содержимое секции else + % Секция не найдена - выводим сообщение об ошибке mcuMask.disp(0, 'Ошибка: cекция "%s" не найдена в тексте.', sectionName); end end - - end end \ No newline at end of file diff --git a/McuLib/m/installTemplates.m b/McuLib/m/installTemplates.m index 2f775bf..7eed166 100644 --- a/McuLib/m/installTemplates.m +++ b/McuLib/m/installTemplates.m @@ -1,5 +1,5 @@ function installTemplates(forceCopy) -% installTemplates Копирует содержимое папки templates (включая вложенные папки) из уровня выше McuLib.slx в текущую папку +% installTemplates Копирует содержимое папки templates (включая вложенные папки) из библиотеки (McuLib.slx) в текущую папку % % installTemplates(forceCopy) % diff --git a/McuLib/m/mainConfig.m b/McuLib/m/mainConfig.m index 3a1aee0..f429b4f 100644 --- a/McuLib/m/mainConfig.m +++ b/McuLib/m/mainConfig.m @@ -1,10 +1,17 @@ classdef mainConfig + % Класс для экспорта и импорта конфигурации маски Simulink в JSON + % Позволяет сохранять и загружать настройки блока между сессиями methods(Static) function config = export() + % Экспортирует текущую конфигурацию маски в JSON файл + % Сохраняет параметры обёртки, портов и приложения + blockPath = gcb; mask = Simulink.Mask.get(blockPath); + + % Списки параметров для экспорта по категориям wrapParamToExport = {'wrapperPath', 'enableDebug', 'mcuClk', ... 'threadCycles', 'enableThreading', 'enableDeinit', ... 'periphPath'}; @@ -22,29 +29,32 @@ classdef mainConfig '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' для форматирования - + % Кодируем структуру в JSON с форматированием + jsonStr = jsonencode(config, 'PrettyPrint', true); % Диалог сохранения файла [file, path] = uiputfile('*.json', 'Сохранить конфигурацию как', 'WrapperConfig.json'); @@ -55,8 +65,6 @@ classdef mainConfig filepath = fullfile(path, file); - - % Сохраняем в файл fid = fopen(filepath, 'w'); if fid == -1 @@ -67,8 +75,10 @@ classdef mainConfig fclose(fid); end - function import() + % Импортирует конфигурацию маски из JSON файла + % Восстанавливает параметры обёртки, портов и приложения + % Получаем путь к текущему блоку blockPath = gcb; mask = Simulink.Mask.get(blockPath); @@ -105,19 +115,20 @@ classdef mainConfig mcuMask.disp(0, 'Конфигурация успешно импортирована.'); + % Обновляем периферию после импорта mcuMask.periphUpdate(); end end - methods(Static, Access=private) - function def = exportParamToConfig(mask, paramName) - % mask — объект Simulink.Mask.get(blockPath) - % paramName — имя параметра (как в mask.Parameters.Name) - + % Экспортирует один параметр маски в структуру для JSON + % mask - объект маски Simulink + % paramName - имя параметра для экспорта + % Возвращает структуру с описанием параметра + param = mask.getParameter(paramName); if isempty(param) mcuMask.disp(0, 'Параметр "%s" не найден в маске.', paramName); @@ -127,53 +138,50 @@ classdef mainConfig def = struct(); - % Prompt + % Сохраняем подпись параметра def.Prompt = param.Prompt; - % Тип параметра + % Сохраняем тип параметра def.Type = param.Type; - % Значение по умолчанию + % Сохраняем значение в зависимости от типа val = param.Value; switch lower(param.Type) case 'checkbox' + % Для чекбоксов преобразуем 'on'/'off' в true/false def.Default = strcmp(val, 'on'); case {'edit', 'spinbox'} + % Для числовых полей пробуем преобразовать в число num = str2double(val); if ~isnan(num) def.Default = num; else - def.Default = val; + def.Default = val; % Оставляем строкой если не число end case 'customtable' - def.Default = customtable.parse(param.Name); % или можно попытаться распарсить значение позже + % Для таблиц парсим содержимое в cell-массив + def.Default = customtable.parse(param.Name); case 'text' - def.Default = ''; % или можно попытаться распарсить значение позже + % Для текстовых полей сохраняем пустую строку + 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 - - % Получаем параметр + % Применяет параметр из конфигурации к маске + % mask - объект маски Simulink + % paramName - имя параметра для применения + % def - структура с данными параметра + + % Получаем параметр из маски param = mask.getParameter(paramName); if isempty(param) mcuMask.disp(0, 'Параметр "%s" не найден в маске.', paramName); @@ -185,9 +193,10 @@ classdef mainConfig return; end + % Устанавливаем значение в зависимости от типа параметра switch lower(def.Type) case 'checkbox' - % Логическое значение true/false + % Устанавливаем состояние чекбокса if islogical(def.Default) param.Value = mainConfig.ternary(def.Default, 'on', 'off'); else @@ -195,7 +204,7 @@ classdef mainConfig end case {'edit', 'spinbox'} - % Строка или число + % Устанавливаем значение для текстового поля if isnumeric(def.Default) param.Value = num2str(def.Default); elseif ischar(def.Default) || isstring(def.Default) @@ -205,7 +214,7 @@ classdef mainConfig end case 'customtable' - % Массив строк + % Загружаем данные в таблицу if iscell(def.Default) customtable.collect(paramName, def.Default); else @@ -213,7 +222,7 @@ classdef mainConfig end case 'text' - % Просто текстовая строка + % Устанавливаем текстовое значение if ischar(def.Default) || isstring(def.Default) param.Value = char(def.Default); else @@ -221,7 +230,7 @@ classdef mainConfig end case 'popup' - % popup — установить значение, если оно есть в списке + % Устанавливаем значение выпадающего списка if ischar(def.Default) && isfield(def, 'Def') && any(strcmp(def.Default, def.Def)) param.Value = def.Default; else @@ -229,7 +238,7 @@ classdef mainConfig end otherwise - % По умолчанию просто устанавливаем строковое значение + % Универсальная установка значения if ischar(def.Default) || isstring(def.Default) param.Value = char(def.Default); elseif isnumeric(def.Default) @@ -239,16 +248,16 @@ classdef mainConfig 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 @@ -263,8 +272,9 @@ classdef mainConfig % end end - function result = ternary(cond, a, b) + % Вспомогательная функция - тернарный оператор + % cond - условие, a - значение если true, b - значение если false if cond result = a; else diff --git a/McuLib/m/mcuMask.m b/McuLib/m/mcuMask.m index 104fd17..bf209e3 100644 --- a/McuLib/m/mcuMask.m +++ b/McuLib/m/mcuMask.m @@ -1,156 +1,172 @@ classdef mcuMask -%% CALLBACKS + % Главный класс управления маской блока Simulink для микроконтроллера + % Содержит callback-функции и утилиты для работы с маской + +%% CALLBACKS - функции обратного вызова для элементов маски methods(Static) - % Following properties of 'maskInitContext' are avalaible to use: - % - BlockHandle - % - MaskObject - % - MaskWorkspace - Use get/set APIs to work with mask workspace. + % Функция инициализации маски - вызывается при загрузке блока function MaskInitialization(maskInitContext) % Получаем хэндл текущего блока blk = gcbh; % Получаем объект маски текущего блока mask = Simulink.Mask.get(gcb); + + % Разрешаем самомодификацию маски и отключаем ссылки set_param(blk,"MaskSelfModifiable","on") set_param(blk, 'LinkStatus', 'none'); - % mcuMask.disp(1,''); + + % Проверяем доступность findjobj для внешней консоли try - % Проверка наличия findjobj findjobjAvailable = exist('findjobj', 'file') == 2; catch findjobjAvailable = false; end - % Имя checkbox-параметра (укажите точное имя из маски) - checkboxParamName = 'extConsol'; % пример - findjobjLinkName = 'findjobj_link'; % пример - % Получаем параметр по имени + + % Настраиваем параметры внешней консоли в зависимости от доступности findjobj + checkboxParamName = 'extConsol'; + findjobjLinkName = 'findjobj_link'; + checkboxParam = mask.getParameter(checkboxParamName); findjobjLink = mask.getDialogControl(findjobjLinkName); + if isempty(findjobjLink) error('Параметр %s не найден в маске.', findjobjLinkName); end if isempty(checkboxParam) error('Параметр %s не найден в маске.', checkboxParamName); end - % Блокируем чекбокс, если findjobj не найден + + % Блокируем чекбокс если findjobj не найден if ~findjobjAvailable checkboxParam.Enabled = 'off'; - checkboxParam.Value = 'off'; % и на всякий случай снимаем галочку + checkboxParam.Value = 'off'; checkboxParam.Prompt = 'External Console (requires findjobj)'; - findjobjLink.Visible = 'on'; + findjobjLink.Visible = 'on'; % Показываем ссылку для скачивания else checkboxParam.Enabled = 'on'; checkboxParam.Prompt = 'External Console'; - findjobjLink.Visible = 'off'; + findjobjLink.Visible = 'off'; % Скрываем ссылку end - % формирование таблицы на всю ширину + + % Форматируем таблицы исходных файлов и инклюдов table_names = {'srcTable', 'incTable'}; for k = 1:numel(table_names) table_name = table_names{k}; customtable.format(table_name); end - % запись описания блока + + % Устанавливаем описание блока textDesc = ['Блок для настройки параметров симуляции микроконтроллера. ' newline ... 'Позволяет задавать параметры оболочки, приложения МК и периферии']; - % Получаем объект описания toolTextArea = mask.getDialogControl('BlockDesc'); toolTextArea.Prompt = textDesc; end - %% WRAPPER PARAMS + %% WRAPPER PARAMS - callback-функции для параметров обёртки function wrapperPath_add(callbackContext) + % Добавление пути к файлам обёртки mcuPath.addPath('wrapperPath'); end function enableThreading(callbackContext) + % Включение/выключение многопоточности mainWrap.enableThreading(); end function enableDeinit(callbackContext) + % Включение/выключение деинициализации mainWrap.enableDeinit(); end function extConsol(callbackContext) + % Управление внешней консолью mainWrap.extConsol(); end - %% USER WRAPPER CODE + %% USER WRAPPER CODE - callback-функции для пользовательского кода function appWrapperPath_add(callbackContext) + % Добавление пути к файлам обёртки приложения mcuPath.addPath('appWrapperPath'); end function appWrapperFunc(callbackContext) + % Загрузка функции обёртки для редактирования appWrap.appWrapperFunc(); end function saveAppWrapperCode(callbackContext) + % Сохранение отредактированного кода обёртки appWrap.saveAppWrapperCode(); end function openAppWrapperCode(callbackContext) + % Открытие кода обёртки во внешнем редакторе appWrap.openAppWrapperCode(); end - %% USER CODE + %% USER CODE - callback-функции для пользовательского кода function srcTable(callbackContext) + % Обновление таблицы исходных файлов customtable.update('srcTable'); end function incTable(callbackContext) + % Обновление таблицы заголовочных файлов customtable.update('incTable'); end function btnAddSrc(callbackContext) + % Добавление исходных файлов через диалог выбора mcuPath.addSourceFileTable('srcTable', 'Выберите исходные файлы'); end function btnAddInc(callbackContext) + % Добавление путей включения через диалог выбора mcuPath.addPathTable('incTable', 'Выберите папку с заголовочными файлами'); end - %% PERIPH CONFIG + %% PERIPH CONFIG - callback-функции для конфигурации периферии function periphPath_add(callbackContext) + % Добавление пути к конфигурационному файлу периферии mcuPath.addAnyFile('periphPath'); end function periphUpdate(callbackContext) - modelName = bdroot(gcb); % получить имя верхнего уровня модели + % Обновление конфигурации периферии с асинхронным управлением + modelName = bdroot(gcb); blockName = gcb; - mgr = asynchManage(modelName, blockName); % создать объект класса - mgr.updateGUIfromConfig(); % запустить сохранение и обновление + mgr = asynchManage(modelName, blockName); + mgr.updateGUIfromConfig(); % Запуск обновления через таймер end - %% COMPILE + %% COMPILE - callback-функции для компиляции function compile(callbackContext) + % Компиляция S-функции compiler.compile(); end function setSFuncName(callbackContext) + % Установка имени S-функции в исходном коде block = gcb; - % Получаем параметр имени S-Function из маски блока newName = mcuMask.get_name(); - % Путь к файлу, в котором надо заменить строку - cFilePath = fullfile(pwd, mcuPath.get('wrapperPath'), 'MCU.c'); % <-- укажи правильный путь + % Путь к файлу MCU.c + cFilePath = fullfile(pwd, mcuPath.get('wrapperPath'), 'MCU.c'); - % Считаем файл в память + % Чтение файла try fileText = fileread(cFilePath); catch return; end - % Регулярное выражение для поиска строки с define - % Заменим строку вида: #define S_FUNCTION_NAME old_name + % Поиск и замена определения имени S-функции pattern = '#define\s+S_FUNCTION_NAME\s+\w+'; - - % Новая строка newLine = ['#define S_FUNCTION_NAME ', newName]; - - % Замена updatedText = regexprep(fileText, pattern, newLine); - % Записываем обратно в файл + % Запись изменений обратно в файл fid = fopen(cFilePath, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл для записи.'); @@ -159,39 +175,33 @@ classdef mcuMask fclose(fid); end - %% LINK TO EXTERNAL CONSOLE + %% LINK TO EXTERNAL CONSOLE - callback для внешней консоли function findjobj_link(callbackContext) - web https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects; + % Открытие ссылки на утилиту findjobj + web('https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects'); end end -%% GENERAL TOOLS +%% GENERAL TOOLS - общие утилиты для работы с маской methods(Static, Access = public) function updateModel() + % Обновление модели с компиляцией S-функции 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); - % Включаем возможность изменения маски set_param(blockPath, 'MaskSelfModifiable', 'on'); - % Считываем текущие значения параметров маски + % Применяем текущие значения маски currentMaskValues = get_param(blockPath, 'MaskValues'); - - % Применяем текущие значения заново, чтобы "применить" маску set_param(blockPath, 'MaskValues', currentMaskValues); save_system(modelName); catch ME @@ -201,24 +211,25 @@ classdef mcuMask end function open(blockPath, clear_flag) + % Открытие маски блока open_system(blockPath, 'mask'); mcuMask.disp(clear_flag, ''); end function name = get_name() + % Получение имени S-функции из параметра маски block = gcb; - % Получаем параметр имени S-Function из маски блока name = get_param(block, 'sfuncName'); 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 @@ -226,8 +237,8 @@ classdef mcuMask end end - function children = get_children(ctrl) + % Получение дочерних элементов управления маски if isprop(ctrl, 'DialogControls') children = ctrl.DialogControls; elseif isprop(ctrl, 'Controls') @@ -240,27 +251,28 @@ classdef mcuMask end function params = collect_all_parameters(container) + % Рекурсивный сбор всех параметров маски params = {}; children = container.DialogControls; for i = 1:numel(children) ctrl = children(i); if isa(ctrl, 'Simulink.dialog.Tab') - % Если вкладка — рекурсивно собираем параметры внутри неё + % Рекурсивный обход вкладок params = [params, mcuMask.collect_all_parameters(ctrl)]; else - % Иначе это параметр — добавляем имя - params{end+1} = ctrl.Name; %#ok + % Добавление имени параметра + params{end+1} = ctrl.Name; end end end function delete_all_tabs(mask, container) + % Рекурсивное удаление всех вкладок маски children = container.DialogControls; - % Идём в обратном порядке, чтобы безопасно удалять for i = numel(children):-1:1 ctrl = children(i); if isa(ctrl, 'Simulink.dialog.Tab') - % Сначала рекурсивно удаляем вкладки внутри текущей вкладки + % Рекурсивное удаление вложенных вкладок mcuMask.delete_all_tabs(mask, ctrl); try container.removeDialogControl(ctrl.Name); @@ -271,14 +283,9 @@ classdef mcuMask end end - function tool(text, example) - % Устанавливает заданный текст в параметр Text Area 'toolText' через объект маски - - % Получаем ссылку на текущий блок + % Установка текста подсказки и примера в элементы маски block = gcb; - - % Получаем объект маски mask = Simulink.Mask.get(block); toolTextArea = mask.getDialogControl('toolText'); @@ -288,15 +295,14 @@ classdef mcuMask end function disp(clcFlag, varargin) + % Вывод текста в консоль маски if clcFlag set_param(gcb, 'consoleOutput', ''); end if length(varargin) == 1 && ischar(varargin{1}) - % Если передан один аргумент — просто строка, передаем напрямую out = varargin{1}; else - % Иначе считаем, что первый аргумент — формат, остальные — параметры out = sprintf(varargin{:}); end @@ -304,20 +310,18 @@ classdef mcuMask if ~strcmp(out, '') && ~strcmp(out_now, '') set_param(gcb, 'consoleOutput', [out_now newline out]); else - set_param(gcb, 'consoleOutput', [out_now out]); + set_param(gcb, 'consoleOutput', [out_now out]); + end end - end - function updateModelAsync() + % Асинхронное обновление модели через таймер mdl = bdroot(gcb); try - % Применить изменения, если есть set_param(mdl, 'ApplyChanges', 'on'); catch beep - % Игнорировать, если не удалось end t = timer('StartDelay', 0.01, 'TimerFcn', @(~,~) set_param(mdl, 'SimulationCommand', 'update')); @@ -325,8 +329,8 @@ classdef mcuMask end function v = getMyLibVersion() - v = 'pre-1.03'; + % Получение версии библиотеки + v = 'pre-1.04'; end - end end \ No newline at end of file diff --git a/McuLib/m/mcuPath.m b/McuLib/m/mcuPath.m index fefc8b8..273189f 100644 --- a/McuLib/m/mcuPath.m +++ b/McuLib/m/mcuPath.m @@ -1,109 +1,150 @@ classdef mcuPath + % Класс для работы с путями файлов и папок в маске Simulink + % Обеспечивает преобразование путей, добавление в таблицы и параметры + methods(Static) - %% GET PATH FROM PARAM + %% GET PATH FROM PARAM - получение путей из параметров маски function path = get(paramName) + % Получение значения пути из параметра маски + % paramName - имя параметра маски содержащего путь + % Возвращает строку с путём (абсолютным или относительным) + blockPath = gcb; path = get_param(blockPath, paramName); end - %% ADD PATH TO TABLE + %% ADD PATH TO TABLE - добавление путей в табличные параметры function addSourceFileTable(targetParamName, message) - % Открываем проводник для выбора файлов + % Добавление исходных файлов в таблицу через диалог выбора + % targetParamName - имя табличного параметра маски + % message - сообщение в диалоге выбора + + % Открываем проводник для выбора файлов с фильтрами [files, pathstr] = uigetfile({ ... '*.c;*.cpp', 'Исходные файлы (*.c, *.cpp)'; ... '*.obj;*.lib', 'Библиотеки (*.obj, *.lib)'; ... '*.*', 'Все файлы (*.*)'}, ... message, ... - 'MultiSelect', 'on'); + 'MultiSelect', 'on'); % Разрешаем множественный выбор if isequal(files, 0) - return; % Отмена выбора + return; % Пользователь отменил выбор end + + % Нормализуем входные данные: один файл -> cell array if ischar(files) - files = {files}; % Один файл — в cell + files = {files}; 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) - % Открываем проводник для выбора папок + % Добавление путей к папкам в таблицу через диалог выбора + % targetParamName - имя табличного параметра маски + % message - сообщение в диалоге выбора + + % Открываем проводник для выбора папки pathstr = uigetdir(pwd, message); if isequal(pathstr, 0) - return; % Отмена выбора + 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 + %% ADD PATH TO EDIT - добавление путей в текстовые параметры function addPath(targetParamName, message) + % Добавление пути к папке в текстовый параметр маски + % 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) + % Добавление пути к файлу в текстовый параметр маски + % targetParamName - имя параметра маски для пути к файлу + % message - сообщение в диалоге выбора (не используется) + block = gcbh; mask = Simulink.Mask.get(block); + + % Открываем проводник для выбора любого файла [file, path] = uigetfile({'*.*','Все файлы (*.*)'}, 'Выберите файл'); + if isequal(file, 0) || isequal(path, 0) - % Отмена выбора — ничего не делаем - return; + return; % Пользователь отменил выбор end + + % Формируем полный путь и преобразуем в относительный fullFilePath = fullfile(path, file); rel = mcuPath.absoluteToRelativePath(fullFilePath); + + % Устанавливаем значение параметра маски param = mask.getParameter(targetParamName); param.Value = rel; end - %% GET PATH STRING + %% GET PATH STRING - утилиты преобразования путей function absPath = getAbsolutePath(relPath) - % relativeToAbsolutePath — преобразует относительный путь в абсолютный. - % - % Если путь уже абсолютный — возвращается он же, приведённый к канонической форме. - % Если путь относительный — преобразуется относительно текущей директории. + % Преобразование относительного пути в абсолютный + % relPath - относительный или абсолютный путь + % Возвращает абсолютный путь в канонической форме - % Проверка: абсолютный ли путь + % Проверка: абсолютный ли путь уже if ispc + % Для Windows: путь начинается с буквы диска или \\ isAbsolute = ~isempty(regexp(relPath, '^[a-zA-Z]:[\\/]', 'once')) || startsWith(relPath, '\\'); else + % Для Unix: путь начинается с / isAbsolute = startsWith(relPath, '/'); end @@ -111,7 +152,7 @@ classdef mcuPath % Канонизируем абсолютный путь (убираем ./, ../ и т.п.) absPath = char(java.io.File(relPath).getCanonicalPath()); else - % Строим абсолютный путь от текущей директории + % Строим абсолютный путь относительно текущей директории cwd = pwd; combined = fullfile(cwd, relPath); absPath = char(java.io.File(combined).getCanonicalPath()); @@ -119,50 +160,52 @@ classdef mcuPath end function rel = absoluteToRelativePath(pathstr) - % absoluteToRelativePath — преобразует абсолютный путь в относительный от текущей директории. + % Преобразование абсолютного пути в относительный относительно текущей директории + % pathstr - абсолютный путь для преобразования + % Возвращает относительный путь % - % Если путь находится в текущей директории или вложенной в неё — добавляется префикс './' - % Если выше — формируются переходы '..' - % Если путь совпадает с текущей директорией — возвращается '.' - + % Примеры: + % Если путь в текущей директории -> './filename' + % Если путь выше -> '../parent/filename' + % Если путь совпадает с текущей директорией -> '.' + % Получаем текущую рабочую директорию 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 76770b7..47cd144 100644 --- a/McuLib/m/mcuPorts.m +++ b/McuLib/m/mcuPorts.m @@ -1,36 +1,51 @@ classdef mcuPorts + % Класс для управления портами ввода-вывода в маске Simulink + % Генерирует конфигурационные файлы для портов на основе параметров маски methods(Static) function write() + % Основная функция записи конфигурации портов в файлы + % Генерирует заголовочный файл и файл реализации для портов + block = gcb; mask = Simulink.Mask.get(block); + + % Пути к конфигурационным файлам hPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper_conf.h'); cPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper.c'); + + % Устанавливаем значения по умолчанию для неиспользуемых портов mcuPorts.defaultUnused(); - %% CREATE + + %% СОЗДАНИЕ КОНФИГУРАЦИИ ДЛЯ ВХОДНЫХ ПОРТОВ prefixNumb = 'IN'; [widths, portPrefixes] = mcuPorts.getMaskNames('in'); + % Генерируем текст для заголовочного файла и файла реализации headerText = mcuPorts.addPortHeaderDefines('', widths, prefixNumb, portPrefixes); cText = mcuPorts.addPortCDefines('', widths, prefixNumb, portPrefixes); + %% СОЗДАНИЕ КОНФИГУРАЦИИ ДЛЯ ВЫХОДНЫХ ПОРТОВ prefixNumb = 'OUT'; [widths, portPrefixes] = mcuPorts.getMaskNames('out'); + % Добавляем выходные порты к существующей конфигурации headerText = mcuPorts.addPortHeaderDefines(headerText, widths, prefixNumb, portPrefixes); cText = mcuPorts.addPortCDefines(cText, widths, prefixNumb, portPrefixes); - %% WRITE - + %% ЗАПИСЬ В ФАЙЛЫ + % Читаем существующее содержимое файлов hCode = fileread(hPath); hCode = regexprep(hCode, '\r\n?', '\n'); cCode = fileread(cPath); cCode = regexprep(cCode, '\r\n?', '\n'); + % Вставляем сгенерированную конфигурацию в заголовочный файл code = editCode.insertSection(hCode, '// INPUT/OUTPUTS PARAMS', headerText.PARAMS); code = editCode.insertSection(code, '// INPUT/OUTPUTS AUTO-PARAMS', headerText.AUTO_PARAMS); + % Записываем обновленный заголовочный файл fid = fopen(hPath, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл для записи'); @@ -38,7 +53,10 @@ classdef mcuPorts fwrite(fid, code); fclose(fid); + % Вставляем сгенерированную конфигурацию в файл реализации code = editCode.insertSection(cCode, '// INPUT/OUTPUTS AUTO-PARAMS', cText); + + % Записываем обновленный файл реализации fid = fopen(cPath, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл для записи'); @@ -47,39 +65,46 @@ classdef mcuPorts fclose(fid); end - function [portwidth, defnames] = getMaskNames(port_prefix) + % Получение имен и ширин портов из параметров маски + % port_prefix - префикс портов ('in' или 'out') + % Возвращает ширины портов и их имена + block = gcb; - % Получаем значение из спиннера mask = Simulink.Mask.get(block); + + % Получаем количество портов из спиннера paramName = sprintf('%sNumb', port_prefix); param = mask.getParameter(paramName); numb = str2double(param.Value); - - % Инициализируем массив для значений + % Инициализируем массивы для имен и ширин портов defnames = strings(1, numb); portwidth = []; - % Чтение значений edit-параметров + % Читаем значения параметров для каждого порта for i = 1:numb + % Получаем имя порта paramName = sprintf('%s_port_%d_name', port_prefix, i); param = mask.getParameter(paramName); defnames(i) = param.Value; + + % Получаем ширину порта paramName = sprintf('%s_port_%d_width', port_prefix, i); param = mask.getParameter(paramName); portwidth(i) = str2double(param.Value); end end - function updateMask() + % Обновление видимости параметров портов в маске + % Вызывается при изменении количества портов mcuPorts.updateMask_prefix('in'); mcuPorts.updateMask_prefix('out'); end - function defaultUnused() + % Установка значений по умолчанию для неиспользуемых портов mcuPorts.defaultUnused_prefix('in'); mcuPorts.defaultUnused_prefix('out'); end @@ -87,84 +112,93 @@ classdef mcuPorts methods(Static, Access=private) - - function updateMask_prefix(port_prefix) + % Обновление видимости параметров для конкретного типа портов + % port_prefix - префикс портов ('in' или 'out') + block = gcb; - % Получаем значение из спиннера mask = Simulink.Mask.get(block); + + % Получаем текущее количество портов paramName = sprintf('%sNumb', port_prefix); param = mask.getParameter(paramName); n = str2double(param.Value); - % Максимальное количество портов + % Максимальное количество портов из диапазона параметра maxPorts = param.Range(2); - % Проходим по всем edit-полям + % Обходим все возможные порты for i = 1:maxPorts - % Формируем имя параметра + % Формируем имена параметров для имени и ширины порта paramDefName = sprintf('%s_port_%d_name', port_prefix, i); paramWidthName = sprintf('%s_port_%d_width', port_prefix, i); paramDef = mask.getParameter(paramDefName); paramWidth = mask.getParameter(paramWidthName); if i <= n - % Показываем параметр + % Показываем параметры для используемых портов paramDef.Visible = 'on'; paramWidth.Visible = 'on'; - % Если значение пустое — задаём дефолтное + % Устанавливаем значения по умолчанию если пустые if isempty(strtrim(paramDef.Value)) paramDef.Value = upper(port_prefix); end - % Если значение пустое — задаём дефолтное if isempty(strtrim(paramWidth.Value)) || strcmp(paramWidth.Value, '0') paramWidth.Value = '16'; end else - % Скрываем параметр + % Скрываем параметры для неиспользуемых портов paramDef.Visible = 'off'; paramWidth.Visible = 'off'; end end end - function defaultUnused_prefix(port_prefix) + % Установка значений по умолчанию для неиспользуемых портов + % port_prefix - префикс портов ('in' или 'out') + block = gcb; - % Получаем значение из спиннера mask = Simulink.Mask.get(block); + + % Получаем количество используемых портов paramName = sprintf('%sNumb', port_prefix); param = mask.getParameter(paramName); numb = str2double(param.Value); % Максимальное количество портов maxPorts = param.Range(2); - % Чтение значений edit-параметров + + % Устанавливаем значения по умолчанию для неиспользуемых портов for i = numb+1:maxPorts paramName = sprintf('%s_port_%d_name', port_prefix, i); param = mask.getParameter(paramName); - param.Value = upper(port_prefix); + param.Value = upper(port_prefix); % Имя по умолчанию + paramName = sprintf('%s_port_%d_width', port_prefix, i); param = mask.getParameter(paramName); - param.Value = '16'; + param.Value = '16'; % Ширина по умолчанию end end - function headerText = addPortHeaderDefines(existingText, widths, prefixNumb, portPrefixes) - % existingText — структура с полями PARAMS, AUTO_PARAMS - % widths — вектор ширин портов - % prefixNumb — префикс общего счётчика (например, 'OUT') - % portPrefixes — {'OUT', 'OUT', ...} - + % Генерация define-макросов для заголовочного файла + % existingText - существующий текст конфигурации + % widths - вектор ширин портов + % prefixNumb - общий префикс ('IN' или 'OUT') + % portPrefixes - массив префиксов для каждого порта + % Возвращает структуру с PARAMS и AUTO_PARAMS + n = numel(widths); upperPrefix = upper(prefixNumb); - % === PARAMS === + % === БАЗОВЫЕ ПАРАМЕТРЫ === lines = { sprintf('#define %s_PORT_NUMB %d', upperPrefix, n) }; + + % Добавляем define для ширины каждого порта for i = 1:n lines{end+1} = sprintf('#define %s_PORT_%d_WIDTH %d', ... upper(portPrefixes{i}), i, widths(i)); @@ -172,10 +206,10 @@ classdef mcuPorts newParams = strjoin(lines, newline); newParams = [newParams, newline]; - % === AUTO-PARAMS === + % === АВТОМАТИЧЕСКИ ГЕНЕРИРУЕМЫЕ ПАРАМЕТРЫ === lines = {}; - % Формируем выражение суммы ширин с добавлением PORT + % Формируем выражение для общего размера буфера sumExprParts = cell(1,n); for i = 1:n sumExprParts{i} = sprintf('%s_PORT_%d_WIDTH', upper(portPrefixes{i}), i); @@ -188,6 +222,7 @@ classdef mcuPorts lines{end+1} = ''; lines{end+1} = sprintf('/// === Смещения массивов (внутри общего буфера) ==='); + % Генерируем define для смещений каждого массива в буфере for i = 1:n if i == 1 lines{end+1} = sprintf('#define OFFSET_%s_ARRAY_1 0', upperPrefix); @@ -198,7 +233,7 @@ classdef mcuPorts end newAuto = strjoin(lines, newline); - % === Добавление к существующему === + % === ОБЪЕДИНЕНИЕ С СУЩЕСТВУЮЩИМ ТЕКСТОМ === if isfield(existingText, 'PARAMS') headerText.PARAMS = [existingText.PARAMS, newline, newParams]; else @@ -212,40 +247,43 @@ classdef mcuPorts end end - function cText = addPortCDefines(existingText, widths, prefixNumb, portPrefixes) - % existingText — существующий текст .c - % widths — вектор ширин портов - % prefixNumb — общий префикс ('OUT') - % portPrefixes — {'OUT', 'LOG', ...} - + % Генерация кода для файла реализации (.c) + % existingText - существующий текст + % widths - вектор ширин портов + % prefixNumb - общий префикс ('IN' или 'OUT') + % portPrefixes - массив префиксов для каждого порта + % Возвращает текст для вставки в .c файл + n = numel(widths); upperPrefix = upper(prefixNumb); lowerPrefix = lower(prefixNumb); lines = {}; - % === Таблица длин === + % === ТАБЛИЦА ДЛИН МАССИВОВ === lines{end+1} = '/**'; lines{end+1} = sprintf(' * @brief Таблица длин массивов %s', upperPrefix); lines{end+1} = ' */'; - % Здесь используем общий префикс для количества портов lines{end+1} = sprintf('const int %sLengths[%s_PORT_NUMB] = {', lowerPrefix, upperPrefix); + + % Заполняем таблицу длин for i = 1:n comma = ','; if i == n comma = ''; end - % Используем макросы с портовыми префиксами из portPrefixes lines{end+1} = sprintf(' %s_PORT_%d_WIDTH%s', upper(portPrefixes{i}), i, comma); end lines{end+1} = '};'; - % === Таблица смещений === + % === ТАБЛИЦА СМЕЩЕНИЙ === lines{end+1} = '/**'; lines{end+1} = sprintf(' * @brief Таблица смещений в выходном массиве %s', upperPrefix); lines{end+1} = ' */'; lines{end+1} = sprintf('const int %sOffsets[%s_PORT_NUMB] = {', lowerPrefix, upperPrefix); + + % Заполняем таблицу смещений for i = 1:n comma = ','; if i == n @@ -258,6 +296,7 @@ classdef mcuPorts newText = strjoin(lines, newline); + % Объединяем с существующим текстом if nargin < 1 || isempty(existingText) cText = newText; else @@ -265,20 +304,14 @@ classdef mcuPorts end end - - - - function val = iff(cond, a, b) - % Условное выражение inline + % Вспомогательная функция - тернарный оператор + % cond - условие, a - значение если true, b - значение если false if cond val = a; else val = b; end end - - end - end \ No newline at end of file diff --git a/McuLib/m/mexing.m b/McuLib/m/mexing.m index 1e3b9e2..4524278 100644 --- a/McuLib/m/mexing.m +++ b/McuLib/m/mexing.m @@ -1,146 +1,153 @@ -% Компилирует S-function +% Компилирует S-function для блока микроконтроллера в Simulink +% compile_mode: 1 - компиляция, 0 - обновление конфигурации function res = mexing(compile_mode) global Ts - Ts = 0.00001; + Ts = 0.00001; % Установка глобального времени дискретизации if compile_mode == 1 - delete('*.mexw64') - delete('*.mexw64.pdb') - delete([mcuPath.get('wrapperPath'), '\Outputs\*.*']); - set_param(gcb, 'consoleOutput', ''); - compiler.updateRunBat(); - % Дефайны - definesUserArg = parseDefinesMaskText(); - definesWrapperConfigArg = buildWrapperDefinesString(); - definesPeriphConfigArg = buildConfigDefinesString(); - definesConfigArg = [definesWrapperConfigArg + " " + definesPeriphConfigArg]; - - %режимы компиляции - if mcuMask.read_checkbox('enableDebug') - modeArg = "debug"; - else - modeArg = "release"; - end - if mcuMask.read_checkbox('fullOutput') || mcuMask.read_checkbox('extConsol') - echoArg = 'echo_enable'; - else - echoArg = 'echo_disable'; - end - - [includesArg, codeArg] = make_mex_arguments('incTable', 'srcTable'); - - Name = mcuMask.get_name(); - - % Вызов батника с двумя параметрами: includes и code - 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); - else - [status, cmdout]= system(cmd); - end - - % Сохраним вывод в параметр маски с именем 'consoleOutput' - mcuMask.disp(1, cmdout); + % === РЕЖИМ КОМПИЛЯЦИИ === + setenv('VSLANG', '1033'); % Английский для Visual Studio + % Обновление параметров блока block = gcb; - newName = get_param(block, 'sfuncName'); oldName = get_param(block, 'FunctionName'); if ~strcmp(newName, oldName) - set_param(block, 'FunctionName', newName); + set_param(block, 'FunctionName', newName); % Обновление имени функции end newParam = get_param(block, 'sfuncParam'); oldParam = get_param(block, 'Parameters'); if ~strcmp(newParam, oldParam) - set_param(block, 'Parameters', newParam); + set_param(block, 'Parameters', newParam); % Обновление параметров end + + % Очистка предыдущих файлов компиляции + delete('*.mexw64') + delete('*.mexw64.pdb') + delete([mcuPath.get('wrapperPath'), '\Outputs\*.*']); + set_param(gcb, 'consoleOutput', ''); % Очистка консоли вывода + + % Обновление BAT-файла для компиляции + compiler.updateRunBat(); + + % Формирование дефайнов для компиляции + definesUserArg = parseDefinesMaskText(); % Пользовательские дефайны + definesWrapperConfigArg = buildWrapperDefinesString(); % Дефайны обёртки + definesPeriphConfigArg = buildConfigDefinesString(); % Дефайны периферии + definesConfigArg = [definesWrapperConfigArg + " " + definesPeriphConfigArg]; + + % Определение режимов компиляции + if mcuMask.read_checkbox('enableDebug') + modeArg = "debug"; % Режим отладки + else + modeArg = "release"; % Релизный режим + end + + if mcuMask.read_checkbox('fullOutput') || mcuMask.read_checkbox('extConsol') + echoArg = 'echo_enable'; % Подробный вывод + else + echoArg = 'echo_disable'; % Минимальный вывод + end + + % Формирование аргументов для компиляции + [includesArg, codeArg] = make_mex_arguments('incTable', 'srcTable'); + Name = mcuMask.get_name(); % Имя S-функции + + % Вызов батника компиляции + 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); + else + % Запуск в фоновом режиме + [status, cmdout]= system(cmd); + end + + % Сохранение вывода в параметр маски + mcuMask.disp(1, cmdout); if status == 0 - res = 0; + res = 0; % Успешная компиляция else - res = 1; + res = 1; % Ошибка компиляции end - beep + beep % Звуковое уведомление + else + % === РЕЖИМ ОБНОВЛЕНИЯ КОНФИГУРАЦИИ === blockPath = gcb; - config = configJs.read(blockPath); - config = configJs.update(blockPath, config); - configJs.write(config); - periphConfig.updateMask(blockPath, config); + config = configJs.read(blockPath); % Чтение конфигурации + config = configJs.update(blockPath, config); % Обновление конфигурации + configJs.write(config); % Запись конфигурации + periphConfig.updateMask(blockPath, config); % Обновление маски end end -%% COMPILE PARAMS - +%% COMPILE PARAMS - функции формирования параметров компиляции function [includesArg, codeArg] = make_mex_arguments(incTableName, srcTableame) -%MAKE_MEX_ARGUMENTS Формирует строки аргументов для вызова mex-компиляции через батник -% -% [includesArg, codeArg] = make_mex_arguments(includesCell, codeCell) +% Формирует строки аргументов для вызова mex-компиляции через батник % % Вход: -% includesCell — ячейковый массив путей к директориям include -% codeCell — ячейковый массив исходных файлов +% incTableName - имя таблицы с путями включения +% srcTableame - имя таблицы с исходными файлами % % Выход: -% includesArg — строка для передачи в батник, например: "-I"inc1" -I"inc2"" -% codeArg — строка с исходниками, например: ""src1.c" "src2.cpp"" +% includesArg - строка с флагами включения (-I"path") +% codeArg - строка с исходными файлами ("file1.c" "file2.cpp") - - % Здесь пример получения из маски текущего блока (замени по своему) + % Получение данных из таблиц маски includesCell = customtable.parse(incTableName); codeCell = customtable.parse(srcTableame); - % Оборачиваем пути в кавычки и добавляем -I + % Формирование строки путей включения с флагом -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}; % Текст с пользовательскими определениями + definesText = maskValues{idxUserDefs}; - % Убираем буквальные символы \n и \r + % Обработка специальных символов definesText = strrep(definesText, '\n', ' '); definesText = strrep(definesText, '\r', ' '); - % Разбиваем по переносам строк + % Разбиение на строки lines = split(definesText, {'\n', '\r\n', '\r'}); - parts = strings(1,0); % пустой массив строк + parts = strings(1,0); % массив для хранения дефайнов for k = 1:numel(lines) line = strtrim(lines{k}); @@ -148,7 +155,7 @@ function definesUserArg = parseDefinesMaskText() continue; end - % Разбиваем по пробелам, чтобы получить отдельные определения в строке + % Разбиение строки на токены tokens = split(line); for t = 1:numel(tokens) @@ -157,11 +164,13 @@ function definesUserArg = parseDefinesMaskText() 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); @@ -172,13 +181,13 @@ function definesUserArg = parseDefinesMaskText() definesUserArg = strjoin(parts, ' '); end - - function definesWrapperArg = buildConfigDefinesString() +% Формирование дефайнов из конфигурации периферии + blockHandle = gcbh; mask = Simulink.Mask.get(blockHandle); - tabName = 'configTabAll'; % Имя вкладки (Prompt) + tabName = 'configTabAll'; % Имя вкладки с конфигурацией tabCtrl = mask.getDialogControl(tabName); @@ -186,17 +195,15 @@ function definesWrapperArg = buildConfigDefinesString() error('Вкладка с названием "%s" не найдена в маске', tabName); end - + % Сбор всех параметров из вкладки конфигурации params = mcuMask.collect_all_parameters(tabCtrl); definesWrapperArg = ''; for i = 1:numel(params) - % Получаем имя параметра из контрола paramName = string(params(i)); try - % Получаем объект параметра по имени param = mask.getParameter(paramName); - % Определяем тип параметра + % Обработка разных типов параметров switch lower(param.Type) case 'checkbox' definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0); @@ -205,33 +212,32 @@ function definesWrapperArg = buildConfigDefinesString() case 'popup' definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0); otherwise - % Необрабатываемые типы + % Пропуск необрабатываемых типов end catch ME - % warning('Не удалось получить параметр "%s": %s', paramName, ME.message); + % Игнорирование ошибок для отсутствующих параметров end end end - -%% PARSE FUNCTIONS - function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_define) +% Добавление дефайна на основе параметра маски + blockHandle = gcbh; mask = Simulink.Mask.get(blockHandle); - % Получаем MaskValues, MaskNames + % Получение значений маски maskValues = get_param(blockHandle, 'MaskValues'); paramNames = get_param(blockHandle, 'MaskNames'); - param = mask.getParameter(paramName); % для alias + param = mask.getParameter(paramName); - % Найдём индекс нужного параметра + % Поиск индекса параметра idxParam = find(strcmp(paramNames, paramName), 1); if isempty(idxParam) error('Parameter "%s" not found in block mask parameters.', paramName); end - % Берём alias из маски + % Определение имени дефайна (алиас или значение) val = ''; if ~strcmp(param.Type, 'popup') def_name = param.Alias; @@ -248,23 +254,24 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_ return; end + % Формирование дефайна в зависимости от типа параметра if val_define ~= 0 - % Значение параметра + % Параметры с значениями val = maskValues{idxParam}; if strcmp(param.Evaluate, 'on') - val = evalin('base', val); % Вычисляем выражение - val = num2str(val); % Преобразуем результат в строку + val = evalin('base', val); % Вычисление выражений + val = num2str(val); end - % Формируем define с кавычками и значением newDefine = ['-D"' def_name '__EQ__' val '"']; elseif ~strcmp(param.Type, 'popup') + % Чекбоксы if mcuMask.read_checkbox(paramName) - % Формируем define с кавычками без значения newDefine = ['-D"' def_name '"']; else newDefine = ''; end else + % Выпадающие списки if strcmp(param.Alias, '') newDefine = ['-D"' def_name '"']; else @@ -272,9 +279,7 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_ end end - - - % Добавляем новый define к существующему (string) + % Добавление дефайна к результирующей строке if isempty(definesWrapperArg) || strlength(strtrim(definesWrapperArg)) == 0 definesWrapperArg = newDefine; else @@ -282,19 +287,22 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_ end end +%% CONSOLE FUNCTIONS - функции работы с консолью -%% CONSOLE FUNCTIONS function cmdret = runBatAndShowOutput(cmd) +% Запуск BAT-файла с отображением вывода в реальном времени + import java.io.*; import java.lang.*; - cmdEnglish = ['chcp 437 > nul && ' cmd]; + + 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 = ""; % Здесь будем накапливать весь вывод + cmdret = ""; while true if reader.ready() @@ -302,36 +310,37 @@ function cmdret = runBatAndShowOutput(cmd) if isempty(line) break; end - cmdret = cmdret + string(line) + newline; % сохраняем вывод - % Здесь выводим только новую строку - safeLine = strrep(line, '''', ''''''); % Экранируем апострофы - logWindow_append(safeLine); - drawnow; % обновляем GUI + cmdret = cmdret + string(line) + newline; + safeLine = strrep(line, '''', ''''''); % Экранирование кавычек + logWindow_append(safeLine); % Вывод в окно лога + drawnow; else if ~process.isAlive() - % дочитываем оставшиеся строки + % Дочтение оставшегося вывода while reader.ready() line = char(reader.readLine()); if isempty(line) break; end - cmdret = cmdret + string(line) + newline; % сохраняем вывод + cmdret = cmdret + string(line) + newline; safeLine = strrep(line, '''', ''''''); logWindow_append(safeLine); drawnow; end break; end - pause(0.2); + 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', ... @@ -343,10 +352,12 @@ function logWindow_append(line) 'BackgroundColor', 'white', ... 'Tag', 'LogWindowFigure'); - jScrollPane = findjobj(hEdit); % JScrollPane - jTextArea = jScrollPane.getViewport.getView; % JTextArea внутри JScrollPane + % Получение Java-компонентов для управления скроллингом + jScrollPane = findjobj(hEdit); + jTextArea = jScrollPane.getViewport.getView; end + % Добавление новой строки oldText = get(hEdit, 'String'); if ischar(oldText) oldText = {oldText}; @@ -354,32 +365,30 @@ function logWindow_append(line) set(hEdit, 'String', [oldText; {line}]); drawnow; - % Автоскролл вниз: + + % Автоскроллинг вниз jTextArea.setCaretPosition(jTextArea.getDocument.getLength); drawnow; end - -%% READ CONFIGS - function isOpen = isMaskDialogOpen(blockPath) +% Проверка открыто ли диалоговое окно маски + isOpen = false; try - % Получаем имя блока blockName = get_param(blockPath, 'Name'); - % Получаем список окон MATLAB GUI + % Получение всех открытых окон Java 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) @@ -387,12 +396,11 @@ function isOpen = isMaskDialogOpen(blockPath) return; end catch - % Окно не имеет заголовка — пропускаем + % Пропуск окон без заголовка end end end catch isOpen = false; end -end - +end \ No newline at end of file diff --git a/McuLib/m/periphConfig.m b/McuLib/m/periphConfig.m index a6a8b04..d04832c 100644 --- a/McuLib/m/periphConfig.m +++ b/McuLib/m/periphConfig.m @@ -1,37 +1,45 @@ classdef periphConfig + % Класс для управления конфигурацией периферии в маске Simulink + % Динамически создает элементы маски на основе JSON конфигурации methods(Static) function updateMask(blockPath, config) - % blockPath = [blockPath '/MCU']; - - % Проверяем, была ли маска открыта -% wasOpen = isMaskDialogOpen(blockPath); + % Основная функция обновления маски на основе конфигурации + % Динамически создает вкладки и параметры для периферийных модулей + % blockPath - путь к блоку Simulink + % config - структура конфигурации из JSON файла + mask = Simulink.Mask.get(blockPath); + % Сохраняем состояние таблиц перед изменением маски tableNames = {'incTable', 'srcTable'}; columns_backup = customtable.save_all_tables(tableNames); + try + % Получаем настройки ширины строк из параметра маски rowWidth = str2double(get_param(blockPath, 'rowWidth')); + % Очищаем контейнер конфигурации перед созданием новых элементов 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...) + % Проходим по каждому модулю периферии (ADC, TIM, UART и т.д.) periphs = fieldnames(config); for i = 1:numel(periphs) periph = periphs{i}; - % Сохраняем код, если он есть + % Сохраняем код модуля (исходники и инклюды) в скрытые параметры periphConfig.store_single_periph_code(mask, periph, config.(periph)); - % Проверяем наличие Defines + % Пропускаем модули без секции Defines if ~isfield(config.(periph), 'Defines') continue; end @@ -39,68 +47,78 @@ classdef periphConfig defines = config.(periph).Defines; defNames = fieldnames(defines); - % Создаём вкладку для модуля + % Создаем вкладку для модуля периферии tabCtrl = container.addDialogControl('tab', periph); - tabCtrl.Prompt = [periph ' Config']; + tabCtrl.Prompt = [periph ' Config']; % Отображаемое имя вкладки + % Карта для отслеживания количества строк в каждой вкладке rowCountMap = containers.Map(); + + % Добавляем все параметры Defines в созданную вкладку 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 - % % Повторно открываем маску, если она была открыта - % if wasOpen - % open_system(blockPath, 'mask'); - % end end 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 @@ -109,31 +127,32 @@ classdef periphConfig end else - % Чекбокса нет — просто пытаемся включить вкладку, если она есть + % Чекбокса нет - просто включаем вкладку try tab = container.getDialogControl(periph); tab.Enabled = 'on'; - % Опционально можно синхронизировать параметры, если есть config поле + % Синхронизируем параметры если есть конфигурация if isfield(config, periph) - periphConfig.sync_tab_params(mask, config.(periph), periph); + periphConfig.sync_tab_params(mask, config.(periph), periph); end catch - % Можно не выводить предупреждение — вкладка может быть необязательной - % warning('Вкладка с именем "%s" не найдена.', periph); + % Вкладка может быть необязательной - игнорируем ошибку end end end - end function periphParamCallback(paramName) + % Callback-функция для параметров периферии + % Обрабатывает изменения чекбоксов и других параметров + 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'); @@ -145,11 +164,13 @@ classdef periphConfig 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); @@ -161,19 +182,17 @@ classdef periphConfig return; end - % === Проверка на параметр, связанный с Sources/Includes === - % Проверка: это параметр, у которого есть соответствующий Hidden__Sources или Hidden__Includes + % === Обработка параметров, связанных с Sources/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 @@ -182,49 +201,50 @@ classdef periphConfig 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 - - - % === Если не подошло ни под одно из условий === - % warning('Объект "%s" не поддерживается универсальным коллбеком.', paramName); end function updatePeriphRunMexBat() - % Запись run_mex.bat + % Обновление BAT-файла для компиляции с кодом периферии + % Собирает все исходники и инклюды из скрытых параметров + blockPath = gcb; + % Восстанавливаем код периферии из скрытых параметров маски CodeStruct = periphConfig.restore_periph_code_from_mask(blockPath); periphPath = mcuPath.get('periphPath'); [periphPath, ~, ~] = fileparts(periphPath); + % Добавляем код в BAT-файл компиляции periphConfig.addCodeBat(CodeStruct, periphPath); end - end - - methods(Static, Access=private) - - - function addHiddenParam(mask, containerName, nameBase, kind, existingParams) - % Преобразуем к красивому имени + % Создание скрытого параметра для хранения кода + % nameBase - базовое имя параметра + % kind - тип ('Sources' или 'Includes') + prettyName = strrep(nameBase, '_', ' '); paramName = ['Hidden_' char(nameBase) '_' kind]; + + % Проверяем не существует ли уже параметр if ismember(paramName, existingParams) return; end + % Создаем скрытый параметр mask.addParameter( ... 'Name', paramName, ... 'Type', 'edit', ... @@ -233,14 +253,16 @@ classdef periphConfig '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; % Максимальная глубина рекурсии + maxDepth = 3; % Ограничение глубины рекурсии fields = fieldnames(configStruct); @@ -251,10 +273,11 @@ classdef periphConfig if isstruct(value) if depth < maxDepth - % Рекурсивный вызов для вложенных структур с увеличением глубины + % Рекурсивный вызов для вложенных структур periphConfig.clear_tab_params(mask, value, paramName, depth + 1); end else + % Очищаем параметры Sources/Includes if strcmp(key, 'Sources') || strcmp(key, 'Includes') baseName = configJs.get_final_name_from_prefix(prefix); periphConfig.clear_single_periph_code_param(mask, baseName); @@ -263,12 +286,14 @@ classdef periphConfig end end - function sync_tab_params(mask, configStruct, prefix, depth) + % Рекурсивная синхронизация параметров вкладки + % Используется при включении вкладки + if nargin < 4 depth = 0; end - maxDepth = 3; % Максимальная глубина рекурсии + maxDepth = 3; fields = fieldnames(configStruct); @@ -279,7 +304,6 @@ classdef periphConfig if isstruct(value) if depth < maxDepth - % Рекурсивный вызов для вложенных структур с увеличением глубины periphConfig.sync_tab_params(mask, value, paramName, depth + 1); end else @@ -292,13 +316,15 @@ classdef periphConfig end function create_all_code_storage_params(blockPath, config) + % Создание всех скрытых параметров для хранения кода периферии + mask = Simulink.Mask.get(blockPath); containerName = 'configTabAll'; container = mask.getDialogControl(containerName); existingParams = mcuMask.collect_all_parameters(container); - % Убедимся, что контейнер существует + % Создаем скрытую вкладку для хранения параметров кода tabName = 'hiddenCodeTab'; tab = mask.getDialogControl(tabName); if isempty(tab) @@ -309,20 +335,22 @@ classdef periphConfig tab.Visible = 'off'; end - % Запуск рекурсивного обхода + % Рекурсивный обход конфигурации для создания параметров periphConfig.process_struct_recursive(mask, tabName, config, {}, existingParams); end function process_struct_recursive(mask, tabName, currentStruct, nameStack, existingParams) + % Рекурсивный обход структуры конфигурации + % Создает скрытые параметры для всех Sources и Includes + fields = fieldnames(currentStruct); for i = 1:numel(fields) fieldName = fields{i}; value = currentStruct.(fieldName); - newStack = [nameStack, fieldName]; % Добавляем уровень к имени + newStack = [nameStack, fieldName]; - % Если value — структура, обходим дальше if isstruct(value) - % Проверяем: это структура с Sources/Includes или просто промежуточный узел? + % Проверяем наличие Sources и Includes в структуре hasSources = isfield(value, 'Sources'); hasIncludes = isfield(value, 'Includes'); @@ -339,33 +367,37 @@ classdef periphConfig end end - function cleanup_obsolete_code_params(blockPath, config) + % Очистка устаревших скрытых параметров + % Удаляет параметры для периферии, которой больше нет в конфигурации + mask = Simulink.Mask.get(blockPath); maskParams = mask.Parameters; - % Получаем список актуальных периферий + % Получаем список актуальных периферийных модулей validPeriphs = fieldnames(config); 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 end function codeStruct = restore_periph_code_from_mask(blockPath) + % Восстановление кода периферии из скрытых параметров маски + % Собирает все Sources и Includes в одну структуру + mask = Simulink.Mask.get(blockPath); maskParams = mask.Parameters; @@ -375,26 +407,27 @@ classdef periphConfig for i = 1:numel(maskParams) name = maskParams(i).Name; - % Ищем параметры Sources + % Поиск параметров 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 + allSources = [allSources; lines]; end continue; end - % Ищем параметры Includes + % Поиск параметров Includes tokensInc = regexp(name, '^Hidden_(\w+)_Includes$', 'tokens'); if ~isempty(tokensInc) val = maskParams(i).Value; @@ -407,27 +440,28 @@ classdef periphConfig lines = splitlines(valStr); lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines)); - allIncludes = [allIncludes; lines]; %#ok + allIncludes = [allIncludes; lines]; 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); if isempty(container) warning('Контейнер "%s" не найден.', containerName); return; end - % Рекурсивно собрать все параметры (не вкладки) + % Собираем все параметры в контейнере paramsToDelete = mcuMask.collect_all_parameters(container); % Удаляем все параметры @@ -439,30 +473,27 @@ classdef periphConfig end end - % Рекурсивно удалить все вкладки внутри контейнера + % Рекурсивно удаляем все вкладки mcuMask.delete_all_tabs(mask, container); end - function res = addCodeBat(codeConfig, codePath) - % Добавить сурсы и пути в батник - % Возвращает 0 при успехе, 1 при ошибке + % Добавление кода периферии в BAT-файл компиляции + try - % Формируем строки + % Формируем строки для BAT-файла srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources, codePath); incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes, codePath); - % Записываем результат - res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT'); % Всё прошло успешно + % Обновляем BAT-файл + res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT'); catch - % В случае ошибки просто возвращаем 1 - res = 1; + res = 1; % Ошибка end end function res = addUserFunctions(userCodeConfig) - % Добавить функции и дефайны в исходный код wrapper - % userCodeConfig — структура config.UserCode + % Добавление пользовательских функций в код обёртки initFuncsText = ''; simFuncsText = ''; @@ -471,43 +502,45 @@ classdef periphConfig if isfield(userCodeConfig, 'Functions') funcs = userCodeConfig.Functions; + % Обрабатываем функции инициализации if isfield(funcs, 'PeriphInit') initFuncs = funcs.PeriphInit; initFuncsText = strjoin(strcat('\t', initFuncs, ';'), '\n'); end + % Обрабатываем функции симуляции if isfield(funcs, 'PeriphSimulation') simFuncs = funcs.PeriphSimulation; simFuncsText = strjoin(strcat('\t', simFuncs, ';'), '\n'); end + % Обрабатываем функции деинициализации if isfield(funcs, 'PeriphDeinit') deinitFuncs = funcs.PeriphDeinit; deinitFuncsText = strjoin(strcat('\t', deinitFuncs, ';'), '\n'); end + % Записываем функции в файл обёртки res = periphConfig.writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText); end end function res = writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText) - % Входные параметры: - % srcText - текст для записи set code_... - % incText - текст для записи set includes_... - % - % Возвращает: - % res - 0 при успехе, 1 при ошибке + % Запись пользовательских функций в файл mcu_wrapper.c + wrapPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper.c'); res = 1; try + % Читаем текущее содержимое файла code = fileread(wrapPath); code = regexprep(code, '\r\n?', '\n'); - % Записываем строки initFuncsText и simFuncsText + % Вставляем функции в соответствующие секции code = editCode.insertSection(code, '// PERIPH INIT', initFuncsText); code = editCode.insertSection(code, '// PERIPH SIM', simFuncsText); code = editCode.insertSection(code, '// PERIPH DEINIT', deinitFuncsText); + % Записываем обновленный файл fid = fopen(wrapPath, 'w', 'n', 'UTF-8'); if fid == -1 error('Не удалось открыть файл для записи'); @@ -521,18 +554,22 @@ classdef periphConfig end 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 и т.п.) - + % Добавление одного параметра конфигурации в маску + % mask - объект маски + % containerName - имя контейнера + % periphName - имя периферийного модуля + % defPrompt - имя параметра + % def - структура с описанием параметра + % rowCountMap - карта для отслеживания строк + % rowWidth - ширина строки + + % Инициализация счетчика строк для этого модуля 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 @@ -540,20 +577,20 @@ classdef periphConfig end rowCountMap(periphName) = rowCount; - % Найдем контейнер с таким именем + % Получаем контейнер container = mask.getDialogControl(containerName); if isempty(container) error('Контейнер "%s" не найден в маске.', containerName); end - % Проверим, есть ли вкладка с именем periphName, если нет — создадим + % Создаем вкладку если её нет tabCtrl = mask.getDialogControl(periphName); if isempty(tabCtrl) tabCtrl = container.addDialogControl('tab', periphName); tabCtrl.Prompt = [periphName ' Config']; end - % Определяем тип параметра (checkbox или edit) + % Определяем тип параметра switch lower(def.Type) case 'checkbox' paramType = 'checkbox'; @@ -562,20 +599,20 @@ classdef periphConfig case 'popup' paramType = 'popup'; otherwise - % Игнорируем остальные типы - return; + return; % Пропускаем неизвестные типы end + % Создаем валидное имя параметра paramName = matlab.lang.makeValidName(defPrompt); - % Получаем значение + % Устанавливаем значение по умолчанию if strcmp(paramType, 'popup') if isfield(def, 'Def') && iscell(def.Def) && ~isempty(def.Def) choices = def.Def; - valStr = ''; % по умолчанию — ничего + valStr = ''; elseif isfield(def, 'Options') choices = def.Def; - valStr = ''; % по умолчанию — ничего + valStr = ''; else warning('Popout параметр "%s" не содержит допустимого списка в Def.', defPrompt); return; @@ -636,12 +673,11 @@ classdef periphConfig param.Callback = callback; end - - - %% ELEMENTARY + %% ELEMENTARY FUNCTIONS - базовые вспомогательные функции function clear_single_periph_code_param(mask, periph) - % Очистка кода одного поля конфига + % Очистка кода одного модуля периферии + paramNames = { ['Hidden_' char(periph) '_Sources'], ['Hidden_' char(periph) '_Includes'] @@ -653,14 +689,14 @@ classdef periphConfig 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 @@ -671,7 +707,6 @@ classdef periphConfig end end - % Сохраняем Includes, если они есть if isfield(code, 'Includes') paramName = ['Hidden_' char(periph) '_Includes']; try @@ -683,15 +718,13 @@ classdef periphConfig end end - - function res = ternary(cond, valTrue, valFalse) + % Вспомогательная функция - тернарный оператор if cond res = valTrue; else res = valFalse; end end - end -end +end \ No newline at end of file diff --git a/mcuwrapper.prj b/mcuwrapper.prj index 9b26494..d0ca6ac 100644 --- a/mcuwrapper.prj +++ b/mcuwrapper.prj @@ -7,7 +7,7 @@ Library for run MCU program in Simulink - 1.03 + 1.04 ${PROJECT_ROOT}\MCU Wrapper.mltbx @@ -84,7 +84,6 @@ ${PROJECT_ROOT}\McuLib - ${PROJECT_ROOT}\McuLib\.library_installed.mat ${PROJECT_ROOT}\McuLib\install_my_library.m ${PROJECT_ROOT}\McuLib\lib ${PROJECT_ROOT}\McuLib\m