One of the components necessary for the end goal of controlling a motor is pulse width modulation (PWM). This component will be used to switch the FETs in the bridge to which the motor is connected. For a brushed DC motor two PWM components will be required; for a three phase brushless motor three.
This post covers the development of a single PWM component. Some of the features that I would like my PWM module to have include;
- A single counter is used to drive multiple PWM components.
- The resolution of the PWM is up to 14-bits.
- Higher PWM carrier frequency (at the cost of lower resolution) can be achieved by using a least significant subset of bits from the counter. High PWM carrier frequencies are useful for driving low inductance motors.
- Make the switch points of the PWM symmetric about the midpoint of the PWM carrier wave. The carrier midpoint would be the maximum value if a triangle carrier wave is used. I will be using a sawtooth carrier wave. The allows a simpler counter. I only need to count up and roll over compared to creating an up-down counter.
- Generate a carrier wave midpoint signal that can be used to trigger motor current sampling. Sampling the motor currents as far as possible from the PWM switch points reduced noise in the current measurement.
Start Vivado and create a new project.
Click “Next“. Make it an “RTL Project” and check “Do not specify source at this time“. Click “Next“. Select your part. In my case the “Cora Z7-10” board from Digilent. Click “Next“. Review the summary and click “Finish” when satisfied.
Under “PROJECT MANAGER” in the “Flow Navigator” panel click “Settings“. Choose your target language. I will be using VHDL.
Click “OK”. Under “IP INTEGRATOR” in the “Flow Navigator” panel click “Create Block Design“. Give your design a name and click “OK“.
In the “Diagram” panel add IP by clicking on the “+” button. Add a binary counter. Double click the counter block and configure it to an output width of 14 bits. In the control tab check “Clock Enable (CE)” and “Synchronous Clear (SCLR)“.
Click “OK“. The counter needs to be driven by a clock. Click the “+” button and add the “Clocking Wizard” IP. Double click the “Clock Wizard” block to open it. Under the “Board” tab set “CLK_IN1” to “sys_clock“. Check out the “Clocking Options” tab. I just left the default settings. Under the “Output Clocks” tab set the frequency of the “clk_out1” output. I selected 250 MHz. This gives a particular set of possible carrier frequencies depending on the number of bits of the counter that are used. Higher frequency PWM permits lower current ripple in loads (ex. motor coils) with low inductance. A higher clock frequency could probably used. If you select a higher clock frequency, verify that all timing constraints when the hardware builds. The “Output Clocks” tab is also where the optional inputs, outputs and reset are configured. My selections are as shown in the figure below.
Counter Bits | PWM Carrier Frequency (KHz) |
14 | 15.2588 |
13 | 30.5176 |
12 | 61.0352 |
11 | 122.070 |
Check out the “MMCM Settings” and “Summary” tabs. Click “OK” when satisfied.
Add ports to connect to “resetn” and “clk_in1“. Right click in the “Diagram” panel and select “Create Port …” from the menu to create each port. For the “resetn” port set the direction to “Input“, the type to “Reset” and the polarity to “Active Low“. For the port which will connect to “clk_in1“, name the port “sys_clock“. Set the direction to “Input“. Set the type to “Clock“. Specify the frequency as “125” MHz. You may need to select “Regenerate Layout“, from the right click menu, to get the diagram elements grouped.
From the “Project Manager” click “Add Sources“. Select “Add or create design sources“. Click “Next>” Click “Create File” and give it the name “PWM“. Click “OK” to add it to the local project. Click “Finish“. You could enter the port definitions in the “Define Module” dialog. Just click “OK“. We will edit the file directly. The file should appear in the hierarchy of the “Sources” tab. Open the file for editing. The source code file can be cloned from this repository.
The PWM module takes as inputs the PWM sawtooth carrier signal in the form of the count from the binary counter and the switch points to the left and right of the centre of the carrier signal. It uses the same clock as the counter. The reset is driven by the “locked” signal from the clocking wizard. The module is held is reset until the generated clock has stabilised. While in reset, the “ce” and “clr” signals are held at a level to keep the binary counter cleared and disabled. Outputs are the PWM signal itself (“pwm_out“) and a signal called “mid” which transitions from low to high at the centre of the carrier signal. This edge transition can be used to trigger current sensor acquisition and synchronise servo loops in motor control.
In the “Diagram” panel, right click and select “Add Module …“. Select the “PWM” module and click “OK“. The module with its input and output ports should appear in the diagram. Again you may need to “Regenerate Layout” to get the blocks to group. Add “mid” and “pwm_out” ports to the diagram. These are both “Output” ports of type “Other“. For now we will drive the left and right switch point signals of the PWM block with constants. Later these signals will be driven by a processor. Use the “+” button to add two “Constant” blocks. Double click to open and set the width to 13. Enter positive values for the switch points. I used 3636 for the left switch point and 4556 for the right switch point. This will give a a symmetric pulse about the centre of the carrier signal. With all signals connected, the block diagram should look something like the following figure.
Write click in the “Diagram” panel and select “Validate Design“. Everything should check out. With block diagram complete, right-click on the board file (.bd) in the sources hierarchy and click “Create HDL Wrapper …“. Let Vivado manage the wrapper and auto-update. Click “OK“.
Constraints need to be specified for the input and output ports in the diagram. Under the “PROJECT MANAGER” click “Add Sources“. Select “Add or create constraints” and click “Next>“. Select “Add Files“. Browse to where the you installed the Digilent Cora Z7 master constraint file. “Cora-Z7-10-Master.xdc” in my case. Select the file. Click “OK“. Make sure that “Copy constrains files into project” is checked. Click “Finish“. The constrains file should now appear under the “Constraints” section of the “Sources” tab hierarchy. Open the file to edit. Uncomment the two lines under the “##PL System Clock” comment. Change the name in braces to “sys_clk”.
## PL System Clock set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports { sys_clock }]; #IO_L13P_T2_MRCC_35 Sch=sysclk create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports { sys_clock }];#set
Select IO pins for the “resetn“, “mid” and “pwm_out” signals. I used the first three IO pins of the JA Pmod header. Uncomment the lines and edit to set the IO names.
## Pmod Header JA set_property -dict { PACKAGE_PIN Y18 IOSTANDARD LVCMOS33 } [get_ports { resetn }]; #IO_L17P_T2_34 Sch=ja_p[1] set_property -dict { PACKAGE_PIN Y19 IOSTANDARD LVCMOS33 } [get_ports { mid }]; #IO_L17N_T2_34 Sch=ja_n[1] set_property -dict { PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 } [get_ports { pwm_out }]; #IO_L7P_T1_34 Sch=ja_p[2]
The complete constraints file is in the repository.
Under the “PROJECT MANAGER” > “PROGRAM AND DEBUG” click “Generate Bitstream“. When the build finishes, open the implementation. Check the “Design Timing Summary” and verify that the timing constraints are satisfied. The “Project Summary” will show the device utilisation.
It remains to check if the design actually works as expected. One way to check the design is to simulate it. The simulator is a very useful tool for debugging. It allows you to inspect both external ports and internal signals. Under the “PROJECT MANAGER” click “Add Sources“. Select “Add or create simulation sources“. Click “Next>“. Specify the simulation set. I used the already created set “sim_1“. This entry can be used to create multiple simulation sets. Click “Create File“. Select the file type; VHDL in my case. Give the file a name. I used “PWM_Test1“. Select the file location local to the project. Click “OK“. Make sure that “Include all design sources for simulation” is checked. Click “Finish“. You can specify the IO ports with the “Define Module” dialog. Just click “OK“. We will edit the file directly.
The “PWM_Test1” file should appear under “Simulation Sources” in the “Sources” hierarchy. Open the file to edit. This file is a wrapper around the “System_1_wrapper” in the “sim_1” set. When done the file should look like this the one in the repository.
Once you save the file it should take its place at the top of the “sim_1” hierarchy.
Under the “PROJECT MANAGER” click “Settings“. Under “Project Settings” click “Simulation“. Review the various tabs. I left the default settings.
Click “OK“.
Under the “PROJECT MANAGER” click “Run Simulation -> Run Behavioral Simulation“. The IO pins appear by default in the simulation “Wave Window“. Add the counter. Select the counter under the “Scope” tab. In the “Objects” panel, right click “Q[13:0]” and add it to the wave window. In the wave window right click on “Q[13:0]” and set “Waveform Style” to “Analog“. Run the simulation for the interval specified in the simulation toolbar by clicking the right arrow button with the “(T)” subscript. Next to the simulation time. I ran for three 100 microsecond intervals for a total of 300 microseconds. The waveforms should show up in the wave window. They didn’t initially on by Ubuntu 16.04 computer. If I toggle from “Default Layout” to ” Simulation Layout” and back, using the upper right selection box, the waveforms magically appear. I need to do this each time I run a simulation segment. I don’t have this problem on Windows. You can verify the sawtooth carrier with a 66.67 microsecond period (15 KHz). The “mid” signal transitions high at the midpoint of the carrier. The “pwm_out” signal has the expected pulse width and is centred about the carrier signal midpoint.
Finally lets program the board and view the outputs on oscilloscope. Under the “PROGRAM MANAGER -> PROGRAM AND DEBUG” click “Open Hardware Manager“. If it doesn’t immediately connect click “auto connect“. Click “Program Device“. Verify that the bit file is correct. Verify that “Enable end of startup check” is checked. Click “Program“.
Connect your scope. I am using a Digilent Analog Discovery2 to monitor the “mid” and the “pwm_out” signals. The Analog Discovery 2 is a compact, inexpensive device; perfect for the job. I highly recommend it.
Here are the output signals from the board.
In useful system the switch points will not be constants. In the next post a processor will be added to set the switch points.