Even if you don't want to use ASF (I don't even use Atmel Studio) you are still best off including and compiling with the Atmel packs that define all of the registers and pins, similar to the "io.h" files used with AVR. All example code here is using definitions/structures from the SAMD21 pack. ARM is significantly different than AVR and requires a lot more steps to get something started, but once the clocks are taken care of, it's similar to many other microcontrollers in that you are just setting register values.
Here's the samd21 datasheet, for reference.
There are numerous steps involved, and I can add more details to each upon request.
1. Configure System Clocks
This is extremely involved, but necessary to do anything...
2. Enable Power to modules (not all of these are necessary, just an example)
PM->APBBMASK.reg =
PM_APBBMASK_DMAC |
PM_APBBMASK_PORT |
PM_APBBMASK_NVMCTRL;
PM->APBCMASK.reg =
PM_APBCMASK_ADC | // This is for the ADC
PM_APBCMASK_DAC | // This is for the DAC
PM_APBCMASK_TC3; // This is for Timer/Counter 3
3. Configure ports and pins
DAC for SAMD21 is PA02, Port MUX B
4. Attach Generic Clock to the module
#define DAC_GCLKGEN 3 // examples using GLCK Generator 3
GCLK->CLKCTRL.reg = (DAC_GCLKGEN << 8) | GCLK_CLKCTRL_ID_DAC | GCLK_CLKCTRL_CLKEN;
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
5. Configure the module registers
DAC->CTRLB.reg = 0x40; // use AVCC as the reference
6. Create some macros/functions
#define DAC_VREF 3.3f // this is the value of AVCC, the reference voltage
#define DAC_MASK 0x3FF // DAC is 10 bit, so this is the maximum value
#define DAC_FACTOR (DAC_MASK / DAC_VREF) // For converting to voltage
#define DAC_ON() DAC->CTRLB.reg = 0x43
#define DAC_OFF() DAC->CTRLB.reg = 0x40
/**
* Set the DAC output register with the value.
*
* @param value 10-bit value to be set
*/
static void
DAC_setRaw(const uint16_t value) {
DAC_OFF();
DAC->CTRLA.reg = DAC_CTRLA_ENABLE;
while (1 == DAC->STATUS.bit.SYNCBUSY);
DAC->DATA.reg = DAC_MASK & value;
while (1 == DAC->STATUS.bit.SYNCBUSY);
DAC_ON();
}
/**
* Set the DAC output register with the value.
* @note the 8-bit value is left shifted; the 4 lsb are 0.
*
* @param value 8-bit value to be set
*/
static inline void
DAC_setRaw8(const uint8_t value) {
DAC_setRaw((uint16_t)(value) << 4);
}
/**
* Set the DAC to be the specified potential.
*
* @param potential the potential to be set
*/
static inline void
DAC_set(const float potential) {
DAC_setRaw((uint16_t)(potential * DAC_FACTOR);
}
7. Use the functions in your code
I don't know what your "inputs" actually are or where they are coming from, but you can use the DAC_setRaw() and DAC_setRaw8() functions to set using a raw number or the DAC_set() function to set using a voltage floating point number.
There is also an ADC input internally connected to the DAC you can use to check if it's actually running, but that's a story for another day...