Project

Pulse-Width Modulation with the SAM4S Xplained Pro

April 14, 2016 by Robert Keim

This article covers everything you need to know to generate pulse-width-modulated signals with Atmel’s SAM4S Xplained Pro development board.

This article covers everything you need to know to generate pulse-width-modulated signals with Atmel’s SAM4S Xplained Pro development board.

Supporting Information

Required Hardware/Software

Before a PWM DAC, We Need PWM

This article is intended to build upon a recent two-article series that explored 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

  1. you can’t or really don’t want to use an external DAC or a microcontroller with an integrated DAC,
  2. the PWM hardware provides DAC resolution that is adequate for your application,
  3. your digital supply voltage is accurate and predictable, and
  4. you can achieve acceptable output ripple and settling time with nothing more than a basic RC low-pass filter.

We have covered the theoretical side of this topic, and now it’s time to put theory into practice and then take a look at some real-life results. For this, we will use the SAM4S Xplained Pro “evaluation kit,” which is the term Atmel uses for something that I would be inclined to call a “development board.” We will also use the PROTO1 and I/O1 extension boards, both of which are included in the SAM4S Xplained Pro starter kit. Actually, though, for this project the extension boards are a convenience, not a necessity; if all you have is the SAM4S board, you can rig up low-pass circuitry with a breadboard or something.

Before we can turn a PWM signal into a digitally controlled analog voltage, we need to generate a PWM signal, and 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 somewhat poorly documented, and there are various little details you need to get right before you actually see the expected PWM waveform on the expected pin. Thus, we will devote the rest of this article to mastering the PWM interface, and in the next article, we will incorporate the DAC functionality.

And by the way, don’t forget to add the PWM ASF module to the project before you attempt to use the ASF’s PWM functionality. The procedure for adding an ASF module is discussed in the “Step by Step” section of Intro to Project Development with the Atmel SAM4S Xplained Pro.

Connecting Signal to Pin

The microcontroller on the SAM4S board supports four separate PWM channels; each channel has complementary outputs, so all together we have up to eight PWM signals. The SAM4S Xplained Pro user guide tells us that two of these signals are included in the standard extension header pinout:

 

 

In this project we don’t need complementary signals, so we will use only 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.

 

 

 

If we look 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 that drives this signal is PA23:

 

 

So how do we tell the microcontroller to drive the PWM signal on pin PA23? For this 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 IOPORT_CREATE_PIN macro allows us to attach the name “PWM_DAC” to pin 23 in parallel input/output controller A. Now we have to configure the PWM_DAC pin so that it is used for a peripheral function rather than as general-purpose I/O, and furthermore we 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) of the SAM4S series datasheet tells us which of the four we need:

 

Now we can properly formulate a call to pio_configure_pin():

pio_configure_pin(PWM_DAC, PIO_TYPE_PIO_PERIPH_B);

PWM Clock

The next thing we need to do is 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), but we only need one, so we will disable clock B. The PWM clocks are derived from the microcontroller’s peripheral clock. We don’t need to discuss the details of the clocking hardware because the ASF handles the low-level configuration for us; if you’re interested, 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);

Now we 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()
};

Here we have set clock A to 1 MHZ; clock B is disabled. We supply the master clock frequency by making a call to sysclk_get_cpu_hz(). To apply this configuration, we use the pwm_init() function:

pwm_init(PWM, &PWMDAC_clock_config);

PWM Options

We’re getting close—we just need to configure the PWM channel itself and then enable the 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 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 obvious difference between these settings is that changing from left-aligned mode to 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 description that is actually helpful: 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. Thus, 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 let the member identifier fool you: this does not define the duty cycle. 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, again in units of clock ticks. In the above example 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

Let’s look at some oscope measurements for different PWM configurations. We’ll start with the configuration given in the above code excerpts: clock source = 1 MHz, polarity = PWM_HIGH, alignment = PWM_ALIGN_LEFT, ul_period = 20, ul_duty = 5. Note that the relevant timing characteristics are displayed on the right side of the scope captures.

 

 

If we keep everything the same but switch to polarity = PWM_LOW, we get this:

 

 

If we go back to PWM_HIGH then change to alignment = PWM_ALIGN_CENTER, we see the following:

 

 

The 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 use the following link to download the source and project files, and all of the “main.c” code is given after the link. In the next article we will use our newfound PWM expertise to explore PWM digital-to-analog conversion.

PWM_with_SAM4S_Xplained_Pro.zip

#include 

#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);
}

Next Article in Series:

 

Give this project a try for yourself! Get the BOM.

3 Comments
  • S
    stupid April 28, 2016

    hi pardon my ignorance…
    are the following commands reserved or variables?
    if reserved where can i find more info?

    IOPORT_CREATE_PIN()
    pio_configure_pin()
    pmc_enable_periph_clk()
    pwm_init()
    pmc_enable_periph_clk()

    Like. Reply
    • C
      CarryWise March 16, 2021
      Neither. Those aren't reserved "keywords" nor are they variables. Those are function calls, defined in a library referenced somewhere in the include statements. This is basic "C" language programming, you might want to just take a course on that if you're interested in understanding code examples like above. However, the understanding the core language still doesn't teach you about any given "library" which is provided to work with some set of hardware. In those cases, you still need to learn about the library and what function calls you need to make, in what order, and what you have to pass to them in order to make the hardware do what you want.
      Like. Reply
  • K
    kajota December 28, 2018

    One issue that I ran into with this is that the PWM would not come on if I called pwm_channel_disable. Once I removed that, the PWM would would work but every time I want to disable and then re-enable the PWM I have to call the pwm_channel_init again. I haven’t researched this enough to figure out which register specifically needs to be set.

    The part about ul_period and ul_duty variables was extremely useful. Thanks for the description. I know I would have been hung up on that for a while.

    Like. Reply