Midi auf dem Atmega8... unzuverlässige Werte... What do?

danke. das ist sehr aufschlussreich. ich triggere flankengesteuert. also alles safe..


jetzt mal wieder zurück zur optokoppler front: ich habe jetzt mal den PC900 verbaut. der versprach ja ordentlich schnell zu sein. leider kommt es zu komischen effekten. das erste byte der midi-messages wird, soweit ich es sagen kann, nicht korrekt übertragen. statt ner 10010000 (note-on event) kommt ne 11011000 raus. das zweite byte passt (gibt die tonhöhe, in meinem fall ein mittleres C vor). insgesamt verhält sich dadurch natürlich mein programm völlig falsch. mit dem alten (langsamen) optokoppler lief alles super, wenn man davon absah, dass ab und zu einfach eine komplette midi-message nicht erkannt wurde, und dementsprechend auch kein interrupt ausgelöst wurde. mit diesem hier kommen FALSCHE bytes an. da ich erst gestern eine midi-learn funktion implementiert habe (ich drücke auf einen knopf, das nächste event wird sich gemerkt und fortan als vergleichswert für künftige midi-messages heran gezogen) lasen sich dem programm zwar die falsche midi-message als "richtige" bei bringen, aber zufriedenstellend ist das nicht. zumal ich eben herrausfand, dass ab und zu auch midi-messages als richtig interpretiert werden, wenn ich nur mal ein paar sekunden wie wild an einem kontroller-poti zwirbele. dabei werden einfach so schnell so viele midi-messages ausgeschüttet, dass dann irgendeine schon ins rasta passt.

das sollte alles nicht so sein...

offenbar tut der Optokoppler seinen dienst sehr schlecht. habt ihr ne idee?
 
...statt ner 10010000 (note-on event) kommt ne 11011000 raus...
Hmm... sieht so aus, als wenn die Flanke nicht schnell genug fällt...
Bringt es was, den Pullup zu vergrößern?
Möglicherweise steuert der OK auch nicht voll durch, sodaß er wie'n Widerstand wirkt, und zusammen mit drr Eingangskapazität des AVR-Beinchens für die fallende Flanke 'n Tiefpaß bildet...
(In dem Fall köntest Du versuchsweise den 220R direkt am OK verkleinern (irgend'n sinnigen (großen) R parallelschalten). - Man müßte mal mit'nem Oszi vor und nach dem OK vergleichen (cave!: unterschiedliche Gnd-Potentiale, oder?))
 
Hi,

- Man müßte mal mit'nem Oszi vor und nach dem OK vergleichen (cave!: unterschiedliche Gnd-Potentiale, oder?))

Theoretisch ja ...

Beitrag #12 von matze f.
also ist doch mein midi auf der leitung erstmal ne stromschleife, die ich dann mit dem optokoppler wieder in spannung umwandle... und dann versteh ich einfach nicht, was dieser 220 ohm widerstand auf der rechten seite (vor dem optokoppler) tut...

MIDI-Kabel.png
schlimmer ist allerdings der 220R nach +5V und der 220R zum UART im Sender. Die Kombination aus OK-LED und 220R im Empfänger schwebt also sozusagen zwischen GND und Vcc.

Wenn man dann ein Zweikanal-Oszi am Empfänger und Sender anschließt, dann legt man entweder den Sender tot (GND am 220R nach Vcc und damit an der Anode der OK-LED) oder man gibt dem UART echt was zu knabbern da er nur noch den 220R nach GND treibt (also 3facher Strom). Damit ist dann die OK-LED dauernd an.

Da kann man eigentlich nur am Empfänger mit nem normalen Oszi-Kanal und am Sender mit nem Differenzialtastkopf arbeiten.

Wenn Sender und Empfänger-GND verbunden/gleich sind, dann kann man auf Senderseite das heiße Ende des Tastkopfes auf den MIDI-Ausgang vom UART (also hinter dem 220R) legen. Damit gehen dann aber ein paar Werte (zB Strom durch die Diode) auf der Anzeige verloren. Man sieht lediglich das gesendete Bitmuster.

Auf jeden Fall muß man etwas überlegen bevorman das Oszi "mal eben" anklemmt. ;)

Gruß
Dino
 
Ich hab hier irgend'n Datenblatt von Sharp gefunden, aber kA, obs das richtige ist...

Der Koppler hat'n integrierten "Verstärker" drin, der 'ne Stromversorgung erfordert. Und als Pullup sind da 280Ohm angegeben. Für den Detector 4mA bei 1,1V oder so...

Dein Datenblatt?

@Dino: genau, an 'n Differenztastkopf dachte ich...
 
Hallo,

Ich hab hier irgend'n Datenblatt von Sharp gefunden, aber kA, obs das richtige ist...

Der Koppler hat'n integrierten "Verstärker" drin, der 'ne Stromversorgung erfordert. Und als Pullup sind da 280Ohm angegeben. Für den Detector 4mA bei 1,2V oder so...

Dein Datenblatt?

hier mal das Datenblatt ... Anhang anzeigen PC900V_DigitalOutputType_OPIC_10s.pdf

Auf Seite 2 sieht man ganz deutlich das der Empfänger des OK zwingend eine Versorgung benötigt.
Wenn man den wie einen normalen OK einfach als Transistor ansieht, dann wird das nicht funktionieren.

@Matze : Wie hast du den angeschlossen? Der sollte eigentlich am Pin6 Vcc dran haben. Am Pin5 gehört GND dran und am Pin4 gehört ein PullUp nach Vcc dran und dein Atmel. Ob der PullUp nun wirklich 280R haben soll oder auch ein 1k reicht kann man ja mal testen. Auf jeden Fall ist der Empfänger ein IC und kein reiner Fototransistor.

