This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

  • TI Thinks Resolved

MSP430FR2355: DAC noise

Prodigy 150 points

Replies: 5

Views: 98

Part Number: MSP430FR2355

Hello,

For my application I am running a while loop which reads from the ADC (sequence of channels mode) and then writes that value to the DAC (reference Vcc) to then be amplified and used to control a pressure transducer.

In the loop, the ADC is enabled and then the device is put into low power mode with interrupts enabled. This makes it so the ADC can read from the channels with Timer_B as the timer source.

Immediately after exiting LPM the desired reading is written to the SAC3DAC register to be outputted from the DAC. That output is buffered with the internal opamp so that the analog signal can be seen on P3.5

The signal from P3.5 is then passed through another amplifier, giving the output a swing of 0-10V.  On the 0-10V output there is about 90mV peak to peak of noise. I tried filtering the output and was able to reduce the output noise to 11mV P-P.

Q: Is there a way to eliminate the noise coming from the MCU closer to the source? Could it be a timing issue? Or is this just the inherent noise in the system? I am having issues with controlling the transducer so that it will hold a given pressure.

Thank you very much in advance, any help is appreciated!

Below I have posted some relevant parts of the code. Let me know if any other information could help.

ADC initialization:

//Initialize the ADC Module
    /*
     * Base Address for the ADC Module
     * Use TB1.1B as sample/hold signal to trigger conversion
     * USE ADCOSC Digital Oscillator as clock source
     * Use clock divider of 1
     */
    ADC_init(ADC_BASE,
             ADC_SAMPLEHOLDSOURCE_2,
             ADC_CLOCKSOURCE_ADCOSC,
             ADC_CLOCKDIVIDER_1);

    ADC_enable(ADC_BASE);

    /*
     * Base Address for the ADC Module
     * Sample/hold for 16 clock cycles
     * Do not enable Multiple Sampling      !! Enabled multiple samples
     */
    ADC_setupSamplingTimer(ADC_BASE,
                           ADC_CYCLEHOLD_16_CYCLES,
                           ADC_MULTIPLESAMPLESDISABLE);

    /*
     * Base Address for the ADC Module
     * Using 12bit resolution
     */
    ADC_setResolution(ADC_BASE,
                      ADC_RESOLUTION_12BIT);

    //Configure the Memory Buffer
    /*
     * Base Address for the ADC Module
     * Use input A9
     * Use positive reference of AVcc
     * Use negative reference of AVss
     */
    ADC_configureMemory(ADC_BASE,
                        ADC_INPUT_A9,
                        ADC_VREFPOS_AVCC,
                        ADC_VREFNEG_AVSS);

    __delay_cycles(5000);

    ADC_clearInterrupt(ADC_BASE,
                       ADC_COMPLETED_INTERRUPT);

    //Enable the Memory Buffer Interrupt
    ADC_enableInterrupt(ADC_BASE,
                        ADC_COMPLETED_INTERRUPT);

    Timer_B_outputPWMParam param = {0};
    param.clockSource = TIMER_B_CLOCKSOURCE_SMCLK;
    param.clockSourceDivider = TIMER_B_CLOCKSOURCE_DIVIDER_20;
    param.timerPeriod = 100;
    param.compareRegister = TIMER_B_CAPTURECOMPARE_REGISTER_1;
    param.compareOutputMode = TIMER_B_OUTPUTMODE_TOGGLE_RESET;
    param.dutyCycle = param.timerPeriod/2;
    Timer_B_outputPWM(TIMER_B1_BASE, &param);


    adc_count  = 9;        // Starts at 9, and repeats ADC interrupt until adc_count=0, then
    ADC_startConversion(ADC_BASE, ADC_SEQOFCHANNELS);         // enable and start conversion
    __delay_cycles(5000);  // allow ADC to settle (Is this necessary?)

ADC Interrupt Service routine:

// ADC interrupt service routine
   #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
   #pragma vector=ADC_VECTOR
   __interrupt void ADC_ISR(void)
   #elif defined(__GNUC__)
   void __attribute__ ((interrupt(ADC_VECTOR))) ADC_ISR (void)
   #else
   #error Compiler not supported!
   #endif
{
  switch(__even_in_range(ADCIV,ADCIV_ADCIFG))
  {
           case ADCIV_NONE:
               break;
           case ADCIV_ADCOVIFG:
               break;
           case ADCIV_ADCTOVIFG:
               break;
           case ADCIV_ADCHIIFG:
               break;
           case ADCIV_ADCLOIFG:
               break;
           case ADCIV_ADCINIFG:
               break;
           case ADCIV_ADCIFG:

             if(adc_count == 9){             // * P5.1 (Fast Slew)
                 A9_Result = ADCMEM0;
                 if(P4IN & BIT5){
                 FastSlew = A9_Result;
                 }
             }
             else if(adc_count == 8){        // * P5.0 (Manual Pot)
                 A8_Result = ADCMEM0;
                 Manual_Pot = A8_Result;
                 if(P4IN & BIT5){     // Setup mode
                     if(P1IN & BIT5){     // Manual mode
                         if(!(P1IN & BIT3)){  // Rise button pressed
                             RiseCp = 4095 - Manual_Pot;    // Set Rise Changepoint
                         }
                         if(!(P3IN & BIT4)){  // Fall button pressed
                         FallCp = 4095 - Manual_Pot;    // Set Fall Changepoint
                         }
                     }
                 }
             }
             else if(adc_count == 7){
                 A7_Result = ADCMEM0;
             }
             else if(adc_count == 6){
                 A6_Result = ADCMEM0;
             }
             else if(adc_count == 5){
                 A5_Result = ADCMEM0;
             }
             else if(adc_count == 4){        // * P1.4 (Display Reading)
                 A4_Result = ADCMEM0;
             }
             else if(adc_count == 3){
                 A3_Result = ADCMEM0;
             }
             else if(adc_count == 2){
                 A2_Result = ADCMEM0;
             }
             else if(adc_count == 1){        // * P1.1 (SlowSlew)
                  A9_Result = ADCMEM0;
                  if(P4IN & BIT5){
                  SlowSlew = A9_Result;
                  }
             }
             else if(adc_count == 0){
                 A0_Result = ADCMEM0;
                 __bic_SR_register_on_exit(LPM0_bits);    // exit low power mode
             }

             if(adc_count != 0){
                 adc_count --;
             }
  }
}

Loop used for ADC reading and DAC writing:

                  while(P1IN & BIT5){
                      // Set pressure based on pot, nothing else
                      P1OUT &= ~BIT2;         // Turn off display lock
                      P3IE &= ~BIT1;        // disable P3.1 interrupt (Opto-Isolator)
                      P3IFG &= ~BIT1;       // clear interrupt flag (Opto-Isolator)
                      P1IE &= ~BIT3;        // disable P1.3 interrupt (rise button)
                      P1IFG &= ~BIT3;       // Clear P1.3 interrupt (rise button)
                      P3IE &= ~BIT4;        // disable P3.4 interrupt (fall button)
                      P3IFG &= ~BIT4;       // Clear P3.4 interrupt flag (fall button)
                      P4IE &= ~BIT5;        // disable P4.5 interrupt (Setup/Run switch)
                      P4IFG &= ~BIT5;       // Clear P4.5 interrupt flag (Setup/Run switch)
                      P1IE &= ~BIT5;        // disable P1.5 interrupt (Auto/Manual switch)
                      P1IFG &= ~BIT5;       // Clear P1.5 interrupt flag (Auto/Manual switch)
                      mode = POLLING_MODE;
                      auto_ready = 0;
                      if(y == 0){                   // setup GPIO pins and ADC/DAC for the rest of while loop
                          functiongenerator_init_GPIO();
                          functiongenerator_enable();
                          y = 1;
                          ADCCTL0 &= ~ADCENC | ADCSC;             // clear ADC conversion bits just in case to prevent sticking
                      }
                      //functiongenerator();
                      ADCCTL0 |= ADCENC | ADCSC;
                      __bis_SR_register(LPM0_bits + GIE);
                      ADCCTL0 &= ~ADCENC_1;
                      adc_count = 9;
                      SAC3DAT = 4095 - Manual_Pot;
                      if(P4IN & BIT5){
                          break;
                      }
                  }
                  y = 0;  // Reset flag for Run/Manual loop

  • I made an audio AGC (Automatic Gain Control) circuit using the same part. Two ideas for you to explore based on what I found/did:

    The input to the A2D should be though a 390 to 680 ohm resistor and a .001 to .01 uF cap. Work the RC so it is 10x your highest input frequency. On the PCB, as ground comes up under the part, connect the Vref- to that ground, and make sure that is the "hub" of your analog ground. The cap needs to go directly to this ground. This will help filter the ground noise. If you are using the internal reference of 2.5 volts, you have about .61 mV/610 uV per bit. Ground noise in many systems is up in the 10 mV range, more if you have switching supplies on the board. You need this RC network to filter out a lot of that noise. Note that while you've raise the input impedance of your source, the cap counteracts that and looks like a very low impedance during the A2D's sample and hold period.

    Consider an external reference, like a REF3025. It will be a bit less noisy (and more accurate) than the internal. Using the power supply rails for a reference is not good for what you are doing. For audio, it worked OK, but at no signal in where the AGC had high gain, I heard noise.

    The other idea is filter the output of the DAC. At a minimum, another RC network. I used a sallen-key filte (google it)r, but be careful with the op amp offset voltage. You may need to "trim" that with a 500K resistor to ground or your reference. If you amp uses gnd as a reference, be sure it's a single trace back to the analog ground point. Don't wire it directly into the op amp, have a resistor that matches the other leg of the op amp. That way, you are not introducing system ground noise into your amp. 

    Look at the noise at the chip output (after the RC) to make sure your amp isn't making things works. If you have ground planes, make sure they are not under the RC network. Remember the single point ground for all input filter caps and the reference.

    So things to try and the order to try them:

    1) Add RC network close to chip to filter your input.

    2) Evaluate using the rails, internal 2.5 and external 2.5 for a reference and see what that does.

    3) Add post-dac filter, even if the RC is 10x your max frequency. Then consider a sallen key if you need even better results, but be aware that offset errors will need to be trimmed out on a per-PCB basis and you need to margin the offset for both changes in the power supply and temperature. Alternatively, you can just add some C in the feedback resistor of your op amp and not worry too much about offset issues.

    I think you you try this out, you can determine the sources of noise at the various stages of your design. You also mentioned that the output was 0 to 10 V. You can get op amps that go down to 0, and you can get op amps that are fast, but it's hard to get op amps that go to gnd and are fast. You may want to put on a LM7660 chip to make a minus supply for your amp to insure you are not getting distortion around gnd. Presumably, the positive rail for the amp is at least 12V, with 15 being preferable for the same reason. When you are living in a 12 bit world, each bit is about .025% world. Noise, offset errors and non-linearity are much more of an issue than in many systems.

    When you're done, your proto board will be a cut-up ugly wires-in-the-air mess, but you'll be ready for a re-work.

    I didn't look at your code much, but I would suggest that every time you set the timer period, add a comment on what that is in both uS and frequency. I saw the delay you had, so  It looks like this is a < 50 KHz system, so the RCs should be feasible given the conversion time.

    I don't know what your experience is in analog design, I hope this helps and does not offend. There are many good app notes about A2D designs and layouts. 

    Pete

  • How do you set DACLSEL? I see quite a lot of jitter in this code, so a timer trigger for the DAC would be the only way to avoid distortion.

  • In reply to Bruce McKenney47378:

      For the audio Automatic Gain Control (volume of signal in varies, output is always at a constant volume) I had a timer that started a A2D measurement on the input. The IRQ routine for that A2D then read the value, and took the next output from a big sample FIFO buffer (50 mS of delay on the audio, I was sampling at a 20 KHz/50 uS rate) and outputted to the DAC.

    The hardware multiply on the 2355 was critical. Non real-time code (think "big loop") figured out what the gain should be for a constant gain output. When I retrieved a sample from the output of the FIFO, I multiplied it by the current gain, then put it in the DAC.

    As such, the time delay from the A2D input to the DAC output was always constant in terms of the micro seconds of delay, and constant with respect to the sample period. While this was not a filter, this is equivalent to having no phase distortion or a constant group delay, whichever you prefer to use.

    If you signal is ground referenced, you are stuck with dealing with ground noise. For my AGC, I created a "false ground" a 1/2 of the reference voltage. I sampled this voltage too, so the system looked a bit like a differential input system that also helps reduce noise. 12 bits of resolution in a single ended system is rough...  If you sample time is small enough, you can average 4 samples (at 4x the required final sample rate) to reduce noise "a bit"- and this literally is by a bit.

    At any rate, for just about any control or audio system, I would use timers for both sampling and outputting to the DAC. If you can closely couple them together as I did by having the A2D input IRQ routine also output whatever the next DAC output should be, you will have no jitter in your system, especially if you insure that the IRQ routine runs in a fixed time (no "if" statements without an "else" that takes the same amount of time).

    When I do these sorts of things, I tend to either make a 1st pass PCB with lots of room and pads for measurement so I can understand what is going on, or use more PCB room that needed if the final board has no sever size constraints.

    Pete

  • In reply to Peter Hallenbeck:

    Bruce McKenney47378 My DACLSEL is DACLSEL_0. That is the DAC output updates immediately when a value is written to the DACDAT register. I'll definitely look into putting both ADC and DAC on timers.

    Peter Hallenbeck Thank you for all the information! I will certainly implement some if not most of these suggestions.

    I thought I would also add some oscilloscope screenshots to help paint a bigger picture. I'm wondering if my voltage regulator is oscillating, or if this might point to a timing issue as you both suggested? Or perhaps ground shifting? There is no filter on the output. When I zoomed into both the 3.3V rail (yellow) and the DAC output through internal buffer (blue) I see how they are similar.

    Below are the mentioned images:

    3.3V (yellow), DAC + internal buffer output (blue)

    Amplified DAC signal (yellow), 5V rail (blue):

  • In reply to Michael Rowane:

    That's way too much noise on both supplies. What is the regulator used to go from 5 to 3.3 v?  Figure out how much C it takes to quiet down the 3.3, should be able to get it down to 10mV or so. Note also that if you are measuring with the probe using the long ground wire, that can make things look worse too (although in the uS range, not so much). Kick in your bandwidth limit and see what changes. I typically have a 100uF 10V 1206 ceramic  cap on my 3.3 regulator for "bulk". Make sure your regulator is OK with that (it should be). Also how much C do you have near the uP chip? should have a .1 and a 10 uF as close as you can get it, again in ceramic, 0603 if you can on the .1uF. If the DAC is off the reference for the A2D, and the A2D reference is the rails, this explains a lot.

    Given that the 5V is quieter, is sure seems like your 3.3 reg is ringing. I use the FR2355 with a 5V supply that was a switching supply, then a LM317 to make the 3.3V. With proper layout, I got less than 1mV of noise into the A2D (with an external 2.5V reference). It's hard to measure that with a scope with all the ground noise, you need to run the A2D and print the value out when a fixed DC voltage goes into it. A AA batter is a good, constant voltage source for this. You should be able to make code that samples and puts it out to the DAC. Connect the battery, and see just how much supply noise is getting into your system.

    In addition to more C, put more load on the power supply to make sure it's OK. Some have minimum loads too. a 100 ohm resistor will put 33 mA out. So try that first, see if things stabilize. Then try more C, see if they stabilize.  As long as the power supply is that dirty, nothin will ever work well.

    Pete

This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.