Aus RN-Wissen.de
Version vom 10. Oktober 2006, 12:51 Uhr von PicNick (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche
Rasenmaehroboter fuer schwierige und grosse Gaerten im Test

Erzielt werden soll eine sichere, transparente Daten-Übertragung zwischen µCs und PCs sowie uCs bzw PCs untereinander. Die geplanten Übertragungsmedien sind im Moment:

  • UART (RS232), vor allem zur Anbindung von uCs an PCs
  • LAN (IP), vor allem zur Anbindung von PCs untereinander (z.B. Steuerrechner - GUI)

Dabei betrachtet dieses Projekt die IP-Funktionalitäten als "black-box" und alles unterhalb der üblichen Schnittstelle (sockets) wird elegant als Level-0 betrachtet.

  • i2c (spätere Ausbaustufe), vor allem zur Anbindung von uCs untereinander

Ziel der Schicht 0 muß es sein für die oberen Schichten Datenpakete mit einer bestimmten Länge zu übertragen. Dabei sollen die oberen Schichten nichts über das Medium (UART, LAN, i2c) wissen müssen. Da die Datenbytes alle Werte von 0-255 einnehmen können, muß die jeweilige Schicht geeignete Kontrollmechanismen definieren.

Level-0 UART (point-to-point)

Die Implementierung der Schicht 0 hängt stark von den verwendeten Medien ab. Die Arbeitsgruppe ist übereins gekommen, Daten über den UART wie folgt zu übertragen: Die Nachrichten wird mit einem STX-Zeichen begonnen, dann die Daten, eine Kontrollsumme und als Abschluß ein ETX-Zeichen.

START....daten....CHECK.ENDE

Jetzt kann natürlich zwischen den Daten jederzeit ein Zeichen drin sein, das denselben Wert hat wie eine Start- oder Endekennung. Daher wird "Bytestuffing" verwendet

Byte-Stuffing

Es gibt drei besondere Zeichen

  • STX als Start-Kennung
  • ETX als Ende-Kennung UND
  • PFX als Daten-Kennung (durch dieses Zeichen wird das nachfolgende zu einem normalen Datenbyte)

Am besten ein Beispiel:

Oben ist eine Nachricht, die ein Zeichen enthält, das denselben Wert hat wie die Endekennung (ETX) Bis zu dem Zeichen werden die Daten normal übertragen. Dann wird aber das PFX eingefügt und danach der Rest.


Stuffing.png

Der Empfänger findet das Startzeichen STX und beginnt, die Zeichen nach der Reihe zu übernehmen. Dann entdeckt er PFX. Das speichert er zwar nicht, aber dadurch enterpretiert er das Folgezeichen, das ja eigentlich eine Endekennung wäre, nicht als solches und gibt es zu den normalen Daten dazu. So gehts weiter bis zum Ende ETX, das ja "blank" dasteht, also sozusagen "echt" ist.

Es ist immerhin möglich, daß ein Teilnehmer beim Empfangen irgendwie nicht gut drauf ist, und er erst genau zwischen PFX und Folgezeichen die Bytes wieder richtig mitkriegt. Er würde also denken, daß dieses Folgezeichen ein Kontrollzeichen ist und letzlich Müll empfangen. Für diesen Fall wurde vorgeschlagen, dieses Folgezeichen gleich überhaupt durch eine Addition unkenntlich zu machen. Derjenige, der das PFX mitgekriegt hat, weiß das ja und kann wieder subtrahieren, unser Schläfer weiß das aber eben nicht, kann aber auch kein (falsches) Steuerzeichen erkennen. Beim Wiedereinstieg an einer beliebigen anderen Stelle kann sowieso nicht Schlimmes passieren, denn bis zum nächsten un-prefixten STX wird ja alles ignoriert.

Kontrollsumme BCC

  • Beginnend mit NULL, werden alle Datenzeichen (ohne PFX, STX, ETX und andere Kontrollzeichen) mit Exklusiv-Oder "übereinandergelegt". Das Ergebnis hängt der Sender an die Daten an. Es wird zusammen mit den Daten geprefixed.
  • Der Empfänger tut das Gleiche, dann aber vergleicht er sein Ergebnis mit dem Prüf-Byte, das der Sender weggeschickt hat.

Stimmt das Prüfbyte nicht, wird die Nachricht verworfen. Das ist zwar auch nicht todsicher, aber immerhin beruhigend. Wenn sich das nicht bewährt, kann immer noch ein CRC-Verfahren festgelegt werden.

Praxis

  • Maximale Frame-Size
#define FRAME_C_MAX   127     

Das ist die maximale Netto-Länge zwischen STX und ETX, exklusive. Auch das scheint für µC etwas groß, aber es wird davon ausgegangen, daß die real benötigten Messages weit kürzer sein werden, d.h. das regelt sich vermutlich von selbst durch die Vereinbarungen und Restriktionen in den oberen Layern

  • Kontrollzeichen
#define CTL_M_MASK   0xF8 
#define CTL_M_ADON   0x08 
#define CTL_C_BASE   0xA8 
#define CTL_C_STX   CTL_C_BASE + 1 
#define CTL_C_ETX   CTL_C_BASE + 2 
#define CTL_C_PFX   CTL_C_BASE + 3 
  • Code-Beispiel/Muster für Paket-senden

( transmit() stellt die Funktion für den tatsächlichen physischen Output dar).

static unsigned char bTxBcc;            // Checksum für BCC 
// --------------------------------------------------------------
void TxStartFrame ( void )
{
  bTxBcc = 0
  transmit ( CTL_C_STX )                         
}
// --------------------------------------------------------------
void TxCloseFrame ( void )
{
  TxSendStuffByte ( bTxBcc )  // auch das BCC mit ev. prefixed werden
  transmit ( CTL_C_ETX )                         
}
// --------------------------------------------------------------
void TxSendFrameByte ( unsigned char bTxChar)
{
     bTxBcc ^= bTxChar
     TxSendStuffByte ( bTxChar ) 
}
// --------------------------------------------------------------
void TxSendStuffByte ( unsigned char bTxChar)
{
     if (bTxChar & CTL_M_MASK) == CTL_C_BASE) 
     { 
       transmit ( CTL_C_PFX )
       transmit ( bTxChar + CTL_M_ADON )      
     } 
     else 
     { 
       transmit ( bTxChar )        
     } 
}
  • Die entsprechende Empfangsroutine ist etwas komplexer und wird wesentlich mehr vom Geschick und den Gewohnheiten des Programmieres bestimmt.

