配置驅動開發:初探零代碼構建嵌入式軟件配置工具

前言

在嵌入式軟件開發中,硬件初始化與寄存器配置長期依賴人工編寫重復代碼。以STM32外設初始化為例,開發者需手動完成時鐘使能、引腳模式設置、參數配置等步驟,不僅耗時易錯(如位掩碼寫反、模式枚舉值混淆),更因代碼冗余導致維護成本高——修改一個外設參數可能需同步調整多處代碼,稍有不慎便引發硬件異常。

當看到同事通過GUI配置,自動生成底層代碼后,其效率提升讓我意識到:傳統"手寫代碼+文檔"的模式已難以適應復雜嵌入式系統的開發需求。為此,我簡要探索了一下??配置即代碼(Configuration-as-Code)??方案,目標是通過結構化配置文件與自動化工具鏈,實現"零手寫代碼"生成高質量嵌入式初始化代碼,同時兼顧可維護性與靈活性。

技術選型與核心架構

針對嵌入式開發的特殊性,配置工具需滿足三大核心需求:

  1. ??嚴格的Schema驗證機制??:確保配置文件符合硬件約束(如引腳復用沖突、參數取值范圍),從源頭避免無效配置;
  2. ??樹形結構清晰表達層級??:映射硬件物理層級的嵌套關系(如"外設→端口→引腳→參數"),提升配置文件的可讀性與可維護性;
  3. ??注釋能力便于維護??:支持多語言注釋(如中文硬件說明、英文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())

?以下是迭代過程:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914139.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914139.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914139.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Elasticsearch混合搜索深度解析(下):執行機制與完整流程

引言 在上篇中&#xff0c;我們發現了KNN結果通過SubSearch機制被保留的關鍵事實。本篇將繼續深入分析混合搜索的執行機制&#xff0c;揭示完整的處理流程&#xff0c;并解答之前的所有疑惑。 深入源碼分析 1. SubSearch的執行機制 1.1 KnnScoreDocQueryBuilder的實現 KNN結果被…

Apache HTTP Server 從安裝到配置

一、Apache 是什么&#xff1f;Apache&#xff08;全稱 Apache HTTP Server&#xff09;是當前最流行的開源Web服務器軟件之一&#xff0c;由Apache軟件基金會維護。它以穩定性高、模塊化設計和靈活的配置著稱&#xff0c;支持Linux、Windows等多平臺&#xff0c;是搭建個人博客…

php中調用對象的方法可以使用array($object, ‘methodName‘)?

是的&#xff0c;在PHP中&#xff0c;array($object, methodName) 是一種標準的回調語法&#xff0c;用于表示“調用某個對象的特定方法”。這種語法可以被許多函數&#xff08;如 call_user_func()、call_user_func_array()、usort() 等&#xff09;識別并執行。 語法原理 在P…

【設計模式】單例模式 餓漢式單例與懶漢式單例

單例模式&#xff08;Singleton Pattern&#xff09;詳解一、單例模式簡介 單例模式&#xff08;Singleton Pattern&#xff09; 是一種 創建型設計模式&#xff0c;它確保一個類只有一個實例&#xff0c;并提供一個全局訪問點來獲取這個實例。&#xff08;對象創建型模式&…

vue3 el-table 行數據沾滿格自動換行

在使用 Vue 3 結合 Element Plus 的 <el-table> 組件時&#xff0c;如果你希望當表格中的行數據文本過長時能夠自動換行&#xff0c;而不是溢出到其他單元格或簡單地截斷&#xff0c;你可以通過以下幾種方式來實現&#xff1a;方法 1&#xff1a;使用 CSS最簡單的方法是通…

windows電腦遠程win系統服務器上的wsl2

情況 我自己使用win11筆記本電腦&#xff0c;想要遠程win11服務器上的wsl2 我這里只有服務器安裝了wsl2&#xff0c;win11筆記本沒有安裝 因此下面提到的Ubuntu終端指的是win服務器上的wsl2終端 一定要區分是在哪里輸入命令&#xff01;&#xff01; 安裝SSH 在服務器上&#x…

神經輻射場 (NeRF):重構三維世界的AI新視角

神經輻射場 (NeRF)&#xff1a;重構三維世界的AI新視角 舊金山蜿蜒起伏的街道上&#xff0c;一輛裝備12個攝像頭的Waymo自動駕駛測試車緩緩駛過。它記錄的280萬張街景圖像并未被簡單地拼接成平面地圖&#xff0c;而是被輸入一個名為Block-NeRF的神經網絡。數周后&#xff0c;一…

Kubernetes自動擴縮容方案對比與實踐指南

Kubernetes自動擴縮容方案對比與實踐指南 隨著微服務架構和容器化的廣泛采用&#xff0c;Kubernetes 自動擴縮容&#xff08;Autoscaling&#xff09;成為保障生產環境性能穩定與資源高效利用的關鍵技術。面對水平 Pod 擴縮容、垂直資源調整、集群節點擴縮容以及事件驅動擴縮容…

