NOOB Question On Interface Code For MCP4921 12bit DAC?

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello Everyone,

I'm fairly new to C and AVR's in general. So, the learning curve is steep and any assistance is appreciated. :lol:

Anyways, for starters I'm working with the ATmega168 aka "Arduino".

I basically just started the project and the concept is to get a signal from a guitar and use the on board ADC coming in from PORTA->PIN0 and perform some audio processing and then run the output to an external MCP4921 12bit DAC.

I have written the following function and was wondering if someone familiar with the MCP4921 could look at the function to see if it's sound or not.

References:

1. The include file I am using for the dac creatively "dac.h" was barrowed from the AF_Wave Shield located here: http://www.ladyada.net/media/wavshield/AFWave_18-02-09.zip

2. The datasheet for the MCP4921 is located here:http://ww1.microchip.com/downloads/en/DeviceDoc/21897B.pdf

Enough said here's the function:

void send_to_dac(uint16_t sample)
{

/* Convert from ADC's 10bit to DAC's 12bit resolution*/

sample = sample * 4;

/* Get The DAC */

sellect_dac();

/*** START OF DAC ***/
/*CONFIGURATION BITS*/

/* FIRST BIT: select dac A */

dac_data_low(); // 0

/* Send Bite */

dac_clock_up(); dac_clock_down();

/* SECOND BIT:(BUF)*/

dac_data_high(); // 1
     
/* Send Bite */

dac_clock_up(); dac_clock_down();

/* THIRD BIT:(GA)*/
/* Already High */

//dac_data_high(); // 1

/* Send Bite */

dac_clock_up(); dac_clock_down();

/* FORTH BIT:(SHDN)*/ 
/* Already High */

//dac_data_high(); // 1

/* Send Bite */

dac_clock_up(); dac_clock_down();

/*** END OF DAC ***/
/*CONFIGURATION BITS*/


/*Start by masking 12th bit of sample data. Shift right working form MSB to LSB per Write Command - Fig. 15-1 */

sample >>= 4;

/* Loop until all 12bits have been transferred */

for(uint8_t i=0;i<12;i++)
{
/*Test if bit is High*/

#if(sample & 0x80)
DAC_DI_PORT |= _BV(DAC_DI); 

/*If true then configure Register to be High If not true then bit is Low*/

#else

/* Then configure Register to be Low */

DAC_DI_PORT |= ~_BV(DAC_DI); 

/*** SEND DATA ***/

/*Power clock up to transmit bit*/

dac_clock_up(); 

/* Do not shift mask on last cycle 
All others shift mask over one bit */

#if(i<12) 
sample >>= 1; 
#endif

/* Power clock down */

dac_clock_down(); 

#endif
}

/* Release DAC from CS */

unselect_dac();

/* Only if using 4 wire connection */

dac_latch_down(); 
dac_latch_up(); 

}

The code does compile without any warnings or errors.

Thanks,

Bill

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are confusing preprocessor directives (computed at compile time) with runtime functions (computed while the program is running).

For example, instead of

#if(i<12) 
sample >>= 1; 
#endif 

you want

if(i<12) 
    sample >>= 1; 

There are other issues:

/* Then configure Register to be Low */ 
DAC_DI_PORT |= ~_BV(DAC_DI); 

should be

/* Then configure Register to be Low */ 
DAC_DI_PORT &= ~_BV(DAC_DI); 

and your handling of "sample" is wrong - draw it out and see which way things are getting shifted as you are hopping all over the place with it.
/mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wow, I just use the AVR's SPI peripheral.

Here is my code for the driver for that chip. As it is a different toolchain, your syntax for selecting/deselecting will be different. The spi() primitive in this write-only application is nothing but loading the SPDR and waiting for SPIF. I'll past the SPI setup at the head.

// SPI initialization
// SPI Type: Master
// SPI Clock Rate: 312.500 kHz
// SPI Clock Phase: Cycle Half
// SPI Clock Polarity: Low
// SPI Data Order: MSB First
SPCR=0x52;
SPSR=0x00;
...
//
// **************************************************************************
// *
// *		D A C _ D R I V E R
// *
// **************************************************************************
//
// Output to the Microchip MCP4921 12-bit, single-channel, SPI DAC
//
//	/CS is on PB.1
//	/LDAC is tied low on the chip
//
//	Output is 16 bits, MSB first.  Four command/control bits, followed by 12 data.
//
//	The DAC will work in SPI mode 0 or mode 3.
//
#if 0
reference copy
#define	DAC_SELECT			PORTB.2		// /CD
#define		SELECT_DAC_YES	0			// active low
#define		SELECT_DAC_NO	1

// Control bits--ONLY APPLIED TO A BYTE!
#define	DAC_CNTL_AB			0x80		//	0=channelA; 1=channelB.  Single channel use 0
#define	DAC_CNTL_BUF		0x40		//	0=unbuffered; 1=buffered.  Use 0 (unbuffered)
#define	DAC_CNTL_GA			0x20		//	0=gain of 2x; 1=gain of 1x.	Use 1 (1x)
#define	DAC_CNTL_SHDN		0x10		//	0=shutdown; 1=active.  Use 1

// "Normal" config, tacked onto the front of each 12-bit data value
#define	DAC_CNTL			(DAC_CNTL_GA | DAC_CNTL_SHDN)
#endif
void dac_driver(unsigned int dac_data)
{
unsigned int	work;		// manipulate registered copy
unsigned char	bwork;		// 8-bits worth

// Select the chip
	DAC_SELECT = SELECT_DAC_YES;

// Get the parameter
	work = dac_data;
// MSB first; work with high byte
	bwork = (unsigned char)(work >> 8);
// Mask off any crap
	bwork &= 0x0f;
// Add control bits
	bwork |= DAC_CNTL;

// Output control bits and high four data bits
	spi (bwork);

// Get low data bits
	bwork = (unsigned char) work;
//	... and output
	spi (bwork);

// Deselect the chip
	DAC_SELECT = SELECT_DAC_NO;
}

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mike,

Thanks for the comments I am still new at this so please bare with me!

I really did not realize what actual purpose of the # directive. When I wrote this piece of code. I took bits & pieces from the Arduino AF_Wave library. Basically the wave.cpp file, and was converting code snippets to fill my projects needs. Since the code compiled, I thought it was pretty much correct.

In regards to this section of code:

DAC_DI_PORT &= ~_BV(DAC_DI);

All I can say is nice catch Mike! But, I do have a question though???

The posted code reads:

if(sample & 0x80)             // Test if bit is High
   DAC_DI_PORT |= _BV(DAC_DI);// If true the configure High to Register
else                          // If not then bit is Low
   DAC_DI_PORT &= ~_BV(DAC_DI);  // Then configure Low to Register 

Since Var "sample" is in 16-bit resolution shouldn't I be testing it against 0x8000 instead of 0x80? In the wave.cpp code the author was breaking it into 8-bit samples.

Shouldn't it read:

if(sample & 0x8000)           // Test if bit is High
   DAC_DI_PORT |= _BV(DAC_DI);// If true the configure High to Register
else                          // If not then bit is Low
   DAC_DI_PORT &= ~_BV(DAC_DI);  // Then configure Low to Register 

Quote:
... handling of "sample" is wrong - draw it out and see which way things are getting shifted as you are hopping all over the place with it.

I thought I was pretty straight forward with it. The Chip has two DACs on it, and the DAC requires the data to be sent starting with the first four configuration bits in the order that is laid out in the function. DAC(A|B)? | BUFFERED 1=Yes | GAIN(1x|2x) | SHUTDOWN MODE (1=Enabled). Then the Data is to be sent to the DAC MSB first. So, MSB->LSB reading from right to left.

The function as written:

1. Sends the first four configuration bits without using the 16-bit DATA.

2. Then shifts the 16-bit DATA variable over four bits to the right to read the MSB bit first.

sample>>4

3. Test the bit then shift over to the right by one.

sample>>1

4. Cycle through all 12-bits by continually shifting right by one to reach the LSB.

So, the code is consistently moving from right to left in one bit intervals.

Am I, doing something wrong? :?

Mike, I do thank you for your help and if you see somthing else wrong.
The more eyes the better! :roll:

theusch was kind enough to post his driver for the DAC and I will be using it until I have to add some external memory to the project. Doing so would open up some room to make a nice delay and or other features. So unless I use the UART as an SPI I will still need this block of code in the future.

theusch,

Thank you for being so kind in sharing your driver for the DAC. At my skill level I didn't even think of going that route. I have been reading a copy of "Embedded C Programming and the ATMEL AVR" for a guide, but at the moment I am only into chapter two of the book. You mentioned the tool chain and this should not be a problem for me. I am currently setup and using the GCC compiler through AvrStudio4. Compilers like the Arduino that are designed to hide code is a bad starting point for anyone trying to learn a new programming language.

Tomorrow I will create a header file for the driver and adjust the ports being used to PORTB. But, it's getting kind of late tonight. :shock:

I do have a couple of questions though!

In the comments at the top states:

//The SPI Clock Phase: Cycle Half

But the way the SPSR is set up SPR1 = 1 and SPR0 = 0 this makes Fosc/32. I may be wrong but, shouldn't SPCR be set to 0x50 and SPSR set to 0x01 for Fosc/2 or am I thinking of something totally different here?

And if I would want to enable Interrupts the SPCR value would have to change from 0x50 to 0xD0?

Also, shouldn't DAC_CTNL ha! I see it now! Daaah! :oops:

Anyways Thanks Again theusch!

Bill

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

And if I would want to enable Interrupts the SPCR value would have to change from 0x50 to 0xD0?

You would rarely if ever want to use interrupt-driven SPI master on an AVR; certainly not for this simple device with small packets. It is just too slow with the double ISR servicing, and a lot more complicated than my "driver":
-- select chip
-- build first byte
-- send first byte
-- send second byte
-- deselect chip.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch,

Quote:

... just too slow with the double ISR servicing ...

Your right I didn't think of that aspect.

I finished the adjustments to the driver today and put the code in a "c" file so that it can be easily be added to any project that uses the GCC compiler. I also had to write a SPI Lib to keep it simple and not cluttered with other routines.

Here is the file, I named it after the device.

// 
// ************************************************************************** 
// * 
// *      D A C _ D R I V E R 
// * 
// ************************************************************************** 
// 
// Output to the Microchip MCP4921 12-bit, single-channel, SPI DAC 
// 
//   /CS is on PB.2
//   /LDAC is tied low on the chip 
// 
//   Output is 16 bits, MSB first.  Four command/control bits, followed by 12 data. 
// 
//   The DAC will work in SPI mode 0 or mode 3. 
// 
#include 
#include 
#include 


// "Normal" config, tacked onto the front of each 12-bit data value 
#define   DAC_CNTL         (DAC_CNTL_GA | DAC_CNTL_SHDN) 



void send2dac(unsigned int dac_data) 
{ 
unsigned int   work;      // manipulate registered copy 
unsigned char   bwork;      // 8-bits worth 

dac_data *= 4; // Convert 10 Bit to 12 Bit if incomming sample does not need to be converted - Just comment out

	
// Select the chip 
   DAC_SELECT_PORT &= ~_BV(DAC_SELECT);


// Get the parameter 
   work = dac_data; 
// MSB first; work with high byte 
   bwork = (unsigned char)(work >> 8); 
// Mask off any crap 
   bwork &= 0x0f; 
// Add control bits 
   bwork |= DAC_CNTL; 

// Output control bits and high four data bits 
   spi_transfer(bwork); 

// Get low data bits 
   bwork = (unsigned char) work; 
//   ... and output 
	spi_transfer(bwork);

// Deselect the chip 
   DAC_SELECT_PORT |= _BV(DAC_SELECT);
} 

