目录

前言

一、数据管理:从“冗余存储”到“单一信源”的质变

传统XML模式的痛点:

JSON+Jinja2的改进:

二、逻辑扩展:从“硬编码”到“模板驱动”的灵活性飞跃

传统模式的局限:

JSON+Jinja2的突破:

三、多格式输出:从“分散处理”到“统一引擎”的效率提升

传统模式的困境:

JSON+Jinja2的统一:

四、工程实践价值:从“工具”到“平台”的跨越

总结:架构升级的核心价值

完整代码

main.py

gpio_config.j2


 

前言

在嵌入式开发领域,配置即代码(Configuration-as-Code)方案通过将硬件配置转化为结构化数据(如XML),试图实现“配置-代码”的自动化映射。然而,该方案在实践中暴露出三大核心痛点:

其一,​​数据分散存储​​:配置信息同时存在于UI控件(如下拉框、输入框的实时状态)和临时变量中,生成代码时需遍历所有控件提取数据。这不仅导致控件状态与核心数据可能不同步(如用户修改控件后未触发保存),更因重复解析控件增加了时间开销(尤其当配置行数较多时,解析耗时显著上升)。

其二,​​模板与逻辑强耦合​​:代码生成逻辑与UI渲染深度绑定——修改输出格式(如从HAL库切换至LL库)需同时调整界面交互逻辑和核心数据提取代码,维护成本随格式复杂度呈指数级增长。

针对上述问题,改进方案通过引入​​JSON统一数据层​​与​​Jinja2模板引擎​​,重构了“配置-数据-输出”的全链路流程。本文将对比改进前后的架构差异,揭示新方案如何破解传统配置即代码的落地困境。

 

一、数据管理:从“冗余存储”到“单一信源”的质变

传统XML模式的痛点:

  • ​数据冗余存储​​:XML既是配置存储格式(如gpio_config.xml),又是UI渲染的数据源(需解析后填充控件)。同一份配置需在内存和磁盘中重复存储,增加维护成本。

JSON+Jinja2的改进:

  • ​单一数据信源​​:所有配置信息仅存储于JSON文件(如gpio_config.json),既作为持久化存储,又作为UI和代码生成的唯一数据源。UI控件由JSON动态渲染(如_bind_data_to_ui方法),无需额外存储。
  • ​无同步成本​​:UI状态变更实时更新JSON(如用户修改引脚号后,core_data立即同步),无需“保存”操作触发写磁盘,数据一致性由内存状态保证。

​结论​​:JSON作为单一数据信源,消除了传统模式中的冗余存储和同步风险,使数据管理更简洁、可靠。

二、逻辑扩展:从“硬编码”到“模板驱动”的灵活性飞跃

传统模式的局限:

  • ​代码生成逻辑耦合​​:C代码生成与UI逻辑深度绑定(如generate_code方法中直接拼接字符串),修改输出格式(如从HAL库切换到LL库)需重写整个生成逻辑,维护成本极高。
  • ​XML模板僵化​​:若需新增输出格式(如Markdown文档),需新增XML解析分支,导致代码冗余(如generate_xmlgenerate_markdown方法重复解析XML)。