【CVPR2025】計算機視覺|SIREN: 元學習賦能!突破INR高分辨率圖像分類難題

論文地址&#xff1a;https://arxiv.org/pdf/2503.18123v1 代碼地址&#xff1a;https://github.com/SanderGielisse/MWT 關注UP CV縫合怪&#xff0c;分享最計算機視覺新即插即用模塊&#xff0c;并提供配套的論文資料與代碼。 https://space.bilibili.com/473764881 摘要 …

牛客周賽 Round 99

賽時成績如下&#xff1a;A. Round 99題目描述 對于給定的五位整數&#xff0c;檢查其中是否含有數字 99&#xff1b;換句話說&#xff0c;檢查是否存在相鄰的兩個數位&#xff0c;其值均為 。解題思路&#xff1a; 檢查相鄰的兩個數字是否均為9#include <bits/stdc.h> u…

從0到1搭建個人技術博客:用GitHub Pages+Hexo實現

一、為什么要搭建個人技術博客&#xff1f; 在技術圈&#xff0c;擁有個人博客的好處不言而喻&#xff1a; 簡歷加分項&#xff1a;面試官更青睞有技術沉淀的候選人知識系統化&#xff1a;輸出倒逼輸入&#xff0c;加深技術理解人脈拓展&#xff1a;吸引同行關注&#xff0c;…

Ubuntu22.04 設置顯示存在雙屏卻無法雙屏顯示

文章目錄一、背景描述二、解決方法一、背景描述 回到工位后&#xff0c;發現昨天離開時還可正常顯示的雙屏&#xff0c;今早ubuntu22.04 的設置界面顯示有雙屏&#xff0c;但外接的顯示屏無法正常顯示。 首先&#xff0c;查看當前圖像處理顯卡是否為N卡&#xff0c;沒錯&#…

高亞科技簽約奕源金屬,助力打造高效智能化采購管理體系

深圳市奕源金屬制品有限公司近日&#xff0c;國內企業管理軟件服務商高亞科技與深圳市奕源金屬制品有限公司&#xff08;以下簡稱“奕源金屬”&#xff09;正式簽約&#xff0c;雙方將基于高亞科技自主研發的8Manage SRM采購管理系統&#xff0c;共同推動奕源金屬采購管理的數字…

數據結構之map

map的基本介紹我們常常把map稱之為映射&#xff0c;就是將一個元素&#xff08;通常稱之為key鍵&#xff09;與一個相對應的值&#xff08;通常稱之為value&#xff09;關聯起來&#xff0c;比如說一個學生的名字&#xff08;key&#xff09;有與之對應的成績&#xff08;value…

vue3 canvas 選擇器 Canvas 增加頁面性能

文章目錄Vue3 選擇器 Canvas 增加頁面性能基于Vue3 Composition API和Canvas實現的交互式選擇器&#xff0c;支持PC端和移動端的拖動選擇、多選取消選擇功能vue3組件封裝html代碼Vue3 選擇器 Canvas 增加頁面性能 基于Vue3 Composition API和Canvas實現的交互式選擇器&#xf…

Python 實戰:打造多文件批量重命名工具

引言在實際運維、測試、數據分析、開發流程中&#xff0c;我們經常會處理成百上千條命令操作&#xff0c;例如&#xff1a;各種腳本任務&#xff08;啟動、備份、重啟、日志查看&#xff09;數據處理流程&#xff08;爬取 → 清洗 → 統計 → 可視化&#xff09;配置自動化&…

設計模式筆記_結構型_代理模式

1. 代理模式介紹代理模式是一種結構型設計模式&#xff0c;它允許你提供一個代理對象來控制對另一個對象的訪問。代理對象通常在客戶端和目標對象之間起到中介作用&#xff0c;能夠在不改變目標對象的前提下增加額外的功能操作&#xff0c;比如延遲初始化、訪問控制、日志記錄等…

C語言<數據結構-單鏈表>(收尾)

上篇博客我將基礎的尾插、尾刪、頭插、頭刪逐一講解了&#xff0c;這篇博客將對上篇博客進行收尾&#xff0c;講一下指定位置操作增刪以及查找這幾個函數&#xff0c;其實大同小異&#xff1a;一.查找函數&#xff1a;查找函數其實就是一個簡單的循環遍歷&#xff0c;所以不加以…

十年架構心路:從單機到云原生的分布式系統演進史

十年架構心路&#xff1a;從單機到云原生的分布式系統演進史 這里寫目錄標題十年架構心路&#xff1a;從單機到云原生的分布式系統演進史一、技術生涯的起點&#xff1a;單體架構的黃金時代1.1 典型技術棧1.2 記憶深刻的故障二、分布式架構轉型期2.1 服務化拆分實踐2.2 分布式事…

使用docker搭建nginx

安裝docker 和 docker compose驗證docker版本配置docker目錄配置代理&#xff0c;使docker能訪問外網能否ping通最后直接拉入鏡像即可docker pull nginx