Auf die Pinbelegung am Empfänger achten!! Da ist im Datenblatt zwischen Innenschaltung und IC-Bild Pin4 und Pin5 verdreht!! Also aufpassen. Das verwirrt an der Stelle leicht.

Auf Seite 4 Fig10 sieht man das die H->L und L->H Zeiten bei 4-5mA LED-Strom am kürzesten sind. Bei höherem Strom geht die L->H Zeit nach oben weil der Empfänger gesättigt ist.

Daneben in Fig11 sieht man das gefahrlos auch nen 1k-Load statt dem 280R verwendet werden kann ohne die Rise/Fall-Zeiten zu stark zu verschlechtern. Mehr sollte man aber nicht nehmen.

1. It is recommended that a by-pass capacitor of more than 0.01µF is added between VCC and GND near the device in order to stabilize power supply line.
2. Handle this product the same as with other integrated circuits against static electricity.

Gruß
Dino
 
Wie hast du den angeschlossen? Der sollte eigentlich am Pin6 Vcc dran haben. Am Pin5 gehört GND dran und am Pin4 gehört ein PullUp nach Vcc dran und dein Atmel. Ob der PullUp nun wirklich 280R haben soll oder auch ein 1k reicht kann man ja mal testen. Auf jeden Fall ist der Empfänger ein IC und kein reiner Fototransistor.
jup, das hab ich schon richti gemacht denke ich. hab auch auf der anderen seite mit dem oszi geschaut. kommt schöner pegel raus...

Auf die Pinbelegung am Empfänger achten!! Da ist im Datenblatt zwischen Innenschaltung und IC-Bild Pin4 und Pin5 verdreht!! Also aufpassen. Das verwirrt an der Stelle leicht.

ja, das hab ich am anfang gleich mal verkehrt gemacht und den ersten OK gegrillt. und die dinger kosten sch** 1,70 € beim conrad!!

ich habe gestern das ganze nochmal auf ein neues board aufgelötet. jetzt funktioniert es. das einzige, was ich geändert habe, war der ic-sockel, den ich jetzt weggelasen habe. offenbar hatter der so viel kapazitäten, dass dadurch die flanken unsauber wurden. jetzt werden die bytes richtig übertragen. ich werden das die tage nochmal mit sockel aufbauen, um endgültig zu klären ob es daran lag...

aber es droht schon ein neues problem.. irgendwie werde ich nicht fertig....:

mir ist aufgefallen, dass zwar die interrupts sauber ausgelöst werden, aber nur, wenn ich zwischen den einzelnen "tastendrücken" ein wenig zeit vergehen lassen. am besten wurde das deutlich, wenn ich einen drehpoti von meinem midi-controller bewegte. da war es dann so, dass zwar bei kurzen drehimpulsen der atmega8 all das tat, was man von ihm erwartete, wenn man aber ohne pause hin und her drehte, so geschah nur am anfang was, danach nichts. das machte mich stutzig, weil eigentlich sollten ja auch in dem fall des ununterbrochenen drehens die richtigen midi-nachrichten (also das 1. byte der drei) mit der richtigen kanalnummer durchkommen. dem war aber nicht so. es machte den anschein, als ob dann überhaupt nichts gesendet wird, was ja nicht sein kann. also habe ich da heute mal etwas hinterher geforscht.
tja, und jetzt bin ich wieder mal ziemlich frustriert, da ich gerade noch dachte, ich hätte uart und midi im griff, und jetzt geht nix wie es soll:

das kleine programm das ich schrieb sollte einfach nur in ner endlosschleife rumhängen und drauf warten, dass das uart-interrupt ein register ("flag0") mit einsen füllt. ist dem so, wird verzweigt, eine LED-kette angemacht und ein zählregister in einer schleife hochgezählt. wenn das 0xFF erreicht wird wieder verzweigt, die LEDs ausgeschaltet und die flag0 wieder zurück gesetzt. damit das nicht zu schnell geschieht, und man auch deutlich sieht, dass die LEDs an waren, habe ich in die zählschleife noch ein paar hundert NOPs eingefügt. (das ist zwar nicht die feine englische art, und bei mikrocontroller.net würde ich bestimmt auch mächtig war zu hören kriegen dafür, aber hier sollte es einfach mal um die funktionsprüfung gehen... quick an dirty)
anschließend wird wieder zurück in die haupt-schleife gesprungen, wo auf das nächste interrupt gewartet wird, das die flag0 mit 0xFF füllt. soweit die theorie.
ES FUNKTIONIERT EINFACH NICHT!! die LEDs gehen an, und nicht mehr aus. ganz so, als ob das programm nie die stelle erreicht an der die flag zurück gesetzt wird und die LEDs überschrieben werden. hab den ganzen (!!) tag damit verbrach herrauszufinden war da los ist. habe dazu zum test ein zweites interrupt (flankengesteuert) eingebaut, das ich einfach mit taster auslösen kann. DA HAT ES AUCH FUNKTIONIERT. die LEDs gehen kurz an, danach wieder aus. obwohl im uart interrupt-vektor exakt das gleiche steht wie im INT0-vektor. da wird nix anderes gemacht, als das flag0-register gesetzt.

Ganz offenbar habe ich etwas fundamental wichtiges bei interrupts und Uart noch nicht verstanden. oder es ist ein fehler in der matrix. letzters ist nicht unwahrscheinlich ;-)


hier mal der quellcode. vielleicht könnt ihr meinen denkfehler sehen:
Code:
#define __SFR_OFFSET 0 
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>

#define flag0 r19
#define temp0 r20
#define zeit1 r24		//in diesen beiden register wird die zeit für die ausgabe hochgezählt

.section .text
.global main

main:

//*********************Stackpointer initialisierung*********************
ldi temp0, hi8(RAMEND)
out SPH, temp0			//über die vorgeschriebene reihenfolgen hier findet man überall abweichende angaben. ich hab beide varianten ausprobiert. hat keinen unterschied gemacht..

ldi temp0, lo8(RAMEND)
out SPL, temp0

//*********************Ports konfigurieren*********************
ldi temp0, 0xFF				
out DDRB, temp0


//*********************Uart konfigurieren*********************
ldi temp0, 0b00000000			//läd das obere Baudratenregister. wichtig!: erst das high-register beschreiben, dann das low.
out UBRRH, temp0

ldi temp0, 0b00000111			//läd das untere Baudratenregister. 
out UBRRL, temp0

ldi temp0, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)	//Frame format auf 8 bit setzten. kann man womöglich auch weglassen
out UCSRC, temp0

sbi UCSRB, RXCIE
sbi UCSRB, RXEN

//*********************INT0 initialisieren*********************
ldi temp0, (1<<ISC01) | (1<<ISC00) 	//INT0 auf steigende flanke konfigurieren
out MCUCR, temp0

ldi temp0, (1<<INT0) 			//INT0 aktivieren
out GICR, temp0


//*********************initialisieren*********************
ldi temp0, 0b11111110			//Eine LED leuchtet, wenn startbereit
out PORTB, temp0

ldi temp0, 0x00
ldi flag0, 0
ldi zeit1, 0x00
sei


//*********************main-loop*********************\\

loop0:
	cpi flag0, 0xFF			//hier wird verglichen, ob in dem flag0-register 1en stehen. wenn ja -> verzweigen
	breq mrk1

rjmp loop0




//*********************blinken*********************\\

mrk1:
	
	ldi temp0, 0b10101010			//hier weden die LEDs angeschaltet.
	out PORTB, temp0
	
	rjmp nopz				//der besseren übersichtlichkeit wegen, stehen hier nicht direkt die vielen NOPs, sondern es wird gesprungen und danach wieder zurückgesprungen...
	nopz_ret:

	//cli
	cpi zeit1, 0xFF				//hier wird vergleichen, ob das zeit1-register schon mit 255 gefüllt ist.
	breq mrk2
	inc zeit1				// hier wird zeit1 hochgezählt
	//sei

rjmp loop0

mrk2:
	ldi flag0, 0x00				//flag0 wieder zurück setzten
	ldi temp0, 0xFF
	out PORTB, temp0			// LEDs ausschalten (negiert!)
	ldi zeit1, 0				// zeit1 zurücksetzten
rjmp loop0

nopz:
	nop
	nop
	.
	.
	.
	// hier stehen eben wie gesagt ca. 250 NOPs. hab die mal weggelassen... bitte nicht zu sehr schimpfen deswegen...
	.
	.
	nop
	nop

rjmp nopz_ret


//*********************Uart interrupt*********************
.global INT0_vect
INT0_vect:
	ldi flag0, 0xFF
reti	


.global USART_RXC_vect
USART_RXC_vect:
	ldi flag0, 0xFF

reti




diese stelle ist auch interessant:
//cli
cpi zeit1, 0xFF
breq mrk2
inc zeit1
//sei

wenn mal cli un sei hier mit rein nimmt, dann schaltet sich zwar die LEDs nach einem Uart-interrupt wieder aus, dafür kann man sowohl das uart- als auch das int0 interrupt nicht wiederholt abfeuern...
ich seh echt nicht durch. bitte helft mir, wenn ihr könnt...
 
Hallo Matze,

dein Sourcecode ist mir noch nicht ganz verständlich.

Folgendes ist mir aufgefallen ...

Code:
    ldi temp0, 0b10101010            //hier weden die LEDs angeschaltet.
    out PORTB, ausgabe

Ich finde die Definition von "ausgabe" nicht.


Was passiert, wenn zeit1 = 0xFF ist und du "cli" und "sei" verwendest?
Code:
    cli
    cpi zeit1, 0xFF                //hier wird vergleichen, ob das zeit1-register schon mit 255 gefüllt ist.
    breq mrk2
    inc zeit1                // hier wird zeit1 hochgezählt
    sei

"sei" wird nicht ausgeführt, auch nicht im weiteren Programmablauf. USART und Ext0 Interrupt ISRs werden nicht ausgeführt.

Vielleicht bringt dich das schon mal auf Ideen.

Dirk :ciao:
 
Ich finde die Definition von "ausgabe" nicht.

ups. übertragungsfehler. in meinem programm habe ich ein register "ausgabe", dass ich mit 01010101 lade. das hab ich der besseren übersichtlichkeit wegen hier mal weg gelassen und den port direkt mit temp0 geladen. dann hab ich leider vergessen "ausgabe" durch temp0 zu ersetzten. hier ist es, wie es eigentlich aussieht:

Code:
ldi temp0, 0b10101010			//hier weden die LEDs angeschaltet.
out PORTB, temp0
habs oben auch editiert


"sei" wird nicht ausgeführt, auch nicht im weiteren Programmablauf. USART und Ext0 Interrupt ISRs werden nicht ausgeführt.
ja richtig, wenn ich das mit "sei" und "cli" schreibe, wird verzweigt bevor sei wieder erreicht wird. das kann nicht gehen...
aber diese beiden befehle hatte ich ja eigentlich garnicht drin....
 
