1
\$\begingroup\$

This is my first time using a PID control loop so I have a lot to learn. I will explain what I am trying to do before I ask any questions.

I have an FPGA running a processing core that I made myself, which works fine. I then have a 24Vdc motor (4500rpm rated) that is driven by a L298N H-bridge motor driver board. The FPGA provides the signals for run forward, run reverse and a PWM signal. Coupled directly to the motor is a 1000ppr 2 channel rotary quad encoder that measures rotational speed of the motor. There is a potentiometer that is used to set a speed setpoint for the motor, which is scaled to 0rpm to 4000rpm.

The FPGA is a Nexys A7-100T development board that has 2 four digit seven segment displays, and some latching switches. The goal of this system is for a user to set the speed setpoint of the motor which is displayed on one display, select motor run/stop and forward/reverse with the switches, and the FPGA should control the motor speed to match the setpoint. The motor measured speed is then displayed on the other seven segment display.

The FPGA clock is 100MHz and from that I generate a counter that counts from 0 to 5000 in a loop (I picked 5000 for a 20kHz PWM frequency). To generate the PWM signal, I compare a value that is calculated in the PID loop which will be between 0 and 5000. When the counter value is less than the comparison value, PWM signal is high and vice versa. I will post the PWM code at the bottom.

The PID code is performed every 24ms on the rising edge of a 41.67Hz clock. The speed setpoint and measured RPM are sampled in the processing program cyclically, which is around 2MHZ. But the calculated setpoints are performed by the PID code every 24ms. I am only proportional gain currently as I expect the system to just oscillate at worst. Then can add i and d.

My problem is that the motor keeps starting and stopping erratically, to the point where it starts, reaches 800rpm, and stops. The repeats this.

In the code below, bits 0 to 13 contain the setpoint speed and bits 14 to 27 contain the measured speed. Bit 28 is not used, bit 29 is run/stop, bit 30 is forward/reverse and bit 31 informs that the motor has stopped for changing direction. There is a FSM for this too but I didn't post as I think it is not the problem.

There are 3 processes in the code below.

-First is the counter

-Second is the PID code

-Third is for the PWM output switching

Now for my questions. I think everything mentioned above is ok, but I am happy to hear if anyone thinks this design may cause issues.

  1. Does it matter that the PID output is updated every 24ms and fed into the counter when the counter is free running and not at 0?

  2. Do I need to cap the PID output to a low threshold instead of 0? Say when the error is small like 10rpm, the PID output could be 20, which is a very small PWM duty cycle.

  3. Could 24ms be too slow to refresh a motor control application?

What are your thoughts?



library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use work.Data_Sizes_Package.ALL;

entity PWM_Counter_and_Comparator is

 Generic (Max_Counter_Value : integer := 5000);
    
 Port (PWM_Comparison_Value : in std_logic_vector(27 downto 0);
       Clock : in std_logic;
       PID_Clock : in std_logic; 
       Run_Reset : in std_logic;
       PWM_Output : out std_logic
       );
end PWM_Counter_and_Comparator;

architecture Behavioral of PWM_Counter_and_Comparator is

signal Setpoint_RPM : integer range 0 to 5000 := 0;
signal Actual_RPM : integer range 0 to 5000 := 0;
signal Count_Value : integer range 0 to 5000 := 0;
signal PID_Comparison_Value : integer range 0 to 5000 := 0;
signal comp : integer range 0 to 5000 := 0;

constant Kp : integer := 2000;
constant Kd : integer := 0;
constant Ki : integer := 0;

begin

Setpoint_RPM <= conv_integer(unsigned(PWM_Comparison_Value(13 downto 0)));
Actual_RPM <= conv_integer(unsigned(PWM_Comparison_Value(27 downto 14))); 

Process(Clock , Run_Reset)

begin

    if(Run_Reset = '0') then
      Count_Value <= 0;
          
    elsif(rising_edge(Clock)) then
    
         if(Count_Value = Max_Counter_Value) then
            Count_Value <= 0;
              
         elsif(Run_Reset = '1') then
               Count_Value <= Count_Value + 1;
    
         end if;
         
     end if;

end Process;


Process(PID_Clock , Run_Reset)

variable Error_RPM :  integer range -5000 to 5000 := 0;
variable Last_Error_RPM : integer range -500000 to 500000 := 0;
variable Error_Sum:  integer range -500000 to 500000 := 0;
variable Error_Change:  integer range -500000 to 500000 := 0;
variable PID_Output:  integer range -500000 to 500000 := 0;