Here is the "h" file:


**************************************************************************// 
//                               H File
//
**************************************************************************
// * 
// *      D A C _ D R I V E R 
// * 
// ************************************************************************** 
// 
// Output to the Microchip MCP4921 12-bit, single-channel, SPI DAC 
// 
//   /CS is on PB.2
//   /LDAC is tied low on the chip 
// 
//   Output is 16 bits, MSB first.  Four command/control bits, followed by 12 data. 
// 
//   The DAC will work in SPI mode 0 or mode 3. 
// 


#define DAC_SELECT_PORT PORTB
#define DAC_SELECT_DDR DDRB
#define DAC_SELECT PINB2


#define DAC_DI_PORT PORTB
#define DAC_DI_DDR DDRB
#define DAC_DI PINB3

#define DAC_DO_PORT PORTB
#define DAC_DO_DDR DDRB
#define DAC_DO PINB4

#define DAC_CLK_PORT PORTB
#define DAC_CLK_DDR DDRB
#define DAC_CLK PINB5


// Control bits--ONLY APPLIED TO A BYTE!
#define DAC_CNTL_AB 0x8000      //   0=channelA; 1=channelB.  Single channel use 0 

#define DAC_CNTL_BUF 0x4000      //   0=unbuffered; 1=buffered.  Use 0 (unbuffered)

#define DAC_CNTL_GA 0x2000		//   0=gain of 2x; 1=gain of 1x.   Use 1 (1x)

#define DAC_CNTL_SHDN 0x1000	//   0=shutdown; 1=active.  Use 1 


void dac_init(void);

void send2dac(unsigned int v);

The header still needs some more work to simplify configuration changes but for now it will serve my purposes.

I still have to build the circuit and do some more reading on ADC's before being able to test it out. But I learned a lot from this thread and I hope others will too.

Thanks again, for your help!

Bill

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You start with sample having the 10 bit ADC value in the low 10 bits. You then shift it left twice (sample *= 4)
and then right 4 times (sample >>= 4). So we now have the top 8 bits of the ADC in the low 8 bits of sample, and have lost 2 bits.

You then loop 12 times, and each time shift out the eighth bit of sample, and then right shift one, so you end up shifting out the hi bit of the ADC and 11 bits of 0s.

I'm guessing you should take the 10 bit ADC, left shift twice, and loop 12 times, each time masking with 0x0200 and left shifting once.
/mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Mike,

Thanks for the input, because it would have taken me forever to realize that when I did the conversion from 10bit to 12bit. That it would set the focus on that variable to the 12th bit already. With that little mistake it then cascaded throughout the entire function.

Boy I really feel stupid now! :oops:

Also thank you, for bringing it to my attention that the value that I should test against should be the exact value I desire. That defiantly makes sense and it was something that the book didn't cover.

I do know that I will have to keep reminding myself that I am working on the bit level. Something I never dealt with before. And I guess coding is not like riding a bicycle since I haven't really done anything for the past ten years.

With the adjustments you suggested it appears that the piece of code is pretty square now, and you've taught me a few things about "C" and I appreciate that.


void dac_send_val(uint16_t sample)
{
	sample = sample * 4; 
	
	select_dac(); // Request Dac from CS
	
  /*** START OF DAC CONFIGURATION BITS ***/
                
                // FIRST BIT
                dac_data_low(); // 0
                dac_clock_up();dac_clock_down();

                // SECOND BIT
                dac_data_high();// 1
                dac_clock_up(); dac_clock_down();

                // THIRD BIT
                // already high
                dac_clock_up(); dac_clock_down();

                // FOURTH BIT
                // already high
                dac_clock_up();dac_clock_down(); 

      /*** END OF DAC CONFIGURATION BITS ***/


	for(uint8_t i=0; i<12; i++)
	{
                #if(sample & 0x0200)
                    DAC_DI_PORT |= _BV(DAC_DI);
                #else
                    DAC_DI_PORT &= ~_BV(DAC_DI);
                #endif

             /*Start to send*/

           dac_clock_up();

                 #if(i<12)
                    sample >>= 1;
                 #endif

           dac_clock_down();

	}

  unselect_dac(); // Release Dac from CS
  dac_latch_down(); 
  dac_latch_up();
    
  }