1. Du liest das UDR nie aus, oder? Folglich wird der das IRQ-Flag nie gelöscht, der IRQ immer wieder getriggert.
2. Werden beim Midi doch Telegramme aus mehreren Bytes gesendet, oder? Schlägt dann möglicherweise der nächste RX-IRQ vielleicht während Deiner NOPs zu.
 
1. Du liest das UDR nie aus, oder? Folglich wird der das IRQ-Flag nie gelöscht, der IRQ immer wieder getriggert.
ne ich les das UDR nie aus. ich muss sagen, mir hat der begriff IRQ-flag auch bis eben nichts gesagt. ich vermute, dass das die flag ist, die anzeigt, ob ein interrupt aufgetreten wäre, während ein anderes abgearbeitet wurde... so richtig hat mir google zu diesem stichwort auch nicht weiter geholfen.
aber mir dämmert, dass das das problem ist. einfach mal das UDR auslesen sollte also die sache regeln, oder? dann wird dieses IRQ-flag resetet und alles sollte so laufen, wie ich das denke?


2. Werden beim Midi doch Telegramme aus mehreren Bytes gesendet, oder? Schlägt dann möglicherweise der nächste RX-IRQ vielleicht während Deiner NOPs zu.
ja, es werden immer 3 bytes bei normalen messages erzeugt. von so dingen wie sys-ex und so sehe ich mal ab. im ersten byte steht in den ersten 4 bits die event-art. z.b. note-on oder note-off, oder andere controller-werte (pitchwheel etc..). im zweiten byte steht im falle von note-on und note-off dann die tonhöhte. dabei ist das höchte bit vom zweiten byte immer ne null (im gegensatz zum ersten bit des ersten bytes, was immer ne 1 ist). im dritten byte steht im falle der note-on nachricht die anschlagsstärke, also ein wert von 0 bis 127, der angibt, wie schnell die taste gedrückt wurde (hier ist das oberste bit auch immer ne 0). hier ist die gemeinheit, dass bei manchen geräten die note-off nachrichten garkeine note-off nachrichten sind, sondern note-on nachrichten mit ner velocity von 0. aber das nur mal so am rande..

es ist sehr wahrschienlich, dass das zweite byte kommt, während die NOPs durchlaufen werden. aber das sollte ja nix machen. in dem fall würde die flag0 neu gesetzt werden, aber ansonsten an genau der selben stelle weiter gemacht werden, wo die NOPs verlassen wurde... in beiden fällen würden nach erreichen des endes der zeit-schleife die flag0 wieder resettet und gut is...
 
Ich versuch's mal einfach zu erklären:

normalerweise arbeitet der AVR sein Programm der Reihenfolge nach ab (also einen Befehl nach dem anderen - ok, dabei sind bedingte und unbedingte Sprünge möglich).
Zusätzlich kann das programm aber jederzeit unterbrochen werden und an einer festgelegten Stelle im Code fortgesetzt werden, wenn eben ein Interrupt-Ereignis eintritt, und dieser Interrupt selbst sowie die Interrupts global freigegeben sind.
Jede Interrupt-Quelle besitzt einen festen Einsprungpunkt im Program-Flash. Alle zusammen befinden sich direkt an dessen Anfang, man nennt diesen Bereich auch Interrupt Vector Table (IVT). Dort paßt dann je nur eine Sprunginstruktion rein, die auf die jeweilige Interrupt Service Routine (ISR) weiterleitet. Beim Eintritt des Interrupt wird automatisch die Rücksprungadresse auf dem Stack, folglich könnte man die ISR mit einem Return (RET) verlassen um wieder da weiterzumachen, wo der Interrupt unterbrach. (RET nimmt 'ne Adresse vom Stack, und setzt das Programm da fort). Da aber beim prung in die IVT Interrupts automatisch global deaktiviert werden (I in SREG gelöscht) wären die dann ja immer noch deaktiviert - deswegen verläßt man 'ne ISR üblicherweise mit Return from Interrupt - RETI.

Du hast also mehrere mögliche Interruptquellen (beim Mega8 sind es 18 und der Reset (welcher ja keiner ist)), jeder (außer Reset) hat in irgendeiner Art ein Flag, das das eingetretene Interrupt-Event signalisiert. Viele Interrupts müssen in irgendeiner Form konfiguriert werden, lokal befähigt werden und global befähigt werden. Bei vielen Interrupts wird das Flag durch den Sprung in die IVT wieder zurückgesetzt - aber nicht bei allen.
Mal konkret zum UART (des Mega8) - der wird mit folgenden I/O-Registern bedient:
  • UCSRA - USART Control and Statusregister A
    enthält 6 Flags:
    • RXC - USART Recieve Complete-Flag -> ist "1", solange sich "ungelesene" Bytes im recieve-Buffer befinden. Ist der Buffer leer, ist es "0". Der Buffer ist bei deaktiviertem Reciever immer leer. Dieses Flag triggert den "Recieve Complete Interrupt", so dieser lokal und global befähigt ist.
    • TXC - USART Transmit Complete-Flag -> wird jedesmal gesetzt, wenn ein kompletter Frame ("Byte") aus dem Transmit-Schieberegister rausgeschoben (gesendet) wurde, und sich auch kein weiteres Byte im Transmit-Buffer befindet, der Transmitter also nichts mehr zu tun hat. Dieses Flag triggert den "Transmit complete Interrupt" (so lokal und global befähigt). Das Flag wird beim Sprung in die IVT automatisch gelöscht, kann aber auch manuell gelöscht werden, indem man es mit einer 1 beschreibt.
    • UDRE - USART Data Register empty -> ist gesetzt, wenn (solange) der Transmit-Buffer leer ist (und ein neues Byte aufnehmen kann). Dabei kann der Transmitter selbst aber durchaus noch ein Byte aus dem Transmit-Schieberegister herausschieben. UDRE kann den "Data Register Empty-Interrupt) triggern (wenn befähigt).
    • FE - Frame Error -> wird gesetzt, wenn statt eines erwarteten Stop-Bits 'ne "0" empfangen wurde. Wird
      urch Auslesen von Udre automatisch gelöscht.
    • DOR - Data Overrun -> wird gesetzt, wenn beide Positionen im Recieve-Buffer besetzt sind (unelesen), ein vollständiges Byte im Recieve-Schieberegister wartet, UND das Startbit des nächsten Bytes empfangen wird. (klar, das kann jetzt nirgends mehr hingeschoben werden - Stau). Wird das UDR ausgelesen, rutschen die Buffer-Bytes nach, es ist wieder Platz - DOR wird also (erstmal) wieder zurückgesetzt
    • PE - Parity Error -> ist gesetzt, wenn das nächste Byte im Recieve-Buffer einen Paritätsfehler hat.
    Und 2 Konfigurationsbits
    • U2X - Double USART Transmission Speed -> halbiert den USART-Prescaler, was effektiv die Übergtragungsgeschwindigkeit verdoppelt. Allerdings läuft dann der Reciever auch nur noch mit 3 Samples im 8fach Oversampling (sonst 5 Samples im 16fach Oversampling).
    • MPCM - Multi Prozessor Communication Mode -> zieh ich mir jetzt nicht rein, damit scheint man mehrere Slaves mit einem Master betreiben zu können, wobei der Master seinen jeweiligen Slave vorher adressiert.

...mehr kommt später...
 
Hallo, der UART-krimi geht weiter ;-)

ein paar tage habe ich jetzt nicht weiter an dem projekt gearbeitet. jetzt aber wieder angefangen. und bin auf ein weiteres für mich unlösbares problem gestoßen.
mein programm tut im wesentlichen folgendes: wenn ich eine taste auf dem midi-keyboard drücke, dann wird das ankommende byte auf gewisse kriterien geprüft, und dann gegebenenfalls eine LED kurz angeschaltet. das hat soweit alles gut fuktioniert. allerdings ist mir aufgefallen, dass ich zwischen den tastendrücken immer ca. eine sekunde zeit vergehen lassen muss, sonst klappt es nicht. um das zu untersuchen, habe ich mal den code soweit wie möglich eingedampft. hier mal der wichtigste teil (die ganzen konfugurations-sachen hab ich jetzt mal weg gelassen):

Code:
sei

//*********************main-loop*********************
loop0:
	mov msg1, byte1 			//erstmal byte1 wieder frei machen

        cli                                             //es hat sich gezeigt, dass während des folgenden vergleichs die interrupts am besten ausgeschaltet sind
	cp msg1, key1				//überprüfen, ob note-on-event und channel mit key1 überein stimmen. (key1 enthält die schablone für das erste midi byte
	breq ausgabe_on
	ret_ausgabe_on:
        sei

	cpi zeit1, 5                             //das zeit1 register tickt gleichmäßig hoch. in ausgabe_on wird das wieder genullt. erreicht es dann wieder den wert 5, so werden die LEDs wieder ausgeschaltet.
	breq ausgabe_off
	ret_ausgabe_off:

rjmp loop0


//*********************ausgabe_off*********************
ausgabe_off:
	ldi temp0, 0xFF
	out PORTB, temp0
rjmp ret_ausgabe_off


//*********************ausgabe_on*********************
ausgabe_on:
	out PORTB, ausgabe
	ldi zeit1, 0
rjmp ret_ausgabe_on

//*********************Uart interrupt*********************
.global USART_RXC_vect
USART_RXC_vect:

	in byte1, UDR                   //hier wird das ankommende byte in das byte1-register kopiert, um dann in der main-loop weiter prozessiert zu werden
reti

//*********************TIMER0 Overflow interrupt*********************
.global TIMER0_OVF_vect
TIMER0_OVF_vect:

	inc zeit1                            //hier wird mit einem regelmäßig das zeit-register hochgezählt. beim led-anschalten wird zeit1 auf null gesetzt!
reti

wenn ich die leds direkt im uart-interrupt einschalte, dann geht es, wie man sich das vorstellt. ich kann so schnell wie ich will die tasten drücken und es blinkt stets. das ist ja schonmal ein gutes zeichen und zeigt, dass die hardware (der optokoppler) auf jedenfall schnell genug ist. allerdings verstehe ich nicht, wodurch diese Tot-zeit zustande kommt, wenn der code ist der obigen form vorliegt...

kann mir jemand weiter helfen?
 
Servus matze f.,

eigentlich sollte Midi kein so grosses Problem sein. Das Problem ist die Vielfalt.
Wenn ich Dich richtig verstanden hab, liest eine Interuptroutine die Daten ein, speichert sie, und dann verwendet das Hauptprogramm diese Daten. Richtig ?
Deinen Code hab ich noch nicht angesehen.
Rechne mal, wie genau Du mit Deinen Einstellungen die Bautrate einhälst.
Du darft mit der ISR nicht warten, bis alle Bytes eines Befehls komplett eingelesen sind.
Das dauert zu lange, weil Midi langsam ist. Ich hab es so gemacht, das ein Rx-Empfang einenIRQ ausgelöst.
Code:
   ldi temp, (1<<RXCIE0)|(1<<RXEN0) 
   sts UCSR0B, temp
Das ankomende Byte wird geholt und gespeichert.
Code:
     push temp
	 ldi  temp1_ISR, sreg

	 lds  temp, UDR0          ; Wert holen.

     sbrs temp, 7             ; MIDI Ststus-Byte?
     rjmp Datenbyte           ; Nein. Dann Datenbyte speichern
     andi temp, 0xf0          ; Midikanalinfo abtrennen
     cpi  temp, 0x80          ; Midi OFF ?
	 breq Midi_Note           ; Ja.  Speichern             
     cpi  temp, 0x90          ; Midi ON ?
     brne EOI                   ; weder Midi-ON noch OFF

Midi_Note:
     hier Midi Statusbyte speichern
 EOI:  ; End of IRQ
	 sts sreg, temp1_ISR
     pop temp

     reti

Datenbyte:

     hier die Datenbyts speichern
 
     rjmp EOI

Ende ISR. Code ist nur ein Auszug. Zum Speicher der Daten dient ein Zeiger auf eine Tabelle.
In meinem Beispiel werte ich nur Note ON/OFF aus. Midikanal ignoriere ich.
Beim nächsten Rx-IRQ holt sich die ISR das zweite Byte, das dritte, das vierte usw.
Das Hauptprogramm liest dann die Daten aus der Tabelle wieder raus, ohne sich um den Empfang kümmern zu müssen. Je nachdem, wie schnell sich das Hauptprogramm um die Daten kümmern kann sollte die Tabelle länger oder kürzer sein, damit keine Daten verloren gehen.
In der Interuptroutine selektiere ich die Daten schon vor. Kommt z.B. ein Befehl rein, der Controllerdaten sendet, speichere ich nicht und warte so lange, bis wieder z.B. ein Note ON Befehl kommt. Und: Manche Keyboards senden kein neues Statusbyte, wenn es sich nicht ändert. Also z.B. Note On gefolgt von 8x Notenwert + Velocity. Das spart Übertragungszeit.
Als Optokoppler verwende ich den 6N137.

Hoffe, das hilft Dir ein wenig weiter.
 
Hallo Gottfried

eigentlich sollte Midi kein so grosses Problem sein. Das Problem ist die Vielfalt.
Wenn ich Dich richtig verstanden hab, liest eine Interuptroutine die Daten ein, speichert sie, und dann verwendet das Hauptprogramm diese Daten. Richtig ?
Jup. allerdings bedeutet "speichern" hier, dass ich das einfach erstmal in ein register schreibe und nicht irgendwo in ne tabelle oder so...

Rechne mal, wie genau Du mit Deinen Einstellungen die Bautrate einhälst.
Du darft mit der ISR nicht warten, bis alle Bytes eines Befehls komplett eingelesen sind.
Das dauert zu lange, weil Midi langsam ist.
naja, ist ja nicht so, dass ich alle drei bytes abwarte und dann erst mit dem verarbeiten anfange. ich lese das erste byte, schau nach ob es eine note-on-message auf dem richtigen kanal ist. wenn das der fall ist, dann wird mit dem nachfolgenden byte anders verfahren...
Code:

ldi temp, (1<<RXCIE0)|(1<<RXEN0)
sts UCSR0B, temp

Das ankomende Byte wird geholt und gespeichert.
Code:

push temp
ldi temp1_ISR, sreg

lds temp, UDR0 ; Wert holen.

sbrs temp, 7 ; MIDI Ststus-Byte?
rjmp Datenbyte ; Nein. Dann Datenbyte speichern
andi temp, 0xf0 ; Midikanalinfo abtrennen
cpi temp, 0x80 ; Midi OFF ?
breq Midi_Note ; Ja. Speichern
cpi temp, 0x90 ; Midi ON ?
brne EOI ; weder Midi-ON noch OFF

Midi_Note:
hier Midi Statusbyte speichern
EOI: ; End of IRQ
sts sreg, temp1_ISR
pop temp

reti

Datenbyte:

hier die Datenbyts speichern

rjmp EOI
Hey kool. danke für den code. lass mich mal kurz beschreiben, wie ich den interpretieren würde. und berichtige mich, wenn ich falsch liege. zunächst rettest du den inhalt deines temp-registers auf den stag. dann lädst du den inhalt deines statusregisters in temp1_ISR, wobei ich nicht genau weiß, für was das gut ist. dann lädst du den wert deines angekommenen bytes in temp. dann überprüfst du, ob das oberste bit gesetzt ist, da dies ja anzeigt, ob es sich um das erste der drei bytes handelt. ich nehme jetzt mal an, der test war positiv. dann maskierst du die obersten vier bits, die ja den message-typ enthalten. wenn in diesen obersten vier bits dann ne midi-off nachricht steckt, dann verzweigst du wieder. wenn nicht, dann schaust du, ob ne midi-on nachricht drin steckt. wenn dem nicht so ist, dann springst du zu EOI, nimmst dir wieder den temp vom stack und springs in die main-loop zurück. wenn ne midi-on oder midi-off nachricht drin war, dann speicherst du die ja weg... warum? was macht dein programm dann später damit?
und wenn das oberste bit garkeine eins war, dann springs du gleich zu "datenbyte:" und speicherst den wert weg.

soweit so gut. das ganze steht also in deinem U-art interrupt, right? ist das nicht ein bisschen viel? ich dachte mir das halt so, dass ich mir in dem Uart interrupt nur kurz den wert vom UDR hole, und dann sofort wieder rauspringe und in der main-loop das ganze weiter verarbeite.
aber vielleicht ist das so wie du das gemacht hast viel schlauer. vor allem das "sbrs temp, 7" ist ne gute idee. das werde ich auch so machen. die idee erstmal alle daten in ne tabelle weg zu schreiben ist auch super. das hat mir echt VIEEEL gebracht. nochmals danke.

nichts desto trotz wäre es schön, wenn du auch mal nen kurzen blick auf meinen quelltext wirfst. viel komplizierter ist der auch nicht, und ich habe die befürchtung ich habe da wieder mal ne grundsätzliche verständnislücke... Ich kann mir diese kurze Totzeit einfach nicht erklären...
 
Servus,

LotadaC
Um es auch zu retten. Andi und CPI beeinflussen das SREG.
So ist es. SREG sollte in einer IRQ-Routine immer gespeichert werden und am Ende wieder hergestellt werden.
Code:
	in byte1, UDR                   //hier wird das ankommende byte in das byte1-register kopiert, um dann in der main-loop weiter prozessiert zu werden
reti
Speicherst Du alle ankommenden Daten in byte1, überschreibst also den vorherigen Wert? Das ist gefährlich, denn wie stellst Du sicher, das Dein mainprogramm den Wert verarbeitet hat bevor er überschrieben wird. Frägst Du ständig ab, ob was neues in byte1 steht. Wenn ja, dann kannst Du Dir das einlesen per IRQ sparen. Bringt keinen Zeitvorteil. Wenn Dein mainprogamm schnell genug ist, kannst Du gleich UDR einlesen ohne umweg über byte1.
wenn ne midi-on oder midi-off nachricht drin war, dann speicherst du die ja weg... warum?
Damit das Hauptprogramm sich den Wert holen kann wenn es den Wert braucht.
was macht dein programm dann später damit?
und wenn das oberste bit garkeine eins war, dann springs du gleich zu "datenbyte:" und speicherst den wert weg.
Mein Programm erzeugt eine Rechteckfrequenz die der Midinote entspricht. Bei C1 gebe ich z.B. eine Frequenz von 16,35Hz aus.
Bei A4 z.B. 220Hz usw. Die Frequenz erzeuge ich mit Timer2 im FastPWM-Mode.
Die Frequenz bleibt immer an. Mich interessiert im Prinzip nur Note ON, wenn sich der Wert der Taste ändert. Note Off brauch ich nur als GATE-Off Signal.

Ich kann mir diese kurze Totzeit einfach nicht erklären... .
Sieht aus, als werden da Daten verschluckt. Hast Da das mal komplett im AVR-Studio durchsimuliert.
Ich hatte mal den Stack nicht richtig gespeichert. dann ist er irgendwann übergelaufen und der ATMega hat sich geresetet. Das ging so schnell, ich merkte nur eine periodisches Auftreten eines fehlers. Achte beim Simulieren also auch auf den Stack und die Register, die in der ISR verwendet werden incl. SREG.

Die Baudrate für das Empfangen muss auf alle Fälle stimmen.

Dein Programm schau ich mal demnächst durch.

Gruss

Gottfried
 
Servus,

sei

//*********************main-loop*********************
loop0:
mov msg1, byte1 //erstmal byte1 wieder frei machen

cli //es hat sich gezeigt, dass während des folgenden vergleichs die interrupts am besten ausgeschaltet sind
Byte1 ist ja schon gesichert, warum also CLI. Stopt den Timer auch!
cp msg1, key1 //überprüfen, ob note-on-event und channel mit key1 überein stimmen. (key1 enthält die schablone für das erste midi byte
breq ausgabe_on
ret_ausgabe_on:
sei
Wenn die Taste gedrückt wurde, verzweigst Du und schaltest die Led ON.

cpi zeit1, 5 //das zeit1 register tickt gleichmäßig hoch. in ausgabe_on wird das wieder genullt. erreicht es dann wieder den wert 5, so werden die LEDs wieder ausgeschaltet.
breq ausgabe_off
ret_ausgabe_off:

rjmp loop0
In Ordnung denke ich
//*********************ausgabe_off*********************
ausgabe_off:
ldi temp0, 0xFF
out PORTB, temp0
rjmp ret_ausgabe_off
Zurück zum loop

//*********************ausgabe_on*********************
ausgabe_on:
out PORTB, ausgabe
ldi zeit1, 0
rjmp ret_ausgabe_on
Anstelle rjmp ret_ausgabe_on -> rjmp loop0. Abfrage Zeit=5 nutzlos, da jetzt gerade auf Null gesetzt.


Solage wie byte1=key1 verzweigst Du immer in diesen Programmteil.
Zeit= wird ständig auf null gesetzt bis ein Datenbyte ankommt.
Ist das so gewollt?
//*********************Uart interrupt*********************
.global USART_RXC_vect
USART_RXC_vect:

in byte1, UDR //hier wird das ankommende byte in das byte1-register kopiert, um dann in der main-loop weiter prozessiert zu werden
reti

//*********************TIMER0 Overflow interrupt*********************
.global TIMER0_OVF_vect
TIMER0_OVF_vect:

inc zeit1 //hier wird mit einem regelmäßig das zeit-register hochgezählt. beim led-anschalten wird zeit1 auf null gesetzt!
reti

