Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

PID motor control with DAQmx ANSI C

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?

0 Kudos
Message 1 of 7
(4,366 Views)

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

0 Kudos
Message 2 of 7
(4,352 Views)

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.

0 Kudos
Message 3 of 7
(4,343 Views)

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

0 Kudos
Message 4 of 7
(4,340 Views)

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

0 Kudos
Message 5 of 7
(4,317 Views)

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.

0 Kudos
Message 6 of 7
(4,303 Views)

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

0 Kudos
Message 7 of 7
(4,289 Views)