Serieller Datenempfang im Interrupt / "Bascom"

Hey Werner,

der bisherige Code steuert ja mein 128x64 Pixel LED-Display (rot/grün). Ich hab's mit 128x96 getestet, dann muss ich aber wieder runter auf 57.600, weil die Timer-Routine zuviel Zeit in Anspruch nimmt. Meide Idee ist, die Display's in Module á 128x64 aufzuteilen und dann mehrere, z.B. 6 St. an einen xMega anzukorken, der ja 8 UART's hat und diesen dann mit max. Speed zu feuern. Somit ist man flexibel und kann entweder nur ein Display direkt treiben oder aber mehrere über den Master-xMega. So könnte man dann ein großes Display mit z.B. 384x192 ansteuern...

Alternativ würde sich auch die Ansteuerung der einzelnen Module per SPI eignen, die UARTs können ja auch als SPI-Master betrieben werden, allerdings hat der M644 bzw. M1284 nur einen SPI, wenn ich mich recht erinnere.

Aus meiner Sicht gibt es daher nur zwei Möglichkeiten, die Baudrate zu erhöhen: Timer-Routine optimieren oder UART-Buffer vergrößern bzw. effizienter gestalten. Ggf. kann ich anstatt eines M644/1284 nen xMega nehmen und alle SPI's nutzen, allerdings befürchte ich, dass das nicht viel bringt, da Bascom halt immer entscheiden muss, welchen SPI es gerade nehmen soll. Ich hab dann zwar ne schnellere Hardware, aber die Software ist langsamer :)

Ich hab schonmal probiert, den Inhalt des UART's direkt per DMA in die LED-Buffer zu schieben (auf dem xMega). Das klappt auch, aber dort ist schon bei 38.400 Schluss.

Gruß
Jan
 
Hallo Jan,

Anbei eine Überarbeitung, die(noch ungeprüft) stimmen müßte.


Grüße
Werner

Code:
'..............................................................................
'..............................................................................
'..............................................................................
Const CBuffsize = 2+1         ' Maximale Anzahl (+1) der Zeichen
'..............................................................................
Dim RSWordVal  As Word        ' einzulesende Variable 
Dim RSCounter  As Byte        ' Zeichenzähler
'..............................................................................
mRreg Alias R16
zRreg Alias R17
'..............................................................................
On URXC Rs232receive Nosave   ' Keine automatische Register-Rettung
'..............................................................................
Enable URXC                   ' Interrupt (Byte über USART erhalten) enabled
Enable Interrupts             ' Interrupts global zulassen
'******************************************************************************
'********************* RS232- Interrupthandler (Receiver) *********************
'******************************************************************************
Rs232receive:
$asm
  PUSH    mReg                ' 1.Arbeitsregister retten
  IN      mReg,SREG           ' Statusregister einlesen (absolut notwendig)
  PUSH    mReg                ' und sichern
  IN      mReg,UDR            ' Zeichen vom UART auslesen
  PUSH    zReg                ' Hilfsregister sichern
  PUSH    YL                  ' Y-Pointer sichern
  PUSH    YH
'..............................................................................
  LDS     zReg,{RSCounter}    ' Zeichenzähler einlesen 
  Loadadr RSWordVal,Y         ' Adresse der Variablen Wert nach Y laden
  ADD     YL,zReg             ' Speicheradresse berechnen
  BCC     Rs232receive0:      ' (+0 oder +1 bei 2 Zeichenempfang)
  INC     YH
Rs232receive0:
  INC     zReg                ' JETZT: Zeichenzähler erhöhen 
  BREQ    Rs232receiveE       ' wenn jetzt 255 -> abbrechen
  STS     {RSCounter},zReg    ' sonst: neuen Zeichenzähler abspeichern 
  CPI     zReg,CBuffSize      ' schon (2+1) Zeichen (= zu viele)
  BRSH    Rs232receiveE       ' ja (Sprung bei größer oder gleich)
  ST      Y,mReg              ' nein, Zeichen eintragen 
'..............................................................................
Rs232receiveE:                ' Register wieder restaurieren 
  POP     YH
  POP     YL
  POP     zReg                
  POP     mReg                ' sowie Flags wiederherstellen
  OUT     SREG , mReg
  POP     mReg
' RETI                        ' BASCOM ersetzt in diesem Fall, d.h. ISR-Routine
$end Asm                      ' die über ON 'Interrupt' 'Label' deklariert ist
Return                        ' dieses 'RETURN' durch 'RETI' (nicht RET)
'..............................................................................
'..............................................................................
'..............................................................................
 
Hallo Werner,

wenn ich das richtig sehe, stehen in der Variable "RSWordVal" meine Daten...Wann weiß ich, dass die Daten da zur Auswertung bereitstehen?

Und würde es nicht noch was bringen, den UART komplett in ASM zu konfigurieren, also BASCOM da mal aussen vor zu lassen? Sollte ich doch dann flexibler bzgl. der Baudraten sein oder?

Gruß
Jan
 
Hallo Jan,

richtig, das Ergebnis (die beiden gesendeten Byte) stehen in "RSWordVal".

Ansonsten brauchst du nur die Variable "RSCounter" abfragen. Abhängig vom Wert, kannst du folgende Fälle unterscheiden:

RSCounter=0: Initialisierung zu Beginn bzw. nach Auslesen von "RSWordVal"
RSCounter=1: Erstes Zeichen gelesen
RSCounter=2: 2 Zeichen gelesen: jetzt solltest du "RSWordVal" auslesen und "RSCounter" wieder auf Null setzen
RSCounter=3..255:es kommen zu viele Zeichen über die RS232. An dem Wert könntest du eventuell erkennen, UM WIEVIEL zu schnell - das mußt du ausprobieren. Die Zählvariable wird zwar erhöht, der Wert von "RSWordVal" spiegelt aber den Wert der ersten beiden übermittelten Zeichen wieder.

Dein Code könnte in etwa so aussehen:

Code:
RSCounter=0
MainLoop:
  if RFCounter < 2 then
    Goto Mainloop
  endif
  WorVar=RSWordVal  ' 1. Variable übernehmen 
  RSCounter=0       ' 2. Empfangspufferpuffer wieder freischalten
  ....              ' .... Verarbeitung
Goto Mainloop

So, ich hoffe, das passt. Viel Erfolg schon einmal
Werner

P.S. Die Initialisierung in Assembler durchzuführen bringt keinen Geschwindigkeitsvorteil - das macht Bascom für dich/mich
 
Hey Werner,

vielen Dank für die Hilfe. Werde es morgen mal testen.

Dein Code-Beispiel verwirrt mich etwas, warum nicht einfach

Code:
Do

If RSCounter = 2 Then
   RSCounter = 0

   ...Analyse der Daten

End If

Loop

Ist das mit dem Goto schneller?

Danke und Gruß
Jan
 
Uuuuups Jan,

böse Falle :playingball:.

Um's klar zu sagen: nein, du solltest den Code nicht so umstellen, wie du es vorgeschlagen hast, sondern ihn in der Art übernehmen, wie ich es geschrieben habe. Es liegt auch nicht am "Goto".

Die Variablen "RSWordVal" und "RSCounter" sehen zwar ganz harmlos aus, sind aber in Wirklichkeit sowohl Datenpuffer einer Interruptserviceroutine (ISR) als auch eine Art Statusregister der selbigen.

Wenn du zum Beispiel nach Empfang der ersten beiden Bytes gerade am Abarbeiten der gerade angekommenen Zahl bist, kann es ja sein, dass ein neues Zeichen über die Schnittstelle ankommt. In diesem Fall wird dein Hauptprogramm unterbrochen und die ISR-Routine wird aufgerufen. Die Routine liest das Zeichen ein (damit es nicht zum Überlauf kommt) und schaut dann nach, ob das "Writeenableflag" gesetzt ist. Dieses Flag wirst du allerdings vergeblich suchen; dieses Flag wird durch den Inhalt der Variablen RSCounter gebildet: bei einem Wert von “1” oder “2” (genauer “0” oder “1”) wird das gerade eingelesene Zeichen auch gespeichert (entsprechend das Low- oder Highbyte der Variabklen “RSWordVal”).

Solltest du die Variable “RSCounter” aber - so wie du es vorschlägst – auf “0” zurücksetzen ohne vorher (zu Beginn deiner Abarbeitung den Wert in eine andere Variable zu übertragen) wird nun “RSWordVal” während der Abarbeitung verändert (zuerst das Lowbyte).

Sollte deine Abarbeitung länger dauern, könnte auch noch passieren, dass ein zweites Byte über die Schnittstelle kommt. Dann passiert das gleiche noch einmal, wobei diesmal das Highbyte verändert wird!!

Und du fragst dich dann, warum die Routine nicht das macht, was du eigentlich möchtest.

Sollte deine Abarbeitung sogar noch länger dauern, so dass ein drittes Zeichen abgeholt wer-den kann, so merkt auch das die ISR-Routine. Sie liest auch diesmal das Zeichen ein (damit dein Programm nicht hängenbleibt, weil keiner die Daten abholt) speichert es aber nicht ab. Bei einem Wert Variablen "RSCounter" von > 2 wirkt die Variable also als "Write not Enabled", damit du deinen AVR-Speicher nicht vollschreibst und damit andere Variablen überschreibst.
Der Counter "RSCounter" wird allerdings weiter erhöht, so dass du in der Testphase einen Indikator an die Hand bekommst, der dir halbquantitativ sagt, um wieviel du deine Baudrate senken musst bzw. deine Abarbeitung beschleunigen.

Wenn du dein Programm entsprechend optimiert hast, wird dann praktisch gleichzeitig während deiner Abarbeitung ein neues Wort empfangen, so dass du beim Rücksprung an den Schleifenanfang sofort weitermachen kannst.


Viel Erfolg
Werner
 
Hi
In Bacom kann ich nicht viel dazu beitragen, aber in Assembler schreibe ich über RS 232 ankommende Zeichen in einen Ringpuffer. Dabei ist ein Doppelregister auf den Anfangdes Puffes gestellt und es gibt einen Read- und einen Write-Zeiger, die jeweils auf das Doppelregister addiert werden. Erreichen sie die Grenze, werden sie auf 0 gestellt und fangen von vorn an. Anfangs sind Read und Write gleich. Bei einem ankommenden Zeichen wird der Puffer beschrieben und Write um 1 erhöht. Genzen noch abfragen, aber das war's für die ISR.
Das Hauptprogramm prüft eigentlich nur, ob Read und Write unterschiedlich sind. Wenn du weißt, es kommen immer 2 Bytes, so kannst du die Differenz von 2 ja auswerten und dann beide Bytes zusammen bearbeiten. Den Ringpuffer machst du so groß, wie du denkst, das ein Telegramm an Daten hat. Bei einer hohen Geschwindigkeit und vielen Telegrammen direkt hintereinander machst du ihn halt etwas größer.
Code:
;---------------- Empfangsroutine im Controler ------------------------------------ 

int_rxc:
	PUSH	temp_Reg			; temp auf dem Stack sichern
	IN	temp_Reg, sreg			; SREG sichern    
	PUSH	temp_Reg
	Push	XL
	Push	XH
	PUSH	a0
	CLR	Zero
	LDS	a0, WritePos
	IN	Work_Reg, UDR
	LDI	XL,LOW(Com_Buff)		; x-Pointer auf Empfangspuffer
	LDI	XH,HIGH(Com_Buff)
	ADD	XL, a0
	ADC	XH, Zero	
	ST	X, Work_Reg
	INC	a0
	CPI	a0, 90
	BRLO	End_rxc
	CLR	a0

End_rxc:	
	STS	WritePos, a0
	POP	a0
	POP	XH
	POP	XL
	POP	temp_Reg    
	OUT	sreg, temp_Reg			; SREG wiederherstellen
	POP	temp_Reg			; temp wiederherstellen    
	RETI				

;***********************************************************************************
Vielleicht hilft dir dieser Ansatz.
Gruß oldmax
 
Moin Werner,

kriege Deinen Code nicht compiled.

Erster Bug waren die Namen der Register. Nachdem ich sie umbenannt habe (mRreg > mReg, dito mit zRreg) gings schon weiter, allerdings kennt Bascom den ASM-Befehl BCC (bzw. BCCTR) (Branch Conditional to Count Register) nicht...meckert rum wg. 'Unknown ASM Mnemonic'.

Kannst Du da nochmal drübergucken.

Danke und Gruß
Jan
 
Hi Werner,

erster Test leider negativ, selbst mit 57.600 gibt's schon 'Pixelfehler'...werd mal weiter sehen...kann meine Routine zur Auswertung nicht noch optimiert werden? Das Geshifte dauert wohl ziemlich lange.

Gruß
Jan
 
Hallo Jan,

da war noch ein Doppelpunkt zu viel.
Jetzt ist es syntaktisch richtig.

Code:
'..............................................................................
'..............................................................................
'..............................................................................
Const CBuffsize = 2+1         ' Maximale Anzahl (+1) der Zeichen
'..............................................................................
Dim RSWordVal  As Word        ' einzulesende Variable
Dim RSCounter  As Byte        ' Zeichenzähler
'..............................................................................
On URXC Rs232receive Nosave   ' Keine automatische Register-Rettung
'..............................................................................
Enable URXC                   ' Interrupt (Byte über USART erhalten) enabled
Enable Interrupts             ' Interrupts global zulassen
'******************************************************************************
'********************* RS232- Interrupthandler (Receiver) *********************
'******************************************************************************
mReg Alias R16
zReg Alias R17
'..............................................................................
Rs232receive:
$asm
          PUSH    mReg                ' 1.Arbeitsregister retten
          IN      mReg,SREG           ' Statusregister einlesen (absolut notwendig)
          PUSH    mReg                ' und sichern
          IN      mReg,UDR            ' Zeichen vom UART auslesen
          PUSH    zReg                ' Hilfsregister sichern
          PUSH    YL                  ' Y-Pointer sichern
          PUSH    YH
        '..............................................................................
          LDS     zReg,{RSCounter}    ' Zeichenzähler einlesen
          Loadadr RSWordVal,Y         ' Adresse der Variablen Wert nach Y laden
          ADD     YL,zReg             ' Speicheradresse berechnen
          BRCC    Rs232receive0      ' (+0 oder +1 bei 2 Zeichenempfang)
          INC     YH
Rs232receive0:
          INC     zReg                ' JETZT: Zeichenzähler erhöhen
          BREQ    Rs232receiveE       ' wenn jetzt 255 -> abbrechen
          STS     {RSCounter},zReg    ' sonst: neuen Zeichenzähler abspeichern
          CPI     zReg,CBuffSize      ' schon (2+1) Zeichen (= zu viele)
          BRSH    Rs232receiveE       ' ja (Sprung bei größer oder gleich)
          ST      Y,mReg              ' nein, Zeichen eintragen
        '..............................................................................
        Rs232receiveE:                ' Register wieder restaurieren
          POP     YH
          POP     YL
          POP     zReg
          POP     mReg                ' sowie Flags wiederherstellen
          OUT     SREG , mReg
          POP     mReg
        ' RETI                        ' BASCOM ersetzt in diesem Fall, d.h. ISR-Routine
        $end Asm                      ' die über ON 'Interrupt' 'Label' deklariert ist
        Return                        ' dieses 'RETURN' durch 'RETI' (nicht RET)
        '..............................................................................
        '..............................................................................
        '..............................................................................

Grüße
Werner
 
Hey Werner,

den hab ich schon entfernt, vorher kompilierte er ja gar nicht....aber wie schon geschrieben, habe ich bereits bei 57.600 erhebliche Pixelfehler, sodass diese Variante so nicht brauchbar ist.

