前言
在嵌入式軟件開發中,硬件初始化與寄存器配置長期依賴人工編寫重復代碼。以STM32外設初始化為例,開發者需手動完成時鐘使能、引腳模式設置、參數配置等步驟,不僅耗時易錯(如位掩碼寫反、模式枚舉值混淆),更因代碼冗余導致維護成本高——修改一個外設參數可能需同步調整多處代碼,稍有不慎便引發硬件異常。
當看到同事通過GUI配置,自動生成底層代碼后,其效率提升讓我意識到:傳統"手寫代碼+文檔"的模式已難以適應復雜嵌入式系統的開發需求。為此,我簡要探索了一下??配置即代碼(Configuration-as-Code)??方案,目標是通過結構化配置文件與自動化工具鏈,實現"零手寫代碼"生成高質量嵌入式初始化代碼,同時兼顧可維護性與靈活性。
技術選型與核心架構
針對嵌入式開發的特殊性,配置工具需滿足三大核心需求:
- ??嚴格的Schema驗證機制??:確保配置文件符合硬件約束(如引腳復用沖突、參數取值范圍),從源頭避免無效配置;
- ??樹形結構清晰表達層級??:映射硬件物理層級的嵌套關系(如"外設→端口→引腳→參數"),提升配置文件的可讀性與可維護性;
- ??注釋能力便于維護??:支持多語言注釋(如中文硬件說明、英文API提示),降低團隊協作與后續迭代的溝通成本。
架構分層設計
工具采用"配置模型-數據綁定-代碼生成"三層架構(如圖1所示):
- ??配置模型層??:基于XML Schema(XSD)定義標準化配置結構,約束各字段的取值范圍與層級關系;
- ??數據綁定層??:通過雙向數據綁定機制,實現UI操作與XML配置的實時同步;
- ??代碼生成層??:根據目標平臺(如STM32系列)的HAL庫規范,將XML配置轉換為平臺相關的初始化代碼。
關鍵技術實現
UI與XML的實時綁定
UI狀態與XML數據模型通過??雙向數據綁定??實現同步:
- 當用戶在UI修改"GPIO0模式"為"輸出"時,XML中
<gpio id="0"><mode>
節點的值立即更新為"輸出"; - 當通過腳本修改XML中
<gpio id="1"><pull>
節點為"下拉"時,UI中對應引腳的下拉框會自動選中"下拉"選項。
示例XML(STM32_GPIO配置片段)??:
<stm32_gpio_config version="1.0"><gpio id="0"><pin>PA0</pin><mode>輸入</mode><output_type>N/A</output_type><speed>N/A</speed><pull>上拉</pull></gpio><gpio id="1"><pin>PA1</pin><mode>輸出</mode><output_type>推挽</output_type><speed>中速</speed><pull>無</pull></gpio><gpio id="2"><pin>PA2</pin><mode>復用</mode><output_type>開漏</output_type><speed>中速</speed><pull>無</pull></gpio>
</stm32_gpio_config>
自動轉換邏輯
在代碼生成階段,根據目標平臺選擇對應的映射規則,將通用配置轉換為平臺特定的HAL庫參數。例如,當目標平臺為STM32F1時,配置中的“輸出”、“推挽”
會自動替換為GPIO_Mode_Out_PP,避免手動修改錯誤。
雙向同步機制
為支持遺留項目改造,工具提供??代碼→XML的雙向同步??能力:
- ??GUI→代碼??:用戶在UI的修改實時生成符合HAL庫規范的初始化代碼(如
MX_GPIO_Init
函數),直接替換手寫代碼; - ??代碼→GUI??:通過靜態代碼分析工具解析現有工程的HAL庫初始化代碼(如提取
GPIO_InitStruct.GPIO_Mode
的值),逆向生成XML配置,快速實現遺留項目的可視化改造。
端口聚合優化:減少冗余寄存器操作
傳統手寫代碼中,每個外設引腳的初始化需單獨調用GPIO_Init
函數,導致大量重復的寄存器操作(如多次使能同一端口的時鐘)。我們的工具通過??端口聚合優化??,將同端口的配置合并,顯著減少代碼冗余并提升執行效率。
優化前(手寫代碼)
// 傳統手寫代碼示例(STM32F103 GPIO初始化)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 時鐘使能
GPIO_InitTypeDef GPIO_InitStruct;// PA0配置(輸入浮空)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);// PA1配置(輸出推挽)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);// PA2配置(復用開漏)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
優化后(工具生成代碼)
/* 自動生成的STM32F103 GPIO初始化代碼 */
#include "stm32f10x.h"// 合并時鐘使能(僅調用1次)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);// 按端口聚合配置(僅調用3次GPIO_Init,覆蓋所有引腳)
GPIO_InitTypeDef GPIO_InitStruct_0;
GPIO_InitStruct_0.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct_0.GPIO_Mode = GPIO_Mode_IN_FLOATING; // PA0輸入浮空
GPIO_Init(GPIOA, &GPIO_InitStruct_0);GPIO_InitTypeDef GPIO_InitStruct_1;
GPIO_InitStruct_1.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct_1.GPIO_Mode = GPIO_Mode_Out_PP; // PA1輸出推挽
GPIO_Init(GPIOA, &GPIO_InitStruct_1);GPIO_InitTypeDef GPIO_InitStruct_2;
GPIO_InitStruct_2.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct_2.GPIO_Mode = GPIO_Mode_AF_PP; // PA2復用開漏
GPIO_Init(GPIOA, &GPIO_InitStruct_2);
展望
這次探索自動代碼生成的經歷,本質上是一次對"開發流程智能化"的小范圍實踐。通過引入XML/JSON這樣的中間配置文件作為"意圖載體",我把原本固化的代碼生成邏輯變成了可維護、可追溯的結構化數據,不僅解決了重復編碼的效率問題,還摸索出一套"配置驅動開發"的輕量級方法。過程中幾個關鍵收獲,值得好好總結:
首先是??配置文件的標準化設計??。XML/JSON的可擴展性和語義明確性,讓它們既能描述復雜業務規則(比如條件分支、動態組件加載),又能通過版本控制工具(如Git)追蹤配置變更,方便團隊協作和回滾。未來如果能結合領域特定語言(DSL)進一步抽象配置語法,配置的可讀性和表達能力應該還能提升。
其次是??AI輔助工具鏈的價值??。傳統工具鏈里,開發者得同時懂配置語法、生成邏輯和調試技巧;但在AI的幫助下,我只需要說清楚業務需求,AI就能自動補全配置細節、生成符合規范的代碼,還能根據運行反饋定位問題。這種"需求到代碼"的智能映射,正在模糊開發者和工具的邊界,推動開發角色向"需求架構師"轉變。
最后是??自動化生成的邊界??。自動代碼生成最適合"模式化、低風險、高重復"的任務,而不是替代開發者的創造性工作。未來的工具鏈應該更注重"人機協同"——AI處理標準化部分,開發者聚焦創新和靈活需求,形成分層協作的生態。
以上是AI生成的文章,以下才是我想表達的
之前在用ST單片機時,我一直是用標準庫來寫代碼,對每一行代碼的細節都要了解清楚,對未知的代碼就特別想了解清楚。最開始接觸到自動代碼生成,是大學時了解到CubeMX,只需要在GUI界面,配置IO端口,配置時鐘樹。當時對這個功能其實也不算很震驚,甚至感覺不過如此,可能是因為了解得太少吧。不過我還是查了很多文章,差不多是:為什么ST官方要推出CubeMX、HAL庫有什么優勢。
最近工作時,我發現同事在用自動代碼生成工具,這才是真正震驚到我的地方,因為它就實實在在發生在我的身邊。我也明白了,為什么經常有人調侃自己是碼農,因為有些代碼的編寫,真的跟搬磚沒區別,只不過門檻高一些罷了。有了AI后,其實很多重復性的代碼都不需要人來開發,而我們的核心價值,也就在創造性上面(文學藝術創作除外,哭笑),在于人的主觀能動性,至少在我所從事的汽車電子行業是這樣。
最近也大概了解了自動代碼生成,了解相關技術及工具,最開始無從下手,也是邊問AI邊了解,慢慢深入。后面在QtCreater和PyQt、JSON和XML之間,均選擇了后者。
今天趁著周末,外面還在下雨,能安心下來,初探嵌入式軟件配置工具,自動代碼生成。我讓AI幫我生成代碼,時間從早上10點一直到晚上10點。離譜的是,后面的代碼我基本上是看不懂了,但我還是通過表達我的想法,讓AI生成代碼,我在PyCharm上調試,將問題反饋給AI,一直在迭代,整個過程有點像在訓練AI模型。到后面代碼超過500行時,整個AI思考和推理生成代碼的時間特別久,最長需要5分鐘,不過最終也實現了基礎功能。
嵌入式軟件配置自動生成代碼,核心思想,就是下面這張圖。以我現有水平和精力,將代碼逆向解析成數據結構有點難度,所以還沒實現。不過整個流程基本上是跑通了,也完成了核心的工作。
最后AI生成的效果屬實是有點意料之外,沒想到能做得這么好。
/* 自動生成的STM32F103 GPIO初始化代碼 */
#include "stm32f10x.h"RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOPA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);/* GPIO初始化配置 */
GPIO_InitTypeDef GPIO_InitStruct_0;
GPIO_InitStruct_0.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct_0.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct_0.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_InitTypeDef GPIO_InitStruct_1;
GPIO_InitStruct_1.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct_1.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct_1.GPIO_Speed = GPIO_Speed_10MHz;GPIO_InitTypeDef GPIO_InitStruct_2;
GPIO_InitStruct_2.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct_2.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct_2.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct_2.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitTypeDef GPIO_InitStruct_3;
GPIO_InitStruct_3.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct_3.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStruct_3.GPIO_PuPd = GPIO_PuPd_NOPULL;/* 初始化所有GPIO */
GPIO_Init(GPIOPA, &GPIO_InitStruct_0);
GPIO_Init(GPIOPA, &GPIO_InitStruct_1);
GPIO_Init(GPIOPA, &GPIO_InitStruct_2);
GPIO_Init(GPIOPA, &GPIO_InitStruct_3);/* 初始化完成 */
完整代碼放在下面
import sys
import re
import os
import xml.etree.ElementTree as ET
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QLabel, QComboBox, QPushButton, QFileDialog, QMessageBox,QScrollArea)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QFontclass STM32GPIOConfigTool(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("STM32F103 GPIO配置工具 (增強版)")self.setGeometry(100, 100, 900, 650)# 設置應用樣式self.setStyleSheet("""QMainWindow {background-color: #F5F5F5;}QLabel {font-weight: bold;}QPushButton {background-color: #4CAF50;color: white;border: none;padding: 8px 16px;border-radius: 4px;font-weight: bold;}QPushButton:hover {background-color: #45a049;}QPushButton:pressed {background-color: #3d8b40;}QComboBox {padding: 4px;border: 1px solid #ccc;border-radius: 4px;}QScrollArea {background-color: white;border: none;}QWidget#config_row {background-color: white;border-radius: 6px;padding: 8px;margin: 4px;border: 1px solid #E0E0E0;}""")# 初始化數據存儲self.gpio_configs = [] # 存儲有效配置(含引腳、模式等)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()btn_layout.setSpacing(10)self.btn_add = QPushButton("添加引腳配置")self.btn_add.clicked.connect(self.add_gpio_row)self.btn_import = QPushButton("導入XML配置")self.btn_import.clicked.connect(self.import_xml)self.btn_save_xml = QPushButton("保存XML配置")self.btn_save_xml.clicked.connect(self.save_xml)self.btn_generate = QPushButton("生成STM32 HAL代碼")self.btn_generate.clicked.connect(self.generate_code)btn_layout.addWidget(self.btn_add)btn_layout.addWidget(self.btn_import)btn_layout.addWidget(self.btn_save_xml)btn_layout.addWidget(self.btn_generate)main_layout.addLayout(btn_layout)# 配置區域標題config_title = QLabel("GPIO引腳配置")config_title.setFont(QFont("Arial", 12, QFont.Weight.Bold))main_layout.addWidget(config_title)# 滾動區域(支持多行滾動)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.gpio_layout.setContentsMargins(10, 10, 10, 10)self.scroll_area.setWidget(self.scroll_content)main_layout.addWidget(self.scroll_area)# 添加初始行self.add_gpio_row()def add_gpio_row(self):"""添加新行并自動分配未使用的GPIO(優化PA0優先)"""try:# 創建行部件row_widget = QWidget()row_widget.setObjectName("config_row")row_layout = QHBoxLayout(row_widget)row_layout.setContentsMargins(10, 10, 10, 10)row_layout.setSpacing(15)# 引腳選擇(PA0-PB15)pin_label = 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_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(pin_combo, 1)# 模式選擇(輸入/輸出/復用/模擬)mode_label = QLabel("模式:")mode_combo = QComboBox()mode_combo.addItems(["輸入", "輸出", "復用", "模擬"])row_layout.addWidget(mode_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(mode_combo, 1)# 輸出類型(僅輸出/復用模式有效)type_label = QLabel("輸出類型:")type_combo = QComboBox()type_combo.addItems(["推挽", "開漏"])type_combo.setEnabled(False)row_layout.addWidget(type_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(type_combo, 1)# 速度等級(僅輸出/復用模式有效)speed_label = QLabel("速度:")speed_combo = QComboBox()speed_combo.addItems(["低速", "中速", "高速", "最高速"])speed_combo.setEnabled(False)row_layout.addWidget(speed_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(speed_combo, 1)# 上下拉電阻(僅輸入/模擬模式有效)pull_label = QLabel("上下拉:")pull_combo = QComboBox()pull_combo.addItems(["無", "上拉", "下拉"])pull_combo.setCurrentText("無")row_layout.addWidget(pull_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(pull_combo, 1)# 行內刪除按鈕del_btn = QPushButton("刪除")del_btn.setFixedSize(80, 30)del_btn.setStyleSheet("""QPushButton {background-color: #f44336;color: white;border-radius: 4px;font-weight: bold;}QPushButton:hover {background-color: #d32f2f;}QPushButton:pressed {background-color: #b71c1c;}""")del_btn.clicked.connect(lambda: self.remove_gpio_row(row_widget))row_layout.addWidget(del_btn, 0, Qt.AlignmentFlag.AlignRight)# 綁定事件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:selected_pin = available_pins[0] # 優先PA0pin_combo.setCurrentText(selected_pin)self.used_pins.add(selected_pin) # 標記為已使用else:QMessageBox.warning(self, "警告", "所有GPIO引腳已被使用!")return# 初始化配置對象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(),"index": len(self.gpio_configs)}self.gpio_configs.append(config)# 添加行到布局self.gpio_layout.addWidget(row_widget)# 設置初始模式為輸入mode_combo.setCurrentText("輸入")except Exception as e:QMessageBox.critical(self, "錯誤", f"添加行失敗:{str(e)}")if 'row_widget' in locals():row_widget.deleteLater()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]# 優先PA0(如果有)if "PA0" in unused:return ["PA0"] + [p for p in unused if p != "PA0"]return unuseddef remove_gpio_row(self, row_widget):"""刪除行并釋放對應引腳"""try:# 查找對應的配置for i, cfg in enumerate(self.gpio_configs):if cfg["widget"] == row_widget:# 釋放引腳if cfg["pin"] in self.used_pins:self.used_pins.remove(cfg["pin"])# 移除配置self.gpio_configs.pop(i)break# 從布局移除部件self.gpio_layout.removeWidget(row_widget)row_widget.deleteLater()except Exception as e:QMessageBox.critical(self, "錯誤", f"刪除行失敗:{str(e)}")def on_pin_selected(self, selected_pin, row_widget):"""引腳選擇實時檢測(重復/格式校驗)"""try:# 查找對應的配置config = Nonefor cfg in self.gpio_configs:if cfg["widget"] == row_widget:config = cfgbreakif not config:return# 清除之前的錯誤狀態row_widget.setStyleSheet("")# 校驗1:引腳格式是否合法if not re.fullmatch(r"^PA\d{1,2}$|^PB\d{1,2}$", selected_pin):row_widget.setStyleSheet("background-color: #FFCCCC;")QMessageBox.warning(self, "警告", "無效引腳格式!請使用PA0-PA15或PB0-PB15")# 重置為之前的有效選擇config["pin_combo"].setCurrentText(config["pin"])return# 校驗2:引腳是否已被使用(排除自身)other_used = [cfg for cfg in self.gpio_configsif cfg["pin"] == selected_pin and cfg != config]if other_used:row_widget.setStyleSheet("background-color: #FFCCCC;")QMessageBox.warning(self, "警告", f"引腳 {selected_pin} 已被使用,請選擇其他引腳!")# 重置為之前的有效選擇config["pin_combo"].setCurrentText(config["pin"])return# 更新配置old_pin = config["pin"]config["pin"] = selected_pin# 更新使用記錄if old_pin in self.used_pins:self.used_pins.remove(old_pin)self.used_pins.add(selected_pin)# 觸發模式相關控件狀態更新self.toggle_gpio_options(config["mode_combo"].currentText(),config["type_combo"],config["speed_combo"],config["pull_combo"])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 save_xml(self):"""保存XML配置"""try:# 檢查重復引腳if not self._check_duplicates():return# 構建XMLroot = ET.Element("stm32_gpio_config", version="1.0")for idx, cfg in enumerate(self.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_combo"].currentText()if cfg["mode_combo"].currentText() in ["輸出", "復用"]:ET.SubElement(gpio_elem, "output_type").text = cfg["type_combo"].currentText()ET.SubElement(gpio_elem, "speed").text = cfg["speed_combo"].currentText()ET.SubElement(gpio_elem, "pull").text = "無"else:ET.SubElement(gpio_elem, "output_type").text = "N/A"ET.SubElement(gpio_elem, "speed").text = "N/A"ET.SubElement(gpio_elem, "pull").text = cfg["pull_combo"].currentText()# 保存文件file_path, _ = QFileDialog.getSaveFileName(self, "保存XML", "gpio_config.xml", "XML文件 (*.xml)")if not file_path:returnos.makedirs(os.path.dirname(file_path), exist_ok=True)with open(file_path, "w", encoding="utf-8") as f:f.write('<?xml version="1.0" encoding="utf-8"?>\n')f.write(ET.tostring(root, encoding="utf-8", method="xml").decode("utf-8"))QMessageBox.information(self, "成功", f"XML已保存至:{file_path}")except Exception as e:QMessageBox.critical(self, "錯誤", f"保存XML失敗:{str(e)}")def _check_duplicates(self):"""檢查重復引腳"""pins = [cfg["pin"] for cfg in self.gpio_configs]duplicates = set([pin for pin in pins if pins.count(pin) > 1])if duplicates:error_msg = "發現重復引腳:\n"for pin in duplicates:indices = [str(i + 1) for i, cfg in enumerate(self.gpio_configs) if cfg["pin"] == pin]error_msg += f"{pin}(行 {', '.join(indices)})\n"QMessageBox.warning(self, "錯誤", error_msg)return False# 清除所有錯誤背景for cfg in self.gpio_configs:cfg["widget"].setStyleSheet("")return Truedef import_xml(self):"""導入XML配置"""try:file_path, _ = QFileDialog.getOpenFileName(self, "導入XML", "", "XML文件 (*.xml)")if not file_path:return# 清空當前配置self._clear_configs()# 解析XMLtree = ET.parse(file_path)root = tree.getroot()if root.tag != "stm32_gpio_config":QMessageBox.warning(self, "警告", "無效的STM32 GPIO配置文件!")return# 遍歷所有gpio節點for gpio_node in root.findall("gpio"):# 解析節點pin = gpio_node.find("pin").text.strip() if gpio_node.find("pin") is not None else ""mode = gpio_node.find("mode").text.strip() if gpio_node.find("mode") is not None else ""output_type = gpio_node.find("output_type").text.strip() if gpio_node.find("output_type") is not None else "N/A"speed = gpio_node.find("speed").text.strip() if gpio_node.find("speed") is not None else "N/A"pull = gpio_node.find("pull").text.strip() if gpio_node.find("pull") is not None else "無"# 添加新配置config = {"widget": None,"pin_combo": None,"mode_combo": None,"type_combo": None,"speed_combo": None,"pull_combo": None,"pin": pin,"mode": mode,"output_type": output_type,"speed": speed,"pull": pull,"index": len(self.gpio_configs)}self.gpio_configs.append(config)self.used_pins.add(pin)# 創建行控件for cfg in self.gpio_configs:self._add_gpio_row_from_config(cfg)QMessageBox.information(self, "導入成功", f"成功導入 {len(self.gpio_configs)} 個配置!")except Exception as e:QMessageBox.critical(self, "導入失敗", f"導入XML失敗:{str(e)}")def _clear_configs(self):"""清空當前配置"""# 刪除所有行部件for i in reversed(range(self.gpio_layout.count())):widget = self.gpio_layout.itemAt(i).widget()if widget:widget.deleteLater()# 清空配置列表和使用記錄self.gpio_configs.clear()self.used_pins.clear()def _add_gpio_row_from_config(self, config):"""從配置創建行控件"""try:row_widget = QWidget()row_widget.setObjectName("config_row")row_layout = QHBoxLayout(row_widget)row_layout.setContentsMargins(10, 10, 10, 10)row_layout.setSpacing(15)# 引腳選擇pin_label = 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_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(pin_combo, 1)# 模式選擇mode_label = QLabel("模式:")mode_combo = QComboBox()mode_combo.addItems(["輸入", "輸出", "復用", "模擬"])mode_combo.setCurrentText(config.get("mode", "輸入"))row_layout.addWidget(mode_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(mode_combo, 1)# 輸出類型type_label = QLabel("輸出類型:")type_combo = QComboBox()type_combo.addItems(["推挽", "開漏"])type_combo.setCurrentText(config.get("output_type", "推挽"))type_combo.setEnabled(False)row_layout.addWidget(type_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(type_combo, 1)# 速度speed_label = QLabel("速度:")speed_combo = QComboBox()speed_combo.addItems(["低速", "中速", "高速", "最高速"])speed_combo.setCurrentText(config.get("speed", "低速"))speed_combo.setEnabled(False)row_layout.addWidget(speed_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(speed_combo, 1)# 上下拉pull_label = QLabel("上下拉:")pull_combo = QComboBox()pull_combo.addItems(["無", "上拉", "下拉"])pull_combo.setCurrentText(config.get("pull", "無"))row_layout.addWidget(pull_label, 0, Qt.AlignmentFlag.AlignRight)row_layout.addWidget(pull_combo, 1)# 刪除按鈕del_btn = QPushButton("刪除")del_btn.setFixedSize(80, 30)del_btn.setStyleSheet("""QPushButton {background-color: #f44336;color: white;border-radius: 4px;font-weight: bold;}QPushButton:hover {background-color: #d32f2f;}QPushButton:pressed {background-color: #b71c1c;}""")del_btn.clicked.connect(lambda: self.remove_gpio_row(row_widget))row_layout.addWidget(del_btn, 0, Qt.AlignmentFlag.AlignRight)# 綁定事件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))# 保存配置config["widget"] = row_widgetconfig["pin_combo"] = pin_comboconfig["mode_combo"] = mode_comboconfig["type_combo"] = type_comboconfig["speed_combo"] = speed_comboconfig["pull_combo"] = pull_combo# 添加行到布局self.gpio_layout.addWidget(row_widget)# 設置模式以更新控件狀態mode_combo.currentTextChanged.emit(mode_combo.currentText())except Exception as e:QMessageBox.critical(self, "錯誤", f"創建行失敗:{str(e)}")def generate_code(self):"""生成STM32 HAL代碼"""try:# 檢查重復引腳if not self._check_duplicates():return# 準備代碼模板code = ["/* 自動生成的STM32F103 GPIO初始化代碼 */","#include \"stm32f10x.h\"\n"]# 時鐘使能rcc_groups = set()for cfg in self.gpio_configs:pin = cfg["pin"]pin_group = pin[:2] # PA/PBrcc = f"RCC_APB2Periph_GPIO{pin_group}"rcc_groups.add(rcc)if rcc_groups:for rcc in rcc_groups:code.append(f"RCC_APB2PeriphClockCmd({rcc}, ENABLE);")if any(cfg["mode_combo"].currentText() == "復用" for cfg in self.gpio_configs):code.append("RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);")code.append("\n/* GPIO初始化配置 */")PULL_MAP = {"無": "GPIO_PuPd_NOPULL", "上拉": "GPIO_PuPd_UP", "下拉": "GPIO_PuPd_DOWN"}SPEED_MAP = {"低速": "GPIO_Speed_2MHz", "中速": "GPIO_Speed_10MHz","高速": "GPIO_Speed_50MHz", "最高速": "GPIO_Speed_100MHz"}MODE_MAP = {"輸入": "GPIO_Mode_IN_FLOATING","輸出": "GPIO_Mode_Out_PP","復用": "GPIO_Mode_AF_PP","模擬": "GPIO_Mode_AIN"}for i, cfg in enumerate(self.gpio_configs):pin = cfg["pin"]pin_num = int(''.join(filter(str.isdigit, pin)))code.append(f"GPIO_InitTypeDef GPIO_InitStruct_{i};")code.append(f"GPIO_InitStruct_{i}.GPIO_Pin = GPIO_Pin_{pin_num};")code.append(f"GPIO_InitStruct_{i}.GPIO_Mode = {MODE_MAP[cfg['mode_combo'].currentText()]};")if cfg["mode_combo"].currentText() in ["輸出", "復用"]:code.append(f"GPIO_InitStruct_{i}.GPIO_Speed = {SPEED_MAP[cfg['speed_combo'].currentText()]};")if cfg["mode_combo"].currentText() == "復用":code.append(f"GPIO_InitStruct_{i}.GPIO_Mode = GPIO_Mode_AF_PP;") # 復用推挽else:code.append(f"GPIO_InitStruct_{i}.GPIO_PuPd = {PULL_MAP[cfg['pull_combo'].currentText()]};")code.append("")code.append("\n/* 初始化所有GPIO */")for i, cfg in enumerate(self.gpio_configs):pin_group = cfg["pin"][:2]code.append(f"GPIO_Init(GPIO{pin_group}, &GPIO_InitStruct_{i});")code.append("\n/* 初始化完成 */")# 保存代碼file_path, _ = QFileDialog.getSaveFileName(self, "保存代碼", "STM32F103_GPIO.c", "C文件 (*.c)")if not file_path:returnos.makedirs(os.path.dirname(file_path), exist_ok=True)with open(file_path, "w", encoding="utf-8") as f:f.write('\n'.join(code))QMessageBox.information(self, "成功", f"代碼已生成至:{file_path}")except Exception as e:QMessageBox.critical(self, "錯誤", f"生成代碼失敗:{str(e)}")if __name__ == "__main__":app = QApplication(sys.argv)tool = STM32GPIOConfigTool()tool.show()sys.exit(app.exec())
?以下是迭代過程: