diff --git a/.gitignore b/.gitignore index 83834db..89e2885 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,12 @@ __pycache__ *.csv *.typ *.pdf +*.py +*.png +*.csv +scripts -# Vélarsértækar skrár sem PlatformIO býr til sjálft +# Skrár sem PlatformIO býr til sjálft .vscode/c_cpp_properties.json .vscode/launch.json compile_commands.json diff --git a/include/motor_controller.h b/include/motor_controller.h index 0018626..841db5d 100644 --- a/include/motor_controller.h +++ b/include/motor_controller.h @@ -21,7 +21,7 @@ struct ControlSample class MotorController { private: - static constexpr double DEFAULT_REFERENCE_RPM = 40.0; + static constexpr double DEFAULT_REFERENCE_RPM = 25.0; static constexpr double PWM_PER_PERCENT = 255.0 / 100.0; static const uint8_t LED_PIN = 5; // PB5 = D13 = LED_BUILTIN diff --git a/scripts/plot_step_response.py b/scripts/plot_step_response.py index 1e3f085..d1c340c 100644 --- a/scripts/plot_step_response.py +++ b/scripts/plot_step_response.py @@ -13,7 +13,9 @@ Skriptan metur lika sveiflutima (Tu) og utslag ef kerfid sveiflast, sem nytist til ad finna Ku/Tu fyrir Ziegler-Nichols. Notkun: - python plot_step_response.py [inntak.csv] [uttak.png] [titill] + python plot_step_response.py [inntak.csv] [uttak.png] [titill] [--no-pwm] + +--no-pwm teiknar bara RPM (PWM-dalkurinn ma lika vanta i CSV-skranni) Sjalfgefid: data/step_response.csv -> data/step_response.png """ @@ -24,6 +26,10 @@ import matplotlib.pyplot as plt COLUMNS = ["time_ms", "ref_rpm", "actual_rpm", "pwm_actual"] +REQUIRED = ["time_ms", "ref_rpm", "actual_rpm"] + +# Onnur nofn sem eldri utgafur firmware-sins hafa notad fyrir sama dalk +ALIASES = {"pwm": "pwm_actual"} def empty_cols(): @@ -37,13 +43,17 @@ def load_csv(path): for row in csv.reader(f): if row and row[0].strip() == "time_ms": header = [name.strip() for name in row] + header = [ALIASES.get(name, name) for name in header] + missing = [key for key in REQUIRED if key not in header] + if missing: + sys.exit(f"Dalka vantar i haus {row}: {missing}") continue if header is None: # rusl a undan fyrsta haus (t.d. "State: Initialization") continue try: values = {key: float(row[header.index(key)]) - for key in COLUMNS} + for key in COLUMNS if key in header} except (ValueError, IndexError): # sleppir linum sem eru ekki gild data (t.d. "State: ...") continue @@ -52,7 +62,7 @@ def load_csv(path): if cols["time_ms"] and values["time_ms"] <= cols["time_ms"][-1]: cols = empty_cols() - for key in COLUMNS: + for key in values: cols[key].append(values[key]) return cols @@ -92,11 +102,12 @@ def estimate_oscillation(time_s, actual_rpm): return statistics.fmean(periods), amplitude -def plot(cols, out_path, title_suffix=""): +def plot(cols, out_path, title_suffix="", show_pwm=True): time_s = [t / 1000.0 for t in cols["time_ms"]] ref_rpm = cols["ref_rpm"] actual_rpm = cols["actual_rpm"] pwm_actual = cols["pwm_actual"] + show_pwm = show_pwm and len(pwm_actual) == len(time_s) fig, ax1 = plt.subplots(figsize=(10, 5)) @@ -108,12 +119,17 @@ def plot(cols, out_path, title_suffix=""): ax1.set_ylabel("Angular velocity [RPM]") ax1.grid(True, alpha=0.3) - ax2 = ax1.twinx() - ax2.plot(time_s, pwm_actual, label="PWM actual (clamped)", - color="#d62728", linewidth=1.4) - ax2.axhline(255, color="#d62728", linewidth=0.6, alpha=0.3) - ax2.axhline(-255, color="#d62728", linewidth=0.6, alpha=0.3) - ax2.set_ylabel("PWM [-255, 255]") + lines, labels = ax1.get_legend_handles_labels() + if show_pwm: + ax2 = ax1.twinx() + ax2.plot(time_s, pwm_actual, label="PWM actual (clamped)", + color="#d62728", linewidth=1.4) + ax2.axhline(255, color="#d62728", linewidth=0.6, alpha=0.3) + ax2.axhline(-255, color="#d62728", linewidth=0.6, alpha=0.3) + ax2.set_ylabel("PWM [-255, 255]") + lines2, labels2 = ax2.get_legend_handles_labels() + lines += lines2 + labels += labels2 # Merkjum stokkpunktinn (fyrsta stadin thar sem ref hoppar fra 0) step_idx = next((i for i, r in enumerate(ref_rpm) if r > 0), None) @@ -124,22 +140,22 @@ def plot(cols, out_path, title_suffix=""): fontsize=9, va="bottom") osc = estimate_oscillation(time_s, actual_rpm) - if osc is not None: - tu, amplitude = osc - print(f"Sveifla: Tu = {tu * 1000:.1f} ms, utslag = +-{amplitude:.2f} RPM") - ax1.text(0.01, 0.98, f"Tu ≈ {tu * 1000:.1f} ms\n" - f"amplitude ≈ ±{amplitude:.2f} RPM", - transform=ax1.transAxes, fontsize=9, va="top", - bbox=dict(facecolor="white", alpha=0.8, edgecolor="#cccccc")) + # if osc is not None: + # tu, amplitude = osc + # print(f"Sveifla: Tu = {tu * 1000:.1f} ms, utslag = +-{amplitude:.2f} RPM") + # ax1.text(0.99, 0.98, f"Tu ≈ {tu * 1000:.1f} ms\n" + # f"amplitude ≈ ±{amplitude:.2f} RPM", + # transform=ax1.transAxes, fontsize=9, va="top", ha="right", + # bbox=dict(facecolor="white", alpha=0.8, edgecolor="#cccccc")) + # else: + # print("Engin skyr sveifla fannst i seinni helmingi gagnanna") + + ax1.legend(lines, labels, loc="lower right", fontsize=8) + + if show_pwm: + title = "Step response: RPM and PWM over time" else: - print("Engin skyr sveifla fannst i seinni helmingi gagnanna") - - lines1, labels1 = ax1.get_legend_handles_labels() - lines2, labels2 = ax2.get_legend_handles_labels() - ax1.legend(lines1 + lines2, labels1 + labels2, loc="lower right", - fontsize=8) - - title = "Step response: RPM and PWM over time" + title = "Step response: RPM over time" if title_suffix: title += f" ({title_suffix})" fig.suptitle(title) @@ -149,10 +165,12 @@ def plot(cols, out_path, title_suffix=""): if __name__ == "__main__": - in_path = sys.argv[1] if len(sys.argv) > 1 else "data/step_response.csv" - out_path = sys.argv[2] if len(sys.argv) > 2 else "data/step_response.png" - title_suffix = sys.argv[3] if len(sys.argv) > 3 else "" + show_pwm = "--no-pwm" not in sys.argv + args = [a for a in sys.argv[1:] if a != "--no-pwm"] + in_path = args[0] if len(args) > 0 else "data/step_response.csv" + out_path = args[1] if len(args) > 1 else "data/step_response.png" + title_suffix = args[2] if len(args) > 2 else "" data = load_csv(in_path) if not data["time_ms"]: sys.exit(f"Engin gogn fundust i {in_path}") - plot(data, out_path, title_suffix) + plot(data, out_path, title_suffix, show_pwm) diff --git a/src/main.cpp b/src/main.cpp index 7d67ae3..7416535 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,9 +19,9 @@ RotaryEncoderDriver encoder; MotorDriver motor; -const double Kp = 45.0; // Ziegler-Nichols Method 45.0 -const double Ti = 0.0; // 0.226 -const double T = 0.0; // 0.004 +const double Kp = 10.125; // Ku * 0.45 +const double Ti = 0.0334; // Tu * 0.83 37 +const double T = 0.004; // 0.004 // P_controller controller(Kp); PI_controller controller(Kp, Ti, T); diff --git a/src/operational_state.cpp b/src/operational_state.cpp index dbeb5a7..f79ad27 100644 --- a/src/operational_state.cpp +++ b/src/operational_state.cpp @@ -15,7 +15,7 @@ void OperationalState::enter() // Tíminn byrjar á 0 í hvert skipti sem farið er inn í Operational sample_count = 0; - Serial.println("time_ms,ref_rpm,actual_rpm,pwm"); + Serial.println("time_ms,ref_rpm,actual_rpm"); } // Part 1 @@ -48,9 +48,9 @@ void OperationalState::update() Serial.print(","); Serial.print(sample.ref_rpm, 1); Serial.print(","); - Serial.print(sample.actual_rpm, 2); - Serial.print(","); - Serial.println(sample.pwm_actual, 1); + Serial.println(sample.actual_rpm, 2); + // Serial.print(","); + // Serial.println(sample.pwm_actual, 1); } }