Usb_Oszilloskop
Dienstag, 22. Mai 2012
 
 

PIC Mikrocontroller Forum  |  PIC Mikrocontroller  |  Beispielcodes und Projekte  |  Usb_Oszilloskop « vorheriges nächstes »
Seiten: [1] Nach unten Drucken
Autor Thema: Usb_Oszilloskop  (Gelesen 8272 mal)
 
qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« am: Februar 16, 2010, 23:11:27 »

PicUsbOszilloskop - Projekt
Ein Mini-Oszilloskop mit einem PIC18F2550, der analoge Spannungen digitalisiert und die Daten via Usb-Schnittstelle an den PC leitet.

Das Projekt ist eventuell für Leute interessant, die sich gerade mit der PIC-Usb-Schnittstelle herumschlagen.
Die Grundlage des Projekts ist die PIC-Firmware von Microchip (Interrupt-Transfer) (Version von 2004).
Die Samplerate des PIC ist 114,5 KByte/s und die Usb-Übertragungsrate ist 128 KByte/s.
Die Entwicklungsumgebung ist auf der PIC-Seite: MPLab mit MCC18-C-Compiler und auf der PC-Seite: VisualC++.
Der PIC ist ein 18F2550 mit 20MHz Quarz, braucht eigene Stromversorgung (nicht USB-powered), VIDPID = 04D8_000C

Ich beschreibe hier mein Projekt - aber eigentlich sind es lauter Fragen. Ich bin mir bei einigen Dingen nicht sicher, ob sie so stimmen, wie ich sie beschreibe und hoffe auf Kommentare, die mögliche Fehler korrigieren und Verbesserungsvorschläge machen.


Themen:
PIC:
ADC, maximale Samplerate(Huch), Usb-Interrupt-Transfer, UsbFirmware anpassen, Synchronisation des PicProgramms mit dem UsbBusTakt, simples Ping-Pong-buffering
PC:
Synchronisation des PC-Programms mit dem UsbBusTakt, MultimediaTimer, Untersuchung der Funktion "MPUSBReadInt"
 

Ziel:
Ziel des Projekts war, eine Art Spielzeugoszilloskop zu bauen, das aus einem PIC und aus einer UsbLeitung zum PC besteht und sonst aus nichts. Die ADC-Möglichkeiten des PIC sollten möglichst gut ausgenutzt und die Daten via USB möglichst in Echtzeit an den PC gesendet werden, wo sie dann in beliebiger Form ausgewertet und angezeigt werden können.

Schwierigkeiten:
Der PIC muss einerseits den ADC in konstanten Zeitabständen (und möglichst oft) auslesen und andererseits die gesammelten Daten über die UsbSchnittstelle paketweise aussenden. Das Absenden der Datenpakete darf nicht so lange dauern, dass der PIC mit dem lesen des ADC in Verzug kommt.

Lösungsansatz:

Sampling (ADC, Samplerate):
Das Wichtigste für ein Oszilloskop scheint mir eine konstante und einigermaßen hohe Samplerate zu sein. Daher wird der ADC über einen Interrupt getriggert, der vom TIMER1 ausgelöst wird (CCP-Modul als „Special-Event-Trigger“). Die gesamte ADC-Prozedur wird von der PicHardware übernommen - das Programm muss nur noch das ADC-Resultat auslesen. Das Auslesen erfolgt in einer InterruptRoutine, die die ADC-Daten in einen Puffer kopiert. Die damit erreichbare Samplerate (ADC leicht übertaktet, kaum AcquisitionTime) ist bei mir 8,666ys pro Sample -> ca. 115 kHz.  Für ein Oszilloskop immer noch recht mager. Die Auflösung betragt nur 8 Bit (ca. 20mV). (Auf die beiden LSB des 10-Bit-ADC habe ich verzichtet).

USB:
InterruptTransfer
Die UsbVerbindung muss pro ms (pro USB-Frame)  115 Bytes ADC-Daten und ein paar Informationsdaten übertragen.
Mein UsbProgrammteil baut auf der Firmware von Microchip auf  (MCHPFSUSB/fw/Demo).
Ich verwende Interrupt-Datentransfer (siehe:usb in a nutshell/ usb4/ InterruptTransfer). Wahrscheinlich wäre Isochronous Transfer besser geeignet, aber das InterruptDing war schon kompliziert genug. InterruptDatenPekete sind auf 64 Bytes beschränkt. Das heißt ich brauche 2 Pakete pro UsbFrame (pro 1 ms). Es ist mir aber nicht gelungen, mehr als ein solches Paket innerhalb eines Frames über eine einzelne Usb-Pipe zu versenden . Daher habe ich zunächst 2 Pipes für 2 separate Endpoints installiert, die mein PC-Programm jede Millisekunde gleichzeitig abruft (um daraufhin 2 * 64 Bytes zu empfangen). Falls jemand weiß, wie man das besser lösen kann, wäre ich für eine Mitteilung sehr dankbar.


