bookmark_borderMSP-EXP430FR5739: ADC10B mit PWM triggern

Im folgenden Beispiel wird mit dem Timer A0 eine PWM (50 Hz, 10 % duty cycle) erzeugt welche ohne CPU Interaktion den ADC 10B des MSP430FR5739 triggert und eine Messung auslöst. Gemessen wird die Spannung an Port 1.1. Ist der ADC fertig wird ein Interrupt ausgelöst.
Nur während die ISR läuft wacht die CPU kurz auf.

[c]
#include „msp430fr5739.h“

unsigned int ADC_Result;

void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT

// Setup clock system
CSCTL0_H = 0xA5; // Password
CSCTL1 |= DCOFSEL0 + DCOFSEL1; // DCO 8 MHz
CSCTL2 = SELA_3 + SELS_3 + SELM_3; // set ACLK = SMCLK = DCO/8

// Port Setup
P1DIR |= BIT0; // Set P1.0 to output direction
P1SEL0 |= BIT0; // P1.0 is output for PWM for debugging
P1SEL1 |= BIT1; // P1.1 is input for ADC
P1SEL0 |= BIT1; // P1.1 is input for ADC

// Setup Timer
TA0CCR0 = 20000 – 1; // PWM Period 50 Hz
TA0CCTL1 = OUTMOD_7; // CCR1 reset/set
TA0CCR1 = 2000; // CCR1 PWM duty cycle 10 %
TA0CTL = TASSEL_2 + MC_1 + TACLR; // SMCLK, up mode, clear TAR

// Setup ADC
ADC10CTL0 &= ~ADC10ENC;
ADC10CTL0 |= ADC10SHT_2 + ADC10ON; // ADC10ON, S&H=16 ADC clks
ADC10CTL1 |= ADC10CONSEQ_2; // Single-channel repeat
ADC10CTL1 |= ADC10SHS_1; // Set PWM as trigger source
ADC10CTL1 &= ~ADC10SHP; // Bypass Sample timer
ADC10CTL2 |= ADC10RES; // 10-bit conversion results
ADC10MCTL0 |= ADC10INCH_1; // A1 ADC input select; Vref=AVCC
ADC10IE |= ADC10IE0; // Enable ADC conv complete interrupt
ADC10CTL0 |= ADC10ENC; // Enable ADC conversation

_BIS_SR(GIE);

while(1) __bic_SR_register(CPUOFF);
}

#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{
__no_operation();
switch(__even_in_range(ADC10IV,12))
{
case 0: break; // No interrupt
case 2: break; // conversion result overflow
case 4: break; // conversion time overflow
case 6: break; // ADC10HI
case 8: break; // ADC10LO
case 10: break; // ADC10IN
case 12:
ADC_Result = ADC10MEM0;
__no_operation();
__bic_SR_register_on_exit(CPUOFF);
break;

default: break;
}
}
[/c]

bookmark_borderMSP430 LaunchPad SPI Example

Das folgende Programm verschickt auf Tasterdruck (S2) ein byte (0x22) per SPI.

[c]
// P1.5 = SCLK
// P1.6 = DOUT
// P1.7 = DIN

#include

void main(void)
{

WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

// Port 1.3: Switch Input
P1DIR &= ~BIT3; // P1.3 = input
P1REN |= BIT3; // Pullup/down resistor enabled
P1OUT |= BIT3; // Pin is pulled enabled
P1IE |= BIT3; // Enable interrupts from switch
P1IES |= BIT3; // rising edge

// Setup USI -> SPI Master
USICTL0 |= USIPE7 + USIPE6 + USIPE5 + USIMST + USIOE; // Port, SPI master
USICTL1 |= USIIE; // Counter interrupt, flag remains set
USICTL1 |= USICKPH; // Data is captured on the first SCLK edge and changed on the following edge
USICKCTL = USIDIV_4 + USISSEL_2; // /16 SMCLK
USICTL1 &= ~USIIFG; // reset IRQ flag
USICTL0 &= ~USISWRST; // USI released for operation

_BIS_SR(GIE);
while(1);
}

#pragma vector=USI_VECTOR
__interrupt void universal_serial_interface(void)
{
USICTL1 &= ~USIIFG; // reset IRQ flag
}

#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
P1IE &= ~BIT3; // Disable interrupts from switch

USISRL = 0x22; // write data to send into fifo
USICNT = 8; // reload counter

P1IFG &= ~BIT3; // clear Interrupt Flag
P1IE |= BIT3; // Enable interrupts from switch
}
[/c]

Mit dem folgenden Programm können die Daten auf einem zweiten LaunchPad empfangen werden:

[c]
#include

void main(void)
{
int received_data[1];
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

// Setup USI -> SPI Slave
USICTL0 |= USIPE7 + USIPE6 + USIPE5 + USIOE; // Port setup
USICNT &= ~USI16B; // only 8 bit mode
USICTL0 &= ~USIMST; // SPI slave
USICTL1 |= USIIE; // Counter interrupt, flag remains set
USICTL1 |= USICKPH; // Data is captured on the first SCLK edge and changed on the following edge
USICKCTL = USIDIV_4 + USISSEL_2; // /16 SMCLK
USICTL1 &= ~USIIFG; // reset IRQ flag
USICTL0 &= ~USISWRST; // USI released for operation
received_data[0] = USISRH;
received_data[0] = USISRL; // Empty USISRL
//USISRL = 0x22;
USICNT = 7; // init-load counter

_BIS_SR(GIE);
while(1);

}

#pragma vector=USI_VECTOR
__interrupt void universal_serial_interface(void)
{
int received_data[1];
received_data[0] = USISRH;
received_data[0] = USISRL;

USICNT = 7;
USICTL1 &= ~USIIFG; // reset IRQ flag
}
[/c]

bookmark_borderMSP430 LaunchPad PWM Example (MSP430G2231)

Mit dem MSP430 ist es möglich eine PWM zu erzeugen – ganz ohne CPU Interaktion und ohne Interrupts. Die ISR in diesem Beispiel dient ausschließlich dazu den duty cycle zu verändern.

Der Trick ist den Output der Caputre/Compare Unit zu verwenden. Er kann direkt an den GPIO Port 1.2 „angeschlossen“ werden.

Das LaunchPad ist für dieses Beispiel mit einem MSP430G2231 bestückt.

[c]
#include

int main(void) {

while(1){ // All in a while loop just for safety
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

// GPIO Port 1
// Port 1.2: PWM Output
P1DIR |= BIT2; // P1.2 = output
P1SEL |= BIT2; // P1.2 = TA1 output
// Port 1.3: Switch Input
P1DIR &= ~BIT3; // P1.3 = input
P1REN |= BIT3; // Pullup/down resistor enabled
P1OUT |= BIT3; // Pin is pulled enabled
P1IE |= BIT3; // Enable interrupts from switch
P1IES |= BIT3; // rising edge

// Clock System
BCSCTL3 |= LFXT1S_2; // ACLK source is VLO, 12 kHz

// Timer A
TACCTL1 |= OUTMOD_7; // TACCR1 reset/set
TACTL |= TASSEL_1; // Select ACLK as source
TACTL |= MC_1; // Up mode: the timer counts up to TACCR0
TACCR0 = 0x009F; // PWM Period – up to int 159
TACCR1 = 0x0000; // TACCR1 PWM Duty Cycle, 0 %

_BIS_SR(LPM3_bits + GIE); // Go to LPM 3 and enable interrupts in general
}
}

#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
int TACCR1_reg = TACCR1;
P1IE &= ~BIT3; // Disable interrupts from switch

if(TACCR1_reg >= 160) // PWM Duty Cycle weiterzählen
{ // Zähler zählt bis 0x009F, also 159
TACCR1 = 0; // 100 % duty cycle entspricht dann 160
} else {
TACCR1 = TACCR1_reg + 20;
}

P1IFG &= ~BIT3; // clear Interrupt Flag
P1IE |= BIT3; // Enable interrupts from switch
}
[/c]

Bringt man den MSP wie im Beispielcode in den Low Power Mode 3 messe ich rund 64 uA Stromaufnahme (Stromversorgung per USB). Im LPM2 waren es 93 uA, im LPM1 157 uA und im Active Mode ca. 450 uA.
Entfernt man die Jumper der LEDs (J5), die des BiWire JTAG und die der UART Schnittstelle (TEST, RST, RXD, TXD an J3) misst man im LPM3 noch 0,8 bis 0,9 uA (mein Messgerät ist nicht so genau…). Fasst man jedoch mit der Hand an die Pins des MSP430 schwankt die Stromaufnahme stark. Das liegt an den uninitialisierten Ports welche in undefinierten Zuständen floaten können. Um das zu ändern kann man diese entweder alle auf GND oder Vdd legen oder als Output initialisieren bevor die eigentliche Portinitialisierung vorgenommen wird.

[c]
P1DIR = 0xFF; // Output
P2DIR = 0xFF;
P1OUT = 0x00; // Value = low
P2OUT = 0x00;
[/c]

