Hoi.
Ik ben bezig met het ontwerpen van een elektronische aansturing voor een beiaard. Deze moet met MIDI werken.
Het is me gelukt de midi data via UART te ontvangen. (Na een weerstand en een ontkoppelcapaciteit toe te voegen)
Alle (denk ik toch) note-on events worden ontvangen en de PWM duty cycle wordt correct aangepast volgens de ontvangen velocity. Bij Note-off events gebeurt er echter niets (het zou ook een note-on met velocity 0 kunnen zijn). Verder gebeurt er ook nog dit:
Als ik toets C4 aangeslagen heb, verandert de PWM duty cycle correct. Echter, meestal, als ik daarna nog een toets (moet niet C4 zijn) aansla, dan wordt de duty cycle aangepast aan de ontvangen noot (eerste data byte dus) en niet aan de ontvangen velocity (tweede data byte). Volgens mij is er iets wat ik over het hoofd zie.

Ik zit momenteel echt vast, dus ik zou enorm dankbaar zijn moest iemand me nu kunnen helpen.

Ik werk met een ATMega168 µC.

Dit is mijn code (midi verwerking gebeurt in de main routine):

#include <stdio.h>
#include <stdbool.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/pgmspace.h>

#define PWMDDR     	DDRD
#define PWMPIN     	PD6
#define PWMENDDR	DDRD
#define PWMENPIN	PD2 //INT0
#define PWMENPORT	PORTD

#define NOTE 0x3C //Note C4

//set desired baud rate

#define BAUDRATE 31250

//calculate UBRR value

#define UBRRVAL ((F_CPU/(BAUDRATE*16UL))-1) //Divide by 8 i.s.o. 16 if U2X0 bit is set
//#define UBRRVAL ((F_CPU/(BAUDRATE*8UL))-1) //Divide by 16 i.s.o. 8 if U2X0 bit is not set

volatile uint8_t rxbuff; //Last character read from the UART
volatile uint8_t laststatus;
volatile uint8_t lastnote;
volatile uint8_t lastvelocity;
volatile uint8_t databyteindex;

const unsigned char VelocityToPWM[] PROGMEM =
{
	0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,
	46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,
	90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,
	126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,
	160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,
	194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,
	228,230,232,234,236,238,240,242,244,246,248,250,252,254
};

void USART_Init(uint16_t ubrr)
{
	UBRR0H = (uint8_t)(ubrr>>8);
	UBRR0L = (uint8_t)(ubrr & 0xFF);
	//UCSR0A |= _BV(U2X0); //Improves baud rate error
	//Enable receiver and transmitter and Rx complete interrupt (must provide routine)
	UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
	// Set frame format: asynchronous, no parity, 8 bit data, 1 stop bit
	UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
}

void note_off(void)
{
	if (lastnote == NOTE)
	{
		OCR0A = 0; //Set compare value to 0, so PWM is off
	}
}

void note_on(void)
{
	if (lastvelocity == 0) {note_off();}
	else {
		if (lastnote == NOTE)
		{
			OCR0A = pgm_read_byte(&(VelocityToPWM[lastvelocity]));		
		}
		//OCR0A = 0xBF; //Set PWM duty cycle to 3/4
	}
}

int main(void)
{
	uint8_t temp;

	DDRD &= ~_BV(PD3);
	PORTD = 0;

	DDRB |= _BV(PB1);
	PORTB = 0;

	//Set PWM pin as output
	PWMDDR |= _BV(PWMPIN);
	OCR0A = 0; //Set compare value to 0, so PWM is off
	//Set Timer0 as 8-bit FastPWM, set when upcounting and running at full MCU clock
	TCCR0A = ( (_BV(WGM01) | _BV(WGM00) | _BV(COM0A1)) & ~_BV(COM0A0) );
	TCCR0B = ( _BV(CS00) & ~_BV(WGM02) & ~_BV(CS01) & ~_BV(CS02) );	

	PWMENDDR &= ~_BV(PWMENPIN); //Set as input
	PWMENPORT |= _BV(PWMENPIN); //Enable pull-up resistor

	EICRA = (_BV(ISC01) & ~_BV(ISC00)) | (_BV(ISC10) & ~_BV(ISC11)); //Set INT0 for falling edge, INT1 for logical change
	EIMSK |= _BV(INT0) | _BV(INT1);

	USART_Init(UBRRVAL);

	sei();

	while(1)
	{
		temp = rxbuff;
		if (temp!=0) {
			rxbuff = 0;

			//Send the received data back

			//wait for empty transmit buffer
			while (!(UCSR0A&(1<<UDRE0))){};
			//send received data back
			UDR0=temp;


			//Process the received data

			if (bit_is_set(temp,7)) //Status byte 
			{
				if (temp < 0xF8)
				{
					laststatus = temp;
					databyteindex = 0;
				}
				else {} //System Realtime message, just ignore
			}
			else //Data byte
			{
				databyteindex++;
				switch (laststatus & 0xF0) //Command part of the status byte
				{
					case 0x90: //Note on
					{
						switch (databyteindex) {
							case 1: lastnote = temp; break;
							case 2: lastvelocity = temp; databyteindex = 0; note_on(); break;
							default: databyteindex = 0; break; //This should never happen!!
						}
						break;
					}
					case 0x80: //Note off
					{
						switch (databyteindex) {
							case 1: lastnote = temp; break;
							case 2: lastvelocity = temp; databyteindex = 0; note_off(); break;
							default: databyteindex = 0; break; //This should never happen!!
						}
						break;
					}
				}
			}
		}
	}

	return 0;

}

