Use a SAM4S Microcontroller to Create a DMA Digital-to-Analog Conversion
2017-10-13 | By All About Circuits
License: See Original Project Arduino
Use a SAM4S Microcontroller to Create a DMA Digital-to-Analog Conversion: The Timer/Counter
Courtesy of All About Circuits
In this project, we use the SAM4S timer/counter peripheral to precisely control the DAC’s sampling rate.
Required Items
• SAM4S Xplained Pro Evaluation Kit
• Atmel Studio
A Real DAC
In another article in this series, we analyze the performance of a pulse-width-modulation-based digital-to-analog converter. From that article, we already know that PWM DACs are far from impressive. However, the main reason the PWM DAC is still available is in part because (as mentioned in another article on the same topic) a lot of microcontrollers do not incorporate dedicated DAC hardware. The SAM4S series devices provide two digitally controlled analog outputs.
In this project, our main goal is generating a sine wave using the SAM4S DAC module. This task by itself is quite nontrivial thanks to the somewhat labyrinthine relationship between the SAM4S datasheet, the Atmel Software Framework (ASF), the ASF documentation, and the microcontroller’s actual hardware. However, we will go a step further, feeding data to the DAC via direct memory access (DMA) rather than CPU instructions.
Though looking at sine waves on an oscilloscope is rather enjoyable, this project is not really an end in itself. Think of it more as: 1) a vehicle to explore important SAM4S capabilities, namely DMA, precision timing/counting, and integrated digital-to-analog conversion; and 2) a foundation for projects you may come across that require synchronous, CPU-efficient digital-to-analog conversion. Such projects could include synthesizing audio signals or generating baseband waveforms for a software-defined radio.
Free-Running Versus Triggered
You can use the DAC simply by writing a value to the DAC’s conversion-data register, commonly referred to as free-running mode. The analog output voltage updates as soon as you write the data (or, more precisely, 25 DAC clock ticks after you write the data). This works for some applications. However, to accurately synthesize the periodic signals (such as a sinusoid), all the output-voltage updates must be separated by identical intervals. For this, we will use triggered mode, in which a write to the conversion-data register does not actually modify the output voltage until the DAC hardware is triggered by a separate timing signal. Page 1124 of the SAM4S datasheet refers to this trigger as "external," which can be a bit misleading—usually, "external" means "external to the chip," but in this case it means "external to the DAC module." Below are the trigger-signal options:
We will use the TIO signal from timer/counter channel 1, because this signal can conveniently be probed via terminal 1 in the row of through-holes labeled "PIOD INTERFACE":
As seen in the image above, this terminal 1 has been connected to pin A15, and the SAM4S datasheet (page 52) tells us we can drive signal TIOA1 on PA15 if we connect this pin to peripheral B:
The "A" after TIO shows us that this is the first of two output signals (TIOA and TIOB) each timer/counter channel generates; the "1" after "TIOA" refers to channel 1 of the timer/counter module, which includes three independent, identical channels.
Capture vs. Waveform
You can configure a timer/counter channel for capture mode or waveform mode. In capture mode, TIOA and TIOB are inputs; a signal that is connected to TIOA or TIOB will be used to tell the timer/counter hardware to store the current counter value in another separate register. This functionality is useful for measuring the delay between two pulses or pulse width. However, for this project, we want to generate a timing signal rather than measure one, so we need waveform mode. Waveform mode refers to the timer/counter’s ability to generate a variety of digital waveforms through the three compare registers and different counting modes. Luckily, our task is relatively straightforward —we just need a square wave with frequency equal to our desired DAC sample rate. Because of this, we will use the up-counting-only mode, which requires only one compare register.
First, we need to choose the clock that will drive the counter register. Here are our options:
We will configure the microcontroller for a master clock (MCK) frequency of 96 MHz. The desired DAC sample rate is 1 MHz, and MCK/8 (i.e., 12 MHz) is a good choice for generating a 1 MHz DAC trigger, mainly because the required divider value is an integer.
With the frequency of the clock source for the counter chosen, we then need to choose the proper value for "Register C," abbreviated RC. This register can be used to reset the counter when the value held in RC is reached. See the image below.
So, setting the WAVESEL bits in the "TC Channel Mode" register to binary 10 and then loading the proper compare value into RC gives us a counter that resets and starts counting up from 0 each time the counter value equals RC. The last thing to tell to tell the timer/counter channel is that it will need to modify the logic level of TIOA every time this match occurs. This can be done by means of the ACPC field in the following channel mode register:
We used the toggle option. The following diagram conveys the relationship between the RC compare value and the waveform generated on TIOA.
Firmware
Now that all the timer/counter work is done, it’s time for us to translate all this into code. For this we will use (as usual) the ASF. First, we will add all the ASF modules we need for the entire project:
All modules underlined in green are the modules that need to be added (the others are included automatically by Atmel Studio).
Next we have to include two preprocessor definitions:
//Port I/O
#define TC0_TIOA_CH1 IOPORT_CREATE_PIN(PIOA, 15)
//Timer configuration
#define TC0_CHANNEL1 1
The first attaches a name to pin A15, and the second allows us to refer to timer/counter channel 1 with TC0_CHANNEL1 instead of simply using the numeral 1.
Next we the peripheral clock for the timer/counter enabled. This is a detail that can be a bit confusing, so take note of the following discussion—it could help save some time and frustration down the road. The confusion centers on the question of which peripheral ID to use with the pmc_enable_periph_clk() function. In our opinion, the obvious answer is to use ID_TC0, because we work with channel 1 in timer/counter module 0. The reality can be seen in the timer/counter channel-initialization function, tc_init()—the first argument is TC0 or TC1 (referring to timer/counter module 0 or module 1). The second argument is the channel number. It seems that corresponding the peripheral ID to the peripheral module itself would be most natural, rather than corresponding with the channels in the module. However, as you may have guessed, this is not the case. It turns out that the peripheral clock is enabled or disabled for individual timer channels. To make is even more confusing, the SAM4S datasheet (page 50) refers to these channels as follows:
So with the peripheral identifiers, we have timer/counter channels 0 through 5, and elsewhere we have timer/counter module 0 channel 0 through 2 and timer/counter module 1 channel 0 through 2. This is all to encourage you to be aware of the fact that you need to enable the peripheral clock for each timer/counter channel you are using, with peripheral ID channels 0–2 corresponding to module 0 channels 0–2, and peripheral ID channels 3–5 corresponding to module 1 channels 0–2.
After we enable the peripheral clock via pmc_enable_periph_clk(ID_TC1), we can configure the timer/counter channel with:
tc_init(TC0, TC0_CHANNEL1,
TC_CMR_TCCLKS_TIMER_CLOCK2 //MCK/8
| TC_CMR_WAVE //waveform mode
| TC_CMR_WAVSEL_UP_RC //count up, reset on RC match
| TC_CMR_ACPC_TOGGLE //toggle TIOA on RC match
);
With the tc_init() function, we will first specify the timer/counter module and the channel within the module, and then use the logical OR operator along with ASF preprocessor definitions (found in "tc.h") to specify the proper values for whichever fields need to be modified in the channel mode register. As seen in the comments in the code excerpt above, we are configuring the channel for
1. MCK/8 clock source,
2. waveform mode,
3. up-counting-only mode with automatic reset when the counter value equals RC, and
4. TIOA toggle when the counter value equals RC.
The diagram above lets us know that the value in RC should be 6 to produce a 1 MHz clock:
fTIOA=12 MHz2×RC ? RC=12 MHz2×1 MHz=6fTIOA=12 MHz2×RC ? RC=12 MHz2×1 MHz=6
tc_write_rc(TC0, TC0_CHANNEL1, 6);
Now all that is left if is enabling the timer/counter channel with a call to tc_start():
tc_start(TC0, TC0_CHANNEL1);
Results and Conclusion
This link can be used to download the source and project files for this stage of the project.
A scope capture of the resulting waveform looks like this:
With our sample-rate signal exactly how we want it, we can fire up the DAC!
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum