C语言 简单的PID循环在Arduino不工作时,我通过串行发送一个值

9lowa7mx  于 2023-04-19  发布在  其他
关注(0)|答案(2)|浏览(160)

我在Arduino Nano上运行了一个PID循环,我计划通过USB连接到Raspberry Pi。我正在我的笔记本电脑上做一些测试,我试图确保它工作,但它不工作。代码发布在下面。你可以看到我在下面的循环中注解掉了target = sin函数。当target = rpm被注解掉时,而target = sin则不是。程序运行得很完美。然而,当我注解掉target = sin并取消注解target = rpm时,它不起作用。我已经在串行监视器窗口中向nano发送了值,并且我知道它已经接收到该值,因为我在监视器中看到“Transmission received”。当target = sin处于活动状态时,目标值也会在串行监视器中更新,但是当我发送一个值给串行监视器时,它的目标值显示为零。有人能解释一下我做错了什么吗?

#include <CytronMotorDriver.h> 

  // Configure the motor driver.
CytronMD motorR(PWM_DIR, 3, 4);  // PWM 1 = Pin D3, DIR 1 = Pin D4.

// Configure encoder pins
#define EncoderA 6 // yellow wire 
#define EncoderB 7 // green wire

volatile int posi = 0;      // position of motor
long previousT = 0;
float eprevious = 0;
float eintegral = 0;

int rpm;
int rpm_correction_factor = 1;
float wheel_circumference = 0.090;

// The setup routine runs once when you press reset.
void setup() {
  Serial.begin(9600);
  pinMode(EncoderA, INPUT);
  pinMode(EncoderB, INPUT);
  attachInterrupt(digitalPinToInterrupt(EncoderA), readEncoder, RISING); //whenever encoder phase A is HIGH, readEncoder function is called
  Serial.println("target position");
  }

// setSpeed can be any number for -255 to 255 (negative means reverse)
void loop() {
   //check if arduino has received some data. if data has arrived, use readStringUntil to get the next line. All bytes received until /n are converted to useable communication data
    if (Serial.available() > 0) {
        String received_speed = Serial.readStringUntil('\n');
        int rpm = round(rpm_correction_factor * (received_speed.toInt())/(wheel_circumference * 3.14 * 60));
        Serial.print("transmission received");
    }
  
  
  //int target = 250*sin(previousT/1e6); //test target position for PID control loop
  int target = rpm; //target position from serial port 

  //PID constants (these values may have to be adjusted)
  float kp = 1;
  float kd = 0;
  float ki = 0;

  //finding the time difference
  long currentT = micros();
  float deltaT = ((float)(currentT-previousT))/1.0e6;
  previousT = currentT;  

  int motor_position = 0;
  noInterrupts();
  motor_position = posi;
  interrupts();

  //error calculation
  int e = motor_position - target; //position and target may have to be switched depending on how the motor leads are wired

  //derivative
  float dedt = (e - eprevious)/(deltaT);

  //integral
  eintegral = eintegral + e*deltaT;

  //control signal
  float u = kp*e + kd*dedt + ki*eintegral;

  //set motor power and limit it to between -255 and +255
  //float power = fabs(u);
  float power = u;
  if(power>255){
    power = 255;
  }
  if(power<-255){
    power = -255;
  }

  motorR.setSpeed(power);
  //store previous error
  eprevious = e;

  //print target and position to serial plotter
  Serial.print(target);
  Serial.print(",");
  Serial.println(motor_position);
  //Serial.println();

 
}

void readEncoder(){
  // called when EncoderA is high; If the motor is rotating counterclockwise, EncoderB should already be high, so we would add one to the position 
  int b = digitalRead(EncoderB);
  if(b>0) {
    posi ++;
    }
  else{
    posi --;
    }
  }
gab6jxml

gab6jxml1#

看起来这个问题是我们程序员称之为“阴影”的问题。你有一个变量rpm,它被定义在顶部,使其成为一个全局变量:

int rpm;
int rpm_correction_factor = 1;
float wheel_circumference = 0.090;

然而,当你从Serial读取数据时,你实际上是将该变量重新定义为if块的“作用域”的局部变量:

if (Serial.available() > 0) {
        String received_speed = Serial.readStringUntil('\n');
        int rpm = round(rpm_correction_factor * (received_speed.toInt())/(wheel_circumference * 3.14 * 60));
        Serial.print("transmission received");
    }

这个rpm变量和你的全局rpm * 不 * 一样。它只在if块中可见。一旦你离开if块,这个变量就不再存在了。本质上,你永远不会设置你在这里引用的rpm变量:

int target = rpm; //target position from serial port

这个rpm变量在你的程序中永远不会被触及,因此,它仍然是0。所以这里的解决方案是删除rpm之前的int

if (Serial.available() > 0) {
        String received_speed = Serial.readStringUntil('\n');
        rpm = round(rpm_correction_factor * (received_speed.toInt())/(wheel_circumference * 3.14 * 60));
        Serial.print("transmission received");
    }

这样你就只能赋值给全局版本。我不知道为什么这没有抛出某种警告。我不是一个arduino的家伙,但要确保所有的警告都被启用。这至少应该触发了-Wall的“未使用的变量”。如果这是gcc或clang,可以用-Wshadow启用更具体的警告。

kfgdxczn

kfgdxczn2#

有几个问题...
1.使用3.14代替(例如)M_PI
1.以ASCII发送值(如Serial.readStringUntil('\n');所示)进一步增加了问题(最好发送固定大小 * 二进制 * 数据值)。
1.虽然通过一些调试端口(例如UART)发送调试信息是“好”的,但发送/接收这些信息的时间会扰乱电机控制所需的真实的时序。
对于商业机器人电机控制系统(使用linux(和ROS)的控制系统)已经这样做了,将 * 位置 * 值发送到Arduino(使用FreeRTOS的等效系统),控制是“草率/松散的”。
Arduino必须以精确的间隔提供PWM值。它必须能够处理RPi发送的丢失/损坏的值。
我们发现,控制系统应该[周期性地]发送“轨迹/速度”值而不是位置值。也就是说,发送(例如)旋转速度值(例如弧度/秒)。或者,“路径”,例如机器人对于给定行程的 * 最终 * 地板位置。
当你发送位置值时,你有很多脆弱的计算来计算到达值的延迟,基于端口速度,离开时间戳(来自RPi)和Arduino上的到达时间戳。
相反,让Arduino获取速度值并自行计算PWM值。
然后,arduino可以以高速率计算PWM位置值,并且如果RPi确定速度/轨迹改变,则仅需要来自RPi的控制。
考虑一下这个类比......两个人在一辆车里。一个是司机[Arduino],另一个正在阅读Map[RPi]。如果乘客说在拐角处右转,乘客不应该告诉司机如何在每时每刻实现转弯。(例如)乘客不应该发送命令,例如:将方向盘向右转动30度,施加制动力,保持1.3秒,将方向盘恢复到直立位置等。

相关问题