639 lines
26 KiB
Matlab
639 lines
26 KiB
Matlab
classdef periphConfig
|
||
|
||
methods(Static)
|
||
function updateMask(blockPath, config)
|
||
% blockPath = [blockPath '/MCU'];
|
||
|
||
% Проверяем, была ли маска открыта
|
||
% wasOpen = isMaskDialogOpen(blockPath);
|
||
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...)
|
||
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);
|
||
|
||
% Восстанавливаем таблицы
|
||
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 config = update_config(blockPath, config)
|
||
if isempty(config)
|
||
return;
|
||
end
|
||
|
||
mask = Simulink.Mask.get(blockPath);
|
||
maskParams = mask.Parameters;
|
||
paramNames = arrayfun(@(p) p.Name, maskParams, 'UniformOutput', false);
|
||
|
||
% Обработка остальных секций (с дефайнами)
|
||
periphs = fieldnames(config);
|
||
for i = 1:numel(periphs)
|
||
periph = periphs{i};
|
||
|
||
% Пропускаем Code и UserCode
|
||
if strcmp(periph, 'Code') || strcmp(periph, 'UserCode')
|
||
continue;
|
||
end
|
||
|
||
% Проверяем есть ли Defines
|
||
if ~isfield(config.(periph), 'Defines')
|
||
continue;
|
||
end
|
||
|
||
defines = config.(periph).Defines;
|
||
defNames = fieldnames(defines);
|
||
|
||
for j = 1:numel(defNames)
|
||
defPrompt = defNames{j};
|
||
paramName = matlab.lang.makeValidName(defPrompt);
|
||
|
||
% Проверка, существует ли параметр с таким именем
|
||
if ismember(paramName, paramNames)
|
||
param = mask.getParameter(paramName);
|
||
valStr = param.Value;
|
||
|
||
% Проверяем, существует ли элемент defPrompt в структуре defines
|
||
if isfield(defines, defPrompt)
|
||
% Преобразуем строку в соответствующий тип
|
||
if strcmpi(defines.(defPrompt).Type, 'checkbox')
|
||
config.(periph).Defines.(defPrompt).Default = strcmpi(valStr, 'on');
|
||
elseif strcmpi(defines.(defPrompt).Type, 'edit')
|
||
valNum = str2double(valStr);
|
||
if isnan(valNum)
|
||
config.(periph).Defines.(defPrompt).Default = valStr;
|
||
else
|
||
config.(periph).Defines.(defPrompt).Default = valNum;
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function config = read_config(blockPath)
|
||
mask = Simulink.Mask.get(blockPath);
|
||
|
||
pathparam = mask.getParameter('periphPath');
|
||
config_path = pathparam.Value;
|
||
|
||
if ~isempty(config_path)
|
||
jsonText = fileread(config_path);
|
||
config = jsondecode(jsonText);
|
||
else
|
||
config = [];
|
||
end
|
||
end
|
||
|
||
function write_config(config)
|
||
if isempty(config)
|
||
return
|
||
end
|
||
|
||
blockHandle = gcbh;
|
||
mask = Simulink.Mask.get(blockHandle);
|
||
|
||
pathparam = mask.getParameter('periphPath');
|
||
config_path = pathparam.Value;
|
||
|
||
jsonText = jsonencode(config, 'PrettyPrint', true);
|
||
fid = fopen(config_path, 'w', 'n', 'UTF-8');
|
||
if fid == -1
|
||
error('Не удалось открыть файл periph_config.json для записи.');
|
||
end
|
||
fwrite(fid, jsonText, 'char');
|
||
fclose(fid);
|
||
end
|
||
|
||
function update_callback()
|
||
blockPath = gcb;
|
||
mask = Simulink.Mask.get(blockPath);
|
||
|
||
config = periphConfig.read_config(blockPath);
|
||
|
||
periphs = fieldnames(config);
|
||
for i = 1:numel(periphs)
|
||
periph = periphs{i};
|
||
|
||
% Сохраняем код, если он есть
|
||
periphConfig.store_single_periph_code(mask, periph, config.(periph));
|
||
end
|
||
|
||
containerName = 'configTabAll';
|
||
container = mask.getDialogControl(containerName);
|
||
paramsAll = mcuMask.collect_all_parameters(container);
|
||
% Цикл по параметрам
|
||
for i = 1:length(paramsAll)
|
||
name = paramsAll{i};
|
||
|
||
% Ищем параметры вида Tab_<Periph>_Enable
|
||
expr = '^Tab_(\w+)_Enable$';
|
||
tokens = regexp(name, expr, 'tokens');
|
||
|
||
if ~isempty(tokens)
|
||
periph = tokens{1}{1}; % Извлекаем имя вкладки
|
||
paramObj = mask.getParameter(name);
|
||
val = paramObj.Value;
|
||
|
||
if strcmpi(val, 'off')
|
||
% Найдём вкладку по имени
|
||
try
|
||
tab = container.getDialogControl(periph);
|
||
tab.Enabled = 'off';
|
||
periphConfig.clear_single_periph_code_param(mask, periph);
|
||
catch
|
||
warning('Вкладка с именем "%s" не найдена.', periph);
|
||
end
|
||
else
|
||
% Найдём вкладку по имени
|
||
try
|
||
tab = container.getDialogControl(periph);
|
||
tab.Enabled = 'on';
|
||
periphConfig.store_single_periph_code(mask, periph, config.(periph));
|
||
catch
|
||
warning('Вкладка с именем "%s" не найдена.', periph);
|
||
end
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
|
||
periphConfig.getWrapperCode();
|
||
end
|
||
|
||
|
||
|
||
function getWrapperCode()
|
||
blockPath = gcb;
|
||
CodeStruct = periphConfig.restore_periph_code_from_mask(blockPath);
|
||
periphPath = mcuPath.get('periphPath');
|
||
[periphPath, ~, ~] = fileparts(periphPath);
|
||
|
||
periphConfig.addCodeBat(CodeStruct, periphPath);
|
||
|
||
end
|
||
end
|
||
|
||
|
||
|
||
methods(Static, Access=private)
|
||
|
||
function create_all_code_storage_params(blockPath, config)
|
||
mask = Simulink.Mask.get(blockPath);
|
||
existingParams = arrayfun(@(p) p.Name, mask.Parameters, 'UniformOutput', false);
|
||
|
||
periphs = fieldnames(config);
|
||
for i = 1:numel(periphs)
|
||
periph = periphs{i};
|
||
|
||
% Проверяем, нужно ли создавать параметры
|
||
hasSources = isfield(config.(periph), 'Sources');
|
||
hasIncludes = isfield(config.(periph), 'Includes');
|
||
|
||
if ~hasSources && ~hasIncludes
|
||
continue;
|
||
end
|
||
|
||
% Добавляем параметр для Sources
|
||
if hasSources
|
||
srcParamName = ['Hidden_' periph '_Sources'];
|
||
if ~ismember(srcParamName, existingParams)
|
||
mask.addParameter( ...
|
||
'Name', srcParamName, ...
|
||
'Type', 'edit', ...
|
||
'Prompt', '', ...
|
||
'Value', '', ...
|
||
'Visible', 'off' ...
|
||
);
|
||
fprintf('Создан скрытый параметр: %s\n', srcParamName);
|
||
end
|
||
end
|
||
|
||
% Добавляем параметр для Includes
|
||
if hasIncludes
|
||
incParamName = ['Hidden_' periph '_Includes'];
|
||
if ~ismember(incParamName, existingParams)
|
||
mask.addParameter( ...
|
||
'Name', incParamName, ...
|
||
'Type', 'edit', ...
|
||
'Prompt', '', ...
|
||
'Value', '', ...
|
||
'Visible', 'off' ...
|
||
);
|
||
fprintf('Создан скрытый параметр: %s\n', incParamName);
|
||
end
|
||
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_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') && isfield(code.Sources, 'Options')
|
||
paramName = ['Hidden_' periph '_Sources'];
|
||
try
|
||
param = mask.getParameter(paramName);
|
||
param.Value = periphConfig.convert_code_value(code.Sources.Options);
|
||
catch
|
||
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
|
||
end
|
||
end
|
||
|
||
% Сохраняем Includes, если они есть
|
||
if isfield(code, 'Includes') && isfield(code.Includes, 'Options')
|
||
paramName = ['Hidden_' periph '_Includes'];
|
||
try
|
||
param = mask.getParameter(paramName);
|
||
param.Value = periphConfig.convert_code_value(code.Includes.Options);
|
||
catch
|
||
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
|
||
end
|
||
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 = addCodeBat(codeConfig, codePath)
|
||
% Возвращает 0 при успехе, 1 при ошибке
|
||
try
|
||
% Формируем строки
|
||
srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources, codePath);
|
||
incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes, codePath);
|
||
|
||
% Записываем результат
|
||
res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT'); % Всё прошло успешно
|
||
catch
|
||
% В случае ошибки просто возвращаем 1
|
||
res = 1;
|
||
end
|
||
end
|
||
|
||
|
||
function res = addUserFunctions(userCodeConfig)
|
||
% userCodeConfig — структура config.UserCode
|
||
|
||
initFuncsText = '';
|
||
simFuncsText = '';
|
||
deinitFuncsText = '';
|
||
|
||
if isfield(userCodeConfig, 'Functions')
|
||
funcs = userCodeConfig.Functions;
|
||
|
||
if isfield(funcs, 'PeriphInit') && isfield(funcs.PeriphInit, 'Options')
|
||
initFuncs = funcs.PeriphInit.Options;
|
||
initFuncsText = strjoin(strcat('\t', initFuncs, ';'), '\n');
|
||
end
|
||
|
||
if isfield(funcs, 'PeriphSimulation') && isfield(funcs.PeriphSimulation, 'Options')
|
||
simFuncs = funcs.PeriphSimulation.Options;
|
||
simFuncsText = strjoin(strcat('\t', simFuncs, ';'), '\n');
|
||
end
|
||
|
||
if isfield(funcs, 'PeriphDeinit') && isfield(funcs.PeriphDeinit, 'Options')
|
||
deinitFuncs = funcs.PeriphDeinit.Options;
|
||
deinitFuncsText = strjoin(strcat('\t', deinitFuncs, ';'), '\n');
|
||
end
|
||
|
||
res = periphConfig.updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText);
|
||
end
|
||
end
|
||
|
||
|
||
function res = updateWrapperCode(initFuncsText, simFuncsText, deinitFuncsText)
|
||
% Входные параметры:
|
||
% srcText - текст для записи set code_...
|
||
% incText - текст для записи set includes_...
|
||
%
|
||
% Возвращает:
|
||
% res - 0 при успехе, 1 при ошибке
|
||
wrapPath = fullfile(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('Не удалось открыть файл для записи');
|
||
end
|
||
fwrite(fid, code);
|
||
fclose(fid);
|
||
res = 1;
|
||
catch ME
|
||
error('Ошибка: неудачная запись в файл при записи файла: %s', ME.message);
|
||
end
|
||
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 и т.п.)
|
||
|
||
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 = 0;
|
||
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';
|
||
case 'edit'
|
||
paramType = 'edit';
|
||
case 'popup'
|
||
paramType = 'popup';
|
||
otherwise
|
||
% Игнорируем остальные типы
|
||
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 = ''; % по умолчанию — ничего
|
||
else
|
||
warning('Popout параметр "%s" не содержит допустимого списка в Def.', defPrompt);
|
||
return;
|
||
end
|
||
else
|
||
val = def.Default;
|
||
if islogical(val)
|
||
valStr = periphConfig.ternary(val, 'on', 'off');
|
||
elseif isnumeric(val)
|
||
valStr = num2str(val);
|
||
elseif ischar(val)
|
||
valStr = val;
|
||
else
|
||
error('Unsupported default value type for %s.%s', periphName, defPrompt);
|
||
end
|
||
end
|
||
|
||
% Добавляем параметр в маску
|
||
if strcmp(paramType, 'popup')
|
||
param = mask.addParameter( ...
|
||
'Type', paramType, ...
|
||
'Prompt', def.Prompt, ...
|
||
'Name', paramName, ...
|
||
'Container', periphName ...
|
||
);
|
||
else
|
||
param = mask.addParameter( ...
|
||
'Type', paramType, ...
|
||
'Prompt', def.Prompt, ...
|
||
'Name', paramName, ...
|
||
'Value', valStr, ...
|
||
'Container', periphName ...
|
||
);
|
||
end
|
||
|
||
param.Evaluate = 'off';
|
||
|
||
if def.NewRow
|
||
param.DialogControl.Row = 'new';
|
||
else
|
||
param.DialogControl.Row = 'current';
|
||
end
|
||
|
||
if isfield(def, 'Def')
|
||
if strcmp(paramType, 'popup')
|
||
param.TypeOptions = def.Def;
|
||
else
|
||
param.Alias = def.Def;
|
||
end
|
||
end
|
||
|
||
param.Callback = 'periphConfig.update_callback();';
|
||
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 = ternary(cond, valTrue, valFalse)
|
||
if cond
|
||
res = valTrue;
|
||
else
|
||
res = valFalse;
|
||
end
|
||
end
|
||
|
||
end
|
||
end
|