DAC module reference#include <dac.h>Makefile :
MODULES+=dac
The DAC (Digital to Analog Converter) is a peripheral that is able to output an analog voltage on a pin. This can be useful for an arbitrary waveform generator (AWG), to control the amplification of a transistor, ...
If you want to generate an arbitrary waveform, the DMA can be used to feed the DAC with the content of a buffer. For this, define an array of size n, fill it with the samples for one period of the signal you want to generate, and use setFrequency() to specify at which frequency the samples need to be sent (this is the frequency of your signal multiplied by the number of samples in each period). You can then use start(), isReloadEmpty() and reload() to start the conversion. In order to continuously repeat the buffer, simply set the repeat flag to true when calling start(). For example, a sinewave can be generated with the following code :
#include <carbide.h>
#include <core.h>
#include <dac.h>
// Precomputed sinewave with 128 samples
// Generated with the following Python snippet :
// [int(511 * math.sin(2*math.pi*x/128) + 512) for x in range(0,128)]
uint16_t sin[] = {512, 537, 562, 586, 611, 636, 660, 684, 707, 730,
752, 774, 795, 816, 836, 855, 873, 890, 907, 922, 936, 950, 962,
973, 984, 993, 1000, 1007, 1013, 1017, 1020, 1022, 1023, 1022,
1020, 1017, 1013, 1007, 1000, 993, 984, 973, 962, 950, 936, 922,
907, 890, 873, 855, 836, 816, 795, 774, 752, 730, 707, 684, 660,
636, 611, 586, 562, 537, 512, 486, 461, 437, 412, 387, 363, 339,
316, 293, 271, 249, 228, 207, 187, 168, 150, 133, 116, 101, 87,
73, 61, 50, 39, 30, 23, 16, 10, 6, 3, 1, 1, 1, 3, 6, 10, 16, 23,
30, 39, 50, 61, 73, 87, 101, 116, 133, 150, 168, 187, 207, 228,
249, 271, 293, 316, 339, 363, 387, 412, 437, 461, 486};
const int N = sizeof(sin) / 2; // Divided by two because each sample
// is two-byte long
int main() {
// Initialize the board
Carbide::init();
// Enable the DAC
DAC::enable();
// Set the sample frequency of the DAC to output a 1kHz sinewave
DAC::setFrequency(1000 * N);
// Start the DAC with the repeat flag enabled
DAC::start(sin, N, true);
while (true) {
// ...
}
}
There are two other peripherals that can be used for similar purposes :
Note : the DAC output is always referenced to the Vref pin (next to the Vcc pin on Carbide) : the output voltage varies linearly between 0V (value=0) and Vref (value=1023). Remember to provide a voltage on Vref in order to see an output on the DAC.
write() or start().value must be between 0 and 1023.buffer of length n. The DAC will use the DMA to output each of the samples in the buffer in order and at the frequency specified using setFrequency(). If repeat is set to true, the same samples will be repeated indefinitely, otherwise the DAC will stop at the last sample in the buffer (and keep the current value).start()) is finished.start() is called. Return true if the given frequency is within bounds.true if the current operation started with start() is finished.true if the DAC is ready to receive a new buffer with reload().interrupt=Interrupt::TRANSFER_FINISHED) or when the DAC is ready to receive a new buffer with reload() (interrupt=Interrupt::RELOAD_EMPTY).The DAC peripheral is pretty straightforward and doesn't allow much customization.
#ifndef _DAC_H_
#define _DAC_H_
#include <stdint.h>
#include "gpio.h"
// Digital to Analog Converter
// This module is used to generate an analog voltage on a pin
namespace DAC {
// Peripheral memory space base address
const uint32_t DAC_BASE = 0x4003C000;
// Registers addresses
const uint32_t OFFSET_CR = 0x00; // Control Register
const uint32_t OFFSET_MR = 0x04; // Mode Register
const uint32_t OFFSET_CDR = 0x08; // Conversion Data Register
const uint32_t OFFSET_IER = 0x0C; // Interrupt Enable Register
const uint32_t OFFSET_IDR = 0x10; // Interrupt Disable Register
const uint32_t OFFSET_IMR = 0x14; // Interrupt Mask Register
const uint32_t OFFSET_ISR = 0x18; // Interrupt Status Register
const uint32_t OFFSET_WPMR = 0xE4; // Write Protect Mode Register
const uint32_t OFFSET_WPSR = 0xE8; // Write Protect Status Register
// Constants
const uint32_t CR_SWRST = 0;
const uint32_t MR_TRGEN = 0;
const uint32_t MR_TRGSEL = 1;
const uint32_t MR_DACEN = 4;
const uint32_t MR_WORD = 5;
const uint32_t MR_STARTUP = 8;
const uint32_t MR_CLKDIV = 16;
const uint32_t WPMR_WPEN = 0;
const uint32_t WPMR_WPKEY = 0x444143 << 8;
enum class Interrupt {
RELOAD_EMPTY,
TRANSFER_FINISHED
};
// Module API
void enable();
void disable();
void write(uint16_t value); // 0 <= value <= 1023
void start(uint16_t* buffer, int n, bool repeat=false);
void reload(uint16_t* buffer, int n);
void stop();
bool setFrequency(unsigned long frequency);
bool isFinished();
bool isReloadEmpty();
void enableInterrupt(void (*handler)(), Interrupt interrupt=Interrupt::TRANSFER_FINISHED);
void disableInterrupt(Interrupt interrupt=Interrupt::TRANSFER_FINISHED);
void setPin(GPIO::Pin pin);
}
#endif
#include "dac.h"
#include "pm.h"
#include "dma.h"
namespace DAC {
// Package-dependant, defined in pins_sam4l_XX.cpp
extern struct GPIO::Pin PIN_VOUT;
bool _enabled = false;
int _dmaChannel = -1;
// Enable the DAC controller
void enable() {
// Enable the clock
PM::enablePeripheralClock(PM::CLK_DAC);
// Set the pin in peripheral mode
GPIO::enablePeripheral(PIN_VOUT);
// CR (Control Register) : issue a software reset
(*(volatile uint32_t*)(DAC_BASE + OFFSET_CR))
= 1 << CR_SWRST;
// WPMR (Write Protect Mode Register) : unlock the MR register
(*(volatile uint32_t*)(DAC_BASE + OFFSET_WPMR))
= WPMR_WPKEY
| 0 << WPMR_WPEN;
// MR (Mode Register) : setup and enable the DAC
(*(volatile uint32_t*)(DAC_BASE + OFFSET_MR))
= 0 << MR_TRGEN // TRGEN : internal trigger
| 1 << MR_DACEN // DACEN : enable DAC
| 0 << MR_WORD // WORD : half-word transfer
| 100 << MR_STARTUP // STARTUP : startup time ~12µs
| 0 << MR_CLKDIV; // CLKDIV : clock divider for internal trigger
// WPMR (Write Protect Mode Register) : lock the MR register
(*(volatile uint32_t*)(DAC_BASE + OFFSET_WPMR))
= WPMR_WPKEY
| 1 << WPMR_WPEN;
_dmaChannel = DMA::setupChannel(_dmaChannel, DMA::Device::DAC, DMA::Size::HALFWORD);
_enabled = true;
}
// Disable the DAC controller and free ressources
void disable() {
// WPMR (Write Protect Mode Register) : unlock the MR register
(*(volatile uint32_t*)(DAC_BASE + OFFSET_WPMR))
= WPMR_WPKEY
| 0 << WPMR_WPEN;
// MR (Mode Register) : disable the DAC
(*(volatile uint32_t*)(DAC_BASE + OFFSET_MR)) = 0;
// Free the pin
GPIO::disablePeripheral(PIN_VOUT);
// Disable the clock
PM::disablePeripheralClock(PM::CLK_DAC);
}
void write(uint16_t value) {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Cut high values
if (value > 1023) {
value = 1023;
}
// CDR (Conversion Data Register) : write new value
(*(volatile uint32_t*)(DAC_BASE + OFFSET_CDR)) = value;
}
void start(uint16_t* buffer, int n, bool repeat) {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Stop any previous operation
DMA::stopChannel(_dmaChannel);
if (!repeat) {
// Disable the ring mode of the DMA channel
DMA::disableRing(_dmaChannel);
// Start a new operation
DMA::startChannel(_dmaChannel, (uint32_t)buffer, n);
} else {
// Enable the ring mode of the DMA channel
DMA::enableRing(_dmaChannel);
// Start a new operation
DMA::startChannel(_dmaChannel, (uint32_t)buffer, n);
// Reload the DMA channel with the same buffer, which
// will be repeated indefinitely
DMA::reloadChannel(_dmaChannel, (uint32_t)buffer, n);
}
}
void reload(uint16_t* buffer, int n) {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Reload the DMA
DMA::reloadChannel(_dmaChannel, (uint32_t)buffer, n);
}
void stop() {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Stop the DMA
DMA::stopChannel(_dmaChannel);
}
bool isFinished() {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
return DMA::isFinished(_dmaChannel);
}
bool isReloadEmpty() {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
return DMA::isReloadEmpty(_dmaChannel);
}
bool setFrequency(unsigned long frequency) {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Compute the clock divider for the internal trigger
if (frequency == 0) {
return false;
}
unsigned long clkdiv = PM::getModuleClockFrequency(PM::CLK_DAC) / frequency;
if (clkdiv > 0xFFFF) {
return false;
}
// WPMR (Write Protect Mode Register) : unlock the MR register
(*(volatile uint32_t*)(DAC_BASE + OFFSET_WPMR))
= WPMR_WPKEY
| 0 << WPMR_WPEN;
// MR (Mode Register) : update CLKDIV
uint32_t mr = (*(volatile uint32_t*)(DAC_BASE + OFFSET_MR));
(*(volatile uint32_t*)(DAC_BASE + OFFSET_MR))
= (mr & 0x0000FFFF)
| clkdiv << MR_CLKDIV;
// WPMR (Write Protect Mode Register) : lock the MR register
(*(volatile uint32_t*)(DAC_BASE + OFFSET_WPMR))
= WPMR_WPKEY
| 1 << WPMR_WPEN;
return true;
}
void enableInterrupt(void (*handler)(), Interrupt interrupt) {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Enable the interrupt directly in the DMA
DMA::enableInterrupt(_dmaChannel, handler, static_cast<DMA::Interrupt>(interrupt));
}
void disableInterrupt(Interrupt interrupt) {
// Enable the DAC controller if it is not already
if (!_enabled) {
enable();
}
// Disable the interrupt in the DMA
DMA::disableInterrupt(_dmaChannel, static_cast<DMA::Interrupt>(interrupt));
}
void setPin(GPIO::Pin pin) {
PIN_VOUT = pin;
}
}