bsp写的差不多了

This commit is contained in:
2025-08-05 03:47:11 +08:00
parent af7529b529
commit bebfbd716c
29 changed files with 946 additions and 410 deletions

View File

@@ -1,6 +1,12 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy, QTreeWidget, QTreeWidgetItem, QStackedWidget
from PyQt5.QtCore import Qt
from qfluentwidgets import TitleLabel, BodyLabel
from qfluentwidgets import TitleLabel, BodyLabel, PushButton
from app.tools.analyzing_ioc import analyzing_ioc
from app.code_page.bsp_interface import bsp
import os
import csv
import importlib
class CodeGenerateInterface(QWidget):
def __init__(self, project_path, parent=None):
@@ -8,14 +14,137 @@ class CodeGenerateInterface(QWidget):
self.setObjectName("CodeGenerateInterface")
self.project_path = project_path
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignTop)
layout.setContentsMargins(10, 10, 10, 10)
self._init_ui()
title = TitleLabel("代码生成页面")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
def _init_ui(self):
"""初始化界面布局"""
main_layout = QVBoxLayout(self)
main_layout.setAlignment(Qt.AlignTop)
main_layout.setContentsMargins(10, 10, 10, 10)
desc = BodyLabel(f"当前工程路径: {self.project_path}")
desc.setAlignment(Qt.AlignCenter)
layout.addWidget(desc)
top_layout = self._create_top_layout()
main_layout.addLayout(top_layout)
# 下方主区域,左右分栏
content_layout = QHBoxLayout()
content_layout.setContentsMargins(0, 10, 0, 0)
main_layout.addLayout(content_layout)
# 左侧树形列表
self.tree = QTreeWidget()
self.tree.setHeaderHidden(True)
self.tree.setMaximumWidth(250)
content_layout.addWidget(self.tree)
# 右侧内容区
self.stack = QStackedWidget()
content_layout.addWidget(self.stack)
self._load_csv_and_build_tree()
self.tree.itemClicked.connect(self.on_tree_item_clicked)
def _create_top_layout(self):
"""创建顶部横向布局"""
top_layout = QHBoxLayout()
top_layout.setAlignment(Qt.AlignTop)
# 项目名称标签
project_name = os.path.basename(self.project_path)
name_label = BodyLabel(f"项目名称: {project_name}")
name_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
top_layout.addWidget(name_label)
# FreeRTOS状态标签
freertos_label = BodyLabel(f"FreeRTOS: {self._get_freertos_status()}")
freertos_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
top_layout.addWidget(freertos_label)
# 生成代码按钮
generate_btn = PushButton("Generate Code")
generate_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
generate_btn.clicked.connect(self.generate_code)
top_layout.addWidget(generate_btn, alignment=Qt.AlignRight)
return top_layout
def generate_code(self):
"""生成代码逻辑"""
# 收集所有已加载的页面对象
pages = []
for i in range(self.stack.count()):
widget = self.stack.widget(i)
pages.append(widget)
bsp.generate_bsp(self.project_path, pages)
# component.generate_component(self.project_path)
# device.generate_device(self.project_path)
def _get_freertos_status(self):
"""获取FreeRTOS状态"""
ioc_files = [f for f in os.listdir(self.project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(self.project_path, ioc_files[0])
return "开启" if analyzing_ioc.is_freertos_enabled_from_ioc(ioc_path) else "未开启"
return "未找到.ioc文件"
def _load_csv_and_build_tree(self):
# 获取脚本目录
script_dir = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(script_dir, "../assets/User_code/config.csv")
csv_path = os.path.abspath(csv_path)
print(f"加载CSV路径: {csv_path}")
if not os.path.exists(csv_path):
print(f"配置文件未找到: {csv_path}")
return
self.tree.clear()
with open(csv_path, newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
row = [cell.strip() for cell in row if cell.strip()]
if not row:
continue
main_title = row[0]
main_item = QTreeWidgetItem([main_title])
for sub in row[1:]:
sub_item = QTreeWidgetItem([sub])
main_item.addChild(sub_item)
self.tree.addTopLevelItem(main_item)
self.tree.repaint()
def on_tree_item_clicked(self, item, column):
if item.parent():
main_title = item.parent().text(0)
sub_title = item.text(0)
class_name = f"{main_title}_{sub_title}".replace("-", "_")
widget = self._get_or_create_page(class_name)
if widget:
self.stack.setCurrentWidget(widget)
def _get_or_create_page(self, class_name):
for i in range(self.stack.count()):
w = self.stack.widget(i)
if w.objectName() == class_name:
return w
try:
module_name = f"app.code_page.{class_name.split('_')[0]}_interface"
module = importlib.import_module(module_name)
cls = getattr(module, class_name)
widget = cls(self.project_path)
widget.setObjectName(class_name)
self.stack.addWidget(widget)
print(f"加载页面类: {class_name} 来自模块: {module_name}")
return widget
except Exception as e:
# 自动识别通用外设页面
from app.code_page.bsp_interface import BspSimplePeripheral
peripheral_name = class_name.split('_')[1] if '_' in class_name else class_name
# 模板文件名自动推断
template_names = {
'header': f"{peripheral_name.lower()}.h",
'source': f"{peripheral_name.lower()}.c"
}
widget = BspSimplePeripheral(self.project_path, peripheral_name, template_names)
widget.setObjectName(class_name)
self.stack.addWidget(widget)
print(f"自动加载通用外设页面: {class_name}")
return widget

View File

@@ -0,0 +1,453 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QCheckBox, QComboBox, QTableWidget, QHeaderView, QMessageBox
from qfluentwidgets import TitleLabel, BodyLabel, PushButton
from app.tools.analyzing_ioc import analyzing_ioc
from app.tools.code_generator import CodeGenerator
import os
import csv
class BspSimplePeripheral(QWidget):
def __init__(self, project_path, peripheral_name, template_names):
super().__init__()
self.project_path = project_path
self.peripheral_name = peripheral_name
self.template_names = template_names
# 加载描述
describe_path = os.path.join(os.path.dirname(__file__), "../../assets/User_code/bsp/describe.csv")
self.descriptions = load_descriptions(describe_path)
self._init_ui()
self._load_config()
def _init_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(TitleLabel(f"{self.peripheral_name} 配置"))
desc = self.descriptions.get(self.peripheral_name.lower(), "")
if desc:
layout.addWidget(BodyLabel(f"功能说明:{desc}"))
self.generate_checkbox = QCheckBox(f"启用 {self.peripheral_name}")
layout.addWidget(self.generate_checkbox)
def is_need_generate(self):
return self.generate_checkbox.isChecked()
def _generate_bsp_code_internal(self):
if not self.is_need_generate():
return False
template_dir = CodeGenerator.get_template_dir()
# 直接拷贝模板,无需特殊处理
for key, filename in self.template_names.items():
template_path = os.path.join(template_dir, filename)
template_content = CodeGenerator.load_template(template_path)
if not template_content:
return False
output_path = os.path.join(self.project_path, f"User/bsp/{filename}")
if not CodeGenerator.save_file(template_content, output_path):
return False
self._save_config()
return True
def _save_config(self):
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
config_data = CodeGenerator.load_config(config_path)
config_data[self.peripheral_name.lower()] = {'enabled': True}
CodeGenerator.save_config(config_data, config_path)
def _load_config(self):
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
config_data = CodeGenerator.load_config(config_path)
conf = config_data.get(self.peripheral_name.lower(), {})
if conf.get('enabled', False):
self.generate_checkbox.setChecked(True)
class BspPeripheralBase(QWidget):
def __init__(self, project_path, peripheral_name, template_names, enum_prefix, handle_prefix, yaml_key, get_available_func):
super().__init__()
self.project_path = project_path
self.peripheral_name = peripheral_name
self.template_names = template_names
self.enum_prefix = enum_prefix
self.handle_prefix = handle_prefix
self.yaml_key = yaml_key
self.get_available_func = get_available_func
self.available_list = []
# 新增:加载描述
describe_path = os.path.join(os.path.dirname(__file__), "../../assets/User_code/bsp/describe.csv")
self.descriptions = load_descriptions(describe_path)
self._init_ui()
self._load_config()
def _init_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(TitleLabel(f"{self.peripheral_name} 配置"))
desc = self.descriptions.get(self.peripheral_name.lower(), "")
if desc:
layout.addWidget(BodyLabel(f"功能说明:{desc}"))
self.generate_checkbox = QCheckBox(f"生成 {self.peripheral_name} 代码")
self.generate_checkbox = QCheckBox(f"生成 {self.peripheral_name} 代码")
self.generate_checkbox.stateChanged.connect(self._on_generate_changed)
layout.addWidget(self.generate_checkbox)
self.content_widget = QWidget()
content_layout = QVBoxLayout(self.content_widget)
self._get_available_list()
if not self.available_list:
content_layout.addWidget(BodyLabel(f"在 .ioc 文件中未找到已启用的 {self.peripheral_name}"))
else:
content_layout.addWidget(BodyLabel(f"可用的 {self.peripheral_name}: {', '.join(self.available_list)}"))
self.table = QTableWidget(0, 3)
self.table.setHorizontalHeaderLabels(["设备名称", f"{self.peripheral_name}选择", "操作"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
content_layout.addWidget(self.table)
add_btn = PushButton(f"添加 {self.peripheral_name} 设备")
add_btn.clicked.connect(self._add_device)
content_layout.addWidget(add_btn)
layout.addWidget(self.content_widget)
self.content_widget.setEnabled(False)
def _get_available_list(self):
self.available_list = self.get_available_func(self.project_path)
def _on_generate_changed(self, state):
self.content_widget.setEnabled(state == 2)
def _add_device(self):
row = self.table.rowCount()
self.table.insertRow(row)
name_edit = QLineEdit()
name_edit.setPlaceholderText(f"输入设备名称")
self.table.setCellWidget(row, 0, name_edit)
combo = QComboBox()
combo.addItems(self.available_list)
self.table.setCellWidget(row, 1, combo)
del_btn = PushButton("删除")
del_btn.clicked.connect(lambda: self._delete_device(row))
self.table.setCellWidget(row, 2, del_btn)
def _delete_device(self, row):
self.table.removeRow(row)
def _collect_configs(self):
configs = []
for row in range(self.table.rowCount()):
name_widget = self.table.cellWidget(row, 0)
sel_widget = self.table.cellWidget(row, 1)
if name_widget and sel_widget:
name = name_widget.text().strip()
sel = sel_widget.currentText()
if name and sel:
configs.append((name.upper(), sel))
return configs
def is_need_generate(self):
return self.generate_checkbox.isChecked() and bool(self._collect_configs())
def generate_bsp_code(self):
if not self.is_need_generate():
return False
configs = self._collect_configs()
if not configs:
return False
template_dir = CodeGenerator.get_template_dir()
if not self._generate_header_file(configs, template_dir):
return False
if not self._generate_source_file(configs, template_dir):
return False
self._save_config(configs)
QMessageBox.information(self, "成功", f"{self.peripheral_name} 代码生成成功!")
return True
def _generate_header_file(self, configs, template_dir):
template_path = os.path.join(template_dir, self.template_names['header'])
template_content = CodeGenerator.load_template(template_path)
if not template_content:
return False
enum_lines = [f" {self.enum_prefix}_{name}," for name, _ in configs]
content = CodeGenerator.replace_auto_generated(
template_content, f"AUTO GENERATED {self.enum_prefix}_NAME", "\n".join(enum_lines)
)
output_path = os.path.join(self.project_path, f"User/bsp/{self.template_names['header']}")
return CodeGenerator.save_file(content, output_path)
def _generate_source_file(self, configs, template_dir):
template_path = os.path.join(template_dir, self.template_names['source'])
template_content = CodeGenerator.load_template(template_path)
if not template_content:
return False
# Get函数
get_lines = []
for idx, (name, instance) in enumerate(configs):
if idx == 0:
get_lines.append(f" if ({self.handle_prefix}->Instance == {instance})")
else:
get_lines.append(f" else if ({self.handle_prefix}->Instance == {instance})")
get_lines.append(f" return {self.enum_prefix}_{name};")
content = CodeGenerator.replace_auto_generated(
template_content, f"AUTO GENERATED {self.enum_prefix.split('_')[1]}_GET", "\n".join(get_lines)
)
# Handle函数
handle_lines = []
for name, instance in configs:
handle_lines.append(f" case {self.enum_prefix}_{name}:")
handle_lines.append(f" return &h{instance.lower()};")
content = CodeGenerator.replace_auto_generated(
content, f"AUTO GENERATED {self.enum_prefix}_GET_HANDLE", "\n".join(handle_lines)
)
output_path = os.path.join(self.project_path, f"User/bsp/{self.template_names['source']}")
return CodeGenerator.save_file(content, output_path)
def _save_config(self, configs):
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
config_data = CodeGenerator.load_config(config_path)
config_data[self.yaml_key] = {
'enabled': True,
'devices': [{'name': name, 'instance': instance} for name, instance in configs]
}
CodeGenerator.save_config(config_data, config_path)
def _load_config(self):
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
config_data = CodeGenerator.load_config(config_path)
conf = config_data.get(self.yaml_key, {})
if conf.get('enabled', False):
self.generate_checkbox.setChecked(True)
devices = conf.get('devices', [])
for device in devices:
if self.available_list:
self._add_device()
row = self.table.rowCount() - 1
name_widget = self.table.cellWidget(row, 0)
sel_widget = self.table.cellWidget(row, 1)
if name_widget:
name_widget.setText(device.get('name', ''))
if sel_widget:
instance = device.get('instance', '')
if instance in self.available_list:
sel_widget.setCurrentText(instance)
def _generate_bsp_code_internal(self):
if not self.is_need_generate():
return False
configs = self._collect_configs()
if not configs:
return False
template_dir = CodeGenerator.get_template_dir()
if not self._generate_header_file(configs, template_dir):
return False
if not self._generate_source_file(configs, template_dir):
return False
self._save_config(configs)
return True
def load_descriptions(csv_path):
descriptions = {}
if os.path.exists(csv_path):
with open(csv_path, encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
if len(row) >= 2:
key, desc = row[0].strip(), row[1].strip()
descriptions[key.lower()] = desc
return descriptions
# 各外设的可用列表获取函数
def get_available_i2c(project_path):
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(project_path, ioc_files[0])
return analyzing_ioc.get_enabled_i2c_from_ioc(ioc_path)
return []
def get_available_can(project_path):
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(project_path, ioc_files[0])
return analyzing_ioc.get_enabled_can_from_ioc(ioc_path)
return []
def get_available_spi(project_path):
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(project_path, ioc_files[0])
return analyzing_ioc.get_enabled_spi_from_ioc(ioc_path)
return []
def get_available_uart(project_path):
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(project_path, ioc_files[0])
return analyzing_ioc.get_enabled_uart_from_ioc(ioc_path)
return []
def get_available_gpio(project_path):
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(project_path, ioc_files[0])
return analyzing_ioc.get_enabled_gpio_from_ioc(ioc_path)
return []
# 具体外设类
class bsp_i2c(BspPeripheralBase):
def __init__(self, project_path):
super().__init__(
project_path,
"I2C",
{'header': 'i2c.h', 'source': 'i2c.c'},
"BSP_I2C",
"hi2c",
"i2c",
get_available_i2c
)
class bsp_can(BspPeripheralBase):
def __init__(self, project_path):
super().__init__(
project_path,
"CAN",
{'header': 'can.h', 'source': 'can.c'},
"BSP_CAN",
"hcan",
"can",
get_available_can
)
class bsp_spi(BspPeripheralBase):
def __init__(self, project_path):
super().__init__(
project_path,
"SPI",
{'header': 'spi.h', 'source': 'spi.c'},
"BSP_SPI",
"hspi",
"spi",
get_available_spi
)
class bsp_uart(BspPeripheralBase):
def __init__(self, project_path):
super().__init__(
project_path,
"UART",
{'header': 'uart.h', 'source': 'uart.c'},
"BSP_UART",
"huart",
"uart",
get_available_uart
)
class bsp_gpio(QWidget):
def __init__(self, project_path):
super().__init__()
self.project_path = project_path
self.available_list = get_available_gpio(project_path)
# 新增:加载描述
describe_path = os.path.join(os.path.dirname(__file__), "../../assets/User_code/bsp/describe.csv")
self.descriptions = load_descriptions(describe_path)
self._init_ui()
def _init_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(TitleLabel("GPIO 配置"))
# 新增:显示描述
desc = self.descriptions.get("gpio", "")
if desc:
layout.addWidget(BodyLabel(f"功能说明:{desc}"))
self.generate_checkbox = QCheckBox("生成 GPIO 代码")
layout.addWidget(self.generate_checkbox)
if not self.available_list:
layout.addWidget(BodyLabel("在 .ioc 文件中未找到可用的 GPIO"))
else:
self.table = QTableWidget(len(self.available_list), 1)
self.table.setHorizontalHeaderLabels(["Label"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
for row, item in enumerate(self.available_list):
from PyQt5.QtWidgets import QTableWidgetItem
self.table.setItem(row, 0, QTableWidgetItem(item['label']))
self.table.setEditTriggers(QTableWidget.NoEditTriggers)
layout.addWidget(self.table)
def is_need_generate(self):
return self.generate_checkbox.isChecked() and bool(self.available_list)
def _generate_bsp_code_internal(self):
if not self.is_need_generate():
return False
template_dir = CodeGenerator.get_template_dir()
if not self._generate_header_file(template_dir):
return False
if not self._generate_source_file(template_dir):
return False
self._save_config()
return True
def _generate_header_file(self, template_dir):
template_path = os.path.join(template_dir, "gpio.h")
template_content = CodeGenerator.load_template(template_path)
if not template_content:
return False
# 如有需要可在此处插入自动生成内容
output_path = os.path.join(self.project_path, "User/bsp/gpio.h")
return CodeGenerator.save_file(template_content, output_path)
def _generate_source_file(self, template_dir):
template_path = os.path.join(template_dir, "gpio.c")
template_content = CodeGenerator.load_template(template_path)
if not template_content:
return False
# 生成 IRQ 使能/禁用代码
enable_lines = []
disable_lines = []
for item in self.available_list:
label = item['label']
enable_lines.append(f" case {label}_Pin:")
enable_lines.append(f" HAL_NVIC_EnableIRQ({label}_EXTI_IRQn);")
enable_lines.append(f" break;")
disable_lines.append(f" case {label}_Pin:")
disable_lines.append(f" HAL_NVIC_DisableIRQ({label}_EXTI_IRQn);")
disable_lines.append(f" break;")
content = CodeGenerator.replace_auto_generated(
template_content, "AUTO GENERATED BSP_GPIO_ENABLE_IRQ", "\n".join(enable_lines)
)
content = CodeGenerator.replace_auto_generated(
content, "AUTO GENERATED BSP_GPIO_DISABLE_IRQ", "\n".join(disable_lines)
)
output_path = os.path.join(self.project_path, "User/bsp/gpio.c")
return CodeGenerator.save_file(content, output_path)
def _save_config(self):
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
config_data = CodeGenerator.load_config(config_path)
config_data['gpio'] = {
'enabled': True,
'labels': [item['label'] for item in self.available_list]
}
CodeGenerator.save_config(config_data, config_path)
class bsp(QWidget):
def __init__(self, project_path):
super().__init__()
self.project_path = project_path
@staticmethod
def generate_bsp(project_path, pages):
total = 0
success_count = 0
fail_count = 0
fail_list = []
for page in pages:
name = page.objectName() if hasattr(page, "objectName") else str(page)
if hasattr(page, "is_need_generate") and page.is_need_generate():
total += 1
try:
result = page._generate_bsp_code_internal()
if result:
success_count += 1
else:
fail_count += 1
fail_list.append(name)
except Exception as e:
fail_count += 1
fail_list.append(f"{name} (异常: {e})")
msg = f"总共尝试生成 {total} 项,成功 {success_count} 项,失败 {fail_count} 项。"
if fail_list:
msg += "\n失败项:\n" + "\n".join(fail_list)
QMessageBox.information(None, "代码生成结果", msg)

View File

View File

View File

@@ -1,7 +1,146 @@
class analyzing_ioc:
def __init__(self, ioc_data): # 初始化方法接收IOC数据
self.ioc_data = ioc_data # 存储IOC数据
@staticmethod
def is_freertos_enabled_from_ioc(ioc_path):
"""
检查指定 .ioc 文件是否开启了 FreeRTOS
"""
config = {}
with open(ioc_path, encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
config[key.strip()] = value.strip()
ip_keys = [k for k in config if k.startswith('Mcu.IP')]
for k in ip_keys:
if config[k] == 'FREERTOS':
return True
for k in config:
if k.startswith('FREERTOS.'):
return True
return False
@staticmethod
def get_enabled_i2c_from_ioc(ioc_path):
"""
从.ioc文件中获取已启用的I2C列表
返回格式: ['I2C1', 'I2C3'] 等
"""
enabled_i2c = []
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
# 检查是否启用了I2C
if key.startswith('Mcu.IP') and value.startswith('I2C'):
# 提取I2C编号如I2C1, I2C2等
i2c_name = value.split('.')[0] if '.' in value else value
if i2c_name not in enabled_i2c:
enabled_i2c.append(i2c_name)
return sorted(enabled_i2c)
@staticmethod
def get_enabled_spi_from_ioc(ioc_path):
"""
获取已启用的SPI列表
返回格式: ['SPI1', 'SPI2'] 等
"""
enabled_spi = []
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
if key.startswith('Mcu.IP') and value.startswith('SPI'):
spi_name = value.split('.')[0] if '.' in value else value
if spi_name not in enabled_spi:
enabled_spi.append(spi_name)
return sorted(enabled_spi)
@staticmethod
def get_enabled_can_from_ioc(ioc_path):
"""
获取已启用的CAN列表
返回格式: ['CAN1', 'CAN2'] 等
"""
enabled_can = []
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
if key.startswith('Mcu.IP') and value.startswith('CAN'):
can_name = value.split('.')[0] if '.' in value else value
if can_name not in enabled_can:
enabled_can.append(can_name)
return sorted(enabled_can)
@staticmethod
def get_enabled_uart_from_ioc(ioc_path):
"""
获取已启用的UART/USART列表
返回格式: ['USART1', 'USART2', 'UART4'] 等
"""
enabled_uart = []
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
# 检查是否启用了UART或USART
if key.startswith('Mcu.IP') and (value.startswith('USART') or value.startswith('UART')):
uart_name = value.split('.')[0] if '.' in value else value
if uart_name not in enabled_uart:
enabled_uart.append(uart_name)
return sorted(enabled_uart)
@staticmethod
def get_enabled_gpio_from_ioc(ioc_path):
"""
获取所有带 EXTI 且有 Label 的 GPIO返回 [{'pin': 'PC4', 'label': 'ACCL_INT'}, ...]
"""
gpio_list = []
gpio_params = {}
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
if '.GPIOParameters' in key:
gpio_params[key.split('.')[0]] = value
elif '.GPIO_Label' in key:
pin = key.split('.')[0]
gpio_params[f"{pin}_label"] = value
elif '.GPIO_ModeDefaultEXTI' in key:
pin = key.split('.')[0]
gpio_params[f"{pin}_exti"] = value
for pin in gpio_params:
if not pin.endswith('_label') and not pin.endswith('_exti'):
label = gpio_params.get(f"{pin}_label", None)
exti = gpio_params.get(f"{pin}_exti", None)
if label and exti:
gpio_list.append({'pin': pin, 'label': label})
return gpio_list

View File

@@ -0,0 +1,75 @@
import os
import yaml
import shutil
from typing import Dict, List, Tuple
class CodeGenerator:
"""通用代码生成器"""
@staticmethod
def load_template(template_path: str) -> str:
"""加载代码模板"""
try:
with open(template_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
print(f"加载模板失败: {template_path}, 错误: {e}")
return ""
@staticmethod
def replace_auto_generated(content: str, marker: str, replacement: str) -> str:
"""替换自动生成的代码标记"""
marker_line = f"/* {marker} */"
if marker_line in content:
return content.replace(marker_line, replacement)
return content
@staticmethod
def save_file(content: str, file_path: str) -> bool:
"""保存文件"""
try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return True
except Exception as e:
print(f"保存文件失败: {file_path}, 错误: {e}")
return False
@staticmethod
def load_config(config_path: str) -> Dict:
"""加载配置文件"""
if os.path.exists(config_path):
try:
with open(config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f) or {}
except Exception as e:
print(f"加载配置失败: {config_path}, 错误: {e}")
return {}
@staticmethod
def save_config(config: Dict, config_path: str) -> bool:
"""保存配置文件"""
try:
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
yaml.safe_dump(config, f, allow_unicode=True, default_flow_style=False)
return True
except Exception as e:
print(f"保存配置失败: {config_path}, 错误: {e}")
return False
@staticmethod
def get_template_dir():
"""获取模板文件目录"""
# 从当前文件向上找到 MRobot 目录,然后定位到模板目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 向上找到 MRobot 根目录
while os.path.basename(current_dir) != 'MRobot' and current_dir != '/':
current_dir = os.path.dirname(current_dir)
if os.path.basename(current_dir) == 'MRobot':
return os.path.join(current_dir, "assets/User_code/bsp")
else:
# 如果找不到,使用相对路径作为备选
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "assets/User_code/bsp")