PingPongPuffer
Die nächste Schwierigkeit war, die ADC-Daten, die sich in einer Millisekunde ansammeln (114 Bytes) schnell genug aus einem temporären Puffer in den USB-Puffer zu schreiben, ohne dass der ADC-Interrupt diesen Vorgang unterbricht und Bytes in den Puffer schreibt, die eigentlich erst ins nächste Datenpaket gehören. Es ist normaler Weise nicht möglich, die Bytes direkt in den USB-Puffer zu schreiben, weil es da zu Konflikten mit der USB-Hardware kommt, sobald diese den Puffer ausliest um den Inhalt an den PC zu senden (shared buffer) - außer man hat 2 solche Puffer, die abwechselnd zum Einsatz kommen (Ping-Pong-Puffer). ((Beim PIC gibt es die Möglichkeit, jeden einzelnen Endpoint mit 2 UsbPuffern auszustatten, also mit PingPongPuffern. Das war mir aber zu mühsam.)) Daher habe ich einen Endpoint für Ping (PufferA) und einen anderen Endpoint für Pong (PufferB) eingesetzt. In der ersten  Millisekunde wird Puffer-A kontinuierlich von der ADC-Interrupt-Routine beschrieben (alle 8,66ys ein Byte) während Puffer-B zunächst auf den UsbTransfer wartet  und dann von der Hardware gesendet wird (wie lange er wartet, hängt vom Datenverkehr im UsbBus ab, aber es muss innerhalb dieser Millisekunde [dieses Frames] passieren). In der nächsten  Millisekunde ist es umgekehrt: Puffer-B wird vom ADC-Interrupt beschrieben und Puffer-A wird gesendet.

Pointer zum UsbPuffer
Woher weiß die  ADC-Interrupt-Routine, in welchen Puffer sie schreiben darf? Die Routine benutzt einen Pointer (FSR0, mit automatischem increment [POSTINC0]), der von der AdcSendRoutine jedes mal, wenn Puffer-A gerade gesendet wurde auf die Startadresse des Puffers-A gesetzt wird. Wenn Puffer-B gesendet wurde, wird der Pointer auf Puffer-B gesetzt.
Bevor die AdcSendRoutine den Pointer umschreibt, kopiert sie ihn noch in den Puffer hinein. Diese Zahl gibt an,  wie viele Bytes, die die Interruptroutine in den Puffer geschrieben hat (im Regelfall 114 bis 115) – die Zahl wird vom PC-Programm benötigt.

Vier Endpoints
Genau genommen sind es nicht nur 2, sondern insgesamt 4 Puffer: Puffer-A und Puffer-B bestehen jeweils aus zwei 64-Byte-Puffern. Jeder ist einem eigenen Endpoint zugeordnet. Der Grund dafür ist, dass Interrupt-Endpoints nur 64Bytes/ Paket übertragen können.
In der ersten Millisekunde werden Puffer1&2(Puffer-A) beschrieben (2 * 64 Bytes) und Puffer3&4(Puffer-B) gesendet, in der nächsten Millisekunde ist es umgekehrt. Ich habe also 4 Endpoints, die über 4 Pipes mit dem PC kommunizieren.

AdcSendRoutine
Die AdcSendRoutine ist ein Loop, der folgende Stationen durchläuft:

Code:
1.)  Warte auf ein „Start-Of-Frame-Signal“ (SOFIF). Hier wird die
     AdcSendRoutine mit dem UsbBusTakt synchronisiert.
     Wenn SOFIF == 1, gehe zur nächsten Station:

2.)  Warte bis Puffer1&2 gesendet wurde (wait until(USIE[1,2] == 0)).
     (USIE ist ein Semaphor: eine Flag, die anzeigt, ob der Puffer von der
     UsbHardware genutzt wird (USIE==1)oder ob er für das UserProgramm zur
     Verfügung steht(USIE==0)).

3.)  Setze den ADC-Interrupt-Schreibe-Pointer auf die Adresse von Puffer1.
     Ab jetzt darf hier geschrieben werden, weil der Puffer gerade gesendet
     wurde und erst in ca.1,5 bis 2 ms wieder gesendet wird. Die Interruptroutine
     wird in der kommenden Millisekunde ca. 114 Bytes in Puffer1 und Puffer2 schreiben.
     Da der Pointer jetzt auf die Puffer1&2 gerichtet ist, sind die Puffer3&4 frei
     um gesendet zu werden: USIE[3,4] = 1 (set Buffer ready). Die Puffer sollten
     noch in dieser Millisekunde (innerhalb dieses Frames) abgeschickt werden.
     Wann genau, ist ungewiss und hängt davon ab, wann der USB-Bus das
     „IN-Token“(= Signal: “schick etwas an den PC“) an die Endpoints3&4 ausgibt.
     ((Bevor die Puffer3&4 zum Senden frei gegeben werden, werden noch Steuerdaten
     für das PC-Programm an die letzten zwei Pufferstelle von Puffer4 geschrieben)).

4.)  Warte auf das nächste „Start-Of-Frame-Signal“ (SOFIF). (jetzt kommt die
     selbe Prozedur für die anderen 2 Puffer ):

5.)  Warte bis die Puffer3&4 gesendet wurden (wait until(USIE[3,4] == 0)).

6.)  Setze den ADC-Interrupt-Schreibe-Pointer auf die Adresse von Puffer3. 
     Jetzt sind die Puffer1&2 frei um gesendet zu werden-> USIE[1,2] = 1 (set Buffer
     ready). Die Interruptroutine wird in der kommenden Millisekunde ca. 114 Bytes
     in Puffer3 und Puffer4 schreiben.

7.)  Zurück zu Station 1.).

