bluehash 1,581 Posted February 10, 2011 Share Posted February 10, 2011 Entry to the March 2011 contest is open. This is an opportunity for you to show off what you can do with the MSP430. Projects based on February being the Valentine month are appreciated, but you are free to submit anything you create with the MSP430. The 43oh community forum has been growing both with good discussions and members. The project section of the forum as well as the Blog has a lot of project ideas you can base your submission on. Feel free to ask questions about the project on the forum. Previous Contests November 2010 [Announce] [ Winner] December 2010 [Announce / Winner] Last Date for entries: 21st April, 2011. Prizes: Winner : Texas Instruments has graciously donated an Ez430-Rf200 Wireless Development Tool.[Estore] [Info] [Wiki] Runner up :[J]bremnant has been kind enough to donate a pristine unopened Launchpad kit. Although you may already have one, having another Launchpad can be quite handy like a project you may not want to take apart or recreate[J]oby's SPI Ninja. Thanks [J]bremnant for sponsoring this month's contest giveaway. To submit your entry, make an entry into this thread with the following: 1 - A small description of your project. 2 - A picture or video of your setup 3 - Code. 4 - Schematic(rough/hand drawn is fine as long its legible) About judging the winner : A week before the contest ends, a poll will be created with all the project entries. Only members of the forum will be allowed to vote. The contest will roll over to the next month if there are fewer than 5 projects(four or less). A few simple rules to follow: - You must be a member of the 43oh forum at least a week before your submission. - One entry from each member will be permitted. - Your project can be anything based around the MSP430. You may interface the MSP430 to another controller. - You may reuse code from anywhere as long as you give credit to the original author. - You must submit code and schematics of your project with a proper description. - You can submit your MSP430 project, if it was created before the annoucement of the contest. - You must have at least 5 posts in the forums, for your entry to be considered when the voting begins. [1st May, 2011] Voting closed. Congratulations to NatureTM on his "Polyphonic MIDI Dynth" project. He wins this April 2011 Project of the Month Contest. Runner up is the "Serial to 7 segment LED display driver v1" by Rob. A big thank you to PhirePhly, GeoNomad and SugarAddict for participating. Wish you all luck in the upcoming contests jbremnant 1 Link to post Share on other sites
jbremnant 17 Posted February 14, 2011 Share Posted February 14, 2011 Again, thanks for the mention and hope many participate. I've been busy with work and other educational responsibilities, but hopefully I can put something out there either this one or the next! Good luck everyone. Link to post Share on other sites
PhirePhly 5 Posted February 14, 2011 Share Posted February 14, 2011 Well someone's gotta be first: A basic battery-powered LCD clock. Tells time in 24 hour format through a 4 digit seven segment LCD. Two buttons for setting hour and minute. Running current is below 12uA, which means this thing is looking forward to a good 2.5 years running off a CR2032 coincell, like it is. With just the 100uF capacitor and no battery, it can run for more than 15 seconds, so even battery replacements can be done without loss of time, if you're fast enough. The display is wired to display full hexadecimal, so it can really be used for any project that needs a 4 digit LOW power display. A 20DIP MSP430 would probably help the power budget to a degree, removing the need for the shift register between the MSP430 and LCD driver. If only I had waited another month to build this one... Source code Full writeup with video and parts list LCDClock.c zeke, bluehash and jsolarski 3 Link to post Share on other sites
RobG 1,892 Posted February 16, 2011 Share Posted February 16, 2011 Serial to 7 segment LED display driver v1 This is a milestone for my project so I figured I will include it in this month's PotM. The idea behind it was to create a simple display driver which could communicate with my LaunchPad over 2 wires so I don't have to deal with shift registers, wiring, wasted ports, and display routines each time I work on a new project. There are 2 parts, driver board and display board. Schematic for the driver board is below, schematic for the display board is here. Input: 2 wire 16bit SPI Output: up to 8 seven segment LED displays, no decimal point (I decided not to include it with this version because 20pin chips are now available and things will get a lot easier.) Functions: Clear display, set single digit, set pair of digits (decimal or hex), set 4 digits (12 bit signed, but cannot show the sign, all you get is ABS,) scroll left, scroll right. Connector SV2 connects to display board. Connector SV1 allows easy pin reassignment for future I2C protocol support. Connector SV3: 1 - Vcc, 2 - clock, 3 - unused for SPI, 4 - data in, 5 - GND. Controller code #include "msp430g2231.h" // 0-9,A,b,c,d,E,F,blank,-,r,C,o,h,H,L,u,t,i,n,P,U,deg,= const unsigned char to7digit[32] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F, 0x77,0x7C,0x58,0x5E,0x79,0x71, 0x00,0x40,0x50,0x39,0x5C,0x74,0x76,0x38, 0x1C,0x78,0x18,0x54,0x73,0x3E,0x63,0x48}; const unsigned char digitSelector[8] = {BIT0, BIT1, BIT2, BIT3, BIT4, BIT5, BIT6, BIT7}; const unsigned char mask2 = 0xC0; const unsigned char mask1 = 0x3F; const unsigned char mask1spi = 0x5F; unsigned char tmpDIR = 0; unsigned char tmpOUT = 0; unsigned char digit = 0; unsigned char digitData[8] = {16,16,16,16,16,16,16,16}; unsigned int rxData = 0; unsigned char rxDataL = 0; unsigned char rxDataH = 0; unsigned char switchVar = 0; unsigned char negative = 0; unsigned char blankChar = 16; void main() { WDTCTL = WDTPW + WDTHOLD; BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; CCTL0 = CCIE; CCR0 = 125; TACTL = TASSEL_2 + MC_1 + ID_3; P2SEL &= ~(BIT6|BIT7); USICTL0 |= USIPE7 + USIPE5; USICTL1 |= USICKPH + USICKPL + USIIE; USICTL0 &= ~USISWRST; USICNT = 16 | USI16B; __enable_interrupt(); while(1) { } } // USI interrupt service routine #pragma vector = USI_VECTOR __interrupt void USI_RX (void) { //SPI rxData = USISR; rxDataL = USISRL; rxDataH = USISRH; USICNT |= 16; switchVar = rxDataH & 0xE0; switch(switchVar) { case 0xA0: //10100AAADDDDDDDD (A0xxh-A7xxh) Set A digit to D (0-255, anything above 32 will be useless) //10101000AAADDDDD (A8xxh) Set A digit to D, pretty much as above, but limited to 32 characters //(how many meaningful can you do with 7 segment anyway?) switchVar = rxDataH & 0xF8; switch(switchVar) { case 0xA8: switchVar = rxDataL & 0xE0; switchVar >>= 5; digitData[switchVar] = rxDataL & 0x1F; break; case 0xA0: switchVar = rxDataH & 0x07; digitData[switchVar] = rxDataL; // TODO 32+ are not available at this time, will produce unexpected results break; } break; case 0x80: //100000AADDDDDDDD (80xxh-83xxh) Set 2 digits to 8 bit unsigned value (convert to BCD) A=0 digits 0-1, etc. //Value can be 0-99 (score boards with 4 sets of 2 digits?) //100001AADDDDDDDD (84xxh-87xxh) As above, but no BCD conversion, just HEX switchVar = rxDataH & 0xFC; switch(switchVar) { case 0x80: switchVar = rxDataH & 0x03; switchVar <<= 1; digitData[switchVar + 1] = blankChar; digitData[switchVar] = 0; //TODO values bigger than 99 will overflow, check and display overflow while(rxDataL > 0) { digitData[switchVar] = rxDataL % 10; rxDataL /= 10; switchVar++; } break; case 0x84: switchVar = rxDataH & 0x03; switchVar <<= 1; digitData[switchVar] = rxDataL & 0x0F; digitData[switchVar + 1] = rxDataL >> 4; break; } break; case 0xC0: //110ADDDDDDDDDDDD (Cxxxh, Dxxxh) Set 4 digits to 12 bit signed integer (convert to BCD) //A=0 digits 0-3, A=1 digits 4-7, D= signed 12 bit integer, //DP of digits 0 and 4 should be connected to "-" LEDs, //value can be 0-1999 with floating decimal point and "-" sign switchVar = 0; if(rxData & BITC) switchVar = 4; // first digit is 4 digitData[switchVar] = 0; digitData[switchVar + 1] = blankChar; digitData[switchVar + 2] = blankChar; digitData[switchVar + 3] = blankChar; rxData &= 0x0FFF; negative = 0; if(rxData & BITB) { negative = 1; rxData = -(rxData); } while(rxData > 0) { digitData[switchVar] = rxData % 10; rxData /= 10; switchVar++; } //TODO handle negative sign //decimal point has to be set with another cmd //also, when DP will determine blank digits, 0.0 0.00 break; case 0xE0: //111XXXXXXXXXXXXX (Exxxh, Fxxxh) Set configuration values like leading 0 blank, etc. switchVar = rxDataH; switch(switchVar) { case 0xE0: // set blank char to 0 blankChar = 0; break; case 0xE1: // set blank char to, well, blank blankChar = 16; break; case 0xEF: // clear display by setting all digits to blank char switchVar = 0; while(switchVar < 8) { digitData[switchVar] = blankChar; switchVar++; } break; case 0xE2: // scroll left switchVar = 7; while(switchVar > 0) { digitData[switchVar] = digitData[switchVar - 1]; switchVar--; } digitData[0] = rxDataL & 0x1F; break; case 0xE3: // scroll right switchVar = 0; while(switchVar < 7) { digitData[switchVar] = digitData[switchVar + 1]; switchVar++; } digitData[7] = rxDataL & 0x1F; break; } break; } } // Timer A0 interrupt service routine #pragma vector = TIMERA0_VECTOR __interrupt void Timer_A (void) { P1OUT &= ~mask1spi; //spi P2OUT &= ~mask2; P1DIR &= ~mask1spi; //spi P2DIR &= ~mask2; tmpDIR = to7digit[digitData[digit]]; tmpOUT = digitSelector[digit]; if(tmpDIR & tmpOUT) tmpDIR |= BIT7; tmpDIR |= digitSelector[digit]; P1DIR |= tmpDIR & mask1spi; P1OUT |= tmpOUT & mask1spi; P2DIR |= tmpDIR & BIT7; //spi P2OUT |= tmpOUT & BIT7; //spi tmpDIR <<= 1; //spi tmpOUT <<= 1; //spi P2DIR |= tmpDIR & BIT6; //spi P2OUT |= tmpOUT & BIT6; //spi digit++; if (digit == 8) digit = 0; } Master code for testing purposes #include unsigned int number = 0; unsigned int digit = 0; unsigned int data = 0; void main(void) { WDTCTL = WDTPW + WDTHOLD; // ---- this is whats needed to send USICTL0 |= USIPE6 + USIPE5 + USIMST + USIOE; USICTL1 |= USICKPH + USICKPL + USIIE; USICKCTL = USIDIV_7 + USISSEL_2; USICTL0 &= ~USISWRST; USICNT = USI16B; // --------------------------------- _delay_cycles(2000); _bis_SR_register(GIE); int n = 0; int d = 0; // ------------------------- // example of how to clear display and set blank char while(n < 2) { USISRH = 0xE0; //set blank char to 0 USICNT |= 16; _delay_cycles(2500); //wait for USI to finish sending USISRH = 0xEF; //clear display USICNT |= 16; _delay_cycles(1000000); //delay 1s USISRH = 0xE1; //set blank char to "blank" USICNT |= 16; _delay_cycles(2500); USISRH = 0xEF; //clear display USICNT |= 16; _delay_cycles(1000000); n++; } // ------------------------- // ------------------------- // example of how to set single digits, scrolls through all available chars for(n = 0; n<32; n++) { //n is character number to be displayed for(d = 0; d<8; d++) { //d is digit number data = d; data <<=5; //digit number is in bits 5-7 data |= n; //char number is in bits 0-4 USISRL = data; USISRH = 0xA8; //command to set single digit USICNT |= 16; _delay_cycles(20000); } } // ------------------------- _delay_cycles(1000000); // ------------------------- // clear display USISRH = 0xE1; USICNT |= 16; _delay_cycles(2500); USISRH = 0xEF; USICNT |= 16; _delay_cycles(2500); // ------------------------- // ------------------------- // example of how to set a pair of digits // this one sets digits 2-3 to decimal representation of n // n must be in the range of 0-99 // if n is <10, left digit will be blanked with blank char. for(n = 0; n<100; n++) { USISRL = n; USISRH = 0x81; //cmd to set pair 2-3 to BCD USICNT |= 16; _delay_cycles(100000); } // ------------------------- // ------------------------- // example of how to set a pair of digits // this one sets digits 4-5 to decimal representation of n // n must be in the range of 0-99 // if n is <10, left digit will be blanked with blank char. for(n = 0; n<100; n++) { USISRL = n; USISRH = 0x82; //cmd to set pair 4-5 to BCD USICNT |= 16; _delay_cycles(50000); } // ------------------------- // ------------------------- // example of how to set a pair of digits // this one sets digits 0-1 to hex value of n // n must be in the range of 00-FF for(n = 0; n<256; n++) { USISRL = n; USISRH = 0x84; //cmd to set pair 0-1 to hex USICNT |= 16; _delay_cycles(50000); } // ------------------------- _delay_cycles(1000000); // ------------------------- // clear display USISRH = 0xEF; USICNT |= 16; _delay_cycles(2500); // ------------------------- // ------------------------- // example of how to use 12 bit value, digits 0-3 // i must in the range of -1999 to 1999 int i = 0; for(i = 0; i<2000; i+=5) { USISR = i; USISRH |= 0xC0; USICNT |= 16; _delay_cycles(20000); } // ------------------------- _delay_cycles(1000000); // ------------------------- // another example USISRH = 0xEF; USICNT |= 16; _delay_cycles(2500); const char toSend[12] = {0,15,1,30,2,3,3,2,4,31,5,25}; //digit,char,digit,char... t=23oF for(i = 0; i<12; i+=2) { USISRL = (toSend[i] << 5) | toSend[i + 1]; USISRH = 0xA8; //command to set single digit USICNT |= 16; _delay_cycles(2500); } for(i = 4; i<10; i++) { USISRL = (2 << 5) | i; USISRH = 0xA8; //command to set single digit USICNT |= 16; _delay_cycles(1000000); } // ------------------------- _delay_cycles(2000000); // ------------------------- // another example // send an array USICTL1 &= ~USIIE; USISRH = 0xEF; USICNT |= 16; _delay_cycles(2500); const char array[4] = {21,20,3,4}; //char,char,... in reversed order d = 3; // start position for(i = 0; i<4; i++) { USISRL = (d << 5) | array[i]; USISRH = 0xA8; //command to set single digit USICNT |= 16; _delay_cycles(2500); d++; } // ------------------------- _delay_cycles(1000000); // ------------------------- // scroll example for(i = 0; i<8; i++) { USISRH = 0xE3; // scroll right USISRL = 16; // new char, blank USICNT |= 16; _delay_cycles(150000); } // ------------------------- const char msg[12] = {22,14,23,23,0,16,4,3,20,21,16,16}; for(i = 0; i<12; i++) { USISRH = 0xE2; // scroll left USISRL = msg[i]; // new char from an array USICNT |= 16; _delay_cycles(150000); } _delay_cycles(1000000); const char msg2[27] = {5,14,18,26,10,23,16,25,20,16,23,14,13,16,23,10,24,27,12,21,16,28,10,13,16,16,16}; for(i = 0; i<27; i++) { USISRH = 0xE2; // scroll left USISRL = msg2[i]; // new char from an array USICNT |= 16; _delay_cycles(150000); if(i==6 || i==12 || i==20) _delay_cycles(1000000); } while(1) { } } // --- this may be used, but ISR is not required if dealing directly with IF. // USI interrupt service routine #pragma vector = USI_VECTOR __interrupt void USI_TX (void) { USICTL1 &= ~USIIFG; // logic to send could be here } zeke, bluehash, jsolarski and 1 other 4 Link to post Share on other sites
GeoNomad 8 Posted February 26, 2011 Share Posted February 26, 2011 The GoPro HD Hero camera does not have a remote shutter input, but it does have an interface bus on the back designed for an add-on LCD display and other accessories not yet available. I thought it would be interesting to interface an MSP430 to the camera to automate the taking of photos for time-lapse photography, and to add a remote control and motion detection. The easiest way to do this turned out to be the PWR/MODE button which is brought to the connector on the back. The camera has a one-button mode which will take a photo or start a video recording when the camera is powered on. I elected to use the EZ430 F2012 because I had one that I got for free a while back. The interface to the camera is via two wires - the pullup on the input for the power button provides enough power to run the MSP430. A 1,000 microfarad cap provides power for when the processor grounds the input to turn the camera on and off. The program only has to do a few things. After initializing the output and setting up the watchdog timer to interrupt every 256 mSec, it goes to sleep. On interrupt, it decides what to do next which depends on where it is in the picture taking cycle. A cycle consists of: Turn on the camera with a 250 mSec low pulse. Wait 3 seconds for the image capture. Turn off the camera with a 3 second low pulse. Flash the LED to show things are working. Wait until it is time to do it all again. //****************************************************************************** // // GoPro HD Camera Control using BUS connector // for MSP430F2012 // // Uses PWR/MODE button to wake camera up and take photo in One Button Mode // // Set SHOT_RATE for number of seconds between shots // If not using xtal control, time requires CALIBRATION // // closes PWR button for 250 mSec to wake up camera // waits 3.75 seconds for snap and store // closes PWR button for 3 seconds to turn camera off // flashes LED to indicate start of next cycle // // Peter Jennings http://benlo.com/msp430 // //****************************************************************************** #include "msp430.h" #define SHOT_RATE 60 // seconds between shots #define CALIBRATION 7 // calibration to improve accuracy #define WAITING 0 // waiting for next cycle #define STARTING 1 // button down to start #define WAITING_CAMERA 2 // waiting for camera to take pic #define STOPPING 3 // button down to stop static int tick; static int state; static int time; // seconds since last save void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer P1SEL |= 0x00; // P1.1 option select - just I/O P1DIR |= 0x11; // Set P1.0 P1.4 to output direction P1OUT |= 0x10; // LED off, GoPro button off BCSCTL2 |= DIVS_3; // SMCLK/8 WDTCTL = WDT_MDLY_32; // WDT Timer interval 32mS tick = 0; time = 10; // wait for cap to charge up state = WAITING; IE1 |= WDTIE; // Enable WDT interrupt 256 mSec _BIS_SR(LPM0_bits + GIE); // Enter LPM0 with interrupt } // Watchdog Timer interrupt service routine #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void) { if ( (state == STARTING) && (tick >= 1 ) ) // start takes .25 seconds { state = WAITING_CAMERA; P1OUT |= 0x10; // button up } if ( tick & 0x03 ) // most of the time { P1OUT &= ~0x01; // LED off and go back to sleep } else // about once very 1.024 seconds { time++; if ( (state == WAITING) && (time >= SHOT_RATE+CALIBRATION) ) // time for photo { P1OUT &= ~0x10; // button down time = 0; tick = 0; state = STARTING; } else if ((state == WAITING_CAMERA) && (time >= 4) ) // time to turn off { state = STOPPING; P1OUT &= ~0x10; // button down } else if ((state == STOPPING) && (time >= 7)) // should be off now { state = WAITING; P1OUT |= 0x10; // button up P1OUT |= 0x01; // LED flash to indicate done cycle } } tick++; // 256 mSec ticks } I have written no more than a few lines of code for this processor in my life and am basically unfamiliar with it. I would appreciate any pointers from those who are more familiar with the chip, both with regards to the hardware interfacing and the software program. I am sure I am missing some basic details and it would be good to correct them before other users implement these instructions. Feedback is encouraged. The complete writeup is at http://benlo.com/msp430/GoProController.html Peter Link to post Share on other sites
bluehash 1,581 Posted February 26, 2011 Author Share Posted February 26, 2011 He geoNomad. Sweet work! Welcome to the forums. This contest has rolled over to the next month as we need more than 5 entries for the contest to be valid. Also, make sure you make at least 5 posts for your enter to be valid. Below are the rules: A few simple rules to follow: - You must be a member of the 43oh forum at least a week before your submission. - One entry from each member will be permitted. - Your project can be anything based around the MSP430. You may interface the MSP430 to another controller. - You may reuse code from anywhere as long as you give credit to the original author. - You must submit code and schematics of your project with a proper description. - You can submit your MSP430 project, if it was created before the annoucement of the contest. - You must have at least 5 posts in the forums, for your entry to be considered when the voting begins. Link to post Share on other sites
GeoNomad 8 Posted February 26, 2011 Share Posted February 26, 2011 He geoNomad. Sweet work! Welcome to the forums. This contest has rolled over to the next month as we need more than 5 entries for the contest to be valid. Also, make sure you make at least 5 posts for your enter to be valid. Well, I'm not all that worried about the contest, but I would like to get feedback from those more knowledgeable than me. Should that be in a different thread from the contest entry? Peter Link to post Share on other sites
bluehash 1,581 Posted February 26, 2011 Author Share Posted February 26, 2011 You can post it in the Projects section. Link to post Share on other sites
bluehash 1,581 Posted April 2, 2011 Author Share Posted April 2, 2011 Moved to April 2011. Two more projects and this would make a good month for a Contest. Submit your projects, no matter how simple they are. Link to post Share on other sites
gwdeveloper 275 Posted April 10, 2011 Share Posted April 10, 2011 I'm hurrying. It may not make it for this month, but definitely next round. EDIT: Not going to make it this month but here's the basics for my idea... We raise chickens for eggs and meat on our backyard farm (http://www.roundrockfunnyfarm.com). Quail are coming in July. All are started from eggs hatching in our incubators. My project uses multiple ez430-rf2500t devices to build a wireless incubator and brood box monitoring system. There will be 3-4 end devices sending temperature, humidity, water levels and egg-turner position back to an access point with lcd. The info will be displayed in real time as well as logging events. Each end device will be equipped with thermistor, photo diode and hih-4030. Still deciding on the best route for detecting the turner position -micro switches, rotary encoder or just a pot. Gave up on using CCS-Grace for this project. Grace is actually a bit limiting when you want to use the wireless features of the ez430. Link to post Share on other sites
MystBoy 5 Posted April 12, 2011 Share Posted April 12, 2011 I'm hurrying. It may not make it for this month, but definitely next round. ill join next month too Link to post Share on other sites
bluehash 1,581 Posted April 23, 2011 Author Share Posted April 23, 2011 SugarAddict's submission: viewtopic.php?f=8&t=732&hilit=tlc5940#p4986 Ok... here's something much nicer... still horribly commented code with some stuff that could be cleaned up or improved... // I know that my commenting sucks ass... I'm not exactly working on a team project here :-P #define SetLow(port, pin) (port &= ~pin) #define SetHigh(port, pin) (port |= pin) #define Pulse(port, pin) do{SetHigh(port, pin);SetLow(port, pin);} while(0) #define DELTA_1MHZ 244 // 244 x 4096Hz = 999.4Hz #define DELTA_8MHZ 1953 // 1953 x 4096Hz = 7.99MHz #define DELTA_12MHZ 2930 // 2930 x 4096Hz = 12.00MHz #define DELTA_16MHZ 3906 // 3906 x 4096Hz = 15.99MHz #define DELTA_18MHZ 4395 // 4395 x 4096Hz = 18.00MHz #define DELTA_20MHZ 4883 // 4883 x 4096Hz = 20.00MHz #define DELTA_24MHZ 5859 // 5859 x 4096Hz = 23.99MHz #include "msp430g2252.h" void Rotate(void); void Bounce(void); void Set_DCO(unsigned int Delta); // Set DCO to selected frequency void SendDotCorrectionData(void); void SendGreyScaleData(void); int dcdata[] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; int gsdata[] = { 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; char CntrA = 0, CntrB = 0, dcsent = 0, rotator = 0, bounce = 12, bouncedir = 0; int Counter = 0, data = 0, data2 = 0; void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop WDT // BCSCTL1 = CALBC1_16MHZ; // Set range // DCOCTL = CALDCO_16MHZ; // Set DCO step + modulation Set_DCO(4639); // 19 MHz // 1.6 DCPRG, 1.7 GSCLK // 2.0 VPRG, 2.1/2.2 SIN, 2.3 XLAT. 2.4 SCLK, 2.5 BLANK P1DIR |= (BIT6|BIT7); P2DIR |= (BIT0|BIT1|BIT2|BIT3|BIT4|BIT5); P1OUT &= ~(BIT7); P1OUT |= (BIT6); P2OUT &= ~(BIT1|BIT2|BIT3|BIT4); P2OUT |= (BIT0|BIT5); CCTL0 = CCIE; // CCR0 interrupt enabled CCR0 = 56; TACTL = TASSEL_2 + MC_1 + ID_0; // SMCLK, Up TACTL |= MC_0; // Stop SendDotCorrectionData(); SendGreyScaleData(); TACTL &= ~MC_0; // Start _bis_SR_register(LPM0_bits + GIE); // Enter LPM0 w/ interrupt } // Timer A0 interrupt service routine #pragma vector = TIMER0_A0_VECTOR __interrupt void Timer_A (void) { Counter++; if(Counter>4095) { TACTL |= MC_0; // Stop Counter = 0; CntrA = 0; CntrB = 0; SetHigh(P2OUT,BIT5); Pulse(P2OUT,BIT3); rotator++; if(rotator>3) { //Rotate(); Bounce(); rotator = 0; } SendGreyScaleData(); TACTL &= ~MC_0; // Start } Pulse(P1OUT, BIT7); } void Rotate(void) { data = gsdata[31]; for(CntrA = 31;CntrA > 0;CntrA--) { gsdata[CntrA] = gsdata[CntrA-1]; } gsdata[0] = data; data = 0; CntrA = 0; } void Bounce(void) { char i = 0; if(bounce == 31) bouncedir = 1; if(bounce == 0) bouncedir = 0; if(bouncedir == 0) // Up { for(i=0;i { gsdata[i] >>= 1; } bounce++; gsdata[bounce] = 0x1000; } else // down { for(i=0;i { gsdata[i] >>= 1; } bounce--; gsdata[bounce] = 0x1000; } } void Set_DCO(unsigned int Delta) // Set DCO to selected frequency { unsigned int Compare, Oldcapture = 0; BCSCTL1 |= DIVA_3; // ACLK = LFXT1CLK/8 TACCTL0 = CM_1 + CCIS_1 + CAP; // CAP, ACLK TACTL = TASSEL_2 + MC_2 + TACLR; // SMCLK, cont-mode, clear while (1) { while (!(CCIFG & TACCTL0)); // Wait until capture occured TACCTL0 &= ~CCIFG; // Capture occured, clear flag Compare = TACCR0; // Get current captured SMCLK Compare = Compare - Oldcapture; // SMCLK difference Oldcapture = TACCR0; // Save current captured SMCLK if (Delta == Compare) break; // If equal, leave "while(1)" else if (Delta { DCOCTL--; // DCO is too fast, slow it down if (DCOCTL == 0xFF) // Did DCO roll under? if (BCSCTL1 & 0x0f) BCSCTL1--; // Select lower RSEL } else { DCOCTL++; // DCO is too slow, speed it up if (DCOCTL == 0x00) // Did DCO roll over? if ((BCSCTL1 & 0x0f) != 0x0f) BCSCTL1++; // Sel higher RSEL } } TACCTL0 = 0; // Stop TACCR0 TACTL = 0; // Stop Timer_A BCSCTL1 &= ~DIVA_3; // ACLK = LFXT1CLK } void SendDotCorrectionData(void) { CntrA = 0; CntrB = 0; data = dcdata[CntrA]; while(CntrA { if(CntrB { if(data & 0x8000) { SetHigh(P2OUT,BIT1); SetHigh(P2OUT,BIT2); } else { SetLow(P2OUT,BIT1); SetLow(P2OUT,BIT2); } data CntrB++; Pulse(P2OUT,BIT4); } else { CntrA++; if(CntrA { CntrB = 1; data = dcdata[CntrA]; if(data & 0x8000) { SetHigh(P2OUT,BIT1); SetHigh(P2OUT,BIT2); } else { SetLow(P2OUT,BIT1); SetLow(P2OUT,BIT2); } data Pulse(P2OUT,BIT4); } } } Pulse(P2OUT,BIT3); CntrA = 0; CntrB = 0; dcsent = 1; SetLow(P2OUT,BIT0); } void SendGreyScaleData(void) { CntrA = 0; CntrB = 0; data = gsdata[CntrA]; data2 = gsdata[(CntrA+16)]; while(CntrA { if(CntrB { if(data & 0x1000) SetHigh(P2OUT,BIT1); else SetLow(P2OUT,BIT1); if(data2 & 0x1000) SetHigh(P2OUT,BIT2); else SetLow(P2OUT,BIT2); data data2 CntrB++; Pulse(P2OUT,BIT4); } else { CntrA++; if(CntrA { CntrB = 1; data = gsdata[CntrA]; data2 = gsdata[(CntrA+16)]; if(data & 0x1000) SetHigh(P2OUT,BIT1); else SetLow(P2OUT,BIT1); if(data2 & 0x1000) SetHigh(P2OUT,BIT2); else SetLow(P2OUT,BIT2); data data2 Pulse(P2OUT,BIT4); } } } Pulse(P2OUT,BIT3); SetLow(P2OUT,BIT5); if(dcsent == 1) { Pulse(P2OUT,BIT4); dcsent = 2; } } Link to post Share on other sites
bluehash 1,581 Posted April 23, 2011 Author Share Posted April 23, 2011 One more submission before voting starts. Just one. Link to post Share on other sites
NatureTM 100 Posted April 23, 2011 Share Posted April 23, 2011 One more submission before voting starts. Just one. About to enter mine. I figured today was the deadline, but just read at the top of the post that the 21st was the deadline. I figured it was today since it's a week before the end of the month. Is the 21st a misprint? Link to post Share on other sites
NatureTM 100 Posted April 23, 2011 Share Posted April 23, 2011 My submission is a polyphonic midi synth. It's the one I posted in the forums awhile ago, with some revisions to make it polyphonic. It was developed on a g2231, so it will work with the original 2K msp's. I still consider it a work-in-progress, but in a usable state. For the DAC, I chose the MCP4725. I picked this one because it was the cheapest DAC-on-a-breakout from SparkFun. http://www.sparkfun.com/products/8736 I used some TI I2C example code to communicate with it. Since the I2C communications are using the USI, I needed a different way to get the serial MIDI data from the MIDI controller. I set up TimerA in capture mode, and triggered it on both rising and falling edges in the midi data. From that I could see how long the MIDI line was in a given state and thus how many 0's or 1's I got. (TimerA ticks in state / TimerA ticks per bit = number of bits.) My previous code using the USI was a mess, so I was happy to try something different. For the synth part, I have a function that takes the frequency and position within the wave, and returns an amplitude. To mix for polyphony, I just average the amplitudes of several of the waves and send the average to the DAC. The synth sounds pretty cruddy. There's several reasons for this, but it mostly boils down to a need for more free CPU time. A faster chip or more efficient code would help. I have ideas for the latter, but I may just take a completely different approach for the synth altogether. I think I'll start concentrating more on the actual sound, and less on seeing what I can squeeze from the chip. There are functions for square, triangle, and sawtooth waves, however all but square are too computationally expensive for polyphony. The code requires a calibrated DCO. Here's the code -- you might want to copy/paste it into a bigger window (I really need to learn how to make a library): #include "msp430g2231.h" #include #define MCLK_FREQUENCY 16000000 #define MIDI_FREQUENCY 31250 #define PIN_MIDI_DATA BIT2 #define PIN_SPEAKER BIT4 #define MIDI_CHANNEL 0 // MIDI is 1-indexed so setting this to 0 is midi channel 1 #define MAX_POLYPHONY 7 #define WDT_DIVIDER 64 #define SCALE_SIZE 12 #define number_of_bytes 2 const unsigned long WDT_FREQUENCY = MCLK_FREQUENCY / WDT_DIVIDER; const unsigned int synthNotes[sCALE_SIZE] = {9956, 10548, 11175, 11840, 12544, 13290, 14080, 14917, 7902, 8372, 8869, 9397}; const unsigned int TICKS_PER_BIT = MCLK_FREQUENCY / MIDI_FREQUENCY; unsigned int wdtCounter = 0; char MST_Data[number_of_bytes] = {0x00, 0x00}; char SLV_Addr = 0xC0; char byteCount, Transmit = 0; volatile char I2C_State = 0; char newVelocity; char newNote; int noteIndex = 0; unsigned int wdtPeriodNotes[MAX_POLYPHONY]; unsigned int tNotes[MAX_POLYPHONY] = {0}; char notes[MAX_POLYPHONY] = {0}; char velocities[MAX_POLYPHONY] = {0}; bool midiRXBitState = true; char partialMidiData = 0xFF; bool haveNewMidiByte = false; char newMidiByte; void waitForMidiLoop(); void handleNoteOn(); void handleNoteOff(); void updateWaveTimes(); void synthProcess(); void mixToOutputArray(); void handleNoteEvent(); void shiftLeft(char index); void updateSynth(bool on); char getNoteIndex(char note); void addBits(unsigned int stateCount, bool state); void updateADC(void); void Setup_USI_Master_TX (void); void main(){ DCOCTL = CALDCO_16MHZ; BCSCTL1 = CALBC1_16MHZ; WDTCTL = WDTPW + WDTTMSEL + WDTIS1 + WDTIS0; IE1 |= WDTIE; TACTL |= TASSEL_2; // TACLK = SMCLK P1SEL |= PIN_MIDI_DATA; // Timer A compare input TACCTL1 |= CCIE + CAP + CM_3; // enable capture and interrupt on rising and falling edge TACCTL0 |= CCIE; // enable timeout interrupt TACCR0 = 9 * TICKS_PER_BIT; // should never have more than 8 bits P1OUT |= 0xC0; P1REN |= 0xC0; P1DIR = BIT0 + PIN_SPEAKER + BIT6 + BIT7; I2C_State = 0; // wasn't init'ing properly??? TACTL |= MC_1; // start Timer A _EINT(); // enable interrupts while(1){ waitForMidiLoop(); if(newMidiByte == (0x90 | MIDI_CHANNEL)){ haveNewMidiByte = false; handleNoteOn(); P1OUT ^= BIT0; } else if(newMidiByte == (0x80 | MIDI_CHANNEL)){ haveNewMidiByte = false; handleNoteOff(); } else haveNewMidiByte = false; } } void waitForMidiLoop(){ while(!haveNewMidiByte){ synthProcess(); } } // ################################################## // ################# Synth Stuff #################### void synthProcess(){ if(noteIndex){ updateWaveTimes(); if(haveNewMidiByte) return; mixToOutputArray(); if(haveNewMidiByte) return; // if(!I2C_State){ updateADC(); // } } } // basicly tracks our position within the waveform void updateWaveTimes(){ static unsigned int lastWdt = 0; char iNote; if(lastWdt != wdtCounter){ unsigned int wdtDelta = wdtCounter - lastWdt; lastWdt = wdtCounter; for(iNote = 0; iNote < noteIndex; iNote++){ tNotes[iNote] += wdtDelta; if(tNotes[iNote] >= wdtPeriodNotes[iNote]){ tNotes[iNote] = tNotes[iNote] - wdtPeriodNotes[iNote]; } } } } unsigned int triangle(unsigned int position, unsigned int wavelength){ unsigned int halfWavelength = wavelength / 2; if(position <= halfWavelength) return 0x0FFF * (unsigned long)position / halfWavelength; else return 0x0FFF - (0x0FFF * (unsigned long)(position - halfWavelength) / halfWavelength); } unsigned int sawtooth(unsigned int position, unsigned int wavelength){ return (unsigned long)position * 0x0FFF / wavelength; } // use this one for velocity sensitivity //unsigned int square(unsigned int position, unsigned int wavelength, char velocity){ // if(position > wavelength >> 1) // return 0x001F * velocity; // else // return 0; //} unsigned int square(unsigned int position, unsigned int wavelength){ if(position > wavelength >> 1) return 0x0FFF; else return 0; } // A fast but lo-fi digital mixer. void mixToOutputArray(){ char iSum; unsigned int sum = 0; for(iSum = 0; iSum < noteIndex; iSum++) sum += square(tNotes[iSum], wdtPeriodNotes[iSum]); // using waveforms other than square takes extra cpu time and can lead to unexpected results, // but they're there to mess with. (you may miss midi events and get crudd(y/ier) sound) // sum += triangle(tNotes[iSum], wdtPeriodNotes[iSum]); // sum += sawtooth(tNotes[iSum], wdtPeriodNotes[iSum]); // use this one for velocity sensitivity // sum += square(tNotes[iSum], wdtPeriodNotes[iSum], velocities[iSum]); // Hack to keep the volume more level if(noteIndex == 1) sum /= 2; else sum /= noteIndex; MST_Data[0] = 0x000F & (sum >> 8); MST_Data[1] = 0x00FF & sum; } unsigned int midiNoteToWdtPeriod(char midiNote){ return (WDT_FREQUENCY / (synthNotes[midiNote % 12] >> ((127 - midiNote) / 12))); } #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void){ wdtCounter++; } // ############################################### // ################# MIDI stuff ################## void handleNoteOn(){ do{ waitForMidiLoop(); if(newMidiByte & 0x80) break; newNote = newMidiByte; haveNewMidiByte = false; waitForMidiLoop(); if(newMidiByte & 0x80) break; newVelocity = newMidiByte; haveNewMidiByte = false; handleNoteEvent(); }while(1); } void handleNoteOff(){ do{ waitForMidiLoop(); if(newMidiByte & 0x80) break; newNote = newMidiByte; haveNewMidiByte = false; newVelocity = 0; handleNoteEvent(); }while(1); } void handleNoteEvent(){ if(newVelocity != 0){ // add a new note when the poly cache is full, replacing the oldest if(MAX_POLYPHONY == noteIndex){ shiftLeft(0); notes[MAX_POLYPHONY - 1] = newNote; velocities[MAX_POLYPHONY - 1] = newVelocity; wdtPeriodNotes[MAX_POLYPHONY - 1] = midiNoteToWdtPeriod(newNote); } // add a new note else{ notes[noteIndex] = newNote; velocities[noteIndex] = newVelocity; wdtPeriodNotes[noteIndex] = midiNoteToWdtPeriod(newNote); noteIndex++; } } else if(getNoteIndex(newNote) < MAX_POLYPHONY){ shiftLeft(getNoteIndex(newNote)); noteIndex -= 2; if(noteIndex >= 0){ noteIndex++; } else{ noteIndex = 0; // set DC offset to zero when no notes MST_Data[0] = 0; MST_Data[1] = 0; updateADC(); } } } // Shift all notes to the right of index left by one, // overwriting index and freeing a spot at the end of // the array void shiftLeft(char index){ int i; for(i = index; i < MAX_POLYPHONY - 1; i++){ notes[i] = notes[i + 1]; velocities[i] = velocities[i + 1]; wdtPeriodNotes[i] = wdtPeriodNotes[i + 1]; } } char getNoteIndex(char note){ int i; for(i = 0; i < MAX_POLYPHONY; i++) if(notes[i] == note) return i; return MAX_POLYPHONY + 1; } // add a new bit to the incomplete midi data variable void shiftInMSB(bool state){ if(state){ partialMidiData >>= 1; partialMidiData |= BIT7; } else{ partialMidiData >>= 1; } } // add the new midi data to the incomplete midi data variable void addBits(unsigned int stateCount, bool state){ while(stateCount > TICKS_PER_BIT / 2){ if(!(partialMidiData & BIT0)){ // are we shifting out the start bit? // if(haveNewMidiByte) // while(1); // catch when we miss midi data (for debugging) shiftInMSB(state); newMidiByte = partialMidiData; // if((newMidiByte != 0xFE) && (newMidiByte != 0xFF)) if(newMidiByte != 0xFE) haveNewMidiByte = true; partialMidiData = 0xFF; } else shiftInMSB(state); stateCount -= TICKS_PER_BIT; } } // CCR1 capture interrupt. Triggers on both edges, adds fresh midi data #pragma vector=TIMERA1_VECTOR __interrupt void CCR1_interrupt(void){ TAR = 0; TACCTL0 &= ~CCIFG; unsigned int tEdge = TACCR1; midiRXBitState = PIN_MIDI_DATA & P1IN; TAIV = 0; addBits(tEdge, !midiRXBitState); } // Like a timeout to add midi data if there's new data, but no edge in too long #pragma vector=TIMERA0_VECTOR __interrupt void CCR0_interrupt(void){ // don't add data if no partial data received, unless the new is all 0's if((partialMidiData != 0xFF) || !midiRXBitState) addBits(9 * TICKS_PER_BIT, midiRXBitState); // in case we captured an edge during this ISR, don't re-add these bits // in the CCR1 ISR TACCR1 = 0; } // ############################################################ // ###################### I2C ADC Stuff ####################### void updateADC(void){ Setup_USI_Master_TX(); USICTL1 |= USIIFG; // Set flag and start communication } void Data_TX (void){ USISRL = MST_Data[byteCount]; // Load data byte USICNT |= 0x08; // Bit counter = 8, start TX I2C_State = 10; // next state: receive data (N)Ack byteCount++; } void Setup_USI_Master_TX (void) { _DINT(); byteCount = 0; Transmit = 1; USICTL0 = USIPE6+USIPE7+USIMST+USISWRST; // Port & USI mode setup USICTL1 = USII2C+USIIE; // Enable I2C mode & USI interrupt USICKCTL = USIDIV_3+USISSEL_2+USICKPL; // USI clk: SCL = SMCLK/128 USICNT |= USIIFGCC; // Disable automatic clear control USICTL0 &= ~USISWRST; // Enable USI USICTL1 &= ~USIIFG; // Clear pending flag _EINT(); } // I2C handler for ADC adapted from TI example code. This runs pretty quick, // so hopefully it doen't clash with Midi input interrupts. #pragma vector = USI_VECTOR __interrupt void USI_TXRX (void) { switch(__even_in_range(I2C_State,14)) { case 0: // Generate Start Condition & send address to slave byteCount = 0; USISRL = 0x00; // Generate Start Condition... USICTL0 |= USIGE+USIOE; USICTL0 &= ~USIGE; USISRL = SLV_Addr; USICNT = (USICNT & 0xE0) + 0x08; // Bit counter = 8, TX Address I2C_State = 2; // next state: rcv address (N)Ack break; case 2: // Receive Address Ack/Nack bit USICTL0 &= ~USIOE; // SDA = input USICNT |= 0x01; // Bit counter=1, receive (N)Ack bit I2C_State = 4; // Go to next state: check (N)Ack break; case 4: // Process Address Ack/Nack & handle data TX USICTL0 |= USIOE; // SDA = output if (USISRL & 0x01) // If Nack received... { // Send stop... USISRL = 0x00; USICNT |= 0x01; // Bit counter=1, SCL high, SDA low I2C_State = 14; // Go to next state: generate Stop } else { // Ack received, TX data to slave... USISRL = MST_Data[byteCount]; // Load data byte USICNT |= 0x08; // Bit counter = 8, start TX I2C_State = 10; // next state: receive data (N)Ack byteCount++; } break; case 10: // Receive Data Ack/Nack bit USICTL0 &= ~USIOE; // SDA = input USICNT |= 0x01; // Bit counter = 1, receive (N)Ack bit I2C_State = 12; // Go to next state: check (N)Ack break; case 12: // Process Data Ack/Nack & send Stop USICTL0 |= USIOE; if (byteCount == number_of_bytes){// If last byte USISRL = 0x00; I2C_State = 14; // Go to next state: generate Stop USICNT |= 0x01; } // set count=1 to trigger next state else{ Data_TX(); // TX byte } break; case 14:// Generate Stop Condition USISRL = 0x0FF; // USISRL = 1 to release SDA USICTL0 |= USIGE; // Transparent latch enabled USICTL0 &= ~(USIGE+USIOE); // Latch/SDA output disabled I2C_State = 0; // Reset state machine for next xmt break; } USICTL1 &= ~USIIFG; // Clear pending flag } The right side of the image looks cut off. [Right-click -> view image] to see the whole thing. NOTE: There should be about a 190K resistor between the DAC and the amp in the schematic. Not entirely necessary, but will scale the DAC output down to something more reasonable for the LM386 Link to post Share on other sites
Recommended Posts