Network Controller/PC RS232 mit Windows

Für unser PC / µC Netzwerk muß auch am PC programmiert werden. Da es doch manchmal Anfragen gibt, stell ich einmal dar, wie man von einem PC-Programm aus an die RS232-Schnittstelle kommen kann. Und zwar im üblichen Modus,

  • also 8-Bit,
  • ohne Parity,
  • ohne Flußkontrolle (3 Draht)

Ich verwende übrigens das MS VisualC++.

Windows-Interface

Open

  • Get Handle
char	cName[]="\\\\.\\COM1";  
HANDLE	hFile;

    hFile= CreateFile(cName,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);

    if(hFile==INVALID_HANDLE_VALUE)    // Das war nix, eventuell anderes "COM" versuchen

Das Handle "hFile" wird nun für alle Calls angegeben, wie halt bei Windows üblich.

  • Set State

Mit diesem Handle kann (muß) man die Eigenschaften festlegen. Ich sag's ehrlich, mit den Time-Out Argumenten hab' ich nicht weiter 'rumprobiert, mir war nur wichtig, daß es funktioniert. Das letzte Mal hab ich noch "*.SYS" Treiber für das Steinzeit-DOS geschrieben. Also eventuell bitte um Nachsicht.

COMMTIMEOUTS	sTo;
DCB		sDcb;

	memset(&sDcb,0,sizeof(sDcb));
	sDcb.DCBlength          = sizeof(sDcb);
	sDcb.BaudRate     	= Baud;            // 9600 oder eine andere Baudrate
	sDcb.fParity      	= FALSE;
	sDcb.fBinary      	= TRUE;
	sDcb.Parity       	= NOPARITY;
	sDcb.StopBits     	= ONESTOPBIT;
	sDcb.fOutxCtsFlow 	= FALSE;
	sDcb.fOutxDsrFlow 	= FALSE; 
	sDcb.fDtrControl	= DTR_CONTROL_ENABLE;
	sDcb.fRtsControl	= RTS_CONTROL_ENABLE;
	sDcb.fDsrSensitivity    = FALSE;
	sDcb.fAbortOnError	= FALSE;
	sDcb.ByteSize     	= 8;
	
	if(SetCommState(hFile,&sDcb)) 
 	{
	  sTo.ReadIntervalTimeout	   = MAXDWORD; 		// 0 ms Read-Timeout
	  sTo.ReadTotalTimeoutMultiplier   = 0; 
	  sTo.ReadTotalTimeoutConstant     = 0; 
	  sTo.WriteTotalTimeoutMultiplier  = 1;			// 1*2 ms Write Timeout
	  sTo.WriteTotalTimeoutConstant    = 2;
  	  if(SetCommTimeouts((HANDLE)hFile,&sTo))
		return 1;  // O.K. return
        }
      	CloseHandle(hFile);
        return 0;          // Im Fehlerfall
}

