how to use defined values like DACC_MR

Hi all,

in the arduino distribution there is deep down an include file dacc_component.h
(...../arduino-1.5.1/hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include)
which defines a lot of addresses, bit fields and registers of the DACC

but I fail to see how to include this file. it is part of CMSIS and the examples are about gcc makefiles.
not sketches.

reason is that I would like to start by having total control and low level access, and only then
delve into the pre-fabbed function calls that seem to be the norm.

does anybody have a suggestion ?

well, at least I found a way to use the ArduinoIDE and still be able to follow the ARM Datasheet to the letter.
This enables me to discover the DUE hardware in some more detail.

frankly I totally got lost in the standard stuff for the DUE. there seems a libsam, something called CMSIS,
.h files all over the place. And no explanation (at least where I looked for it) on how to apply these or make any sense of it.
maybe i'm just too shallow in the gcc department, but that's me.

so I skipped all that and wrote a small test sketch reading the CHIP ID values, using no libraries or includes whatsoever.
the values used for addresses and offsets come directly from the datasheet.
hopefully this helps others to get a grip on the DUE too.

/*
CHIPID own test
on Arduino Due = SAM3x8e
All data from datasheet
*/
#define CHIPiD_BASE_REG_ADDR 0x400E0940 
#define CHIPiD_CIDR_OFFSET 0x0
#define CHIPiD_EXID_OFFSET 0x4

#define CHIPiD_CIDR   (*((volatile unsigned long *) (CHIPiD_BASE_REG_ADDR + CHIPiD_CIDR_OFFSET)))
#define CHIPiD_EXID   (*((volatile unsigned long *) (CHIPiD_BASE_REG_ADDR + CHIPiD_EXID_OFFSET)))

void setup() {
Serial.begin(9600);  
unsigned long test = 0x0;
test = CHIPiD_CIDR;
Serial.println(test);
}

void loop() {}

this answers with the value 677251680
which is the decimal version of 0x285E0A60,
as shown in table 30-1 of the datasheet as the value for
a SAM3x8e ARM chip.

now I will try to attack the DAC in the same way, using direct access to it's registers.

this code uses the DAC in the simplest way I could think of.
DMA transfers and interrupts are not used.
it generates a sawtooth wave.

/*
dACC own test
on Arduino Due = SAM3x8e
All data from datasheet
constant names contain one lower case letter to avoid clashes with
other (unwanted) define's
*/

#define dACC_ID 38

#define dACC_BASE_REG_ADDR 0x400C8000

#define dACC_CR_OFFSET 0x00
#define dACC_MR_OFFSET 0x04
#define dACC_CHER_OFFSET 0x10
#define dACC_CHDR_OFFSET 0x14
#define dACC_CHSR_OFFSET 0x18
#define dACC_CDR_OFFSET 0x20
#define dACC_IER_OFFSET 0x24
#define dACC_IDR_OFFSET 0x28
#define dACC_IMR_OFFSET 0x2C
#define dACC_ISR_OFFSET 0x30
#define dACC_ACR_OFFSET 0x94
#define dACC_WPMR_OFFSET 0xE4
#define dACC_WPSR_OFFSET 0xE8

#define dACC_CR   (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_CR_OFFSET) ))
#define dACC_MR   (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_MR_OFFSET) ))
#define dACC_CHER (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_CHER_OFFSET) ))
#define dACC_CHDR (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_CHDR_OFFSET) ))
#define dACC_CHSR (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_CHSR_OFFSET) ))
#define dACC_CDR  (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_CDR_OFFSET) ))
#define dACC_IER  (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_IER_OFFSET) ))
#define dACC_IDR  (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_IDR_OFFSET) ))
#define dACC_IMR  (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_IMR_OFFSET) ))
#define dACC_ISR  (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_ISR_OFFSET) ))
#define dACC_ACR  (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_ACR_OFFSET) ))
#define dACC_WPMR (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_WPMR_OFFSET) ))
#define dACC_WPSR (*((volatile unsigned long *) (dACC_BASE_REG_ADDR + dACC_WPSR_OFFSET) ))