begin

    if(Run_Reset = '0') then
      Error_RPM := 0;
      Last_Error_RPM := 0;
      Error_Sum := 0;
      Error_Change := 0;
      PID_Output := 0;
          
    elsif((rising_edge(PID_Clock)) and Run_Reset = '1') then
    
      Error_RPM := Setpoint_RPM - Actual_RPM;
      
      Error_Sum := Error_Sum + Error_RPM;
      
      if(Error_Sum > 50000) then
         Error_Sum := 50000;
      
      elsif(Error_Sum < -50000) then
         Error_Sum := -50000;
      end if;
      
      Error_Change := Error_RPM - Last_Error_RPM;
      
      PID_Output := ( (Kp * Error_RPM) + (Ki * Error_Sum) + (Kd * Error_Change) / 100); 
      
      Last_Error_RPM := Error_RPM;
      
        if(PID_Output > Max_Counter_Value) then
            PID_Output := Max_Counter_Value;
              
         elsif(PID_Output < 0) then
           PID_Output := 0;
    
          end if;
      
      PID_Comparison_Value <= PID_Output;
      
      end if;
      
end Process;


Process(Clock , Run_Reset)

begin
    
    if((Count_Value < PID_Comparison_Value) and (Run_Reset = '1')) then
      PWM_Output <= '1';
         
    else 
      PWM_Output <= '0';
         
    end if;
    
end Process;

end Behavioral;


EDIT 1

This image below shows the concept of generating the PWM using a counter and comparison value.

*Not my image, taken from Google images.

PWM switching example

**EDIT 2 **

Here is a link to the L298N motor driver: L298N Motor Driver Board

