Need Help With PID code - Motor Driver

Hi there,

I am working on a project where I am using an Arduino nano as a PID controller for a 12v DC Motor. This is an automotive application, using OE Toyota componentry (Variable Nozzle Turbocharger)

The hardware consists of a DC motor connected to an arm, which decreases or increases the angle of the vanes inside the turbine housing of the turbo. Attached to the motors' shaft, is a position sensor that outputs a linear 0-5v signal based on motor angle/rotation/position (much the same as a TPS sensor for a throttle body) There are two independent sensor output channels for failsafe - both output the same scale and are equal to one another.

I have been working on trying to develop some code to drive this and have not been having much luck. I'm relatively new to programming and while I know enough to get by with most simple projects, I don't know enough to get this to where it needs to be.

I recently stumbled upon on some code a user on here (@system) created for a drive by wire (electric) throttle and had confirmed it working. The way the DBW throttle works is basically the exact same as this, however the sensor scales are inverse (one channel reads 0-5v, the other is 5-0v)

So i'm hoping there may be someone on here who could advise what I should change certain parts of the code to in order to suit my hardware/setup. Even better if the original creator of the code sees this and could help me out.

I will have an external ECU sending a 5v PWM to the Arduino which will be the set-point. The Arduino then needs to read the motors' current position and drive the motor forward or backward in order to achieve the commanded set-point value.

The lower and upper bounds of my motor are values of 280-900.

I understand most of the code, however the below sections I just don't understand enough about with the syntax etc.

myPID.SetTunings(0.15, 2.00, 0.00);
int TPS0 = constrain(analogRead(0), 663, 54); //Range: 663 - 54
int TPS1 = constrain(analogRead(1), 164, 970); //Range: 164 - 970
int diff0 = (TPS0-54)+map((TPS1-164), 0, 806, 0, 609);

Input = map(constrain((TPS1 - 164), 0, 806), 0, 806, 0, 380);
int Pedal0 = constrain(analogRead(2), 0, 180); //Range: 0 - 180
int Pedal1 = constrain(analogRead(3), 0, 380); //Range: 0 - 380
int diff1 = Pedal0 - map(Pedal1, 0, 380, 0, 180);

DBW_THROTTLE_PID_NOTED.ino (6.5 KB)
The full code for your reference. A lot of the other stuff for gear shifting etc isn't relevant to me. I have added notes next to the parts I need to change.

Hoping someone can give me a hand with this. I understand the values have been constrained and also remapped, I just don't know enough about these functions and how to adapt my values into this.

Thanks in advance!

If your challenge is simply inverting the analog inputs, that’s easy, but beyond that, I hope there’s someone on board that knows more.

Good luck

I just need to basically tweak those parts of the code to suit my setup. So both TPS0 & TPS1 need to read the same range (280 - 900)

And Pedal0 (will be my PWM input from external device) can read any range from 0-1023 basically, but just not sure how to adapt. Don't need Pedal1 at all. Just the one PWM input.

If that makes sense?

I'm confident it is relatively simple for someone who has a better understanding than myself :slight_smile:

are the "two independent sensors" TPS0 and TPS1?
is so, why are they "constrained" to different ranges?

They're part of the one sensor, just 2x channels for failsafe. In the example code's case, one channel reads 5v for fully open, 0v for fully closed. The other channel reads the opposite.
I think he has inverted one, so that both are reading the same (like what my sensor does)

