element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
FPGA
  • Technologies
  • More
FPGA
Blog Learning AMD Zynq: a project to generate a set of PWM signals. 2 - add overall control block, delay and pulse signal
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
FPGA requires membership for participation - click to join
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 4 May 2023 8:15 PM Date Created
  • Views 4113 views
  • Likes 8 likes
  • Comments 49 comments
  • pynq-z2
  • amdzynqultrasoundpulse
  • zynq
  • fpga
  • vivado
  • vhdl
  • pynq
  • spartan
Related
Recommended

Learning AMD Zynq: a project to generate a set of PWM signals. 2 - add overall control block, delay and pulse signal

Jan Cumps
Jan Cumps
4 May 2023
Learning AMD Zynq: a project to generate a set of PWM signals. 2 - add overall control block, delay and pulse signal

Continuation of  Learning AMD Zynq: a project to generate a set of PWM signals. 1 - problem statement and possible approach .

 yepe has a goal to create a set of signals for an ultrasone pulse generator.

image

Status after Post 1

In Post 1, we got the PWM and PWMN signals, and a first stab at the PULSE signal. Let's start at that stable point here, and try to add

  • the initial delay between PRF falling edge and the start of the pulse train
  • a PULSE signal that starts and ends exactly when the pulse train starts and ends.

Here's the design from the 1st blog:

image

The clocking wizard generates a 320 MHz clock. High enough to give the PWM signal a decent granularity for the dead times (2.5 ns steps).
The PWM generator generates 2 complementary signals, PWM and PWMN, with configurable deadband.
The PWM generator has an enable pin. When that pin is low, it will set PWM low and PWMN high, else it outputs the train of pulses

image image

Goal of this post:

  • Build a control block that will tell the PWM when to generate pulses (control its enable)
  • let that block also control the PULSE signal
  • establish the configurable? delay at the start
  • maybe handle PRF?

What this session will not do: handle GATE

Same as in post 1, the action will be in the comments. It's a collaborative exercise.

Update 4/5/2023:

Possible signal controller design (partly - not all states and signals implemented)

image

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

package PulsePckg is

    component signal_controller is
        generic (
            start_delay_resolution : integer := 6;  -- bit width of the counter used delaying pulse train start
            train_length           : integer := 7;  -- bit width of the PWM burst train counter
            gate_delay_resolution  : integer := 6;  -- bit width of the counter used delaying the gate signal start 
            counter_resolution     : integer := 7   -- big enought o hold the largest of above
        );
        port (
            n_reset_i     : in   std_logic;                                                -- async reset
            clk_i         : in   std_logic;                                                -- Input clock.
            start_delay_i : in   std_logic_vector (start_delay_resolution - 1 downto 0);   -- Duty-cycle input.
            train_length_i: in   std_logic_vector (train_length - 1 downto 0);             -- how many PWMs in a burst train.
            prime_i       : in   std_logic;                                                -- allow cycle start
            fire_i        : in   std_logic;                                                -- start a cycle
            prf_o         : out  std_logic;                                                -- PRF
            enable_o      : out  std_logic;                                                -- enable pwm
            pulse_o       : out  std_logic;                                                -- PULSE
            gate_o        : out  std_logic;                                                -- GATE
            leds_o        : out std_logic_vector (3 downto 0)
        );
    end component;

end package;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;


entity signal_controller is
    generic (
        train_length           : integer := 7;  -- bit width of the PWM burst train counter
        start_delay_resolution : integer := 6;  -- bit width of the counter used delaying pulse train start
        gate_delay_resolution  : integer := 6;  -- bit width of the counter used delaying the gate signal start 
        counter_resolution     : integer := 7   -- big enought o hold the largest of above
    );
    port (
        n_reset_i     : in   std_logic;                                                -- async reset
        clk_i         : in   std_logic;                                                -- Input clock.
        start_delay_i : in   std_logic_vector (start_delay_resolution - 1 downto 0);   -- Duty-cycle input.
        train_length_i: in   std_logic_vector (train_length - 1 downto 0);             -- how many PWMs in a burst train.
        prime_i       : in   std_logic;                                                -- allow cycle start
        fire_i        : in   std_logic;                                                -- start a cycle
        prf_o         : out  std_logic;                                                -- PRF
        enable_o      : out  std_logic;                                                -- enable pwm
        pulse_o       : out  std_logic;                                                -- PULSE
        gate_o        : out  std_logic;                                                -- GATE
        leds_o        : out std_logic_vector (3 downto 0)
    );