\$\endgroup\$
12
  • 1
    \$\begingroup\$ if((Count_Value < PID_Comparison_Value) combined with "PWM_Output <= '1' or '0' makes no sense. you do switch something like chopping. \$\endgroup\$ Commented Apr 23, 2021 at 9:14
  • 1
    \$\begingroup\$ Ok, still not clear 100%, but enough to say that either it's wrong. If the output voltage is zero at 50% DT, then it has to output 50% when controller output is 0. \$\endgroup\$ Commented Apr 23, 2021 at 9:26
  • 1
    \$\begingroup\$ I don't know about your H-bridge. Either you have PWM(0-100%)+DIR or two channels PWM 0-100%, where 50% means zero voltage and left 100%, right 0% means full left,,... \$\endgroup\$ Commented Apr 23, 2021 at 9:51
  • 1
    \$\begingroup\$ Is it intentional that you only divide the D part of PID regulator by 100 when you are calculating PID_Output ? \$\endgroup\$
    – Rokta
    Commented Apr 23, 2021 at 11:53
  • 1
    \$\begingroup\$ Might be better to buffer the PWM value and reload at the beginning of the PWM cycle. \$\endgroup\$ Commented Apr 23, 2021 at 23:38

3 Answers 3

2
\$\begingroup\$

Does it matter that the PID output is updated every 24ms and fed into the counter when the counter is free running and not at 0?

Not an FPGA expert, but maybe it matters. If the processes are running parallel, then you have to make sure that the result of read counter gives a consistent data, ie the whole number.

Do I need to cap the PID output to a low threshold instead of 0? Say when the error is small like 10rpm, the PID output could be 20, which is a very small PWM duty cycle.

No. It could make more troubles than benefits, and this is not needed when you'll introduce the integration part.

Could 24ms be too slow to refresh a motor control application?

Not necessarily. It could be even too fast, it depends on system rise time. A too fast loop execution deteriorates the calculation of D-part makes it useless, but also for the I-part. If you sum a very tinny float number to a large float number it will treat it as zero, so the integrator won't work correctly at high update rate wit single precision float.

EDIT:

Image source enter image description here

Image source enter image description here

Note that the above PWM tutorial holds true, if the load is purely resistive, while it doesn't hold for motor or capacitive load. If you switch on the diagonal switches of the H-bridge you will charge the capacitor and turning the switches off won't discharge it, so you'll measure the full voltage regardless of the PWM ratio.

While the motor will "discharge" it's BEMF through the freewheeling diodes, still it won't give the exact ratio 50% PWM = 50% Vcc. To acheive this you would need to toggle both sides of the H-bridge, for example 50% 50% gives 0V output,...but the poor L298N can't be used for that. You can only set a PWM 0-100% ENA and use direction enable CW/CCW for IN1 and IN2.

If you have used a L298N without freewheeling diodes connected with motor, then the IC is RIP.

EDIT:

There is a PID algorithm in incremental form, aka velocity form, hence the name is derived from velocity controller, the one you want to build.

Transfer function of classic PID ...

$$u=K_p (\varepsilon + \dfrac{1}{T_i}\int\varepsilon\cdot dt \ + T_d\dfrac{d\varepsilon}{dt}) $$

... is differentiated and back integrated

$$u=K_p\int (\dfrac{d\varepsilon}{dt} + \dfrac{1}{T_i}\varepsilon\ + T_d\dfrac{d^2\varepsilon}{dt^2}) dt+C $$

This transaltes to incremental form when discretized:

U(k)= dU + U(k-1)

$$u(k)=K_p(\dfrac{d\varepsilon}{dt}+ \dfrac{1}{T_i}\varepsilon\ + T_d\dfrac{d^2\varepsilon}{dt^2}) + u(k-1)$$

The limiting and anti-windup is then implemented very simple

if u(k) > max
 u(k) = max
elseif u(k) < min
 u(k) = min

Note that the incremental form (velocity form) PID works properly only if you have the integrator working correctly, with disabled integrator it doesn't work correctly. So maybe you could implement a classic P-controller for ZN tuning purpose, then switch to incremental one when you determine the parameters.

\$\endgroup\$
6
  • \$\begingroup\$ The processes only write their data out after the process has occurred, so the counter should be valid I think. The motor in this system has no load so the system rise time I think it pretty quick. I think my problem is that the maximum error I can have is 4000rpm (when motor is not running). Wonder do I need to scale my PID comparator value to suit this? \$\endgroup\$
    – David777
    Commented Apr 23, 2021 at 9:48
  • \$\begingroup\$ Ah right, ok so the L298N PWM might be working, as I cannot measure it. Also I think the L298N that I am using has free wheeling diodes built into the board. I have provided a link to the board I have used in the question (Edit 2). Also do you think the issue is the maths in my PID code as the hardware seems ok? \$\endgroup\$
    – David777
    Commented Apr 23, 2021 at 11:11
  • 1
    \$\begingroup\$ @David777 Your PID algo is very bad, but it should drive a motor when only P control is used, then you can do ZN tuning and calculate the constants Ti and Td. Note that ZN method requires real values for Ti,Td (seconds). I can give you a hint when you will solve the first issues. \$\endgroup\$ Commented Apr 23, 2021 at 17:08
  • \$\begingroup\$ I have done some thinking and I see what you mean that my design is very bad. I was designing PWM 0-100% to equate to 0-4000rpm but it doesn't work like that. Is it a better idea to pick a PWM duty cycle range with motor speeds that range from say 2000rpm to 4000rpm. From testing, in my case this is between 75% (1850rpm) and 95% (4300rpm) duty cycle? \$\endgroup\$
    – David777
    Commented Apr 24, 2021 at 18:58
  • \$\begingroup\$ I have the PID algorithm sorted, now ready for tuning. \$\endgroup\$
    – David777
    Commented Apr 27, 2021 at 17:30
0
\$\begingroup\$

There are few errors in PID_Output calculations. First is that you only divide D part of PID output calculations by 100. Second one is more serious one. What is output of PID regulator when actual speed is above set speed (with ki and kd equal to 0)?

Result is erratic behavior if acceleration of motor is too fast or PID update is too slow. You will need to check if that is the case yourself.

\$\endgroup\$
2
  • \$\begingroup\$ I actually scaled down the constants and removed that divide by 100 from the calculation. I think I see the issue, you and Marko are saying that the output in my case when the actual speed is greater than the setpoint, the output goes to 0. I don't understand PID enough yet. Sounds like I need to scale the PID output as a 50% / 50% PWM signal for less than and greater than. How is this normally achieved in PID? \$\endgroup\$
    – David777
    Commented Apr 23, 2021 at 13:05
  • \$\begingroup\$ I also think the motor acceleration could be too fast for the PID loop \$\endgroup\$
    – David777
    Commented Apr 23, 2021 at 13:06
0
\$\begingroup\$

Regarding the PID control loop timing. I would say 24 ms is very slow for most motor control applications. The best way you can evaluate how fast your PID control loop needs to be is by calculating the mechanical time constant of your system. The mechanical components, such as the spinning mass, dictate how long transient events take. This time constant will give you an idea of how fast your motor will respond to a voltage step input.

Its a pretty simple equation that you can use. You just need to know three things about the system you are controlling: the inertia of your motor and mechanical system \${J}\$ {kg*m^2}, the motors resistance \$R\$ {Ohms}, and the back emf constant \$K\$ {Vs/rad} \begin{equation} \frac{JR}{K^2} \end{equation}

If 3x the mechanical time constant is smaller than your PID loop time then you will know you are in trouble, and your controller won't even control a transient event. I would say your probably want to be 10 times faster than your mechanical time constant.

\$\endgroup\$
1
  • \$\begingroup\$ I have changed the loop timing to 16ms, that is as fast as I can go and still read with 1 rpm precision. Any faster than 15ms and I lose accuracy in the rpm calculation. It didn't make much of a difference. I think I need 1ms minimum, or even faster. \$\endgroup\$
    – David777
    Commented Apr 26, 2021 at 17:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.