Die AdcSendRoutine ist ein Loop, der nur von einem PC-Steuerbefehl abgebrochen werden kann. Das heißt: innerhalb der Routine müssten auch alle StandardRequests des Usb-Protokolls abgearbeitet werden (RESET, SUSPEND, STALL... (siehe: USB in a NutShell)). Darauf verzichtet mein Programm innerhalb der AdcSendRoutine (ohne dass es zu Problemen kommt). Nur die "UsbTransactionComplete-Flag" (TRNIF)muss regelmäßig gelöscht werden (siehe: MCHPFSUSB_FW_UG_51679a.pdf / Kapitel:1.3.2.2.2). Das passiert in den 4 Warteschleifen der Routine.
Die Wartezeit zwischen SOFIF ==1 und USIE ==1 ist nicht immer gleich lang. Daher werden in den Puffern 1&2 und 3&4  mal mehr, mal weniger Bytes sein. War die Wartezeit diesmal länger, wird sie beim nächsten mal entsprechend kürzer sein, so dass im Mittel immer 114,5 AdcDatenBytes pro ms an den PC gesendet werden.  Aus diesem Grund ist es wichtig dem PC-Programm die genaue Anzahl der gültigen AdcDatenBytes für jedes DatenPaket mitzuteilen(Steuerbyte am Ende des Pakets).
Außerdem wird an jedes Paket eine fortlaufende Paketnummer angehängt. Das ist wichtig, weil das System in der Anfangsphase (in den ersten 10 ms) oft "stolpert", was zur Folge hat, dass die Pakete in falscher Reihenfolge ankommen (Genaueres später).

PC-Programm:
Steuerbefehl an den PIC
Nachdem der PIC über USB an den PC angeschlossen und vom Betriebssystem erkannt worden ist (SetupPhase), tut er zunächst einmal nichts, als auf einen "SteuerBefehl" des PC-UserProgramms zu warten. Die Steuerbefehle werden nicht über die Endpointpipes-1bis4 abgewickelt, sondern über einen eigenen Endpoint, der auch Daten vom PC empfangen kann (Endpoint_IN_OUT). Beim Befehl: "SendeAdcDaten_Start" springt das PIC-Programm in den oben beschriebenen AdcSendRoutine-Loop, in dem es solange bleibt, bis der Befehl: "Send_Stop" vom PC kommt.

IN-Transfer
Gleich nachdem das PC-Programm den Startbefehl gegeben hat, öffnet es die 4 Kommunikationspipes zum PIC. Ab diesem Moment schickt der Usb-Controller des PC jede Millisekunde 4 IN-Tokens (“schick etwas an den PC“) über die 4 Pipes an die 4 Endpoints des PIC.

a.)IN-Token für Endpoint1: "sende 64 Bytes über die Pipe1 an den PC"
b.)IN-Token für Endpoint2: "sende 64 Bytes über die Pipe2 an den PC"
c.)IN-Token für Endpoint3: "sende 64 Bytes über die Pipe3 an den PC"
d.)IN-Token für Endpoint4: "sende 64 Bytes über die Pipe4 an den PC"

Diese 4 Befehle kommen kurz nacheinander beim PIC an. Von der AdcSendRoutine des PIC können aber nur zwei dieser Befehle innerhalb einer Millisekunde ausgeführt werden (es sind immer nur zwei der vier Endpointbuffers sendebereit [USIE = 1]). Die anderen 2 Befehle werden vom PIC „geNAKt“(= der PIC sendet das NAK-Signal an den PC, das bedeutet: EndpointBuffer-n nicht bereit). Das hat zur Folge, dass die Befehle vom UsbController beim nächsten Frame (1 ms später) erneut an den PIC geschickt werden - genau zu dem Zeitpunkt, zu dem sie vom PIC erwartet werden (zu diesem Zeitpunkt sind nämlich die zuvor geNAKten Endpoints bereits sendebereit, dafür werden jetzt die 2 anderen Endpoints geNAKt).
Die Reihenfolge der erfolgreich beantworteten in IN-Tokens ist: 1.Millisekunde: a&b,  2.Millisekunde: c&d, 3.Millisekunde: a&b,  4.Millisekunde: c&d, u.s.w...
Die gesendeten Datenpakete werden in einem Puffer des Usbtreibers geparkt und müssen vom Userprogramm von dort ausgelesen werden: für jede Pipe ein eigener Lesebefehl (Genaueres später).

Einpendelphase
In den ersten paar Millisekunden nach dem Startsignal passiert es manchmal, dass sich die Reihenfolge der beantworteten und der geNAKten IN-Tokens vertauscht. Wie das genau vor sich geht ist mir unklar. Jedenfalls kommen dann im UserProgramm Datenpakete mit vertauschten Paketnummern an. Da hilft es nur, die Lesebefehle für Pipe1&2 oder für Pipe3&4 für zwei Millisekunden auszusetzen. Nach 5 bis 30 ms ist der Fehler ausgeglichen und das System ist eingependelt. Ab dann lauft es stabil. Das heißt: sowohl PIC- als auch PC-Programm haben sich mit dem UsbBusTakt synchronisiert.

MultimediaTimer
Der Teil des PC-Programms, der die Kommunikation mit dem PIC abwickelt, ist eine sogenannte TimerCallbackRoutine: das ist im Prinzip vergleichbar mit einer TimerInterrupt-Routine beim PIC. Sie wird vom "MultimediaTimer" des PC, alle 2 ms getriggert.
Mit "timeSetEvent" wird der MultimediaTimer (high-resolution-timer) etabliert. Er ruft in einstellbaren Intervallen eine TimerCallbackRoutine des Anwenderprogramms auf (bei mir alle 2ms).

TimerCallbackRoutine
Diese Routine durchlauft im Wesentlichen folgende Stationen:

Code:
1.)
  if(SyncFlag != 1)  //wenn mit den DatenPaketnummern alles in Ordnung ist...
        Read(Pipe1 ) //(Befehl: a) 64Bytes in den UserProgPuffer+Offset+0
        Read(Pipe2 ) //(Befehl: b) 64Bytes in den UserProgPuffer+Offset+64

  if(SyncFlag != 2)  //wenn mit den DatenPaketnummern alles in Ordnung ist...
        Read(Pipe3 ) //(Befehl: c) 64Bytes in den UserProgPuffer+Offset+128
        Read(Pipe4 ) //(Befehl: d) 64Bytes in den UserProgPuffer+Offset+192

                     //als Read-Befehl verwendet das Programm "MPUSBReadInt",
                     //ein spezielles Api von Microchip, das mit dem Microchip-USB-
                     //Driver "mchpusb.sys" kommuniziert. (siehe:
                    //[MCHPFSUSB/Pc/mpusbapi/Dll/Borland_C /Source/_mpusbapi.cpp])
  SyncFlag = 0
2.)
  if(PaketNummer_Pipe3&4 - PaketNummer_Pipe1&2 > 2) //wenn Datenpakete der
                     //Pipe1&2 verschluckt wurden....
        SyncFlag = 2 //überspringe beim nächsten Durchlauf die Befehle: c und d

  if(PaketNummer_Pipe3&4 <= PaketNummer_Pipe1&2 ) //wenn Datenpakete der Pipe3&4
                     //verschluckt wurden....
        SyncFlag = 1 //überspringe beim nächsten Durchlauf die Befehle: a und b

  if(AnzahlAdcDaten_Pipe1&2 < 90 ODER AnzahlAdcDaten_Pipe3&4<90)
                     //wenn die Anzahl der gültigen AdcDatenBytes zu
                     //gering ist...
Sende_Stop; SendeAdcDaten_Start; //Neustart der
                     //PIC-Adc-SendRoutine (das hilft!!!)
3.)
  UserProgPuffer_Offset += 256
  if(UserProgPuffer_Offset >= UserProgPuffer_Size)
UserProgPuffer_Offset = 0
ZeichneOszilloskopGraph()  //der UserProgPuffer wird gezeichnet

  return

MPUSBReadInt
Wenn ich das richtig verstanden habe, lauft das Lesen der Daten mit dem Befehl MPUSBReadInt folgender Maßen ab:
Sobald die Pipe (der Datentransfer) zu einem bestimmten PIC-Endpoint geöffnet ist (in meinem Fall mit der Funktion: MPUSBOpen (EndpointX_ASYNC)), sendet der UsbTreiber in regelmäßigen Intervallen ein IN-Token an den Endpoint (via UsbController ). Die Dauer des Intervalls ist durch den EndpointDescriptor festgesetzt (bei mir 1ms). Die hereinkommenden PIC-Daten werden in einem Treiberpuffer gespeichert (da passen 100 Datenpakete a 64 Bytes hinein). Wenn der Puffer voll ist, werden alle neu hereinkommenden Daten ignoriert.
Wenn jetzt das Userprogramm MPUSBReadInt aufruft, wird das älteste Datenpaket aus dem Treiberpuffer gelesen (= in den UserProgPuffer verschoben) und die Funktion kehrt sofort zum Programm zurück. Das nächste MPUSBReadInt liest dann das zweitältesten Paket u.s.w..
Wenn MPUSBReadInt zu selten aufgerufen wird, wenn also weniger Pakete aus dem Treiberpuffer ausgelesen werden, als hereinkommen, wird der Puffer immer voller, bis er die 100er-Grenze errreicht. Ab da ignoriert der Treiber jedes Datenpaket, wenn nicht zuvor ein MPUSBReadInt durch das Lesen eines Pakets, Platz im Puffer freigeschaufelt hat. (Wenn die Lesefrequenz halb so groß ist, wie die Frequenz der hereinkommenden Pakete, geht jedes zweite Paket verloren).
Wenn die MPUSBReadInt-Befehle häufiger kommen, als die Datenpakete, wird der Puffer immer leerer, bis kein Datenpaket mehr auf Vorrat liegt. Ab da tritt die Wartefunktion von MPUSBReadInt in Kraft: Die Wartezeit von MPUSBReadInt ("timeout-interval") ist frei wählbar (z.B. 2ms) und wird mit dem Befehl mitgeschickt. MPUSBReadInt wartet also eine Weile auf hereinkommende Daten und blockiert dadurch den Programmfluss. Wenn innerhalb des Timeout Daten  hereinkommen, werden sie gelesen, MPUSBReadInt kehrt zurück und das Programm geht weiter.
(Wenn keine Daten innerhalb des Timeout hereinkommen, kehrt MPUSBReadInt mit einer Fehlermeldung (im Returnwert) zurück).