//dACC_WPMR bitfields
#define WPEN_mSK    0x00000001
// dACC_MR bitfields
#define  STARTUP_mSK 0x3F000000
#define  USERSEL_mSK 0x00030000
#define      TAG_mSK 0x00100000
#define     MAXS_mSK 0x00200000
#define  REFRESH_mSK 0x0000FF00
#define     WORD_mSK 0x00000010
#define    SLEEP_mSK 0x00000020
#define FASTWKUP_mSK 0x00000040
#define    TRGEN_mSK 0x00000001
#define   TRGSEL_mSK 0x0000000E
// dACC_CHER,CHDR,CHSR bitfields
#define      CH0_mSK 0x00000001
#define      CH1_mSK 0x00000002
// dACC_IER,IDR,IMR,ISR bitfields
#define    TXRDY_mSK 0x00000001
#define      EOC_mSK 0x00000001
#define    ENDTX_mSK 0x00000001
#define   TXBUFE_mSK 0x00000001
// dACC_ACR bitfields
#define  IBCTLDACCORE_mSK 0x00000200
#define      IBCTLCH0_mSK 0x00000003
#define      IBCTLCH1_mSK 0x0000000C

unsigned long loopcounter = 0x00000000;

void setup() {
unsigned long test = 0x00000000;  
Serial.begin(9600);  
/*
enable power ; use PMC because we focus on DAC now, not PMC module.
somehow these functions work without includes. 
*/
pmc_enable_periph_clk(dACC_ID);

/* 
if using interrupts, program interrupt controller first 
skip for now
*/

/* clock :  dACC clock = MCK /2 
skip for now, accept whatever the clock is
*/

/* check if protection is off
dACC_WPMR.WPEN == 0  
*/
test = dACC_WPMR & WPEN_mSK;
if (test > 0) Serial.println("dACC_WPMR.WPEN is on; config of dACC registers will fail");

/****************************************/
/* config dACC_ModeRegister (MR)        */
/****************************************/

/* startup time must be set 
set dACC_MR.STARTUP field
see table for values and associated number of DACclock ticks
go for max = 63 = 4032 periods of DAC clock (= MCK/2 ?)
*/
dACC_MR = (dACC_MR & (~STARTUP_mSK)) | (STARTUP_mSK & 0xFFFFFFFF) ; 
Serial.print("dACC_MR.STARTUP set to 63, MR = ");
Serial.println(dACC_MR,HEX);

/* refresh time must be chosen
set dACC_MR.REFRESH field
period = 1024*REFRESH/dACC_CLK should be smaller than 20 usec
just a guess because dACC_CLK not known: set to 15
*/
dACC_MR = (dACC_MR & (~REFRESH_mSK)) | (REFRESH_mSK & 0xFFFF0FFF); 
Serial.print("dACC_MR.REFRESH set to 15, MR = ");
Serial.println(dACC_MR,HEX);

/* select freerunning or external trigger
dACC_MR.TRGEN == 0 means free running
*/
dACC_MR = (dACC_MR & (~TRGEN_mSK)); 

/* select (ext?) trigger source
dACC_MR.TRGSEL == 0..7 = ext(1), timer/counters(3) or pwm(2), 
rest is reserved
can be skipped because freerunning (TRGEN=0)
*/

/* set word/halfword data delivery
dACC_MR.WORD == 0 means half word mode
*/
dACC_MR = dACC_MR & (~WORD_mSK) ;

/* sleep mode selection
use dACC_MR.SLEEP == 0 for continuous power on
otherwise things get complicated
*/
dACC_MR = dACC_MR & (~SLEEP_mSK);

/* set use of TAG for channel select
dACC_MR.TAG = 0 means no TAG mode
*/
dACC_MR = dACC_MR & (~TAG_mSK); 

/* set user selected channel 
which is only sensible when TAG mode = off
dACC_MR.USER_SEL = 0 means channel 0, 1 means channel 1 
value 2 and 3 reserved/undefined 
*/
dACC_MR = dACC_MR & (~USERSEL_mSK);

Serial.print("dACC_MR set to :");
Serial.println(dACC_MR,HEX);

/*********************************************/
/* config dACC_ChannelEnableRegister(CHER)   */
/*********************************************/
/* dACC_CHER.CH1=1 enables CH1,idem for CH0  */
/* enable CH0, write-only register        !  */
dACC_CHER = CH0_mSK ;

/*********************************************/
/* config dACC_ChannelDisableRegister(CHDR)  */
/*********************************************/
/* dACC_CHDR.CH1=1 disables CH1,idem for CH0 */
/* disable CH1, write only register !        */
dACC_CHDR = CH1_mSK ;


/********************************************/
/* dACC_ChannelStatusRegister(CHSR) r/o     */
/********************************************/
/* if dACC_CHSR.CH1=1 then CH1 is enabled   */
/* idem for CH0                             */
Serial.print("Channel status is :");
Serial.println(dACC_CHSR,HEX);

/********************************************/
/* dACC_InterruptEnableRegister(IER)        */
/* set no interrupts                        */

/********************************************/
/* dACC_InterruptDisableRegister(IDR)        */
/* disable all interrupts                   */
dACC_IDR = TXBUFE_mSK & ENDTX_mSK & EOC_mSK & TXRDY_mSK ;

/********************************************/
/* dACC_InterruptMaskRegister(IMR)        */

/********************************************/
/* dACC_InterruptStatusRegister(ISR)        */
Serial.print("dACC_ISR set to :");
Serial.println(dACC_ISR,HEX);

/********************************************/
/* dACC_AnalogCurrentRegister(ACR)          */
/* set IBCTLDACCORE = 0b10,IBCLTLCH0 = 0b10 */
/* datasheet does not show other values     */
dACC_ACR = 0x00000202 ;
Serial.print("dACC_ACR set to :");
Serial.println(dACC_ACR,HEX);
}
// end of setup()

