3
\$\begingroup\$

I am trying to control 13 different servos with my Spartan3 FPGA. For that, I need 13 independent PWM signals generated by my FPGA. The PWM module that I am using works perfectly fine when only one output pin is controlled (instantiated as follows).

  SERVO_1 : entity work.Servo(Behavioral)
  port map (
    clk => clk,
    rst => rst,
    position => s_pos_1, -- integer from 0 to 255 (256 steps from 0° to 180°)
     pwm => pwm_1 -- output pin of FPGA
  );

But as soon as I add more servo entities, the output of the PWM pins behave unexpectedly. Some are constantly high, low, or are constant in their pulse width (frozen). Overall, the behaviour is (for me) kind of random (talking about actual hardware behavior). I instantiate the entities as follows:

  SERVO_1 : entity work.Servo(Behavioral)
  port map (
    clk => clk,
    rst => rst,
    position => s_pos_1,
     pwm => pwm_1
  );
  
  SERVO_2 : entity work.Servo(Behavioral)
  port map (
    clk => clk,
    rst => rst,
    position => s_pos_2,
     pwm => pwm_2
  );
  
  SERVO_3 : entity work.Servo(Behavioral)
  port map (
    clk => clk,
    rst => rst,
    position => s_pos_3,
     pwm => pwm_3
  );

-- ... and so on

What I checked so far

  • The simulation works as expected, every pwm output behaves correctly.
  • I can safely say that the problem has nothing to do with the servos/current/power supply/drivers.
  • I am checking the outputs on a scope all the time.
  • Checked that there are no signals without default value
  • Changed multiplication in VHDL to accessing a ROM with the needed values (which were multiplicated before, I thought that maybe the multiplication part caused the problems)
  • Checked the logic utilization of my FPGA, which is rather low
  • Checked the timing report of ISE Webpack, no errors

Besides that I get no errors during synthesis.

Logic utilization

There are two infos regarding timing:

  • Timing:3390 - This architecture does not support a default System Jitter value, please add SYSTEM_JITTER constraint to the UCF to modify the Clock Uncertainty calculation.
  • Timing:3389 - This architecture does not support 'Discrete Jitter' and 'Phase Error' calculations, these terms will be zero in the Clock Uncertainty calculation. Please make appropriate modification to SYSTEM_JITTER to account for the unsupported Discrete Jitter and Phase Error

Source code of Servo.vhd:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.round;


entity Servo is
  port (
    clk : in std_logic;
    rst : in std_logic;
    position : in integer range 0 to 255;
    pwm : out std_logic
  );
end Servo;


architecture Behavioral of Servo is

  constant clk_hz : real := 50.0e6; -- 50 MHz Spartan3 
  constant pulse_hz : real := 50.0; -- 50 Hz PWM-Signal
  constant min_pulse_us : real := 500.0; -- 0° pulse width 
  constant max_pulse_us : real := 2500.0; -- 180° pulse width
  constant step_bits : positive := 8; -- 0 to 255
  constant step_count : positive := 2**step_bits;
  
    -- Number of clock cycles in Microseconds
  function cycles_per_us (us_count : real) return integer is
  begin
    return integer(round(clk_hz / 1.0e6 * us_count));
  end function;
  
  constant min_count : integer := cycles_per_us(min_pulse_us);  -- 0° in clock cycles
  constant max_count : integer := cycles_per_us(max_pulse_us);  -- 180° in clock cycles
  constant min_max_range_us : real := max_pulse_us - min_pulse_us;
  constant step_us : real := min_max_range_us / real(step_count - 1);
  constant cycles_per_step : positive := cycles_per_us(step_us); -- clock cycles per step (1/256)
  
  constant counter_max : integer := integer(round(clk_hz / pulse_hz)) - 1; -- clock cycles for 20 ms (50 Hz)
  signal counter : integer range 0 to counter_max;

  signal duty_cycle : integer range 0 to max_count;

begin

--  position <= to_integer(rom_data);
  
  COUNTER_PROC : process(clk) -- basic counter
  begin
    if rising_edge(clk) then
      if rst = '1' then
        counter <= 0;

      else
        if counter < counter_max then
          counter <= counter + 1;
        else
          counter <= 0;
        end if;

      end if;
    end if;
  end process;
  
  PWM_PROC : process(clk) -- Setting the pwm pin according to duty_cycle
  begin
    if rising_edge(clk) then
      if rst = '1' then
        pwm <= '0';

      else
        pwm <= '0';

        if counter < duty_cycle then
          pwm <= '1';
        end if;

      end if;
    end if;
  end process;
  
  DUTY_CYCLE_PROC : process(clk) -- Setting the duty_cycle
  begin
    if rising_edge(clk) then
      if rst = '1' then
        duty_cycle <= min_count;

      else
        duty_cycle <= position * cycles_per_step + min_count;

      end if;
    end if;
  end process;


end Behavioral;

'clk' is connected to the 50 MHz oscillator pin. 'rst' is a signal and comes from a reset module that synchronizes everything with 3 FFs.

\$\endgroup\$
4
  • \$\begingroup\$ 1) Check the PWM outputs on a scope. 2) Check the servo power supply voltage also on the scope. You haven't told us the PSU current rating or the servo max operating current, so that's the first cause to eliminate. 3) Where are all these s_pos_x signals coming from? If all positions are connected to s_pos_1, do you see all 13 servos operanitg smoothly in unison? \$\endgroup\$
    – user16324
    Commented Jan 31, 2022 at 18:30
  • \$\begingroup\$ 1) I am checking the outputs on a scope all the time. 2) I can safely say that the problem has nothing to do with the servos/current/power supply/driver. 3) The signals are instantiated in my main.vhd where a basic process sets s_pos_x values. No, but if I instead use constant values the signals are generated correctly. \$\endgroup\$
    – Ahmad
    Commented Jan 31, 2022 at 18:47
  • \$\begingroup\$ (a) Not your question but just a point on clear style: use an 'else' for your 'pwm <= '0'. It's easier to follow if every branch leads to a leaf on the tree, rather than a default value into a signal then an 'if' to change it. Helps keep the assignments to a close together in large designs. (b) How are deriving 'clk'? Is it from a pin or PLL or is it from a counter that divides down a higher frequency? (c) Where does 'rst' come from? (d) Please edit this info' into the body of your question (not in comments, not at the question end under 'Edit'). \$\endgroup\$
    – TonyM
    Commented Jan 31, 2022 at 19:34
  • \$\begingroup\$ (a) Thanks! (b) 'clk' is connected to the 50 MHz oscillator pin. (c) 'rst' is a signal and comes from a reset module that synchronizes everything with 3 FFs. (d) Going to do that! \$\endgroup\$
    – Ahmad
    Commented Jan 31, 2022 at 21:52

1 Answer 1

1
\$\begingroup\$

After one week of trying I figured out that the problem was caused by the JTAG programmer. I was using the JTAG programmer from Digilent with its Adept software. After switching to a Xilinx programmer and using iMPACT everything worked fine. I cannot really say why the Digilent programmer caused those problems.

\$\endgroup\$

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.