ISR (INT0_vect)
{
	OCR0A = 0; //Set compare value to 0, so PWM is off
	//OCR0A = 0xBF; //Set PWM duty cycle to 3/4
}

ISR (INT1_vect) //Only for debugging
{
	//copy input bitstream to output PB1
	if (bit_is_set(PIND,PD3)) {PORTB |= _BV(PB1);}
		else {PORTB &= ~_BV(PB1);}
}

/*
 * UART receive interrupt.  Fetch the character received and buffer
 * it, unless there was a framing error.
 */
ISR (USART_RX_vect)
{
 	uint8_t c;
 	
	c = UDR0;
 	if (bit_is_clear(UCSR0A, FE0)) //No framing error
   	{
		rxbuff = c;
    }
}

Alvast bedankt!

Ok. Ik heb de Rx ingang bitstream en de Tx teruggezonden bitstream aan een digitale scope gehangen.
Blijkbaar, bij een note-off event (dat eigenlijk een note-on event met velocity 0 is) wordt de tweede data byte, met de velocity van 0, niet ontvangen.

Hieronder twee scope afbeeldingen. De verticale stippellijnen geven het eerste frame aan (dt = 0,32ms). Je ziet duidelijk dat er 3 boodschappen verzonden worden, maar slechts de eerste twee worden teruggestuurd.

Enig idee wat het probleem zou kunnen zijn en hoe het op te lossen?

Ontvangen bitstream:
http://stijns.ulyssis.be/Rx.png

Teruggezonden bitstream:
http://stijns.ulyssis.be/Tx.png

Nu voel ik me echt dom.
Ik negeerde in mijn code altijd bytes die 0 waren (betekende voor mij dat er geen nieuwe data was). Maar een byte met waarde 0 kan dus wel bij midi.
Ik heb nu een bool toegevoegd die bijhoudt of er nieuwe data is.

Ik moet echt wat slaap hebben :D

Beschouw dit topic maar als gesloten.
Sorry voor de overbodige post, maar misschien heeft iemand nog iets aan mijn code.
Ik voeg nog even de werkende code toe hieronder.
En nog een paar tips: gebruik ALTIJD een ontkoppelcapaciteit tussen Vcc en gnd, ook bij het testen; gebruik een weerstand tussen uitgang en diode, ook al wordt de stroom door de µC beperkt, dit zorgt voor storingen.

#include <stdio.h>
#include <stdbool.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/pgmspace.h>

#define PWMDDR     	DDRD
#define PWMPIN     	PD6
#define PWMENDDR	DDRD
#define PWMENPIN	PD2 //INT0
#define PWMENPORT	PORTD

#define NOTE 0x3C //Note C4

//set desired baud rate

#define BAUDRATE 31250

//calculate UBRR value

#define UBRRVAL ((F_CPU/(BAUDRATE*16UL))-1) //Divide by 8 i.s.o. 16 if U2X0 bit is set
//#define UBRRVAL ((F_CPU/(BAUDRATE*8UL))-1) //Divide by 16 i.s.o. 8 if U2X0 bit is not set

volatile uint8_t rxbuff; //Last character read from the UART
volatile bool rxnew;
uint8_t laststatus;
uint8_t lastnote;
uint8_t lastvelocity;
uint8_t databyteindex;

const unsigned char VelocityToPWM[] PROGMEM =
{
	0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,
	46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,
	90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,
	126,128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,
	160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,
	194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,
	228,230,232,234,236,238,240,242,244,246,248,250,252,254
};

void USART_Init(uint16_t ubrr)
{
	UBRR0H = (uint8_t)(ubrr>>8);
	UBRR0L = (uint8_t)(ubrr & 0xFF);
	//UCSR0A |= _BV(U2X0); //Improves baud rate error
	//Enable receiver and transmitter and Rx complete interrupt (must provide routine)
	UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
	// Set frame format: asynchronous, no parity, 8 bit data, 1 stop bit
	UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
}

void note_off(void)
{
	if (lastnote == NOTE)
	{
		OCR0A = 0; //Set compare value to 0, so PWM is off
	}
}

void note_on(void)
{
	if (lastvelocity == 0) {note_off();}
	else {
		if (lastnote == NOTE)
		{
			OCR0A = pgm_read_byte(&(VelocityToPWM[lastvelocity]));		
		}
	}
}