void loop() {
/* simple mode : if dACC_ISR.TXRDY write dACC Conversion Data Register(CDR)
use of fifo is automatic ? if full, then TXRDY goes 0 
reading dACC_ISR should clear EOC flag */

if ((dACC_ISR & TXRDY_mSK) > 0x00000000) {
  dACC_CDR = (loopcounter++ & 0x00000FFF); //| ((loopcounter++ & 0x00000FFF) << 16)  ; 
}

} // end of loop

I now got the DAC to work via the PDC, still without any need for interrupts.
pity I cannot post > 9500 char, see attachment

lessons learned :

  • when using half-word mode, for some reason the value in TCR and TNCR must be double the actual value.
    in full word mode, stuff works as expected.
  • on the scope, the signal returns to zero (0.7 volts in fact) after each value being output.
    this is strange, and was not the case when writing to the DAC without PDC.
    tweaking the REFRESH parameter shows no difference
  • using Serial.println() messes up the DAC output, apparently the DAC must wait until Serial.println() is done.

Time to go study other datasheets, maybe the other SAM3 datasheets contain more info.

ownDMADACC.ino (5.27 KB)

point number 2 above seems solved.
I think the DAC, when used with the PDC, cannot work in half-word mode.

instead you are forced to feed it two 12 bit values in one 32 bits word.
this is the only way to fill the DAC ffo with useful data.

that way length is consistent, but feeding data is awkward.
it is perhaps simplest to cast an array of (unsigned) integer to
an array of half-as-many unsigned long.

I created an array of longs, filled with intX + (intXplus1 << 16) and got a clean sine wave
without the "return to zero" behaviour.

Hello Raalst. You literally made my day by posting this. I have been trying to get the Due's DAC to work in freerun mode for a long time now without success. Thanks for posting the code. I know this is an old post of yours but I was wondering if I could pick your brain a little.

I was analyzing the code you posted on Mar 2, 2013 and I have a few questions.

1- In line #79 you used the command "pmc_enable_periph_clk(dACC_ID)" I didn't know it was required to turn on the DACC_CLK manually. Can you post any info about where you found this info. As far as I know this was not mentioned in the data sheet for the SAM3X.

2- You accessed the DAC by building up from the base address, but I had previously had some luck using commands like ADC->ADC_MR = 0x08 when addressing the ADC but not with the DAC. Do you know of any reason this wouldn't work. (i.e. commands like DACC->DACC_MR = 0x03000002 etc.)

3- Why did you set the startup time to it's maximum value?

4- The DAC is in freerun mode so why did you need to read the EOC flag?

I am currently working on a project which requires the due's ADC and DAC to be in freerun mode because analogWrite is way too slow. When it comes to programming I am a newbie but eager to learn. Thanks in advance for any help you can give.