Compare commits

..

10 Commits

Author SHA1 Message Date
abdf0f1e50 установщик 2025-06-16 08:21:39 +03:00
4c78383edf чуть структурированго но надо еще
перенести работу с конфигом в отдельную папку и реализовать конфиг для всего симулятора. Чтобы можно было легко переносить настройки оболочки
2025-06-16 00:28:13 +03:00
9d5cd30bb8 исправлено авторазмещение 2025-06-15 23:59:06 +03:00
5a92e0bec0 сделано подключение сурсов по конфигу, но надо бы как-то структуризировать 2025-06-15 23:39:34 +03:00
982d29560f надо сделать подключение сурсов по чекбоксам
пока ничего не работает
2025-06-15 16:48:59 +03:00
02379d4c46 обновлен шаблон run_bat и симуликн модель 2025-06-15 16:05:30 +03:00
0145ec818e добавлено авторазмещение параметров по колву элементов в строке
сделана инициализация таблицы по необходимости (деинициализированные колонки)
2025-06-15 15:52:44 +03:00
7c2fb99908 добавлена поддержка popup в конфиге
добавлена задание src в run_bat.mex по чекбоксу
исправлены лишние абзацы в disp
2025-06-15 15:18:11 +03:00
058d3a00cf добавелно включение вкладок по конфигу 2025-06-15 12:52:53 +03:00
edb22966ff структуризировано:
выделены отдельный файл для работы с wrapper и appwrapper
2025-06-15 10:55:05 +03:00
13 changed files with 845 additions and 353 deletions

Binary file not shown.

Binary file not shown.

156
McuLib/m/appWrap.m Normal file
View File