Synchronisation
Diese Wartefähigkeit des Read-Befehls ist sehr wichtig. Denn nur dadurch synchronisiert sich das Programm mit dem UsbBusTakt und es gehen keine Daten verloren. Voraussetzung dafür, dass die Read-Befehle warten ist, dass sie oft genug aufgerufen werden, damit sich der Treiberpuffer leert. (Für dieses Oszilloskopprojekt ist es in Wirklichkeit nicht unbedingt nötig, dass das Userprogramm sofort auf die Daten zugreift, wenn sie hereingekommen.)


Vermischtes:
Pic-UsbFirmware-File
Um einen besseren Überblick über die Microchip-UsbFirmware zu bekommen, habe ich alle benötigten Firmware-Funktionen zusammen mit meinen Userfunktionen in ein einzelnes File gepackt (UsbOszi.c). Daneben gibt es in meinem PIC-Projekt nur noch zwei Header-Dateien und "18F2550.lkr". Die AdcSendRoutine und die ADC-Interrupt-Routine sind in Assembler Code geschrieben, um eine bessere Kontrolle über das timing und den Pointer (FSR0) zu gewährleisten.

PC-Projekt-Files
Das PC-Programm verwendet den UsbTreiber von Microchip "mchpusb.sys". In das C++Projekt sind neben den Standard-Headerdateien auch usbDsc.lib & .h (für die Microchip-Usb-APIs) , und WINMM.lib (für den Multimediatimer) eingebunden.

Schwächen des Projekts
Das PC-Programm ist nicht gerade ressourcenschonend. Durch die Wartezeiten in der TimerRoutine beansprucht das Programm viel Rechnerzeit.
Das Oszilloskop wird auf langsameren Rechnern durch andere Usb-Geräte (z.B. eine Usb-Maus) leicht irritiert. Das Äußert sich darin, dass die Datenpakete in unregelmäßigen Intervallen übertragen werden (1ms +/- 0,12ms). Daher kann es vorkommen, dass in einem Paketen z.B. nur 96 ADC-DatenBytes (statt 114) sind. Im darauf Folgenden müssten es dann 132 ADC-DatenBytes sein. Die Paketgröße ist aber nur 128 -> 4 Bytes gehen verloren. ((genau genommen gehen 6 Bytes verloren, weil die letzten 2 Bytes im Datenpaket für Paketnummer und Anzahl_der_gültigen_Bytes reserviert sind))
Der PIC-Quellcode ist ziemlich chaotisch. Im PC-Programm fehlen noch ein paar Features.

Tests und Hardware
Ich habe das Programm auf unterschiedlich schnellen Rechnern, aber nur unter WindowsXP getestet.
Die Hardware des Oszilloskops besteht nur aus einem PIC, einer LED und einem Quarz(+Kondensatoren). Um Eingangsschutzdioden und Spannungsteiler am Analogeingang habe ich mich nicht gekümmert. Ich habe die Schaltung ausschließlich mit Wechselspannungen im Bereich: 0 bis 5V getestet.

Dateianhang
usbOszilloskop_PIC.zip
HexFile, Anschluss-Schema, UsbTreiber (mchpusb.sys + inf) und 3 Quellcodedateien (UsbOszi.c, UsbOszi.h, typedefs.h)
 
usbOszilloskop_Pc.zip
UsbOszilloskop.exe und Quellcodedateien (MainFrm.cpp, Oszilloskop.cpp, MainFrm.h)

Bilder
UsbOszi1: Ein Screenshot des PC-Programms. Angezeigt wird der Output eines 6kHz-Multivibrators
UsbOszi2: Ein Screenshot des PC-Programms. Angezeigt wird der Output einer s/w-Cmos-Kamera (eindeutig zu schnell für das Oszilloskop)



* usbOszilloskop_PIC.zip (100.11 KB - runtergeladen 290 Mal.)
* usbOszilloskop_Pc.zip (27.05 KB - runtergeladen 239 Mal.)

* UsbOsz2.JPG (271.23 KB, 1256x495 - angeschaut 613 Mal.)

* UsbOsziKam.JPG (244.36 KB, 1256x495 - angeschaut 459 Mal.)
« Letzte Änderung: Februar 16, 2010, 23:39:02 von qwer » Gespeichert
Stampede
Globaler Moderator
Hero Member
*****
Offline Offline

Beiträge: 969



Profil anzeigen WWW
« Antworten #1 am: Februar 18, 2010, 19:03:25 »

Hallo qwer,

einentlich eine ganz coole Idee. Nur ist der Interruptbasierte Transfer über USB sehr langsam, vielleicht solltest du mal BULK ausprobieren. Leider fehlen die zip-Dateien.
Was sind denn nun konkret deine Fragen?

Grüße
Stefan
Gespeichert

qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #2 am: Februar 18, 2010, 22:05:01 »

Hallo Stefan, danke für dein Interesse.

Zitat
Nur ist der Interruptbasierte Transfer über USB sehr langsam,...
Die Datentransferrate von 128KByte/s reicht für dieses Projekt eigentlich aus -> der PIC kann so wie so nicht mehr als 115KBytes ADC-Daten pro Sekunde produzieren. Aber es stimmt schon: die Verwendung von 4 Interrupttransfer-Pipes ist sehr unelegant.

Zitat
...vielleicht solltest du mal BULK ausprobieren.
ja, bulk-Transfer würde wahrscheinlich vieles vereinfachen (z.B.: nur 2  Endpoints). Aber der Nachteil bei bulk scheint mir zu sein, dass diese Tranferart keine hohe Priorität bei der Bus-Bandbreitezuteilung hat, und daher die Wartezeit (latency) der bulk-Datenpakete nicht garantiert ist (zuerst kommen die anderen Transfers dran (isochronous und interrupt), erst wenn dann noch Zeit bleibt, wird bulk bedient) – wenn ich das richtig verstanden habe.
Bei mir ist aber die Voraussetzung für das Funktionieren des Datenflusses (ADC -> Usb-Puffer  -> PIC-Hardware -> UsbHost), dass die Puffer mit den ADC-Daten  in ganz regelmäßigen Abständen an den PC abgeschickt werden – anders gesagt: die Pufferinhalte müssen rechtzeitig aus dem PIC raus  und die Puffer wieder für die CPU freigeben werden (alle 2 ms), damit die ständig neu hereinkommenden ADC-Daten nicht die alten Daten im Puffer überschreiben.

Zitat
Leider fehlen die zip-Dateien
Welche zip-Dateien meinst du?

Zitat
Was sind denn nun konkret deine Fragen?
Was mir nach wie vor unklar ist, (und das ist auch meine Frage ans Forum): wie schickt man mehrere Datenpakete (z. B.  64-Byte-Pakete) innerhalb eines Frames über eine einzelne Pipe?
freue mich auf Antwort, qwer.



Gespeichert
Stampede
Globaler Moderator
Hero Member
*****
Offline Offline

Beiträge: 969



Profil anzeigen WWW
« Antworten #3 am: Februar 18, 2010, 23:26:30 »

Hi qwer,

Zitat
wie schickt man mehrere Datenpakete (z. B.  64-Byte-Pakete) innerhalb eines Frames über eine einzelne Pipe?
Bei einem Fullspeed Gerät passen neunzehn 64 Byte Übertragungen in einen Frame. Soweit ich weiß, kann aber jeder Endpunkt pro Frame nur einmal seine Daten übertragen, sodass du mehre Endpunkte nutzen muss um eine höhere Datenrate zu erreichen. Damit würde ich deine Frage mit "das geht nicht" beantworten.
Bulk ist natürlich deutlich schneller, garantiert aber keine maximale Latenz, daher ist es wahrscheinlich für deine Zwecke wirklich ungeeignet.
Zitat
Wahrscheinlich wäre Isochronous Transfer besser geeignet
Leider ist dabei nicht garantiert, dass die Daten auch ankommen. Es gibt kein Handshake, die Daten beliebiger Größe pro Frame werden konstant gesendet.
Ich hätte für dein Projekt wahrscheinlich gleich einen PIC32 genommen, der hat zumindest einen 1MSPS ADC, was aber immer noch die Problematik mit USB mit sich bringt. Da der aber sehr viel RAM hat, könnte das hier mit Bulk Transfers unter Umständen klappen.
Zitat
Welche zip-Dateien meinst du?
Du schreibst etwas von "Dateianhang" in deinem Beitrag. Also hatte ich erwartet, dass es auch was zum Download gibt.  Smiley
Wenn du dich tiefergehend mit USB beschäftigen musst oder möchtest, sind die Bücher (v.a. USB Complete) von Jan Axelson sehr empfehlenswert.

Gruß
Stefan
Gespeichert

qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #4 am: Februar 19, 2010, 00:49:48 »

Stefan, danke für deine Antwort. Das wars was ich wissen wollte. Aber mit deiner Antwort ist natürlich gleich eine weitere Frage aufgetaucht:

Zitat
Bulk ist natürlich deutlich schneller
Warum? Da sind doch auch nur 64 Bytes/Paket erlaubt. Oder kann man bei bulk-Transfer mehrere Pakete pro Frame pro Endpunkt schicken?

Zitat
Du schreibst etwas von "Dateianhang" in deinem Beitrag. Also hatte ich erwartet, dass es auch was zum Download gibt.
In meinem browser scheinen die Dateien am Ende meines Beitrags auf (sind auch schon einige male heruntergeladen worden). Sicherheitshalber noch einmal:

* usbOszilloskop_PIC_Copy.zip (100.11 KB - runtergeladen 168 Mal.)
* usbOszilloskop_Pc_Copy.zip (27.05 KB - runtergeladen 157 Mal.)
Gespeichert
Stampede
Globaler Moderator
Hero Member
*****
Offline Offline

Beiträge: 969



Profil anzeigen WWW
« Antworten #5 am: Februar 19, 2010, 09:03:00 »

Hi,

Zitat
Oder kann man bei bulk-Transfer mehrere Pakete pro Frame pro Endpunkt schicken?
Es können bis zu 19 Bulk Transfers pro Frame stattfinden, was in einer Datenrate von max. 1216 Byte/ms entspricht, also rund 1,2MB/s. Leider schafft das meines Wissens aber nur der PIC32, es gibt da ein Highspeed Custom Example in der Application Library. Mit dem 18F sollten aber paar hundert kb/s schon drin sein.

Gruß
Stefan
Gespeichert

qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #6 am: Februar 19, 2010, 22:48:09 »

vielen dank für die gute information. werde jetzt mit bulk experimentieren, qwer.
Gespeichert
vlad_2770
Newbie
*
Offline Offline

Beiträge: 2


Profil anzeigen
« Antworten #7 am: Februar 25, 2010, 16:37:16 »

Entschuldigen Sie mein Deutsch (ich benutze einen Übersetzer).

Ich möchte Sie bitten, mehr über dieses Projekt. Es ist erstaunlich!Ich erkenne ich, ob er als HID 18F2550. Nicht mich, wie ich apre ein HID-Gerät, sondern als ein Fragezeichen und immer noch.
Können Sie schreiben Sie bitte eine E-Mail an adrian_copou@yahoo.com.au? Sie fragen, e-mail Dinge.

Danke
Gespeichert
qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #8 am: Februar 26, 2010, 14:19:44 »

hello adrian,
i am sorry - i can't understand your question. your translator produces no meaningful word combinations. please repeat your question in english.
Maybe this answers your question:
the "usb-oszilloskop" is not a HID-device. it uses the microchip-custom-driver "mchpusb.sys", which is included in the project zip-file: "usbOszilloskop_Pc.zip"
Gespeichert
vlad_2770
Newbie
*
Offline Offline

Beiträge: 2


Profil anzeigen
« Antworten #9 am: Februar 27, 2010, 10:39:06 »

Finally, after I looked a bit through the source code, I saw it's not a HID application. But anyway, is a very good application! Congratulations!
It's almost what I need.Now I have to analyze a serial port protocol and for that I will use your PIC code unchanged (because it's a state of art what you done) and I would be needing to save all data sent by the PIC in a buffer for future observation on the PC. So I started to write an aplication how work just for one ADC channel (ADC 0 channel), but i don't understand very well what command need the PC to send to PIC for starting the data transfer form PIC to PC. Can you tell me please what is the value for PicByte? Also I appreciate if you tell me more about details of communication between PC and PIC. Until now I know that PC send a command to PIC:
send_buf[0]= 4;         //Flag für fast-output auf on setzen
send_buf[1]= PicByte  ;   //Bit0: einschalten, Bit1 = 0: ADC/Test-Modus
SendReceiveUSB(2, 0);
but like i said i don't know in my case what is the value for PicByte. And after this command the pic starts to send to PC the values of one ADC channel? Or should i still sending other commands to PIC?
It would be great if you can send me all its source code to make the debug to see all the values (PicByte and stuff) and how the PC aplication works.

Thank you very much
Gespeichert
qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #10 am: Februar 27, 2010, 15:47:47 »

Hello Adrian
(thank you for your friendly comment and excuse my bad English. I hope, my English is better than your German-translator.)
First, the principle of   communication:
The PIC operates in Usb- Interrupt-Transfer-Mode. In this mode, one device endpoint can deliver just 64 Bytes payload per millisecond. Therefor the PIC uses 4 IN-endpoints (endpoint2, 3, 4 and 5) to transmit data in a kind of ping-pong-buffering system: every other millisecond 128 Bytes over endpoints2&3 -  every other millisecond 128 Bytes over endpoints4&5. (= 256 Bytes in 2 milliseconds). Due to the sample rate of the Pic-ADC, the number of valid ADC-data is just 114 Bytes per millisecond  (not 128).
Apart from this, there is another “double-endpoint” ( IN-OUT-Endpoint) for transmission of commands from PC to PIC.
Each PIC-endpoint communicates with the PC over a pipe: 
myOutPipe + myInPipe is used for PC-commands (function „SendReceiveUSB(x,x)“) and
myInPipe2 , myInPipe3, myInPipe4 and myInPipe5 are used for fast ADC-Data-Transfer (function „UsbComm()“).
SendReceiveUSB(NumberOfBytesToSend, NumberOfBytesToReceive);
Before calling SendReceiveUSB you have to write a request code to send_buf[0] which is sent to the PIC-function: „ServiceRequests“.
Apart from this, there are some parameters you write to send_buf[1] (PicByte):
If you want to start the ADC-data-transfer, you do: send_buf[0] = 4; and send_buf[1] = 1; . this is the start signal for the PIC to send ADC-Data from channel_0.  (send_buf[1] = 3; //B'00000011' means: send a test-signal, send_buf[1] = 129; //B'10000001' means: send ADC-Data with timing-control-data (just for testing), send_buf[1] = 0 means: stop  ADC-data-transfer, the channel selection is not supported (always channel_0)).
Function “OnStart()”:
After writing the start signal to  send_buf[0] and send_buf[1], you call SendReceiveUSB(2, 0);.
Next, the pipes are opened and a TimerEvent is established, which calls a TimercallBack-routine every 2 ms. The TimerEvent has to be prepared (see function: “OnCreate(lpCrStr)”). (include WINMM.lib to your project (multimedia timer)).
Function “UsbComm()”:
This function is called by the TimerCallBack-routine every 2 ms. It reads the 4 pipe buffers -> 256 Bytes. (include usbDscLib.h and  usbDscLib.lib in your project (for communication with the driver)).
You should leave the function unchanged until (not including) the passage: “newTime.Quadpart = oldTime.....”.
The complicated construction with  SyncFlag is important for the starting phase. It corrects some synchronization errors at the beginning (about 5ms) of the “fast” usb traffic .
Behind that point, you can copy the 256 bytes from receive_buf to your buffer.

The important functions are:
OnCreate(), OpenDevice(), CloseDevice(), SendReceiveUSB(), OnStop(), OnStart(), TimerCallBack(), UsbComm().
The rest is just dialog-box-stuff.

* WINMM.LIB (42.95 KB - runtergeladen 131 Mal.)
* usbDscLib.lib (7.04 KB - runtergeladen 161 Mal.)
* usbDscLib.h (3.55 KB - runtergeladen 139 Mal.)
Gespeichert
vlad_2770
Newbie
*
Offline Offline

Beiträge: 2


Profil anzeigen
« Antworten #11 am: Februar 28, 2010, 11:56:03 »

Your English it’s very good. You described very well the application  and I understood each step, and I wrote some code in C#  (i don’t know VC++) but now I have a problem: I don’t know where  I make a mistake because I send the command for start the ADC-data-transfer and after that I start to reading the data but I receive noting. I don’t know if these functions work properly (_MPUSBRead, _MPUSBWrite, _MPUSBReadInt). I can’t debug these functions. I saw just _MPUSBOpen in debug and works. Please can you add in the PIC source some code for debug these functions?  Something likes that:
-if the PIC receive the correct command for start the ADC-data-transfer from PC one Led form PortB turn ON (PortB0 =ON)
-when the PIC start the transfer form PIC to PC on Led from PortB turn ON (PortB1=ON)

I saw that you have used #define mLED_0  PORTCbits.RC7 and mLED_0 = 0;//KontrollLED, but I don’t know why  it’s work inversely, but the PIC do their job.

Thank you very much!

Adrian

P.S. I wrote this message like this because I can’t login
Gespeichert
qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #12 am: Februar 28, 2010, 19:10:31 »

Hello Adrian,
you can use the LED  on RC7 (mLED_0) as control signal: when you power on the PIC, the LED should give light; when you connect the usb-plug, the LED should blink; when the start signal for ADC-data-transfer from PC arrives, the LED should give light without blinking. ((some comments in the source code of function “ServiceRequests” are wrong:
 ADCON0 = 0b00000000;   //select Channel-0, ADC-Module OFF
 INTCON = 0b00000000;   //clear GIEH: disable HighPriorityInterrupts  ))
An other possibility for debugging was, to display the error values of function “UsbComm”: Er, ErrorCount,  ErrorCount1,  ErrorCount2,  ErrorCount3.
An other thing:
The values in receive_buf[126] and receive_buf[254] are the data packet numbers (increased by 2);
The values in receive_buf[127] and receive_buf[255] are the number of valid ADC-Data (in most cases 114 or 115).
Use “receive_buf” without adding the offset-pointer “mpBuffPointer”. Just add the constant offsets: 0, 64, 128 and 192:
“if(! MPUSBReadInt(myInPipe2,receive_buf + 64 , ReceiveLength, &RecvLength,WaitRead))”
Maybe the problem is usbDscLib. This is a file I compiled  using microchips “_mpusbapi.cpp”. I prefer using this lib, over using microchips mpusbapi.dll, which is the common way. Other solutions are shown in: http://comvcon.blogspot.com/2008/11/using-mpusbapicpp-directly-instead-of.html.
All files you need are in MCHPFSUSB/Pc/Mpusbapi/Dll/....(Borland_C/Source). You can download the microchip-usb-directory at:
http://ww1.microchip.com/downloads/en/DeviceDoc/MCHPFSUSB_Setup.EXE
good luck.
Gespeichert
vlad_2770
Newbie
*
Offline Offline

Beiträge: 2


Profil anzeigen
« Antworten #13 am: März 08, 2010, 12:54:02 »

Sorry to bother you again, but I have a problem. Because I don't have a few files of your VC + + project, I could not do debug the VC++ code.
Instead, source files for PIC are all and I could do the project and add some code to see if my functions in C # (Visual C + + only understand the language) works well. My functions works well but i have a problem. If I send to PIC send_buf[0] = 4; and send_buf[1] = 1, my PIC is disconnected if i send them send_buf[0] = 5; and send_buf[1] = 1 or send_buf[0] = 4; and send_buf[1] = 2 everything is ok. If I use your PC software again everything is ok, so do not understand why  in my code if send_buf[0] = 4; and send_buf[1] = 1 don't work.

Please can you help me?
Gespeichert
qwer
Newbie
*
Offline Offline

Beiträge: 15


Profil anzeigen
« Antworten #14 am: März 09, 2010, 21:34:06 »

Hello Adrian,
i'm afraid i cant help you. In my C++ project, there are a lot of files included, which are part of the programming environment VisualC++. Its impossible to pick them out.
One of the most important thing is the timing. Try to check if your code reads 256 bytes every two milliseconds. If it reads less than that amount of bytes, you get an overflow of the usb-driver buffer, which can cause a shut down of the PIC. My code uses “mTimerTime” in UsbComm to show the time between “TimerCallBack”-calls. This must be 2 ms on average.
Gespeichert
Seiten: [1] Nach oben Drucken 
« vorheriges nächstes »
Gehe zu:  

Powered by MySQL Powered by PHP Made for Mozilla (Firefox) Made for Internet Explorer
Seite erstellt in 0.057 Sekunden mit 19 Zugriffen.
 
Top! Top!