end signal_controller;

architecture arch of signal_controller is
    type states is (ready, delay, pulse, gate_delay, gate, done );
    signal counter : integer range 0 to 2**counter_resolution-1;
    signal state  : states;

begin


    clocked: process(clk_i)

    begin

        if rising_edge(clk_i) then
            -- sync reset
            if n_reset_i = '0' then
                counter <= 0;
                state <= ready;
                -- reset output states
                prf_o <= '1';
                enable_o <= '0';
                pulse_o <= '0';
                gate_o <= '0';
                leds_o <= (others => '0');
            else
                counter <= counter + 1;
                -- default output states
                prf_o <= '1';
                enable_o <= '0';
                pulse_o <= '0';
                gate_o <= '0';
                leds_o <= (others => '0');

                case State is

                    when ready =>  -- start a pulse group as soon as the fire bit is set
                        leds_o <= (0 => '1', others => '0');
                        -- check if the caller has reset the prime bit
                        if prime_i = '0' then
                            -- if yes, start if the fire bit is set.
                            if fire_i = '1' then
                                counter <= 0;
                                state <= delay;
                            end if;
                        end if;

                    when delay =>
                        leds_o <= (0 => '0', 1 => '1', others => '0');
                        prf_o <= '0';
                        if counter >= to_integer(unsigned(start_delay_i)) - 1 then
                            counter <= 0;
                            enable_o <= '1';
                            state <= pulse;
                        end if;

                    when pulse =>
                        leds_o <= (0 => '1', 1 => '1', others => '0');
                        enable_o <= '1';
                        prf_o <= '0';
                        if counter < to_integer(unsigned(train_length_i)) - 1 then  -- let the pulse change sync with last edge of PWM
                            pulse_o <= '1';
                        elsif counter = to_integer(unsigned(train_length_i)) - 1 then
                            pulse_o <= '1';
                            counter <= 0;
                            state <= gate_delay;
                        end if;

                    when gate_delay =>
                        leds_o <= (0 => '0', 1 => '0', 2 => '1', others => '0');
                        state <= gate;  -- todo implement

                    when gate =>
                        leds_o <= (0 => '1', 1 => '0', 2 => '1', others => '0');
                        state <= done;  -- todo implement

                    when done =>
                        leds_o <= (0 => '1', 1 => '1', 2 => '1', others => '0');
                        -- check if the caller has reset the fire bit
                        if fire_i = '0' then
                            -- if yes, prime if the prime_bit is set.
                            if prime_i = '1' then
                                counter <= 0;
                                state <= ready;
                            end if;
                        end if;

                end case;

            end if; -- sync reset
        end if; -- rising_edge

    end process clocked;

end architecture;

warning: the train length should always result in full PWM cycles. If you disable the PWM module before it had the chance to generate a full cycle, both PWM and PWMN will switch at the same time and not respect the deadband.

Block diagram:

image

Capture:

image

Jupyter:

image

Jupyter source:

