Jump to content
43oh

Fraunchpad Accelerometer Datalogger


Recommended Posts

Just wanted to create a post and start sharing code for the MSP-430FR5739.

 

I won an extra board after watching the recent Webinar. They sent a webcam with it as they want the winners to participate in a live Q&A session on the new Fram hardware. So, as a quick project to be able to experiment with it, I'm making a RGB led change brightness and color based on tilt of the accelerometer. The ADC->PWM data will be logged to the FRAM device for playback. ie; record a hand/arm movement as RGB color patterns, then play it back. Hope that doesn't sound silly or too simple.

 

EDIT: made a few updates to the progress and code

 

Progress:

WDT enabled as debouncing interval

Clock initialized with XT1 sourced for ACLK

RTC initialized and using 1second interrupt to toggle LED

UART on UCA0 initialized for RX interrupt and toggle LED on receiving 1 byte. TX is done without interrupts. This will have bluetooth module for data transfers.

GPIO intialized for accel, unused ports, onboard buttons (with interrupts), and led display

~TimerB outputting PWM on TB0.1, TB0.2 and TB1.1. |This is put on temporary hold|

Communication with CC2500 and TX/RX wireless data working (http://www.43oh.com/forum/viewtopic.php?f=8&t=1192) I am replacing the RF2500T with a CC2500 module from mdfly.

ADC10 using DMA to read 64 samples of each accelerometer channel.

FRAMwrite function added to store the data in FRAM. |needs a check for storage space available and read back function|

 

 

/****************************************************************/
/* Greg Whitmore												*/
/* greg@gwdeveloper.net											*/
/* www.gwdeveloper.net											*/
/****************************************************************/
/* released under the "Use at your own risk" license			*/
/* use it how you want, where you want and have fun				*/
/* debugging the code.											*/
/* MSP-EXP430FR5739												*/
/****************************************************************/

#include "msp430fr5739.h"

#define PWMPERIOD 2001
#define FRAM_START 0xC800

//int pwm, red, blue, green;
unsigned int ADCdata[64];
unsigned long *FRAM_accel;

int i;

/*
// floats for accuracy, but need u_ints for rgb pwm
typedef struct accelerometer
{
float xA;
float yA;
float zA;
}my_sensor;
*/

typedef struct accelerometer
{
unsigned int xA;
unsigned int yA;
unsigned int zA;
}my_sensor;

my_sensor accel;

unsigned char RXbyte;

void WDT_init(void);
void CS_init(void);
void GPIO_init(void);
void ADC_init(void);
void TimerA0_init(void);
void adc_DMA_init(void);
void UART_init(void);
void THERM_init(void);
void TimerB_init(void);
void RTC_init(void);

void FRAMwrite(unsigned int data);

void TXString( char* string, int length );

void main(void)
{
WDT_init();
CS_init();
RTC_init();
UART_init();
GPIO_init();
TimerA0_init();
ADC_init();
adc_DMA_init();

//TimerB_init();

__enable_interrupt();

FRAM_accel = (unsigned long *)FRAM_START;

while (1)
{

	while (ADC10CTL1 & BUSY);			// wait for adc10 to be ready
	ADC10MCTL0 = ADC10SREF_0 + ADC10INCH_12; // sample channel 12 first; xA
	ADC10CTL0 |= ADC10ENC + ADC10SC;	// start sampling and conversion

	LPM0;		// sleep until conversions are transferred by dma

		accel.xA = 0;
		for (i=0; i < 64; i++)			// total 64 samples of channel
		{
			accel.xA += ADCdata[i];
		}

	while (ADC10CTL1 & BUSY);
	ADC10MCTL0 = ADC10SREF_0 + ADC10INCH_13; // sample channel 13; yA
	ADC10CTL0 |= ADC10ENC + ADC10SC;

	LPM0;

		accel.yA = 0;
		for (i=0; i < 64; i++)
		{
			accel.yA += ADCdata[i];
		}

	while (ADC10CTL1 & BUSY);
	ADC10MCTL0 = ADC10SREF_0 + ADC10INCH_14; // sample channel 14; zA
	ADC10CTL0 |= ADC10ENC + ADC10SC;

	LPM0;

		accel.zA = 0;
		for (i=0; i < 64; i++)
		{
			accel.zA += ADCdata[i];
		}

	// average all samples to prepare PWM duty cycles
	accel.xA = accel.xA >> 6;		// /64
	accel.yA = accel.yA >> 6;		// /64
	accel.zA = accel.zA >> 6;		// /64

	FRAMwrite(accel.xA);
	FRAMwrite(accel.yA);
	FRAMwrite(accel.zA);

	__no_operation();

}

}

void FRAMwrite(unsigned int data)
{
int i;

for ( i = 0; i < (sizeof data); i++)
{
	*FRAM_accel++ = data;	
}	
}

void CS_init(void)
{
PJSEL0 |= BIT4;						// XT1
PJSEL1 &= ~BIT4;

CSCTL0_H = 0xA5;                          // Unlock register
CSCTL1 |= DCOFSEL1 + DCOFSEL1;            // Set max. DCO setting; 8Mhz
CSCTL2 = SELA_0 + SELS_3 + SELM_3;        // set ACLK = XT1; S/MCLK = DCO
CSCTL3 = DIVA_0 + DIVS_0 + DIVM_0;        // set all dividers
CSCTL4 = XT1DRIVE_0; 
CSCTL4 &= ~XT1OFF;

do
{
	CSCTL5 &= ~XT1OFFG;
                                             // Clear XT1 fault flag
	SFRIFG1 &= ~OFIFG; 
}while (SFRIFG1&OFIFG);                   // Test oscillator fault flag
CSCTL0_H = 0x01;                          // Lock Register
}

void UART_init(void)
{
P2SEL1 |= BIT0 + BIT1;
P2SEL0 &= ~(BIT0 + BIT1);

UCA0CTL1 |= UCSWRST; 
UCA0CTL1 = UCSSEL_1;                      // Set ACLK = 32768 as UCBRCLK
UCA0BR0 = 3;                              // 9600 baud
UCA0BR1 = 0; 
UCA0MCTLW |= 0x5300;                      // 32768/9600 - INT(32768/9600)=0.41
                                             // UCBRSx value = 0x53 (See UG)
UCA0CTL1 &= ~UCSWRST;                     // release from reset
UCA0IE |= UCRXIE;                         // Enable RX interrupt
}

void GPIO_init(void)
{
// analog accelerometer
P3SEL0 |= BIT0 + BIT1 + BIT2;
P3SEL1 |= BIT0 + BIT1 + BIT2;

//P3OUT &= ~(BIT0 + BIT1 + BIT2);
//P3DIR &= ~(BIT0 + BIT1 + BIT2);
//P3REN |= BIT0 + BIT1 + BIT2;

// turn accel on via voltage divider (also for thermistor)
P2DIR |= BIT7;
P2OUT |= BIT7;

// unused below; P1.4=TB0.1 P1.5=TB0.2 P1.6=TB1.1
P1OUT &= ~(BIT0 + BIT1 + BIT2 + BIT3 + BIT7);
P1DIR &= ~(BIT0 + BIT1 + BIT2 + BIT3 + BIT7);
P1REN |= (BIT0 + BIT1 + BIT2 + BIT3 + BIT7);

P2OUT &= ~(BIT2 + BIT3 + BIT4 + BIT5 + BIT6);
P2DIR &= ~(BIT2 + BIT3 + BIT4 + BIT5 + BIT6);
P2REN |= (BIT2 + BIT3 + BIT4 + BIT5 + BIT6);

// buttons
P4OUT |= BIT0 +BIT1;
P4DIR &= ~(BIT0 + BIT1);
P4REN |= BIT0 + BIT1;
P4IES &= ~(BIT0+BIT1);
P4IE = BIT0+BIT1;
P4IFG = 0;

// led display; P3.7 will toggle on RTC; P3.6 will toggle on UART RXD
P3OUT &= ~(BIT7+BIT6+BIT5+BIT4);
P3DIR |= BIT7+BIT6+BIT5+BIT4;
PJOUT &= ~(BIT0+BIT1+BIT2+BIT3);
PJDIR |= BIT0+BIT1+BIT2+BIT3;

}

void ADC_init(void)
{
ADC10CTL0 &= ~ADC10ENC;											// stop adc10_b to change settings
ADC10CTL0 = ADC10MSC + ADC10SHT_2 + ADC10ON;					// multi sample; 16 cycles; enable adc10
ADC10CTL1 = ADC10SHS_1 + ADC10DIV_0 + ADC10CONSEQ_2 + ADC10SSEL_3;	// ta0 trigger; /1; repeat-seq; smclk
ADC10CTL2 = ADC10RES;										// 10 bit

// start is controlled in main loop; stop is done with dma isr
}

// trigger for adc send to dma
void TimerA0_init(void)
{
// output ta0.1 for debugging
P1DIR |= BIT0;                       // P1.0
P1SEL0 |= BIT0;                      // P1.0 TA0.1 output

TA0CTL = TASSEL_2 + ID_0 + MC_1;				// smclk; /1 ; up mode
TA0CCR0 = 2001 - 1;								// ~ 2KHz

TA0CCTL1 = OUTMOD_4;							// toggle
}

void adc_DMA_init(void)
{
DMACTL0 = DMA0TSEL__ADC10IFG;						// select adc10 trigger
__data16_write_addr( (unsigned short)&DMA0SA, (unsigned long)&ADC10MEM0 );	// dma single source address
__data16_write_addr( (unsigned short)&DMA0DA, (unsigned long)&ADCdata[0] );	// destination array

DMA0SZ = 64;				// 64 conversions each channel, channels 12-14 selected in main routine after isr exit

// repeat; increment dest address; enable dma; enable interrupt; high level
DMA0CTL = DMADT_4 + DMADSTINCR_3 + DMAEN + DMAIE + DMALEVEL;

}

void THERM_init(void)
{
// turn off ref control
REFCTL0 |= REFTCOFF; 
REFCTL0 &= ~REFON;  

// P1.4 input from NTC
P1OUT &= ~BIT4;      
P1DIR |= BIT4; 

// P2.7 voltage divider; needed for accel and therm
P2OUT &= ~BIT7;     
P2DIR |= BIT7; 	
}

void TimerB_init(void)
{
// TB0.1 = red
P1DIR |= BIT4;                       // P1.4
P1SEL0 |= BIT4;                      // P1.4 TB0.1 output

TB0CTL = TBSSEL_2 + MC_1;         // SMCLK, up mode, clear TAR
TB0CCR0 = PWMPERIOD - 1;             // PWM Period
TB0CCTL1 = OUTMOD_7;                 // CCR1 PWM reset/set

// TB0.2 = green
P1DIR |= BIT5;                       // P1.5
P1SEL0 |= BIT5;                      // P1.5 TB0.2 output

//TB0CCR0 = PWMPERIOD - 1;             // PWM Period
TB0CCTL2 = OUTMOD_7;                 // CCR1 PWM reset/set
TB0CTL = TBSSEL_2 + MC_1 + TBCLR;         // SMCLK, up mode, clear TAR

// TB1.1 = blue
P1DIR |= BIT6;                       // P1.6
P1SEL0 |= BIT6;                      // P1.6 TB1.1 output

TB1CCR0 = PWMPERIOD - 1;             // PWM Period
TB1CCTL1 = OUTMOD_7;                 // CCR1 PWM reset/set
TB1CTL = TBSSEL_2 + MC_1 + TBCLR;         // SMCLK, up mode, clear TAR

}

// rtc initialization
void RTC_init(void)
{
RTCCTL01 |= RTCBCD + RTCHOLD + RTCRDYIE;	// bcd mode; hold rtc for setting; enable rtc ready interrupt; 1sec

RTCSEC = 0x00;
RTCMIN = 0x30;
RTCHOUR = 0x04;

RTCCTL01 &= ~RTCHOLD;				// release rtchold, begin count
}

// wdt initialization for debouncing
void WDT_init(void)
{
   WDTCTL = WDTPW + WDTTMSEL + WDTSSEL_2 + WDTIS_6;	// WDT as interval timer for debouncing of buttons
   												// interval mode, vlo, /512
   SFRIFG1 &= ~(WDTIFG);								// clear WDT flags
   SFRIE1 |= WDTIE;								// enable WDT interrupt
}

#pragma vector=PORT4_VECTOR
__interrupt void Port_4(void)
{

switch(__even_in_range(P4IV,P4IV_P4IFG1))
   {
     case P4IV_P4IFG0:        

       P4IFG &= ~BIT0;                         // Clear P4.0 IFG
       P4IE &= ~BIT0;

       P3OUT ^= BIT7;
       // do something here

	SFRIFG1 &= ~WDTIFG;
	WDTCTL = (WDTCTL & 6) + WDTCNTCL + WDTPW + WDTTMSEL;
	SFRIE1 |= WDTIE;

       break;

     case P4IV_P4IFG1:

       P4IFG &= ~BIT1;                     // Clear P4.1 IFG
	P4IE &= ~BIT1;

       P3OUT ^= BIT6;
       // do something here

	SFRIFG1 &= ~WDTIFG;
	WDTCTL = (WDTCTL & 6) + WDTCNTCL + WDTPW + WDTTMSEL;
	SFRIE1 |= WDTIE;

       break;

     default:
       break;
   }  
}

// wdt timer is used for debouncing
#pragma vector=WDT_VECTOR
__interrupt void watchdog_isr(void)
{
   SFRIE1 &= ~WDTIE;

   P4IFG &= ~(BIT0 + BIT1);			// clear button flags
   P4IE |= BIT0 + BIT1;			// re-enable interrupt
}

/// adc10_b isr
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{
switch(__even_in_range(ADC10IV,ADC10IV_ADC10IFG))
{
   case ADC10IV_NONE: break;               // No interrupt
   case ADC10IV_ADC10OVIFG: break;         // conversion result overflow
   case ADC10IV_ADC10TOVIFG: break;        // conversion time overflow
   case ADC10IV_ADC10HIIFG: break;         // ADC10HI
   case ADC10IV_ADC10LOIFG: break;         // ADC10LO
   case ADC10IV_ADC10INIFG: break;         // ADC10IN

   case ADC10IV_ADC10IFG:

// adc transfers being handled by dma isr
__no_operation();

break;

   default: break; 
}  
}

// dma isr; dma0 on adc trigger
#pragma vector=DMA_VECTOR
__interrupt void dma_isr(void)
{
switch(__even_in_range(DMAIV,DMAIV_DMA2IFG))
{
   case  DMAIV_NONE: break;                          // No interrupt
   case  DMAIV_DMA0IFG: 								 // DMA0IFG

   //triggered by adc conversions
   PJOUT ^= BIT1;				// led indicator
   ADC10CTL0 &= ~ADC10ENC;		// disable adc10 to re-select channel after exit
LPM0_EXIT;

break;                                 

   case  DMAIV_DMA1IFG: break;                          // DMA1IFG
   case  DMAIV_DMA2IFG: break;                          // DMA2IFG

   default: break; 
 }   
}

// UCA0 uart isr
#pragma vector=USCI_A0_VECTOR
__interrupt void uart_RX_isr(void)
{
switch(__even_in_range(UCA0IV,0x08))
{
	case 0:break;								// Vector 0 - no interrupt

	case 2:										// Vector 2 - RXIFG
	P3OUT ^= BIT5;
	UCA0IFG &=~ UCRXIFG;						// Clear interrupt
	RXbyte = UCA0RXBUF;							//
	break;

	case 4:break;								// Vector 4 - TXIFG

	default: break;  
}
}

// RTC_b isr
#pragma vector=RTC_VECTOR
__interrupt void rtc_isr(void)
{
switch(__even_in_range(RTCIV,0x12))
{
   case RTCIV_NONE: break;               // No interrupt
   case RTCIV_RTCRDYIFG: 				// rtc ready
   		while (!(RTCRDY));
   		PJOUT ^= BIT0;
   			break;         
   case RTCIV_RTCTEVIFG: 				// rtc interval timer
   		// do nothing here for now
   			break;        
   case RTCIV_RTCAIFG: break;         // rtc user alarm
   case RTCIV_RT0PSIFG: break;         // rtc prescaler 0
   case RTCIV_RT1PSIFG: break;         // rtc prescaler 1  
   case RTCIV_RTCOFIFG: break;			// rtc oscillator fault                               
   default: break; 
}
}

// tx function borrowed from TI's virtual_com_cmds.c
void TXString( char* string, int length )
{
 int pointer;
 for( pointer = 0; pointer < length; pointer++)
 {
   volatile int i;
   UCA0TXBUF = string[pointer];
   while (!(UCA0IFG&UCTXIFG));              // USCI_A0 TX buffer ready?
 }
}

I plan to maintain the code here as well as store it on my github page.

Link to post
Share on other sites
  • 2 weeks later...

Made some major updates to the code. It's currently a datalogger. Logging the accelerometer channels to the FRAM. Each channel is read separately using the DMA and accumulating 64 samples. Samples are taken ~ 2kHz, trigger by TimerA0. The samples are averaged then stored to FRAM.

 

I have not made considerations, yet, for available space or reading the data back. The stored data has simply been verifed via the CCS memory debug window. I used the window to make sure my storage start address left plenty of room for any more program additions, so it starts at 0xC800 but I may bump this a bit more. I also need to sort out storing unsigned integers better as I'm writing an 8 bit value, leaving a 0 gap before the next value is stored.

 

Next up is to sort the stored PWM values and have them adjust the PWM outputs of TimerB for use on the RGB led as a simple display. Just learned about the availability of grouping TimerB channels as well as the ability to set it to a 10 bit counter, so that should help with that. Mostly, I wanted to have some sort of program functioning for tomorrow's Q&A session with TI on the FRAM device. ( a bit nervous as I'll probably be in there with 19 other E.E. guys and I'm a M.E. who has too much brain activity )

 

Anyone have any tips on writing the whole accelerometer struct in one FRAM write as opposed to 3 separate writes for X, Y, & Z?

Link to post
Share on other sites

Anyone have any tips on writing the whole accelerometer struct in one FRAM write as opposed to 3 separate writes for X, Y, & Z?

 

Hmm... I'm not quite sure this analysis is correct, so please correct me if I'm wrong.

In the current code "sizeof data" will always be evaluated to 2, as "unsigned int" is 16 bits. FRAM_accel++ will always increase the pointer by 4, as "unsigned long" is 32 bits.

As far as I can see that means that you write the value of data twice into FRAM, and increment the pointer by 4. That means that the memory looks something like this after one write:

d   FRAM_START
d   FRAM_START+1
0   FRAM_START+2
0   FRAM_START+3
d   FRAM_START+4
d   FRAM_START+5
0   FRAM_START+6
0   FRAM_START+7

 

Now, my suggestion is the following, which I haven't tried out, so I'm not sure it works:

unsigned char *FRAM_accel;

void FRAMwrite(char *ptr, unsigned int size)
{
int i;

for ( i = 0; i < size; i++)
{
	*FRAM_accel++ = *ptr++;	
}	
}

void main() 
{
       FRAMwrite((char*)&accel, sizeof(accel));
}

 

First, redeclaring FRAM_accel as char* means that ++ only increments the pointer by one byte. (unsigned char* is probably more correct). Next, by casting the pointer to the structure to a char* the memory is accessed without knowledge about its format. We are then free to write every single byte to the FRAM one at a time.

I think the copying can be optimized by using words rather than bytes (i.e. cast the char* to int* before the assignment, and increment i by 2), but then again it might be good to know that this works first :-)

 

Cheers,

Paul

Link to post
Share on other sites

Thanks Paul, I will test your code out this afternoon. The "sizeof data" was used as I initially planned on writing floats to the FRAM but started focusing more on adjusted PWM with the data and needed the ints for that.

 

There's another issue with the code above. It runs until the FRAM is full, then crashes the board. It needs be reprogrammed in order to reset the running program.

Link to post
Share on other sites

FRAM can be used just like RAM (usually).

 

struct TACCEL {
 int x;
 int y;
 int z;
};

const struct TACCEL aa[100];                     // Allocate accel data in FRAM
struct TACCEL *ap = (struct TACCEL *)aa;         // Pointer to address to write accel data

struct TACCEL ad;                                // Accel data struct
ad.x = 1;
ad.y = 2;
ad.z = 3;
*ap++ = ad;                          // Write accel struct to FRAM and increment pointer

Link to post
Share on other sites

Ok, this makes much more sense now. The TI examples made it seem as something special needed to be done to write to the FRAM, but that's probably my misunderstanding it.

 

So, I can allocate a huge chunk of the FRAM (much more than 100 bytes) as the data logging storage area and write my struct only in that allocated portion? I think what's happening is that it's filling the FRAM, looping, writing over the code and then crashing the board. If that's actually possible...

 

I need to learn more about the proper time and place to use * and & for variables. :?

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...