Torque control aka current control of brushed DC

Hi.

I have spent countless hours on my project and i am now out of ideas and energy. I dont understand the PID. I understand the math - no problem, but as i start to tune the system, everything i thought i knew about response of my tuning completely fails. And the worst thing is that i cant understand why (so pretty please with a cherry on the top) help me out.

I am trying to achieve true torque control (aka current control) which is done (as i understand it) by sampling the amount of current that runs in the system and using that as the Input-parameter of the PID algorithm. So far i have used the library from Arduino.cc but things get messed up when i start tuning.

The system consists of the following:
pancakemotor: http://www.pml.com.cn/ver2en/motors/GPM16LR.html
Motorboard: http://www.robotshop.com/eu/en/devantech-md03-50v-20a-h-bridge-driver.html
Ampsensor: 50A Hall-Effect Bi-directional Current Sensor
Arduino Uno
Pot for setpoint adjustment.

My system is (for now) very simular to this: Arduino PID torque control - YouTube
and i want to achieve exactly the same as seen in the video.

My hallsensor (to measure current) is quite jumpy, so i added a small low-pass filter. besides that i use an average of 5 measurements and the median when gathering Setpoint and Input.

Weird things happening is the following:

  1. the output wont go back to 0 as i turn the setpoint to 0 - i am not using any I gain, so it should stabilize quite quickly at 0 PWM right? Sometimes (not all the time) my output will continue to run, leaving the Input floating way over (20-100 from 0-1023) my setpoint thats turned down to 0.

  2. I cant stabilize the system, the output is varying too much, so the feel of the shaft is very jumpy.

PLEASE..........ANYONE i am out of things to try.

PID tuning is perhaps an art, but the first step is posting your code...

Also you mention a low pass filter, but don't describe it. Averaging consecutive readings
introduces delay, and phase delay is important in control systems.

The PWM frequency used is also important - what is it?

Can you look at the sensor output on a 'scope? How much noise is there (hall sensors
are usually quite noisy)?

That sensor looks to have a ferrite core, so it may have hysteresis.

Hello again.
First off, thanks for the feedback MarkT.

Sorry for my late reply - unfortunately i dont own a oscilloscope but i hope the readings form my DAQ will do the job.
The first picture (hallsensor uden load) is the hallsensor connected to 5v but with no movement of the motor. The other picture shows the output as i wrap the string around the shaft and pull so a current is induced in the motor and sensor.

It seems like the signal will produce an AMP-reading within half an amp?!? peak to peak= 0,0225V /0,04mv/V = 0,56Amp I dont think that will be satisfactory for me but i am no expert - perhaps my/a filter would be a solution (or make it worse)?
I have tried with several but not sure how big a timeconstant is too big - lack of experience.

Regarding the PWM i can read from the datasheet that the motorboard outputs PWM @ 15kHz. i am signalling the motorboard @ 32kHz from the Arduino (TCCR2B = TCCR2B & 0b11111000 | 0x01) why is that important?

Here is the code so far:

#include <PID_v1.h>

//**************Declaring pins**********

//input/output pins
const int setpointPin = A3; // connected to a pot
const int inputPin = A1;  // connected to the Hallsensor
const int outputPin = 3;  
const int turnLeftPin = 2;  //Directionpin on the motor board. 


// Diverse globale variabler
double zeroInput;  // sensorreading at atartup or no load

double avgInput;    //Calculated average input
double avgSetpoint;
double Setpoint, Input, Output; // PID variables
double Kp;  //PID-reg***************
double Ki;
double Kd;


// smooth variables
float ampArray[5];
float ampSort[5];
float amp2;
float ampOut;
const int nrReadings = 5;


// changeble values
unsigned long previousMillis = 0UL;     // store timer current value, and start offset
unsigned long inputinterval = 0UL;   // speed the loop runs 

PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
unsigned long serialTime; //this will help us know when to talk with processing


void setup()
{
  // Motorcontroller
  pinMode(outputPin, OUTPUT);
  pinMode(turnLeftPin, OUTPUT);
  pinMode(inputPin, INPUT);
  pinMode(setpointPin, INPUT);

  // Diverse
  Serial.begin(9600);

  // PWM frekvens
  TCCR2B = TCCR2B & 0b11111000 | 0x01;  // Timer 2: PWM 3 & 11 @ 31372 Hz the motorboard is specified to want >20kHz

  //initialize the variables we're linked to
  Setpoint = 0;
  zeroInput = analogRead(inputPin);

  turnLeftPin, LOW; // before implementing PID in both directions i start with one direction 
  //Turn the pid on
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0, 150);  //Limits the output
  myPID.SetSampleTime(10); // Determines how often the PID algorithm evaluates (default = 200)


}


//**************************LOOP***************************************

void loop() {

  // current times since startup
  unsigned long currentMillis = millis();


  if (currentMillis - previousMillis > inputinterval) {
    //previousMillis = currentMillis;
    previousMillis += inputinterval;  

    Motormove();
  }

  //send-receive with processing if it's time
  if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime+=100;
  }
}