Für mich ergibt sich daraus:
Die Zeit wird doch solange auf Null gesetzt, bis msg1 ungleich key1.
Setze Byte1 im Programmteil "Ausgabe On" auf Null. Ein einmaliges aufrufen dieses Teils reicht doch.
Hoffe ich blicke da einigermaßen durch und Du kommst weiter damit.
Der kurze Loop ist schnell genug, um alle byte zu lesen.
Schreib Dir einen Programmablaufplan. Dann wird die Sache übersichtlicher.

Gruß
 
Speicherst Du alle ankommenden Daten in byte1, überschreibst also den vorherigen Wert? Das ist gefährlich, denn wie stellst Du sicher, das Dein mainprogramm den Wert verarbeitet hat bevor er überschrieben wird. Frägst Du ständig ab, ob was neues in byte1 steht. Wenn ja, dann kannst Du Dir das einlesen per IRQ sparen. Bringt keinen Zeitvorteil. Wenn Dein mainprogamm schnell genug ist, kannst Du gleich UDR einlesen ohne umweg über byte1.

naja. das erste was ich mit byte1 mache ist, dass ich es in msg1 kopiere. danach wird weiter mit msg1 gearbeitet. kommt nun ein interrupt, so wird byte1 verändert, aber msg1 wird ja erstmal nicht angetastet, bis die ganzen nachfolgenden operationen abgearbeitet sind. kommt also irgendwann ein interrupt wird byte1 überschrieben, aber erst wenn die main-loop einmal durchlaufen ist, wird msg1 neu beschrieben und alles kann von neuem losgehen und verglichen werden... ich hielt das für ne gute idee. natürlich ist die sache mit der tabelle schlauer...

Sieht aus, als werden da Daten verschluckt. Hast Da das mal komplett im AVR-Studio durchsimuliert.
ne. ich arbeite unter linux. ich hatte mir zwar mal ne virtuelle box mit avr-studio gemacht, aber so richtig dolle fand ich das programm nicht und hab mich nicht so sehr mit befasst. wäre jetzt auch ein ziemlicher aufwand, da das programm ja für gcc geschrieben ist. aber ich werde das mal machen, wenn es der einzige weg ist. dazu müsste ich halt irgendwie auch uart in avr-studio simuliert kriegen... geht das alles???

sei

//*********************main-loop*********************
loop0:
mov msg1, byte1 //erstmal byte1 wieder frei machen

cli //es hat sich gezeigt, dass während des folgenden vergleichs die interrupts am besten ausgeschaltet sind
Byte1 ist ja schon gesichert, warum also CLI. Stopt den Timer auch!
ja, das ging nicht anders. wenn ich an dieser stelle das cli und kurz danach das sei weglasse, dann kommt es zu einem regelmäßigen blinken der LEDs, was anzeigt, das der vergleich positiv ausfällt und verzweigt wird, obwohl gar keine taste gedrückt wurde und somit auch kein uart-interrupt ausgelöst wurde. dieses blinken kam so etwa im abstand von 2 bis 3 sekunden.
hab lang drüber nachgedacht. letztlich kam mir nur eine erklärung: während das programm läuft kommt es ja regelmäßig zu overflow-interrupts vom timer. wenn das genau dann geschieht, wenn der vergleich durchgelaufen ist, aber noch nicht breq verarbeitet wurde, dann wird durch den overflow die zero-flag gesetzt und wenn wieder zurück in das hauptprogramm gesprungen wird, so kuckt der breq-befehl nur irgendwie nach, ob die zero-falg gesetzt wurde, was ja sonst nur der fall ist, wenn der cpi positiv ausgefallen ist. aber so ist die zero-flag gesetzt obwohl dem nicht so ist. und dann wird verzweigt. vermutlich kann ich das cli und sei umgehen, wenn ich das SREG sichere :) . wieder was gelernt


Anstelle rjmp ret_ausgabe_on -> rjmp loop0. Abfrage Zeit=5 nutzlos, da jetzt gerade auf Null gesetzt.
ja, ok. so wie der code in der form da steht hätte man das sinnvoller machen können. ist wie gesagt nur die extrem eingedampfte form eines längeren quelltextes, wo das sinn machte, weil danach noch was kam...


Solage wie byte1=key1 verzweigst Du immer in diesen Programmteil.
Zeit= wird ständig auf null gesetzt bis ein Datenbyte ankommt.
Ist das so gewollt?

ich hab das eben mal abgeändert, so dass byte1 bei ausgabe_on auch mit 0 initialisiert wird. macht keinen unterschied. aber wie gesagt, wenn ich die uart-interrupt so abändere:

Code:
.global USART_RXC_vect
USART_RXC_vect:

	in byte1, UDR
	out PORTB, ausgabe
	ldi zeit1, 0

reti

und ausgabe_on nur noch byte 1 initialisert:

Code:
ausgabe_on:
		
	ldi byte1, 0

rjmp ret_ausgabe_on

dann geht alles ohne totzeit... Ich brenne nur darauf zu erfahren, warum dies so ist!

Schreib Dir einen Programmablaufplan. Dann wird die Sache übersichtlicher.
ich hab keine ahnung wie man einen korrekten programmablaufplan schreibt. aber für mich, der ja das programm selbst geschrieben hat stellt es sich so recht übersichtlich dar :)
 
Oder hast Du das unbeabsichtigt zu absolut formuliert?

Nein. Ich hab es nicht als absolut definiert. Ich schrieb "sollte". Also eine Empfehlung. Letztendlich liege es im ermessen des Programmierers wie er das umsetzt.
Ich persönlich schreib es rein. Wenn der Code mit der Zeit wächst oder umgeschrieben wird, ist es schnell vergessen. Wäre bei mir nicht das erste mal.

Gute Nacht
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)