gwdeveloper 275 Posted October 22, 2011 Share Posted October 22, 2011 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. Quote Link to post Share on other sites
bluehash 1,581 Posted October 22, 2011 Share Posted October 22, 2011 Nice project. Let us know how the FRAM performs. Do you select how much Ram/Flash you need as the memory space is unified. Can you change it in the linker? Good luck! Quote Link to post Share on other sites
gwdeveloper 275 Posted November 2, 2011 Author Share Posted November 2, 2011 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? Quote Link to post Share on other sites
xpg 127 Posted November 3, 2011 Share Posted November 3, 2011 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 gwdeveloper 1 Quote Link to post Share on other sites
gwdeveloper 275 Posted November 3, 2011 Author Share Posted November 3, 2011 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. Quote Link to post Share on other sites
oPossum 1,083 Posted November 3, 2011 Share Posted November 3, 2011 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 xpg and gwdeveloper 2 Quote Link to post Share on other sites
xpg 127 Posted November 3, 2011 Share Posted November 3, 2011 That's quite a bit more elegant, oPossum. Thanks for sharing! Quote Link to post Share on other sites
gwdeveloper 275 Posted November 3, 2011 Author Share Posted November 3, 2011 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. :? Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.