void Motormove() {
  Setpoint = getSetpoint(); //This can be commented out and then changed in processing instead of a pot.
  Input = getInput();
  myPID.Compute();
  analogWrite(outputPin, Output);
}

double getSetpoint() {
  double sum;
  double avg;
  for (int i = 0; i <= nrReadings; i++) {
    sum += analogRead(setpointPin);  
    delayMicroseconds(50);   
  }
  avgSetpoint = sum/nrReadings;
  sum = 0;
  return avgSetpoint;
}

double getInput() { 
  double sum;
  double avg;
  for (int i = 0; i <= nrReadings; i++) {
    sum += analogRead(inputPin);       
    delayMicroseconds(50);
  }
  avgInput = sum/nrReadings;
  sum = 0;
  return avgInput;
}


// These functions should give a median but for now i am not using them. 

double sortAmpMedian() {
  for (int i = 0; i < nrReadings; i++) {
    ampSort[i] = ampArray[i];
  }
  bubbleSort();
  bubbleSort();
  bubbleSort();
  for (int i=0; i < nrReadings; i++) {
    ampArray[i] = analogRead(inputPin)-zeroInput; 
  }
  amp2 = 0;
  for (int j=1; j<nrReadings-1; j++) {
    amp2 += ampSort[j];
  }
  ampOut = 0;
  ampOut = amp2/nrReadings-2;
}

void bubbleSort() {
  int out, in;
  float swapper;
  for(out=0 ; out < 5; out++) {  // outer loop
    for(in=out; in < 5; in++)  {  // inner loop
      if( ampSort[in] > ampSort[in+1] ) {   
        // swap them:
        swapper = ampSort[in];
        ampSort[in] = ampSort[in+1];
        ampSort[in+1] = swapper;
      }
    }
  }
}






////********************************* 
/********************************************
 * Serial Communication functions / helpers
 ********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output  
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param
void SerialReceive()
{

  // read the bytes sent from Processing
  int index=0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while(Serial.available()&&index<26)
  {
    if(index==0) Auto_Man = Serial.read();
    else if(index==1) Direct_Reverse = Serial.read();
    else foo.asBytes[index-2] = Serial.read();
    index++;
  } 

  // if the information we got was in the correct format, 
  // read it into the system
  if(index==26  && (Auto_Man==0 || Auto_Man==1)&& (Direct_Reverse==0 || Direct_Reverse==1))
  {
    Setpoint=double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the 
    //   value of "Input"  in most cases (as 
    //   in this one) this is not needed.
    if(Auto_Man==0)                       // * only change the output if we are in 
    {                                     //   manual mode.  otherwise we'll get an
      Output=double(foo.asFloat[2]);      //   output blip, then the controller will 
    }                                     //   overwrite.

    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //

    if(Auto_Man==0) myPID.SetMode(MANUAL);// * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //

    if(Direct_Reverse==0) myPID.SetControllerDirection(DIRECT);// * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}

// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings.  much easier than getting
// floats from processing to here no?
void SerialSend()
{
  Serial.print("PID ");
  Serial.print(Setpoint);   
  Serial.print(" ");
  Serial.print(Input);   
  Serial.print(" ");
  Serial.print(Output);   
  Serial.print(" ");
  Serial.print(myPID.GetKp());   
  Serial.print(" ");
  Serial.print(myPID.GetKi());   
  Serial.print(" ");
  Serial.print(myPID.GetKd());   
  Serial.print(" ");
  if(myPID.GetMode()==AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");  
  Serial.print(" ");
  if(myPID.GetDirection()==DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}

If you made it to here - Thanks again for your time.

Bjorn

The PWM frequency matters because you don't want your PID loop to respond
at that timescale, you want it filtered out from your sensor readings.

Since that's a nice high PWM it should be easy to add suitable low-pass filtering
to cut it out but retain a speedy PID response.

However that hall sensor does look mighty noisy (will it be cleaner with low
pass filtering? I'd suggest RC filter with cutoff say 1kHz as a first guess, see
if the result is clean enough)

Hall sensors are noisy, it is the way of things.

Hey
@ MarkT. It (of cause) helped with the RC filter (nice), now i am down to about 1/2 the noise (0.014V) how do you think that will work?
There is one funny (read terrible) but interesting response of the system. If i apply a steady low output from the arduino the spring will tighten but after a few seconds the motor begins to make a little audible noise. Thats not the problem, but once in a while it is as if the motor looses power for an instant - the duration of the "hole" is very short, only enough to make the motor rotate opposite the pulling direction for a few (15-20) degrees, then the motor reenergizes. My first guess was that it was the motor board is going into overcurrent protection (a feature it has to not burn out) due to the varying and very low resistance at certain rotor-positions, and the very low inductance of this kind of motor. I have looked at the PWM on a oscilloscope and it does not seem to change when the motor looses power.
So i have a motor that seems to get a constant PWM voltage but occasionally looses power for (less than) a split second. ????!!!!!!????? Anyone?
I am running PWM @ 32kHz

Thanks