MSP430 Microcontroller using ADC and PWM

MSP430 Microcontroller using ADC and PWM

2019, Sep 30    

It is a simple project using PWM as output and ADC as input in a MSP430FR5969 Microcontroller, in other words, the output voltage (for example a LED) can increase/decrease according to the ADC Value.

The MSP430FR5969 DataSheet and User’s Guide that is used to develop this code. You can find this code here.

Code

CLOCK

As you can see in the datasheet, you can configure different clocks for different utilities, so you can configure the clocks as your project needs. in this case, the MCLK -> DCO = 1MHZ, the ACLK -> LFXTCLK (~32Khz) = 32.768 Hz and the SMCLK -> DCO (1Mhz / 32) = 31.250 hz

// CLOCK

  // MASTER CLOCK (MCLK)                    // CPU CLOCK
  CSCTL0_H = CSKEY >> 8;                    // Unlock CS registers
  CSCTL1 = DCOFSEL_0;                       // DCO = 1Mhz
  CSCTL2 = SELM__DCOCLK;                    // MCLK -> DCO
  CSCTL3 = DIVM__1;                         // Div / 1 (therefore the MCLK clock remains the same)

  // AUXILIARY CLOCK (ACLK)                 // TIMER CLOCK
  CSCTL2 |= SELA__LFXTCLK;                  // ACLK -> LFXTCLK (~32Khz) = 32.768 Hz
  CSCTL3 |= DIVA__1;                        // Div / 1

  // SUB-MASTER CLOCK (SMCLK)                // ADC CLOCK
  CSCTL2 |= SELS__DCOCLK;                    // SMCLK -> DCO
  CSCTL3 |= DIVS__32;                        // Div /32 (1Mhz / 32) = 31.250 hz

PWM

This example project uses the TimerB counter with the Up-Mode, and Reset/set mode. The output is reseted when the timer counts to TBxCCR1 value, and it is set when the timer counts to the TBxCCR0 value, so hence it’s clear that if the relation TBxCCR0/TBxCCR1 changes, the duty cycle PWM will also change.

  // Configure PWM output (TIMER B)
  TB0CTL = TBSSEL__ACLK | ID__1 | MC__UP | TBCLR | TBIE;             // TIMERB -> ACLK , DIV/1 , UP_MODE, Interrupt Enable
  TB0CCTL1 = OUTMOD_7;                                               // MODE RESET/SET

  //Duty Cycle
  TB0CCR0 = 1000-1;
  TB0CCR1 = 100;                                                    // Duty Cycle = (10%) - initial duty cycle

Initialization ADC function:

It’s a 12-bit resolution ADC, therefore N=2^12= 4.096, so the LSB = 3.45v/4096 = 0.0008423 = 0.84mv difference for each bit. as ADC12VRSEL = 0, Therefore VR+ = AVCC (3.45v) and VR-= AVss (0v).

  // Configure ADC12
  
  ADC12CTL0 &= (ADC12ENC);										   //  Conversion Disabled for now
  ADC12CTL0 = ADC12SHT0_2 | ADC12ON;                               //  Sampling time (16 cycles) and Turn on the ADC
  ADC12CTL1 = ADC12SSEL_3 | ADC12SHP;                              //  ADC12CLK -> SMCLK
  ADC12CTL2 = ADC12RES__12BIT;                                     //  12-bit resolution
  ADC12MCTL0 = ADC12INCH_2 | ADC12DIF;                             //  Channel2 ADC input select; Vref=AVCC
  ADC12IER0 = ADC12IE0;                                            //  Enable ADC conv complete interrupt

Read ADC value and change the output

ADC12MEM0 is the register that holds the ADC Value.

while (1)
  {
    __delay_cycles(5000);
    ADC12CTL0 |= ADC12ENC | ADC12SC;                               // Start sampling/conversion
	//while (!(ADC12IFGR0));                                       // Wait the ADC time sampling

    __bis_SR_register(GIE);                                        // Interrupt Enable
    __no_operation();                                              // For debugger
  }
}

// ADC12 INTERRUPT
#pragma vector = ADC12_VECTOR
__interrupt void ADC12_ISR(void){

    P1OUT ^= BIT0;                                                   // Output Led to verify if the ADC interrupt is OK

    result = ADC12MEM0;	
    if(result>=0x0340 && result < 0x0A80){                     //  ADC >= 822 AND ADC < 2688
        TB0CCR1 = 450;                  //   PWM Duty Cycle (45%)
    }
    else if(result>=0x0A80){                                      // ADC > = 2688
        TB0CCR1 = 900;                  //   PWM Duty Cycle (90%)
    }
    else{
        TB0CCR1 = 100;                  //   PWM Duty Cycle (10%)
}

}

//TIMER B INTERRUPT (B1 VECTOR)
#pragma vector=TIMER0_B1_VECTOR
__interrupt void Timer_B1(void){

    TB0CTL &= ~(TBIFG);                                               // Clear Flag

}