what are these values for? PWM to drive the motor? (aren't PWM value 0-255) are these related to the TPS or Pedal values?

Those are the raw input values via analogRead of the sensor input pin.

so what needs to be in the 280-900.

It looks like TPS0 is only used for 'diff0' (failure detection) and ignored for Input. The values below 164 and above 970 are clipped and the range from 164 (now 0) to 970 (now 806) are re-mapped to the range 0 to 380. It might be clearer to write:

  Input = constrain(TPS1, 164, 970);
  Input = map(Input, 164, 970, 0, 380);

What are the frequency and duty-cycle range of the PWM signal? In a PID controller, the Setpoint (desired value) and the Input (current value) must be in the same units. What units are you using? If the units don't naturally correspond, you will have to convert one of the two values into the other's units. For example if your sensor measures inches and your Setpoint is in cm you will have to convert to a common unit of measure.

If the two signals are used to detect a hardware failure, you should NOT constrain them before checking for a difference. The constrain might hide a failure.

Thanks for the reply.

Ok, so I am somewhat understanding it properly by the looks.

So if for instance I wanted to only read/act upon the values between a certain range, say, 280 - 900, I would just use:

Input = constrain(TPS1, 280, 970);

In terms of the external ECU, it's frequency can be programmed so shouldn't be an issue.

For now I am just trying to get this code working on the bench - using a pot as the Setpoint. So that will obviously read from 0-1023

So yeah, I understand that whatever value i'm using, has to be consistent across the field. And in terms of testing on the bench the Input is an analogRead of the position sensor (0-1023) and the Set-point is an analogRead of the pot (also 0-1023)

In terms of the matter of the hardware failure detection, due to both channels on my sensor reading the same scale in parallel to one another, I don't think i'll have an issue there.

I guess my real question is; do I need to constrain at all? The Input values can only be between 280 - 900 (as the motor physically bottoms out on a stop at each end of its' movement. And the Setpoint can be whatever I need it to be (0-1023 for instance)

Does constraining only cap the allowable range between the two points? Or does it also rescale that region so that the lower end is now 0% and the upper end is 100%? Giving more accuracy within that range?

Yes. The constrain() function sets any value below the minimum to the minimum and any value above the maximum to the maximum. It does not otherwise change the value.

If you allow the Setpoint to be outside the available range of the Input then the PID will jam the motor against the physical stop, trying to reach the Setpoint. This will stress your power supply components and could easily damage the motor.

Ok, that makes sense and Is what I have done in my code that i'm playing around with.

I have the serial monitor reading some of the key values and the constrain() is working (can see the values don't go outside of this range when turning the pot)

I'll post below what I have done for the key parts of the code I'm playing around with for testing purposes. I have commented a lot of the other stuff out, for simplicity debugging and testing on the bench

So for the TPS read, this is what I have.

//PID Loop tunings
myPID.SetTunings(0.15, 2.00, 0.00);
TPS0 = constrain(analogRead(A2), 290, 890);

Then the Input

//PID Input from TPS
Input = TPS0;
Pedal0 = constrain(analogRead(A1), 290, 890);

Then the setpoint:

//PID Setpoint from Throttle Pedal
Setpoint = Pedal0;
//Set throttle to 0
if(Setpoint <= 5)
{
analogWrite(speedPin,0);
}
else
{
myPID.Compute();
analogWrite(speedPin,Output);
}

I'm guessing for the Setpoint, I should be changing it from <=5 to <=295? (My lowest bounds of my Input range?)

Currently with the code I have modified, the motor is just jammed one way and has no reaction to the pot. So I have to quickly shut the PSU off. I'm guessing it may be due to the abovementioned?

Then the last lines in the sketch that I feel have any relevance:

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

I am now looking at the beginning of the code where this is commented out, but I am assuming I am going to have to tweak these also, just not sure in what way.

//Uncomment to tune the PID loop
*/
float Kp = mapfloat(analogRead(4), 0, 1023, 0, 5);
Serial.print(Kp, DEC);
Serial.print(" ");
float Ki = mapfloat(analogRead(5), 0, 1023, 0, 5);
Serial.print(Ki, DEC);
Serial.print(" ");
myPID.SetTunings(Kp, Ki, 0.00);
*/

In looking at the above, I can't see what is coming into A4 or A5. I can see that it's obviously a 5v PWM 0-1023, then it's been rescaled to 0-5 (guessing this is to simulate voltage - as the pedal position and throttle position sensors are 0-5v linear range)

This is noted at the top:

Hardware Connections:
-TPS 0: Pin A0 (Blue Wire)
-TPS 1: Pin A1 (Thin White Wire)
-Throttle Input 0: Pin A2 (White Wire)
-Throttle Input 1: Pin A3 (White Marked Wire)
-Error LED Pin A5 (Blue Wire)
-UpShift Relay: Pin 2 (Yellow Wire)
-DownShift Relay: Pin 3 (Pink Wire)
-UpShift Button: Pin 4 (Green Wire)
-DownShift Button: Pin 5 (Orange Wire)
-Ignition Cut Pin 6 (White with Green Stripe Wire)
-Neutral Switch Pin 7 (Brown Wire)
-L298N H-Bridge Enable A: Pin 9 (N/A)
-L298N H-Bridge Input 1: Pin 8 (Other Speaker Wire)
-L298N H-Bridge Input 2: Pin 11 (Gray Line Wire)

Hardware Connections:
-TPS 0: Pin A0 (Blue Wire)
-TPS 1: Pin A1 (Thin White Wire)
-Throttle Input 0: Pin A2 (White Wire)
-Throttle Input 1: Pin A3 (White Marked Wire)
-Error LED Pin A5 (Blue Wire)
-UpShift Relay: Pin 2 (Yellow Wire)
-DownShift Relay: Pin 3 (Pink Wire)
-UpShift Button: Pin 4 (Green Wire)
-DownShift Button: Pin 5 (Orange Wire)
-Ignition Cut Pin 6 (White with Green Stripe Wire)
-Neutral Switch Pin 7 (Brown Wire)
-L298N H-Bridge Enable A: Pin 9 (N/A)
-L298N H-Bridge Input 1: Pin 8 (Other Speaker Wire)
-L298N H-Bridge Input 2: Pin 11 (Gray Line Wire)

I feel it's starting to make a bit more sense to me, but will be interested to see what you make of it/if i'm thinking along the correct lines?

You don't want to do that! That's like saying "when I release the throttle pedal I want the throttle to stay right where it was at the time."

You have a 'speedPin' for setting the motor speed, but how do you set the motor direction? What if the Setpoint (pedal position) is lower than the current Input (throttle position)?

There should be no reason to constrain the TPS0 input since you say it will be constrained mechanically. Instead of constraining the pedal position input, you should map it to the desired TPS0 range:

Input = TPS0 = analogRead(A2); // Expect 290 to 890
Setpoint = Pedal0 = map(analogRead(A1), 0, 1023, 290, 890);
myPID.Compute();
if (Output < 0)
{
  SetMotorDirectionLower();
  analogWrite(speedPin, -Output);
}
else
{
  SetMotorDirectionHigher();
  analogWrite(speedPin, Output);
}

You will need to add myPID.setOutputLimits(-255.0, +255.0); to setup(). The default limits are 0 to 255.

Yeah, that's what I was scratching my head about too, still don't understand why that was like that, but it didn't seem right to me...

This is where I am really needing some guidance from someone with a lot more knowledge in this field than myself (so thank you for taking the time to help me with this)

I was under the assumption (because the OP stated this code was working for him) that it should be catered for, as the a throttle obviously has to rotate in both directions depending on accelerator pedal position. I'm trying to wrap my head around what parts of the code need modifying to suit my circumstances.

Ok, that makes perfect sense.
I have just put your section of code you wrote into the sketch, however I'm getting the error message " 'SetMotorDirectionLower' cannot be used as a function " when verifying the code.

I don't know how the code was setting the motor direction so I put in some dummy function names. My guess is that you got an "is not defined in this scope" error and added a variable of that name instead of adding a function of that name. You can't call a variable.

I looked at the original code and it looks like their throttle positioner has an internal motor controller that takes a 5V PWM signal and positions the throttle directly. It's sort of like a hobby servo but using PWM instead of pulse length. I think their whole PID thing is fairly useless.

What are you using for a motor driver? How is it connected to your Arduino?

Ok, so based on what you've seen. What do you think I should do?

I am using a DFRobot motor driver.

Connected to the Arduino as per the sketch. In1 = 4, In2 = 7, PWM = 6

I don't think the Bosch throttle has any internal driver. Just motor + & -, and position circuits. Can have a look at the spec sheet here

The spec sheet says that the two feedback potentiometers go in opposite directions.

It looks like the motor takes 6 to 16 volts and draws less than 10A. Do you have a power supply for it?

The "IN1, IN2, and PWM" inputs of your motor driver are fairly common. PWM always sets the speed. The IN1 and IN2 pins are opposites: HIGH,LOW for one direction and LOW,HIGH for the other. Determine by experiment which direction makes the throttle position 'lower' and which makes it 'higher'.

Yes, that's what I was saying in the first post I think, the creator had to convert and then invert one of the ranges to read the same as the other.

Yes, I have everything setup with a PSU capable of 20a. My motor in particular only draws roughly 5a peak.

Yes, I am familiar with the motor driver and it's function. I know which way is which, so that's all good there.
I have done a couple of projects utilising motor drivers. Just new to PID and associated coding, so needing some advice on what would need to be looked at/changed in order to try utilise the core of this code for my application.

Knowing what you know now, and reading thru the original code, does it make any more sense?

Well, the compiler tells me that some diff0 and diff1 variables aren't used. When I comment out those it tells me that TPS0 is not used. There are a few places where analogRead() is called and the result is not used. The analog pin numbers are not named, making it hard to understand what the code is doing. The output is a PWM value from 0 to 255 so it is not driving a bi-directional DC motor so it is not like your throttle.

The main part of the original sketch seems to boil down to this:

//PID Library
#include <PID_v1.h>

//Pins assignments
const byte TPS1Pin = A1;
const byte Pedal1Pin = A3;
const byte SpeedPin = 9;

// Analog input range limits
const int TPS1Min = 164;
const int TPS1Max = 970;
const int Pedal1Min = 0;
const int Pedal1Max = 380;

//Define Variables we'll be connecting to PID
double Setpoint, Input, Output;

// Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 0.15, 2.00, 0.00, DIRECT);

void setup()
{
  Serial.begin(9600);
  delay(200);

  myPID.SetMode(AUTOMATIC);
  pinMode(SpeedPin, OUTPUT);
}

void loop()
{
  //PID Input from TPS
  Input = constrain(map(analogRead(TPS1Pin), TPS1Min, TPS1Max, Pedal1Min, Pedal1Max), Pedal1Min, Pedal1Max);

  //PID Setpoint from Pedal
  Setpoint = constrain(analogRead(Pedal1Pin), Pedal1Min, Pedal1Max);

  myPID.Compute();

  analogWrite(SpeedPin, Output);
}

To make that work on your hardware you will need to use Output to drive your bi-directional DC motor, which I have shown how to do previously.

Thank you yet again for taking your time and having patience to help me with this, John.

So when you say

Do you mean something like this? (for simplicity sake)

if (Output < 0)
{
analogWrite(speedPin, -150);
}
else
{
analogWrite(speedPin, 150);
}

I know your aformentioned code was using

if (Output < 0)
{
SetMotorDirectionLower();
analogWrite(speedPin, -Output);
}
else
{
SetMotorDirectionHigher();
analogWrite(speedPin, Output);
}

Also, I notice there is no call for the output to In1 or In2 for the motor driver - i'm assuming these just both get connected to 5v and you're using the PWM value to drive either direction?