mcu_matlab/McuLib/m/periphConfig.m
2025-11-07 14:52:52 +03:00

730 lines
33 KiB
Matlab
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

classdef periphConfig
% Класс для управления конфигурацией периферии в маске Simulink
% Динамически создает элементы маски на основе JSON конфигурации
methods(Static)
function updateMask(blockPath, config)
% Основная функция обновления маски на основе конфигурации
% Динамически создает вкладки и параметры для периферийных модулей
% 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, UART и т.д.)
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();
% Добавляем все параметры 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.update();
% Восстанавливаем таблицы после изменений
customtable.restore_all_tables(tableNames, columns_backup);
catch
% В случае ошибки восстанавливаем таблицы
customtable.restore_all_tables(tableNames, columns_backup);
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);
% Получаем имена всех вкладок в контейнере
allTabs = container.DialogControls;
allTabNames = arrayfun(@(t) t.Name, allTabs, 'UniformOutput', false);
fieldsConfig = fieldnames(config);
% Обрабатываем каждую вкладку
for i = 1:length(allTabNames)
periph = fieldsConfig{i};
% Проверяем наличие чекбокса управления для этой вкладки
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';
% Синхронизируем параметры если есть конфигурация
if isfield(config, periph)
periphConfig.sync_tab_params(mask, config.(periph), periph);
end
catch
% Вкладка может быть необязательной - игнорируем ошибку
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');
% === Обработка чекбоксов управления вкладками ===
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 ===
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;
% Получаем структуру конфигурации для этого параметра
try
valueStruct = configJs.get_field(config, nameBase);
catch
warning('Не удалось найти путь %s в config.', nameBase);
return;
end
if strcmpi(useVal, 'on')
% Сохраняем код если параметр включен
try
periphConfig.store_single_periph_code(mask, nameBase, valueStruct);
catch
warning('Не удалось сохранить параметры для %s.', nameBase);
end
else
% Очищаем код если параметр выключен
periphConfig.clear_single_periph_code_param(mask, nameBase);
end
return;
end
end
function updatePeriphRunMexBat()
% Обновление 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', ...
'Prompt', ['Hidden ' prettyName ' ' kind], ...
'Value', '', ...
'Visible', 'off', ...
'Container', containerName ...
);
end
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
% Очищаем параметры 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);
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 = configJs.get_final_name_from_prefix(prefix);
periphConfig.store_single_periph_code(mask, baseName, configStruct);
end
end
end
end
function create_all_code_storage_params(blockPath, config)
% Создание всех скрытых параметров для хранения кода периферии
mask = Simulink.Mask.get(blockPath);
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)
% Рекурсивный обход структуры конфигурации
% Создает скрытые параметры для всех Sources и Includes
fields = fieldnames(currentStruct);
for i = 1:numel(fields)
fieldName = fields{i};
value = currentStruct.(fieldName);
newStack = [nameStack, fieldName];
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;
% Ищем параметры хранения кода
expr = '^Hidden_(\w+)_(Sources|Includes)$';
tokens = regexp(paramName, expr, 'tokens');
if ~isempty(tokens)
periph = tokens{1}{1};
% Удаляем параметр если периферия больше не существует
if ~ismember(periph, validPeriphs)
mask.removeParameter(paramName);
end
end
end
end
function codeStruct = restore_periph_code_from_mask(blockPath)
% Восстановление кода периферии из скрытых параметров маски
% Собирает все Sources и Includes в одну структуру
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];
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];
end
continue;
end
end
% Формируем результирующую структуру
codeStruct = struct();
codeStruct.Sources = allSources;
codeStruct.Includes = allIncludes;
end
function clear_all_from_container(mask, containerName)
% Полная очистка контейнера - удаление всех параметров и вкладок
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)
% Добавление кода периферии в BAT-файл компиляции
try
% Формируем строки для BAT-файла
srcText = compiler.createSourcesBat('code_PERIPH', codeConfig.Sources, codePath);
incText = compiler.createIncludesBat('includes_PERIPH', codeConfig.Includes, codePath);
% Обновляем BAT-файл
res = compiler.updateRunMexBat(srcText, incText, ':: PERIPH BAT');
catch
res = 1; % Ошибка
end
end
function res = addUserFunctions(userCodeConfig)
% Добавление пользовательских функций в код обёртки
initFuncsText = '';
simFuncsText = '';
deinitFuncsText = '';
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)
% Запись пользовательских функций в файл mcu_wrapper.c
wrapPath = fullfile(mcuPath.get('wrapperPath'), 'mcu_wrapper.c');
res = 1;
try
% Читаем текущее содержимое файла
code = fileread(wrapPath);
code = regexprep(code, '\r\n?', '\n');
% Вставляем функции в соответствующие секции
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 - объект маски
% containerName - имя контейнера
% periphName - имя периферийного модуля
% defPrompt - имя параметра
% def - структура с описанием параметра
% rowCountMap - карта для отслеживания строк
% rowWidth - ширина строки
% Инициализация счетчика строк для этого модуля
if ~isKey(rowCountMap, periphName)
rowCountMap(periphName) = 0;
end
rowCount = rowCountMap(periphName) + 1;
% Определяем начинать ли новую строку
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)
error('Контейнер "%s" не найден в маске.', containerName);
end
% Создаем вкладку если её нет
tabCtrl = mask.getDialogControl(periphName);
if isempty(tabCtrl)
tabCtrl = container.addDialogControl('tab', periphName);
tabCtrl.Prompt = [periphName ' Config'];
end
% Определяем тип параметра
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 = '';
elseif isfield(def, 'Options')
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')
if iscell(def.Def)
param.TypeOptions = def.Def;
elseif isfield(def, 'Options')
param.Alias = def.Def;
param.TypeOptions = def.Options;
end
else
param.Alias = def.Def;
end
end
callback = sprintf('try periphConfig.periphParamCallback("%s"); catch end', paramName);
param.Callback = callback;
end
%% ELEMENTARY FUNCTIONS - базовые вспомогательные функции
function clear_single_periph_code_param(mask, periph)
% Очистка кода одного модуля периферии
paramNames = {
['Hidden_' char(periph) '_Sources'],
['Hidden_' char(periph) '_Includes']
};
for i = 1:numel(paramNames)
paramName = paramNames{i};
try
param = mask.getParameter(paramName);
param.Value = '';
catch
% Параметр не существует - игнорируем
end
end
end
function store_single_periph_code(mask, periph, code)
% Сохранение кода одного модуля периферии в скрытые параметры
if isfield(code, 'Sources')
paramName = ['Hidden_' char(periph) '_Sources'];
try
param = mask.getParameter(paramName);
param.Value = configJs.convert_code_value(code.Sources);
catch
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
end
end
if isfield(code, 'Includes')
paramName = ['Hidden_' char(periph) '_Includes'];
try
param = mask.getParameter(paramName);
param.Value = configJs.convert_code_value(code.Includes);
catch
mcuMask.disp(0, ['Параметр ' paramName ' не найден']);
end
end
end
function res = ternary(cond, valTrue, valFalse)
% Вспомогательная функция - тернарный оператор
if cond
res = valTrue;
else
res = valFalse;
end
end
end
end