Hallo zusammen,
ich bau gerade ein Diagnoseinterface für alte Eberspächer Standheizungen
(Modell D3WZ). Die Standheizung schickt zyklisch einen Datenstrom
variabler Länge.
Die ersten drei Byte sind statisch immer die Zeichenfolge "ZHE" bzw.
0x5A 0x45 0x48.
Danach kommt ein Byte mit dem aktuellen Fehlercode (0 wenn alles OK
ist). Das 5te Byte ist die Anzahl der Fehlercodes im Fehlerspeicher (0-5
sind möglich), gefolgt von maximal 5 Bytes (Fehler 1 - 5). Danach kommen
viele Termperaturdaten, Statusbits etc (da fehlt mir leider die
Umrechnung, bleibt daher vorerst unbeachtet).
Das Bild zeigt einen normalen Datenstrom, Fehlerspeicher mit 5 Fehlern
gefüllt.
Jetzt möchte ich quasi permanent empfangen (aktuell ist mein Buffer 100
Byte groß) und dann in dem Buffer den String ZHE suchen. So stelle ich
sicher das ich den Start-Punkt kenne und kann dann die Bytes 4-10
auswerten.
Wie suche ich einen String in einem größerem String?
Paul schrieb:> Jetzt möchte ich quasi permanent empfangen (aktuell ist mein Buffer 100> Byte groß) und dann in dem Buffer den String ZHE suchen.
Warum so kompliziert. Gucke dir direkt die Zeichen an, die rein kommen.
Per FSM prüfst du, ob "ZHE" kommt und damit setzt du deinen
Schreibpointer auf den Pufferanfang. Dann hast du deine relevanten Daten
auf festen Positionen im Puffer stehen.
Das Ende scheint doch auch eindeutig definiert 00 00 00 05
Damit packst Du immer ein komplettes Telegramm in deinen String Buffer
und verwurstest den dann….
Paul schrieb:> Die ersten drei Byte sind statisch immer die Zeichenfolge "ZHE" bzw.> 0x5A 0x45 0x48.
Es ist immer ungünstig, auf etwas in der Zukunft zu warten. Es könnte
durchaus sein, dass es nicht kommt oder korrumpiert wird.
Ich würde da den Spieß umdrehen und mir die bereits empfangenen Zeichen
vor dem ZHE anschauen. Oder eben wie gesagt das Ende der
Zeichenkette abwarten, wenn dies tatsächlich immer 00 00 00 05 ist.
Paul schrieb:> Wie suche ich einen String in einem größerem String?
Da gibt es mehrere Ansätze:
A) Mit welcher Programmiersprache?
B) Du suchst gar nicht, sondern du nimmst jedes einzelne empfangene Byte
in die Hände und schaust, ob es ein Z ist. Wenn ja, dann setzt du einen
Zähler auf 1, wenn nein, dann setzt du den Zähler auf 0. Wenn der Zähler
auf 1 ist, dann schaust du, ob das Zeichen ein H ist. Falls ja, dann
setzt du den Zähler auf 2, wenn nicht, dann auf 0. Und wenn der Zähler
auf 2 ist, dann schaust du, ob das empfangene Zeichen ein E ist. Falls
ja, dann setzt du den Zähler auf 3, wenn nicht, dann auf 0. Wenn der
Zähler 0 und das empfangene Zeichen ein Z ist, dann setzt du den Zähler
auf 1, sonst auf 0.
Dieser Zähler ist der oben erwähnte Zustandsautomat (FSM).
Und du siehst: wenn der Zähler irgendwann 3 ist, dann hast du gerade
eben das E von ZHE empfangen. Du kannst das ganze Telegramm davor
auswerten.
> (aktuell ist mein Buffer 100 Byte groß)
Mein Tipp: wenn du den Ringpuffer für die SIO 128 oder 256 Bytes machst,
dann ist die Pufferverwaltung einfach, weil nichts verglichen, sondern
nur maskiert werden muss. Denn auch wenn dir als Zehnfingermensch das
Zehnersystem und seine Potenzen (10, 100, 1000, 10000,...) reinläuft wie
Butter: so ein binärer Rechner tut sich mit dem Zweiersystem (2, 4, 8,
16, 32, ...) viel leichter.
Und dann wird so ein Ringpuffer auch recht kompakt:
http://www.lothar-miller.de/s9y/categories/51-Fifo
Lothar M. schrieb:> Es ist immer ungünstig, auf etwas in der Zukunft zu warten. Es könnte> durchaus sein, dass es nicht kommt oder korrumpiert wird.
OK, leider ist der Anfang der einzig statische Teil. Der Endstring kann
variieren, sonst wäre die Welt schön einfach. In einem anderen Projekt
empfange ich so lange die Daten, bis ein <CR> kommt. Das ist schön
einfach.
Lothar M. schrieb:> A) Mit welcher Programmiersprache?
Ich verwende C auf einem STM32.
Lothar M. schrieb:> Dieser Zähler ist der oben erwähnte Zustandsautomat (FSM).
OK, ich habe hier schon mal die FSM gebaut. Ich frage quasi live ab
welche Zeichen reinkommen. Ich komme auch beim Counter = 3 an,
allerdings empgange ich dann so viel bis mein Buffer überläuft. Der
Buffer habe ich auf 128 angepasst. Wenn ich eine for Schleife drüber
setzte läuft diese so schnell, dass ich das akutell anligende Zeichen
den Buffer füllt. Wie löse ich das elegant?
Das mit dem FIFO Buffer von deiner Website muss ich erst mal
verinnerlichen :D
> Wie suche ich einen String in einem größerem String?
Garnicht. Mach in deiner Empfangsroutine eine Statemachine:
Wenn State=Anfang: If Zeichen=Z State=State2
Wenn State=State: If Zeichen=H State=State3 else State=Anfang
[..]
Wenn State=Am_Ende: If LetzesZeichenEmpfangen Copy Daten,
Flagready=true, State=Anfang
So arbeitest du dich durch die ganzen reinkommenden Daten.
Wichtig ist:
1. Immer nur kurz fuer ein Byte in der STatemachine bleiben, egal wie
lang
der Source im IRQ auch wird. Also schnell wieder raus aus dem IRQ.
2. Im Zweifel immer auf Anfang zurueck damit dein Code immer frisch
syncronisiert wenn was schief laeuft.
Olaf
Olaf schrieb:> Wichtig ist:> 1. Immer nur kurz fuer ein Byte in der STatemachine bleiben, egal wie> lang der Source im IRQ auch wird. Also schnell wieder raus aus dem IRQ.
Kurz: die FSM ist nicht in der ISR. In der ISR wird nur 1 Zeichen
abgeholt und in den Puffer geschrieben.
Wolfgang schrieb:> Gucke dir direkt die Zeichen an, die rein kommen.
Das passt nicht so einfach mit der aufwendigen HAL zusammen. Was im
normalen Leben ein besserer Dreizeiler ist, wächst sich mit dem Ding zu
einem internationalen Raumfahrtprogramm aus...
Besonders beim UART-Handling hat sich da bei ST augenscheinlich ein
frischer Absolvent in bisher unerreichte Höhen verstiegen (siehe
Screenshot). Da braucht man dann schon mal einen halben Tag, bis man das
Ding zurechtgeknechtet hat und 1 Interrupt für 1 Zeichen bekommt, wo man
dann genau dieses 1 Zeichen einlesen kann. Genug gelästert.
Also: zuerst mal mit dem CubeMX den ganzen µC konfigurieren (das ist der
Teil, den man tatsächlich nutzen kann). Und dann via HAL noch den
Interrupt mit der HAL_UART_Receive_IT() aktivieren:
1
staticvoidMX_USART2_UART_Init(void)
2
{
3
/* USER CODE BEGIN USART2_Init 0 */
4
/* USER CODE END USART2_Init 0 */
5
6
/* USER CODE BEGIN USART2_Init 1 */
7
/* USER CODE END USART2_Init 1 */
8
9
huart2.Instance=USART2;
10
:
11
:
12
huart2.Init.OverSampling=UART_OVERSAMPLING_16;
13
if(HAL_UART_Init(&huart2)!=HAL_OK)
14
{
15
Error_Handler();
16
}
17
18
/* USER CODE BEGIN USART2_Init 2 */
19
// via HAL den Interrupt aktivieren.
20
// Wichtig: Pointer!=0 und Anzahl!=0
21
HAL_UART_Receive_IT(&huart2,(void*)1,1);
22
/* USER CODE END USART2_Init 2 */
23
}
Jetzt wird nach dem Empfang eines Zeichens vom Framework der
USART2_IRQHandler() aufgerufen, der wiederum den unheimlichen Klimbim im
HAL_UART_IRQHandler() aufruft, wo wieder alles Mögliche aufgerufen wird.
Und das alles in der ISR... Schluck!
Aber Rettung naht: dort im USART2_IRQHandler() wird jetzt eingegriffen.
Vor der ganze Klimbim losläuft, wird das Zeichen abgeholt und in den
oben mal genannten Ringpuffer namens geschrieben:
1
:
2
voidUSART2_IRQHandler(void)
3
{
4
/* USER CODE BEGIN USART2_IRQn 0 */
5
if((USART2->SR&USART_SR_RXNE)==USART_SR_RXNE)
6
{
7
rxbuf[++rxw%bufsize]=(uint8_t)(USART2->DR);// Read data, clear flag
Das if(0) dient zum hinterlistigen Deaktivieren des HAL_Aufrufs, der ja
vom CubeMX verwaltet wird. Vielleicht gibts auch noch weniger fiese
Methoden, aber Hauptsache läuft.
Paul schrieb:> Die Standheizung schickt zyklisch einen Datenstrom variabler Länge.
Hoppla, übersehen. Dann ist es tatsächlich einfacher, auf die Zukunft zu
hoffen... ;-)
Also geht es weiter mit der Verwaltung des Ringpuffers in der
Hauptschleife:
1
:
2
#define bufsize 128 /* Zweierpotenz!!! */
3
charrxbuf[bufsize];
4
volatileuint32_trxw=0,rxr=0;// Pointer für USART Empfang
5
:
6
charchrx,cnt=0;
7
charerrcnt,error[5];
8
:
9
:
10
// UART Empfang abhandeln
11
if(rxw!=rxr)// Zeichen gekommen?
12
{
13
chrx=rxbuf[++rxr%bufsize];// Zeichen einlesen
14
switch(cnt){
15
case0:if(chrx=='Z')cnt++;// Z erkannt? Zähler hochzählen
16
break;
17
case1:if(chrx=='H')cnt++;// H erkannt? Zähler hochzählen
18
elsecnt=0;// Fehlstart
19
break;
20
case2:if(chrx=='E')cnt++;// E erkannt? Zähler hochzählen
Abdul K. schrieb:> Die Liste der Funktionen für einen simplen UART ist echt eindrucksvoll.
Das ist der Preis der HAL. Für einfache kleine Aufgaben unnötig komplex
und für komplexe Aufgaben manchmal unzureichend. Dafür kann man relativ
leicht auf andere STM32 wechseln.
Mag sein, aber nix für mich. Auf einem 8051 hab ich in Hex-Code in ca.
300 Byte einen kompletten seriell auf parallel Ringbuffer inkl. Xon/off
für meinen damaligen Parallelinterface-Drucker geschrieben. Die
Druckdaten kamen seriell vom Mac an. Assembler hatte ich keinen, also
direkt in Hex gecodet. Damit wurden dann Platinenlayouts gedruckt.
Höllenlärm eines NEC P2200 :-)
> Kurz: die FSM ist nicht in der ISR. In der ISR wird nur 1 Zeichen> abgeholt und in den Puffer geschrieben.
Nein, ich habe kein Problem damit eine einfache Ueberpruefung bereits im
IRQ zu machen. Wir programmieren hier ja nicht in Python und koennen es
uns daher erlauben. .-)
Das hat dann den Vorteil das man sofort wieder syncronbereit ist wenn es
zu einem Uebertragungsfehler kam. Zuletzt hab ich sowas z.B bei einer
Empfangsroutine einer IR-FB genutzt die sehr zuverlaessig arbeitet, auch
wenn sie kurz vorher etwas falsches empfangen hat, was dort ja haeufig
vorkommt.
Ich weiss z.B das das dritte Byte immer 0xa0 sein muss und breche sofort
ab wenn das nicht stimmt.
> Das ist der Preis der HAL. Für einfache kleine Aufgaben unnötig komplex> und für komplexe Aufgaben manchmal unzureichend.
Daran erkennt man das man besser die Finger davon laesst. Ja, auch ich
hab das schon mal genutzt wenn ich faul sein wollte, aber ich hab dann
auch mal geschaut was ich da aufrufe, das dann direkt in meinen Source
kopiert und dann nochmal vereinfacht. Damit kannst du praktisch sofort
die Ausfuehrungszeit halbieren und das ist ja gerade bei IRQ-Routinen
echt nett.
> Dafür kann man relativ leicht auf andere STM32 wechseln.
Die sind dann aber auch nicht verfuegbar. Wir wechseln gerade deshalb
zwischen komplett andere CPUs (ARM, !ARM). Was glaubst du wie kacke das
ist wenn dann alles auf proprietaeren Murks basiert. .-)
Olaf
Olaf schrieb:> Das hat dann den Vorteil das man sofort wieder syncronbereit ist wenn es> zu einem Uebertragungsfehler kam.
Da sehe ich noch keinen Vorteil, das kann ich ja genauso in der mainloop
abarbeiten. Der Ringpuffer verliert ja auch kein Zeichen und die FSM
läuft in der Mainloop gleich gut wie in der ISR.
Aber hier gilt wie üblich: jeder so wie ihm beliebt, denn genau dafür
hat er sich ja den µC gekauft.
Das schreit ja schon nach einem kleinen Atmega/Tiny "Coprozessor" der
sich darum kümmert.
Das ist ja der Hammer, solch ein Atomkratftwerk dafür aufzubauen.
Also Hobbyprogrammierer würde ich mir sowas nie antun.
Wäre das mein Job würde ich mir selbst in die Klöten beißen;-)
Rainer V. schrieb:> Wattislos? schrieb:>> Wäre das mein Job würde ich mir selbst in die Klöten beißen;-)>> Also ich würde beten, dass es nie jemand bemerkt...> Gruß Rainer
Und ich würde wetten, dass Du es nie schaffst.
Nicht weil Du zu unbeweglich bist..
Vielleicht solltest du auch noch die Pause zwischen den Telegrammen als
Startsignal behandeln. Also, falls 0x5A 0x45 0x48 auch in den Daten
vorkommen kann ...
LG, Sebastian
Lothar M. schrieb:> Was im> normalen Leben ein besserer Dreizeiler ist, wächst sich mit dem Ding zu> einem internationalen Raumfahrtprogramm aus...
OK, ich verstehe, die HAL ist ziemlich aufgebläht. Da hab ich bis jetzt
noch nie drüber nachgedacht/bin da noch nie an Grenzen gestoßen.
Lothar M. schrieb:> den> Interrupt mit der HAL_UART_Receive_IT() aktivieren:
OK, so weit klar. Aber was genau macht:
Lothar M. schrieb:> HAL_UART_Receive_IT(&huart2, (void*)1, 1);
Es startet den Empfang für UART2, zeigt auf eine Empfangsvariable und
bekommt noch die Byte Anzahl mitgegeben, aber worauf zeig denn der
zweite Parameter? Oder ist das nur ein Dummy, denn sobald was empfangen
wurde springt er ja eh in die ISR?
Lothar M. schrieb:> HAL_UART_IRQHandler() aufruft, wo wieder alles Mögliche aufgerufen wird.> Und das alles in der ISR... Schluck!
Kann ich (interessehalber) in der ISR einen Ausgang setzen und mit dem
Oszi die Laufzeit messen? nur um mal den Unterschied zwischen HAL und
Direkt zu vergleichen oder ist das zu ungenau und macht keinen Sinn?
Lothar M. schrieb:> Also geht es weiter mit der Verwaltung des Ringpuffers in der> Hauptschleife:
Ok, aber das klappt doch nur wenn meine Hauptschleife schnell genug ist
um den Puffer zu leeren? Sonst könnte es ja theoretisch sein, dass der
128 Byte große Buffer schon drei mal überschrieben wurde und bevor ich
in der Hauptschleife den Buffer auslesen kann?
Lothar M. schrieb:> Das wars.
Vielen Dank für deine Geduld und deine Muße es so super zu erklären,
sogar direkt am lebenden Objekt! Der Wahnsinn :).
Stefan ⛄ F. schrieb:> Das ist der Preis der HAL. Für einfache kleine Aufgaben unnötig komplex> und für komplexe Aufgaben manchmal unzureichend. Dafür kann man relativ> leicht auf andere STM32 wechseln.
Gibt es Kriterien nach den man entscheidet das man die HAL lieber links
liegen lässt und den direkten Weg wählt?
Lothar M. schrieb:> Der Ringpuffer verliert ja auch kein Zeichen und die FSM> läuft in der Mainloop gleich gut wie in der ISR.
Aber doch nur, solange es nicht überläuft, oder?
Wattislos? schrieb:> Das schreit ja schon nach einem kleinen Atmega/Tiny "Coprozessor"> der> sich darum kümmert.> Das ist ja der Hammer, solch ein Atomkratftwerk dafür aufzubauen.
Sicher, dass kann auch ein ATTINY. Ich hab auch mal mit Atmel und Co
angefangen, aber das Debugging finde ich auf den STM32 deutlich bequemer
und da ich das als Hobby betreibe und gerne einen "Standard µC" habe,
auf dem meine Projekte laufen bin ich bis jetzt mit F103 und F303 ganz
gut gefahren. Ich will ja auch keine Stückzahlen aufbauen, vllt werden
es mal zwei bis drei für paar Schrauberkumpels (diese Heizungen sind in
allen VAG Dieseln von Mitte 90 bis 2005 drin und haben oftmals Probleme)
Wattislos? schrieb:> Also Hobbyprogrammierer würde ich mir sowas nie antun.> Wäre das mein Job würde ich mir selbst in die Klöten beißen;-)
Was man sich als Hobby antut kann ja zum Glück jeder selbst entscheiden
;).
Paul schrieb:> OK, so weit klar. Aber was genau macht:> Lothar M. schrieb:>> HAL_UART_Receive_IT(&huart2, (void*)1, 1);>> Es startet den Empfang für UART2, zeigt auf eine Empfangsvariable und> bekommt noch die Byte Anzahl mitgegeben, aber worauf zeig denn der> zweite Parameter? Oder ist das nur ein Dummy, denn sobald was empfangen> wurde springt er ja eh in die ISR?
Das zweite Argument für HAL_UART_Receive_IT() ist die Adresse, wo
enmpfangene Zeichen hingeschrieben werden.
Wenn ich das Zeichen nicht an der Adresse 1 haben will, würde ich da
auch nicht eine 1 hinschreiben.
Entsprechend ist der eigentlich richtige Datentyp für den zweiten
Parameter auch uint8_t*.
Paul schrieb:> Aber was genau macht:>> HAL_UART_Receive_IT(&huart2, (void*)1, 1);> Es startet den Empfang für UART2, zeigt auf eine Empfangsvariable und> bekommt noch die Byte Anzahl mitgegeben, aber worauf zeig denn der> zweite Parameter? Oder ist das nur ein Dummy, denn sobald was empfangen> wurde springt er ja eh in die ISR?
Markier mal den Funktionsaufruf und geh dann mit der F3-Taste oder über
Rechtsclick-"Open Declaration" zu der Funktionsimplementierung. Sieh dir
die Deklaration und den Code der Funktion an und dir wird klar, warum da
beim pData und bei Size etwas "ungleich 0" stehen muss.
/* Check that a Rx process is not already ongoing */
4
if(huart->RxState==HAL_UART_STATE_READY)
5
{
6
if((pData==NULL)||(Size==0U))
7
{
8
returnHAL_ERROR;
9
}
10
11
/* Process Locked */
12
__HAL_LOCK(huart);
13
14
/* Set Reception type to Standard reception */
15
huart->ReceptionType=HAL_UART_RECEPTION_STANDARD;
16
17
return(UART_Start_Receive_IT(huart,pData,Size));
18
}
19
else
20
{
21
returnHAL_BUSY;
22
}
23
}
Mit einem Nullpointer oder einer 0 als Size wird die Funktion gleich
beendet. Mit passenden Werten wird die zum Freischalten der Interrupts
nötige UART_Start_Receive_IT() aufgerufen:
Ist zwischen 2 Frames keine Pause?
Die meisten STM haben sich extra HW im UART um auf Pausen auf zu syncen.
Warum also in Software machen?
Zusammen mit einem DMA und 1-2 Empfangspuffer braucht das keine
Rechenleistung.
Wenn der Pausen Interrupt kommt einfach schauen ob der Anfang stimmt.
Wenn ja nehmen, ansonsten verwerfen.