I know that the driver theusch was kind enough to post, that I'm converting also has a couple mistakes in my code.

But I'm still working on that but, many thanks to you for your time and patients in assisting me with this piece of code. I only hope that someday I can do the same for someone else.

Bill

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But Mike,

Now that I thought about it the (sample & 0x0200) might not be correct. Reason being that the upper nibble (Configuration Bits) have already been sent. So, the first digit could be any thing. Right?

But the value of 0x0200 is equivalent to 0b0000001000000000 and by using the "&" operator this would mean that between the sample and the mask.

And I'm pulling this from the book that I'm using to learn C.

And boy, I hope it's not bad information since it's supposed to be at a colleage level.

Anyways, it states that "The And operator will result in a 1 at each bit position where both operands where 1". If this correct then the if statement will set the DAC_DI pin high on 1 and low for 0.

So, if I got this right then shouldn't the value for (sample & 0x0200) be more on the lines of (sample & 0x0fff) for the If statement?

It's still a little confusing to me and for some stupid reason I'm having a hard time wrapping my head around it.

Thanks Again,

Bill

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bill-
For the bit testing, you want to look at the 12th bit only and use that to set DAC_DI, and clock it into the DAC. Then left shift (so what was the 11th bit is in bit 12) and repeat 11 more times.

Note that (sample & 0x0200) will evaluate to either 0 or 0x0200

    for(uint8_t i=0; i<12; i++) 
    { 
        if ((sample & 0x0200) != 0)
            DAC_DI_PORT |= _BV(DAC_DI); 
        else 
            DAC_DI_PORT &= ~_BV(DAC_DI); 

        dac_clock_up(); 
        sample <<= 1; 
        dac_clock_down(); 
    }

/mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mike,

I appreciate the code you placed it seems to clean things up quite a bit. It’s more direct and corrects my thinking as far as working with bits go. And again, I was going the wrong way in my shift operation.

I've been doing a lot of reading in regards to masking and bit shift operations on the internet. More confusing than solidifying. But, now I think I have it. Whichever way you shift when the bit exceeds the range of the bite being used it falls off and cannot be retrieved. So when I was shifting >> I was losing the lower bits. But as the code goes shifting << I am reading off the 12th bit as you stated.

See I was under the misconception that you can read a byte the same way as a string. Able to scan the byte back and forth without losing information.

Totally Wrong!

Another misconception was that I would have to do a shift to get rid of place holders for the extra four configuration bits that where already sent. But now I realize that when I use the identifier unsigned int for the variable sample. It is no longer in the Byte format. Therefore I don't have to worry about bits that do not exist.

Another issue that I was having and still did not find the answer anywhere on the internet. Was which way are the bits being transferd? MSB first or LSB first? But I guess, that it just depends on which way you are shifting the bits.

I did set up the code in a different project and ran it through AVRStudio4's Debugger. Thier is a problem in using the value of 0x0200 in the if statement.


unsigned int sample = 0b0000001011010110; //0x02D6 - 726

int main()
{
sample *= 4;  //101101011000 - 0x0B58 - 2904

if ((sample & 0x0200) != 0) //kicks out 10101100000011

The code above even throws in some extra bits and not all of the correct value, at least in the debugger it does this.

Anyways, when using the value of 0x0800 in the if statement:


unsigned int sample = 0b0000001011010110; //0x02D6 - 726

int main()
{
sample *= 4;  //101101011000 - 0x0B58 - 2904

if ((sample & 0x0800) != 0) //kicks out 101101011000

Giving the desired result.

Well I said before, I cann't thank you enough for all of you help in nurse maidding me on this piece of code.

Happy Holidays!

Bill

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello

Can you edit your complete work here or on a project file ?
I am trying to do the same thing and understand.
Thanx