Jetzt misst man noch wie vor ca. 0,7 bis 0,8 uA (ein bisschen weniger als vorher) aber dieser Wert bleibt konstant wenn man mit der Hand die Pins berührt. Entferne ich mein Oszi von Port 1.2 sind es nur noch 0,6 uA.

Mit dem folgenden Programm kann man duty cycle und Frequenz einer PWM messen:
[c]
#include

void init_board(void);

unsigned int RISE1 = 0;
unsigned int RISE2 = 0;
unsigned int FALL1 = 0;
unsigned int PERIOD = 0;
unsigned int DUTYCY = 0;
unsigned int DUTYCYPERCENT = 0;

int main(void) {

while(1){ // All in a while loop just for safety
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

init_board(); // Set all pins to output low

// GPIO Port 1
P1DIR &= ~BIT1; // P1.1 is input
P1REN |= BIT1; // Enable Pullup/down
P1OUT &= ~BIT1; // Pull down
P1SEL |= BIT1; // Enable primary peripheral module function

// Clock System
BCSCTL3 |= LFXT1S_2; // ACLK source is VLO, 12 kHz

// Timer A
TACTL |= MC_2; // Continues mode: the timer counts up to 0xFFFF
TACTL |= TASSEL_1; // Select ACLK as source

// Capture/Compare Block 0
TACCTL0 |= CM_1; // Capture rising edge
TACCTL0 |= CCIS_0; // Capture input select: 0 – CCIxA
TACCTL0 |= CAP; // Capture mode
TACCTL0 |= CCIE; // Capture/compare interrupt enable.

_BIS_SR(LPM3_bits + GIE); // Go to LPM 3 and enable interrupts in general
}
}

#pragma vector=TIMERA0_VECTOR
__interrupt void TimerA0(void)
{
int CM0_REG = TACCTL0 & 0xC000; //
TACCTL0 &= ~CCIE; // Capture/compare interrupt disable.

if(CM0_REG == CM_1) // If rising edge captured
{
RISE1 = TAR;
PERIOD = RISE1 – RISE2;
RISE2 = RISE1;

TACCTL0 &= ~CM_1;
TACCTL0 |= CM_2; // Next time: capture falling edge
}

if(CM0_REG == CM_2) // If falling edge captured
{
FALL1 = TAR;
DUTYCY = FALL1 – RISE1;

// Duty cycle in Prozent berechnen
// Funktioniert nicht bei 0 % und 100 % da keine flanken mehr auftreten
if(PERIOD != 0) DUTYCYPERCENT = ((100 * DUTYCY) / PERIOD);

TACCTL0 &= ~CM_2;
TACCTL0 |= CM_1; // Next time: capture rising edge
}

TACCTL0 &= ~CCIFG; // Clear interrupt flag
TACCTL0 |= CCIE; // Capture/compare interrupt enable.
}

void init_board(void)
{
P1DIR = 0xFF;
P2DIR = 0xFF;
P1OUT = 0x00;
P2OUT = 0x00;
}
[/c]

Das Erzeugen einer PWM könnte in Assembler dann so aussehen (hier ist der duty cycle aber fest eingestellt):

[as3]
;
.cdecls C,LIST, „msp430x20x3.h“

.text

RESET mov.w #0280h,SP ; Initialize stackpointer
StopWDT mov.w #WDTPW+WDTHOLD,&WDTCTL ; Stop WDT

mainf ; Basic device setup for power consumption reduction
mov.b #0FFh,&P1DIR ; All Port 1 Pins are outputs
mov.b #0FFh,&P2DIR ; All Port 2 Pins are outputs
mov.b #00h,&P1OUT ; All Port 1 outputs are low
mov.b #00h,&P2OUT ; All Port 2 outputs are low

; GPIO Port 1, P1.2 is PWM Output
bis.b #04h,&P1DIR ; P1.2 is an output
bis.b #04h,&P1SEL ; P1.2 is connected to timer A

; Clock system setup
bis.w #LFXT1S_2,&BCSCTL3 ; ACLK source is VLO

; Timer A setup
bis.w #OUTMOD_7,&TACCTL1 ; TACCR1 reset/set
bis.w #TASSEL_1,&TACTL ; Select ACLK as source
bis.w #MC_1,&TACTL ; Up mode: the timer counts up to TACCR0
mov.w #009Fh,&TACCR0 ; PWM Period – up to int 159
mov.w #002Fh,&TACCR1 ; TACCR1 PWM Duty Cycle

stophere nop
bis.w #LPM3+GIE,SR
nop
nop
jmp stophere

; Interrupt Vectors
.sect „.reset“ ; MSP430 reset vector
.short RESET
.end
[/as3]