JSON+Jinja2的突破:

  • ​模板与逻辑分离​​:Jinja2模板(如gpio_config.j2)仅定义输出格式,不包含任何业务逻辑。代码生成时仅需传入core_data,模板引擎自动渲染:
    /* STM32 HAL GPIO初始化代码 */
    {% for cfg in configs %}
    GPIO_InitTypeDef GPIO_InitStruct_{{ loop.index }};
    GPIO_InitStruct_{{ loop.index }}.GPIO_Pin = GPIO_Pin_{{ cfg.pin[2:] }};
    GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = {{ mode_map[cfg.mode] }};
    {% endfor %}
    • ​格式切换​​:新增LL库输出格式只需创建新模板(如gpio_config_ll.j2),无需修改核心逻辑。
    • ​维护便捷​​:模板可独立版本管理(如Git跟踪gpio_config.j2的修改),团队协作时无需同步代码,仅需更新模板。
  • ​动态渲染能力​​:Jinja2支持条件判断、循环嵌套等高级特性,可灵活处理复杂场景(如根据引脚模式自动跳过无效字段):
{% if cfg.mode in ["输出", "复用"] %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Speed = {{ speed_map[cfg.speed] }};
{% endif %}

​结论​​:模板引擎将“数据”与“表现”彻底分离,使逻辑扩展从“修改代码”变为“修改模板”,大幅降低维护成本。

三、多格式输出:从“分散处理”到“统一引擎”的效率提升

传统模式的困境:

  • ​多格式重复处理​​:生成C代码、XML、JSON文件时,需分别编写解析/生成逻辑(如generate_c_codegenerate_xmlgenerate_json),代码重复率高。
  • ​一致性风险​​:不同格式的输出可能因逻辑差异导致配置不一致(如XML中的pull字段与JSON中的pull值不同步)。

JSON+Jinja2的统一:

  • ​单一数据驱动多输出​​:所有格式的输出均基于同一core_data,通过不同模板渲染:
    # 生成C代码
    c_template = Template(open("gpio_config.j2").read())
    c_code = c_template.render(configs=core_data["gpio_configs"])
    
    # 生成XML
    xml_template = Template(open("gpio_config_xml.j2").read())
    xml_content = xml_template.render(configs=core_data["gpio_configs"])
    
    # 生成JSON
    json_content = json.dumps(core_data, indent=4)
    • ​一致性保证​​:所有格式的输出均来自同一数据源,彻底避免不同格式间的配置冲突。
    • ​效率提升​​:利用模板,只需调用模板生成引起,去渲染模板,相比传统方式时间更短。

​结论​​:统一数据源与模板引擎的结合,使多格式输出从“分散处理”变为“集中渲染”,显著提升效率并保证一致性。

四、工程实践价值:从“工具”到“平台”的跨越

JSON+Jinja2的架构设计,本质上是为嵌入式开发工具构建了一个​​可扩展的配置平台​​,其价值远超单一功能的提升:

  • ​支持复杂场景​​:可轻松扩展支持STM32新系列(如H7)、新外设(如CAN FD),仅需新增模板和数据校验规则。
  • ​集成自动化流程​​:JSON输出可直接对接CI/CD工具(如GitHub Actions),实现“配置修改→自动生成代码→测试验证”的全流程自动化。
  • ​降低学习成本​​:模板语法(Jinja2)与数据格式(JSON)均为行业标准,新成员可快速上手,无需理解复杂的UI逻辑。

总结:架构升级的核心价值

JSON数据存储结合Jinja2模板的架构,通过​​单一数据信源、模板驱动逻辑、统一多格式输出​​三大核心改进,将嵌入式配置工具从“功能实现”升级为“工程平台”。其优势不仅体现在开发效率的提升上,更在于为后续扩展(如支持AI辅助配置、云协作)预留了灵活的技术路径,是现代嵌入式开发工具的典型范式。

 

完整代码

main.py

import sys
import os
import json
import xml.etree.ElementTree as ET
import re
from datetime import datetime
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QComboBox, QPushButton, QFileDialog, QMessageBox,
                             QScrollArea)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QFont
from jinja2 import Template
import chardet


class STM32GPIOConfigTool(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("STM32F103 GPIO配置工具")
        self.setGeometry(100, 100, 900, 650)

        # 初始化数据存储
        self.gpio_configs = []  # 存储GPIO配置
        self.used_pins = set()  # 记录已使用的引脚

        # 主布局
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)
        main_layout.setSpacing(15)
        main_layout.setContentsMargins(15, 15, 15, 15)

        # 标题
        title_label = QLabel("STM32F103 GPIO配置工具")
        title_label.setFont(QFont("Arial", 16, QFont.Weight.Bold))
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        main_layout.addWidget(title_label)

        # 操作按钮区域
        btn_layout = QHBoxLayout()
        self.btn_add = QPushButton("添加引脚配置")
        self.btn_add.clicked.connect(self.add_gpio_row)
        self.btn_import_xml = QPushButton("导入XML配置")
        self.btn_import_xml.clicked.connect(self.import_xml)
        self.btn_generate_code = QPushButton("生成STM32 HAL代码")
        self.btn_generate_code.clicked.connect(self.generate_code_via_jinja)
        btn_layout.addWidget(self.btn_add)
        btn_layout.addWidget(self.btn_import_xml)
        btn_layout.addWidget(self.btn_generate_code)
        main_layout.addLayout(btn_layout)

        # 配置区域标题
        config_title = QLabel("GPIO引脚配置")
        config_title.setFont(QFont("Arial", 12, QFont.Weight.Bold))
        main_layout.addWidget(config_title)

        # 滚动区域(用于显示GPIO配置行)
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.scroll_content = QWidget()
        self.gpio_layout = QVBoxLayout(self.scroll_content)
        self.gpio_layout.setSpacing(10)
        self.scroll_area.setWidget(self.scroll_content)
        main_layout.addWidget(self.scroll_area)

        # 状态标签
        self.status_label = QLabel("就绪 | 引脚数: 0")
        main_layout.addWidget(self.status_label)

        # 添加初始行(默认PA0)
        self.add_gpio_row()

    def update_status(self):
        """更新状态标签"""
        self.status_label.setText(f"就绪 | 引脚数: {len(self.gpio_configs)} | 已用引脚: {', '.join(sorted(self.used_pins))}")

    def add_gpio_row(self):
        """添加新的GPIO配置行"""
        try:
            row_widget = QWidget()
            row_layout = QHBoxLayout(row_widget)
            row_layout.setContentsMargins(10, 10, 10, 10)
            row_layout.setSpacing(15)

            # 引脚选择(PA0-PB15)
            row_layout.addWidget(QLabel("引脚:"))
            pin_combo = QComboBox()
            pin_combo.addItems([f"PA{i}" for i in range(16)] + [f"PB{i}" for i in range(16)])
            row_layout.addWidget(pin_combo, 1)

            # 模式选择(输入/输出/复用/模拟)
            row_layout.addWidget(QLabel("模式:"))
            mode_combo = QComboBox()
            mode_combo.addItems(["输入", "输出", "复用", "模拟"])
            row_layout.addWidget(mode_combo, 1)

            # 输出类型(仅输出/复用模式有效)
            row_layout.addWidget(QLabel("输出类型:"))
            type_combo = QComboBox()
            type_combo.addItems(["推挽", "开漏"])
            type_combo.setEnabled(False)  # 初始禁用
            row_layout.addWidget(type_combo, 1)

            # 速度(仅输出/复用模式有效)
            row_layout.addWidget(QLabel("速度:"))
            speed_combo = QComboBox()
            speed_combo.addItems(["低速", "中速", "高速", "最高速"])
            speed_combo.setEnabled(False)  # 初始禁用
            row_layout.addWidget(speed_combo, 1)

            # 上下拉(仅输入/模拟模式有效)
            row_layout.addWidget(QLabel("上下拉:"))
            pull_combo = QComboBox()
            pull_combo.addItems(["无", "上拉", "下拉"])
            pull_combo.setCurrentText("无")
            row_layout.addWidget(pull_combo, 1)

            # 删除按钮
            del_btn = QPushButton("删除")
            del_btn.setFixedSize(80, 30)
            del_btn.setStyleSheet("background-color: #f44336; color: white;")
            del_btn.clicked.connect(lambda: self.remove_gpio_row(row_widget))
            row_layout.addWidget(del_btn, 0)

            # 绑定事件
            mode_combo.currentTextChanged.connect(
                lambda mode, tc=type_combo, sc=speed_combo, pc=pull_combo:
                self._toggle_gpio_options(mode, tc, sc, pc)
            )
            pin_combo.currentTextChanged.connect(
                lambda text, rw=row_widget: self._on_pin_selected(text, rw)
            )

            # 自动分配未使用的PA0
            available_pins = self._get_unused_pins()
            if available_pins:
                pin_combo.setCurrentText(available_pins[0])
                self.used_pins.add(available_pins[0])
                row_config = {
                    "widget": row_widget,
                    "pin_combo": pin_combo,
                    "mode_combo": mode_combo,
                    "type_combo": type_combo,
                    "speed_combo": speed_combo,
                    "pull_combo": pull_combo,
                    "pin": pin_combo.currentText(),
                    "mode": "输入",
                    "pull": "无",
                    "output_type": "推挽",  # 默认值,但仅用于输出/复用模式
                    "speed": "低速"  # 默认值,但仅用于输出/复用模式
                }
                self.gpio_configs.append(row_config)
                self.gpio_layout.addWidget(row_widget)
                self.update_status()
            else:
                QMessageBox.warning(self, "警告", "所有GPIO引脚已被使用!")

        except Exception as e:
            QMessageBox.critical(self, "错误", f"添加行失败:{str(e)}")

    def remove_gpio_row(self, row_widget):
        """删除GPIO配置行"""
        try:
            for i, cfg in enumerate(self.gpio_configs):
                if cfg["widget"] == row_widget:
                    self.used_pins.discard(cfg["pin"])
                    self.gpio_configs.pop(i)
                    row_widget.deleteLater()
                    self.update_status()
                    return
            QMessageBox.warning(self, "警告", "未找到要删除的行")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"删除行失败:{str(e)}")

    def _on_pin_selected(self, selected_pin, row_widget):
        """引脚选择改变时的校验逻辑"""
        try:
            for cfg in self.gpio_configs:
                if cfg["widget"] == row_widget:
                    # 校验引脚格式
                    if not re.fullmatch(r"^P[AB]\d{1,2}$", selected_pin):
                        row_widget.setStyleSheet("background: #FFCCCC;")
                        QMessageBox.warning(self, "警告", "引脚格式错误(仅支持PA0-PA15/PB0-PB15)")
                        cfg["pin_combo"].setCurrentText(cfg["pin"])
                        return

                    # 校验引脚是否已被使用
                    if selected_pin in self.used_pins and selected_pin != cfg["pin"]:
                        row_widget.setStyleSheet("background: #FFCCCC;")
                        QMessageBox.warning(self, "警告", f"引脚 {selected_pin} 已被其他行使用")
                        cfg["pin_combo"].setCurrentText(cfg["pin"])
                        return

                    # 更新配置
                    old_pin = cfg["pin"]
                    cfg["pin"] = selected_pin
                    self.used_pins.discard(old_pin)
                    self.used_pins.add(selected_pin)
                    # 重置样式
                    row_widget.setStyleSheet("")
                    self._toggle_gpio_options(
                        cfg["mode_combo"].currentText(),
                        cfg["type_combo"],
                        cfg["speed_combo"],
                        cfg["pull_combo"]
                    )
                    self.update_status()
                    break
        except Exception as e:
            QMessageBox.critical(self, "错误", f"引脚校验失败:{str(e)}")

    def _toggle_gpio_options(self, mode, type_combo, speed_combo, pull_combo):
        """根据模式控制输出类型、速度、上下拉的可用性"""
        is_input = mode == "输入"
        is_analog = mode == "模拟"
        is_output_remap = mode in ["输出", "复用"]

        # 上下拉:仅输入/模拟模式可用
        pull_combo.setEnabled(is_input or is_analog)
        if is_analog:
            pull_combo.setCurrentText("无")  # 模拟模式强制无上下拉
            pull_combo.setEnabled(False)

        # 输出类型和速度:仅输出/复用模式可用
        type_combo.setEnabled(is_output_remap)
        speed_combo.setEnabled(is_output_remap)

        # 复用/输出模式下,上下拉强制无
        if is_output_remap:
            pull_combo.setEnabled(False)
            pull_combo.setCurrentText("无")

    def _get_unused_pins(self):
        """获取所有未使用的GPIO引脚(PA0优先)"""
        all_pins = [f"PA{i}" for i in range(16)] + [f"PB{i}" for i in range(16)]
        unused = [p for p in all_pins if p not in self.used_pins]
        return ["PA0"] + [p for p in unused if p != "PA0"] if "PA0" in unused else unused

    def import_xml(self):
        """导入STM32 GPIO配置XML文件"""
        try:
            file_path, _ = QFileDialog.getOpenFileName(
                self, "导入XML配置", "", "XML文件 (*.xml)"
            )
            if not file_path:
                return

            # 清空当前配置
            self._clear_configs()

            # 自动检测编码
            with open(file_path, 'rb') as f:
                result = chardet.detect(f.read())
                encoding = result['encoding'] or 'gbk'

            # 读取文件
            with open(file_path, 'r', encoding=encoding, errors='replace') as f:
                xml_content = f.read()

            # 解析XML
            root = ET.fromstring(xml_content)
            if root.tag != "stm32_gpio_config":
                raise ValueError("无效的STM32 GPIO配置文件")

            # 处理每个GPIO节点
            for gpio_node in root.findall("gpio"):
                pin = gpio_node.find("pin").text.strip()
                mode = gpio_node.find("mode").text.strip()
                pull = gpio_node.find("pull").text.strip() if gpio_node.find("pull") is not None else "无"
                output_type = gpio_node.find("output_type").text.strip() if gpio_node.find(
                    "output_type") is not None else "推挽"
                speed = gpio_node.find("speed").text.strip() if gpio_node.find("speed") is not None else "低速"

                # 添加配置
                self.used_pins.add(pin)
                self._add_gpio_row_from_config({
                    "pin": pin,
                    "mode": mode,
                    "pull": pull,
                    "output_type": output_type,
                    "speed": speed
                })

            QMessageBox.information(self, "导入成功", f"成功导入 {len(self.gpio_configs)} 个GPIO配置")
            self.update_status()

        except Exception as e:
            QMessageBox.critical(self, "导入失败", f"导入XML失败:{str(e)}")

    def _clear_configs(self):
        """清空所有配置"""
        # 清空界面行
        while self.gpio_layout.count():
            item = self.gpio_layout.takeAt(0)
            widget = item.widget()
            if widget:
                widget.deleteLater()

        # 清空数据
        self.gpio_configs = []
        self.used_pins = set()
        self.update_status()

    def _add_gpio_row_from_config(self, config):
        """从配置字典创建界面行"""
        try:
            row_widget = QWidget()
            row_layout = QHBoxLayout(row_widget)
            row_layout.setContentsMargins(10, 10, 10, 10)
            row_layout.setSpacing(15)

            # 引脚选择
            row_layout.addWidget(QLabel("引脚:"))
            pin_combo = QComboBox()
            pin_combo.addItems([f"PA{i}" for i in range(16)] + [f"PB{i}" for i in range(16)])
            pin_combo.setCurrentText(config["pin"])
            row_layout.addWidget(pin_combo, 1)

            # 模式选择
            row_layout.addWidget(QLabel("模式:"))
            mode_combo = QComboBox()
            mode_combo.addItems(["输入", "输出", "复用", "模拟"])
            mode_combo.setCurrentText(config["mode"])
            row_layout.addWidget(mode_combo, 1)

            # 输出类型
            row_layout.addWidget(QLabel("输出类型:"))
            type_combo = QComboBox()
            type_combo.addItems(["推挽", "开漏"])
            type_combo.setCurrentText(config["output_type"])
            row_layout.addWidget(type_combo, 1)

            # 速度
            row_layout.addWidget(QLabel("速度:"))
            speed_combo = QComboBox()
            speed_combo.addItems(["低速", "中速", "高速", "最高速"])
            speed_combo.setCurrentText(config["speed"])
            row_layout.addWidget(speed_combo, 1)

            # 上下拉
            row_layout.addWidget(QLabel("上下拉:"))
            pull_combo = QComboBox()
            pull_combo.addItems(["无", "上拉", "下拉"])
            pull_combo.setCurrentText(config["pull"])
            row_layout.addWidget(pull_combo, 1)

            # 删除按钮
            del_btn = QPushButton("删除")
            del_btn.setFixedSize(80, 30)
            del_btn.setStyleSheet("background-color: #f44336; color: white;")
            del_btn.clicked.connect(lambda: self.remove_gpio_row(row_widget))
            row_layout.addWidget(del_btn, 0)

            # 保存配置
            row_config = {
                "widget": row_widget,
                "pin_combo": pin_combo,
                "mode_combo": mode_combo,
                "type_combo": type_combo,
                "speed_combo": speed_combo,
                "pull_combo": pull_combo,
                "pin": config["pin"],
                "mode": config["mode"],
                "pull": config["pull"],
                "output_type": config["output_type"],
                "speed": config["speed"]
            }
            self.gpio_configs.append(row_config)
            self.gpio_layout.addWidget(row_widget)

            # 更新控件状态
            self._toggle_gpio_options(
                config["mode"],
                type_combo,
                speed_combo,
                pull_combo
            )

        except Exception as e:
            QMessageBox.critical(self, "错误", f"创建行失败:{str(e)}")

    def generate_code_via_jinja(self):
        """生成STM32 HAL代码(使用外部模板)"""
        try:
            # 1. 生成JSON数据(从当前配置)
            json_data = {
                "metadata": {
                    "version": "1.0",
                    "generated_at": datetime.now().isoformat()
                },
                "global_config": {
                    "rcc_afio": "启用" if any(
                        cfg["mode_combo"].currentText() in ["输出", "复用"] for cfg in self.gpio_configs
                    ) else "禁用"
                },
                "gpio_configs": []
            }

            # 确保获取当前控件值
            for cfg in self.gpio_configs:
                gpio_config = {
                    "pin": cfg["pin_combo"].currentText(),
                    "mode": cfg["mode_combo"].currentText(),
                    "pull": cfg["pull_combo"].currentText(),
                    "output_type": cfg["type_combo"].currentText(),
                    "speed": cfg["speed_combo"].currentText()
                }
                json_data["gpio_configs"].append(gpio_config)

            # 2. 使用外部模板文件
            template_path = os.path.join(os.path.dirname(__file__), "gpio_config.j2")

            # 检查模板文件是否存在
            if not os.path.exists(template_path):
                QMessageBox.critical(self, "错误", f"模板文件未找到:{template_path}")
                return

            # 读取模板文件
            with open(template_path, "r", encoding="utf-8") as f:
                template_str = f.read()

            # 3. 渲染模板生成C代码
            template = Template(template_str, trim_blocks=True, lstrip_blocks=True)
            code = template.render(
                configs=json_data["gpio_configs"],
                rcc_afio=json_data["global_config"]["rcc_afio"],
                timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            )

            # 4. 保存文件
            c_file_path, _ = QFileDialog.getSaveFileName(
                self, "保存C代码", "STM32F103_GPIO_Init.c", "C文件 (*.c)"
            )
            if not c_file_path:
                return

            # 保存C代码
            with open(c_file_path, "w", encoding="utf-8") as f:
                f.write(code)

            # 保存XML文件
            xml_file_path = c_file_path.replace(".c", "_gpio_config.xml")
            self._save_xml(xml_file_path, json_data)

            # 保存JSON文件
            json_file_path = c_file_path.replace(".c", "_gpio_config.json")
            self._save_json(json_file_path, json_data)

            QMessageBox.information(self, "生成成功",
                                    f"C代码已保存:{c_file_path}\n"
                                    f"XML文件已保存:{xml_file_path}\n"
                                    f"JSON文件已保存:{json_file_path}")

        except Exception as e:
            QMessageBox.critical(self, "生成失败", f"生成代码失败:{str(e)}")

    def _save_xml(self, file_path, json_data):
        """保存XML文件"""
        try:
            root = ET.Element("stm32_gpio_config", version="1.0")

            for idx, cfg in enumerate(json_data["gpio_configs"]):
                gpio_elem = ET.SubElement(root, "gpio", id=str(idx))
                ET.SubElement(gpio_elem, "pin").text = cfg["pin"]
                ET.SubElement(gpio_elem, "mode").text = cfg["mode"]
                ET.SubElement(gpio_elem, "pull").text = cfg["pull"]
                ET.SubElement(gpio_elem, "output_type").text = cfg["output_type"]
                ET.SubElement(gpio_elem, "speed").text = cfg["speed"]

            tree = ET.ElementTree(root)
            tree.write(file_path, encoding="utf-8", xml_declaration=True)

        except Exception as e:
            raise RuntimeError(f"保存XML失败:{str(e)}")

    def _save_json(self, file_path, json_data):
        """保存JSON文件"""
        try:
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(json_data, f, indent=4, ensure_ascii=False)
        except Exception as e:
            raise RuntimeError(f"保存JSON失败:{str(e)}")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    tool = STM32GPIOConfigTool()
    tool.show()
    sys.exit(app.exec())