Read

  • Fragen, ob Daten da sind
COMSTAT	sComStat;
DWORD	dwErrorFlags = 0;

	ClearCommError(hFile, &dwErrorFlags, &sComStat);

        sComStat.cbInQue;     // Die Anzahl der Bytes im Buffer
  • Daten abholen
DWORD	dwCount;
	ReadFile(hFile, Buffer, Max, &dwCount, 0);
  • Buffer ein Bereich für die Daten
  • Max die Anzahl der gewünschten Bytes
  • dwCount die Anzahl der tatsächlich bekommenen Bytes

Write

	WriteFile(hFile, Buffer, Count, &dwCount, 0);
  • Buffer die zu sendenden Daten
  • Max die Anzahl der Bytes
  • dwCount die Anzahl der tatsächlich gesendeten Bytes

Close

     	CloseHandle(hFile);

Anwendung für RN-COMM

Entspricht den Level-0 Spezifikationen.

Etwas mehr Theorie hier

Allgemeine Definitionen

#define CTL_M_MASK	0xF8
#define CTL_C_BASE	0xA8
#define CTL_M_ADON	0x18

#define CTL_C_STX	CTL_C_BASE + 1
#define CTL_C_ETX	CTL_C_BASE + 2
#define CTL_C_PFX	CTL_C_BASE + 3

Senden

Durch das Protokoll gegeben ist es einfacher, die Zeichen einzeln zu senden und zu lesen. Ich verwende daher eine Zwischenfunktion, die für Einzelzeichen ausgelegt ist. Um mehrbytige Felder zu senden, verwende ich ggf. eine UNION

union {
  char     bByte[8];
  short    wWord;
  short    iLong;
   ..usw...  
} TxUnion;

int iX;

       TxUnion.iLong  =      ein  32-Bit Wert
       for (iX = 0; iX < 4; iX++)
           SendChanData ( TxUnion.bByte[iX]);
      
       TxUnion.wWord  =      ein  16-Bit Wert
       SendChanData ( TxUnion.bByte[0]);
       SendChanData ( TxUnion.bByte[1]);