@ -0,0 +1,156 @@
classdef appWrap
methods(Static)
function appWrapperFunc()
block = gcb;
% Получаем имя функции и путь к файлам
[filename, section, tool, example]= appWrap.getAppWrapperUserFile(block);
mcuMask.tool(tool, example);
% Загружаем содержимое файла
set_param(block, 'appWrapperCode', '');
try
code = fileread(filename);
code = regexprep(code, '\r\n?', '\n'); % нормализуем окончания строк
includesText = editCode.extractSection(code, section);
set_param(block, 'appWrapperCode', includesText);
catch
end
% % Поиск тела обычной функции
% expr = sprintf('void %s()', sel);
% funcBody = editCode.extractSection(code, expr);
% set_param(block, 'wrapperCode', funcBody);
end
function saveAppWrapperCode()
block = gcb;
% Получаем имя функции и путь к файлам
[filename, section] = appWrap.getAppWrapperUserFile(block);
if ~isfile(filename)
errordlg(['Файл не найден: ', filename]);
return;
end
sel = get_param(block, 'appWrapperFunc');
basePath = get_param(block, 'appWrapperPath');
if isempty(basePath)
errordlg('Не указан путь к файлам обёртки (wrapperPath).');
return;
end
newBody = get_param(block, 'appWrapperCode');
code = fileread(filename);
code = regexprep(code, '\r\n?', '\n');
newBody = strrep(newBody, '\', '\\');
code = editCode.insertSection(code, section, newBody);
% else
% % Обновляем тело функции
% expr = sprintf('void %s()', sel);
% code = editCode.insertSection(code, expr, newBody);
% end
fid = fopen(filename, 'w', 'n', 'UTF-8');
if fid == -1
errordlg('Не удалось открыть файл для записи');
return;
end
fwrite(fid, code);
fclose(fid);
mcuMask.disp(1, ['Обновлено: ' sel]);
end
function openAppWrapperCode()
block = gcb;
% Получаем имя функции и путь к файлам
filename = appWrap.getAbsolutePath(mcuMask.getAppWrapperUserFile(block));
if exist(filename, 'file') == 2
% Формируем команду без кавычек
cmd = sprintf('rundll32.exe shell32.dll,OpenAs_RunDLL %s', filename);
status = system(cmd);
if status ~= 0
errordlg('Не удалось открыть окно выбора приложения.');
end
else
errordlg('Файл не найден');
end
end
end
%% SPECIFIC TOOLS
methods(Static, Access = private)
function [filename, section, tool, example] = getAppWrapperUserFile(block)
sel = get_param(block, 'appWrapperFunc');
basePath = mcuPath.get('appWrapperPath');
if isempty(basePath)
errordlg('Не указан путь к файлам обёртки (wrapperPath).');
return;
end
% Формируем путь к файлу в зависимости от типа запроса
if strcmp(sel, 'Includes')
filename = fullfile(basePath, 'app_includes.h');
section = '// INCLUDES';
tool = 'Инклюды для доступа к коду МК в коде оболочке';
example = '#include "main.h"';
elseif strcmp(sel, 'Dummy')
filename = fullfile(basePath, 'app_wrapper.c');
section = '// DUMMY';
tool = 'Заглушки для различных функций и переменных';
example = ['CAN_HandleTypeDef hcan = {0};' newline...
'void hardware_func(handle *huart) {}' newline...
'int wait_for_hardware_flag(int *flag) {' newline...
' return 1;' newline...
'}' newline...
''];
elseif strcmp(sel, 'App Init')
filename = fullfile(basePath, 'app_init.c');
section = '// USER APP INIT';
tool = ['Код для инициализации приложения МК.' newline newline...
'Вызов функций инициализации, если не используется отдельный поток для main().'];
example = 'init_func();';
elseif strcmp(sel, 'App Step')
filename = fullfile(basePath, 'app_wrapper.c');
section = '// USER APP STEP';
tool = ['Код приложения МК для вызова в шаге симуляции.' newline newline ...
'Вызов функций программы МК, если не используется отдельный поток для main().'];
example = 'step_func();';
elseif strcmp(sel, 'App Inputs')
filename = fullfile(basePath, 'app_io.c');
section = '// USER APP INPUT';
tool = ['Работа с буффером для портов S-Function' newline newline ...
'Буфер в начале хранит входные порты S-Function, далее идут выходные порты:' newline ...
'Buffer[0:15] - входной порт, Buffer[16:31] - входной 1 порт, ' newline ...
'Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт'];
example = ['// чтение 1-го элемента 0-го входного массива' newline...
'app_variable_2 = ReadInputArray(0, 1);' newline newline...
'// запись в буфер выходов' newline ...
'app_variable_2 = Buffer[10];'];
elseif strcmp(sel, 'App Outputs')
filename = fullfile(basePath, 'app_io.c');
section = '// USER APP OUTPUT';
tool = ['Работа с буффером для портов S-Function' newline newline ...
'Буфер в начале хранит входные порты S-Function, далее идут выходные порты:' newline ...
'Buffer[0:15] - входной порт, Buffer[16:31] - входной 1 порт, ' newline ...
'Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт'];
example = ['// запись в 1-й элемент 0-го выходного массива' newline...
'WriteOutputArray(app_variable, 0, 1);' newline newline ...
'// запись в буфер выходов' newline ...
'Buffer[XD_OUTPUT_START + 10] = app_variable_2;'];
elseif strcmp(sel, 'App Deinit')
filename = fullfile(basePath, 'app_init.c');
section = '// USER APP DEINIT';
tool = ['Код для деинициализации приложения МК.' newline newline ...
'Можно деинициализировать приложение МК, для повторного запуска.'];
example = 'memset(&htim1, sizeof(htim1), 0;';
else
tool = '';
mcuMask.disp(0, '\nОшибка выбора типа секции кода: неизвестное значение');
end
end
end
end

View File

@ -39,7 +39,7 @@ classdef asynchManage < handle
methods (Access = private)
function saveCallback(obj)
try
mcuMask.saveAndClose(obj.maskBlockPath);
mcuMask.close(obj.maskBlockPath);
save_system(obj.modelName);
catch ME
warning('progr:Nneg', 'Ошибка при сохранении модели: %s', ME.message);
@ -79,7 +79,7 @@ classdef asynchManage < handle
function GUIconfigCallback(obj)
try
mcuMask.saveAndClose(obj.maskBlockPath);
mcuMask.close(obj.maskBlockPath);
mexing(0);
catch ME
warning('progr:Nneg', 'Ошибка при обновлении модели: %s', ME.message);

View File

@ -26,10 +26,11 @@ classdef compiler
% Список заголовочных файлов (.h)
includes = { '.\'
};
periphPath = mcuPath.get('wrapperPath');
wrapperPath = mcuPath.get('wrapperPath');
% [wrapperPath, ~, ~] = fileparts(wrapperPath);
% Формируем строки
wrapperSrcText = compiler.createSourcesBat('code_WRAPPER', sources, periphPath);
wrapperIncText = compiler.createIncludesBat('includes_WRAPPER', includes, periphPath);
wrapperSrcText = compiler.createSourcesBat('code_WRAPPER', sources, wrapperPath);
wrapperIncText = compiler.createIncludesBat('includes_WRAPPER', includes, wrapperPath);
% Записываем результат
res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: WRAPPER BAT'); % Всё прошло успешно
@ -54,7 +55,7 @@ classdef compiler
% Записываем результат
res = compiler.updateRunMexBat(wrapperSrcText, wrapperIncText, ':: APP WRAPPER BAT'); % Всё прошло успешно
periphConfig.updatePeriphRunMexBat();
end

View File

@ -8,13 +8,15 @@ classdef customtable
tableControl = mask.getDialogControl(table_name);
tableParameter = mask.getParameter(table_name);
nCols = tableControl.getNumberOfColumns;
% if nCols > 0
% for i = 1:nCols
% tableControl.removeColumn(1);
% end
% end
% column = tableControl.addColumn(Name='Title', Type='edit');
% tableControl.Sortable = 'on';
% инициализация колонок если они пустые
% такое случается при removeParameter
if isempty(tableControl.Columns) || (nCols > 1)
for i = 1:nCols
tableControl.removeColumn(1);
end
column = tableControl.addColumn(Name='Title', Type='edit');
tableControl.Sortable = 'on';
end
column.Name = tableParameter.Alias;
end

72
McuLib/m/mainWrap.m Normal file
View File

@ -0,0 +1,72 @@
classdef mainWrap
methods(Static)
function enableThreading()
block = gcb;
maskNames = get_param(block, 'MaskNames');
maskValues = get_param(block, 'MaskValues');
maskEnables = get_param(block, 'MaskEnables');
idxEnable = find(strcmp(maskNames, 'enableThreading'));
idxEdit = find(strcmp(maskNames, 'threadCycles'));
if isempty(idxEnable) || isempty(idxEdit)
error('Параметры enableThreading или threadCycles не найдены в маске');
end
val = maskValues{idxEnable};
if strcmp(val, 'on')
maskEnables{idxEdit} = 'on';
else
maskEnables{idxEdit} = 'off';
end
set_param(block, 'MaskEnables', maskEnables);
end
function enableDeinit()
block = gcb;
maskNames = get_param(block, 'MaskNames');
maskValues = get_param(block, 'MaskValues');
maskEnables = get_param(block, 'MaskEnables');
idxEnable = find(strcmp(maskNames, 'enableThreading'));
idxEdit = find(strcmp(maskNames, 'threadCycles'));
if isempty(idxEnable) || isempty(idxEdit)
error('Параметры enableThreading или threadCycles не найдены в маске');
end
val = maskValues{idxEnable};
if strcmp(val, 'on')
maskEnables{idxEdit} = 'on';
else
maskEnables{idxEdit} = 'off';
end
set_param(block, 'MaskEnables', maskEnables);
end
function extConsol()
block = gcb;
mask = Simulink.Mask.get(block);
fullOut = mask.getParameter('fullOutput');
extCons = mask.getParameter('extConsol');
if isempty(extCons) || isempty(fullOut)
error('Параметры fullOutput или extConsol не найдены в маске');
end
if(strcmp(extCons.Enabled, 'on'))
if strcmp(extCons.Value, 'on')
fullOut.Enabled = 'off';
fullOut.Value = 'on';
else
fullOut.Enabled = 'on';
end
else
fullOut.Enabled = 'on';
end
end
end
%% SPECIFIC TOOLS
methods(Static, Access = private)
end
end

View File

@ -1,5 +1,5 @@
classdef mcuMask
%% CALLBACKS
methods(Static)
% Following properties of 'maskInitContext' are avalaible to use:
% - BlockHandle
@ -58,148 +58,37 @@ classdef mcuMask
end
%% WRAPPER PARAMS
function enableThreading(callbackContext)
block = gcb;
maskNames = get_param(block, 'MaskNames');
maskValues = get_param(block, 'MaskValues');
maskEnables = get_param(block, 'MaskEnables');
idxEnable = find(strcmp(maskNames, 'enableThreading'));
idxEdit = find(strcmp(maskNames, 'threadCycles'));
if isempty(idxEnable) || isempty(idxEdit)
error('Параметры enableThreading или threadCycles не найдены в маске');
end
val = maskValues{idxEnable};
if strcmp(val, 'on')
maskEnables{idxEdit} = 'on';
else
maskEnables{idxEdit} = 'off';
end
set_param(block, 'MaskEnables', maskEnables);
end
function enableDeinit(callbackContext)
block = gcb;
maskNames = get_param(block, 'MaskNames');
maskValues = get_param(block, 'MaskValues');
maskEnables = get_param(block, 'MaskEnables');
idxEnable = find(strcmp(maskNames, 'enableThreading'));
idxEdit = find(strcmp(maskNames, 'threadCycles'));
if isempty(idxEnable) || isempty(idxEdit)
error('Параметры enableThreading или threadCycles не найдены в маске');
end
val = maskValues{idxEnable};
if strcmp(val, 'on')
maskEnables{idxEdit} = 'on';
else
maskEnables{idxEdit} = 'off';
end
set_param(block, 'MaskEnables', maskEnables);
end
function extConsol(callbackContext)
block = gcb;
mask = Simulink.Mask.get(block);
fullOut = mask.getParameter('fullOutput');
extCons = mask.getParameter('extConsol');
if isempty(extCons) || isempty(fullOut)
error('Параметры fullOutput или extConsol не найдены в маске');
end
if(strcmp(extCons.Enabled, 'on'))
if strcmp(extCons.Value, 'on')
fullOut.Enabled = 'off';
fullOut.Value = 'on';
else
fullOut.Enabled = 'on';
end
else
fullOut.Enabled = 'on';
end
end
function wrapperPath_add(callbackContext)
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
function appWrapperPath_add(callbackContext)
mcuPath.addPath('appWrapperPath');
end
%% USER WRAPPER CODE
function appWrapperFunc(callbackContext)
block = gcb;
% Получаем имя функции и путь к файлам
[filename, section, tool, example]= mcuMask.getWrapperUserFile(block);
mcuMask.tool(tool, example);
% Загружаем содержимое файла
set_param(block, 'appWrapperCode', '');
try
code = fileread(filename);
code = regexprep(code, '\r\n?', '\n'); % нормализуем окончания строк
includesText = editCode.extractSection(code, section);
set_param(block, 'appWrapperCode', includesText);
catch
end
% % Поиск тела обычной функции
% expr = sprintf('void %s()', sel);
% funcBody = editCode.extractSection(code, expr);
% set_param(block, 'wrapperCode', funcBody);
appWrap.appWrapperFunc();
end
function saveAppWrapperCode(callbackContext)
block = gcb;
% Получаем имя функции и путь к файлам
[filename, section] = mcuMask.getWrapperUserFile(block);
if ~isfile(filename)
errordlg(['Файл не найден: ', filename]);
return;
end
sel = get_param(block, 'appWrapperFunc');
basePath = get_param(block, 'appWrapperPath');
if isempty(basePath)
errordlg('Не указан путь к файлам обёртки (wrapperPath).');
return;
end
newBody = get_param(block, 'appWrapperCode');
code = fileread(filename);
code = regexprep(code, '\r\n?', '\n');
newBody = strrep(newBody, '\', '\\');
code = editCode.insertSection(code, section, newBody);
% else
% % Обновляем тело функции
% expr = sprintf('void %s()', sel);
% code = editCode.insertSection(code, expr, newBody);
% end
fid = fopen(filename, 'w', 'n', 'UTF-8');
if fid == -1
errordlg('Не удалось открыть файл для записи');
return;
end
fwrite(fid, code);
fclose(fid);
mcuMask.disp(1, ['Обновлено: ' sel]);
appWrap.saveAppWrapperCode();
end
function openAppWrapperCode(callbackContext)
block = gcb;
% Получаем имя функции и путь к файлам
filename = mcuPath.getAbsolutePath(mcuMask.getWrapperUserFile(block));
if exist(filename, 'file') == 2
% Формируем команду без кавычек
cmd = sprintf('rundll32.exe shell32.dll,OpenAs_RunDLL %s', filename);
status = system(cmd);
if status ~= 0
errordlg('Не удалось открыть окно выбора приложения.');
end
else
errordlg('Файл не найден');
end
appWrap.openAppWrapperCode();
end
%% USER CODE
@ -220,36 +109,23 @@ classdef mcuMask
end
%% PERIPH CONFIG
function periphPath_add(callbackContext)
mcuPath.addAnyFile('periphPath');
end
function periphUpdate(callbackContext)
modelName = bdroot(gcb); % получить имя верхнего уровня модели
blockName = gcb;
mgr = asynchManage(modelName, blockName); % создать объект класса
mgr.updateGUIfromConfig(); % запустить сохранение и обновление
end
%% COMPILE
function compile(callbackContext)
compiler.compile();
end
function updateModel(callbackContext)
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 findjobj_link(callbackContext)
web('https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects');
end
function set_name(callbackContext)
function setSFuncName(callbackContext)
block = gcb;
% Получаем параметр имени S-Function из маски блока
newName = mcuMask.get_name();
@ -282,88 +158,30 @@ classdef mcuMask
fwrite(fid, updatedText);
fclose(fid);
end
end
%% SPECIFIC TOOLS
methods(Static, Access = private)
function [filename, section, tool, example] = getWrapperUserFile(block)
sel = get_param(block, 'appWrapperFunc');
basePath = mcuPath.get('appWrapperPath');
if isempty(basePath)
errordlg('Не указан путь к файлам обёртки (wrapperPath).');
return;
end
% Формируем путь к файлу в зависимости от типа запроса
if strcmp(sel, 'Includes')
filename = fullfile(basePath, 'app_includes.h');
section = '// INCLUDES';
tool = 'Инклюды для доступа к коду МК в коде оболочке';
example = '#include "main.h"';
elseif strcmp(sel, 'Dummy')
filename = fullfile(basePath, 'app_wrapper.c');
section = '// DUMMY';
tool = 'Заглушки для различных функций и переменных';
example = ['CAN_HandleTypeDef hcan = {0};' newline...
'void hardware_func(handle *huart) {}' newline...
'int wait_for_hardware_flag(int *flag) {' newline...
' return 1;' newline...
'}' newline...
''];
elseif strcmp(sel, 'App Init')
filename = fullfile(basePath, 'app_init.c');
section = '// USER APP INIT';
tool = ['Код для инициализации приложения МК.' newline newline...
'Вызов функций инициализации, если не используется отдельный поток для main().'];
example = 'init_func();';
elseif strcmp(sel, 'App Step')
filename = fullfile(basePath, 'app_wrapper.c');
section = '// USER APP STEP';
tool = ['Код приложения МК для вызова в шаге симуляции.' newline newline ...
'Вызов функций программы МК, если не используется отдельный поток для main().'];
example = 'step_func();';
elseif strcmp(sel, 'App Inputs')
filename = fullfile(basePath, 'app_io.c');
section = '// USER APP INPUT';
tool = ['Работа с буффером для портов S-Function' newline newline ...
'Буфер в начале хранит входные порты S-Function, далее идут выходные порты:' newline ...
'Buffer[0:15] - входной порт, Buffer[16:31] - входной 1 порт, ' newline ...
'Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт'];
example = ['// чтение 1-го элемента 0-го входного массива' newline...
'app_variable_2 = ReadInputArray(0, 1);' newline newline...
'// запись в буфер выходов' newline ...
'app_variable_2 = Buffer[10];'];
elseif strcmp(sel, 'App Outputs')
filename = fullfile(basePath, 'app_io.c');
section = '// USER APP OUTPUT';
tool = ['Работа с буффером для портов S-Function' newline newline ...
'Буфер в начале хранит входные порты S-Function, далее идут выходные порты:' newline ...
'Buffer[0:15] - входной порт, Buffer[16:31] - входной 1 порт, ' newline ...
'Buffer[32:47] - выходной 1 порт, Buffer[48:63] - выходной 2 порт'];
example = ['// запись в 1-й элемент 0-го выходного массива' newline...
'WriteOutputArray(app_variable, 0, 1);' newline newline ...
'// запись в буфер выходов' newline ...
'Buffer[XD_OUTPUT_START + 10] = app_variable_2;'];
elseif strcmp(sel, 'App Deinit')
filename = fullfile(basePath, 'app_init.c');
section = '// USER APP DEINIT';
tool = ['Код для деинициализации приложения МК.' newline newline ...
'Можно деинициализировать приложение МК, для повторного запуска.'];
example = 'memset(&htim1, sizeof(htim1), 0;';
else
tool = '';
mcuMask.disp(0, '\nОшибка выбора типа секции кода: неизвестное значение');
end
%% LINK TO EXTERNAL CONSOLE
function findjobj_link(callbackContext)
web https://www.mathworks.com/matlabcentral/fileexchange/14317-findjobj-find-java-handles-of-matlab-graphic-objects;
end
end
%% GENERAL TOOLS
methods(Static, Access = public)
function saveAndClose(blockPath)
function updateModel()
addpath(mcuPath.get('wrapperPath'));
res = mexing(1);
if res ~= 0
return;
end
modelName = bdroot(gcb); % получить имя верхнего уровня модели
blockName = gcb;
mgr = asynchManage(modelName, blockName); % создать объект класса
mgr.saveAndUpdateModel(); % запустить сохранение и обновление
end
function close(blockPath)
try
% Считываем текущее имя модели
modelName = bdroot(blockPath);
@ -483,8 +301,12 @@ classdef mcuMask
end
out_now = get_param(gcb, 'consoleOutput');
if ~strcmp(out, '') && ~strcmp(out_now, '')
set_param(gcb, 'consoleOutput', [out_now newline out]);
else
set_param(gcb, 'consoleOutput', [out_now out]);
end
end
function updateModelAsync()

View File

@ -1,13 +1,13 @@
classdef mcuPath
methods(Static)
%% GET PATH FROM PARAM
function path = get(paramName)
blockPath = gcb;
path = get_param(blockPath, paramName);
end
%% ADD PATH TO TABLE
function addSourceFileTable(targetParamName, message)
% Открываем проводник для выбора файлов
@ -61,6 +61,7 @@ classdef mcuPath
customtable.collect(targetParamName, oldTable);
end
%% ADD PATH TO EDIT
function addPath(targetParamName, message)
block = gcb;
@ -91,6 +92,8 @@ classdef mcuPath
param.Value = rel;
end
%% GET PATH STRING
function absPath = getAbsolutePath(relPath)
% relativeToAbsolutePath преобразует относительный путь в абсолютный.
%
@ -115,8 +118,6 @@ classdef mcuPath
end
end
function rel = absoluteToRelativePath(pathstr)
% absoluteToRelativePath преобразует абсолютный путь в относительный от текущей директории.
%
@ -163,6 +164,5 @@ classdef mcuPath
end
end
end
end

View File

@ -69,7 +69,7 @@ function res = mexing(compile_mode)
config = periphConfig.read_config(blockPath);
config = periphConfig.update_config(blockPath, config);
periphConfig.write_config(config);
periphConfig.update(blockPath, config);
periphConfig.updateMask(blockPath, config);
end
end
@ -202,6 +202,8 @@ function definesWrapperArg = buildConfigDefinesString()
definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0);
case 'edit'
definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 1);
case 'popup'
definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, 0);
otherwise
% Необрабатываемые типы
end
@ -230,7 +232,15 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_
end
% Берём alias из маски
alias = param.Alias;
if ~strcmp(param.Type, 'popup')
def_name = param.Alias;
else
def_name = param.Value;
end
if strcmp(def_name, '')
return;
end
if val_define ~= 0
% Значение параметра
@ -240,14 +250,16 @@ function definesWrapperArg = addDefineByParam(definesWrapperArg, paramName, val_
val = num2str(val); % Преобразуем результат в строку
end
% Формируем define с кавычками и значением
newDefine = ['-D"' alias '__EQ__' val '"'];
else
newDefine = ['-D"' def_name '__EQ__' val '"'];
elseif ~strcmp(param.Type, 'popup')
if mcuMask.read_checkbox(paramName)
% Формируем define с кавычками без значения
newDefine = ['-D"' alias '"'];
newDefine = ['-D"' def_name '"'];
else
newDefine = '';
end
else
newDefine = ['-D"' def_name '"'];
end

View File

@ -1,82 +1,215 @@
classdef periphConfig
methods(Static)
function update(blockPath, config)
function updateMask(blockPath, config)
% blockPath = [blockPath '/MCU'];
% Проверяем, была ли маска открыта
% wasOpen = isMaskDialogOpen(blockPath);
mask = Simulink.Mask.get(blockPath);
periphPath = mcuPath.get('periphPath');
[periphPath, ~, ~] = fileparts(periphPath);
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);
containerName = 'configTabAll';
periphConfig.clear_all_from_container(mask, containerName);
% Ищем контейнер, в который будем добавлять вкладки
container = mask.getDialogControl(containerName);
if isempty(container)
error('Контейнер "%s" не найден в маске.', containerName);
% Ищем контейнер, в который будем добавлять вкладки
container = mask.getDialogControl(containerName);
if isempty(container)
error('Контейнер "%s" не найден в маске.', containerName);
end
if ~isempty(config)
% Проходим по каждому модулю (ADC, TIM...)
periphs = fieldnames(config);
for i = 1:numel(periphs)
periph = periphs{i};
% Сохраняем код, если он есть
periphConfig.store_single_periph_code(mask, periph, config.(periph));
% Проверяем наличие Defines
if ~isfield(config.(periph), 'Defines')
continue;
end
defines = config.(periph).Defines;
defNames = fieldnames(defines);
% Создаём вкладку для модуля
tabCtrl = container.addDialogControl('tab', periph);
tabCtrl.Prompt = [periph ' Config'];
rowCountMap = containers.Map();
for j = 1:numel(defNames)
defPrompt = defNames{j};
def = defines.(defPrompt);
% Вызов функции добавления одного параметра
periphConfig.addConfig(mask, containerName, periph, defPrompt, def, rowCountMap, rowWidth);
end
end
end
periphConfig.create_all_code_storage_params(blockPath, config);
% periphConfig.cleanup_obsolete_code_params(blockPath, config);
periphConfig.update();
% Восстанавлиperiph = allTabNamesваем таблицы
customtable.restore_all_tables(tableNames, columns_backup);
catch
% Восстанавливаем таблицы
customtable.restore_all_tables(tableNames, columns_backup);
end
if ~isempty(config)
if isfield(config, 'Code')
res = periphConfig.addCodeConfig(config.Code, periphPath);
if res == 0
error('Ошибка: неудачное добавление кода периферии. Проверьте корректность файлов и путей в конфигурационном файле')
end
else
error('Ошибка: в конфигурационном файле не задан исходный код для симуляции периферии')
end
if isfield(config, 'UserCode')
res = periphConfig.addUserCodeConfig(config.UserCode);
if res == 0
error('Ошибка: неудачное добавление функций для симуляции. Проверьте корректность названий функций в конфигурационном файле')
end
else
error('Ошибка: в конфигурационном файле не заданы функции для симуляции периферии')
end
% Проходим по каждому модулю (ADC, TIM...)
periphs = fieldnames(config);
for i = 1:numel(periphs)
periph = periphs{i};
% Пропускаем Code и UserCode, они уже обработаны
if strcmp(periph, 'Code') || strcmp(periph, 'UserCode')
continue;
end
defines = config.(periph).Defines;
defNames = fieldnames(defines);
% Создаём вкладку для модуля
tabCtrl = container.addDialogControl('tab', periph);
tabCtrl.Prompt = [periph ' Config'];
for j = 1:numel(defNames)
defPrompt = defNames{j};
def = defines.(defPrompt);
% Вызов функции добавления одного параметра
periphConfig.addDefineConfig(mask, containerName, periph, defPrompt, def);
end
end
end
% Восстанавливаем таблицы
customtable.restore_all_tables(tableNames, columns_backup);
% % Повторно открываем маску, если она была открыта
% if wasOpen
% open_system(blockPath, 'mask');
% end
end
function update()
blockPath = gcb;
mask = Simulink.Mask.get(blockPath);
config = periphConfig.read_config(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_<Periph>_Enable
checkboxName = ['Tab_' periph '_Enable'];
if ismember(checkboxName, paramsAll)
% Чекбокс есть - проверяем его состояние
paramObj = mask.getParameter(checkboxName);
val = paramObj.Value;
try
tab = container.getDialogControl(periph);
if strcmpi(val, 'off')
tab.Enabled = 'off';
% Рекурсивно очищаем связанные скрытые параметры
periphConfig.clear_tab_params(mask, config.(periph), periph);
else
tab.Enabled = 'on';
periphConfig.sync_tab_params(mask, config.(periph), periph);
end
catch
warning('Вкладка с именем "%s" не найдена.', periph);
end
else
% Чекбокса нет просто пытаемся включить вкладку, если она есть
try
tab = container.getDialogControl(periph);
tab.Enabled = 'on';
% Опционально можно синхронизировать параметры, если есть config поле
if isfield(config, periph)
periphConfig.sync_tab_params(mask, config.(periph), periph);
end
catch
% Можно не выводить предупреждение вкладка может быть необязательной
% warning('Вкладка с именем "%s" не найдена.', periph);
end
end
end
end
function periphParamCallback(paramName)
blockPath = gcb;
mask = Simulink.Mask.get(blockPath);
hObj = mask.getParameter(paramName);
config = periphConfig.read_config(blockPath);
container = mask.getDialogControl('configTabAll');
% === Проверка на Tab_<Periph>_Enable ===
exprTab = '^Tab_(\w+)_Enable$';
tokensTab = regexp(paramName, exprTab, 'tokens');
if ~isempty(tokensTab)
periph = tokensTab{1}{1};
try
tab = container.getDialogControl(periph);
paramVal = hObj.Value;
if strcmpi(paramVal, 'off')
tab.Enabled = 'off';
if isfield(config, periph)
periphConfig.clear_tab_params(mask, config.(periph), periph);
end
else
tab.Enabled = 'on';
if isfield(config, periph)
periphConfig.sync_tab_params(mask, config.(periph), periph);
end
end
catch
warning('Ошибка обработки вкладки "%s".', periph);
end
return;
end
% === Проверка на параметр, связанный с Sources/Includes ===
% Проверка: это параметр, у которого есть соответствующий Hidden_<Name>_Sources или Hidden_<Name>_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 = periphConfig.get_field(config, nameBase);
catch
warning('Не удалось найти путь %s в config.', nameBase);
return;
end
if strcmpi(useVal, 'on')
try
periphConfig.store_single_periph_code(mask, nameBase, valueStruct);
catch
warning('Не удалось сохранить параметры для %s.', nameBase);
end
else
periphConfig.clear_single_periph_code_param(mask, nameBase);
end
return;
end
% === Если не подошло ни под одно из условий ===
% warning('Объект "%s" не поддерживается универсальным коллбеком.', paramName);
end
function updatePeriphRunMexBat()
% Запись run_mex.bat
blockPath = gcb;
CodeStruct = periphConfig.restore_periph_code_from_mask(blockPath);
periphPath = mcuPath.get('periphPath');
[periphPath, ~, ~] = fileparts(periphPath);
periphConfig.addCodeBat(CodeStruct, periphPath);
end
function config = update_config(blockPath, config)
if isempty(config)
return;
@ -167,19 +300,231 @@ classdef periphConfig
end
end
methods(Static, Access=private)
function res = addCodeConfig(codeConfig, periphPath)
function clear_tab_params(mask, configStruct, prefix, depth)
if nargin < 4
depth = 0;
end
maxDepth = 3; % Максимальная глубина рекурсии
fields = fieldnames(configStruct);
for i = 1:numel(fields)
key = fields{i};
value = configStruct.(key);
paramName = [prefix '_' key];
if isstruct(value)
if depth < maxDepth
% Рекурсивный вызов для вложенных структур с увеличением глубины
periphConfig.clear_tab_params(mask, value, paramName, depth + 1);
end
else
if strcmp(key, 'Sources') || strcmp(key, 'Includes')
baseName = periphConfig.get_final_name_from_prefix(prefix);
periphConfig.clear_single_periph_code_param(mask, baseName);
end
end
end
end
function sync_tab_params(mask, configStruct, prefix, depth)
if nargin < 4
depth = 0;
end
maxDepth = 3; % Максимальная глубина рекурсии
fields = fieldnames(configStruct);
for i = 1:numel(fields)
key = fields{i};
value = configStruct.(key);
paramName = [prefix '_' key];
if isstruct(value)
if depth < maxDepth
% Рекурсивный вызов для вложенных структур с увеличением глубины
periphConfig.sync_tab_params(mask, value, paramName, depth + 1);
end
else
if strcmp(key, 'Sources') || strcmp(key, 'Includes')
baseName = periphConfig.get_final_name_from_prefix(prefix);
periphConfig.store_single_periph_code(mask, baseName, configStruct);
end
end
end
end
function create_all_code_storage_params(blockPath, config)
mask = Simulink.Mask.get(blockPath);
containerName = 'configTabAll';
container = mask.getDialogControl(containerName);
existingParams = mcuMask.collect_all_parameters(container);
% Убедимся, что контейнер существует
tabName = 'hiddenCodeTab';
tab = mask.getDialogControl(tabName);
if isempty(tab)
tab = container.addDialogControl('tab', tabName);
tab.Prompt = 'Hidden Code Settings';
tab.Visible = 'off';
else
tab.Visible = 'off';
end
% Запуск рекурсивного обхода
periphConfig.process_struct_recursive(mask, tabName, config, {}, existingParams);
end
function process_struct_recursive(mask, tabName, currentStruct, nameStack, existingParams)
fields = fieldnames(currentStruct);
for i = 1:numel(fields)
fieldName = fields{i};
value = currentStruct.(fieldName);
newStack = [nameStack, fieldName]; % Добавляем уровень к имени
% Если value структура, обходим дальше
if isstruct(value)
% Проверяем: это структура с Sources/Includes или просто промежуточный узел?
hasSources = isfield(value, 'Sources');
hasIncludes = isfield(value, 'Includes');
if hasSources
periphConfig.addHiddenParam(mask, tabName, fieldName, 'Sources', existingParams);
end
if hasIncludes
periphConfig.addHiddenParam(mask, tabName, fieldName, 'Includes', existingParams);
end
% Рекурсивно продолжаем обход
periphConfig.process_struct_recursive(mask, tabName, value, newStack, existingParams);
end
end
end
function 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)
mask = Simulink.Mask.get(blockPath);
maskParams = mask.Parameters;
allSources = {};
allIncludes = {};
for i = 1:numel(maskParams)
name = maskParams(i).Name;
% Ищем параметры Sources
tokensSrc = regexp(name, '^Hidden_(\w+)_Sources$', 'tokens');
if ~isempty(tokensSrc)
val = maskParams(i).Value;
if ischar(val) || isstring(val)
valStr = strtrim(char(val));
% Пропускаем пустые строки и '[]'
if isempty(valStr) || strcmp(valStr, '[]')
continue;
end
lines = splitlines(valStr);
lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines));
allSources = [allSources; lines]; %#ok<AGROW>
end
continue;
end
% Ищем параметры Includes
tokensInc = regexp(name, '^Hidden_(\w+)_Includes$', 'tokens');
if ~isempty(tokensInc)
val = maskParams(i).Value;
if ischar(val) || isstring(val)
valStr = strtrim(char(val));
if isempty(valStr) || strcmp(valStr, '[]')
continue;
end
lines = splitlines(valStr);
lines = lines(~cellfun(@(x) all(isspace(x)) || isempty(x), lines));
allIncludes = [allIncludes; lines]; %#ok<AGROW>
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);
% Удаляем все параметры
for i = 1:numel(paramsToDelete)
try
mask.removeParameter(paramsToDelete{i});
catch
warning('Не удалось удалить параметр %s', paramsToDelete{i});
end
end
% Рекурсивно удалить все вкладки внутри контейнера
mcuMask.delete_all_tabs(mask, container);
end
function res = addCodeBat(codeConfig, codePath)
% Добавить сурсы и пути в батник
% Возвращает 0 при успехе, 1 при ошибке
try
% Формируем строки
srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources.Options, periphPath);
incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes.Options, periphPath);
srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources, codePath);
incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes, codePath);
% Записываем результат
res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT'); % Всё прошло успешно
@ -189,8 +534,8 @@ classdef periphConfig
end
end
function res = addUserCodeConfig(userCodeConfig)
function res = addUserFunctions(userCodeConfig)
% Добавить функции и дефайны в исходный код wrapper
% userCodeConfig структура config.UserCode
initFuncsText = '';
@ -200,27 +545,26 @@ classdef periphConfig
if isfield(userCodeConfig, 'Functions')
funcs = userCodeConfig.Functions;
if isfield(funcs, 'PeriphInit') && isfield(funcs.PeriphInit, 'Options')
initFuncs = funcs.PeriphInit.Options;
if isfield(funcs, 'PeriphInit')
initFuncs = funcs.PeriphInit;
initFuncsText = strjoin(strcat('\t', initFuncs, ';'), '\n');
end
if isfield(funcs, 'PeriphSimulation') && isfield(funcs.PeriphSimulation, 'Options')
simFuncs = funcs.PeriphSimulation.Options;
if isfield(funcs, 'PeriphSimulation')
simFuncs = funcs.PeriphSimulation;
simFuncsText = strjoin(strcat('\t', simFuncs, ';'), '\n');
end
if isfield(funcs, 'PeriphDeinit') && isfield(funcs.PeriphDeinit, 'Options')
deinitFuncs = funcs.PeriphDeinit.Options;
if isfield(funcs, 'PeriphDeinit')
deinitFuncs = funcs.PeriphDeinit;
deinitFuncsText = strjoin(strcat('\t', deinitFuncs, ';'), '\n');
end
res = periphConfig.updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText);
res = periphConfig.writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText);
end
end
function res = updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText)
function res = writeWrapperCode(initFuncsText, simFuncsText, deinitFuncsText)
% Входные параметры:
% srcText - текст для записи set code_...
% incText - текст для записи set includes_...
@ -250,16 +594,26 @@ classdef periphConfig
end
end
function addDefineConfig(mask, containerName, periphName, defPrompt, def)
function addConfig(mask, containerName, periphName, defPrompt, def, rowCountMap, rowWidth)
% mask объект маски Simulink.Mask.get(blockPath)
% containerName имя контейнера, в который добавляем параметр (например, 'configTabAll')
% periphName имя вкладки / контейнера для текущего периферийного блока (например, 'ADC')
% defPrompt имя параметра в Defines (например, 'shift_enable')
% def структура с описанием параметра (Prompt, Def, Type, Default, NewRow и т.п.)
if ~isKey(rowCountMap, periphName)
rowCountMap(periphName) = 0;
end
rowCount = rowCountMap(periphName) + 1;
% Устанавливаем NewRow, если он не задан
if ~isfield(def, 'NewRow')
def.NewRow = mod(rowCount - 1, rowWidth) == 0;
elseif def.NewRow == true
rowCount = 1;
end
rowCountMap(periphName) = rowCount;
% Найдем контейнер с таким именем
container = mask.getDialogControl(containerName);
if isempty(container)
@ -336,36 +690,107 @@ classdef periphConfig
param.DialogControl.Row = 'current';
end
if strcmp(paramType, 'popup')
param.TypeOptions = def.Def;
else
param.Alias = def.Def;
end
end
function clear_all_from_container(mask, containerName)
% allControls = mask.getDialogControls();
container = mask.getDialogControl(containerName);
if isempty(container)
warning('Контейнер "%s" не найден.', containerName);
return;
end
% Рекурсивно собрать все параметры (не вкладки)
paramsToDelete = mcuMask.collect_all_parameters(container);
% Удаляем все параметры
for i = 1:numel(paramsToDelete)
try
mask.removeParameter(paramsToDelete{i});
catch
warning('Не удалось удалить параметр %s', paramsToDelete{i});
if isfield(def, 'Def')
if strcmp(paramType, 'popup')
param.TypeOptions = def.Def;
else
param.Alias = def.Def;
end
end
% Рекурсивно удалить все вкладки внутри контейнера
mcuMask.delete_all_tabs(mask, container);
callback = sprintf('periphConfig.periphParamCallback("%s");', paramName);
param.Callback = callback;
end
%% ELEMENTARY
function clear_single_periph_code_param(mask, periph)
% Очистка кода одного поля конфига
paramNames = {
['Hidden_' periph '_Sources'],
['Hidden_' periph '_Includes']
};
for i = 1:numel(paramNames)
paramName = paramNames{i};
try
param = mask.getParameter(paramName);
param.Value = '';
catch
% Параметр не существует ничего не делаем
end
end
end
function store_single_periph_code(mask, periph, code)
% Запись кода одного поля конфига
% Сохраняем Sources, если они есть
if isfield(code, 'Sources')
paramName = ['Hidden_' char(periph) '_Sources'];
try
param = mask.getParameter(paramName);
param.Value = periphConfig.convert_code_value(code.Sources);
catch
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
end
end
% Сохраняем Includes, если они есть
if isfield(code, 'Includes')
paramName = ['Hidden_' periph '_Includes'];
try
param = mask.getParameter(paramName);
param.Value = periphConfig.convert_code_value(code.Includes);
catch
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
end
end
end
function value = get_field(configStruct, targetConfig)
% получить targetConfig структуру из конфига (для глубоко вложенных)
value = [];
fields = fieldnames(configStruct);
for i = 1:numel(fields)
key = fields{i};
if strcmp(key, targetConfig)
value = configStruct.(key);
return; % нашли и возвращаем
elseif isstruct(configStruct.(key))
value = periphConfig.get_field(configStruct.(key), targetConfig);
if ~isempty(value)
return; % нашли во вложенной структуре
end
end
end
% Если не нашли, можно выбросить ошибку или вернуть пустое
if isempty(value)
% error('Поле "%s" не найдено в структуре.', targetConfig);
end
end
function short = get_final_name_from_prefix(prefix)
% Берёт последнее имя после "_" (читаемое имя)
parts = strsplit(prefix, '_');
short = parts{end};
end
function value = convert_code_value(codeField)
% Преобразует значение поля Options в строку
if ischar(codeField)
value = codeField;
elseif isstring(codeField)
value = char(codeField);
elseif iscell(codeField)
value = strjoin(codeField, newline);
else
% warning('Неподдерживаемый тип данных: сохранено как пустая строка');
value = '';
end
end
function res = ternary(cond, valTrue, valFalse)

