Я пытаюсь создать ПИ-контроллер, используя Arduino и Python.Python

Программы на Python
Anonymous
 Я пытаюсь создать ПИ-контроллер, используя Arduino и Python.

Сообщение Anonymous »

Я пытаюсь создать ПИ-контроллер обратной связи на Python, который взаимодействует с Arduino через последовательный порт. Arduino настраивает вход ШИМ двигателя постоянного тока, чтобы поддерживать его работу на желаемой скорости вращения.
Проблема в том, что контроллер, похоже, работает не очень хорошо. Измеренное значение числа оборотов в минуту колеблется примерно на 40 об/мин, хотя моя цель — удержать его в пределах 5 % от заданного значения.
Я не уверен, какой уровень стабильности мне следует ожидать от этой настройки, и есть ли в моей настройке/коде PI фундаментальные проблемы.
Есть ли какие-нибудь советы о том, как я могу улучшить свой код или настройку, чтобы добиться лучшей стабилизации числа оборотов?
НАСТРОЙКА:
Python -> Arduino -> Motor Shield L293D для управления ШИМ -> BO двигатель постоянного тока 3-6 В с диском энкодера -> датчик скорости lm393 для обратной связи (я также использую внешний источник питания 5 В, подключенный к экрану для двигателя)
Это код Arduino:
#include

// --- Motor Settings ---
AF_DCMotor motor(4); // Motor connected to port M4

// --- Encoder Settings ---
const int encoderPin = 2;
const float PULSES_PER_REVOLUTION = 20.0;
volatile unsigned long pulseDuration = 0;
volatile unsigned long lastPulseTime = 0;

// --- Global Variables ---
float rawSpeedRPM = 0.0;
String serialCommand = ""; // Buffer for Serial commands

// Time interval for automatic RPM transmission
const unsigned long SERIAL_SEND_INTERVAL_US = 100000; // 100 ms
unsigned long lastRPMTransmission = 0;

// ISR to measure encoder pulses
void measurePulse() {
unsigned long currentTime = micros();
unsigned long calculatedDuration = currentTime - lastPulseTime;

if (calculatedDuration > 1000) {
pulseDuration = calculatedDuration;
lastPulseTime = currentTime;
}
}

void updateRawSpeed() {
unsigned long duration;
noInterrupts();
duration = pulseDuration;
interrupts();

float calculatedSpeed = 0;
if (duration > 0 && (micros() - lastPulseTime) < 1000000) {
calculatedSpeed = (1000000.0 / duration) * 60.0 / PULSES_PER_REVOLUTION;
}

rawSpeedRPM = calculatedSpeed;
}

void setup() {
Serial.begin(115200);
pinMode(encoderPin, INPUT_PULLUP);
motor.run(RELEASE);
motor.setSpeed(0);
attachInterrupt(digitalPinToInterrupt(encoderPin), measurePulse, RISING);
serialCommand.reserve(10);
}

void loop() {
updateRawSpeed();

// --- Process PWM commands ---
while (Serial.available() > 0) {
char c = Serial.read();
if (c == '\n') {
processCommand();
serialCommand = "";
} else {
serialCommand += c;
}
}

// --- Continuous RPM transmission ---
if (micros() - lastRPMTransmission >= SERIAL_SEND_INTERVAL_US) {
Serial.println(rawSpeedRPM, 2); // send RPM with 2 decimal places
lastRPMTransmission = micros();
}
}

void processCommand() {
if (serialCommand.startsWith("P")) {
int pwmValue = serialCommand.substring(1).toInt();
if (pwmValue > 255) pwmValue = 255;
if (pwmValue < 0) pwmValue = 0;

if (pwmValue == 0) motor.run(RELEASE);
else {
motor.setSpeed(pwmValue);
motor.run(FORWARD);
}
}
}

Это код/контроллер Python:
import serial
import time
import csv
import pandas as pd
import matplotlib.pyplot as plt

# --- PI Controller Class ---
class PIController:
def __init__(self, Kp, Ki, Ts, setpoint, output_min, output_max):
self.Kp = Kp
self.Ki = Ki
self.Ts = Ts
self.setpoint = setpoint
self.output_min = output_min
self.output_max = output_max
self.integral_term = 0.0

def set_setpoint(self, new_setpoint):
self.setpoint = new_setpoint
self.integral_term = 0.0

def update(self, measured_value):
error = self.setpoint - measured_value
p_term = self.Kp * error
potential_i_term = self.integral_term + (self.Ki * error * self.Ts)

# Anti-windup
if potential_i_term > self.output_max:
self.integral_term = self.output_max
elif potential_i_term < self.output_min:
self.integral_term = self.output_min
else:
self.integral_term = potential_i_term

