Generating PWM Signals with the SAM4S Xplained Pro
2017-07-07 | By All About Circuits
License: See Original Project
Courtesy of All About Circuits
Learn how to get started generating PWM signals with the Atmel SAM4S Xplained Pro development board.
Supporting Information
Intro to Project Development with the Atmel SAM4S Xplained Pro
Turn Your PWM into a DAC
Low-Pass Filter a PWM Signal into an Analog Voltage
Required Hardware/Software
Before a PWM DAC, We Need PWM
This article will build on some articles that explore the technique whereby digital-to-analog conversion is achieved by low-pass filtering a pulse-width-modulated signal. (Click here for Part 1, and here for Part 2.) The general conclusion from these articles is that a PWM DAC is worth considering if
- You don’t want to or can’t use a microcontroller or an external DAC with an integrated DAC,
- The PWM hardware provides DAC resolution that is adequate for your application,
- Your digital supply voltage is accurate and predictable, and
- You can achieve acceptable output ripple and settling time with nothing more than a basic RC low-pass filter.
The articles mentioned above cover the theoretical side of this topic. Now we can put theory into practice and take a look at some real-life results. For this, we’ll be using SAM4S Xplained Pro “evaluation kit” which is the term Atmel uses, but I would be inclined to call a “development board.” We’ll also be using the PROTO1 and I/O1 extension boards. Both of these boards are included in the SAM4S Xplained Pro starter kit. However, the extension boards are a more of a convenience for this project, not a necessity. If you only have the SAM4S board, you can rig up low-pass circuitry with a breadboard or something.
Before we can turn PWM signals into digitally controlled analog voltages, we need to generate a PWM signal. In my opinion, this is not as straightforward as one might expect. It seems to me that the PWM portion of the Atmel Software Framework (ASF) is not documented as well as it should be. There are various small details you’ll need to get right before you can actually see the expected PWM waveform on the expected pin. For that reason, I’ve devoted the rest of this article to mastering the PWM interface, and we will incorporate the DAC functionality into a later article.
Also, don’t forget to add the PWM ASF module to your project before attempting to use the ASF’s PWM functionality. You can how to add the PWM ASF module in the “Step by Step” section of Intro to Project Development with the Atmel SAM4S Xplained Pro.
Connecting Signal to Pin
The SAM4S board’s microcontroller supports four separate PWM channels; each with complementary outputs, making up to eight PWM signals altogether. The SAM4S Xplained Pro user guide tells us that two of these signals are included in the standard extension header pinout:
We won’t need complementary signals in this project, so we’ll only be using pin 7. If you’re using the PROTO1 extension board, this pin is conveniently labeled “PWM+”; if not, you can easily find pin number 7 on the EXT1 header.
Looking at the pinout for EXT1, we see that pin 7 gives us the positive (i.e., not inverted) signal from PWM channel 0, and the port pin driving this signal is PA23:
How do we tell our microcontroller to drive the PWM signal on pin PA23? We need two things: a preprocessor definition that assigns a name to the pin, and a call to pio_configure_pin(). At this point we have enough information for the first of these:
#define PWM_DAC IOPORT_CREATE_PIN(PIOA, 23)
The macro IOPORT_CREATE_PIN will allow us to attach the name “PWM_DAC” to pin 23 in parallel input/output controller A. Next, we need to configure the PWM_DAC pin so that it is used for a peripheral function rather than as a general-purpose I/O. Furthermore, we’ll need to configure it for the correct peripheral line. Each pin can be connected to up to four peripheral signals; these are referred to as peripheral A, B, C, and D. Table 39-2 (page 955) in the SAM4S series datasheet shows us which of the four we need:
Now we are able to properly formulate a call to pio_configure_pin():
pio_configure_pin(PWM_DAC, PIO_TYPE_PIO_PERIPH_B);
PWM Clock
Next, we’ll need to configure the clock that will drive the PWM hardware. The PWM module supports two clocks derived from programmable dividers (referred to as A and B). We will only need one, so we’ll disable clock B. The PWM clocks are derived from the microcontroller’s peripheral clock. We won’t need to cover the details of the clocking hardware because the ASF handles all of the low-level configuration for us; if you are interested in those details, though, you can refer to page 957 in the SAM4S series datasheet.
The first step in the clock configuration process is to enable the peripheral clock for the PWM hardware:
pmc_enable_periph_clk(ID_PWM);
Next, we’ll use a “pwm_clock_t” structure to set the clock speeds; this structure is defined as follows in the “pwm.h” header file:
/** Input parameters when initializing PWM */
typedef struct {
/** Frequency of clock A in Hz (set 0 to turn it off) */
uint32_t ul_clka;
/** Frequency of clock B in Hz (set 0 to turn it off) */
uint32_t ul_clkb;
/** Frequency of master clock in Hz */
uint32_t ul_mck;
} pwm_clock_t;
Here is an example configuration:
pwm_clock_t PWMDAC_clock_config =
{
.ul_clka = 1000000,
.ul_clkb = 0,
.ul_mck = sysclk_get_cpu_hz()
};
Above shows how we set clock A to 1 MHZ; clock B is disabled. We supplied the master clock frequency by making a call to sysclk_get_cpu_hz(). We use the pwm_init() function to apply this configuration:
pwm_init(PWM, &PWMDAC_clock_config);
PWM Options
Now we’re getting close—we’ll just need to configure the PWM channel itself and then enable that channel. The ASF makes PWM configuration fairly convenient: a structure of type “pwm_channel_t” gives us access to the various options, and then we pass the address of this structure to the pwm_channel_init() function. First I’ll give you the code, then we’ll discuss the details.
pwm_channel_instance.channel = PWM_CHANNEL_0;
pwm_channel_instance.ul_prescaler = PWM_CMR_CPRE_CLKA;
pwm_channel_instance.polarity = PWM_HIGH;
pwm_channel_instance.alignment = PWM_ALIGN_LEFT;
pwm_channel_instance.ul_period = 20;
pwm_channel_instance.ul_duty = 5;
- pwm_channel_instance.channel: We are using channel 0.
- pwm_channel_instance.ul_prescaler: We will need to select the clock source; PWM_CMR_CPRE_CLKA corresponds to clock A.
- pwm_channel_instance.polarity: If this is set to PWM_HIGH, the “ul_duty” value defines the width of the logic-high portion of the signal (in other words, logic high is the active state); if it’s set to PWM_LOW, the “ul_duty” value defines the width of the logic-low portion of the signal.
- pwm_channel_instance.alignment: For details on left-aligned mode vs. center-aligned mode, refer to pages 960–961 in the SAM4S series datasheet. In general you want left-aligned mode; center-aligned mode is useful when you need two non-overlapping PWM waveforms. The most noticeable difference between these settings is that changing from the left-aligned mode to the center-aligned mode will cause the PWM period and the active-state pulse width to increase by a factor of 2.
- pwm_channel_instance.ul_period: The ASF documentation describes this member of the structure as the “period cycle value,” and that’s as much information as you will readily find regarding what to do with ul_period. Here’s a helpful description from the datasheet: ul_period defines the duration of the PWM cycle in units of clock ticks. In this example, we selected clock A as the clock source for our PWM channel, and we configured clock A for a frequency of 1 MHz. This makes the unit for ul_period is 1 µs clock ticks. The example code given above has pwm_channel_instance.ul_period = 20, which means that the PWM period is 20 × 1 µs = 20 µs.
- pwm_channel_instance.ul_duty: Don’t be fooled by the member identifier: it does not define the duty cycle. The Duty cycle is the active-state pulse width divided by the period, usually expressed as a percentage. In contrast, ul_duty is the duration of the pulse, which is once again, in units of clock ticks. In the example shown above, we have pwm_channel_instance.ul_duty = 5; thus, the active-state pulse width will be 5 × 1 µs = 5 µs, which corresponds to a duty cycle of (5 µs)/(20 µs) = 25%.
- Now we apply the configuration with a call to pwm_channel_init(), and after that we are ready to enable the channel with pwm_channel_enable().
Results
Now we can look at some oscope measurements for different PWM configurations. Let’s start with the configuration given in the code excerpts above: clock source = 1 MHz, polarity = PWM_HIGH, alignment = PWM_ALIGN_LEFT, ul_period = 20, ul_duty = 5. You’ll notice that the relevant timing characteristics are displayed on the right side of the scope captures.
If we keep the settings the same but switch to polarity = PWM_LOW, we get this:
If we go back to PWM_HIGH and change to alignment = PWM_ALIGN_CENTER, we will see the following:
This next waveform is back to left-aligned mode, and I increased ul_duty to 10:
And here I increased ul_period to 30:
And finally, here is the waveform if I keep everything the same (ul_duty = 10, ul_period = 30) but increase the clock A frequency to 10 MHz.
You can download the source and project files on All About Circuits, and all of the “main.c” code is shown below. In the next article we will use our newly found PWM expertise to explore PWM digital-to-analog conversion.
#include <asf.h>
#define PWM_DAC IOPORT_CREATE_PIN(PIOA, 23)
pwm_channel_t pwm_channel_instance;
int main (void)
{
//clock configuration and initialization
sysclk_init();
/*Disable the watchdog timer and configure/initialize
port pins connected to various components incorporated
into the SAM4S Xplained development platform, e.g., the
NAND flash, the OLED interface, the LEDs, the SW0 pushbutton.*/
board_init();
//connect peripheral B to pin A23
pio_configure_pin(PWM_DAC, PIO_TYPE_PIO_PERIPH_B);
//enable the peripheral clock for the PWM hardware
pmc_enable_periph_clk(ID_PWM);
//disable the channel until it is properly configured
pwm_channel_disable(PWM, PWM_CHANNEL_0);
//PWM clock configuration
pwm_clock_t PWMDAC_clock_config =
{
.ul_clka = 1000000,
.ul_clkb = 0,
.ul_mck = sysclk_get_cpu_hz()
};
//apply the clock configuration
pwm_init(PWM, &PWMDAC_clock_config);
//see the article for details
pwm_channel_instance.channel = PWM_CHANNEL_0;
pwm_channel_instance.ul_prescaler = PWM_CMR_CPRE_CLKA;
pwm_channel_instance.polarity = PWM_HIGH;
pwm_channel_instance.alignment = PWM_ALIGN_LEFT;
pwm_channel_instance.ul_period = 20;
pwm_channel_instance.ul_duty = 5;
//apply the channel configuration
pwm_channel_init(PWM, &pwm_channel_instance);
//configuration is complete, so enable the channel
pwm_channel_enable(PWM, PWM_CHANNEL_0);
while(1);
}
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum