I’m medium Arduino user. A view small think I create already but know I stuck on one issue. I build a cam slider, mechanical it is nearly ready. I tried different types of motors and finally I decide to use a DC-Motor. Position feedback with an encoder. I have a resolution of 0,2mm per signal. I’m pretty happy with this result. To use a dc motor has many reason for me, power supply must be portable, force of a geared is higher than a stepper, DC-Motor has smooth movement, not the steps of a stepper motor. To control the motor, I use a classical H-Bridge (l298). From the logic I tried some pretty simple code. That mean I give a set point and the motor will be powered in the right direction until he reach the position what the encoder count. So far so good and works in principal. But now comes the problem, depending on the drive speed the motor overshoot the target position more or less and start to toggle until he reach his position. Of course was this clear for me that this will happen and I was searching for a compensation. I tried a view thinks on my one but without results. I searched internet for thinks like this. And I always come to PID. The basic logic of PID is known to me, but I’m not an Arduino-Pro-Programmer to fit the existing PID to my needs. I searched already for days to find an Arduino Sketch were is close to me needs. So many PID videos and explanations about PID, but no sketch were it shows the basic with an H-Bridge, DC Gear-Motor and Encoder feedback were works based on positioning and speed control. This issue is very important to me, because as soon I get the linear movement managed I will start to build the x and y axis head. To understand why I need the smooth moved and the positioning precision is because I like the use the slider for motion time laps and normal video and of course a high possibility of repeating the cam movement for special fx. I added my basic code to understand were I’m standing. THX in advance. As soon I’ve a perfect moving system I will make the whole project official. But still fare a way from ready system.
This works fine but you have to tune the parameters and maybe change some of the pins. Its makes a geared dc motor with encode follow a manually turned encoder.
#include <TimerOne.h>
const int outPwm = 9;//pin for speed control
const int outDir = 8;//pin for direction control
//constants for PID calculation
const unsigned long samplePeriod = 400;//time between pid calculations in us
const long kp = 12;//proportional gain
const long ki = 3;//integral gain
const long kd = 2700;//derivative gain
const byte shift = 2;//for shifting/scaling output value
const long outMax = (1024<<shift)-1;//keep max output <=1023
const byte iShift = 11;//right shift for i term
const long iMax = (1024<<(iShift+shift))-1;//keep i term contribution <=1023 and avoid windup
const int stepFrame = 1500;//Number of values for step response
//variables for PID calculation
volatile long iTerm;//summing up integral error
volatile long lastPos;//for calculation of derivative
volatile int output;//output to speedcontrol
enum {FWD, REW} dir;
//data for position calculation
const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
volatile byte encState;//remembering the encoder output and acting as index for encTable[]
volatile byte stepState;//for edge detection on step input = D10
volatile byte inp;//for reading encoder pins
volatile byte setInp;//input signals from second encoder
volatile byte setState;//remembering setpoint encoder state
volatile long setPos;//position setpoint, updated by second encoder
volatile long actPos;//actual position from encoder
volatile long error;
volatile long dInput;
volatile byte sched;//for scheduling calculations
unsigned long t;//timekeeper
void setup(){
pinMode(outPwm, OUTPUT);
pinMode(outDir, OUTPUT);
pinMode(signalPin, OUTPUT);
Timer1.initialize(samplePeriod);
Timer1.attachInterrupt(doPID);
Timer1.pwm(outPwm, 0);
}//setup()
void loop(){
while(1){
PINB |= (1<<4);//toggle signal pin
//aquire input values
inp = PIND;//read inputs 0..7
encState = ((encState<<2)|((inp>>2)&3))&15;//use encoder bits and last state to form index
actPos += encTable[encState];//update actual position on encoder movement
setState = ((setState<<2)|((inp>>4)&3))&15;//use encoder bits and last state to form index
setPos += encTable[setState];
}//while(1)
}//loop()
void doPID(){
/*Compute all the working error variables*/
error = setPos-actPos;
iTerm += (ki*error);
if(iTerm>iMax) iTerm = iMax;
else if(iTerm<-iMax) iTerm = -iMax;
dInput = (actPos-lastPos);
/*Compute PID Output*/
output = kp*error+(iTerm>>iShift)-kd*dInput;
if(output < 0){
dir = REW;
output = -output;
}else dir = FWD;
//if(output<0)
if(output > outMax) output = outMax;
Timer1.setPwmDuty(outPwm, output);
digitalWrite(outDir, dir);
lastPos = actPos;
}//doPID()
THX so far. I just tried to understand a bit your code and to adapt it to my needs, but no chance.
I'm again on the point where I have a code what is fare a way for me. I guess you are using a motor shield what is using just one signal for the direction and one for the speed (PWM). Two points are absolute unknown to me, is the “signalPin” as output and which pins you are using for the encoder.
I appreciate you affords but I’m rely close to give up. The question is do have to change my hardware? But this will be more steps backward than forward.
digitalRead is quite slow. Also i need to read both channels simultaneously. So im reading PIND into a variable. PIND is equivalent to pins 0..7.
(inp>>2)&3 creates a 2 bit value that is pin2 and pin3. This value is then OR'ed with the previous value if those 2 pins shifted left 2 bits formning a 4bit value containg both the current and the previous state of teh encoder. This 4 bit value is then used as an index for encTable[] which translates the two last encoder states into movement 1, -1 or 0.
The same method is applied for pins 4 and 5 to establish the setpoint.
OK. So far clear. This line is not declared and creat an error and is also confusing me bit. What kind of motor controler you are using. You may have an idea to manage your code with my H-Bridge. I have to use two signals for the direction and one for the speed.
THX a lot. I hope I have more time tomorrow to review and to try it, but one think is stil to complicate to understand for me. Remember I'm not a pro-programmer. How can I add the secound pin. Or let me ask in this way, how come I from the PID to the output logick, dirPinA, direPinB and PWM pin. I woud appreciate if somebody got a scetch what is more or les ready and fit to my setup. The tuning I have to do, this is clear just working wirh an H-Bridge and an encoder.
I was just trying the code and modified it as your suggestion. No result. I have now a high frequent motor noise a no movement at all. I was using the code like this. I guess that the output does not work like this. As example the H-Bride request on pinA(HIGH) pinB(LOW) for right turn and pinA(LOW) pinB(HIGH) for left turn. Stop is both pins are Low but this is not important, if PWM signal is low will be also stop. Anyhow, I’m thankful that you spend your time for my newbie problem.
Here is the code I tried:
#include <TimerOne.h>
const int outPwm = 10;//pin for speed control on H-Bridge
const int outDirA = 9;//pin to H-Bridge A
const int outDirB = 8;//pin to H-Bridge B
//constants for PID calculation
const unsigned long samplePeriod = 400;//time between pid calculations in us
const long kp = 12;//proportional gain
const long ki = 3;//integral gain
const long kd = 2700;//derivative gain
const byte shift = 2;//for shifting/scaling output value
const long outMax = (1024<<shift)-1;//keep max output <=1023
const byte iShift = 11;//right shift for i term
const long iMax = (1024<<(iShift+shift))-1;//keep i term contribution <=1023 and avoid windup
const int stepFrame = 1500;//Number of values for step response
//variables for PID calculation
volatile long iTerm;//summing up integral error
volatile long lastPos;//for calculation of derivative
volatile int output;//output to speedcontrol
enum {FWD, REW} dir;
//data for position calculation
const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
volatile byte encState;//remembering the encoder output and acting as index for encTable[]
volatile byte stepState;//for edge detection on step input = D10
volatile byte inp;//for reading encoder pins
volatile byte setInp;//input signals from second encoder
volatile byte setState;//remembering setpoint encoder state
volatile long setPos;//position setpoint, updated by second encoder
volatile long actPos;//actual position from encoder
volatile long error;
volatile long dInput;
volatile byte sched;//for scheduling calculations
unsigned long t;//timekeeper
void setup(){
pinMode(outPwm, OUTPUT);
pinMode(outDirA, OUTPUT);
pinMode(outDirB, OUTPUT);
//pinMode(signalPin, OUTPUT);
Timer1.initialize(samplePeriod);
Timer1.attachInterrupt(doPID);
Timer1.pwm(outPwm, 0);
}//setup()
void loop(){
while(1){
PINB |= (1<<4);//toggle signal pin
//aquire input values
inp = PIND;//read inputs 0..7
encState = ((encState<<2)|((inp>>2)&3))&15;//use encoder bits and last state to form index
actPos += encTable[encState];//update actual position on encoder movement
setState = ((setState<<2)|((inp>>4)&3))&15;//use encoder bits and last state to form index
setPos += encTable[setState];
}//while(1)
}//loop()
void doPID(){
/*Compute all the working error variables*/
error = setPos-actPos;
iTerm += (ki*error);
if(iTerm>iMax) iTerm = iMax;
else if(iTerm<-iMax) iTerm = -iMax;
dInput = (actPos-lastPos);
/*Compute PID Output*/
output = kp*error+(iTerm>>iShift)-kd*dInput;
if(output < 0){
dir = REW;
output = -output;
}else dir = FWD;
//if(output<0)
if(output > outMax) output = outMax;
Timer1.setPwmDuty(outPwm, output);
digitalWrite(outDirA, dir);
digitalWrite(outDirB, 1-dir);
lastPos = actPos;
}//doPID()
I suggest you do the following in order to test your circuit.
Hardwire the driver for just one direction and feed it a standard arduino analogWrite() signal of various duty cycles. This is to ensure that you driver and motor are working properly. There is no point in looking at the code unless this is ensured.
Change the setpoint between various values. What you are describing could be that the motor is at the right position but with a small error that the pid does not fix. Or it is just flipping back and forth a round a encode position. If you have an oscilloscope connect it to the pwm pin and see what happens when you change the setpoint. It would be a good idea to set ki and kd to zero during this.
Once you got an increasing signal for an increasing error the asic function of the pid is ensured and you can start tuning it
Motor and H-Bridge driver is checked. Works with my simple code and I did what you sad. Motor is spinning in one direction with one direction pin. Also this point seams working “ digitalWrite(outDirA, dir);
digitalWrite(outDirB, 1-dir); “ , I tried both ways. Code and Wiring.
I checked with Oszi and no affected on the PWM signal and direction change. System seams stacking between forward and backward with max speed. Encoder works and signal comes to Arduino. I tried various kd and ki values.
This is a good question, but I thought this is not important for the beginning. I was more focused on to stabilize the motor on position. Just to understand my whole target of the project. For final operation I have to implement also end switches as safety stops and ref. point. There are already installed mechanical. Next step if PID works with the slider I will add also a two axis head for the cam, also PID required. But this is not the point at the moment. Of course I need also a set point to tell the system were to go.
I just tried the setup again and the encoder do not affect any think. But he works with my simple test code. I tried also a separate one, and both together as your setup but no effected, just to wipe out any doubts about broken or non-working hardware.
How are you changing the setpoint right now? You will not get any output signal unless there is a difference between setpoint and actual position. (no offense please, im just tryring to make sure that this point can be eliminated)
the easiest way will be for me if I can change the set point for testing via serial input. I can use also a potentiometer, later on i will use a display with bottoms and a menu fur starting different programmes.
In this case it is easier if I have a variable point where I can connect the code later on to the rest or may I use a separate board for the menu and display. So far I know for PID high performance is requested so I will separate tat the Motor Controller from main control. But this will be the next step.
Of you have a suggestion how can I fix the problem I will be teal happy.
You are not answering my question. How are you changing the setpoint right now? In order to test the code and the circuit you must have a setpoint that is different from the actual position otherwise you will get no output. And since i know the code works and it tested and you claim the circuit works the error must be somewhere around here.
I don't know how to change the setpoint in your code. I understand that I can change the setpoint with the secound encoder what is also not working and not my target. I think I give up on this point and I have to loock for an nother solution, like a ready PID controler.
Try this in the setup() function:setPos = 1000
encPos is the process value, setPos is the setpoint. That should be obvious.
Even if you use another PID controller you have to understand how it works, there is just no way round it.