gpio_config.j2

/* 自动生成的STM32F103 GPIO初始化代码 */
/* 生成时间: {{ timestamp }} */
#include "stm32f10x.h"

{% if rcc_afio == "启用" %}
// 启用GPIO和AFIO时钟(复用功能需要AFIO)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
{% else %}
// 仅启用GPIO时钟(无复用功能)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
{% endif %}

/* GPIO初始化配置 */

{% for config in configs %}
// {{ config.pin }} 配置(模式:{{ config.mode }})
GPIO_InitTypeDef GPIO_InitStruct_{{ loop.index }};
GPIO_InitStruct_{{ loop.index }}.GPIO_Pin = GPIO_Pin_{{ config.pin[2:] }};
{% if config.mode == "输入" %}
    {% if config.pull == "上拉" %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = GPIO_Mode_IPU;
    {% elif config.pull == "下拉" %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = GPIO_Mode_IPD;
    {% else %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    {% endif %}
{% elif config.mode == "输出" %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = GPIO_Mode_Out_{{ 'PP' if config.output_type == '推挽' else 'OD' }};
GPIO_InitStruct_{{ loop.index }}.GPIO_Speed = {{ 
    {'低速': 'GPIO_Speed_2MHz', 
     '中速': 'GPIO_Speed_10MHz', 
     '高速': 'GPIO_Speed_50MHz', 
     '最高速': 'GPIO_Speed_50MHz' 
    }[config.speed] 
}};
{% elif config.mode == "复用" %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = GPIO_Mode_AF_{{ 'PP' if config.output_type == '推挽' else 'OD' }};
GPIO_InitStruct_{{ loop.index }}.GPIO_Speed = {{ 
    {'低速': 'GPIO_Speed_2MHz', 
     '中速': 'GPIO_Speed_10MHz', 
     '高速': 'GPIO_Speed_50MHz', 
     '最高速': 'GPIO_Speed_50MHz' 
    }[config.speed] 
}};
{% elif config.mode == "模拟" %}
GPIO_InitStruct_{{ loop.index }}.GPIO_Mode = GPIO_Mode_AIN;
{% endif %}
// 初始化GPIO端口
GPIO_Init(GPIO{{ config.pin[1] }}, &GPIO_InitStruct_{{ loop.index }});

{% endfor %}

{% if rcc_afio == "启用" %}
/* 复用功能特殊处理 */
// 示例:USART1_TX重映射到PA9(根据实际需求修改)
// GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
{% endif %}

/* 初始化完成 */

 

 

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