View File

@ -30,15 +30,17 @@ set defines_WRAPPER=-D"MATLAB"^ -D"__sizeof_ptr=8"
:: -------------------------WRAPPER PATHS AND CODE---------------------------
:: оболочка, которая будет моделировать работу МК в симулинке
set includes_WRAPPER=-I"."^
-I".\MCU_Wrapper"^
-I".\app_wrapper"
:: WRAPPER BAT START
:: WRAPPER BAT END
:: APP WRAPPER BAT START
:: APP WRAPPER BAT END
set includes_WRAPPER= %includes_WRAPPER% %includes_APP_WRAPPER%
set code_WRAPPER= %code_WRAPPER% %code_APP_WRAPPER%
set code_WRAPPER= .\MCU_Wrapper\MCU.c^
.\MCU_Wrapper\mcu_wrapper.c^
.\app_wrapper\app_init.c^
.\app_wrapper\app_io.c^
.\app_wrapper\app_wrapper.c
:: PERIPH BAT START

View File

@ -1,5 +1,5 @@
<deployment-project plugin="plugin.toolbox" plugin-version="1.0">
<configuration build-checksum="1721556499" file="E:\.WORK\MATLAB\mcu_matlab\mcuwrapper.prj" location="E:\.WORK\MATLAB\mcu_matlab" name="mcuwrapper" target="target.toolbox" target-name="Package Toolbox">
<configuration build-checksum="2658716694" file="E:\.WORK\MATLAB\mcu_matlab\mcuwrapper.prj" location="E:\.WORK\MATLAB\mcu_matlab" name="mcuwrapper" target="target.toolbox" target-name="Package Toolbox">
<param.appname>MCU Wrapper</param.appname>
<param.authnamewatermark>Razvalyaev</param.authnamewatermark>
<param.email>wot890089@mail.ru</param.email>