Compare commits

...

3 Commits

Author SHA1 Message Date
f2fedac360 修复task.c 2025-06-20 01:13:04 +08:00
34a0874156 各种修复 2025-06-20 00:54:36 +08:00
e9eb169547 添加了可选运行频率方案 2025-06-20 00:30:21 +08:00
8 changed files with 66 additions and 30 deletions

BIN
.DS_Store vendored

Binary file not shown.

3
.gitignore vendored
View File

@ -31,4 +31,5 @@ Examples/
build/ build/
dist/ dist/
*.spec *.spec
*.exe

View File

@ -1,6 +1,6 @@
[Setup] [Setup]
AppName=MRobot AppName=MRobot
AppVersion=1.0 AppVersion=1.0.1
DefaultDirName={userappdata}\MRobot DefaultDirName={userappdata}\MRobot
DefaultGroupName=MRobot DefaultGroupName=MRobot
OutputDir=. OutputDir=.

View File

@ -52,7 +52,7 @@ from qfluentwidgets import (
from urllib.parse import quote from urllib.parse import quote
from packaging.version import parse as vparse from packaging.version import parse as vparse
__version__ = "1.0.0" __version__ = "1.0.1"
# ===================== 页面基类 ===================== # ===================== 页面基类 =====================
class BaseInterface(QWidget): class BaseInterface(QWidget):
@ -665,7 +665,7 @@ class DataInterface(BaseInterface):
# 2. 在 /* USER CODE BEGIN RTOS_THREADS */ 区域添加 osThreadNew(Task_Init, NULL, &attr_init); # 2. 在 /* USER CODE BEGIN RTOS_THREADS */ 区域添加 osThreadNew(Task_Init, NULL, &attr_init);
rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)' rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)'
match = re.search(rtos_threads_pattern, code, re.DOTALL) match = re.search(rtos_threads_pattern, code, re.DOTALL)
task_line = ' initTaskHandle = osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n' task_line = ' osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n'
if match: if match:
threads_code = match.group(2) threads_code = match.group(2)
if 'Task_Init' not in threads_code: if 'Task_Init' not in threads_code:
@ -712,7 +712,7 @@ class DataInterface(BaseInterface):
) )
def open_task_config_dialog(self): def open_task_config_dialog(self):
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSpinBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSpinBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox
import yaml import yaml
import os import os
@ -720,15 +720,16 @@ class DataInterface(BaseInterface):
def __init__(self, parent=None, config_path=None): def __init__(self, parent=None, config_path=None):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("任务配置") self.setWindowTitle("任务配置")
self.resize(800, 420) self.resize(900, 420)
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.table = QTableWidget(0, 5) self.table = QTableWidget(0, 6)
self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述"]) self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述", "频率控制"])
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch) self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch) self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents)
self.table.setColumnWidth(4, 320) # 任务描述更宽 self.table.setColumnWidth(4, 320) # 任务描述更宽
layout.addWidget(self.table) layout.addWidget(self.table)
btn_layout = QHBoxLayout() btn_layout = QHBoxLayout()
@ -748,6 +749,8 @@ class DataInterface(BaseInterface):
cancel_btn.clicked.connect(self.reject) cancel_btn.clicked.connect(self.reject)
# 自动读取配置文件 # 自动读取配置文件
if config_path and os.path.exists(config_path): if config_path and os.path.exists(config_path):
try: try:
@ -761,6 +764,10 @@ class DataInterface(BaseInterface):
item = QTableWidgetItem(str(t.get(key, ""))) item = QTableWidgetItem(str(t.get(key, "")))
item.setTextAlignment(Qt.AlignCenter) item.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, col, item) self.table.setItem(row, col, item)
# 新增频率控制复选框
freq_ctrl = QCheckBox()
freq_ctrl.setChecked(t.get("freq_control", True))
self.table.setCellWidget(row, 5, freq_ctrl)
except Exception as e: except Exception as e:
pass # 配置文件损坏时忽略 pass # 配置文件损坏时忽略
@ -774,6 +781,9 @@ class DataInterface(BaseInterface):
item = QTableWidgetItem(val) item = QTableWidgetItem(val)
item.setTextAlignment(Qt.AlignCenter) item.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, col, item) self.table.setItem(row, col, item)
freq_ctrl = QCheckBox()
freq_ctrl.setChecked(True)
self.table.setCellWidget(row, 5, freq_ctrl)
def del_row(self): def del_row(self):
rows = set([i.row() for i in self.table.selectedItems()]) rows = set([i.row() for i in self.table.selectedItems()])
@ -784,23 +794,31 @@ class DataInterface(BaseInterface):
tasks = [] tasks = []
for row in range(self.table.rowCount()): for row in range(self.table.rowCount()):
name = self.table.item(row, 0).text().strip() name = self.table.item(row, 0).text().strip()
freq = int(self.table.item(row, 1).text()) freq = self.table.item(row, 1).text()
delay = int(self.table.item(row, 2).text()) delay = int(self.table.item(row, 2).text())
stack = int(self.table.item(row, 3).text()) stack = int(self.table.item(row, 3).text())
desc = self.table.item(row, 4).text().strip() desc = self.table.item(row, 4).text().strip()
freq_ctrl = self.table.cellWidget(row, 5).isChecked()
# 校验 stack 必须为 128*2^n # 校验 stack 必须为 128*2^n
if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0: if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0:
raise ValueError(f"{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等128*2^n") raise ValueError(f"{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等128*2^n")
tasks.append({ task = {
"name": name, "name": name,
"function": f"Task_{name}", "function": f"Task_{name}",
"frequency": freq,
"delay": delay, "delay": delay,
"stack": stack, "stack": stack,
"description": desc "description": desc,
}) "freq_control": freq_ctrl
}
if freq_ctrl:
task["frequency"] = int(freq)
tasks.append(task)
return tasks return tasks
config_path = os.path.join(self.project_path, "User", "task", "config.yaml") config_path = os.path.join(self.project_path, "User", "task", "config.yaml")
dlg = TaskConfigDialog(self, config_path=config_path) dlg = TaskConfigDialog(self, config_path=config_path)
if dlg.exec() == QDialog.Accepted: if dlg.exec() == QDialog.Accepted:
@ -856,19 +874,21 @@ class DataInterface(BaseInterface):
init_c_tpl = os.path.join(template_dir, "init.c.template") init_c_tpl = os.path.join(template_dir, "init.c.template")
task_c_tpl = os.path.join(template_dir, "task.c.template") task_c_tpl = os.path.join(template_dir, "task.c.template")
# 只统计需要频率控制的任务
freq_tasks = [t for t in task_list if t.get("freq_control", True)]
def render_template(path, context): def render_template(path, context):
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
tpl = Template(f.read()) tpl = Template(f.read())
return tpl.render(**context) return tpl.render(**context)
# 构造模板上下文 # 构造模板上下文
context_h = { context_h = {
"thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]), "thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]),
"freq_definitions": "\n".join([f" float {t['name']};" for t in task_list]), "freq_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
"stack_definitions": "\n".join([f" UBaseType_t {t['name']};" for t in task_list]), "stack_definitions": "\n".join([f" UBaseType_t {t['name']};" for t in task_list]),
"last_up_time_definitions": "\n".join([f" float {t['name']};" for t in task_list]), "last_up_time_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
"task_frequency_definitions": "\n".join([f"#define {t['name'].upper()}_FREQ ({t['frequency']})" for t in task_list]), "task_frequency_definitions": "\n".join([f"#define {t['name'].upper()}_FREQ ({t['frequency']})" for t in freq_tasks]),
"task_init_delay_definitions": "\n".join([f"#define {t['name'].upper()}_INIT_DELAY ({t['delay']})" for t in task_list]), "task_init_delay_definitions": "\n".join([f"#define {t['name'].upper()}_INIT_DELAY ({t['delay']})" for t in task_list]),
"task_attr_declarations": "\n".join([f"extern const osThreadAttr_t attr_{t['name']};" for t in task_list]), "task_attr_declarations": "\n".join([f"extern const osThreadAttr_t attr_{t['name']};" for t in task_list]),
"task_function_declarations": "\n".join([f"void {t['function']}(void *argument);" for t in task_list]), "task_function_declarations": "\n".join([f"void {t['function']}(void *argument);" for t in task_list]),
@ -969,17 +989,16 @@ class DataInterface(BaseInterface):
f.write(init_c) f.write(init_c)
# ----------- 生成 task.c ----------- # ----------- 生成 task.c -----------
task_c_tpl = os.path.join(template_dir, "task.c.template")
for t in task_list: for t in task_list:
# 自动换行任务描述
desc = t.get("description", "") desc = t.get("description", "")
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20)) desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
context_task = { context_task = {
"task_name": t["name"], "task_name": t["name"],
"task_function": t["function"], "task_function": t["function"],
"task_frequency": f"{t['name'].upper()}_FREQ", # 使用宏定义 "task_frequency": f"{t['name'].upper()}_FREQ" if t.get("freq_control", True) else None,
"task_delay": f"{t['name'].upper()}_INIT_DELAY", # 使用宏定义 "task_delay": f"{t['name'].upper()}_INIT_DELAY",
"task_description": desc_wrapped "task_description": desc_wrapped,
"freq_control": t.get("freq_control", True)
} }
# 渲染模板 # 渲染模板
with open(task_c_tpl, encoding="utf-8") as f: with open(task_c_tpl, encoding="utf-8") as f:
@ -990,7 +1009,6 @@ class DataInterface(BaseInterface):
if os.path.exists(task_c_path): if os.path.exists(task_c_path):
with open(task_c_path, "r", encoding="utf-8") as f: with open(task_c_path, "r", encoding="utf-8") as f:
old_code = f.read() old_code = f.read()
# 只保留USER区域
def preserve_user_region(new_code, old_code, region_name): def preserve_user_region(new_code, old_code, region_name):
pattern = re.compile( pattern = re.compile(
rf"/\*\s*{region_name}\s*BEGIN\s*\*/(.*?)/\*\s*{region_name}\s*END\s*\*/", rf"/\*\s*{region_name}\s*BEGIN\s*\*/(.*?)/\*\s*{region_name}\s*END\s*\*/",
@ -1007,11 +1025,11 @@ class DataInterface(BaseInterface):
code = preserve_user_region(code, old_code, region) code = preserve_user_region(code, old_code, region)
with open(task_c_path, "w", encoding="utf-8") as f: with open(task_c_path, "w", encoding="utf-8") as f:
f.write(code) f.write(code)
# ----------- 保存任务配置到 config.yaml -----------
config_path = os.path.join(output_dir, "config.yaml")
with open(config_path, "w", encoding="utf-8") as f:
yaml.dump(task_list, f, allow_unicode=True)
# ----------- 保存任务配置到 config.yaml -----------
config_yaml_path = os.path.join(output_dir, "config.yaml")
with open(config_yaml_path, "w", encoding="utf-8") as f:
yaml.safe_dump(task_list, f, allow_unicode=True)
# ===================== 串口终端界面 ===================== # ===================== 串口终端界面 =====================
class SerialReadThread(QThread): class SerialReadThread(QThread):
data_received = pyqtSignal(str) data_received = pyqtSignal(str)

View File

@ -40,4 +40,3 @@ void Task_Init(void *argument) {
osKernelUnlock(); // 解锁内核 osKernelUnlock(); // 解锁内核
osThreadTerminate(osThreadGetId()); // 任务完成后结束自身 osThreadTerminate(osThreadGetId()); // 任务完成后结束自身
} }

View File

@ -22,13 +22,16 @@
void {{task_function}}(void *argument) { void {{task_function}}(void *argument) {
(void)argument; /* 未使用argument消除警告 */ (void)argument; /* 未使用argument消除警告 */
{% if freq_control %}
/* 计算任务运行到指定频率需要等待的tick数 */ /* 计算任务运行到指定频率需要等待的tick数 */
const uint32_t delay_tick = osKernelGetTickFreq() / {{task_frequency}}; const uint32_t delay_tick = osKernelGetTickFreq() / {{task_frequency}};
osDelay({{task_delay}}); /* 延时一段时间再开启任务 */ osDelay({{task_delay}}); /* 延时一段时间再开启任务 */
/* USER CODE INIT BEGIN */
/* USER CODE INIT END */ /* USER CODE INIT BEGIN*/
/* USER CODE INIT END*/
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */ uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
while (1) { while (1) {
tick += delay_tick; /* 计算下一个唤醒时刻 */ tick += delay_tick; /* 计算下一个唤醒时刻 */
@ -37,4 +40,17 @@ void {{task_function}}(void *argument) {
/* USER CODE END */ /* USER CODE END */
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */ osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
} }
{% else %}
osDelay({{task_delay}}); /* 延时一段时间再开启任务 */
/* USER CODE INIT BEGIN*/
/* USER CODE INIT END*/
while (1) {
/* USER CODE BEGIN */
/* USER CODE END */
}
{% endif %}
} }

View File

@ -10,3 +10,4 @@ const osThreadAttr_t attr_init = {
/* User_task */ /* User_task */
{{task_attr_definitions}} {{task_attr_definitions}}

View File

@ -78,3 +78,4 @@ void Task_Init(void *argument);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif