Jump to content
43oh

[ ENDED ] April 2011 - 43oh Project of the Month Contest


The April 2011 - 43oh Project of the Month Contest  

28 members have voted

You do not have permission to vote in this poll, or see the poll results. Please sign in or register to vote in this poll.

Recommended Posts

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

Link to post
Share on other sites

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...

DSCF4857.JPG

DSCF4853.JPG

Source code

 

Full writeup with video and parts list

LCDClock.c

Link to post
Share on other sites

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.

 

post-197-135135496908_thumb.jpg

post-197-13513549664_thumb.png

 

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
}

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

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.

controller1.jpg

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.

 

GoProControllerSchematic.gif

 

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

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
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
  • 1 month later...

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
  • 2 weeks later...

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...

 

MSP430_LED_Control_tlc5940.png

 

 

// 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
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

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
}

 

 

dac.jpg

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

 

dac_amp_top.jpg

 

dac_amp_bottom.jpg

 

optoisolator.jpg

 

optoisolator_top.jpg

 

optoisolator_Bottom.jpg

Link to post
Share on other sites
Guest
This topic is now closed to further replies.
×
×
  • Create New...