{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PWM with generic dead band and duty cycle resolution\n",
    "\n",
    "https://community.element14.com/technologies/fpga-group/b/blog/posts/learning-xilinx-zynq-a-school-project-to-generate-a-set-of-pwm-signals-1---problem-statement-and-possible-approach\n",
    "\n",
    "pwm_ultrasound_pulser.xpr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq import Overlay\n",
    "ol=Overlay(\"pwm_ultrasound_pulser.bit\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq import MMIO\n",
    "RANGE = 8 # Number of bytes; 8/4 = 2x 32-bit locations which is all we need for this example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "duty_address = ol.ip_dict['axi_gpio_duty']['phys_addr']\n",
    "duty_register = MMIO(duty_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "duty_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [],
   "source": [
    "band_address = ol.ip_dict['axi_gpio_band']['phys_addr']\n",
    "band_register = MMIO(band_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "band_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "flags_address = ol.ip_dict['axi_gpio_flags']['phys_addr']\n",
    "flags_register = MMIO(flags_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "flags_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [],
   "source": [
    "start_delay_address = ol.ip_dict['axi_gpio_start_delay']['phys_addr']\n",
    "start_delay_register = MMIO(start_delay_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "start_delay_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_length_address = ol.ip_dict['axi_gpio_train_length']['phys_addr']\n",
    "train_length_register = MMIO(train_length_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "train_length_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [],
   "source": [
    "def duty(duty):\n",
    "    duty_register.write(0x00, duty)\n",
    "    \n",
    "def band(band):\n",
    "    band_register.write(0x00, band)\n",
    "    \n",
    "def dutypct(duty):\n",
    "    duty_register.write(0x00, round((0x1F*2)/(100/duty)))\n",
    "    \n",
    "def fire():\n",
    "    flags_register.write(0x00, 1) # bit 0\n",
    "    flags_register.write(0x00, 0)\n",
    "\n",
    "def prime():\n",
    "    flags_register.write(0x00, 2) # bit 1\n",
    "    flags_register.write(0x00, 0)\n",
    "    \n",
    "def startdelay(startdelay):\n",
    "    start_delay_register.write(0x0, startdelay);\n",
    "\n",
    "def trainlength(trainlength):\n",
    "    train_length_register.write(0x0, trainlength);\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "duty(0x1F)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "dutypct(50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [],
   "source": [
    "band(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "startdelay(20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [],
   "source": [
    "trainlength(125)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [],
   "source": [
    "prime()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "fire()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [],
   "source": [
    "trainlength(125)\n",
    "prime()\n",
    "fire()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

Scope capture (PWM, PWMN, PULSE, PRF)

0 dead time:
image

2 dead time:
image

Compare to requirement:

image

Example with longer initial delay and a longer pulse train, 5 pulses of dead band. The FPGA design is the same. This is done by writing different values to the registers:
image

Study of the edges. You see the PWM and PULSE signal, 0 dead band.
image

A few example settings that generate complete pulses. The first two generate 2 pulses, with a short start delay. The second 2 have a longer start delay and generate 5 pulses. The last one is retriggered in a software loop. I've also played with the deadbands.
image

Vivado project and Jupyter notebook are attached. They are for version 2022.1 and PYNQ-Z2 board
pwm_ultrasound_pulser_and_jupyter_20230506.zip

link to all posts.

  • Sign in to reply

Top Comments

  • Jan Cumps
    Jan Cumps over 2 years ago +1
    test of using a control block, that generates a PULSE signal and controls the pulse train:
  • Jan Cumps
    Jan Cumps over 2 years ago in reply to yepe +1
    The Pulse, Gate and PMW(n) can all be specified as number of CLK pulses. Pulse and PWM(n) have to be active atthe same time' for the same time. Gate hasto be active for the same time, but after a delay…
  • Jan Cumps
    Jan Cumps over 2 years ago in reply to yepe +1
    After running Synthesis, open the synthesis design. Then, you should have access to a IO window (not at my pc at the moment, but I think it's under the Windows menu. There you have to define to what…
  • javagoza
    javagoza over 1 year ago in reply to guillengap

    Good luck! It looks very interesting.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • guillengap
    guillengap over 1 year ago in reply to javagoza

    My idea is to experiment! But now I think I have more resources to experiment with: 1) TTC; 2) AXI_Timer, and 3) TTC+AXI_Timer.

    I already built the scale for drones, and I got 3V mini motors and 4 propellers. I am now in the experimentation phase.

    Kind regards

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza over 1 year ago in reply to guillengap

    It all depends on how many motors you want to control. If 3 is enough for you, the solution that Adam Taylor gives to use the three timers is the one that will use the least resources and the most straightforward.

    If, on the other hand, you need more motors, I think that your project was related to the stabilization of a drone, then you are going to need another solution, if possible in PL. The AXI_timer IP is my favorite, but don't rule out taking a look at Digilent's IP PWM module which have a lot of support from the community.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • guillengap
    guillengap over 1 year ago in reply to Jan Cumps

    You are right!  javagoza 's answer convinced me!

    Kind regards

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to guillengap

    If you are looking for a AXI / Vivado / Vitis solution, I thing that the answer that javagoza posted, is excellent. I don't have that much experience with the Xvendor-specific blocks and Vitis integration.

    When I need a PWM, I usually take a clock wizard to generate a good frequency* that I can derive from.
    Then use HDL (vhdl in my case) to create the PWM from it. 
    I think that javagoza 's approach will deliver better ( and more in-phase) results than my approach.

    * I try to take a frequency that is high enough to give a good resolution - when I need to do duty cycle changes, or include deadband 

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube