Support für BiDiB

Empfangsroutine (für seriellen Link)

Auf einem seriellen Link verwendet BiDiB ein besonderes Byte (das sogenannte MAGIC) zur Kennzeichnung eines Datenrahmens. Damit nich irrtümlich innerhalb der Daten ein MAGIC erkannt wird, muss man verhindern, dass ein normales Datum mit Inhalt =MAGIC übertragen wird. Das erfolgt mittels Voranstellen eines weiteren Sonderzeichens (ESCAPE) und Verändern des Datums. Folglich muss man im Empfänger eine Erkennung von ESCAPE und MAGIC voranstellen und fallweise das ESCAPE'n zurücknehmen.


data = rx_fifo_read();                  // reads one byte
if (data == BIDIB_PKT_MAGIC)
  {
    process_packet();
  }
else if (data == BIDIB_PKT_ESCAPE)
  {
    escape_hot = 1;                    // set a flag for next data
  }
else
  {
    if (escape_hot)
      {
        data ^= 0x20;
        escape_hot = 0;
      }
    collect_data(data);               // in Empfangspaket-Buffer ablegen
  }

Sinnvollerweise macht man die CRC-Berechnung beim Empfang gleich mit, so dass sich der Empfänger erweitert zu:


int run_bidib_host_if(void)
 {
  unsigned char data;

  if (bidib_rx_packet_ready)
    {
      if (bidib_packet_is_processable())
        {
          // yes, we got packet with messages in it, lets loop thro all messages
          bidib_rx_total = bidib_rx_index - 1;
          bidib_rx_index = 0;
          while(bidib_rx_index < bidib_rx_total)
            {
              bidib_rx_index += process_bidib_host_message(&bidib_rx_packet[bidib_rx_index]);
            }
          bidib_rx_index = 0;
          bidib_rx_packet_ready = 0;
        }
      else
        {
          // oops: we got stuck – more messages from the host than we could process
          // we return here, other tasks could process the downstream bidibus,
          // our input fifo will automatically stop the host
          return(0);   // ready again
        }
    }

  while (rx_fifo_ready())                     // accumulate data to get a complete packet
    {
      data = rx_fifo_read();                  // reads one byte
      if (data == BIDIB_PKT_MAGIC)
        {
          if (bidib_rx_crc)                   // check crc
            {
              bidib_send_error(BIDIB_ERR_CRC, bidib_rx_msg_num);
              bidib_rx_crc = 0;
              bidib_rx_index = 0;             // if failed: kill complete packet
            }
          else
            {
              bidib_rx_packet_ready = 1;
            }
        }
      else if (data == BIDIB_PKT_ESCAPE)
        {
          escape_hot = 1;
        }
      else
        {
          if (escape_hot)
            {
              data ^= 0x20;
              escape_hot = 0;
            }
          if (bidib_rx_index < sizeof(bidib_rx_packet))
            {
              bidib_rx_packet[bidib_rx_index++] = data;
              bidib_rx_crc = crc_array[data ^ bidib_rx_crc];
            }
          else
            {
              bidib_send_error(BIDIB_ERR_SIZE, bidib_rx_msg_num);
            }
        }
    }
  return(-1);
}

Der Aufruf von bidib_send_error(err_num, err_para) erfolgt sinnvoller durch Übergabe in eine Queue, aus welcher die Fehler dann an den Host weitergereicht werden.

BiDiB via USB (virtueller COM-Port)

Serielle Schnittstellen werden auf USB als sogenannte virtuelle COM-Schnittstellen abgebildet. Dabei ergibt sich für den Konverter-Chip das Problem, dass er auf der einen Seite (USB) eine Paket-orientierten Betrieb hat, auf der anderen Seite (RS232) eine Byte-orientierte Übertragung vorgesehen ist, speziell die Richtung von RS232 auf USB ist hierbei interessant: wie bildet der Chip empfangene Bytes auf Pakete ab, welche Größe haben diese Pakete und wann tut er das?

Als Beispiel sei hier mal das Verhalten des verbreiteten FTDI Chips FT232RL geschildert:

Zum USB hin agiert er mit einer Paketgröße von 64 Byte, davon sind 2 Byte für Status und 62 Bytes für Nutzdaten vorgesehen. Ein USB-Chip kann max. mit der Rate von 1ms Pakete übertragen (sofern er gepollt wird). Beim Empfang serieller Daten wird ein USB-Transfer ausgelöst, wenn:

  • Der Empfangspuffer voll ist: d.h. es sind 62 Byte serielle Daten angekommen und können weiter transportiert werden.
  • Der Empfangspuffer noch nicht voll ist und ein chipinterner Timeout-Zähler einen Transfer veranlasst. Damit stellt der Chip sicher, dass einzelnen Byte nicht zu lange im Puffer liegen, sondern 'in Zeit' zum Host weiter transportiert werden.
  • Der Empfangspuffer noch nicht voll ist und ein 'Transfertrigger' erfolgt. Transfertrigger können Zustandsänderungen der Leitungen CTS, DSR, DCD, RI sein oder auch der Empfang eines speziellen Zeichens.

Wenn man den FTDI-Chip für serielle Kommunikation zu einen Modellbahnsystem einsetzt, dann sind da in der Regel kleine Pakete unterwegs (welche bei traditionellen Protokollen auch noch als Sequenz aus Frage/Antwort aufgebaut sind), dabei wird der Puffer i. d. R. nur teilweise gefüllt. Der Weitertransport Richtung Host wird dann erst vom Timeout-Zähler veranlasst, welcher beim FTDI-Chip in der Grundeinstellung bei 16ms liegt. Hier besteht also die Gefahr, dass man ein serielles Protokoll wirksam ausbremst.

Optimierungsmöglichkeiten: Für den FT-Chip besteht die Möglichkeit, dass man einen USB-Transfer auf den Empfang des Framezeichens (z. B. 0xFE) auslösen lässt. Da BiDiB am Ende eines Paketes ein fest definiertes Framezeichen hat, kann man dieses Zeichen auch in den FTDI-Chip eintragen. Dies erfolgt mit dem Treiberaufruf 'FT_SetChars'. Dann wird sofort nach Empfang eines seriellen Paket ein USB-Transfer ausgelöst und die Wartezeit von 16ms fällt nicht an.

Bei Windowssystemen kann man in den Eigenschaften des COMport die Paketgröße und die maximale Wartezeit einstellen. Hierzu wird im Gerätemanager der Port angewählt, es öffnet sich das Eigenschaftsfenster der Com-Port-Schnittstelle. Hier kann man dann unter 'Anschlusseinstellungen -> Erweitert…' die erweiterten Einstellungen vornehmen, u.a. Puffergröße und max. Latenz einstellen.