int main(void)
{
	uint8_t temp;

	//Set PWM pin as output
	PWMDDR |= _BV(PWMPIN);
	OCR0A = 0; //Set compare value to 0, so PWM is off
	//Set Timer0 as 8-bit FastPWM, set when upcounting and running at full MCU clock
	TCCR0A = ( (_BV(WGM01) | _BV(WGM00) | _BV(COM0A1)) & ~_BV(COM0A0) );
	TCCR0B = ( _BV(CS00) & ~_BV(WGM02) & ~_BV(CS01) & ~_BV(CS02) );	

	PWMENDDR &= ~_BV(PWMENPIN); //Set as input
	PWMENPORT |= _BV(PWMENPIN); //Enable pull-up resistor

	EICRA = _BV(ISC01) & ~_BV(ISC00); //Set INT0 for falling edge, INT1 for logical change
	EIMSK |= _BV(INT0);

	USART_Init(UBRRVAL);

	sei();

	while(1)
	{		
		if (rxnew) {
			temp = rxbuff;
			rxnew = false;			

			//Send the received data back

			//wait for empty transmit buffer
			while (!(UCSR0A&(1<<UDRE0))){};
			//send received data back
			UDR0=temp;


			//Process the received data

			if (bit_is_set(temp,7)) //Status byte 
			{
				if (temp < 0xF8)
				{
					laststatus = temp;
					databyteindex = 0;
				}
				else {} //System Realtime message, just ignore
			}
			else //Data byte
			{
				databyteindex++;
				switch (laststatus & 0xF0) //Command part of the status byte
				{
					case 0x90: //Note on
					{
						switch (databyteindex) {
							case 1: lastnote = temp; break;
							case 2: lastvelocity = temp; databyteindex = 0; note_on(); break;
							default: databyteindex = 0; break; //This should never happen!!
						}
						break;
					}
					case 0x80: //Note off
					{
						switch (databyteindex) {
							case 1: lastnote = temp; break;
							case 2: lastvelocity = temp; databyteindex = 0; note_off(); break;
							default: databyteindex = 0; break; //This should never happen!!
						}
						break;
					}
				}
			}
		}
	}

	return 0;

}

ISR (INT0_vect) //Turn off PWM
{
	OCR0A = 0; //Set compare value to 0, so PWM is off
}

/*
 * UART receive interrupt.  Fetch the character received and buffer
 * it, unless there was a framing error.
 */
ISR (USART_RX_vect)
{
 	uint8_t c;
 	
	c = UDR0;
 	if (bit_is_clear(UCSR0A, FE0)) //No framing error
   	{
		rxbuff = c;
		rxnew = true;
    }
}

Ja klopt :) , databytes kunnen 0 zijn (en dan zijn alle bits in de byte 0.)
Als msb = 1 is het altijd de statusbyte, bij de vervolg bytes is msb altijd 0 bij MIDI.
Gaat ook maar van 0 - 127 (7 bit data), want 1 erbij en de msb wordt 1. (dan zou het problemen gaan geven, dan zouden de databytes statusbytes kunnen worden.
Het is allemaal 7 bit data. (behalve pitchwheel is 14 bit, das weer 2*7bit.)

Nu ja, je bent er snel achter. Soms is de vraag stellen inderdaad hem beantwoorden.
Ik heb ook wel eens CHR$(0) en "" door elkaar gehaald. :+

Maar zou je nog iets willen zeggen over je beiaard-interface? Bedoel je dat je de midi notes omzet naar het aanslaan van de juiste klok? Klinkt wel spannend, vertel eens?

ik weet dat flash ruimte tegenwoordig geen kont kost maar kan je de VelocityToPWM[] tabel niet net zo makkelijk vervangen door de index 1 positie naar links te shiften?
Zoals ik het nu zie is het een lookupTable die de index maal 2 vermenigvuldigd. Of kan dit variëren?

De bedoeling is inderdaad om met de info uit het binnenkomende midi signaal de juiste klok met de juiste kracht te luiden.
Daarvoor dient dan ook die lookup-table. Ik weet namelijk nog niet welke duty cycle je moet hebben om de klok te luiden met een bepaalde geluidssterkte. Bij velocity 1 zal deze al wel vrij groot moeten zijn.
Misschien moet ik in het begin steeds een duty cycle van 100% geven zodat de magnetisatie zo snel mogelijk gebeurt en dan overschakelen op de juiste waarde?
Dit zal allemaal nog uit tests moeten blijken.

Wat er nog bijkomt: een NMOS om de power te leveren, een LRC filter om te ontstoren, vrijloopdiodes en uiteraard nog een elektromagneet. Alleen heb ik hier totaal geen gedacht van hoe sterk deze moet zijn. Iemand anders misschien?

Het project is trouwens bedoeld voor een mobiele beiaard met kleinere klokjes dan die in de kerk (om dat anders te vervoeren is ook niet zo praktisch).

Hé, wat leuk. Nu, dat gaat vast wel lukken zo te zien. Zelf heb ik natuurlijk geen ervaring met dit soort dingen.
Het idee om de spoel eerst vol aan te sturen en dan terug te nemen naar de juiste waarde (overeenkomend met de velocity-waarde) klinkt logisch. Misschien gaat dat nog wel net zo makkelijk in de hardware (in de geest van: geladen condensator en de rest uit een Velocity-geregelde stroombron). Hoewel het natuurlijk ook te programmeren moet zijn, ik vergeet steeds dat daar zoveel mee kan. :)