#include "msp430.h" #include "inttypes.h" // for DMA address pointers #include // for MPPT functions #define ADC_REFERENCE 0 // 2 = 2.5V internal reference, 1 = 1.5V internal reference, 0 = external Vcc reference #define I2C_CLOCK_PRESCALER 12 // I2C clock = SMCLK / prescaler. Ex. 1.2GHz / 12 = 100kHz /* Maxim DS1845 - 100 tap*/ //#define I2C_POTMETER_TAPS (int)99 // # of taps minus 1 //#define I2C_POTMETER_ADDRESS 0x50 // 7-bit addressing, do not include write-bit //#define I2C_POTMETER_INSTRUCTION 0xF9 /* Maxim DS1845 - 256 tap*/ #define I2C_POTMETER_TAPS (int)255 // # of taps minus 1 #define I2C_POTMETER_ADDRESS 0x50 // 7-bit addressing, do not include write-bit #define I2C_POTMETER_INSTRUCTION 0xF8 /* Analog Devices AD5242 - 256 tap*/ //#define I2C_POTMETER_TAPS (int)255 // # of taps minus 1 //#define I2C_POTMETER_ADDRESS 0x2C // 7-bit addressing, do not include write-bit //#define I2C_POTMETER_INSTRUCTION 0x00 // RADC1, no reset, no shutdown, no logic pin outputs #define I2C_POTMETER_START_TAP ((int)(I2C_POTMETER_TAPS/2)) /* MPPT thresholds */ #define SMALL_STEP 1 #define LARGE_STEP 3 /* Sets the MPPT tracking frequency [Hz]. Recommended range is 20 - 500 for SMCLK = 1.2MHz. Note that this is basically the ADC sampling frequency, and that the frequency with which the digital potentiometer is written is only half of that. Setting the value too low results in integer truncation (TACCR0 is a 16-bit timer compare register) Setting the value too high results in the digi-pot becoming unresponsive, sometimes until it is power-cycled. Up to 550Hz should be OK, but 600Hz is too much. The math: TRACKING_FREQUENCY = SMCLK_FREQUENCY / TACCR0_VALUE <=> TACCR0_VALUE = SMCLK_FREQUENCY / TRACKING_FREQUENCY */ #define TRACKING_FREQUENCY I2C_POTMETER_TAPS #define SMCLK_FREQUENCY 1200000 #define TACCR0_VALUE (SMCLK_FREQUENCY/TRACKING_FREQUENCY) /* abs() generally has undefined behavior for abs(MIN), so a safe version is used instead */ static long int safe_abs(long int i) { long int res; if (LONG_MIN == i) res = LONG_MAX; else res = i < 0 ? -i : i; // IF i<0 THEN return -i ELSE return i return res; } /* USCI module is configured as an synchronous mode I2C master in a single-master enviroment. Both master and slave use 7-bit addressing. Master does not respond to general calls. Clock used is SMCLK/prescaler, ex. 1.2GHz/12 = 100kHz. Master is configured as transmitter. P3.1 = SDA I2C data P3.2 = SCL I2C clock */ void I2C_init() { UCB0CTL1 |= UCSWRST; // Enable USCI SW reset UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // Single I2C master, synchronous mode, 7-bit addressing (both master and slave) UCB0CTL1 = UCSSEL_2 + UCTR + UCSWRST; // Use SMCLK, transmitter mode, keep SW reset UCB0BR0 = I2C_CLOCK_PRESCALER; // fSCL = SMCLK/prescaler UCB0BR1 = 0; UCB0I2CSA = I2C_POTMETER_ADDRESS; // Slave address P3SEL |= BIT1 + BIT2; // Assign I2C pins to USCI_B0 (secondary peripheral module) UCB0CTL1 &= ~UCSWRST; // Clear USCI SW reset, resume operation } /* The digital potmeter is written over I2C. Default is to write a new potmeter tap value. */ void I2C_write_pot_value(int value) { if (value > I2C_POTMETER_TAPS) value = I2C_POTMETER_TAPS; while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent UCB0CTL1 |= UCTR + UCTXSTT; // Switch to I2C transmit mode, send START condition and address while ((IFG2 & UCB0TXIFG) == 0); // Wait for transmit buffer to clear UCB0TXBUF = I2C_POTMETER_INSTRUCTION; // Send instruction while ((IFG2 & UCB0TXIFG) == 0); // Wait for transmit buffer to clear UCB0TXBUF = value; // Send data while ((IFG2 & UCB0TXIFG) == 0); // Wait for transmit buffer to clear UCB0CTL1 |= UCTR + UCTXSTP; // Send STOP condition } /* Initiates the DMA controller to get the two ADC values when both are available and move them to registers associated with the hardware multiplier, which will then multiply them. The DMA raises an interrupt when the transfer is complete, since the multiplier cannot. */ void DMA_init() { DMA0SA = (__SFR_FARPTR) (uintptr_t) &ADC12MEM0; // Source address = ADC0 result DMA0DA = (__SFR_FARPTR) (uintptr_t) &MACS; // Target address = Multiply-accumulate operand DMA0SZ = 0x02; // Transfer size in words DMACTL0 = DMA0TSEL_6; // ADC12IFGx triggers DMA0 DMACTL1 = DMAONFETCH + ENNMI; // Allow CPU to finish current instruction, NMI (non-maskable interrupts, such as the reset pin) may interrupt DMA transfers DMA0CTL = DMADT_5 + DMADSTINCR_3 + DMASRCINCR_3 + DMAEN + DMAIE; // Repeated block transfer (= do not disable DMA after transfer), increment source and destination addresses, enable DMA, enable interrupt RESLO = 0; // Clear hardware multiplier result registers RESHI = 0; } /* Initiates the ADC12 for sequential sampling of P6.0 and P6.1. reference = 2 => 2.5V internal reference reference = 1 => 1.5V internal reference reference = 0 => external Vcc reference */ void ADC_init(int reference) { P6SEL = BIT0 + BIT1; // Enable A/D channel inputs for P6.0 and P6.1 // Turn on ADC12, set reference, extend sampling time to avoid overflow of results if (reference == 2) ADC12CTL0 = ADC12ON + MSC + SHT0_8 + REF2_5V + REFON; // 2.5V internal reference else if (reference == 1) ADC12CTL0 = ADC12ON + MSC + SHT0_8 + REFON; // 1.5V internal reference else ADC12CTL0 = ADC12ON + MSC + SHT0_8; // External Vcc reference ADC12CTL1 = SHP + CONSEQ_1; // Use sampling timer, sequence of channels if ((reference == 2) || (reference == 1)) // 2.5V or 1.5V internal reference { ADC12MCTL0 = SREF_1 + INCH_0; // ref+ = Vref+, channel = A0 ADC12MCTL1 = SREF_1 + INCH_1 + EOS; // ref+ = Vref+, channel = A1, end seq. int i = 0; for (i = 0xFFF; i > 0; i--); // Time for VRef to settle } else // External Vcc reference { ADC12MCTL0 = INCH_0; // ref+ = AVcc, channel = A0 ADC12MCTL1 = INCH_1 + EOS; // ref+ = AVcc, channel = A1, end seq. } ADC12IFG = 0x00; // Reset ADC interrupt flags ADC12CTL0 |= ENC; // Enable conversions } void ADC_start() { RESLO = 0; // Clear hardware multiplier result registers RESHI = 0; ADC12CTL0 |= ADC12SC; // Start convn - software trigger return; } int mppt() { static int R = I2C_POTMETER_START_TAP, R_old = I2C_POTMETER_START_TAP; static int dR = 0; static long int Px = 0, Pk = 0, dP = 0, dP1 = 0, dP2 = 0; static unsigned int odd_even = 0; volatile unsigned int *const hw_mult_RESLO = &RESLO; volatile unsigned int *const hw_mult_RESHI = &RESHI; odd_even ^= 0x0001; // ODD if (odd_even) { Px = *hw_mult_RESHI; // Get calculated power from hardware multiplier Px = Px << 16; Px += *hw_mult_RESLO; dP1 = Px - Pk; // "Between samples" intermediate result return -1; } // EVEN else { Pk = *hw_mult_RESHI; // Get calculated power from hardware multiplier Pk = Pk << 16; Pk += *hw_mult_RESLO; dP2 = Pk - Px; // Change in power due to change in irradiation dP = dP1 - dP2; // Change in power due to pertuberation dR = R - R_old; R_old = R; // save current R for next loop // if the change in power due to irradiation (|dP2|) is smaller // than the change in power due to the MPPT perturbation (|dP|), // it is considered to be slowly changing conditions if (safe_abs(dP2) < safe_abs(dP)) { if (dR >= 0) { if (dP >= 0) R += SMALL_STEP; else R -= SMALL_STEP; } else if (dP >= 0) R -= SMALL_STEP; else R += SMALL_STEP; } // otherwise it is considered to be rapidly changing conditions else { if (dR >= 0) { if (dP >= 0) R += LARGE_STEP; else R -= LARGE_STEP; } else if (dP >= 0) R -= LARGE_STEP; else R += LARGE_STEP; } if (R > I2C_POTMETER_TAPS) R = I2C_POTMETER_TAPS; else if (R < 0) R = 0; return R; } } void timer0_init() { TACCTL0 = CCIE; // CCR0 interrupt enabled TACCR0 = TACCR0_VALUE; TACTL = TASSEL_2 + MC_1; // SMCLK, upmode } void main() { WDTCTL = WDTPW + WDTHOLD; // Stop WDT int R_pot = 0; DMA_init(); ADC_init(ADC_REFERENCE); I2C_init(); I2C_write_pot_value(I2C_POTMETER_START_TAP); timer0_init(); while(1) { _BIS_SR(LPM0_bits + GIE); // Enter LPM0, Enable interrupts R_pot = mppt(); if (R_pot != (-1)) I2C_write_pot_value(R_pot); // Track MPP and update digital pot with new R over I2C } } // DMA interrupt service routine #pragma vector = DMA_VECTOR __interrupt void DMA_ISR(void) { DMA0CTL &= ~DMAIFG; // Clear DMA0 interrupt flag _BIC_SR_IRQ(LPM0_bits); // Exit LPM0 on reti } // Timer A0 interrupt service routine #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { ADC_start(); }