Das beste Ergebnis erziele ich immernoch mit dem "Config SerialIn = Buffered, Size = 255" - Statement.
Wäre schön, wenn man den Buffer noch höher schrauben könnte, aber da muss man an die internen Funktionen von Bascom ran.

Gruß
Jan
 
Das Geshifte dauert wohl ziemlich lange.
Hallo Jan,
kannst du die Sendeseite noch verändern?
Wenn du die Reihenfolge der Bits in den beiden Bytes ändern kannst, dann lässt sich die Empfangsseite noch ein gutes Stück verkürzen.

Das beste Ergebnis erziele ich immernoch mit dem "Config SerialIn = Buffered, Size = 255" - Statement.
Wäre schön, wenn man den Buffer noch höher schrauben könnte, aber da muss man an die internen Funktionen von Bascom ran.
Ich bin ziemlich sicher, dass du durch Werners Methode besser weiterkommst. Die Bascom Routine macht auch nichts anderes, als jedes eintreffende Byte in einem Interrupt zu verarbeiten. Nur dass du da keinen Einfluss drauf hast, wie es geht. Und Werner kann dir die 255 Byte Grenze sicherlich aufmachen, falls nötig.
Allerdings sehe ich die Notwendigkeit hierfür nicht. Ist dein Ausgabe Code immer noch so wie in #35 gezeigt? Hast du mal im Simulator überprüft, wie lange ein Ablauf der Mux-Routine dauert? Der sieht mir nicht aus, als sollte er länger dauern als zwei Bytes zu empfangen. Dann dürfte überhaupt keine Pufferung nötig sein.

Grüße

Sebastian
 
Hi Jan,

Das beste Ergebnis erziele ich immernoch mit dem "Config SerialIn = Buffered, Size = 255" - Statement.

Hast du mal im Simulator überprüft, wie lange ein Ablauf der Mux-Routine dauert? Der sieht mir nicht aus, als sollte er länger dauern als zwei Bytes zu empfangen. Dann dürfte überhaupt keine Pufferung nötig sein.

Ich würde auch sagen das da irgendwas grundlegendes in deinen Routinen falsch läuft wenn du erst durch einen 255Byte-Buffer imstande bist einen 2-Byte langen Datenstrom sauber zu verarbeiten. Du solltest deine Routinen mal auf grundlegende Timing-Probleme abklopfen. Es sollte wirklich möglich sein die Daten ohne irgendeinen Zwischenpuffer zu verarbeiten. Andernfalls treibst du den Teufel eigentlich nur mit dem Beelzebub aus. Sprich ... du löst dein Problem nur scheinbar mit deinem Puffer.

Gruß
Dino
 
Hallo Jan,

ich kann mich nur dino und Sebastian anschließen: "Meine" Routine hat den kleinsten Verwaltungsaufwand - auch was deinen Zugriff von Basic aus betrifft - und ist damit am Schnellsten. Ich hab's ja auch schon versucht, dir zu beschreiben: wenn du dein Abarbeitung des aktuellen Wertes so gestalten kannst, dass dabei exakt 2 neue Zeichen ankommen, läuft dein Programm mit maximaler Geschwindigkeit. Mit 2,1 Zeichen pro Verarbeitung kommen zu viele Zeichen und es kommt bei einem kontinuierlichen Datenstrom irgendwann immer zu einem Überlauf - egal wie groß der Puffer ist.

Die Empfangs-Routine auf mehr Zeichen anzupassen, wäre nicht schwer. Dein Verwaltungsaufwand - und damit deine Abarbeitungszeit - steigt dann aber mehr oder weniger stark an, weil du ja die aktuelle Variable erst "suchen" (aus dem Index die Position)- bzw. deinen Lesezeiger selbst verwalten mußt. Dann kommst du automatisch zu einem Ergebnis, das du bzw. Sebastian beschreibst (SerialIn-Lösung).


Grüße
Werner
 
Moin und sorry für den späten Reply, kommt immer wieder was dazwischen :)

@HinterBlauenAugen: Mein Code ist jetzt wie in #39 gepostet, der Code in #35 läuft nur bis 57.600 sauber, der in #39 auch mit 115.200.

Ich muss gestehen, dass ich mich noch nie mit dem BasCom-Simulator beschäftigt habe, hab da so meine Berührungsängste. Kannst Du den Code mal durch den Simulator laufen lassen? Vielleicht erkennst Du daran ja irgendwelche Timing-Probleme?

Die Sendeseite habe ich übrigens frei unter Kontrolle, ist ja mein .NET-Programm. Um eine bestimmte LED anzusprechen, muss ich dafür die Koordinaten ledX und ledY sowie die Farbe angeben. Das macht insgesamt drei Byte, wenn man davon ausgeht, dass das Display nicht größer als 256x256 ist, sonst würd ein Byte pro Koordinate ja nicht mehr reichen.

Es gibt dafür 2 Byte-Arrays, die jeweils die roten und grünen "Pixel" halten. Jedes Element im Array ist 8 Bit groß. Da jedes einzelne Matrix-Modul 8 LEDs hat, gibt das Array-Element das entsprechende Modul an und die einzelnen Bits (0 - 7) die jeweilige LED (=BitIndex) in diesem Modul...Mann, schwer zu erklären :)

Nun dachte ich mir, dass ich noch einiges an Rechenzeit sparen kann, indem mir mein .NET-Programm die Berechnung der entsprechenden Array-Position und des Bit-Index' übernimmt. Wenn man dann alles zusammenrechnet, benötigt man 2 Bit für die Farbe (00= Aus, 01= rot, 10 = grün und 11 = gelb), 3 Bit für den Bit-Index (0-7) und die restlichen Bits sind für das Array-Element (Maximum = (Displayhöhe * DisplayBreite) / 8 - 1= 1.023 = 10 Bits). Macht also zusammen 15 bzw. 16 Bits (da bei Bascom 1-basiert). Also muss ich anstatt drei Bytes und ner Menge Rechnerei im AVR nur zwei Bytes übertragen, diese auseinanderpflücken (wie im Code zu sehen) und die Werte direkt in die Arrays schreiben.

Wäre klasse, wenn das jetzt jemand verstanden hat und mir sagen kann, wie's viel besser geht, hehe ;)

Gruß
Jan
 
Ich wieder,

ich werd' mich - wohl oder übel - noch mal an die Mux-Routine ranmachen. Da die Displays etwas eigenwillig angesteuert werden wollen, benutze ich im Moment vier SPIOUT's und rechne den entsprechenden ArrayIndex im Timer-Interrupt aus. Das kostet natürlich. Wenn ich die Routine jetzt umbaue, sodass ich nur noch zwei SPI's brauche, für rot und grün, fällt das Gerechne weg und er schiebt mir nur 2 x 64 Bytes, anstatt 4 x 32 Bytes raus. Denke, beides könnte etwas Zeit sparen, sodass ich den Timer optimaler einstellen kann und dadurch mehr Zeit für die Hauptroutine habe...
 
Hallo sHINE,
bevor wir jetzt versuchen, das ganze zu pimpen, erst noch ein paar Fragen:
Sehe ich das richtig, dass du mit jedem übertragenen Word nur ein einziges Bit setzt, auf eine von 4 Farben?
Ich nehme mal an, dass du auf PC Seite die Pixel eins nach dem anderen generierst, oder geht das im wahlfreien Zugriff über das ganze Display?

Grüße

Sebastian
 
Hallo,

Sehe ich das richtig, dass du mit jedem übertragenen Word nur ein einziges Bit setzt, auf eine von 4 Farben?
Ich nehme mal an, dass du auf PC Seite die Pixel eins nach dem anderen generierst, oder geht das im wahlfreien Zugriff über das ganze Display?
Ich hab das so verstanden ... Schon irgendwie ... naja ... optimierungsfähig.
Entweder man überträgt für eine 8Pixel-Zeile 2 Byte (4 Farben sind 2 Bit) oder man macht da irgendwas mit Steuerbyte für Synchronisation und läßt dazwischen die Daten frei vom Sender zum Empfänger durchlaufen.

Gruß
Dino
 

Ü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)