int ComWrite (int Zeichen)
{
DWORD	dwCount;
	WriteFile(hFile,&Zeichen,1,&dwCount,0);
return dwCount;
}


  • Message-Header
 ComWrite (CTL_C_STX);                     // Schreiben/Senden des Message-Startzeichens
 bBCC = 0;                                 // Block Check zurücksetzen  
  • Message-Body

Für jedes Datenzeichen: (+BlockCheck)

void SendChanData ( char bOne )
{
     bBCC ^= bOne;                           // Block Check rechnen 
     SendChanChar ( bOne );                  // und weiter 
}

Für jedes Datenzeichen: (+ Kontrolle Sonderzeichen)

void SendChanChar ( char bOne )
{
     if( (bOne & CTL_M_MASK) == CTL_C_BASE)  // Sonderzeichen ?
     {
        ComWrite (CTL_C_PFX);                // Ja, Präfix senden 
        ComWrite (bOne + CTL_M_ADON);        // und sonderzeichen + offset
     }
     else	
        ComWrite (bOne);                     // Zeichen normal 1:1 senden 
}
  • Message-Trailer
     SendChanChar (bBCC);                    // Block Check anfügen
     ComWrite (CTL_C_ETX);                   // Message Ende


Empfangen

Man kann das verschieden gestalten. Ich mache es so, daß ich durch einen Timer 10 x in der Sekunde schaue, ob was gekommen ist, und interpretiere dann das, was gerade da ist. Das klingt grausam, hat aber bisher gereicht. Trotzdem wäre wohl eine Interrupt-Routine eleganter.

unsigned char	bInChar;
   if (ComGetReadCount( ))                  // Wenn was da ist
   {
	while (ComRead((void*)&bInChar, 1)) // Zeichen holen
	{
    	     iSts = SniffChan( bInChar);    // Zeichen interpretieren. 
                                            // wenn eine Message komplett ist
                                            // kommt eine 1 zurück, sonst eben 0
      	     if (iSts)
             {
                 // eine Message wird verarbeitet
             }    
        }
   }

Die Funktion

int   SniffChan( unsigned char bInChar)

hat insgesamt drei Zustände:

  • Neutral, noch kein STX-Zeichen erhalten.
  • Message, wir haben ein STX-Zeichen erhalten, da gibt's dann zwei Möglichkeiten
    • Daten normal, Steuerzeichen werden erkannt und interpretiert
    • Daten prefixed, Was auch kommt, es gilt als (modifiziertes) Datenbyte

Neutral

    if (bInChar == CTL_C_STX)
    {
	                 // Rücksetzen Daten buffer
	bBCC = 0;  	 // Rücksetzen Block Check 
                         // Ändern Zustand auf "Message" 
     }
    .. // alles andere ist im Moment uninteressant

Daten Normal

        switch (bInChar)
	{
	case CTL_C_STX:					// Start condition
                // Ein Start-Zeichen mittendrin. 
                // d.h. die eigentlich laufende Message ist irgendwie verstümmelt gewesen.
                // Also alles wieder zurücksetzen und eine neue Message anfangen 
                     // Rücksetzen Daten buffer
        	     // Rücksetzen Block Check 
                     // (Zustand bleibt auf "Daten normal")
		break;
	case CTL_C_PFX:					// Prefix
                     // Zustand ändern auf "Daten Prefixed"
		break;
	case CTL_C_ETX:					// End Condition
                // Block Check prüfen. Der Check ist ok, wenn bBCC == 0
                // Zustand ändern auf "Neutral"
		return(1);	// Es ist eine komplette Message da
		break;
	default:	
                ...                             // Zeichen speichern
		bBCC ^= bInChar;                // Blockcheck rechnen 
		break;
	}
        return (0);            // Zeichen verarbeitet, KEINE komplette Message da


Daten Prefixed

        ....                               // ( Zeichen - CTL_M_ADON) speichern
        bBCC ^= ( Zeichen - CTL_M_ADON)    // Blockcheck rechnen 
        return(0);

Autor

Siehe auch


LiFePO4 Speicher Test