DACC: Digital to Analog Converter Controller

The ATSAM4S has 2 12 bit DACC channels that run up to the frequency of the main clock / 2. Once data is put into the REG_DACC_CDR (convert data register) the DACC takes 25 dacc clock cycles to turn that 12 bit number into an analog voltage on the selected channel. There are two ways to select the channel. By default, the USER_SEL field of the DACC_MR is used or if the TAG bit of the DACC_MR is 1 then the channel is selected by adding bits to the DACC_CDR Register. In this mode, the two bits, DACC_CDR[13:12], which are otherwise unused, are employed to select the channel in the same way as with the USER_SEL field. Finally, if the WORD field is set, the two bits, DACC_CDR[13:12] are used for channel selection of the first data and the two bits, DACC_CDR[29:28] for channel selection of the second data.

The DACC can generate an interrupt when a channel is finished converting the data into an analog signal by setting the EOC bit in the DACC_ISR.

When the DACC is read to receive data the TxRdy bit will be set in the DACC_ISR. Do not write to the channel if this bit is not set otherwise the FIFO buffer will become corrupt.

Setup

First we must enable the DACC clock in the PMC.

REG_PMC_PCER0 |= PMC_PCER0_PID30;

If we are going to use interrupts then we have to enable it in the NVIC.

NVIC_EnableIRQ(DACC_IRQn);

By enabling a channel in the DACC_CHER register it also gives control of the pin to the DACC and removes control from the PIO.

REG_DACC_CHER |= DACC_CHER_CH0;

The word transfer bit can set whether or not the full 32 bits will be used from the DACC_CDR or just the lower 16 bits.

Once the clock is started, a channel is enabled we can send data to be sent to the DACC by putting data into the DACC_CDR register.

Example

This is a simple example. It will just output 1.654v on channel 0. We could get fancy and include arm_math.h and create a sinwave, but I dont have an oscilloscope to make sure I did it correctly.

#include "sam.h"
#include "basic_uart.h"

void clock_init(){
    REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN;
    while (!(REG_PMC_SR & PMC_SR_MOSCXTS));
    REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL;
    REG_PMC_MCKR |= MC_MCKR_CSS_MAIN_CLK;
    while (!(REG_PMC_SR & PMC_SR_MCKRDY));
    REG_PMC_MCKR |= PMC_MCKR_PRES_CLK_1;
    while (!(REG_PMC_SR & PMC_SR_MCKRDY));
}

void dacc_init(){
    //enable dacc peripheral clock
    REG_PMC_PCER0 |= PMC_PCER0_PID30;
    //enable dacc interrupts
    //NVIC_EnableIRQ(DACC_IRQn);
	
    //Most settings we want are 0 already
    //tag mode on - use bits in conversion data register to select output channel
    //REG_DACC_MR |= DACC_MR_TAG | DACC_MR_ONE;
    //set half word transfer (16bit)
    //REG_DACC_MR &= ~DACC_MR_WORD;	
    //run in free mode (no external trigger)
    //REG_DACC_MR &= ~DACC_MR_TRGEN;
		
    //enable channel 0
    //setting this bit also gives dacc control of the pin and disables the PIO on this pin
    REG_DACC_CHER |= DACC_CHER_CH0;	
}

void dacc_write(uint16_t data, uint8_t channel){ 
    if (channel == 0){		
        REG_DACC_MR |= DACC_MR_USER_SEL(0);		
    }
    else if (channel == 1){	
        REG_DACC_MR |= DACC_MR_USER_SEL(1);		
    }
    //put data into convert data register
    EG_DACC_CDR |= data;
    //after 25 dacc clock cycles the analog voltage will output on the channel pin
}

int main(void)
{ 	
    SystemInit();
    clock_init();
    UART_Init();
    dacc_init();
    
    uint32_t status = 0;
    while (1)
    {	
        status = REG_DACC_ISR;
        if ((status & DACC_ISR_TXRDY)>=1){			
            //DACC ready to receive data
            dacc_write(2048,0); //1.654v output (3.3v advref)		
        }		
    }	
}