单独的代码架构

This commit is contained in:
Robofish 2025-06-18 14:30:26 +08:00
parent ffad148fcc
commit 78104b724b
18 changed files with 0 additions and 4577 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 zucheng Lv
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2339
MR_Tool.py

File diff suppressed because it is too large Load Diff

673
MRobot.py
View File

@ -1,673 +0,0 @@
import sys
import os
import serial
import serial.tools.list_ports
from PyQt5.QtCore import Qt, pyqtSignal, QThread
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QGroupBox,
QComboBox, QPushButton, QTextEdit, QLineEdit, QLabel, QSizePolicy,
QFileDialog, QMessageBox, QStackedLayout
)
from qfluentwidgets import (
Theme, setTheme, FluentIcon, SwitchButton, BodyLabel, SubtitleLabel,
StrongBodyLabel, HorizontalSeparator, InfoBar, MessageDialog, Dialog,
AvatarWidget, NavigationItemPosition, FluentWindow, NavigationAvatarWidget,
PushButton, TextEdit, LineEdit, ComboBox, ImageLabel
)
from qfluentwidgets import FluentIcon as FIF
import requests
import shutil
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QHeaderView
from qfluentwidgets import (
TreeWidget, InfoBar, InfoBarPosition, MessageDialog, TreeItemDelegate
)
from qfluentwidgets import CheckBox
from qfluentwidgets import TreeWidget
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QFileDialog
from qfluentwidgets import ProgressBar
# ===================== 页面基类 =====================
class BaseInterface(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
# ===================== 首页界面 =====================
class HomeInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("homeInterface")
layout = QVBoxLayout()
layout.setContentsMargins(60, 60, 60, 60)
layout.setSpacing(32)
self.setLayout(layout)
# 顶部logo和欢迎区
top_layout = QHBoxLayout()
logo = ImageLabel('img/MRobot.png')
logo.setFixedSize(260, 80)
top_layout.addWidget(logo, alignment=Qt.AlignmentFlag.AlignTop)
title_layout = QVBoxLayout()
title_layout.addWidget(StrongBodyLabel("欢迎使用 MRobot Toolbox"))
title_layout.addWidget(SubtitleLabel("让你的机器人开发更高效、更智能"))
top_layout.addLayout(title_layout)
top_layout.addStretch()
layout.addLayout(top_layout)
layout.addWidget(HorizontalSeparator())
# 项目简介
layout.addWidget(BodyLabel(
"MRobot Toolbox 是一款集成化的机器人开发辅助工具,"
"支持代码生成、串口终端、主题切换等多种实用功能。\n"
"点击左侧导航栏可快速切换各功能页面。"
))
# 开发者与项目目标
layout.addWidget(HorizontalSeparator())
layout.addWidget(SubtitleLabel("开发者与项目目标"))
layout.addWidget(BodyLabel("开发团队QUT 青岛理工大学 MOVE 战队"))
layout.addWidget(BodyLabel("项目目标:为所有 rmer 和 rcer 提供现代化、简单、高效的机器人开发方式,"
"让机器人开发变得更轻松、更智能。"))
layout.addWidget(BodyLabel("适用于 RM、RC、各类嵌入式机器人项目。"))
# layout.addStretch()
# ===================== 代码生成页面 =====================
class DataInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("dataInterface")
self.stacked_layout = QStackedLayout()
self.setLayout(self.stacked_layout)
# --- 页面1工程路径选择 ---
self.select_widget = QWidget()
select_layout = QVBoxLayout(self.select_widget)
select_layout.addSpacing(40)
select_layout.addWidget(SubtitleLabel("MRobot 代码生成"))
select_layout.addWidget(HorizontalSeparator())
select_layout.addSpacing(10)
select_layout.addWidget(BodyLabel("请选择包含 .ioc 文件的工程文件夹,点击下方按钮进行选择。"))
select_layout.addSpacing(20)
self.choose_btn = PushButton("选择工程路径")
self.choose_btn.clicked.connect(self.choose_project_folder)
select_layout.addWidget(self.choose_btn)
select_layout.addStretch()
self.stacked_layout.addWidget(self.select_widget)
# --- 页面2代码配置 ---
self.config_widget = QWidget()
self.config_layout = QVBoxLayout(self.config_widget)
# 左上角小返回按钮
top_bar = QHBoxLayout()
self.back_btn = PushButton('返回', icon=FluentIcon.SKIP_BACK)
# self.back_btn.setFixedSize(32, 32)
self.back_btn.clicked.connect(self.back_to_select)
self.back_btn.setToolTip("返回")
top_bar.addWidget(self.back_btn, alignment=Qt.AlignmentFlag.AlignLeft)
top_bar.addStretch()
self.config_layout.addLayout(top_bar)
self.config_layout.addWidget(SubtitleLabel("工程配置信息"))
self.config_layout.addWidget(HorizontalSeparator())
self.project_info_labels = []
self.config_layout.addStretch()
self.stacked_layout.addWidget(self.config_widget)
# 默认显示选择页面
self.stacked_layout.setCurrentWidget(self.select_widget)
def choose_project_folder(self):
folder = QFileDialog.getExistingDirectory(self, "请选择代码项目文件夹")
if not folder:
return
ioc_files = [f for f in os.listdir(folder) if f.endswith('.ioc')]
if not ioc_files:
QMessageBox.warning(self, "提示", "未找到.ioc文件请确认项目文件夹。")
return
self.project_path = folder
self.project_name = os.path.basename(folder)
self.ioc_file = os.path.join(folder, ioc_files[0])
self.show_config_page()
def show_config_page(self):
# 清理旧内容
for label in self.project_info_labels:
self.config_layout.removeWidget(label)
label.deleteLater()
self.project_info_labels.clear()
# 显示项目信息
l1 = BodyLabel(f"项目名称: {self.project_name}")
l2 = BodyLabel(f"项目路径: {self.project_path}")
l3 = BodyLabel(f"IOC 文件: {self.ioc_file}")
self.config_layout.insertWidget(2, l1)
self.config_layout.insertWidget(3, l2)
self.config_layout.insertWidget(4, l3)
self.project_info_labels.extend([l1, l2, l3])
self.stacked_layout.setCurrentWidget(self.config_widget)
def back_to_select(self):
self.stacked_layout.setCurrentWidget(self.select_widget)
# ===================== 串口终端界面 =====================
class SerialReadThread(QThread):
data_received = pyqtSignal(str)
def __init__(self, ser):
super().__init__()
self.ser = ser
self._running = True
def run(self):
while self._running:
if self.ser and self.ser.is_open and self.ser.in_waiting:
try:
data = self.ser.readline().decode(errors='ignore')
self.data_received.emit(data)
except Exception:
pass
def stop(self):
self._running = False
self.wait()
class SerialTerminalInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("serialTerminalInterface")
main_layout = QVBoxLayout(self)
main_layout.setSpacing(12)
# 顶部:串口设置区
top_hbox = QHBoxLayout()
top_hbox.addWidget(BodyLabel("串口:"))
self.port_combo = ComboBox()
self.refresh_ports()
top_hbox.addWidget(self.port_combo)
top_hbox.addWidget(BodyLabel("波特率:"))
self.baud_combo = ComboBox()
self.baud_combo.addItems(['9600', '115200', '57600', '38400', '19200', '4800'])
top_hbox.addWidget(self.baud_combo)
self.connect_btn = PushButton("连接")
self.connect_btn.clicked.connect(self.toggle_connection)
top_hbox.addWidget(self.connect_btn)
self.refresh_btn = PushButton(FluentIcon.SYNC, "刷新")
self.refresh_btn.clicked.connect(self.refresh_ports)
top_hbox.addWidget(self.refresh_btn)
top_hbox.addStretch()
main_layout.addLayout(top_hbox)
main_layout.addWidget(HorizontalSeparator())
# 中部:左侧预设命令,右侧显示区
center_hbox = QHBoxLayout()
# 左侧:预设命令竖排
preset_vbox = QVBoxLayout()
preset_vbox.addWidget(SubtitleLabel("快捷指令"))
#快捷指令居中
preset_vbox.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.preset_commands = [
("线程监视器", "RESET"),
("陀螺仪校准", "GET_VERSION"),
("性能监视", "START"),
("重启", "STOP"),
("显示所有设备", "SELF_TEST"),
("查询id", "STATUS"),
]
for label, cmd in self.preset_commands:
btn = PushButton(label)
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
btn.clicked.connect(lambda _, c=cmd: self.send_preset_command(c))
preset_vbox.addWidget(btn)
preset_vbox.addStretch()
main_layout.addLayout(center_hbox, stretch=1)
# 右侧:串口数据显示区
self.text_edit = TextEdit()
self.text_edit.setReadOnly(True)
self.text_edit.setMinimumWidth(400)
center_hbox.addWidget(self.text_edit, 3)
center_hbox.addLayout(preset_vbox, 1)
main_layout.addWidget(HorizontalSeparator())
# 底部:输入区
bottom_hbox = QHBoxLayout()
self.input_line = LineEdit()
self.input_line.setPlaceholderText("输入内容,回车发送")
self.input_line.returnPressed.connect(self.send_data)
bottom_hbox.addWidget(self.input_line, 4)
send_btn = PushButton("发送")
send_btn.clicked.connect(self.send_data)
bottom_hbox.addWidget(send_btn, 1)
self.auto_enter_checkbox = CheckBox("自动回车")
self.auto_enter_checkbox.setChecked(True)
bottom_hbox.addWidget(self.auto_enter_checkbox)
bottom_hbox.addStretch()
main_layout.addLayout(bottom_hbox)
self.ser = None
self.read_thread = None
def send_preset_command(self, cmd):
self.input_line.setText(cmd)
self.send_data()
def refresh_ports(self):
self.port_combo.clear()
ports = serial.tools.list_ports.comports()
for port in ports:
self.port_combo.addItem(port.device)
def toggle_connection(self):
if self.ser and self.ser.is_open:
self.disconnect_serial()
else:
self.connect_serial()
def connect_serial(self):
port = self.port_combo.currentText()
baud = int(self.baud_combo.currentText())
try:
self.ser = serial.Serial(port, baud, timeout=0.1)
self.connect_btn.setText("断开")
self.text_edit.append(f"已连接到 {port} @ {baud}")
self.read_thread = SerialReadThread(self.ser)
self.read_thread.data_received.connect(self.display_data)
self.read_thread.start()
except Exception as e:
self.text_edit.append(f"连接失败: {e}")
def disconnect_serial(self):
if self.read_thread:
self.read_thread.stop()
self.read_thread = None
if self.ser:
self.ser.close()
self.ser = None
self.connect_btn.setText("连接")
self.text_edit.append("已断开连接")
def display_data(self, data):
self.text_edit.moveCursor(QTextCursor.End)
self.text_edit.insertPlainText(data)
self.text_edit.moveCursor(QTextCursor.End)
def send_data(self):
if self.ser and self.ser.is_open:
text = self.input_line.text()
try:
if not text:
self.ser.write('\n'.encode())
else:
for char in text:
self.ser.write(char.encode())
# 判断是否自动回车
if self.auto_enter_checkbox.isChecked():
self.ser.write('\n'.encode())
except Exception as e:
self.text_edit.append(f"发送失败: {e}")
self.input_line.clear()
# ===================== 零件库页面 =====================
# ...existing code...
class DownloadThread(QThread):
progressChanged = pyqtSignal(int)
finished = pyqtSignal(list, list) # success, fail
def __init__(self, files, server_url, secret_key, local_dir, parent=None):
super().__init__(parent)
self.files = files
self.server_url = server_url
self.secret_key = secret_key
self.local_dir = local_dir
def run(self):
success, fail = [], []
total = len(self.files)
max_retry = 3 # 最大重试次数
for idx, rel_path in enumerate(self.files):
retry = 0
while retry < max_retry:
try:
url = f"{self.server_url}/download/{rel_path}"
params = {"key": self.secret_key}
resp = requests.get(url, params=params, stream=True, timeout=10)
if resp.status_code == 200:
local_path = os.path.join(self.local_dir, rel_path)
os.makedirs(os.path.dirname(local_path), exist_ok=True)
with open(local_path, "wb") as f:
shutil.copyfileobj(resp.raw, f)
success.append(rel_path)
break # 下载成功,跳出重试循环
else:
print(f"下载失败({resp.status_code}): {rel_path},第{retry+1}次尝试")
retry += 1
except Exception as e:
print(f"下载异常: {rel_path},第{retry+1}次尝试,错误: {e}")
retry += 1
else:
fail.append(rel_path)
self.progressChanged.emit(int((idx + 1) / total * 100))
self.finished.emit(success, fail)
class PartLibraryInterface(BaseInterface):
SERVER_URL = "http://154.37.215.220:5000"
SECRET_KEY = "MRobot_Download"
LOCAL_LIB_DIR = "mech_lib"
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("partLibraryInterface")
layout = QVBoxLayout(self)
layout.setSpacing(16)
layout.addWidget(SubtitleLabel("零件库在线bate版"))
layout.addWidget(HorizontalSeparator())
layout.addWidget(BodyLabel("可浏览服务器零件库,选择需要的文件下载到本地。(如无法使用或者下载失败,请尝试重新下载或检查网络连接)"))
btn_layout = QHBoxLayout()
refresh_btn = PushButton(FluentIcon.SYNC, "刷新列表")
refresh_btn.clicked.connect(self.refresh_list)
btn_layout.addWidget(refresh_btn)
# 新增:打开本地零件库按钮
open_local_btn = PushButton(FluentIcon.FOLDER, "打开本地零件库")
open_local_btn.clicked.connect(self.open_local_lib)
btn_layout.addWidget(open_local_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.tree = TreeWidget(self)
self.tree.setHeaderLabels(["名称", "类型"])
self.tree.setSelectionMode(self.tree.ExtendedSelection)
self.tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
self.tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
self.tree.setCheckedColor("#0078d4", "#2d7d9a")
self.tree.setBorderRadius(8)
self.tree.setBorderVisible(True)
layout.addWidget(self.tree, stretch=1)
download_btn = PushButton(FluentIcon.DOWNLOAD, "下载选中文件")
download_btn.clicked.connect(self.download_selected_files)
layout.addWidget(download_btn)
self.refresh_list(first=True)
def refresh_list(self, first=False):
self.tree.clear()
try:
resp = requests.get(
f"{self.SERVER_URL}/list",
params={"key": self.SECRET_KEY},
timeout=5
)
resp.raise_for_status()
tree = resp.json()
self.populate_tree(self.tree, tree, "")
if not first:
InfoBar.success(
title="刷新成功",
content="零件库已经是最新的!",
parent=self,
position=InfoBarPosition.TOP,
duration=2000
)
except Exception as e:
InfoBar.error(
title="刷新失败",
content=f"获取零件库失败: {e}",
parent=self,
position=InfoBarPosition.TOP,
duration=3000
)
def populate_tree(self, parent, node, path_prefix):
from PyQt5.QtWidgets import QTreeWidgetItem
for dname, dnode in node.get("dirs", {}).items():
item = QTreeWidgetItem([dname, "文件夹"])
if isinstance(parent, TreeWidget):
parent.addTopLevelItem(item)
else:
parent.addChild(item)
self.populate_tree(item, dnode, os.path.join(path_prefix, dname))
for fname in node.get("files", []):
item = QTreeWidgetItem([fname, "文件"])
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(0, Qt.Unchecked)
item.setData(0, Qt.UserRole, os.path.join(path_prefix, fname))
if isinstance(parent, TreeWidget):
parent.addTopLevelItem(item)
else:
parent.addChild(item)
def get_checked_files(self):
files = []
def _traverse(item):
for i in range(item.childCount()):
child = item.child(i)
if child.text(1) == "文件" and child.checkState(0) == Qt.Checked:
files.append(child.data(0, Qt.UserRole))
_traverse(child)
root = self.tree.invisibleRootItem()
for i in range(root.childCount()):
_traverse(root.child(i))
return files
def download_selected_files(self):
files = self.get_checked_files()
if not files:
InfoBar.info(
title="提示",
content="请先勾选要下载的文件。",
parent=self,
position=InfoBarPosition.TOP,
duration=2000
)
return
# 进度条对话框
self.progress_dialog = Dialog(
title="正在下载",
content="正在下载选中文件,请稍候...",
parent=self
)
self.progress_bar = ProgressBar()
self.progress_bar.setValue(0)
# 插入进度条到内容布局
self.progress_dialog.textLayout.addWidget(self.progress_bar)
self.progress_dialog.show()
# 启动下载线程
self.download_thread = DownloadThread(
files, self.SERVER_URL, self.SECRET_KEY, self.LOCAL_LIB_DIR
)
self.download_thread.progressChanged.connect(self.progress_bar.setValue)
self.download_thread.finished.connect(self.on_download_finished)
self.download_thread.finished.connect(self.download_thread.deleteLater)
self.download_thread.start()
def on_download_finished(self, success, fail):
self.progress_dialog.close()
msg = f"成功下载: {len(success)} 个文件\n失败: {len(fail)} 个文件"
dialog = Dialog(
title="下载结果",
content=msg,
parent=self
)
# 添加“打开文件夹”按钮
open_btn = PushButton("打开文件夹")
def open_folder():
folder = os.path.abspath(self.LOCAL_LIB_DIR)
# 打开文件夹macOS用openWindows用explorerLinux用xdg-open
import platform, subprocess
if platform.system() == "Darwin":
subprocess.call(["open", folder])
elif platform.system() == "Windows":
subprocess.call(["explorer", folder])
else:
subprocess.call(["xdg-open", folder])
dialog.close()
open_btn.clicked.connect(open_folder)
# 添加按钮到Dialog布局
dialog.textLayout.addWidget(open_btn)
dialog.exec()
def open_local_lib(self):
folder = os.path.abspath(self.LOCAL_LIB_DIR)
import platform, subprocess
if platform.system() == "Darwin":
subprocess.call(["open", folder])
elif platform.system() == "Windows":
subprocess.call(["explorer", folder])
else:
subprocess.call(["xdg-open", folder])
# ===================== 设置界面 =====================
class SettingInterface(BaseInterface):
themeSwitchRequested = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("settingInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# 标题
layout.addSpacing(10)
layout.addWidget(SubtitleLabel("设置中心"))
layout.addSpacing(10)
layout.addWidget(HorizontalSeparator())
# 主题切换区域
theme_title = StrongBodyLabel("外观设置")
theme_desc = BodyLabel("切换夜间/白天模式,适应不同环境。")
theme_desc.setWordWrap(True)
layout.addSpacing(10)
layout.addWidget(theme_title)
layout.addWidget(theme_desc)
theme_box = QHBoxLayout()
self.theme_label = BodyLabel("夜间模式")
self.theme_switch = SwitchButton()
self.theme_switch.setChecked(Theme.DARK == Theme.DARK)
self.theme_switch.checkedChanged.connect(self.on_theme_switch)
theme_box.addWidget(self.theme_label)
theme_box.addWidget(self.theme_switch)
theme_box.addStretch()
layout.addLayout(theme_box)
layout.addSpacing(15)
layout.addWidget(HorizontalSeparator())
# 其它设置区域(示例)
other_title = StrongBodyLabel("其它设置")
other_desc = BodyLabel("更多功能正在开发中,敬请期待。")
other_desc.setWordWrap(True)
layout.addSpacing(10)
layout.addWidget(other_title)
layout.addWidget(other_desc)
# 版权信息
layout.addStretch()
copyright_label = BodyLabel("© 2025 MRobot Toolbox")
copyright_label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
layout.addWidget(copyright_label)
layout.addSpacing(10)
def on_theme_switch(self, checked):
self.themeSwitchRequested.emit()
# ===================== 帮助与关于界面 =====================
class HelpInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("helpInterface")
layout = QVBoxLayout()
self.setLayout(layout)
class AboutInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("aboutInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 主窗口与导航 =====================
class MainWindow(FluentWindow):
themeChanged = pyqtSignal(Theme)
def __init__(self):
super().__init__()
self.setWindowTitle("MR_ToolBox")
self.resize(1000, 700)
self.setMinimumSize(800, 600)
# 记录当前主题
self.current_theme = Theme.DARK
# 创建页面实例
self.setting_page = SettingInterface(self)
self.setting_page.themeSwitchRequested.connect(self.toggle_theme)
self.page_registry = [
(HomeInterface(self), FIF.HOME, "首页", NavigationItemPosition.TOP),
(DataInterface(self), FIF.LIBRARY, "MRobot代码生成", NavigationItemPosition.SCROLL),
(SerialTerminalInterface(self), FIF.COMMAND_PROMPT, "Mini_Shell", NavigationItemPosition.SCROLL),
(PartLibraryInterface(self), FIF.DOWNLOAD, "零件库", NavigationItemPosition.SCROLL), # ← 加上这一行
(self.setting_page, FIF.SETTING, "设置", NavigationItemPosition.BOTTOM),
(HelpInterface(self), FIF.HELP, "帮助", NavigationItemPosition.BOTTOM),
(AboutInterface(self), FIF.INFO, "关于", NavigationItemPosition.BOTTOM),
]
self.initNavigation()
def initNavigation(self):
for page, icon, name, position in self.page_registry:
self.addSubInterface(page, icon, name, position)
self.navigationInterface.addSeparator()
avatar = NavigationAvatarWidget('用户', ':/qfluentwidgets/images/avatar.png')
self.navigationInterface.addWidget(
routeKey='avatar',
widget=avatar,
onClick=self.show_user_info, # 这里改为 self.show_user_info
position=NavigationItemPosition.BOTTOM
)
def toggle_theme(self):
# 切换主题
if self.current_theme == Theme.DARK:
self.current_theme = Theme.LIGHT
else:
self.current_theme = Theme.DARK
setTheme(self.current_theme)
# 同步设置界面按钮状态
self.setting_page.theme_switch.setChecked(self.current_theme == Theme.DARK)
def show_user_info(self):
dialog = Dialog(
title="用户信息",
content="用户MRobot至尊VIP用户",
parent=self
)
dialog.exec()
# ===================== 程序入口 =====================
def main():
app = QApplication(sys.argv)
setTheme(Theme.DARK)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

View File

@ -1,719 +0,0 @@
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import sys
import os
import threading
import shutil
import re
from git import Repo
from collections import defaultdict
import csv
import xml.etree.ElementTree as ET
# 配置常量
REPO_DIR = "MRobot_repo"
REPO_URL = "http://gitea.qutrobot.top/robofish/MRobot.git"
if getattr(sys, 'frozen', False): # 检查是否为打包后的环境
CURRENT_DIR = os.path.dirname(sys.executable) # 使用可执行文件所在目录
else:
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) # 使用脚本所在目录
MDK_ARM_DIR = os.path.join(CURRENT_DIR, "MDK-ARM")
USER_DIR = os.path.join(CURRENT_DIR, "User")
class MRobotApp:
def __init__(self):
self.ioc_data = None
self.add_gitignore_var = None # 延迟初始化
self.header_file_vars = {}
self.task_vars = [] # 用于存储任务的变量
# 初始化
def initialize(self):
print("初始化中,正在克隆仓库...")
self.clone_repo()
self.ioc_data = self.find_and_read_ioc_file()
print("初始化完成,启动主窗口...")
self.show_main_window()
# 克隆仓库
def clone_repo(self):
try:
if os.path.exists(REPO_DIR):
shutil.rmtree(REPO_DIR)
print(f"正在克隆仓库到 {REPO_DIR}(仅克隆当前文件内容)...")
Repo.clone_from(REPO_URL, REPO_DIR, multi_options=["--depth=1"])
print("仓库克隆成功!")
except Exception as e:
print(f"克隆仓库时出错: {e}")
# 删除克隆的仓库
def delete_repo(self):
try:
if os.path.exists(REPO_DIR):
shutil.rmtree(REPO_DIR)
print(f"已删除克隆的仓库目录: {REPO_DIR}")
except Exception as e:
print(f"删除仓库目录时出错: {e}")
# 复制文件
def copy_file_from_repo(self, src_path, dest_path):
try:
# 修复路径拼接问题,确保 src_path 不重复包含 REPO_DIR
if src_path.startswith(REPO_DIR):
full_src_path = src_path
else:
full_src_path = os.path.join(REPO_DIR, src_path.lstrip(os.sep))
# 检查源文件是否存在
if not os.path.exists(full_src_path):
print(f"文件 {full_src_path} 不存在!(检查路径或仓库内容)")
return
# 检查目标路径是否有效
if not dest_path or not dest_path.strip():
print("目标路径为空或无效,无法复制文件!")
return
# 创建目标目录(如果不存在)
dest_dir = os.path.dirname(dest_path)
if dest_dir and not os.path.exists(dest_dir):
os.makedirs(dest_dir, exist_ok=True)
# 执行文件复制
shutil.copy(full_src_path, dest_path)
print(f"文件已从 {full_src_path} 复制到 {dest_path}")
except Exception as e:
print(f"复制文件时出错: {e}")
# 查找并读取 .ioc 文件
def find_and_read_ioc_file(self):
try:
for file in os.listdir("."):
if file.endswith(".ioc"):
print(f"找到 .ioc 文件: {file}")
with open(file, "r", encoding="utf-8") as f:
return f.read()
print("未找到 .ioc 文件!")
except Exception as e:
print(f"读取 .ioc 文件时出错: {e}")
return None
# 检查是否启用了 FreeRTOS
def check_freertos_enabled(self, ioc_data):
try:
return bool(re.search(r"Mcu\.IP\d+=FREERTOS", ioc_data))
except Exception as e:
print(f"检查 FreeRTOS 配置时出错: {e}")
return False
# 生成操作
def generate_action(self):
def task():
# 检查并创建目录
self.create_directories()
if self.add_gitignore_var.get():
self.copy_file_from_repo(".gitignore", ".gitignore")
if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
self.copy_file_from_repo("src/freertos.c", os.path.join("Core", "Src", "freertos.c"))
# 定义需要处理的文件夹
folders = ["bsp", "component", "device", "module"]
# 遍历每个文件夹,复制选中的 .h 和 .c 文件
for folder in folders:
folder_dir = os.path.join(REPO_DIR, "User", folder)
if not os.path.exists(folder_dir):
continue # 如果文件夹不存在,跳过
for file_name in os.listdir(folder_dir):
file_base, file_ext = os.path.splitext(file_name)
if file_ext not in [".h", ".c"]:
continue # 只处理 .h 和 .c 文件
# 强制复制与文件夹同名的文件
if file_base == folder:
src_path = os.path.join(folder_dir, file_name)
dest_path = os.path.join("User", folder, file_name)
self.copy_file_from_repo(src_path, dest_path)
continue # 跳过后续检查,直接复制
# 检查是否选中了对应的文件
if file_base in self.header_file_vars and self.header_file_vars[file_base].get():
src_path = os.path.join(folder_dir, file_name)
dest_path = os.path.join("User", folder, file_name)
self.copy_file_from_repo(src_path, dest_path)
threading.Thread(target=task).start()
# 创建必要的目录
def create_directories(self):
try:
directories = [
"User/bsp",
"User/component",
"User/device",
"User/module",
]
# 根据是否启用 FreeRTOS 决定是否创建 User/task
if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
directories.append("User/task")
for directory in directories:
if not os.path.exists(directory):
os.makedirs(directory, exist_ok=True)
print(f"已创建目录: {directory}")
else:
print(f"目录已存在: {directory}")
except Exception as e:
print(f"创建目录时出错: {e}")
# 更新 FreeRTOS 状态标签
def update_freertos_status(self, label):
if self.ioc_data:
status = "已启用" if self.check_freertos_enabled(self.ioc_data) else "未启用"
else:
status = "未检测到 .ioc 文件"
label.config(text=f"FreeRTOS 状态: {status}")
# 显示主窗口
# ...existing code...
# ...existing code...
# 显示主窗口
def show_main_window(self):
root = tk.Tk()
root.title("MRobot 自动生成脚本")
root.geometry("1000x650") # 调整窗口大小以适应布局
# 在窗口关闭时调用 on_closing 方法
root.protocol("WM_DELETE_WINDOW", lambda: self.on_closing(root))
# 初始化 BooleanVar
self.add_gitignore_var = tk.BooleanVar(value=False)
self.auto_configure_var = tk.BooleanVar(value=False) # 新增复选框变量
# 创建主框架
main_frame = ttk.Frame(root)
main_frame.pack(fill="both", expand=True)
# 添加标题
title_label = ttk.Label(main_frame, text="MRobot 自动生成脚本", font=("Arial", 16, "bold"))
title_label.pack(pady=10)
# 添加 FreeRTOS 状态标签
freertos_status_label = ttk.Label(main_frame, text="FreeRTOS 状态: 检测中...", font=("Arial", 12))
freertos_status_label.pack(pady=10)
self.update_freertos_status(freertos_status_label)
# 模块文件选择和任务管理框架(添加滚动功能)
module_task_frame = ttk.Frame(main_frame)
module_task_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 创建 Canvas 和 Scrollbar
canvas = tk.Canvas(module_task_frame)
scrollbar = ttk.Scrollbar(module_task_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
# 配置滚动区域
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# 绑定鼠标滚轮事件
def on_mouse_wheel(event):
canvas.yview_scroll(-1 * int(event.delta / 120), "units")
canvas.bind_all("<MouseWheel>", on_mouse_wheel)
# 布局 Canvas 和 Scrollbar
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 左右布局:模块文件选择框和任务管理框
left_frame = ttk.Frame(scrollable_frame)
left_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5)
right_frame = ttk.Frame(scrollable_frame)
right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
# 模块文件选择框
header_files_frame = ttk.LabelFrame(left_frame, text="模块文件选择", padding=(10, 10))
header_files_frame.pack(fill="both", expand=True, padx=5)
self.header_files_frame = header_files_frame
self.update_header_files()
# 任务管理框
if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
task_frame = ttk.LabelFrame(right_frame, text="任务管理", padding=(10, 10))
task_frame.pack(fill="both", expand=True, padx=5)
self.task_frame = task_frame
self.update_task_ui()
# 添加消息框和生成按钮在同一行
bottom_frame = ttk.Frame(main_frame)
bottom_frame.pack(fill="x", pady=10, side="bottom")
# 消息框
self.message_box = tk.Text(bottom_frame, wrap="word", state="disabled", height=5, width=60)
self.message_box.pack(side="left", fill="x", expand=True, padx=5, pady=5)
# 生成按钮和复选框选项
button_frame = ttk.Frame(bottom_frame)
button_frame.pack(side="right", padx=10)
# 添加复选框容器(横向排列复选框)
checkbox_frame = ttk.Frame(button_frame)
checkbox_frame.pack(side="top", pady=5)
# 添加 .gitignore 复选框(左侧)
ttk.Checkbutton(checkbox_frame, text=".gitignore", variable=self.add_gitignore_var).pack(side="left", padx=5)
# 添加自动配置环境复选框(右侧)
ttk.Checkbutton(checkbox_frame, text="自动环境", variable=self.auto_configure_var).pack(side="left", padx=5)
# 添加生成按钮(竖向排列在复选框下方)
generate_button = ttk.Button(button_frame, text="一键生成MRobot代码", command=self.generate_action)
generate_button.pack(side="top", pady=10)
generate_button.config(width=25) # 设置按钮宽度
# 重定向输出到消息框
self.redirect_output()
# 打印欢迎信息
print("欢迎使用 MRobot 自动生成脚本!")
print("请根据需要选择模块文件和任务。")
print("点击“一键生成MRobot代码”按钮开始生成。")
# 启动 Tkinter 主事件循环
root.mainloop()
# ...existing code...
# ...existing code...
def redirect_output(self):
"""
重定向标准输出到消息框
"""
class TextRedirector:
def __init__(self, text_widget):
self.text_widget = text_widget
def write(self, message):
self.text_widget.config(state="normal")
self.text_widget.insert("end", message)
self.text_widget.see("end")
self.text_widget.config(state="disabled")
def flush(self):
pass
sys.stdout = TextRedirector(self.message_box)
sys.stderr = TextRedirector(self.message_box)
# 修改 update_task_ui 方法
def update_task_ui(self):
# 检查是否有已存在的任务文件
task_dir = os.path.join("User", "task")
if os.path.exists(task_dir):
for file_name in os.listdir(task_dir):
file_base, file_ext = os.path.splitext(file_name)
if file_ext == ".c" and file_base not in ["init", "user_task"] and file_base not in [task_var.get() for task_var, _ in self.task_vars]:
frequency = 100 # 默认频率
user_task_header_path = os.path.join("User", "task", "user_task.h")
if os.path.exists(user_task_header_path):
try:
with open(user_task_header_path, "r", encoding="utf-8") as f:
content = f.read()
pattern = rf"#define\s+TASK_FREQ_{file_base.upper()}\s*\((\d+)[uU]?\)"
match = re.search(pattern, content)
if match:
frequency = int(match.group(1))
print(f"从 user_task.h 文件中读取到任务 {file_base} 的频率: {frequency}")
except Exception as e:
print(f"读取 user_task.h 文件时出错: {e}")
new_task_var = tk.StringVar(value=file_base)
self.task_vars.append((new_task_var, tk.IntVar(value=frequency)))
# 清空任务框架中的所有子组件
for widget in self.task_frame.winfo_children():
widget.destroy()
# 设置任务管理框的固定宽度
self.task_frame.config(width=400)
# 显示任务列表
for i, (task_var, freq_var) in enumerate(self.task_vars):
task_row = ttk.Frame(self.task_frame, width=400)
task_row.pack(fill="x", pady=5)
ttk.Entry(task_row, textvariable=task_var, width=20).pack(side="left", padx=5)
ttk.Label(task_row, text="频率:").pack(side="left", padx=5)
ttk.Spinbox(task_row, from_=1, to=1000, textvariable=freq_var, width=5).pack(side="left", padx=5)
ttk.Button(task_row, text="删除", command=lambda idx=i: self.remove_task(idx)).pack(side="left", padx=5)
# 添加新任务按钮
add_task_button = ttk.Button(self.task_frame, text="添加任务", command=self.add_task)
add_task_button.pack(pady=10)
# 修改 add_task 方法
def add_task(self):
new_task_var = tk.StringVar(value=f"Task_{len(self.task_vars) + 1}")
new_freq_var = tk.IntVar(value=100) # 默认频率为 100
self.task_vars.append((new_task_var, new_freq_var))
self.update_task_ui()
# 修改 remove_task 方法
def remove_task(self, idx):
del self.task_vars[idx]
self.update_task_ui()
# 更新文件夹显示
def update_folder_display(self):
for widget in self.folder_frame.winfo_children():
widget.destroy()
folders = ["User/bsp", "User/component", "User/device", "User/module"]
# if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
# folders.append("User/task")
for folder in folders:
# 去掉 "User/" 前缀
display_name = folder.replace("User/", "")
tk.Label(self.folder_frame, text=display_name).pack()
# 更新 .h 文件复选框
def update_header_files(self):
for widget in self.header_files_frame.winfo_children():
widget.destroy()
folders = ["bsp", "component", "device", "module"]
dependencies = defaultdict(list)
for folder in folders:
folder_dir = os.path.join(REPO_DIR, "User", folder)
if os.path.exists(folder_dir):
dependencies_file = os.path.join(folder_dir, "dependencies.csv")
if os.path.exists(dependencies_file):
with open(dependencies_file, "r", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
if len(row) == 2:
dependencies[row[0]].append(row[1])
# 创建复选框
for folder in folders:
folder_dir = os.path.join(REPO_DIR, "User", folder)
if os.path.exists(folder_dir):
module_frame = ttk.LabelFrame(self.header_files_frame, text=folder.capitalize(), padding=(10, 10))
module_frame.pack(fill="x", pady=5)
row, col = 0, 0
for file in os.listdir(folder_dir):
file_base, file_ext = os.path.splitext(file)
if file_ext == ".h" and file_base != folder:
var = tk.BooleanVar(value=False)
self.header_file_vars[file_base] = var
checkbox = ttk.Checkbutton(
module_frame,
text=file_base,
variable=var,
command=lambda fb=file_base: self.handle_dependencies(fb, dependencies)
)
checkbox.grid(row=row, column=col, padx=5, pady=5, sticky="w")
col += 1
if col >= 6:
col = 0
row += 1
def handle_dependencies(self, file_base, dependencies):
"""
根据依赖关系自动勾选相关模块
"""
if file_base in self.header_file_vars and self.header_file_vars[file_base].get():
# 如果当前模块被选中,自动勾选其依赖项
for dependency in dependencies.get(file_base, []):
dep_base = os.path.basename(dependency)
if dep_base in self.header_file_vars:
self.header_file_vars[dep_base].set(True)
# 在 MRobotApp 类中添加以下方法
def generate_task_files(self):
try:
template_file_path = os.path.join(REPO_DIR, "User", "task", "task.c.template")
task_dir = os.path.join("User", "task")
if not os.path.exists(template_file_path):
print(f"模板文件 {template_file_path} 不存在,无法生成 task.c 文件!")
return
os.makedirs(task_dir, exist_ok=True)
with open(template_file_path, "r", encoding="utf-8") as f:
template_content = f.read()
# 为每个任务生成对应的 task.c 文件
for task_var, _ in self.task_vars: # 解包元组
task_name = f"Task_{task_var.get()}" # 添加前缀 Task_
task_file_path = os.path.join(task_dir, f"{task_var.get().lower()}.c") # 文件名保持原始小写
# 替换模板中的占位符
task_content = template_content.replace("{{task_name}}", task_name)
task_content = task_content.replace("{{task_function}}", task_name)
task_content = task_content.replace(
"{{task_frequency}}", f"TASK_FREQ_{task_var.get().upper()}"
) # 替换为 user_task.h 中的宏定义
task_content = task_content.replace("{{task_delay}}", f"TASK_INIT_DELAY_{task_var.get().upper()}")
with open(task_file_path, "w", encoding="utf-8") as f:
f.write(task_content)
print(f"已成功生成 {task_file_path} 文件!")
except Exception as e:
print(f"生成 task.c 文件时出错: {e}")
# 修改 user_task.c 文件
def modify_user_task_file(self):
try:
template_file_path = os.path.join(REPO_DIR, "User", "task", "user_task.c.template")
generated_task_file_path = os.path.join("User", "task", "user_task.c")
if not os.path.exists(template_file_path):
print(f"模板文件 {template_file_path} 不存在,无法生成 user_task.c 文件!")
return
os.makedirs(os.path.dirname(generated_task_file_path), exist_ok=True)
with open(template_file_path, "r", encoding="utf-8") as f:
template_content = f.read()
# 生成任务属性定义
task_attr_definitions = "\n".join([
f"""const osThreadAttr_t attr_{task_var.get().lower()} = {{
.name = "{task_var.get()}",
.priority = osPriorityNormal,
.stack_size = 128 * 4,
}};"""
for task_var, _ in self.task_vars # 解包元组
])
# 替换模板中的占位符
task_content = template_content.replace("{{task_attr_definitions}}", task_attr_definitions)
with open(generated_task_file_path, "w", encoding="utf-8") as f:
f.write(task_content)
print(f"已成功生成 {generated_task_file_path} 文件!")
except Exception as e:
print(f"修改 user_task.c 文件时出错: {e}")
# ...existing code...
def generate_user_task_header(self):
try:
template_file_path = os.path.join(REPO_DIR, "User", "task", "user_task.h.template")
header_file_path = os.path.join("User", "task", "user_task.h")
if not os.path.exists(template_file_path):
print(f"模板文件 {template_file_path} 不存在,无法生成 user_task.h 文件!")
return
os.makedirs(os.path.dirname(header_file_path), exist_ok=True)
# 如果 user_task.h 已存在,提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
existing_msgq_content = ""
if os.path.exists(header_file_path):
with open(header_file_path, "r", encoding="utf-8") as f:
content = f.read()
# 提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
match = re.search(r"/\* USER MESSAGE BEGIN \*/\s*(.*?)\s*/\* USER MESSAGE END \*/", content, re.DOTALL)
if match:
existing_msgq_content = match.group(1).strip()
print("已存在的 msgq 区域内容:")
print(existing_msgq_content)
with open(template_file_path, "r", encoding="utf-8") as f:
template_content = f.read()
# 定义占位符内容
thread_definitions = "\n".join([f" osThreadId_t {task_var.get().lower()};" for task_var, _ in self.task_vars])
msgq_definitions = existing_msgq_content if existing_msgq_content else " osMessageQueueId_t default_msgq;"
freq_definitions = "\n".join([f" float {task_var.get().lower()};" for task_var, _ in self.task_vars])
last_up_time_definitions = "\n".join([f" uint32_t {task_var.get().lower()};" for task_var, _ in self.task_vars])
task_attr_declarations = "\n".join([f"extern const osThreadAttr_t attr_{task_var.get().lower()};" for task_var, _ in self.task_vars])
task_function_declarations = "\n".join([f"void Task_{task_var.get()}(void *argument);" for task_var, _ in self.task_vars])
task_frequency_definitions = "\n".join([
f"#define TASK_FREQ_{task_var.get().upper()} ({freq_var.get()}u)"
for task_var, freq_var in self.task_vars
])
task_init_delay_definitions = "\n".join([f"#define TASK_INIT_DELAY_{task_var.get().upper()} (0u)" for task_var, _ in self.task_vars])
task_handle_definitions = "\n".join([f" osThreadId_t {task_var.get().lower()};" for task_var, _ in self.task_vars])
# 替换模板中的占位符
header_content = template_content.replace("{{thread_definitions}}", thread_definitions)
header_content = header_content.replace("{{msgq_definitions}}", msgq_definitions)
header_content = header_content.replace("{{freq_definitions}}", freq_definitions)
header_content = header_content.replace("{{last_up_time_definitions}}", last_up_time_definitions)
header_content = header_content.replace("{{task_attr_declarations}}", task_attr_declarations)
header_content = header_content.replace("{{task_function_declarations}}", task_function_declarations)
header_content = header_content.replace("{{task_frequency_definitions}}", task_frequency_definitions)
header_content = header_content.replace("{{task_init_delay_definitions}}", task_init_delay_definitions)
header_content = header_content.replace("{{task_handle_definitions}}", task_handle_definitions)
# 如果存在 /* USER MESSAGE BEGIN */ 区域内容,则保留
if existing_msgq_content:
header_content = re.sub(
r"/\* USER MESSAGE BEGIN \*/\s*.*?\s*/\* USER MESSAGE END \*/",
f"/* USER MESSAGE BEGIN */\n\n {existing_msgq_content}\n\n /* USER MESSAGE END */",
header_content,
flags=re.DOTALL
)
with open(header_file_path, "w", encoding="utf-8") as f:
f.write(header_content)
print(f"已成功生成 {header_file_path} 文件!")
except Exception as e:
print(f"生成 user_task.h 文件时出错: {e}")
def generate_init_file(self):
try:
template_file_path = os.path.join(REPO_DIR, "User", "task", "init.c.template")
generated_file_path = os.path.join("User", "task", "init.c")
if not os.path.exists(template_file_path):
print(f"模板文件 {template_file_path} 不存在,无法生成 init.c 文件!")
return
os.makedirs(os.path.dirname(generated_file_path), exist_ok=True)
# 如果 init.c 已存在,提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
existing_msgq_content = ""
if os.path.exists(generated_file_path):
with open(generated_file_path, "r", encoding="utf-8") as f:
content = f.read()
# 提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
match = re.search(r"/\* USER MESSAGE BEGIN \*/\s*(.*?)\s*/\* USER MESSAGE END \*/", content, re.DOTALL)
if match:
existing_msgq_content = match.group(1).strip()
print("已存在的消息队列区域内容:")
print(existing_msgq_content)
with open(template_file_path, "r", encoding="utf-8") as f:
template_content = f.read()
# 生成任务创建代码
thread_creation_code = "\n".join([
f" task_runtime.thread.{task_var.get().lower()} = osThreadNew(Task_{task_var.get()}, NULL, &attr_{task_var.get().lower()});"
for task_var, _ in self.task_vars # 解包元组
])
# 替换模板中的占位符
init_content = template_content.replace("{{thread_creation_code}}", thread_creation_code)
# 如果存在 /* USER MESSAGE BEGIN */ 区域内容,则保留
if existing_msgq_content:
init_content = re.sub(
r"/\* USER MESSAGE BEGIN \*/\s*.*?\s*/\* USER MESSAGE END \*/",
f"/* USER MESSAGE BEGIN */\n {existing_msgq_content}\n /* USER MESSAGE END */",
init_content,
flags=re.DOTALL
)
with open(generated_file_path, "w", encoding="utf-8") as f:
f.write(init_content)
print(f"已成功生成 {generated_file_path} 文件!")
except Exception as e:
print(f"生成 init.c 文件时出错: {e}")
# 修改 generate_action 方法
def generate_action(self):
def task():
# 检查并创建目录(与 FreeRTOS 状态无关的模块始终创建)
self.create_directories()
# 复制 .gitignore 文件
if self.add_gitignore_var.get():
self.copy_file_from_repo(".gitignore", ".gitignore")
# 如果启用了 FreeRTOS复制相关文件
if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
self.copy_file_from_repo("src/freertos.c", os.path.join("Core", "Src", "freertos.c"))
# 定义需要处理的文件夹(与 FreeRTOS 状态无关)
folders = ["bsp", "component", "device", "module"]
# 遍历每个文件夹,复制选中的 .h 和 .c 文件
for folder in folders:
folder_dir = os.path.join(REPO_DIR, "User", folder)
if not os.path.exists(folder_dir):
continue # 如果文件夹不存在,跳过
for file_name in os.listdir(folder_dir):
file_base, file_ext = os.path.splitext(file_name)
if file_ext not in [".h", ".c"]:
continue # 只处理 .h 和 .c 文件
# 强制复制与文件夹同名的文件
if file_base == folder:
src_path = os.path.join(folder_dir, file_name)
dest_path = os.path.join("User", folder, file_name)
self.copy_file_from_repo(src_path, dest_path)
print(f"强制复制与文件夹同名的文件: {file_name}")
continue # 跳过后续检查,直接复制
# 检查是否选中了对应的文件
if file_base in self.header_file_vars and self.header_file_vars[file_base].get():
src_path = os.path.join(folder_dir, file_name)
dest_path = os.path.join("User", folder, file_name)
self.copy_file_from_repo(src_path, dest_path)
# 如果启用了 FreeRTOS执行任务相关的生成逻辑
if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
# 修改 user_task.c 文件
self.modify_user_task_file()
# 生成 user_task.h 文件
self.generate_user_task_header()
# 生成 init.c 文件
self.generate_init_file()
# 生成 task.c 文件
self.generate_task_files()
# # 自动配置环境
# if self.auto_configure_var.get():
# self.auto_configure_environment()
threading.Thread(target=task).start()
# 程序关闭时清理
def on_closing(self, root):
self.delete_repo()
root.destroy()
# 程序入口
if __name__ == "__main__":
app = MRobotApp()
app.initialize()

View File

@ -1,93 +0,0 @@
# MRobot
更加高效快捷的 STM32 开发架构,诞生于 Robocon 和 Robomaster但绝不仅限于此。
<div align="center">
<img src="./img/MRobot.png" height="100" alt="MRobot Logo">
<p>是时候使用更简洁的方式开发单片机了</p>
<p>
<!-- <img src="https://img.shields.io/github/license/xrobot-org/XRobot.svg" alt="License">
<img src="https://img.shields.io/github/repo-size/xrobot-org/XRobot.svg" alt="Repo Size">
<img src="https://img.shields.io/github/last-commit/xrobot-org/XRobot.svg" alt="Last Commit">
<img src="https://img.shields.io/badge/language-c/c++-F34B7D.svg" alt="Language"> -->
</p>
</div>
---
## 引言
提起嵌入式开发绝大多数人对每次繁琐的配置以及查阅各种文档来写东西感到非常枯燥和浪费使时间对于小形形目创建优雅的架构又比较费事那么我们哟u没有办法快速完成基础环境的搭建后直接开始写逻辑代码呢
这就是**MRobot**。
---
## 获取源代码
(此处可补充获取代码的具体方法)
---
## 主要特色
(此处可补充项目的主要特色)
---
## 组成
<div align="center">
<img src="./image/嵌入式程序层次图.png" alt="嵌入式程序层次图">
</div>
- `src/bsp`
- `src/component`
- `src/device`
- `src/module`
- `src/task`
---
## 应用案例
> **Robomaster**
- 全向轮步兵
- 英雄
- 哨兵
---
## 机器人展示
`以上机器人均使用 MRobot 搭建`
---
## 硬件支持
(此处可补充支持的硬件列表)
---
## 图片展示
## 相关依赖
(此处可补充项目依赖的具体内容)
---
## 构建 exe
使用以下命令构建可执行文件:
```bash
pyinstaller --onefile --windowed
pyinstaller MR_Toolbox.py --onefile --noconsole --icon=img\M.ico --add-data "mr_tool_img\MRobot.png;mr_tool_img"
pyinstaller MR_Tool.py --onefile --noconsole --icon=img\M.ico --add-data "mr_tool_img\MRobot.png;mr_tool_img" --add-data "src;src" --add-data "User;User"

View File

@ -1,168 +0,0 @@
import sys
import webbrowser
import serial
import serial.tools.list_ports
from PyQt5.QtCore import Qt, QSize, pyqtSignal
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QApplication, QLabel, QGroupBox, QGridLayout, QFrame,
QHBoxLayout, QComboBox, QTextEdit, QLineEdit
)
from qfluentwidgets import (
NavigationInterface, NavigationItemPosition, MessageBox,
setTheme, Theme, FluentWindow, NavigationAvatarWidget,
InfoBar, InfoBarPosition, PushButton, FluentIcon
)
from qfluentwidgets import FluentIcon as FIF
# ===================== 页面基类 =====================
class BaseInterface(QWidget):
"""所有页面的基类,页面内容完全自定义"""
def __init__(self, parent=None):
super().__init__(parent=parent)
# ===================== 首页界面 =====================
class HomeInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("homeInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 代码生成页面 =====================
class DataInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("dataInterface")
# 空页面示例
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 串口终端界面 =====================
class SerialTerminalInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("serialTerminalInterface")
layout = QVBoxLayout()
# ===================== 设置界面 =====================
class SettingInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("settingInterface")
layout = QVBoxLayout()
self.themeBtn = PushButton(
"切换夜间", self, FluentIcon.BRUSH
)
self.themeBtn.setFixedWidth(120)
self.themeBtn.clicked.connect(self.onThemeBtnClicked)
layout.addWidget(self.themeBtn)
layout.addStretch(1)
self.setLayout(layout)
# 监听主题变化
mw = self.window()
if hasattr(mw, "themeChanged"):
mw.themeChanged.connect(self.updateThemeBtn)
def onThemeBtnClicked(self):
mw = self.window()
if hasattr(mw, "toggleTheme"):
mw.toggleTheme()
def updateThemeBtn(self, theme):
if theme == Theme.LIGHT:
self.themeBtn.setText("切换夜间")
else:
self.themeBtn.setText("切换白天")
# ===================== 帮助与关于界面 =====================
class HelpInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("helpInterface")
layout = QVBoxLayout()
self.setLayout(layout)
class AboutInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("aboutInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 主窗口与导航 =====================
class MainWindow(FluentWindow):
themeChanged = pyqtSignal(Theme)
def __init__(self):
super().__init__()
self.setWindowTitle("MR_ToolBox")
self.resize(1000, 700)
self.setMinimumSize(800, 600)
setTheme(Theme.LIGHT)
self.theme = Theme.LIGHT
self.page_registry = [
(HomeInterface(self), FIF.HOME, "首页", NavigationItemPosition.TOP),
(DataInterface(self), FIF.LIBRARY, "MRobot代码生成", NavigationItemPosition.SCROLL),
(SerialTerminalInterface(self), FIF.COMMAND_PROMPT, "串口终端", NavigationItemPosition.SCROLL),
(SettingInterface(self), FIF.SETTING, "设置", NavigationItemPosition.BOTTOM),
(HelpInterface(self), FIF.HELP, "帮助", NavigationItemPosition.BOTTOM),
(AboutInterface(self), FIF.INFO, "关于", NavigationItemPosition.BOTTOM),
]
self.initNavigation()
# 把切换主题按钮放到标题栏右侧
self.themeBtn = PushButton("切换夜间", self, FluentIcon.BRUSH)
self.themeBtn.setFixedWidth(120)
self.themeBtn.clicked.connect(self.toggleTheme)
self.addTitleBarWidget(self.themeBtn, align=Qt.AlignRight)
def initNavigation(self):
for page, icon, name, position in self.page_registry:
self.addSubInterface(page, icon, name, position)
self.navigationInterface.addSeparator()
avatar = NavigationAvatarWidget('用户', ':/qfluentwidgets/images/avatar.png')
self.navigationInterface.addWidget(
routeKey='avatar',
widget=avatar,
onClick=self.showUserInfo,
position=NavigationItemPosition.BOTTOM
)
def toggleTheme(self):
if self.theme == Theme.LIGHT:
setTheme(Theme.DARK)
self.theme = Theme.DARK
self.themeBtn.setText("切换白天")
else:
setTheme(Theme.LIGHT)
self.theme = Theme.LIGHT
self.themeBtn.setText("切换夜间")
self.themeChanged.emit(self.theme)
self.refreshStyle()
def refreshStyle(self):
def refresh(widget):
widget.setStyleSheet(widget.styleSheet())
for child in widget.findChildren(QWidget):
refresh(child)
refresh(self)
def showUserInfo(self):
MessageBox("用户信息", "当前登录用户:管理员", self).exec()
# ===================== 程序入口 =====================
def main():
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

BIN
img/.DS_Store vendored

Binary file not shown.

BIN
img/M.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
img/M.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

View File

@ -1,42 +0,0 @@
from PIL import Image
import os
def crop_transparent_background(input_path, output_path):
"""
裁切 PNG 图片的透明背景并保存
:param input_path: 输入图片路径
:param output_path: 输出图片路径
"""
try:
# 打开图片
img = Image.open(input_path)
# 确保图片是 RGBA 模式
if img.mode != "RGBA":
img = img.convert("RGBA")
# 获取图片的 alpha 通道
bbox = img.getbbox()
if bbox:
# 裁切图片
cropped_img = img.crop(bbox)
# 保存裁切后的图片
cropped_img.save(output_path, format="PNG")
print(f"图片已保存到: {output_path}")
else:
print("图片没有透明背景或为空。")
except Exception as e:
print(f"处理图片时出错: {e}")
if __name__ == "__main__":
# 示例:输入和输出路径
input_file = "C:\Mac\Home\Desktop\MRobot\img\M.png" # 替换为你的输入图片路径
output_file = "C:\Mac\Home\Desktop\MRobot\img\M.png" # 替换为你的输出图片路径
# 检查文件是否存在
if os.path.exists(input_file):
crop_transparent_background(input_file, output_file)
else:
print(f"输入文件不存在: {input_file}")

View File

@ -1,286 +0,0 @@
import sys
import numpy as np
import pandas as pd
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox,
QLabel, QTableWidget, QTableWidgetItem, QFileDialog, QTextEdit,
QComboBox, QMessageBox, QHeaderView
)
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt
import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class PolyFitApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("MRobot 多项式拟合工具")
self.resize(1440, 1280)
self.setFont(QFont("微软雅黑", 11))
self.center()
self.data_x = []
self.data_y = []
self.last_coeffs = None
self.last_xmin = None
self.last_xmax = None
# 主布局
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(20)
left_layout = QVBoxLayout()
left_layout.setSpacing(12)
right_layout = QVBoxLayout()
right_layout.setSpacing(12)
main_layout.addLayout(left_layout, 0)
main_layout.addLayout(right_layout, 1)
# 数据输入区
self.table = QTableWidget(0, 2)
self.table.setFont(QFont("Consolas", 11))
self.table.setHorizontalHeaderLabels(["x", "y"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionBehavior(QTableWidget.SelectRows)
left_layout.addWidget(self.table)
btn_row = QHBoxLayout()
self.add_row_btn = QPushButton("添加数据")
self.add_row_btn.setStyleSheet("color: #333;")
self.add_row_btn.clicked.connect(self.add_point_row)
btn_row.addWidget(self.add_row_btn)
self.del_row_btn = QPushButton("删除选中行")
self.del_row_btn.setStyleSheet("color: #333;")
self.del_row_btn.clicked.connect(self.delete_selected_rows)
btn_row.addWidget(self.del_row_btn)
left_layout.addLayout(btn_row)
# 导入导出按钮区
file_btn_row = QHBoxLayout()
self.import_btn = QPushButton("导入Excel文件")
self.import_btn.setStyleSheet("font-weight: bold; color: #333;")
self.import_btn.clicked.connect(self.load_excel)
file_btn_row.addWidget(self.import_btn)
self.export_btn = QPushButton("导出Excel文件")
self.export_btn.setStyleSheet("font-weight: bold; color: #333;")
self.export_btn.clicked.connect(self.export_excel_and_plot)
file_btn_row.addWidget(self.export_btn)
left_layout.addLayout(file_btn_row)
# 拟合参数区
param_layout = QHBoxLayout()
param_layout.addWidget(QLabel("多项式阶数:"))
self.order_spin = QSpinBox()
self.order_spin.setRange(1, 10)
self.order_spin.setValue(2)
param_layout.addWidget(self.order_spin)
left_layout.addLayout(param_layout)
self.fit_btn = QPushButton("拟合并显示")
self.fit_btn.setStyleSheet("font-weight: bold; color: #333;")
self.fit_btn.clicked.connect(self.fit_and_plot)
left_layout.addWidget(self.fit_btn)
# 输出区
self.output = QTextEdit()
self.output.setReadOnly(False)
self.output.setFont(QFont("Consolas", 10))
self.output.setMaximumHeight(150)
left_layout.addWidget(self.output)
code_layout = QHBoxLayout()
code_layout.addWidget(QLabel("输出代码格式:"))
self.code_type = QComboBox()
self.code_type.addItems(["C", "C++", "Python"])
code_layout.addWidget(self.code_type)
self.gen_code_btn = QPushButton("生成函数代码")
self.gen_code_btn.setStyleSheet("color: #333;")
self.gen_code_btn.clicked.connect(self.generate_code)
code_layout.addWidget(self.gen_code_btn)
left_layout.addLayout(code_layout)
# 拟合曲线区
self.figure = Figure(figsize=(5, 4))
self.canvas = FigureCanvas(self.figure)
right_layout.addWidget(self.canvas)
def center(self):
qr = self.frameGeometry()
cp = QApplication.desktop().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def add_point_row(self, x_val="", y_val=""):
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(str(x_val)))
self.table.setItem(row, 1, QTableWidgetItem(str(y_val)))
def delete_selected_rows(self):
selected = self.table.selectionModel().selectedRows()
for idx in sorted(selected, reverse=True):
self.table.removeRow(idx.row())
def load_excel(self):
file, _ = QFileDialog.getOpenFileName(self, "选择Excel文件", "", "Excel Files (*.xlsx *.xls)")
if file:
try:
data = pd.read_excel(file, usecols=[0, 1])
new_x = data.iloc[:, 0].values.tolist()
new_y = data.iloc[:, 1].values.tolist()
for x, y in zip(new_x, new_y):
self.add_point_row(x, y)
QMessageBox.information(self, "成功", "数据导入成功!")
except Exception as e:
QMessageBox.critical(self, "错误", f"读取Excel失败: {e}")
def export_excel_and_plot(self):
file, _ = QFileDialog.getSaveFileName(self, "导出Excel文件", "", "Excel Files (*.xlsx *.xls)")
if file:
x_list, y_list = [], []
for row in range(self.table.rowCount()):
try:
x = float(self.table.item(row, 0).text())
y = float(self.table.item(row, 1).text())
x_list.append(x)
y_list.append(y)
except Exception:
continue
if not x_list or not y_list:
QMessageBox.warning(self, "导出失败", "没有可导出的数据!")
return
df = pd.DataFrame({'x': x_list, 'y': y_list})
try:
df.to_excel(file, index=False)
# 导出同名png图像
png_file = file
if png_file.lower().endswith('.xlsx') or png_file.lower().endswith('.xls'):
png_file = png_file.rsplit('.', 1)[0] + '.png'
else:
png_file = png_file + '.png'
self.figure.savefig(png_file, dpi=150, bbox_inches='tight')
QMessageBox.information(self, "导出成功", f"数据已成功导出到Excel文件\n图像已导出为:{png_file}")
except Exception as e:
QMessageBox.critical(self, "导出错误", f"导出Excel或图像失败: {e}")
def get_manual_points(self):
x_list, y_list = [], []
for row in range(self.table.rowCount()):
try:
x = float(self.table.item(row, 0).text())
y = float(self.table.item(row, 1).text())
x_list.append(x)
y_list.append(y)
except Exception:
continue
return x_list, y_list
def fit_and_plot(self):
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
matplotlib.rcParams['axes.unicode_minus'] = False
matplotlib.rcParams['font.size'] = 14
self.data_x, self.data_y = self.get_manual_points()
try:
order = int(self.order_spin.value())
except ValueError:
QMessageBox.warning(self, "输入错误", "阶数必须为整数!")
return
n_points = len(self.data_x)
if n_points < order + 1:
QMessageBox.warning(self, "数据不足", "数据点数量不足以拟合该阶多项式!")
return
x = np.array(self.data_x, dtype=np.float64)
y = np.array(self.data_y, dtype=np.float64)
x_min, x_max = x.min(), x.max()
if x_max - x_min == 0:
QMessageBox.warning(self, "数据错误", "所有x值都相同无法拟合")
return
try:
coeffs = np.polyfit(x, y, order)
except Exception as e:
QMessageBox.critical(self, "拟合错误", f"多项式拟合失败:{e}")
return
poly = np.poly1d(coeffs)
expr = "y = " + " + ".join([f"{c:.6g}*x^{order-i}" for i, c in enumerate(coeffs)])
self.output.setPlainText(f"{expr}\n")
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.scatter(x, y, color='red', label='数据点')
x_fit = np.linspace(x_min, x_max, 200)
y_fit = poly(x_fit)
ax.plot(x_fit, y_fit, label='拟合曲线')
ax.legend()
self.canvas.draw()
self.last_coeffs = coeffs
self.last_xmin = x_min
self.last_xmax = x_max
def generate_code(self):
if self.last_coeffs is None:
QMessageBox.warning(self, "未拟合", "请先拟合数据!")
return
coeffs = self.last_coeffs
code_type = self.code_type.currentText()
if code_type == "C":
code = self.create_c_function(coeffs)
elif code_type == "C++":
code = self.create_cpp_function(coeffs)
else:
code = self.create_py_function(coeffs)
self.output.setPlainText(code)
def create_c_function(self, coeffs):
lines = ["#include <math.h>", "double polynomial(double x) {", " return "]
n = len(coeffs)
terms = []
for i, c in enumerate(coeffs):
exp = n - i - 1
if exp == 0:
terms.append(f"{c:.8g}")
elif exp == 1:
terms.append(f"{c:.8g}*x")
else:
terms.append(f"{c:.8g}*pow(x,{exp})")
lines[-1] += " + ".join(terms) + ";"
lines.append("}")
return "\n".join(lines)
def create_cpp_function(self, coeffs):
lines = ["#include <cmath>", "double polynomial(double x) {", " return "]
n = len(coeffs)
terms = []
for i, c in enumerate(coeffs):
exp = n - i - 1
if exp == 0:
terms.append(f"{c:.8g}")
elif exp == 1:
terms.append(f"{c:.8g}*x")
else:
terms.append(f"{c:.8g}*pow(x,{exp})")
lines[-1] += " + ".join(terms) + ";"
lines.append("}")
return "\n".join(lines)
def create_py_function(self, coeffs):
n = len(coeffs)
lines = ["def polynomial(x):", " return "]
terms = []
for i, c in enumerate(coeffs):
exp = n - i - 1
if exp == 0:
terms.append(f"{c:.8g}")
elif exp == 1:
terms.append(f"{c:.8g}*x")
else:
terms.append(f"{c:.8g}*x**{exp}")
lines[-1] += " + ".join(terms)
return "\n".join(lines)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = PolyFitApp()
win.show()
sys.exit(app.exec_())

View File

@ -1,131 +0,0 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : freertos.c
* Description : Code for freertos applications
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "task/user_task.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
osThreadId_t initTaskHandle; // 定义 Task_Init 的任务句柄
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
initTaskHandle = osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
// for(;;)
// {
// osDelay(1);
// }
osThreadTerminate(osThreadGetId()); // 结束自身
/* USER CODE END StartDefaultTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */

View File

@ -1,105 +0,0 @@
# 嵌入式 代码
## 软件功能介绍
中心思想:
- 利用好RTOS和中断释放CPU性能保证实时性。
- 一个项目适配不同型号的机器人和不同的操作手。
减少维护的工作量,减少出错的可能性。
## 依赖&环境
- Windows平台下用CubeMX生成项目然后用Keil uvesrion进行编辑、烧写和调试。
## 使用说明
- 环境安装
- [MDK-ARM](https://www.keil.com/) (必备)
- [STM32CubeMX](https://www.st.com/zh/development-tools/stm32cubemx.html) (可选)
- 针对不同板子需要到不同的CubeMX工程文件DevA.ioc、DevC.ioc
- 可选利用CubeMX生成对应的外设初始化代码和Keil工程文件。忽略CAN总线相关错误。
- 每次生成代码后请利用Git丢弃Middlewares文件夹中的所有改变。原因如下。
1. 使用了AC6与CubeMX默认不匹配会影响到FreeRTOS的移植。
2. 使用了比CubeMX更新的FreeRTOS版本降版本会导致部分代码无法编译。
- 因为已经生成过Keil工程文件所以只会覆盖以前生成的代码而不会影响手写的代码。
- 每次生成代码后请在HAL_InitTick函数中添加uwTickPrio = TickPriority;
- 打开MDK-ARM中的DevC.uvprojx即可进行编辑、烧写或调试。
- Keil工程中有两个Target其中Debug用来调试不包含编译器优化等DevC/DevA用来编译输出最终固件。
## 文件目录结构&文件用途说明
| 文件夹 | 来源 | 内容 |
| ---- | ---- | ---- |
| Core | CubeMX | 包含核心代码,外设初始化,系统初始化等 |
| Doc | 开发者 | 文档 |
| Drivers | CubeMX | CMSIS相关库、STM32 HAL |
| Image | 开发者 | 图片 |
| MDK-ARM | CubeMX | Keil uversion 项目相关文件 |
| Middlewares | 开发者 / CubeMX | 中间件 |
| USB_DEVICE | CubeMX | USB相关文件 |
| User | 开发者 | 手动编写的代码 |
| Utils | 开发者 | 使用到的工具如CubeMonitor, Matlab |
| User内 | 内容 |
| ---- | ---- |
| bsp | 文件夹内包含开发板信息基于STM32 HAL对板载的外设进行控制|
| component | 包含各种组件,自成一体,相互依赖,但不依赖于其他文件夹|
| device | 独立于开发板的设备依赖于HAL和bsp|
| module | 对机器人各模块的抽象,各模块一起组成机器人|
| task | 独立的任务module的运行容器也包含通信、姿态解算等 |
## 系统介绍
### 硬件系统框图
| ![步兵嵌入式硬件框图](./Image/步兵嵌入式硬件框图.png?raw=true "步兵嵌入式硬件框图") |
|:--:|
| *步兵嵌入式硬件框图* |
### 软件流程图
| ![步兵嵌入式硬件框图](./Image/嵌入式程序流程图.png?raw=true "步兵嵌入式硬件框图") |
|:--:|
| *步兵嵌入式硬件框图* |
| ![嵌入式程序结构图](./Image/嵌入式程序结构图.png?raw=true "嵌入式程序结构图") |
|:--:|
| *嵌入式程序结构图* |
## 原理介绍
### 云台控制原理
| ![云台控制原理与PX类似](./Image/云台控制原理.png?raw=true "嵌入式程序结构图") |
|:--:|
| *云台控制原理与PX类似* |
### 其他参考文献
- 软件架构参考[PX4 Architectural Overview](https://dev.px4.io/master/en/concept/architecture.html)
- 云台控制参考[PX4 Controller Diagrams](https://dev.px4.io/master/en/flight_stack/controller_diagrams.html)
- 底盘Mixer和CAN的Control Group参考[PX4 Mixing and Actuators](https://dev.px4.io/master/en/concept/mixing.html)
## TODO
- 给BSP USB print加保护允许不同进程的使用。
- 给所有BSP加保护
- device.c里面加上一个Device_Init()在里面初始化所有mutex
- CAN设备代码优化。消息解析发送方向。
- CAN设备动态初始化保存好几组配置。
## Roadmap
1. 在步兵上完成所有功能。