control_signal = p_term + self.integral_term

# Clamp control signal to output limits
if control_signal > self.output_max:
control_signal = self.output_max
elif control_signal < self.output_min:
control_signal = self.output_min

return control_signal

`# --- TEST PARAMETERS ---
SERIAL_PORT = 'COM4'
BAUD_RATE = 115200
TEST_DURATION_s = 10.0

# --- Control Parameters ---
DESIRED_SETPOINT = 250.0 # RPM
TS = 0.10 # Sampling period (s)
PWM_MIN = 0
PWM_MAX = 255

# Artificial delay simulation
ARTIFICIAL_DELAY_s = 0.0

KP = 0.2158
KI = 1.2231

OUTPUT_FILENAME = (
f"setpoint={DESIRED_SETPOINT}_Kp={KP}_Ki={KI}_delay_{ARTIFICIAL_DELAY_s}.csv"
)

print(f"Using gains: Kp={KP}, Ki={KI} (for Ta ~ {0.2*4 if KP > 1 else 0.8}s)")
# -----------------------------------------------------------------

controller = PIController(KP, KI, TS, DESIRED_SETPOINT, PWM_MIN, PWM_MAX)
collected_data = [['Time_s', 'Setpoint_RPM', 'Measured_RPM', 'PWM_Output']]

print("Starting control test...")

# --- Main Execution Block ---
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=0.1)
time.sleep(2)
print(f"Connected to {SERIAL_PORT}. Test starting in 3 seconds...")
print(f"Saving data to: {OUTPUT_FILENAME}")
time.sleep(3)

test_start_time = time.perf_counter()
previous_loop_time = test_start_time
current_time_global = 0.0
current_speed = 0.0

while current_time_global < TEST_DURATION_s:
now = time.monotonic()

if now - previous_loop_time >= TS:
previous_loop_time = now
current_time_global = now - test_start_time

while ser.in_waiting:
try:
line = ser.readline().decode('utf-8').strip()
if line:
current_speed = float(line)
except:
pass

# --- Compute PWM ---
pwm_output = controller.update(current_speed)
pwm_int = int(pwm_output)

command = f'P{pwm_int}\n'
ser.write(command.encode('utf-8'))

# --- Save Data ---
data_to_save = [current_time_global, DESIRED_SETPOINT, current_speed, pwm_int]
collected_data.append(data_to_save)

print(f"Time: {current_time_global:.2f}s, Setpoint: {DESIRED_SETPOINT}, "
f"Measured: {current_speed:.2f}, PWM: {pwm_int}")

print(f"\nTest completed ({TEST_DURATION_s}s).")

except serial.SerialException as e:
print(f"Could not open serial port. {e}")
except KeyboardInterrupt:
print("\nTest interrupted by user.")
finally:
print("Stopping motor...")
try:
if 'ser' in locals() and ser.is_open:
ser.write(b'P0\n')
ser.close()
print("Serial connection closed.")
except Exception as e:
print(f"Error while closing serial connection: {e}")

if len(collected_data) > 1:
try:
with open(OUTPUT_FILENAME, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(collected_data)
print(f"Data successfully saved to '{OUTPUT_FILENAME}'")

# --- PLOTTING SECTION ---
print("Generating result plot...")

# Read file
df = pd.read_csv(OUTPUT_FILENAME)

# Create figure and axes (2 stacked plots)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(10, 8))

# --- Plot 1: Speed (RPM) ---
ax1.plot(df['Time_s'], df['Setpoint_RPM'], 'r--', label='Setpoint (RPM)')
ax1.plot(df['Time_s'], df['Measured_RPM'], 'b-', label='Measured Speed (RPM)')
ax1.set_ylabel('Speed (RPM)')
ax1.set_title('PI Controller Response')
ax1.legend()
ax1.grid(True)

# Lines for 2% settling band
ax1.axhline(y=DESIRED_SETPOINT * 1.02, color='k', linestyle=':', linewidth=0.5)
ax1.axhline(y=DESIRED_SETPOINT * 0.98, color='k', linestyle=':', linewidth=0.5)

# --- Plot 2: Control Effort (PWM) ---
ax2.plot(df['Time_s'], df['PWM_Output'], 'g-', label='PWM Output')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('Control Signal (PWM)')
ax2.grid(True)
ax2.legend()

plt.tight_layout()
plt.show()

except Exception as e:
print(f"Error while generating plot: {e}")

else:
print("No data collected to save or plot.")`



Подробнее здесь: https://stackoverflow.com/questions/798 ... and-python

Вернуться в «Python»