11-15-2011 05:52 PM
Hello Everyone,
Preface: I have searched every nook and cranny of these forums for the problem and wasnt able to find it answered.
For some time now, I have been trying to write a closed loop PID control for some DC motors that we had lying around.
I currently have a PCIe 6321 connected to an SCB 68 connector board. I also have some off the shelf brushed DC motor
drivers which use 0 - 2.5 - 5 V (0 = max ccw, 5 =max cw.) analog signal for driving the motors. These motors have quadrature
encoders that give me 8384 pulses/rotation (after calculating the corresponding gear ratio conversion).
I used the two available AO ports to generate the signal to drive the motors ( I am using the AnalogOutScalar function ) and
was able to control the motion of the motor. I used the ctr0 and ctr1 (PFI8,10 and PFI3,11) to connect my encoder A and Bs.
Using SignalExpress, I was able to plot the encoders and angular position in degrees confirming that my connections were correct.
Now, I started coding for the PID control. I made functions to move the motor which would generate an analog out for a given
value passed as a parameter. And I made a function to read the encoder, (I used the ReadCounterScalarF64 function as I needed
only one value/per call.)
If I initialize the task for reading encoders outside of my control loop and just read the encoder after moving the motor , I get values
up til a certain number of angles and then the output becomes constant. I have kept my timeout small (0.0001) and I recieve no errors.
Me sample rate is 1000.0 samples per second and samples per channel is 1.
My doubt here is, can I just keep calling the scalar counter read function in a loop without any problems? And why would the angle data
suddenly stop updating? And its not even a fixed time, it just stops updating at random times. This issue doesnt occur with SignalExpress.
Also, what would be the best way to go about coding closed loop control system algorithms using DAQmx ANSI C libraries for smooth
functioning? I initially started with multithreading where the encoder was reading in an independent thread and data was being passed
synchronously to the PID algorithm and corresponding analog output was being generated. But asynchronous data is not a correct estimate
of the current state and I am looking for a rather "Research level" PID control.
So ultimately, now I am sticking to a single thread where I send a short pulse of analog voltage and read the corresponding change in position
after every pulse.
I would really appreciate it if you guys can spare some time and help me out.
Prashant.
Note to NI Engineers: The reference files given with the DAQmx software are great but they are very straightforward (one analog input or one encoder read etc.).
It would be great if you guys can put up some more complex examples ( multiple functions in one program, dependent tasks i.e. maybe an analog out
dependent on encoder input (wink). ). If it already exists, can you please be kind enough to point me to that direction?
11-16-2011 05:22 PM
Hi Prashant,
That is interesting, from what it sounds like you should be able to run that read command in the loop without problems. I would suggest having roughly a 10:1 ratio of "samples per second" to "samples read per channel." This could be one of the causes of your encoder reading giving surprising values, so I would set samples per channel to about 100. Also, when you say it stops updating, do you mean that you stop getting values or just that all the values are the same? Can you do the reading of your encoder with an example, and how does it updating go with that?
I know you mentioned reference files already, but I wanted to be make sure you have the page about the text-based examples.
Also, if possible, I would recommend working with LabVIEW with this. There are a lot more initial examples and documentation about control and PID implementation.
Let me know how that works and if you need any more clarification.
Regards,
Nathan B
Applications Engineer
National Instruments
11-17-2011 08:12 AM
Hi Nathan
Thank you so much for replying. Thank you for your suggestion about the 10:1 ratio. Yes, I have access to all the text based examples provided with the CD and the ones online.
Here is the PID loop code
DAQmxCreateTask("",&taskHandle);
//Creating an angular displacement read channel using the counter (ctr0) with Ticks = 8384 and quadrature decoding
DAQmxCreateCIAngEncoderChan(taskHandle, MCP_fl_enc_A_chan,"",DAQmx_Val_X4,0,0.0,DAQmx_Val_AHighBHigh,DAQmx_Val_Degrees,Ticks,0.0,"");
//Specifying pin at which Encoder A is connected = source, with rate as 1000 samples/sec and 100 samples per channel.
DAQmxCfgSampClkTiming(taskHandle, MCP_fl_enc_A_port,1000.0,DAQmx_Val_Rising,DAQmx_Val_ContSamps,100);
//Activating Quadrature encoder read task
DAQmxStartTask(taskHandle);
//read once before loop starts (timeout = 0.001)
DAQmxReadCounterScalarF64(taskHandle,timeout,data,NULL);
//converting float64 to double
doubledata = double(data[0]);
curr_angle = (doubledata);
//calculate initial error and scale it down.
errorpid = (angle_deg-curr_angle)/720;
//Error tolerance set to 0.0001
while (abs(errorpid) > tol){
data[0] =0.0; // <========== Initializing the read data as 0 to check if data has stopped coming or just stopped updating
//proportional gain
P = Kp*errorpid;
//Derivative gain
D = (errorpid-prev_error)*Kd;
prev_error = errorpid;
//Accumulating error for Integral gain
acc_error = acc_error + prev_error;
I = acc_error*Ki;
//some random limits for avoiding integral overflow
if (I > 1) I=0.01;
if(I<-1) I= -0.01;
//calculating PWM out
PWM_out = (PWM_out + (P + I + D)) ;
//limiting PWM out
if (PWM_out > 5.0) PWM_out = 5.0;
if (PWM_out < 0) PWM_out = 0;
value = PWM_out;
//function to send an analog out ov value= value using AO 0
MoveMotor(MCP_fl_ctrl, value); // DAQmxErrChk (DAQmxCreateAOVoltageChan(motor_sig,channel,"",0,5,DAQmx_Val_Volts,""));
//stop Motor after the pulse
Stop_Motor(1);
prev_angle = curr_angle;
// read again
DAQmxReadCounterScalarF64(taskHandle,timeout,data,NULL);
doubledata = double(data[0]);
errorpid = (angle_deg-curr_angle)/720;
}
Sleep(100);
DAQmxStopTask(taskHandle);
DAQmxClearTask(taskHandle);
I haven't included the error checks at each function call in here to reduce the length a little. After your question , I added a data initialization at the beginning of the loop to see
if encoder data had just stopped updating or has completely stopped sending data. It seems, the data just stops because after a few reads, the output becomes 0 permanently.
The only angular read example is a continuous read which requires an external clock source which I currently dont have. So I modified the program a bit and converted the continuous
read to a scalar read with other parameters being the same (Rate = 1000 and samples 100). Now the read function is:
DAQmxErrChk (DAQmxReadCounterScalarF64(taskHandle,10.0,data,0));
Now after acquiring some data, it shows the error,
Multiple sample clock pulses were detected within one period of the input signal. Use a sample clock rate that is slower than the input signal. If you are using an external sample clock, ensure that clock signal is withing the jitter and voltage level specifications and without glitches.
Property: DAQmx_Read_RelativeTo
Requested Value: DAQmx_Val_CurrReadPos
Property: DAQmx_Read_Offset
Requested Value: 0
Status Code: -201314
So I figured that my sample rate is too high, I went all the way down to 10(can we go below 1?) to maintain the 10:1 ratio and all the way up to 1MS/s but it keeps popping the same error
at random times (some times it reads upto 70 degrees sometimes upto 2 degrees on the same settings.)
I can't figure out what is wrong because signal express just shows pristine results.
Thank you so much for your time and patience.
11-17-2011 08:28 AM - edited 11-17-2011 08:37 AM
Hi
Just to add, I added a delay of 50 ms to the modified example code and it keeps reading (even though incorrect, i.e. the increments are correct but due to the delay, the angle is way behind what it is actually supposed to be.) without the error at a sample rate of 1000 and 100 samples per channel.
Also, I am compiling this in visual studio if that makes any difference.
Prashant
11-18-2011 05:21 PM
Hi Prashant,
Sound like you may want to work with a very small wait in your code. You may have freed up needed processor resources by adding the read. I would see how small of a read you can add to allow "correct" functioning. There may need to be more processing focused on the manipulation of the data, and you know the card will take care of evenly spaced samples at the rate you set.
Let me know how different waits affect your program. In LabVIEW, this would be a great case to use Producer-Consumer architecture so you may want to look into ways of investigating similar implementation.
Regards,
Nathan B
Applications Engineer
National Instruments
11-21-2011 02:27 PM
Hi Nathan
Thanks for your reply, I tried the delays but it just progressively made the readings lag more and more even though it didnt show the error this time.
So, I connected my encoders to the Digital ports and wrote a manual quadrature decoder code and used the infinite digital port sampling. I ran this
program in a seperate thread and then accessed the value from the main thread for the PID loop. It works great and I have good control now.
I figured, the read call and the loop cycle were not synchronizing with the encoder reads, and that is why it would give me the error at random times
on constant velocities.
Continuous sampling of the encoder port was not an option as I dont have an external clock at the moment.
Thank you for your time. I really appreciate all of your help.
11-22-2011 05:04 PM
Hi Prashant,
No problem, I am glad you could get it working correctly!
Let me know if you need any more assistance in the future, best of luck with your application.
Regards,
Nathan B
Applications Engineer
National Instruments