Files
Robot_Arm/src/torque_balance.py
T

215 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""关节电机力矩与负载能力校核。
与旧版的区别(这些是旧版实际出错的地方):
1. 拓扑。旧版把关节表手抄成 "J3 肘俯仰 / J4 前臂自转 / J5 腕俯仰 / J6 夹爪",
并据此虚构了一个夹爪机构和 135mm 大臂——与 human_layout.py 的实际拓扑
(J3 大臂自转 / J4 肘俯仰 / J6 腕自转)完全不符,整份报告的结论都是错的。
本版不再复制拓扑:零件表、关节轴、运动学链全部复用 strength_check,
而 strength_check 的轴又直接来自 human_layout,单一事实源。
2. 力臂。旧版把每个关节的质量与力臂写成硬编码常数(J2 = 1.75kg @ 464mm),
几何一改就失效且不会报错。本版按每个零件的真实质心、在可达姿态网格上
搜索最大重力力矩。
3. 惯量。旧版用一组手填的 kg·m²。本版按 Σm·d²(对关节轴的平行轴距离)
在同一姿态网格上取最大值。零件自身转动惯量未计入,对大臂量级的关节
是小修正,已在报告中标注。
电机参数:仓库内没有原始数据手册,以下为工程假定,可用环境变量覆盖。
ARMT_CONT 连续额定输出力矩 (N·m)
ARMT_PEAK 峰值堵转输出力矩 (N·m)
ARMT_ALLOW 封闭塑料外壳热衰减后的许用连续力矩 (N·m)
ARMT_ALPHA 角加速度 (rad/s²)
ARMPAY 末端负载 (kg)
输出:docs/torque_balance.json + docs/torque_report.md
"""
import io
import json
import math
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
import human_layout as HL
from armcore import DOCS
import strength_check as SC
G = 9.81
T_CONT = float(os.environ.get("ARMT_CONT", "2.5"))
T_PEAK = float(os.environ.get("ARMT_PEAK", "8.0"))
T_ALLOW = float(os.environ.get("ARMT_ALLOW", "2.0"))
ALPHA = float(os.environ.get("ARMT_ALPHA", "5.0"))
PAYLOAD = float(os.environ.get("ARMPAY", "0.5"))
# 关节名与轴向,全部取自 human_layout(不手抄)
JOINT_CN = {t[0]: t[1] for t in HL.JOINTS}
JOINT_D = {t[0]: t[3] for t in HL.JOINTS}
def _axis_kind(tag):
d = JOINT_D[tag]
if abs(d[2]) == 1:
return "roll(绕Z)"
if abs(d[0]) == 1:
return "roll(绕X)"
return "pitch(绕Y)"
def analyze(payload_kg=PAYLOAD):
parts = SC.part_table(payload_kg)
jl = SC.joint_loads(parts)
jl0 = SC.joint_loads(SC.part_table(0.0)) # 去掉末端负载,用于反推负载能力
masses = {"J%d" % j: sum(m for _, m, _, g in parts if g >= j) for j in range(1, 7)}
arm_mass = {k: v - payload_kg * 1000.0 for k, v in masses.items()}
res = {"assumptions": {
"motor": "60mm 方形标准关节电机模组,2 级行星减速",
"T_cont_Nm": T_CONT, "T_peak_Nm": T_PEAK, "T_allow_Nm": T_ALLOW,
"alpha_rad_s2": ALPHA, "payload_kg": payload_kg,
"note": "电机参数为工程假定,仓库内无数据手册;结论对此敏感,见报告第 4 节",
}, "joints": {}}
for j in range(1, 7):
tag = "J%d" % j
r = jl[tag]
tau_g = r["tau"] / 1000.0 # N*m,重力
tau_dyn = r["I"] * ALPHA # N*m,惯性
tau_g_arm = jl0[tag]["tau"] / 1000.0 # 去掉负载后的重力力矩
req_static = tau_g
req_peak = tau_g + tau_dyn
res["joints"][tag] = {
"cn": JOINT_CN[tag], "axis": _axis_kind(tag),
"distal_mass_kg": round(masses[tag] / 1000.0, 3),
"arm_mass_kg": round(arm_mass[tag] / 1000.0, 3),
"weight_N": round(r["F"], 1),
"reach_tip_mm": round(r["Lp"], 0),
"tau_gravity_Nm": round(tau_g, 3),
"tau_gravity_arm_only_Nm": round(tau_g_arm, 3),
"I_distal_kgm2": round(r["I"], 5),
"tau_dynamic_Nm": round(tau_dyn, 3),
"tau_required_static_Nm": round(req_static, 3),
"tau_required_peak_Nm": round(req_peak, 3),
"duty_vs_Tcont_pct": round(100.0 * req_static / T_CONT, 1),
"duty_vs_Tallow_pct": round(100.0 * req_static / T_ALLOW, 1),
"peak_margin_x": round(T_PEAK / req_peak, 2) if req_peak > 1e-3 else None,
"status": "OK" if (req_static <= T_ALLOW and req_peak <= T_PEAK) else "OVERLOAD",
}
return res, jl0, jl
def max_payload(res):
"""在许用连续力矩下,末端还能带多少 kg(按各关节取最小)。
线性化:tau_g(p) ≈ tau_g_arm + p·g·L。用的是该关节实测的最大 TCP 力臂,
即最不利姿态下的力臂。
"""
out = {}
for tag, d in res["joints"].items():
L = d["reach_tip_mm"] / 1000.0
if L <= 0:
out[tag] = None
continue
head = T_ALLOW - d["tau_gravity_arm_only_Nm"]
out[tag] = round(max(head, 0.0) / (G * L), 3) if head > 0 else 0.0
return out
def generate_report(res, mp):
L = []
L.append("# 6 自由度机械臂 · 关节电机力矩与负载能力校核\n")
a = res["assumptions"]
L.append("## 1. 前提\n")
L.append("- 驱动:%s" % a["motor"])
L.append("- 连续额定输出力矩 `T_cont` = %.2f N·m;峰值堵转 `T_peak` = %.2f N·m"
% (a["T_cont_Nm"], a["T_peak_Nm"]))
L.append("- 封闭打印外壳热衰减后许用连续力矩 `T_allow` = %.2f N·m;末端负载 %.2f kg;"
"角加速度 %.1f rad/s²" % (a["T_allow_Nm"], a["payload_kg"], a["alpha_rad_s2"]))
L.append("- **%s**\n" % a["note"])
L.append("载荷模型:每个零件按网格体积加权质心定位,随姿态做刚体变换,在可达姿态"
"网格(俯仰轴取行程端点/中值,自转轴取 0/±60/±120/180°)上取重力力矩最大值。"
"力矩为绕**关节自身轴**的分量——这才是电机要出的力。\n")
L.append("## 2. 各关节校核结果\n")
L.append("| 关节 | 名称 | 轴 | 以下质量(kg) | TCP力臂(mm) | 重力力矩(N·m) | 惯性力矩(N·m) | 峰值需求(N·m) | 负荷率(对T_allow) | 峰值裕度 | 判定 |")
L.append("|:---:|:---|:---|---:|---:|---:|---:|---:|---:|---:|:---:|")
for tag, d in res["joints"].items():
L.append("| %s | %s | %s | %.2f | %.0f | **%.2f** | %.2f | %.2f | **%.0f%%** | %s | %s |"
% (tag, d["cn"], d["axis"], d["distal_mass_kg"], d["reach_tip_mm"],
d["tau_gravity_Nm"], d["tau_dynamic_Nm"], d["tau_required_peak_Nm"],
d["duty_vs_Tallow_pct"],
("%.2f×" % d["peak_margin_x"]) if d["peak_margin_x"] else "—", d["status"]))
L.append("")
L.append("## 3. 许用连续工况下的末端负载能力\n")
L.append("去掉末端负载后各关节仍需 `tau_gravity_arm_only` 的重力力矩,余额除以该关节"
"实测最大 TCP 力臂,即为还能挂的负载:\n")
L.append("| 关节 | 仅臂身重力力矩(N·m) | 余额(N·m) | 可挂负载(kg) |")
L.append("|:---:|---:|---:|---:|")
for tag, d in res["joints"].items():
head = T_ALLOW - d["tau_gravity_arm_only_Nm"]
L.append("| %s | %.2f | %+.2f | %s |"
% (tag, d["tau_gravity_arm_only_Nm"], head,
("%.2f" % mp[tag]) if mp[tag] is not None else "—"))
ok = [t for t, v in mp.items() if v is not None]
worst = min(ok, key=lambda t: mp[t]) if ok else None
if worst:
L.append("")
L.append("**整臂瓶颈:%s,可挂末端负载 %.2f kg。**\n" % (worst, mp[worst]))
L.append("## 4. 结论要点\n")
over = [t for t, d in res["joints"].items() if d["status"] == "OVERLOAD"]
if over:
L.append("- **超载关节:%s**。" % "、".join(over))
for t in over:
d = res["joints"][t]
L.append(" - %s(%s)重力 %.2f N·m,超 `T_allow` %.0f%%;即使按峰值堵转算,"
"裕度也只有 %s。" % (t, d["cn"], d["tau_gravity_Nm"],
d["duty_vs_Tallow_pct"] - 100.0,
("%.2f×" % d["peak_margin_x"]) if d["peak_margin_x"] else "—"))
L.append("- 结论对电机参数高度敏感:若实际连续额定高于假定值,"
"请用 `ARMT_CONT` / `ARMT_ALLOW` 覆盖后重跑本脚本。")
else:
L.append("- 全部关节在许用连续力矩内。")
L.append("- 惯量按 Σm·d² 估,**未计入零件自身转动惯量**,对肩/肘是大臂量级的关节"
"约低估一成,惯性力矩一栏应视为下限。")
L.append("- 本版不含被动弹簧平衡。旧版报告提出的 J2 拉簧预补偿(2.48 N·m)在几何上"
"**尚未实现**;若要保留该方案,需要在 P04/P05 之间增加拉簧连杆机构。")
return "\n".join(L)
def main():
res, jl0, jl = analyze(PAYLOAD)
mp = max_payload(res)
io.open(DOCS + "/torque_balance.json", "w", encoding="utf-8").write(
json.dumps(res, ensure_ascii=False, indent=2))
io.open(DOCS + "/torque_report.md", "w", encoding="utf-8").write(generate_report(res, mp))
print("末端负载 %.2f kg T_cont=%.2f T_allow=%.2f T_peak=%.2f alpha=%.1f\n"
% (PAYLOAD, T_CONT, T_ALLOW, T_PEAK, ALPHA))
print("%-4s %-8s %-12s %8s %8s %8s %9s %8s %s"
% ("关节", "名称", "轴", "重力N·m", "惯性N·m", "峰值N·m", "负荷率", "峰值裕度", "判定"))
for tag, d in res["joints"].items():
print("%-4s %-8s %-12s %8.2f %8.2f %8.2f %8.0f%% %8s %s"
% (tag, d["cn"], d["axis"], d["tau_gravity_Nm"], d["tau_dynamic_Nm"],
d["tau_required_peak_Nm"], d["duty_vs_Tallow_pct"],
("%.2fx" % d["peak_margin_x"]) if d["peak_margin_x"] else "-",
d["status"]))
print("\n许用连续力矩下可挂末端负载 (kg):")
for tag in res["joints"]:
print(" %-4s %s" % (tag, ("%.2f" % mp[tag]) if mp[tag] is not None else "—"))
ok = [t for t, v in mp.items() if v is not None]
if ok:
w = min(ok, key=lambda t: mp[t])
print(" 整臂瓶颈 %s -> %.2f kg" % (w, mp[w]))
print("\n已写出 docs/torque_balance.json + docs/torque_report.md")
sys.stdout.flush()
if __name__ == "__main__":
main()
os._exit(0)