我在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 --;
}
}
2条答案
按热度按时间gab6jxml1#
看起来这个问题是我们程序员称之为“阴影”的问题。你有一个变量
rpm
,它被定义在顶部,使其成为一个全局变量:然而,当你从
Serial
读取数据时,你实际上是将该变量重新定义为if块的“作用域”的局部变量:这个
rpm
变量和你的全局rpm
* 不 * 一样。它只在if块中可见。一旦你离开if块,这个变量就不再存在了。本质上,你永远不会设置你在这里引用的rpm
变量:这个
rpm
变量在你的程序中永远不会被触及,因此,它仍然是0。所以这里的解决方案是删除rpm
之前的int
:这样你就只能赋值给全局版本。我不知道为什么这没有抛出某种警告。我不是一个arduino的家伙,但要确保所有的警告都被启用。这至少应该触发了
-Wall
的“未使用的变量”。如果这是gcc或clang,可以用-Wshadow
启用更具体的警告。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秒,将方向盘恢复到直立位置等。