Forum: Projekte & Code CDC für xmega


von Jürgen H. (woodym)


Angehängte Dateien:

Lesenswert?

Hallo,

nach einer langen Reise durch alle möglichen Foren und Code-Sammungen 
zum Thema CDC und Xmega habe ich feststellen müssen das es nichts gibt 
was man eben schnell einbindet.

Das Problem:
ein atxmega32a4u soll per USB angebunden werden.
ein atxmega128a4u soll per USB angebunden werden.

Da es sich hier um einen CNC-Controller handelt ist eine einfache 
serielle Übertragung das was einem zu erst einfällt. Na gut, wenn man 
einen CNC-Controller baut, dann sollte es doch das einfachste sein einen 
FTDI mit auf die Platine zu klatschen (Das nur um denen zuvorzukommen 
die diese Idee als das gelbe vom ei betrachten!). Alle Lösungen mit FTDI 
und PL23xx usw (egal ob on Board oder als usb to Serial-Adapter) haben 
nur einen großen Nachteil: ein Timeout.
Viel werden davon noch nicht viel gehört haben, wer per USB die Daten zu 
einem CNC-Controller überträgt und dort oft Wartezeiten (der Fräser ist 
nun mal etwas langsamer als die Übertragung; zum Glück) hat, wir 
feststellen das oft Daten verworfen werden. Und das nicht reproduzierbar 
an unterschiedlichen stellen. Man kann das ganze nachvollziehen wenn man 
seriell große mengen an Daten überträgt und die Statusleitungen einfach 
einmal längere zeit setzt; plumps werden in einigen Sekunden 200 Bytes 
an Daten übertragen (sagt die sende-Software) die nie ankommen.

Nimmt man eine Hardware-Serielle, wie früher üblich, gibt es dieses 
Problem nicht. Aber welche PC's haben heute schon eine solche 
Schnittstelle mit eingebaut? Die Anzahl der Möglichen PC's sinkt 
täglich.

Aber da gab es doch was? Ja, seit XP (dort anfangs mit zusätzlichen 
Treibern) kann man doch ein USB-CDC-Device ohne Treiber auf der 
Windows-Seite anbinden! Diese Devices lassen auch eine nahezu beliebige 
Verzögerung des Datenstroms zu.

Als erstes denkt man nun an eine Codesammung die es dafür doch geben 
sollte. Ja, ASF, von Atmel selber bereitgestellt sollte das doch können.

Das erste Problem ist ASF selber... Ich habe seit Wochen an meinem Code 
mit WINAVR gearbeitet. Der in der Zwischenzeit doch stattliche Code soll 
nun in das AVR-Studio übertragen werden nur weil ich nicht mal 4k Code 
für USB brauche? Das zweite Problem das ich den CDC-Code noch nicht 
einmal übersetzen konnte (der Prozessor zu neu?) weil einige 
Deklarationen nicht stimmen oder mit dem Code von ASF selber 
Kollidieren.
ASF ist damit für mich ausgeschieden.

LUFA!!! Das ist es!! (so dachte ich)
Nein, Lufa ist so universell das es vermutlich neben USB auch noch 
Toastbrot backen kann (hierfür habe ich aber noch nicht die 
entsprechenden Einstellungen in LUFA gefunden.. aber wer weiß???)
Lufa hat sich als so unübersichtlich erwiesen (und zu dem für Xmega nur 
als experimentell) das es nicht verwendbar war.

Aber da gab es doch noch jemanden der den LUFA-Code so 
zusammengestrichen hat das er wirklich 'übersetzbar' ist. Leider ist 
dieser Code von nonolithlabs.com nicht als CDC ausgelegt. Hier werden 
Blöcke in PIPES übertragen die einen Code auf der Host-Seite erfordern. 
Unter Linux noxh leicht machbar, unter Windows schon eine größere 
Aktion. Zusätzlich wird dann noch die entsprechende Hostsoftware 
benötigt die diese PIPES auch behandelt.

Ich will doch nur seriell Daten übertragen! (jammer)

Also habe ich mich hingesetzt und versucht eine CDC-Software zu 
schreiben.
Winavr ist nun nicht zwingend auf dem neusten Stand (dennoch liebe ich 
den pn davon).
Also erst mal den gcc aktualisiert (hierfür gibt es genügend zum 
Download im Internet). Leider hat es sich gezeigt das diese GCC wohl 
neuer sind, aber dennoch nicht die neuen Prozessoren unterstützen.
Ich habe dann den Toolchain von ATMEL genommen und diesen über den 
Winavr drüberkopiert (vorher Sicherungen machen!!).
Ab diesem Zeitpunkt waren auch die ganzen Deklarationen für die neuen 
Prozessoren da (sind die wirklich noch als neu zu bezeichnen?). Man 
sollte auch nicht vergessen entsprechende Anpassungen für avrdude zu 
machen (Anpassungen in der config-Datei) damit mit avrdude auch 
programmiert werden kann.
Nach jetzt fast 3 Wochen ist nun die angehängte Codesammlung 
rausgekommen.

Als VID und PID habe ich die von Atmel genommen die in deren Beispielen 
verwendet wurden. Diese sind nur als Beispiel zu sehen! Jeder sollte 
sich um gültige um frei verwendbare PID/VID's selber kümmern. Die 
entsprechende .inf-Datei ist im Zip mit drin und wurde für die PID und 
VID aus dem Beispiel angepasst.

In der main.c erfolgt die Initialisierung und dann kann der Code schon 
verwendet werden. Ich habe auf einen Ringbuffer verzichtet weil es wohl 
ehr selten ist das man wirklich damit auf eine Serielle Leitung dann 
schreiben will. Man wird die seriellen Daten selber Interpretieren und 
eigenen Funktionen dahinter legen.
1
while(1)
2
{
3
  // manage usb-task for the endpoints (in loop)
4
  EP_DEF_in(ep_out);
5
  EP_DEF_out(ep_in);
6
  EP_DEF_out(ep_note);
7
8
  // handle data if flag are cleared
9
  if(!cdc_rxb.flag)
10
  {
11
    if(cdc_rxb.bytes==cdc_rxb.len)
12
    {
13
      // all data prcessed; send the data in txd and wait for new data
14
      // only if out-data are processed
15
      if(!cdc_txb.flag)
16
      {
17
        cdc_txb.len=cdc_txb.bytes;
18
        cdc_txb.flag=1;
19
        cdc_rxb.flag=1;
20
        // blink led for receiving data
21
        // led_blink_flag|=LED_BLINK_RED;
22
      }
23
    }
24
    else
25
    {
26
      // process data
27
      res=cdc_rxb.data[cdc_rxb.bytes];
28
      cdc_txb.data[cdc_txb.bytes]=res;
29
      // process here the data (res)
30
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
31
      cdc_rxb.bytes+=1;
32
      cdc_txb.bytes+=1;
33
    }
34
  }
35
}

Über einfache Flags wird gesteuert ob ein Buffer gefüllt werden soll 
oder ob ein Buffer gefüllt ist damit er dann von der USB-Software 
verarbeitet werden kann. Im USB-Code selber sind für die Endpoints 
eigene Buffer verwendet. Das befreit den einfachen Nutzer von einem 
ausgeklügelten Bufferhandling (Buffer müssen gesperrt werden, 
Übertragungen darin stattfinden dann zugeordnet werden... eine schwer 
nazuvollziehende Pointer-schlacht). Da die Inhalte immer umkopiert 
werden, ist die Übertragungsrate etwas langsamer.
Die USB-Tasks für die Endpoints (EP_DEF_in(ep_out)) müssen in der 
Mainloop immer wieder aufgerufen werden. Ich hatte diese zu beginn im 
IRQ mit drin (jetzt ist da nur noch der Task für den EP0 drin) was 
traumhafte datenraten ergeben hatte. Das locking war aber ungleich 
komplexer. Und vorallem das 'Anschupsen' wenn kein IRQ ausgelöst wird 
(alle Daten sind reingekommen, ich möchte nur was schicken) muss dann in 
die IRQ'S mit eingreifen.

Zu den Datenraten:
Wird keine Byteweise Verarbeitung gemacht und ich empfange nur Daten, 
komme ich auf 450kByte übertragungsrate.
Werden die Daten auch noch seriell umkopiert (hier in den ausgabebuffer) 
und nicht zurück gesendet, dann komme ich noch auf ca. 95kBytes an 
datenrate. Werden die Daten als echo gesendet, kann ich das nicht mehr 
sagen weil mein Übertragungsprogramm mit der Darstellung der 
zurückkommenden Daten so ausgelastet ist das es die Datenrate nicht mehr 
darstellt ;-) (es dürften aber immer noch im hunderterkillobit-bereich 
sein).

ich hoffe der Code hilft jemanden. vielleicht ist das auch die Basis 
kleine weitere USB-Devices damit zu realisieren. HDI oder eine 
Massenspeicher sollten damit nicht mehr all zu schwer sein.

bye woodym

von Jürgen H. (woodym)


Angehängte Dateien:

Lesenswert?

Hallo,
so schnell kann es gehen...
hier gleich eine aktuallisierte Version. In der vorigen Version war ein 
Quarz mit 8Mhz beim Clock eingestellt. Wer das Verwenden will wir nicht 
zwingend einen Quarz mit der gleichen Frequenz haben. In der Version 
hier wurden die Normalen Clockeinstellungen genommen die beim USB üblich 
sind.
Der 32Mhz wird auf 48Mhz eingestellt und mit dem USB Start of Frame über 
den DFFL synchronosiert. Die internen 2Mhz werden per PLL mal 16 
genommen und stehen als SystemClock zur Verfügung.

Noch zum Datendurchsatz mit Echo.
1.5Mbyte werden in ca 34 Sekunden übertragen (ist auch abhängig von der 
Art der Daten; wie oft muß die Ausgabe-Software scollen). Das macht ca. 
44KBytes pro Sekunde.

bye woodym

von Simon K. (simon) Benutzerseite


Lesenswert?

Also LUFA lief bei mir praktisch auf Anhieb mit CDC. Auch wenn der XMEGA 
Support noch Experimental ist, habe ich bisher keinerlei Probleme 
festgestellt.

von Jürgen H. (woodym)


Lesenswert?

hallo Simon,

ja, es gab/gibt wirklich welche die damit keine Probleme haben. Das du 
diese enttäuschende Erfahrung nicht machen musstest freut mich. Mir 
konnte keiner darüber Auskunft geben unter welchen Voraussetzungen es 
gehen soll (z.b. welche includes die richtigen sind, welcher GCC dafür 
nötig ist). Genau genommen konnte ich LUFA für jeden Prozessor 
übersetzen, nur nicht für den XMEGA128a4u. Als ich dann versuchte die 
Definitionen für die Boards/Prozessoren entsprechend anzupassen, ging 
auf Grund der unüberschaubaren Abhängigkeiten die Welt unter. Das mag 
für jemanden der das mit entwickelt hat (ich weiß, dazu gehörst du 
nicht) alles völlig easy sein. Für mich waren/sind das nicht 
nachvollziehbare Prozesse gewesen. Zumal es ja wirklich nicht viel ist 
wenn die Config bei usb mal durchlaufen ist. Dann sind ja 'nur' noch die 
Endpoints die richtig beschrieben werden müssen.

Ach so, zum Anfang noch 'ja, es gab/gibt wirklich welche' ...
das ich nicht der einzige war/bin, sieht man an den Beiträgen in 
AVR-Freaks usw..

Das auch viele andere das nicht so hinbekommen haben mit dem CDC sehe 
ich an einigen Projekten die dann Zähneknirschend mit dem code von 
nonolithlabs.com umgesetzt wurden (ist halt kein CDC).

Ich habe viel über USB gelernt durch das selber schreiben. Natürlich 
habe ich auch auf die Erfahrungen von LUFA und ASF zurückgegriffen. Die 
Ganzen Descriptor-Sachen sind von LUFA.

bye woodym

von TR (Gast)


Lesenswert?

Dank dir, funktioniert auch bei mir mit nem XMega32A4U...(welch 
Überaschung!!! ;))

Hatte vorher zwar das "volle" Lufa erfolgreich am laufen, aber deine 
Version gefällt mir besser.

Für Windows 8 muss folgendes für die *.inf Datei beachtet werden:

http://www.nextofwindows.com/how-to-install-an-un-signed-3rd-party-driver-in-windows-8/

von Xmega USB-Anfänger (Gast)


Lesenswert?

Hallo,

ich hab mal dein Zip-Datei ins AVR-Studio in einem eigenen Projekt 
versucht zu kompilieren. Da ich will so wenig wie möglich ändern und 
mich halt langsam an das Thema ranzutasten.
Jetzt gibt mir der Compiler einen Fehler aus "F_USB undecleared" in der 
Datei usb_xm.c Zeile 272.

Wo definierst du die Variable??

Vielen Dank dass du dir die Mühe gemacht hast. hat mir schon ein bischen 
weitergeholefen.

von Basti (Gast)


Lesenswert?

Die one Click Lösung ist doch immernoch ASF CDC... noch nie Probleme 
gehabt, egal welcher Prozessortyp und Source in Atmel Studio einbinden 
sollte nicht wirklich schwer sein, egal wieviel... oder du hast ganz wo 
anders Probleme...

Grüße Basti

von woodym (Gast)


Lesenswert?

hallo 'xmega usb anfänger',
die F_CPU ist mit der F_USB in der makefile definiert. Wenn du eine 
eigene makefile verwendest, mußt du darauf achten das diese Variablen 
auch beim übersetzen mit übergeben werden.

@Basti
Ich freue mich für dich das du mit ASF zufrieden bist. Offensichtlich 
hast du eine speziell für dich gemachte version (die aktuelle Version 
die ich runtergeladen hatte, hat Prozessoren nicht unterstützt oder 
einfach nicht fehlerfrei übersetzt.... mit neu installiertem und 
aktuellem Studio), oder du verwendest nicht die Xmega-Prozessoren die im 
ASF Buggi sind. Der hier vorgestellte Weg ist sicher auch nicht 100%ig. 
Er gibt einem aber erst mal ein erfolgserlebnis welches einem mut zum 
weitermachen gibt. Für mich war es einfacher den USB-Teil des Xmegas neu 
zu schreiben als mich durch 100 erratas und asf-Bugfixings zu lesen in 
der hoffnung ein übersetzbares ergebnis zu erhalten. Ich sprech noch 
nicht mal davon das das dann auch noch funktionieren soll.
Ich freu mich wirklich für dich das es mit ASF funktioniert. Alle 
anderen die die gleiche Erfahrung wie ich gemacht haben, haben hier die 
möglichkeit eine Alternative zu verwenden.

bye woodym

von woodym (Gast)


Lesenswert?

@ Xmega USB-Anfänger
Ich hab dir nochmal die zeilen rausgesucht im Makefil:
1
# Processor frequency.
2
#     This will define a symbol, F_CPU, in all source code files equal to the 
3
#     processor frequency. You can then use this symbol in your source code to 
4
#     calculate timings. Do NOT tack on a 'UL' at the end, this will be done
5
#     automatically to create a 32-bit value in your source code.
6
#     Typical values are:
7
#         F_CPU =  1000000
8
#         F_CPU =  1843200
9
#         F_CPU =  2000000
10
#         F_CPU =  3686400
11
#         F_CPU =  4000000
12
#         F_CPU =  7372800
13
#         F_CPU =  8000000
14
#         F_CPU = 11059200
15
#         F_CPU = 14745600
16
#         F_CPU = 16000000
17
#         F_CPU = 18432000
18
#         F_CPU = 20000000
19
#F_CPU = 16000000
20
F_CPU = 32000000
21
#F_CPU = 8000000
22
23
# USB controller master clock frequency.
24
#     This will define a symbol, F_USB, in all source code files equal to the
25
#     input clock frequency of the USB controller's clock generator in Hz.
26
#
27
#     For the XMEGA chips, this should be equal to a multiple of 6MHz for Low
28
#     Speed USB mode, or a multiple of 48MHz for Full Speed USB mode.
29
F_USB = 48000000

und was dann wichtig ist im makefile (in deinem Makefile sind bereits 
einträge da z.B. die F_CPU)
1
# Place -D or -U options here
2
CDEFS = -DF_CPU=$(F_CPU)UL 
3
CDEFS += -DF_USB=$(F_USB)UL
4
CDEFS += -DBOARD=BOARD_$(BOARD) -DARCH=ARCH_$(ARCH)

bye woodym

von Puck (Gast)


Lesenswert?

Schaut lieber ins Datenblatt und machts gleich in ASM. Spart viel C+ASF 
Studiererei. Den Vorteil den die ASF bringen soll wird von einem 
Mehrfachen an Doku und Bürokratie locker zunichte gemacht.

von Frank91 (Gast)


Lesenswert?

wie installiere ich den die inf datei?
Mein Pc kennt keinen passenden Treiber.
Wähle ich die Inf datei welche in der Zip Datei enthalten ist aus, kommt 
nur die Meldung, dass das "CDC Virtual Com konnte nicht installiert 
werden"

von woodym (Gast)


Lesenswert?

hallo frank91,

Voraussetzung ist, das der Xmega bereits programmiert ist.
Wenn du den Xmega an die usb ansteckst, meckert dein Windows das es 
dafür keinen Treiber hat und trägt nach deinem Treiber. Da gibst du ihm 
an wo diese inf-datei ist.
Wie das bei Win8 aussieht kann ich nicht sagen.
Bei XP muss vorher der cdc-Treiber von Microsoft installiert sein der 
eventuell Bestandteil von irgend einem ServicePack ist.

bye woodym

von Frank91 (Gast)


Lesenswert?

genau so wie du es beschrieben hast hab ich es gemacht :P
aber die Meldung kommt trotzdem immer.
hab Windows 7 64bit.

von Felix H. (masterq)


Lesenswert?

Hallo,

erst mal vielen Dank für deinen Code, ich finde ihn sehr nützlich und 
habe seit längeren etwas ähnliches vor, doch nie geschafft.
Läuft auch auf einen xmega192a3u
Ich habe jedoch leider noch nicht ganz verstanden wie ich ein EP->Host 
Transfer initialisiere.

Nur als Beispiel ich habe einen kleinen UART RX Interrupt der folgenden 
Code ausführt wobei c das Eingegangene Byte ist:
1
  if(c == '\n')
2
  {
3
    cdc_txb.data[cdc_txb.bytes] = '\n';
4
    cdc_txb.data[cdc_txb.bytes + 1] = '\r';
5
    cdc_txb.bytes += 2;
6
    return;
7
  }
8
  cdc_txb.data[cdc_txb.bytes] = '1';
9
  cdc_txb.data[cdc_txb.bytes + 1]= c;
10
  cdc_txb.bytes+=2;

Ich hätte erwartet das in irgendeinen Interrupt oder innerhalb der main 
loop, jetzt erkannt wird das sich der Puffer verändert hat und die Bytes 
werden gesendet.
Das passiert gar nicht oder nach einer Code Modifikation von mir die 
aber wahrscheinlich nicht sehr gescheit ist manchmal.
Das gleiche gilt auch wenn ich immer nur ein Byte fülle anstatt gleich 
2.
2 Bytes sende ich nur um sicher zu gehen das die Zeichen nicht durch 
meinen uart verschluckt werden.

Das ursprüngliche Echo Programm von dir funktioniert immer einwandfrei.

Beste Grüße

Felix

: Bearbeitet durch User
von Felix H. (masterq)


Lesenswert?

Ok, vielleicht habe ich meine Frage schon selber beantwortet.
Das direkt im IRQ in den Buffer zu schreiben scheint keine Gute Idee zu 
sein. Weil die Reihenfolge der Aufrufe wichtig ist.
Ich habe nochmal deine main modifiziert.
So funktioniert es sehr schön, man könnte ebenfalls noch einen Buffer 
anlegen in den man mehrere Bytes schreiben könnte. Welche dann in der 
Main kopiert werden. Das kein Problem sein.

Wenn du magst könntest du mir noch ein wenig auf die Sprünge helfen.
Was müsste ich tun um die Bytes direkt im IRQ in den Buffer zu 
schreiben?

Und sieht meine main für dich irgendwie Problematisch aus:
1
  while(1)
2
  {
3
4
    // manage usb-task for the endpoints (in loop)
5
    EP_DEF_in(ep_out);
6
    EP_DEF_out(ep_in);
7
    EP_DEF_out(ep_note);
8
9
    // handle data if flag are cleared
10
11
    if(is_new) //Nimmt ein neues Byte (Vom uart gespeichert) entgegen.
12
    {
13
      cdc_txb.data[cdc_txb.bytes] = new_char;
14
      cdc_txb.data[cdc_txb.bytes + 1] = '1'; //Nur zum testen
15
      cdc_txb.bytes+=2;
16
      
17
      is_new = false;
18
    }
19
20
    if(cdc_rxb.bytes==cdc_rxb.len)
21
    {
22
      
23
      if(!cdc_txb.flag && cdc_txb.len != cdc_txb.bytes)
24
      {
25
            PORTC.OUTTGL = PIN0_bm;
26
        cdc_txb.len=cdc_txb.bytes;
27
        cdc_txb.flag=1;
28
        cdc_rxb.flag=1;
29
        // blink led for receiving data
30
        // led_blink_flag|=LED_BLINK_RED;
31
      }
32
    }
33
    else if(!cdc_rxb.flag)
34
    {
35
      //Wenn bytes=len liegt nichts an
36
      //Sonst müssen wir schauen ob wir fertig sind
37
      
38
      //Wenn nein holen wir ein byte aus dem puffer
39
      res=cdc_rxb.data[cdc_rxb.bytes];
40
      cdc_rxb.bytes+=1;
41
      //Und machen nichts damit :)
42
    }
43
44
  }

Besten Dank

Felix

von woodym (Gast)


Lesenswert?

hallo Felix,

das ist jetzt schon ein bischen her das ich das gemacht hatte. Wenn ich 
deinen Code richtig verstehe, bekommst du per IRQ ein zeichen und setzt 
dir dann ein flag (is_new). Dann schnappst du dir das Zeichen und 
schiebst es in den tx-buffer für den USB.

Das wird nur bedingt funktionieren. Wenn die Daten schneller von der 
Seriellen kommen als sie vom USB verarbeitet werden können, dann 
überschreibst du dir dein Zeichen. Das abarbeiten in EP_DEF_out kann u.U 
schon etwas brauchen.. zumindest bist die Flags wieder auf 'ich bin 
Frei' sitzen.

Ich habe dort einen einfachen Ringbuffer für die Serielle verwendet und 
schmeiße alle Daten in einem Block raus. Der Aufwand für die USB ist für 
ein Zeichen fast genauso groß wie für z.B. 20 Zeichen.

als Ringbuffer habe ich das verwendet:

ring.c
1
#include <stdlib.h>
2
#include <string.h>
3
#include <avr/pgmspace.h>
4
#include <avr/interrupt.h>  // AVR specific functions to access the program memory
5
6
#include "ring.h"
7
8
/*
9
 *  module global variables
10
 */
11
12
13
void ring_flush(Ring_buffer *ring) 
14
{
15
  ring->head=0;
16
  ring->tail=0;
17
  ring->bytes = 0;
18
}
19
20
unsigned int ring_pending(Ring_buffer *ring) 
21
{
22
  return ring->bytes;
23
}
24
25
unsigned int ring_free(Ring_buffer *ring) 
26
{
27
  return ring->len-ring->bytes;
28
}
29
30
unsigned char ring_lowmark(Ring_buffer *ring) 
31
{
32
  if(ring->bytes<=ring->low)
33
    return 1;
34
  return 0;
35
}
36
37
unsigned char ring_heighmark(Ring_buffer *ring) 
38
{
39
  if(ring->bytes>=ring->heigh)
40
    return 1;
41
  return 0;
42
}
43
44
unsigned int ring_get(Ring_buffer *ring)
45
{
46
  unsigned int tmptail;
47
  unsigned char data;
48
49
  tmptail=ring->tail;
50
51
  if ( ring->head == tmptail ) 
52
  {
53
    ring->bytes=0;
54
    return RING_NO_DATA;   /* no data available */
55
  }
56
  
57
  /* calculate /store buffer index */
58
  tmptail++;
59
60
  if(tmptail>=ring->len) tmptail=0;
61
62
  /* get data from receive buffer */
63
  data = ring->buf[tmptail];
64
65
  ring->tail=tmptail;
66
  ring->bytes--;
67
68
  if(ring->Under_Lowmark)
69
  {
70
    if(ring->bytes==ring->low)
71
    {
72
      ring->Under_Lowmark();
73
    }
74
  }
75
  return data;
76
}
77
78
unsigned char ring_put(Ring_buffer *ring,char data)
79
{
80
  unsigned int tmphead;
81
82
  tmphead  = ring->head + 1;
83
  if(tmphead>=ring->len)
84
  {
85
    tmphead=0;
86
  }
87
  
88
  if( tmphead != ring->tail )
89
  {
90
    ring->bytes++;
91
    ring->buf[tmphead] = data;
92
    ring->head = tmphead;
93
    if(ring->Over_Highmark)
94
    {
95
      if(ring->bytes==ring->heigh)
96
      {
97
        ring->Over_Highmark();
98
      }
99
    }
100
    return 0;
101
  }
102
  ring->bytes=ring->len;
103
  return 1;
104
}
105
106
107
void ring_init(Ring_buffer *ring, unsigned int len)
108
{
109
  unsigned int a;
110
111
  ring->len = len;
112
  ring->bytes = 0;
113
  ring->head = 0;
114
  ring->tail = 0;
115
116
  a=len/10;
117
  ring->low = len/2;
118
119
  if(a<4) a=4;
120
  ring->heigh = len-a;
121
122
  ring->Under_Lowmark=NULL;
123
  ring->Over_Highmark=NULL;
124
}

ring.h
1
#ifndef _ring_H
2
#define _ring_H
3
4
#include "avr_utils.h"
5
6
typedef void (*RingCallback)(void);      // pointer to Callback-Function
7
8
typedef struct ring_buffer 
9
{ 
10
  unsigned int len;
11
  unsigned int head;
12
  unsigned int tail;
13
  unsigned int low;
14
  unsigned int heigh;
15
  unsigned int bytes;
16
17
  unsigned char *buf;
18
19
  RingCallback Under_Lowmark;
20
  RingCallback Over_Highmark;
21
22
} Ring_buffer; 
23
24
#define RING_NO_DATA      0x0100      // no receive data available
25
#define RING_BUFFER_FULL    0x0200      // Buffer full
26
27
28
/*
29
** function prototypes
30
*/
31
32
void ring_flush(Ring_buffer *ring);
33
unsigned int ring_free(Ring_buffer *ring);
34
unsigned int ring_pending(Ring_buffer *ring);
35
unsigned char ring_lowmark(Ring_buffer *ring);
36
unsigned char ring_heighmark(Ring_buffer *ring);
37
38
39
unsigned int ring_get(Ring_buffer *ring);
40
unsigned char ring_put(Ring_buffer *ring,char data);
41
42
void ring_init(Ring_buffer *ring, unsigned int len);
43
44
#endif
45
// end of file

Der Ringbuffer hat auch eine Signalisierung für die Buffer damit man 
RTS/CTS setzen kann. Der Ringbuffer läßt sich optimieren durch 8 bit 
Pointer. Da ich hier aber Buffer verwende die größer als 255 Bytes sind 
habe ich diese Verriante genommen.


ich hoffe das hilft dir.
bye woodym

von woodym (Gast)


Lesenswert?

hallo,

hier noch die rs232-funktionen die ich verwende.

rs232.c
1
#include <stdlib.h>
2
#include <string.h>
3
#include <avr/pgmspace.h>
4
#include <avr/interrupt.h>  // AVR specific functions to access the program memory
5
6
#include "rs232.h"
7
8
/*
9
 *  module global variables
10
 */
11
12
static unsigned char rs232_TxBuf[rs232_TX_BLEN];
13
static unsigned char rs232_RxBuf[rs232_RX_BLEN];
14
15
Ring_buffer rs232_Rxr;
16
Ring_buffer rs232_Txr;
17
18
static volatile unsigned int rs232_LastRxError;
19
20
char rs232_null=0;
21
22
#ifdef rs232_OUTFUNCTIONS
23
char rs232_rsza[20];
24
#endif
25
26
unsigned char rs232_setspeed(unsigned long baud) 
27
{
28
  unsigned long div1k;
29
  unsigned char bscale = 0;
30
  unsigned int bsel;
31
32
33
  if (baud > (F_CPU/32)) return 0;
34
35
  div1k = ((F_CPU * 64UL) / baud) - 1024;
36
  while ((div1k < 2096640UL) && (bscale < 7)) 
37
  {
38
    bscale++;
39
    div1k <<= 1;
40
  }
41
42
  bsel = div1k >> 10;
43
44
  rs232_UART.BAUDCTRLA = bsel&0xff;
45
  rs232_UART.BAUDCTRLB = (bsel>>8) | ((16-bscale) << 4);
46
47
  return 1;
48
}
49
50
51
void rs232_cts_on(void)
52
{
53
#ifdef rs232_CTS_PIN
54
  // we have enough buffer -> signal to other party to start sending again
55
  CLR(rs232_CTS_PIN); // enable remote-transmitt
56
#endif
57
}
58
59
void rs232_cts_off(void)
60
{
61
#ifdef rs232_CTS_PIN
62
  // we have buffer full -> signal to other party to stop sending again
63
  SET(rs232_CTS_PIN); // disable remote-transmitt
64
#endif
65
}
66
67
void rs232_init(void)
68
{
69
70
#ifdef rs232_LED_PIN
71
  CLR(rs232_LED_PIN);
72
  OUTPUT(rs232_LED_PIN);
73
#endif
74
75
  SET(rs232_TX_PIN);
76
  OUTPUT(rs232_TX_PIN);
77
  INPUT(rs232_RX_PIN);
78
79
#ifdef rs232_CTS_PIN
80
  CLR(rs232_CTS_PIN);
81
  OUTPUT(rs232_CTS_PIN);
82
#endif
83
84
#ifdef rs232_RTS_PIN
85
  INPUT(rs232_RTS_PIN);
86
87
  //Interrupt Level an PORT festlegen
88
  PORT_N(rs232_RTS_PIN).INTCTRL |= PORT_INT0LVL_MED_gc;
89
90
  //Interrupt auf beide Flanke einstellen
91
  CTL(rs232_RTS_PIN) = PORT_OPC_TOTEM_gc| PORT_ISC_BOTHEDGES_gc;
92
93
  //Interrupt an PORT PIN freigeben
94
  PORT_N(rs232_RTS_PIN).INT0MASK |= 1<<BIT(rs232_RTS_PIN);
95
96
#endif
97
98
  rs232_setspeed(rs232_BAUD); 
99
100
  rs232_Rxr.buf=rs232_RxBuf;
101
  rs232_Txr.buf=rs232_TxBuf;
102
  ring_init(&rs232_Txr, rs232_TX_BLEN);
103
  ring_init(&rs232_Rxr, rs232_RX_BLEN);
104
105
#ifdef rs232_CTS_PIN
106
  rs232_Rxr.Under_Lowmark=rs232_cts_on;
107
  rs232_Rxr.Over_Highmark=rs232_cts_off;
108
#endif
109
110
#ifdef rs232_RX_LOWMARK
111
  rs232_Rxr.low=rs232_RX_LOWMARK;
112
#endif
113
114
#ifdef rs232_RX_HIGHMARK
115
  rs232_Rxr.heigh=rs232_RX_HIGHMARK;
116
#endif
117
118
  rs232_UART.CTRLA = rs232_IRX_LV; // USART_TXCINTLVL_MED_gc 
119
  rs232_UART.CTRLC = USART_CMODE_ASYNCHRONOUS_gc|USART_CHSIZE_8BIT_gc|USART_PMODE_DISABLED_gc ; // 8bit 
120
  rs232_UART.CTRLB = USART_TXEN_bm | USART_RXEN_bm; // enable tx and rx on USART
121
}
122
123
unsigned int rs232_getc(void)
124
{
125
  unsigned int a;
126
127
  cli();
128
129
  rs232_UART.CTRLA |= rs232_IRX_LV; // USART_RXCINTLVL_MED_gc 
130
131
  a=ring_get(&rs232_Rxr) | (rs232_LastRxError);
132
  rs232_LastRxError=0;
133
  sei();
134
  return a;
135
}
136
137
char rs232_putc(unsigned char data)
138
{
139
  unsigned char st;
140
  cli();
141
  
142
  st=ring_put(&rs232_Txr,data);
143
144
#ifdef rs232_RTS_PIN
145
  if( GET(rs232_RTS_PIN) )
146
  {
147
    /* rts ; disable transmit data */
148
    rs232_UART.CTRLA &= ~(rs232_IDRE_LV);
149
  }
150
  else
151
#endif
152
  {
153
    rs232_UART.CTRLA |= rs232_IDRE_LV; // USART_DREINTLVL_MED_gc 
154
  }
155
  sei();
156
  return st;
157
}
158
159
ISR( rs232_VEC_DRE(rs232_PORT,rs232_PORT_NUM) )
160
{
161
  if(rs232_Txr.bytes)
162
  {
163
    rs232_UART.DATA=ring_get(&rs232_Txr);
164
  }
165
  else
166
  {
167
    /* tx buffer empty, disable UDRE interrupt */
168
    rs232_UART.CTRLA = rs232_IRX_LV|rs232_ITXC_LV;
169
  }
170
}                                                                       // end ISR
171
172
#ifdef rs232_RTS_PIN
173
ISR( PORT0_VECT(rs232_RTS_PIN) )
174
{
175
  if( GET(rs232_RTS_PIN) )
176
  {
177
    /* rts ; disable transmit data */
178
    rs232_UART.CTRLA &= ~(rs232_IDRE_LV);
179
  }
180
  else
181
  {
182
    rs232_UART.CTRLA |= rs232_IDRE_LV; // USART_DREINTLVL_MED_gc 
183
  }
184
}                                                                       // end ISR
185
#endif
186
187
ISR( rs232_VEC_TXC(rs232_PORT,rs232_PORT_NUM) )
188
{
189
  // last irq after send data
190
  rs232_UART.CTRLA = rs232_IRX_LV;
191
}                                                                       // end ISR
192
193
194
ISR( rs232_VEC_RX(rs232_PORT,rs232_PORT_NUM) )
195
{
196
  unsigned int lastRxError;
197
 
198
  /* read UART status register and UART data register */ 
199
  lastRxError= (rs232_UART.STATUS & 0x1C)<<8;
200
    
201
  if(ring_put(&rs232_Rxr,rs232_UART.DATA))
202
  {
203
    /* error: receive buffer overflow */
204
    lastRxError = rs232_BUFFER_OVERFLOW ;
205
  }
206
207
#ifdef rs232_LED_PIN
208
  TGL(rs232_LED_PIN);
209
#endif
210
  rs232_LastRxError = lastRxError;   
211
}                                                                       // end ISR
212
213
#ifdef rs232_OUTFUNCTIONS
214
void rs232_za(unsigned char p, long int *z)
215
{
216
  rs232_rsza[p] = (*z % 10)+48;
217
  *z = *z/10;
218
}
219
220
void rs232_val(unsigned char len) // Einen Nulterimierten String ausgeben
221
{  
222
  unsigned char counter = 0;
223
  unsigned char il;
224
  
225
  if(!rs232_null)                    // Führende Nullen entfernen / lassen
226
  {
227
    while(rs232_rsza[counter] == '0'){counter++;}
228
    // wenn der wert 0 ist, eine 0 ausgeben
229
    if(rs232_rsza[counter]==0) counter --;
230
  }
231
  rs232_puts(&rs232_rsza[counter]);
232
  il=strlen(&rs232_rsza[counter]);
233
  while(il<=len)
234
  {
235
    rs232_putc(' ');
236
    il++;
237
  }
238
}
239
240
void rs232_sint_len(int z,unsigned char len)// Routine zum ausgeben von Zahlen
241
{ 
242
  long int xv;
243
  xv=ABS(z);
244
245
  rs232_rsza[6] = 0;
246
  rs232_za(5, &xv);
247
  rs232_za(4, &xv);
248
  rs232_za(3, &xv);
249
  rs232_za(2, &xv);
250
  rs232_za(1, &xv);
251
  rs232_rsza[0] = (xv)+48;
252
  if(z<0)
253
  {
254
    rs232_putc('-');
255
    if(len) len--;
256
  }
257
  rs232_val(len);
258
}
259
260
void rs232_sint(int z) // Routine zum ausgeben von Zahlen
261
{
262
  rs232_sint_len(z,0);
263
}
264
#endif
265
266
#ifdef rs232_OUTSTRING
267
void rs232_puts(const char *s )
268
{
269
  while (*s)
270
  {
271
    // wait for free space in buffer
272
    while(rs232_putc(*s))
273
    {
274
      ;
275
    }
276
    s++;
277
  }
278
}
279
280
281
void rs232_puts_p(const char *progmem_s )
282
{
283
  register char c;
284
  
285
  while ( (c = pgm_read_byte(progmem_s++)) )
286
  {
287
    // wait for free space in buffer
288
    while(rs232_putc(c))
289
    {
290
      ;
291
    }
292
  }
293
294
}
295
#endif

rs232.h
1
#ifndef _rs232_H
2
#define _rs232_H
3
4
#include "avr_utils.h"
5
#include "ring.h"
6
7
// #define rs232_OUTFUNCTIONS
8
// #define rs232_OUTSTRING
9
10
#define _rs232_VEC_DRE(p,x)  USART##p##x##_DRE_vect
11
#define rs232_VEC_DRE(p,x)  _rs232_VEC_DRE(p,x)
12
13
#define _rs232_VEC_TXC(p,x)  USART##p##x##_TXC_vect
14
#define rs232_VEC_TXC(p,x)  _rs232_VEC_TXC(p,x)
15
16
#define _rs232_VEC_RX(p,x)  USART##p##x##_RXC_vect
17
#define rs232_VEC_RX(p,x)  _rs232_VEC_RX(p,x)
18
19
#define _rs232_U(p,x)    USART##p##x
20
#define rs232_U(p,x)    _rs232_U(p,x)
21
22
23
#define rs232_CTS_PIN  D,1
24
#define rs232_RTS_PIN  D,0
25
//#define rs232_LED_PIN  B,2
26
27
28
#define rs232_PORT    D
29
#define rs232_PORT_NUM  0
30
31
32
#define rs232_UART    rs232_U(rs232_PORT,rs232_PORT_NUM)
33
34
#if (rs232_PORT_NUM) == 0
35
#define rs232_TX_PIN  rs232_PORT,3
36
#define rs232_RX_PIN  rs232_PORT,2
37
#endif
38
39
#if (rs232_PORT_NUM) == 1
40
#define rs232_TX_PIN  rs232_PORT,7
41
#define rs232_RX_PIN  rs232_PORT,6
42
#endif
43
44
#define rs232_IRX_LV  USART_RXCINTLVL_MED_gc
45
#define rs232_IDRE_LV  USART_DREINTLVL_MED_gc
46
#define rs232_ITXC_LV  USART_TXCINTLVL_MED_gc
47
#define rs232_IRTS_LV  PORT_INTLVL_MED_gc
48
49
#define rs232_TX_BLEN  64  // power of 2
50
#define rs232_RX_BLEN  512  // power of 2
51
52
#define rs232_RX_LOWMARK (unsigned int)(rs232_RX_BLEN / 2)
53
#define rs232_RX_HIGHMARK (unsigned int)(rs232_RX_BLEN-64)
54
55
#define rs232_BAUD    9600
56
//#define rs232_BAUD    19200
57
//#define rs232_BAUD    57600
58
//#define rs232_BAUD    115200
59
60
/* 
61
** high byte error return code of uart_getc()
62
*/
63
#define rs232_FRAME_ERROR    0x0800        /* Framing Error by UART       */
64
#define rs232_OVERRUN_ERROR    0x0400        /* Overrun condition by UART   */
65
#define rs232_BUFFER_OVERFLOW  RING_BUFFER_FULL  /* receive ringbuffer overflow */
66
#define rs232_NO_DATA      RING_NO_DATA    /* no receive data available   */
67
68
69
/*
70
** function prototypes
71
*/
72
73
74
void rs232_cts_on(void);
75
void rs232_cts_off(void);
76
77
unsigned char rs232_setspeed(unsigned long baud);
78
void rs232_init(void);
79
void rs232_disable(void);
80
void rs232_enable(void);
81
82
unsigned int rs232_getc(void);
83
char rs232_putc(unsigned char data);
84
85
#ifdef rs232_OUTSTRING
86
void rs232_puts(const char *s );
87
void rs232_puts_p(const char *s );
88
89
#define rs232_puts_P(__s)       rs232_puts_p(PSTR(__s))
90
#endif
91
92
#ifdef rs232_OUTFUNCTIONS
93
void rs232_sint_len(int z,unsigned char len);
94
void rs232_sint(int z);
95
#endif
96
97
extern Ring_buffer rs232_Rxr;
98
extern Ring_buffer rs232_Txr;
99
100
101
102
#endif
103
// end of file


jetzt müßtest du nur den ringbuffer in der main auslesen (nachdem du die 
rs232 initialisiert hast) und immer dann den usb-buffer füllen.

bye woodym

von Felix H. (masterq)


Lesenswert?

Vielen Dank für die wirklich schnelle und umfangreiche Antwort.
Leider habe ich meine Frage wohl nicht genau genug formuliert. 
Entschuldige. Also ein Ring Buffer zu schreiben ist für mich kein 
Problem, und eine hübsche UART Routine habe ich auch. Deinen Ring Buffer 
finde ich ganz hübsch.
Was mir daran nicht so gut gefällt ist das du so oft dereferenzieren 
musst um auf die Ring Daten zuzugreifen. Aber mit sollchen sachen bin 
ich mir oft etwas zu pingelig. Und schneller als die von Atmel (ASF) 
bist du alle Male :) Was ich dir noch empfehlen kann ist DMA und nur 
zweierpotenzen als zugelassene Größe.
DMA ist auf dem XMEGA keine Zauberrei und macht wirklich spaß, lohnt 
sich aber natürlich nur wenn man zumindest hin und wieder etwas größere 
Datenmengen speichern will.
Ich habe eigentlich für alles eigene Routinen geschrieben. Bis auf für 
USB. Das wollte ich als nächstes aber mir fehlt in letzte Zeit zu oft 
die Zeit.

Was ich eher meinte ist ob es möglich ist, bereits in der (UART oder 
sonstwas) IRQ Routine die Daten in USB Buffer zu schreiben. Dieses 3 
fache hin und her kopiere fühlt sich etwas unsauber an finde ich. Obwohl 
ich natürlich ganz klar die Vorteile sehe.

Das Zeichen verloren gehen werden, weil ich sie selbst überschreiben 
könnte ist klar. Ich wollte es halt möglichst einfach halten. Was ich 
dich eher prüfen lassen wollte, ist ob ich die USB Geschichte richtig 
benutze. Oder ob ich dort in irgendwelche hässlichen Zustände kommen 
könnte.

Grüße

Felix

von Felix H. (masterq)


Lesenswert?

Hallo nochmal!
Hätte nochmal eine Frage.
Es gibt:
cdc_rxb.len und cdc_rxb.bytes
wenn die ungleich sind liegen neue Daten an. Soviel habe ich verstanden.

1. Wenn ich jedoch wissen möchte wie viele Bytes reingekommen sind, kann 
ich dann einfach:
n = cdc_rxb.len - cdc_rxb.bytes;
machen?
2. Und von &cdc_rxb.data[cdc_rxb.bytes] die nächsten n Bytes lesen?
3. Was sind cdc_rxb.bytes und cdc_rxb.len überhaupt?

Es scheint ja so gemacht zu werden. Verstehe jedoch nicht wie das 
möglich sein soll, irgendwann muss es doch mal einen wrap geben und man 
kann dann nicht einfach weiter lesen. Oder wird darauf einfach geachtet 
beim USB Buffer füllen...?

Besten Dank

Felix

von woodym (Gast)


Lesenswert?

hallo felix,

also erst mal zu deiner anderen frage....
du kannst auch im irq Daten schreiben. du mußt nur sicherstellen das du 
erst die Daten reinschreibst und dann erst das flag schreibst. das flag 
wird genommen um zu entscheiden ob Daten da sind oder nicht.

die bytes sind nur da, um bereits Daten reinlaufen zu lassen ohne das 
eine Aktion ausgeführt wird (wenn es aus einem ringbuffer kommt).

das ist der code in usb_cdc.c
das ist nicht optimal, aber etwas universeller gehalten weil damit ja 
auch andere enpoints erstellt werden können (hid usw.).

wenn du die task aufrufst
EP_DEF_in(ep_out);
EP_DEF_out(ep_in);
EP_DEF_out(ep_note)

wird bei den out-endpoints nachgesehen ob irgend was zu senden ist. ist 
das flag gesetzt, dann wird der buffer kopiert und eine Übertragung 
angestoßen.

bei den in-endpoints wird nachgesehen ob der usb irgend was asynchrones 
will und ob Daten eingetroffen sind. ist der buffer frei, werden die 
Daten in den in-buffer kopiert und das flag gelöscht.
d.h. das flag ist für dich ein Indikator ob ein block von Daten gesendet 
werden kann oder empfangen wurde.

trägst du erst 10 bytes ein und setzt das flag nicht (beim senden), dann 
passiert auch nichts. du kannst dann data[len] auch weitere Daten 
eintragen bis zu maximallänge des buffers und/oder buffers für den 
endpoint.
dann setzt du das flag und stößt dann erst die Übertragung an.

du solltest möglichst schnell den buffer räumen für den empfang, weil 
erst wenn da reingeschrieben werden kann, geht die usb-übertragung 
weiter.

man könnte den buffer auch als endpoint-buffer direkt nehmen (schneller 
beim senden weil einmal die Daten nicht umkopiert werden müssen), wäre 
dann aber bis zum ende der usb-übertragung blockiert. das wiederum würde 
die datenvorbereitung langsamer machen.

du kannst das ganze auch im irq machen, weil ein setzen eines bytes und 
das lesen 'atomic' ist, also nicht unterbrochen werden kann. beim flag 
sollte man nicht += oder |= usw machen weil das zwei zugriffe sind und 
nicht sichergestellt ist das da zwischen lesen und schreiben kein irq 
dazwischenfunken kann.
1
///////////////////////////////////////////////////////////////////////////////////////
2
// incomming data from usb. this functions have to handle the databuffer pointet with the void-ptr
3
// the data-len is given in le
4
///////////////////////////////////////////////////////////////////////////////////////
5
unsigned int usb_handle_ring_rx(void *pt, unsigned int le)
6
{
7
//  unsigned char *a=pt;
8
  if(  cdc_rxb.flag)
9
  {
10
    memcpy(cdc_rxb.data,(unsigned char *)pt,le);
11
    cdc_rxb.len=le;
12
    cdc_rxb.bytes=0;
13
    cdc_rxb.flag=0;
14
    return 1;
15
  }
16
  return 0;
17
}
18
19
///////////////////////////////////////////////////////////////////////////////////////
20
// outgoing data to usb (device to host). this functions have to handle the databuffer pointet with the void-ptr
21
// the data-max-len is given in le; it have to returnd the len of data in buffer
22
// if nothing to send, return 0
23
///////////////////////////////////////////////////////////////////////////////////////
24
unsigned int usb_handle_ring_tx(void *pt, unsigned int le)
25
{
26
  unsigned int num=0;
27
  if(  cdc_txb.flag)
28
  {
29
    memcpy((unsigned char *)pt,cdc_txb.data,cdc_txb.len);
30
    num=cdc_txb.len;
31
    cdc_txb.bytes=0;
32
    cdc_txb.flag=0;
33
  }
34
  return num;
35
}

um nochmal explizit die frage mit dem wrap zu beantworten. hier gibt es 
kein wrap weil immer ein datenblock übertragen wird, kein ring.

schauen wir uns doch nochmal das an:
1
    if(!cdc_rxb.flag)
2
    {
3
      if(cdc_rxb.bytes==cdc_rxb.len)
4
      {
5
        // all data prcessed; send the data in txd and wait for new data
6
        // only if out-data are processed
7
        if(!cdc_txb.flag)
8
        {
9
          cdc_txb.len=cdc_txb.bytes;
10
          cdc_txb.flag=1;
11
          cdc_rxb.flag=1;
12
          // blink led for receiving data
13
          // led_blink_flag|=LED_BLINK_RED;
14
        }
15
      }
16
      else
17
      {
18
        // process data
19
        res=cdc_rxb.data[cdc_rxb.bytes];
20
        cdc_txb.data[cdc_txb.bytes]=res;
21
        // process here the data (res)
22
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
23
        cdc_rxb.bytes+=1;
24
        cdc_txb.bytes+=1;
25
      }
26
    }

es kommen Daten rein vom usb. damit wird cdc_rxb.len gesetzt. wenn die 
reinkommen, wird cdc_rxb.bytes auf 0 gesetzt.
cdc_rxb.len ist somit ungleich cdc_rxb.bytes.
es wird ein byte rausgeholt (cdc_rxb.data[cdc_rxb.bytes]) und in den 
ty-buffer kopiert (cdc_txb.data[cdc_txb.bytes]). dann werden die bytes 
erhöht.
das umkopieren innerhalb der schleifendurchgänge geht solange bis 
bytes==len ist. also so viele byte kopiert wurden die auch eingelaufen 
sind.
das ist alles fein für den loop-test. überträgst du reale Daten, dann 
kannst du auch einen ganzen block reinschieben und das flag setzen. 
cdc_rxb.bytes wird dafür nicht wirklich benötigt. übertragen werden 
cdc_rxb.len Daten.




bye woodym

von Wolfgang (Gast)


Lesenswert?

Hi woodym,

Vielen Dank für deinen Beitrag. Dein Code hilft einem USB Neuling sehr. 
Ich habe versucht den ASF USB und den LUFA zu durchblicken und bin auch 
versunken...

Ich habe eine Frage: Ich habe deinen Code am Laufen in einem XMEGA256A3U 
und der funktioniert auch tadelos. Aber das geht nur in der 
Application-Mem-Section. Sobald ich versuche den Code in der 
Boot(loader)-Section laufen zu lassen funktioniert die USB-Enumerierung 
nicht mehr. ("USB Gerät wurde nicht erkannt")

Um den Code für die Boot-Section umzubauen habe ich einerseits im Linker 
die Section umgestellt (.text=0x20000), das BOOTRST Flag auf BOOTLDR 
gesetzt und im main nach dem IRQ-Level enable den IRQ Vector vebogen 
(temp = PMIC.CTRL | PMIC_IVSEL_bm; CPP = CCP_IOREG_gc; PMIC.CTRL = 
temp;). Aber irgend etwas habe ich noch vergessen.

Kannst du mir einen Tipp geben wo ich suchen muss?

Schöne Grüße
Wolfgang

von woodym (Gast)


Lesenswert?

hallo Wolfgang,

das ganze ist schon ein paar Tage her ;-)... ich versuche es dennoch.

Wenn das USB als solches mal erkannt wird, dann würde ich vermuten das 
die ganzen IRQ's usw. schon mal laufen. Zumindest funktioniert die 
Initialisierung.
Der ganze Descriptor-Teil liegt in Progmem. Wo legt der Compiler den den 
Teil hin? Der sollte dann auch im Bootloader liegen. Wenn dem nicht so 
ist, wäre das schon eine mögliche Fehlerquelle und würde auch das 
verhalten erklären.
Die andere Frage die sich mir stellt, ob das pgm_read_byte (in 
decriptor.c) bei über 128k Flash so einfach funktioniert oder ob man da 
nicht Flash-blöcke umschalten muß damit die Pointer da richtig 
funktionieren.
Ich meine mich erinnern zu können das bei den großen Flash-Speicher im 
Bootloader immer besondere Aktionen gemacht werden müssen damit diese 
dann auch laufen.

bye woodym

von Wolfgang (Gast)


Lesenswert?

Hi woodym,

Ich war leider wieder etwas im Ausland unterwegs deswegen bin ich immer 
etwas delayed... Aber danke für deine Infos! Ich versuche mich gerade in 
die PROGMEM Thematik genauer einzulesen und es schaut wirklich sehr 
danach aus als kann das auf großen XMEGAs zum Problem werden.
Momentan habe ich einen Bootloader der mich zwar nicht sehr glücklich 
macht, aber erstmal funktioniert (DFU von Atmel gepacht im Hex-Editor 
auf individuellen Bootloader Start-Pin, das kompilieren des Atmel DFU 
Source Codes schaffe ich im GCC auch nicht).
Sobald ich meine primäre Applikation fertig habe werde ich mich des 
Bootloaders nochmal annehmen und melde mich.

Danke
Wolfgang

von woodym (Gast)


Lesenswert?

hallo,

ich habe da etwas gefunden:

https://lists.gnu.org/archive/html/avr-gcc-list/2010-06/msg00066.html

Hier hat auch jemand das Problem das 'progmem' in den ersten 64k 
platziert werden.

Vielleicht ist es um einiges einfacher das PROGMEM einfach weg zu lassen 
und es im Memory einfach durch den Compiler initialisieren zu lassen. Du 
verlierst dann etwas Speicher, was aber im bootloader nicht wirklich 
relevant sein dürfte.

In der Umsetzung PROGMEM weg lassen und wenn read aufgerufen einfach 
direkt auf den speicher zugreifen. Alternativ kannst du auch über den 
Pointer darauf zugreifen der (auch im read) ja vorhanden ist.

bye woodym

von Matthias (Gast)


Lesenswert?

Hallo woodym,

dein Code funktioniert auch bei mir ohne Probleme. Nur leider habe ich 
es nicht geschafft, ihn für meine Zwecke anzupassen.

Das Ziel ist es eine Zeichenkette an den µC  zu senden, dort in einem 
String zu speichern und diesen weiterzuverarbeiten. Die Zeichenkette 
kann unterschiedlich lang sein (max. 6 Zeichen) und endet immer mit 
einem '\r'.

Ich denke, dazu brauche ich eine while-Schleife, in der ich nacheinander 
die empfangenen Zeichen in ein String speicher, solange bis ein '\r' 
empfangen wurde. Das habe ich auch versucht, allerdings ohne Erfolg. Das 
mag daran liegen, dass ich noch nicht genau verstanden habe, wann ich 
welche Flags setzen muss. Die gespeicherte Zeichenkette möchte ich dann 
auch wieder zurückschicken. Mein Code dazu sieht so aus:
1
unsigned char Line[6];
2
while(1)
3
{
4
 //wann genau muss ich das immer aufrufen?
5
 EP_DEF_in(ep_out);
6
 EP_DEF_out(ep_in);
7
 EP_DEF_out(ep_note);
8
9
 if(cdc_rxb.bytes==cdc_rxb.len)
10
 {
11
  if(!cdc_txb.flag && cdc_txb.len != cdc_txb.bytes)
12
  {
13
   cdc_txb.len=cdc_txb.bytes;
14
   cdc_txb.flag=1;
15
   cdc_rxb.flag=1;
16
  }
17
 }
18
 else if(!cdc_rxb.flag)
19
 {
20
  int i=0;
21
  while((Line[i]=cdc_rxb.data[cdc_rxb.bytes])!='\r')
22
  {
23
   cdc_rxb.bytes+=1;
24
   i++;
25
  }
26
 }
27
 int x=0;
28
 while ((cdc_txb.data[cdc_txb.bytes]=Line[x])!='\r')
29
 {
30
   cdc_txb.bytes+=1;
31
   x++;
32
 }
33
}

Könntest du mir etwas auf die Sprünge helfen? Danke!

Schöne Grüße
Matthias

von woodym (Gast)


Lesenswert?

hallo Matthias,

wie wird denn das Telegramm auf die Reise geschickt? Kommen da immer 
alles als Block?
Wenn dem so wäre, dann kannst du gleich im Buffer nach deiner Kennung 
suchen und auf die Funktionen verzweigen. Kommt es aus einer unbekannten 
Quelle (z.B. ein Terminalprogramm) dann mußt du wirklich die einzelnen 
Daten erst einmal zwischenspeichern.

Deine erste Frage steht ja im Code... "//wann genau muss ich das immer 
aufrufen?". Die kurze Antwort IMMER!.

Das ganze USB-Zeug funktioniert ja vollkommen asynchron. es kann zu 
jeder Zeit was reinkommen oder etwas zum Senden sein. Das Senden ist 
relativ einfach, weil ich weiß ja wenn etwas zu senden ist. Schlimmer 
wird es wenn Notifikation übermittelt werden oder ich etwas empfangen 
soll.
Der USB-Controller ist so eingestellt, das er für den empfang von Daten 
bereit ist. Die Daten laufen im Prinzip ohne mein Zutun ein. Irgend wie 
muß ich aber die frohe Kunde erhalten das sich bei den Daten etwas getan 
hat (oder eine Benachrichtigung über einen Statuswechsel). Der Aufruf 
von dem hier tut genau das:
1
 EP_DEF_in(ep_out);
2
 EP_DEF_out(ep_in);
3
 EP_DEF_out(ep_note);

Man könnte auch die expliziten Funktionen für die möglichen Zustände 
aufrufen, das erfordert aber das man genau weiß was zu tun ist. Also 
wird das in Universelle Funktionen zusammengefasst die dann entsprechend 
das tun was nötig ist. Im Eingangsposting habe ich auch beschrieben das 
es per IRQ abgewickelt werden kann, jedoch die Synchronisation ungleich 
komplexer wird. Also wird bei jedem durchlauf einfach nachgesehen ob 
sich etwas getan hat und entsprechend darauf reagiert.

Also das in ein Main-Loop reinpacken dann sollte das ausreichend sein. 
Man kann das in etwa so beschreiben: Je seltener das aufgerufen wird, um 
so langsamer ist auch die Datenübertragung. Frühestens bei Jedem Aufruf 
der Funktionen kann ich auf Stati-Wechsel der USB-Schnittstelle 
reagieren.

Das ist deutlich langsamer als mit IRQ und verbraucht mehr Resourcen, 
ist aber erheblich unkomplizierter zu implementieren. Hier war das Ziel 
möglichst einfach an eine Funktionierende CDC zu kommen. Optimieren kann 
man wenn man versteht was abläuft oder es nötig ist.


Bei deinem Code sehe ich ein paar Fallen; genau genommen Zwei 
wesentliche.
1
  int i=0;
2
  while((Line[i]=cdc_rxb.data[cdc_rxb.bytes])!='\r')
3
  {
4
   cdc_rxb.bytes+=1;
5
   i++;
6
  }

Ich übersetze einmal: Kopiere solange von Line ind cdc_rxb.data fis ein 
\r erreicht wird.

Was ist wenn kein \r kommt? und warum \r.... hängt da ein MAC dran? 
sonst wäre ein \n ehr üblich (Unix \n oder PC \n\r oder \r\n) aber das 
wiederum was du der Schnittstelle gesagt hast was er als Abschlußzeichen 
nehmen soll.

Hier nochmal der ursprüngliche code für die Main-Loop.
1
while(1)
2
{
3
  // manage usb-task for the endpoints (in loop)
4
  EP_DEF_in(ep_out);
5
  EP_DEF_out(ep_in);
6
  EP_DEF_out(ep_note);
7
8
  // handle data if flag are cleared
9
  if(!cdc_rxb.flag)
10
  {
11
    if(cdc_rxb.bytes==cdc_rxb.len)
12
    {
13
      // all data prcessed; send the data in txd and wait for new data
14
      // only if out-data are processed
15
      if(!cdc_txb.flag)
16
      {
17
        cdc_txb.len=cdc_txb.bytes;
18
        cdc_txb.flag=1;
19
        cdc_rxb.flag=1;
20
        // blink led for receiving data
21
        // led_blink_flag|=LED_BLINK_RED;
22
      }
23
    }
24
    else
25
    {
26
      // process data
27
      res=cdc_rxb.data[cdc_rxb.bytes];
28
      cdc_txb.data[cdc_txb.bytes]=res;
29
      // process here the data (res)
30
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
31
      cdc_rxb.bytes+=1;
32
      cdc_txb.bytes+=1;
33
    }
34
  }
35
}

Sind Daten reingekommen? Das Flag wird auf 0 gesetzt wenn Daten da sind. 
Solange das Flag 0 ist, werden auch keine neuen Daten angenommen.
1
  if(!cdc_rxb.flag)

Habe ich bereits so viele Daten verarbeitet wie vorhanden sind?
1
    if(cdc_rxb.bytes==cdc_rxb.len)

und hier gehen wir zuerst auf den else-zweig:
1
      // process data
2
      res=cdc_rxb.data[cdc_rxb.bytes];
3
      cdc_txb.data[cdc_txb.bytes]=res;
4
      // process here the data (res)
5
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
6
      cdc_rxb.bytes+=1;
7
      cdc_txb.bytes+=1;

Hole dir das aktuelle Byte aus den empfangenen Daten raus in 'res'. Da 
bei diesem Beispiel alle empfangenen Daten wieder zurück gesendet werden 
sollen wird 'res' auch wieder in den Ausgabebuffer geschrieben.

In deinem Fall könntest du einfach in Line[i] schreiben, vorausgesetzt 
wurde vor dem 'while' auf 0 gesetzt. Ich schmücke das ein bischen aus...
1
if(i<sizeof(Line)-1)
2
{
3
   Line[i]=res;
4
}
5
if(res=='\r')
6
{
7
   guck_was_in_Line_drin_ist();
8
   // oder einfach Switch mit dem ersten Zeichen in Line
9
   Switch(*Line)
10
   {
11
      case 'A': //die A-Funktion
12
              a-Funktion(Line);
13
          break;
14
      case 'B': //die B-Funktion
15
              b-Funktion(Line);
16
          break;
17
      case 'C': //die C-Funktion
18
              c-Funktion(Line);
19
          break;
20
      default:
21
          // nene... nur gueltige Funktionen machen was
22
          break;
23
   }
24
   // Line wurde komplett abgearbeitet. Als naechstes muß eine neue Line kommen, darum Line wieder von vorne anfangen.
25
   i=0;
26
}

Der code vor dem 'else' sorgt dafür das ein Transmit angestoßen wird 
wenn der Ausgabebuffer gefuellt ist und der Transmitter frei ist 
(!cdc_txb.flag).
1
// das kann alles auch an anderer stelle veranlaßt werden. ich muß den receiver nicht erst freigeben wenn ich Daten sende. 
2
// ich muß auch erst Daten senden wenn ich sie reingepackt habe wenn es sich nicht um einen echo-test handelt
3
      if(!cdc_txb.flag)
4
      {
5
        // soviel soll gesendet werden
6
        cdc_txb.len=cdc_txb.bytes;
7
        // das sorgt dafuer das die Daten gesendet werden
8
        cdc_txb.flag=1;
9
        // das sorgt dafuer das der Receiver wieder Daten aufnehmen kann. 
10
        cdc_rxb.flag=1;
11
12
        // blink led for receiving data
13
        // led_blink_flag|=LED_BLINK_RED;
14
      }

ich hoffe ich konnte helfen

bye woodym

von woodym (Gast)


Lesenswert?

hach, ich sehe gerader ich habe ein ++ vergessen.

falsch
1
Line[i]=res;

richtig
1
Line[i++]=res;

Der Code ist jetzt aus der hohlen Hand... also ungetestet, soll ja nur 
ein Anhaltspunkt sein.
Der Main-Loop ist schon die Schleife in der du die einzelnen Zeichen 
abarbeiten kannst. Und wirklich CPU-Resourcen werden hier nicht 
vergeudet weil meistens an den paar IF's vorbeigerutscht wird. Davon 
abgesehen sollte es auch mit deine Schleifen gehen wenn sichergestellt 
ist das Line genügend groß ist das es die Zeichen aufnehmen kann, das 
auch nur in den Buffer geschrieben wird wenn Platz ist und das dein Line 
mit dem '\r' synchronisiert wird.
Letzteres mache ich in dem code oben mit i=0. Auch wenn das erste 
Telegramm in die Hosen geht, spätestens beim 2. fängt Line immer mit 
deiner Telegrammkennung an. Einfach ist es wenn man bei einem Telegramm 
ein Startzeichen definiert. Da kann dann auch Müll kommen, bei jedem 
Startzeichen wird von neuem versucht das Telegramm zu Interpretieren.

>A12345\r
>B123456\r
>C222\r
>D\r

'>' ist das Startzeichen, \r das Endzeichen.

bye woodym

von woodym (Gast)


Lesenswert?

vielleicht nochmal die flags:

cdc_txb.flag

Eine 0 zeigt an ob der Transmitter frei ist. Ist das der Fall, kann ein 
neues Senden angestoßen werden. Dazu müssen die Daten in cdc_txb.data 
stehen und cdc_txb.len muß mit der Anzahl der Bytes gesetzt sein. Wird 
dann cdc_txb.flag auf 1 gesetzt und dann

EP_DEF_in(ep_out);
EP_DEF_out(ep_in);

aufgerufen, werden die Daten gesendet. Wurden die Daten auch wirklich 
verschickt, wird cdc_txb.flag wieder auf 0 gesetzt.
Das setzen/löschen der flags erfolgt durch die aufrufe von
EP_DEF_in(ep_out);
EP_DEF_out(ep_in);


cdc_rxb.flag
Eine 0 Zeigt das Daten eingegangen sind. Der Buffer cdc_rxb.data ist mit 
den Daten beschrieben. Die Anzahl der Daten ist in cdc_rxb.len 
eingetragen.
Setze ich das Flag auf 1, weiß der Aufruf von
EP_DEF_in(ep_out);
EP_DEF_out(ep_in);

das der Buffer frei ist (alles wurde verarbeitet) und für weitere Daten 
verfügbar ist.

von Matthias (Gast)


Lesenswert?

Hallo woodym,

vielen Dank für die ausführliche Erklärung! Ich werde das heute Abend 
mal durcharbeiten und melde mich dann wieder. Bis dann.

Gruß
Matthias

von Matthias (Gast)


Lesenswert?

Hallo woodym,

ja, die Daten kommen immer als Block und werden von einem eigenen 
Programm gesendet (mit Visual Studio programmiert). Ich sende am Ende 
jeden Blocks ein '\r'. Du hast Recht, ein '\r' ist unüblich. Ich habe 
einfach ein Zeichen genommen, bei dem ich weiß, dass es an keiner 
anderen Stelle der Zeichenkette vorkommt. Ich werde das aber eventuell 
doch auf '\n' ändern, danke für den Hinweis.
Jetzt zum Code: Wenn man einmal verstanden hat wie es genau 
funktioniert, ist es wirklich einfach. Ich habe deinen Vorschlag 
umgesetzt und arbeite die einzelnen Zeichen jetzt in der Main-Loop ab 
und nicht in einer gesonderten while-Schleife. Das ist besser und 
funktioniert auch optimal.
Deinen Vorschlag ein Startzeichen zu definieren werde ich ebenfalls 
berücksichtigen, dann kann ich sicher sein, dass auch wirklich alles 
übertragen wurde und es kann zu keinen Fehlern bei der Interpretation 
der Daten kommen.

Danke nochmal für deine Hilfe!

Gruß
Matthias

von Wolfgang K. (opendcc)


Lesenswert?

Hallo woodym,

vielen Dank für dieses schlankere CDC. Ich habe es auf einen xmega32a4u 
problemlos portiert. Nun ja, fast problemlos, deshalb diese Nachfrage:

in ep_def_in wird bei Transition Complete dann der Handler aufgerufen:
1
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
2
        {
3
          if(p->handler((unsigned char *)b->DATAPTR,b->CNT))
4
          {
5
            b->CNT=p->len-1;
6
            // note: lacr16 = load and clear
7
            LACR16(&(c->STATUS), USB_EP_BUSNACK0_bm | USB_EP_TRNCOMPL0_bm | USB_EP_OVF_bm);   // note: LACR16 means load and clear
8
            p->bank=1;
9
          }
10
        }
Das passiert unter 'ATOMIC', d.h. Interrupt sind in der Zeit komplett 
zu. Ist das unbedingt nötig und wenn ja, warum? Eigentlich hat man doch 
Ruhe, solange USB_EP_TRNCOMPL0 nicht gelöscht worden ist.

Ich möchte wegen der Bedienung anderer Interrupts die globalen 
Interrupts so wenig wie möglich und so kurz wie möglich sperren.

Danke

Wolfgang

von Woodym (Gast)


Lesenswert?

Hallo,
Ich bin gerade im Urlaub deswegen kann ich mir das nicht genau ansehen.
Soweit ich das noch im Kopf habe ist das deswegen weil der Händler, wenn 
definiert, auch die Pointer umschaltet. Damit da nichts kracht muss hier 
dafür gesorgt werden das nichts klemmt und gegenseitig überschreibt. Ist 
kein Händler definiert, spielt das keine Rolle, dann ist das nur die if 
Abfrage während die geblockt wird.
Wenn ich das noch richtig weiß ist das auch alles egal (man braucht das 
Atomic nicht) wenn das nicht über Interrupt gemacht wird wie im 
Beispiel. Dann wird das alles sowieso sequenziell abgearbeitet.

Ich hoffe ich konnte mit der Antwort helfen.

Ach so, auf dieser Basis habe ich vor ein paar Monaten ein Multi CDc 
gemacht mit 5 seriellen... Funktioniert prima ( ich bin stolz ;-) ).

Bye woodym

von Woodym (Gast)


Lesenswert?

Ich hasse die Autokorrektur des Tablets!

von Wolfgang K. (opendcc)


Lesenswert?

Hallo,

ich habe da mal ein bischen durch den Code gelesen. Was mir noch nicht 
klar ist: Das CDC Interface besteht ja aus 3 Endpoints (note, in, out). 
in/out ist klar, aber mit dem note habe ich Verständnisschwierigkeiten:

Hinterlegt sind in usb_handle_ring_note Routinen zum Stellen/Abfragen 
von DCD/DSR. Die Behandlung von LineEncodung erfolgt aber im 
Endpoint[0], also auf dem Device-Endpoint, offenbar also nicht per 
CDC-Interface, sondern global für das gesamte Device.

Du hast doch da schon mal einen composite aus mehreren CDCs gebaut - 
sind diese denn dann unabhängig voneinander (bezogen auf LineEncoding) ?

Und das mit dem Interrupt-Blocken scheint mir bei speziell bei USB_EP_PP 
= 1 (also Wechselbuffer) nicht nötig zu sein.

Servus Wolfgang

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Woodym schrieb:
> Ich hasse die Autokorrektur des Tablets!

Abschalten. ;-)

von Wolfgang K. (opendcc)


Lesenswert?

Hallo,

ich versuche, das mit HID zu kombinieren. Dazu habe ich die 
Descriptortabelle für die Devicekonfig entsprechend erweitert. Aber da 
gibt es dann Probleme: die Tabelle wird dadurch länger als die 64 Bytes 
des EP0. Wie hast Du das gelöst?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wolfgang K. schrieb:
> Wie hast Du das gelöst?

Wie man das bei USB immer löst: in mehr als ein Paket aufteilen.

Falls es genau 64 Byte sind, musst du das berühmt-berüchtigte
ZLP (zero-length packet) nachschieben, damit die andere Seite das
Ende erkennt.

von woodym (Gast)


Lesenswert?

@Jörg Wunsch
als ich meine multi-Serielle gemacht hatte, dann hätte mir die Info Tage 
an Arbeit gespart ;-)

@Wolfgang K.
ich habe das realisiert in void USB_ep0_send_progmem (in usb_xm.c)
aus diesem:
1
  uint8_t *buf = ep0_buf_in;
2
  uint16_t remaining = size;
3
  NVM.CMD = NVM_CMD_NO_OPERATION_gc;
4
5
  USB_EP_pair_t* pair = &endpoints[0];
6
  USB_EP_t* e = &pair->ep[1];
7
  USB_EP_t* b = &pair->ep[1];
8
9
  while (remaining--)
10
  {
11
    *buf++ = pgm_read_byte(addr++);
12
  }
13
14
  b->DATAPTR = (unsigned) ep0_buf_in;
15
  b->CNT = size;
16
  LACR16(&(e->STATUS), USB_EP_BUSNACK0_bm | USB_EP_TRNCOMPL0_bm);

wird das:
1
  uint8_t *buf = ep0_buf_in;
2
  uint8_t ep0_buf_size;
3
  uint16_t remaining = size;
4
  NVM.CMD = NVM_CMD_NO_OPERATION_gc;
5
6
  USB_EP_pair_t* pair = &endpoints[0];
7
  USB_EP_t* e = &pair->ep[1];
8
  ep0_buf_size=USB_DEF_EP0_SIZE;
9
10
  while (1)
11
  {
12
    // wait for ready state
13
    if(e->STATUS&(USB_EP_BUSNACK0_bm))
14
    {
15
      *buf++ = pgm_read_byte(addr++);
16
      remaining--;
17
      ep0_buf_size--;
18
      if(!ep0_buf_size || !remaining)
19
      {
20
        e->DATAPTR = (unsigned) ep0_buf_in;
21
        e->CNT = USB_DEF_EP0_SIZE-ep0_buf_size;
22
        if(!ep0_buf_size && !remaining)
23
        {
24
          // set autozlp
25
          e->CNT |= USB_EP_ZLP_bm;
26
          
27
        }
28
        LACR16(&(e->STATUS), USB_EP_BUSNACK0_bm | USB_EP_TRNCOMPL0_bm);
29
30
        if(!remaining)
31
        {
32
          // last transmitted
33
          break;
34
        }
35
        ep0_buf_size=USB_DEF_EP0_SIZE;
36
        buf=ep0_buf_in;
37
      }
38
    }
39
  }

damit kann der Descriptor 'beliebig' lang sein.
das ZLP wird, wenn nötig, automatisch gesendet (e->CNT |= 
USB_EP_ZLP_bm).

bye woodym

von Wolfgang K. (opendcc)


Lesenswert?

Danke für den Tipp, das ZLP war das Problem. Jetzt wird die Combidevice 
eingelesen. Auf dem HID bekomme ich noch einen STALL, aber das ist das 
nächste Problem ...

Servus Wolfgang

von Christoph M. (chrito)


Angehängte Dateien:

Lesenswert?

Hallo zusammen!

Danke für diesen hervorragenden Beitrag!

Ich habe deinen Code ins Atmel Studio 7 importiert und die Defines aus 
deinem makefile übernommen. Läuft hier auf einem XMEGA128A1 einwandfrei. 
Das lauffähige Projekt ist im Anhang.

Folgende Dinge sind allerdings komisch:

1. Die VirtualSerial.inf läuft auf meinem Host (Win7 64Bit) nicht. Ich 
habe stattdessen von Atmel den Treiber für die Virtuelle Brücke (EDBG 
Virtual COM Port) manuell geladen.

2. Wenn ich die Optimierung von -01 auf -O0 stelle, dann geht es nicht 
mehr: Irgendwo scheint der Code zu langsam zu werden. Aber ohne 
Optimierung ist das Debugging einfacher, so optimiert er alle möglichen 
Variablen weg. Hab versucht, lokal die Optimierung abzuschalten (mit 
#define _DEBUG), aber das geht nicht, hat wohl nichts mit der 
Optimierung zu tun.

3. Bei dem CDC-Beispiel aus ASF
(das hier: 
asf.atmel.com/docs/3.31.0/common.services.usb.class.cdc.device.example.s 
tk600_atxmega128a1u/html/index.html)
funktioniert die Verbindung weiter, wenn man zwischenzeitig einen 
Haltepunkt setzt und dann weiter laufen lässt. Scheint wohl eine 
Fehlerresistenz eingebaut zu sein, die hier fehlt, oder wie ist das???

Danke und Grüße!
Christoph

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph M. schrieb:

> 2. Wenn ich die Optimierung von -01 auf -O0 stelle, dann geht es nicht
> mehr: Irgendwo scheint der Code zu langsam zu werden.

Wundert mich nicht.

Wenn du Variablen oder Zwischenergebnisse im Debugger brauchst,
dann definier' die entsprechenden Variablen vorübergehen "volatile".
Das schaltet dann nur deren Optimierung aus, nicht aber die komplette.

> 3. Bei dem CDC-Beispiel aus ASF
> funktioniert die Verbindung weiter, wenn man zwischenzeitig einen
> Haltepunkt setzt und dann weiter laufen lässt.

Glaub' ich nicht.

Wenn das Device beim Host bekannt ist, bleibt es auch erstmal bekannt,
solange keine Daten zum Device zu senden sind, auch wenn der Code
angehalten ist.  Sowie er aber reagieren müsste, bricht der Kram
auseinander.

Das ist eigentlich eins der Hauptprobleme mit jeglichen USB-Devices
in Mikrocontroller-Firmware; daher sehen wir immer alternativ noch
eine richtige UART vor (sofern möglich), die man während des
Debuggens benutzen kann.

von woodym (Gast)


Lesenswert?

hallo,

zu Punkt 1) kann ich nichts sagen, bei mir funktioniert in inf auf 32 
Bit XP, 64 Bit Win7 und 64 Bit win10.

Zu Punkt 2) .... ich habe noch nie andere Optimierung versucht.

Zu Punkt 3) Ich könnte mir vorstellen das ASF den Treiber per Interrupt 
eingebunden hat. Dann dürften das ganze Handling auch unabhängig von 
Breakpoints funktionieren. Wenn das, wie bei meinem Beispiel, per 
polling eingebunden ist, kann das nicht mehr gehen weil der USB dann 
nicht mehr frei wird weil keiner mehr die Flags wegnehmen kann. Das ist 
das einzige was ich mir hier vorstellen kann.

bye woodym

von Christoph M. (chrito)


Angehängte Dateien:

Lesenswert?

Hallo Jörg, hallo Woodym,

hab das Programm komplett auseinandergenommen und analysiert... es ist 
doch alles etwas anders:

Jörg W. schrieb:
>> 2. Wenn ich die Optimierung von -01 auf -O0 stelle, dann geht es nicht
>> mehr: Irgendwo scheint der Code zu langsam zu werden.
> Wundert mich nicht.

Grund sich zu wundern: Der XMega ist nämlich auch ohne Optimierung 
schnell genug, die USB-Kommunikation zu betreiben. Ohne Optimierung 
scheitert es nur an der Konfiguration des µCs, d.h. dort wo die Code 
Protection abgeschaltet wird. Dieser Mechanismus geht nur mit -O1. Alles 
andere läuft auch mit -O0.

In dem Quellcode, den ich gleich anhänge, habe ich die Optimierung lokal 
mit #pragma GCC optimize ("O1") aktiviert, den Rest des Programmes lasse 
ich mit -O0 laufen.

>> 3. Bei dem CDC-Beispiel aus ASF
>> funktioniert die Verbindung weiter, wenn man zwischenzeitig einen
>> Haltepunkt setzt und dann weiter laufen lässt.
>Glaub' ich nicht.

Ist aber so, zumindest zur Hälfte: Das RX läuft weiter, egal wie lang 
der Haltepunkt war. Ich habe jetzt auch dieses Beispiel modifiziert, und 
auch hier läuft zumindest der RX-Kanal weiter. Du kannst es gerne selber 
ausprobieren.

Der TX geht nach dem Haltepunkt nicht mehr. Ich habe einen Graphen des 
Programms erstellt und die Programmsequenzen analysiert, weil ich auch 
das Senden nach einem Haltepunkt gerne wiederbeleben würde.

Ich weiß soviel:
1. Von einer beliebigen Wartezeit in der while(1) Schleife in main wird 
TX nicht gekillt. D.h. ich kann fünf Sekunden lang die Endpunkte nicht 
mehr bedienen, das macht nichts, es bleibt stabil. Also Woodym: Das 
Pollen ist überhaupt kein Problem!
2. Laut USB-Spec (http://www.beyondlogic.org/usbnutshell/usb6.shtml) 
muss der µC gewisse Anfragen des Host innerhalb von 50 mSek beantworten.
3. Es gibt keinen Programmcode, der regelmäßig auf Host-Anfrage 
ausgeführt wird. Das habe ich durch Loggen der Funktionsaufrufe 
herausgefunden. Also denke ich, dass das Einfrieren der µC-USB-Hardware 
die Ursache für den TX-Abbruch ist. Vieleicht gibt hier der Host-Treiber 
auf und pollt diesen Endpunkt nicht mehr oder so???

Übrigens ist das, was im Programm "Ringpuffer" oder ring_tx heißt, 
eigentlich nur ein normaler Puffer ohne Ringstruktur. Hatte mich anfangs 
etwas verwirrt.

Ich stell euch mal die Übersicht und alles was ich hab hier rein :)

Grüße,
Christoph

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph M. schrieb:
> Das RX läuft weiter, egal wie lang der Haltepunkt war.

Auch dann, wenn der Host in dieser Zeit etwas sendet?

von Christoph M. (chrito)


Lesenswert?

Hier noch wie oben versprochen meine main.c:

Man kann mit "A..." eine LED toggeln, mit "B..." einen Text im 
Dauerfeuer zum Host senden und mit allen anderen Strings "x..." ein Echo 
bekommen. Die Optimierung ist lokal differenziert, global aber für diese 
Tests ausgeschaltet. Prozessor ist XMega128a1, Leiterplatte 
selbstentworfen. Also LEDs wahrscheinlich nicht dort, wo sie bei euch 
sind! Mit dieser main.c geht das Empfangen vom Host weiterhin, das 
Senden ist (warum?) hinüber.
1
#include <stdio.h>
2
#include <string.h>
3
#include <stddef.h>
4
#include <stdlib.h>
5
#include <avr/io.h>
6
#include <avr/interrupt.h>
7
#include <avr/pgmspace.h>
8
9
#include "main.h"
10
#include "usb_cdc.h"
11
#include <util/delay.h>
12
13
14
void init(void) 
15
{
16
  CLK.PSCTRL = 0x00;  // no division on peripheral clock
17
18
  // Power reduction: Stop unused peripherals
19
  PR.PRGEN = PR_AES_bm|PR_EBI_bm;                        // Stop: AES, EBI
20
  PR.PRPA  =           PR_AC_bm|PR_DAC_bm;                  // Stop: DAC, ADC, AC
21
  PR.PRPB  =           PR_AC_bm|PR_DAC_bm;                  // Stop: DAC, ADC, AC
22
  PR.PRPC  = PR_TWI_bm|PR_USART1_bm                                   ;    // Stop: TWI, USART0, USART1, SPI, HIRES
23
  PR.PRPD  = PR_TWI_bm|PR_USART1_bm|             PR_SPI_bm|PR_HIRES_bm;    // Stop: TWI, USART0, USART1, SPI, HIRES
24
  PR.PRPE  = PR_TWI_bm|PR_USART1_bm|PR_USART0_bm|PR_SPI_bm            ;    // Stop: TWI, USART0, USART1, SPI, HIRES
25
26
  PMIC.CTRL = PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm;
27
28
  Config32MHzClock();
29
  usb_init();
30
  PORTB.OUT|= 0b00000111;    // LEDs 5..6 aus
31
  PORTD.OUT = 0b11111111;    // LEDs 7..8 aus
32
  PORTQ.OUT = 0b11111111;    // LEDs 1..4 aus
33
  PORTB.DIR = 0b00000011;    // 1 für Ausgang, 0 für Eingang, LEDs 5..6
34
  PORTD.DIR = 0b00000011;    // 1 für Ausgang, 0 für Eingang, LEDs 7..8
35
  PORTQ.DIR = 0b00001111;    // 1 für Ausgang, 0 für Eingang, LEDs 1..4
36
37
  sei();
38
}
39
40
int main(void)
41
{
42
  uint8_t empfangene_Bytes[12];
43
  uint8_t Echo=0;
44
  uint8_t Dauerantwort=0;
45
  uint16_t Zaehler1;
46
  
47
  init();
48
49
  cdc_rxb.flag=1;   // Anfangszustände für Schreib-/Lesepuffer
50
  cdc_rxb.len=0;
51
  cdc_rxb.bytes=0;
52
53
  cdc_txb.flag=0;
54
  cdc_txb.len=0;
55
  cdc_txb.bytes=0;
56
57
  while(1)
58
  {
59
    EP_DEF_in(ep_out);          // Endpunkt-Tasks
60
    EP_DEF_out(ep_in);
61
    EP_DEF_out(ep_note);
62
63
    if (cdc_rxb.bytes<cdc_rxb.len)    // Neue Bytes empfangen?
64
    {
65
      memcpy(empfangene_Bytes, cdc_rxb.data, cdc_rxb.len);  // aus Puffer lesen
66
      cdc_rxb.bytes=cdc_rxb.len;    // als gelesen markieren
67
      cdc_rxb.flag=1;          // dann später wieder auf neue Daten testen
68
            
69
      switch (empfangene_Bytes[0])  // erstes Byte betrachten
70
      {  case 'A':  PORTQ.OUTTGL=0b00000001;   // LED toggeln     
71
              break;
72
        case 'B':   Dauerantwort = ~Dauerantwort;  // Zustand toggeln
73
              break;
74
        default:  Echo=1;
75
      }
76
    }
77
    
78
    if (Echo==1)  // das Empfangene zurücksenden
79
    {
80
      cdc_txb.bytes=0;
81
      memcpy(cdc_txb.data, empfangene_Bytes, cdc_rxb.len);
82
      cdc_txb.len=cdc_rxb.len;  // ein Zeichen zu versenden
83
      cdc_txb.flag=1; // versenden
84
      Echo=0;
85
    }
86
    
87
    Zaehler1++;
88
    if ((Zaehler1>8)&&(Dauerantwort))
89
    {
90
      Zaehler1=0;
91
      cdc_txb.bytes=0;
92
      memcpy(cdc_txb.data, "Hallo Welt! ", 12);
93
      cdc_txb.len=12; // zwölf Zeichen zu versenden
94
      cdc_txb.flag=1; // versenden
95
    }
96
    
97
    _delay_ms(200);  // funktioniert, egal wie lange hier die Pause ist
98
       
99
  }
100
  return 0;
101
}
102
103
104
// Konfiguration muss mit Optimierung laufen, sonst funktioniert Entsperrung der Code Protection nicht 
105
#pragma GCC optimize ("O1")   
106
void Config32MHzClock(void)
107
{
108
  unsigned char tmp;
109
110
  // get USBRCOSC
111
  NVM.CMD  = NVM_CMD_READ_CALIB_ROW_gc;
112
  tmp = pgm_read_byte(offsetof(NVM_PROD_SIGNATURES_t, USBRCOSC));
113
  NVM_CMD = NVM_CMD_NO_OPERATION_gc;  // Clean up NVM Command register.
114
115
  // enable DFLL for 32MHz osz and trim to 48MHz sync with USB start of frame
116
  OSC.DFLLCTRL = OSC_RC32MCREF_USBSOF_gc;
117
  DFLLRC32M.CALB = tmp;
118
  DFLLRC32M.COMP1 = 0x1B; //Xmega AU manual, 4.17.19
119
  DFLLRC32M.COMP2 = 0xB7;
120
  DFLLRC32M.CTRL = DFLL_ENABLE_bm;
121
122
  // enable 32 MHZ osz (trimmed to 48MHZ for usb)
123
  CCP = CCP_IOREG_gc; //Security Signature to modify clock
124
  OSC.CTRL = OSC_RC32MEN_bm| OSC_RC2MEN_bm; // enable internal 32MHz oscillator
125
  
126
  while(!(OSC.STATUS & OSC_RC32MRDY_bm)); // wait for oscillator ready
127
  
128
  OSC.PLLCTRL = OSC_PLLSRC_RC2M_gc | 16; // 2MHz * 16 = 32MHz
129
  
130
  CCP = CCP_IOREG_gc;
131
  OSC.CTRL = OSC_RC32MEN_bm | OSC_PLLEN_bm | OSC_RC2MEN_bm ; // Enable PLL
132
  
133
  while(!(OSC.STATUS & OSC_PLLRDY_bm)); // wait for PLL ready
134
  
135
  DFLLRC2M.CTRL = DFLL_ENABLE_bm;
136
137
  // use PLL as systemclk
138
  CCP = CCP_IOREG_gc; /* allow changing CLK.CTRL */
139
  CLK.CTRL = CLK_SCLKSEL_PLL_gc; // use PLL output as system clock
140
  //
141
}
142
143
#pragma GCC optimize ("O0")

von Christoph M. (chrito)


Lesenswert?

Jörg W. schrieb:
> Christoph M. schrieb:
>> Das RX läuft weiter, egal wie lang der Haltepunkt war.
>
> Auch dann, wenn der Host in dieser Zeit etwas sendet?

Hab ich gerad mal live für dich getestet... ja. Auch wenn der Host zum 
gestoppten µC sendet, geht es danach wieder! Die gesendet Bytes gehen 
verloren, aber alles nach dem Haltepunkt kommt wieder korrekt an.

VG

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph M. schrieb:
> Die gesendet Bytes gehen verloren, aber alles nach dem Haltepunkt kommt
> wieder korrekt an.

Finde ich in der Tat ungewöhnlich.

von Michael D. (sirs)


Lesenswert?

HI

Würde auch gern etwas mit USB rumspielen und euere Programme als Anfang 
nehmen, aber ich krieg es nicht zum Laufen. Hab dieses 
USB-ganz-einfach-Zip genommen, aber diesen Fehler bekommen:
1
Compiling C: main2.c
2
avr-gcc -c -mmcu=atxmega128a1 -I. -gdwarf-2 -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./main2.lst  -std=gnu99 -MMD -MP -MF .dep/main2.o.d main2.c -o main2.o 
3
In file included from usb_cdc.h:11,
4
                 from main2.c:10:
5
usb_ep.h:56: error: expected specifier-qualifier-list before 'USB_EP_t'
6
usb_ep.h:59: error: expected specifier-qualifier-list before 'USB_EP_t'
7
main2.c:105: warning: ignoring #pragma GCC optimize
8
main2.c: In function 'Config32MHzClock':
9
main2.c:112: error: 'NVM_PROD_SIGNATURES_t' has no member named 'USBRCOSC'
10
main2.c:116: error: 'OSC_RC32MCREF_USBSOF_gc' undeclared (first use in this function)
11
main2.c:116: error: (Each undeclared identifier is reported only once
12
main2.c:116: error: for each function it appears in.)
13
make.exe: *** [main2.o] Error 1

Edit: nutze "Programmers Notepad" und nicht das Atmel Studio.

: Bearbeitet durch User
von woodym (Gast)


Lesenswert?

hallo,

das sieht so aus als würden die Definitionen für den USB nicht vorhanden 
sein.
Nach meiner Kenntnis hat der atxmega128a1 kein USB, in so fern wären die 
Fehlermeldungen richtig. Der atxmega128a1u , der hat USB.
Ich verwende auch den Programmers Notepad (darum hatte ich mich bei der 
Diskussion wegen dem Debug rausgehalten). Hierfür hatte ich mehrfach den 
gcc aktuallisieren müssen, da der beim Notepad mitgelieferte gcc zu alt 
war und die neuen Prozessoren überhaupt nicht richtig definiert waren 
(z.B. ohne USB).

von Christoph M. (chrito)


Lesenswert?

Michael D. schrieb:
> Würde auch gern etwas mit USB rumspielen und euere Programme als Anfang
> nehmen, aber ich krieg es nicht zum Laufen. Hab dieses
> USB-ganz-einfach-Zip genommen, aber diesen Fehler bekommen:

Ja, du musst 128a1u nehmen, mit 128a1 geht es nicht!
Außerdem musst du F_CPU mit 32MHz und F_USB mit 48MHz definieren. Sieht 
so aus, als hättest du F_CPU auf 8MHz stehen.

VG Christoph

von Michael D. (sirs)


Lesenswert?

Dieses eine u hätte man sehen können... und merken können dass der 
installierte gcc tatsächlich den 128a1u nicht kennt.
--> Update ausführen

von Michael D. (sirs)


Lesenswert?

@woodym:
Welche Version hast du jetzt und wie hast du das gemacht? Meine ist 
"avr-gcc (WinAVR 20100110) 4.3.3" nach dem Update und er kennt den 
128a1u immer noch nicht :(

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Michael D. schrieb:
> nach dem Update

Wie willst du einem WinAVR, welches 2010 das letzte Mal aktualisiert
worden ist, denn auch ein "Update" verpassen?

Da musst du schon mal auf eine andere Toolchain umstellen, wenn du
aktuelle Devices benutzen möchtest, und das olle WinAVR aufgeben.
Früher war eben nicht alles besser …

von Christoph M. (chrito)


Lesenswert?

Michael D. schrieb:
> Welche Version hast du jetzt und wie hast du das gemacht? Meine ist
> "avr-gcc (WinAVR 20100110) 4.3.3" nach dem Update und er kennt den
> 128a1u immer noch nicht :(

Ich würd sagen, das hat nichts mit der Toolchain oder dem Compiler zu 
tun, vielmehr mit den eingebundenen Bibliotheken. Denn es fehlen die 
controllerspezifischen Definitionenen und Funktionen.

Du kannst dir ja die Mühe machen, und die Bibliotheken von Atmel Studio 
zu deinem Programmers Notepad rüberschubsen. Oder du installierst Atmel 
Studio und das Projekt müsste sich sofort übersetzen lassen.

VG
Christoph

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph M. schrieb:
> Ich würd sagen, das hat nichts mit der Toolchain oder dem Compiler zu
> tun, vielmehr mit den eingebundenen Bibliotheken.

Der Compiler muss den Controller schon auch kennen, denn er muss aus
der Angabe „-mmcu=atxmega128a1u“ intern eine Reihe von Parametern
ableiten.

von Michael D. (sirs)


Lesenswert?

Jörg W. schrieb:
> Wie willst du einem WinAVR, welches 2010 das letzte Mal aktualisiert
> worden ist, denn auch ein "Update" verpassen?
Ja, gar nicht. Ist mir auch im Nachhinein erst aufgefallen.... 
Kopf->Tisch

WInAVR aufgeben ist wohl die Lösung, aber Atmel Studio will diese Xmegas 
bei mir nicht erkennen oder standardmäßig mitinstallieren. Aber das 
kommt dann in einen anderen Thread.

von Bernd K. (prof7bit)


Lesenswert?

Christoph M. schrieb:
> Du kannst dir ja die Mühe machen, und die Bibliotheken von Atmel Studio
> zu deinem Programmers Notepad rüberschubsen. Oder du installierst Atmel
> Studio und das Projekt müsste sich sofort übersetzen lassen.

Die aktuelle avr-gcc toolchain gibts auch komplett separat als Download 
von Atmel, ohne beigepacktes Studio. Das kann er installieren um die 
verstaubte WinAVR Toolchain zu ersetzen.

von Michael D. (sirs)


Lesenswert?

So, bin jetzt auch soweit, dass das Ding mit dem Programm von chrito 
sich als USB meldet. Es wird aber nicht erkannt(unknown device) vom 
Windoof, egal mit welcher Optimierung. Was ist noch gleich die Lösung 
dafür?
1
Um das hinzubekommen hab ich übrigens manuell ein Update ausgeführt
2
und Daten (zB iox128a1u.h und andere) der avr8-Toolchain von Atmel in
3
den WinAVR-Ordner geschmissen. Jetzt wird das Ding erkannt und der Code
4
kompiliert :)

von Christoph M. (chrito)


Lesenswert?

Hallo Michael,

ändere mal diese Definitionen:
1
#define USB_DEF_VID 0x03EB  // VID for USB (Atmel)
2
#define USB_DEF_PID 0x2404  // PID for USB (Atmel CDC)

Dann hält Windows dein Gerät für ein Atmel Board und installiert den 
Treiber automatisch.

Hat es eigentlich jemand schonmal unter Linux getestet?

VG
Christoph

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph M. schrieb:
> Hat es eigentlich jemand schonmal unter Linux getestet?

Dem sind die IDs egal, es ist ja ein CDC, also wird es als
modemähnliches serielles Gerät registriert – von jedem anderen
Betriebssystem genauso.  Sogar von Windows 10, immerhin können die
da jetzt auch schon. ;-)

: Bearbeitet durch Moderator
von Michael D. (sirs)


Angehängte Dateien:

Lesenswert?

Linux is eben geil und läuft wenn es soll.

Ganz anders mein Board: Es meldet sich zwar an am USB-Port, aber es wird 
nicht erkannt (unknown device). Die PID und VID wird scheinbar nicht 
übertragen, egal welche man ihm übergibt. Woran hängt das denn wieder?
Oder liegts an Windows?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Michael D. schrieb:
> Woran hängt das denn wieder?

Um das rauszufinden, darfst du nicht nur die Definition von ein paar
Makros ansehen, sondern musst auch gucken, wo diese wirklich
verwendet werden.

USB-Debugging ist nicht so ganz trivial, vor allem, weil man die
Echtzeit-Abfolge der Firmware nicht stören darf, sonst ist der Host
sofort eingeschnappt.

Was sich bewährt hat: einen großen RAM-Puffer irgendwo anlegen, in dem
man verschiedene Trace-Informationen hinterlässt während der
Verarbeitung der Setup-Nachrichten.  Nachdem das ganze USB-Geraffel
des Hosts dann durch ist, kann man mit dem Debugger die Chose mal
anhalten und sich in diesem Puffer in Ruhe ansehen, was zuvor passiert
ist.

Wenn du einen Linux-Host zur Verfügung hast, dort kann man per
usbmon den Traffic mit Wireshark aufzeichnen und ansehen (auch denen
eines Windows-Gasts in einer VM).  Das ist recht hilfreich.

: Bearbeitet durch Moderator
von Christoph M. (chrito)


Lesenswert?

Hallo Michael!

Ich hab gerade nochmal das Projekt getestet, das ich hochgeladen habe. 
Es funktioniert auf anhieb.

Daher:
Hast du ein aktuelles Atmel Studio Komplett installiert?
Womit hast du's jetzt kompiliert?
Nutzt du einen XMEGA 128A1U oder vergleichbar?
Ist der USB-Port richtig angeschlossen?
Funktioniert dein Board richtig, Spannungsversorgung usw. alles schick?

VG
Christoph

von Christoph M. (chrito)


Angehängte Dateien:

Lesenswert?

Hallo Michael,

hier nochmal mit anderen VIDs und PIDs, so müsste es auch von Windows 
sofort erkannt werden.
"A..." schaltet eine LED an PQ0 an und aus
"B..." sorgt dafür, dass der Controller ca. einmal pro Sekunde "Hallo 
Welt! " an den Host schickt.

Sollte so direkt laufen. Falls nicht, ist was anderes faul.

VG

: Bearbeitet durch User
von Michael D. (sirs)


Lesenswert?

HI

Kompiliert an meinem Rechner (mit der avr-gcc toolchain) und ausgetestet 
mit meinem normalen Windows und einem anderen -> unbekanntes Device mit 
PID und VID 0000. Ein Ubuntu zum Testen hat gar nix angezeigt.

Oeffnen des Projekts mit einem Atmel Studio 7 im Rechnerraum der Uni hat 
nicht funktioniert: "The project 'USB_ganz_einfach' contains a device 
'ATxmega128A1U' which is not supported by Atmel Studio." Klingt 
vertraut. An irgendeinem Rechner hats dieses Board aber schon mal 
erkannt. Bin mal suchen....

von Michael D. (sirs)


Lesenswert?

Okey, der Rechner kanns jetzt. Erkennt das Board und kann es 
programmieren. Hab nur "#define F_CPU 30000000UL" und "#define F_CPU 
42000000UL" dazugebaut, weil er mich da angemeckert hat. Aber auch hier 
tritt der Fehler wieder auf - unknown device, PID und VID wie im Bild 
Null.

Edit wegen der Nachfrage: Das Board mit dem atxmega128A1U hab ich so bei 
Atmel gekauft, USB fuer Debugger und USB fuer Target (zwei verschidene 
Stecker) waren schon drauf. Strom is genug da, kann da auch Leds dran 
betreiben und schalten.

--> Was auch immer faul ist: Es nervt und ich hab keinen Schimmer woran 
es liegt....

: Bearbeitet durch User
von woodym (Gast)


Lesenswert?

muß das nicht
#define F_CPU 32000000UL
sein?

und usb läuft mit 48000000

von Michael D. (sirs)


Lesenswert?

Ja, ist 32000000UL, ändert aber auch nix.

von woodym (Gast)


Lesenswert?

Hallo,

ich habe mir mal das USB_ganz_einfach_2.zip geschnappt.
Da ich mit dem Programmers Notepad arbeite, habe ich ein Makefile dazu 
gepackt. Den Prozessor habe ich auf den ATXMEGA32A4U eingestellt (den 
habe ich hier gerade liegen) und das ganze übersetzt.
Beim Übersetzten noch kleine Anpassungen (der 32A4U hat keinen Port Q).
Programmieren und Läuft.

Ich kann also nicht nachvollziehen das es sich um ein problem im 
Quellcode handelt.

Ich vermute das die Aktuallisierung des Compilers nicht vollständig ist.
Ich habe das übrigens ähnlich gemacht.... Habe mir eine Kopie meines 
WINAVR angelegt, das Studio installiert und einfach den Compiler mit 
allen includes und Libs in WINAVR reinkopiert.

bye woodym

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

woodym schrieb:
> Ich vermute das die Aktuallisierung des Compilers nicht vollständig ist.

Woraus schlussfolgerst du dieses?

Wenn der Compiler den Chip kennt, dann muss er dafür auch korrekten
Code übersetzen können.  Wenn er ihn nicht kennt, geht einfach gar
nichts (hatten wir ja am Anfang).

von woodym (Gast)


Lesenswert?

hallo,
das schlussfolger ich daraus das der code 1:1 ( portmodifikation 
ausgenommen ) bei mir funktioniert ;-)

und aus dem umstand das ich am anfang auch probleme hatte  bei der 
aktuallisierung des gcc. ich hatte es am anfang versucht mit "schauen 
wir uns die änderungen an". das komplette rüber kopieren hatte da erst 
funktioniert.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

woodym schrieb:
> das schlussfolger ich daraus das der code 1:1 ( portmodifikation
> ausgenommen ) bei mir funktioniert ;-)

Eine sehr gewagte Schlussfolgerung.

von Michael D. (sirs)


Lesenswert?

Hab jetzt ein Beispiel von einem Kollegen bekommen. Ich kann damit 
zumindest (über python und das ASF) mich mit dem µC unterhalten. Nicht 
ganz das was ich wollte (eine Konsole wäre besser gewesen, will 
SCPI-Befehle schicken), aber besser als nix.

Das kompiliert einwandfrei, meldet sich unter dem angegebenen 
Device-Namen und so. Keine Ahnung was das Problem mit euerem Programm 
war, aber das jetzt geht ohne Probleme. Rätselhaft.

(Ich werd es euch wohl nicht schicken, nicht dass der Kollege was 
dagegen hat.)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Michael D. schrieb:
> Keine Ahnung was das Problem mit euerem Programm war

Du weißt: Bugs, die man nicht aufgeklärt hat, tauchen genau dann wieder
auf, wenn man es am wenigsten gebrauchen kann …

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Hallo an alle,
ich habe die USB-CDC Version auch bei mir problemlos zum laufen 
gebracht.
Bei meiner Anwendung benötige ich in 2 Punkten Hilfe, bzw. Ideen.

Vergrößern der Buffer von CDC_TXRX_EPSIZE auf 256Bytes.
Meine Packete sind ca. 200Bytes lang. Muss ich mir einen weiteren Buffer 
bauen oder kann ich das relativ einfach mit der bestehenden Struktur 
lösen?

Wie versende ich das Ganze als stream (fdev_setup_stream(stream, 
_usb_putc, _usb_getc, _FDEV_SETUP_RW);) Mit USB habe ich bisher noch 
nichts gemacht,d aher bitte ich um ein paar Ideen.

Danke

von Christoph M. (chrito)


Lesenswert?

Martin J. schrieb:
> Vergrößern der Buffer von CDC_TXRX_EPSIZE auf 256Bytes.
> Meine Packete sind ca. 200Bytes lang.

Hallo Martin, soweit ich weiß darf der Puffer maximal 64 Byte groß sein, 
das ist bei USB so. D.h. du müsstest deine 200 Bytes in kleinere 
Päckchen segementieren und auf der Gegenseite wieder zusammenbauen.

VG
Christoph

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Martin J. schrieb:
> Wie versende ich das Ganze als stream (fdev_setup_stream(stream,
> _usb_putc, _usb_getc, _FDEV_SETUP_RW);) Mit USB habe ich bisher noch
> nichts gemacht,d aher bitte ich um ein paar Ideen.

usb_putc wird ja von der Applikation mit einzelnen Bytes aufgerufen.
Sinnvoll ist es, innerhalb von usb_putc dann die Bytes erstmal zu
sammeln, entweder bis die 64 Bytes für die maximale Endpoint-Größe
zusammen sind, oder bis ein Timeout verstrichen ist.

Letztlich arbeiten auch fertige USB-Seriell-Wandler (wie FTDI) nach
dieser Methode, nur dass man bei denen vom Host aus den Timeout
konfigurieren kann.

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Hallo,
hab mir das ganze jetzt so aufgebaut.
Gibt es irgendwo ein Register, wo ich sehe, ob eine USB Verbindung da 
ist? Bzw. kann ich eigentlich einfach kontinuierlich Daten in den 
Sendebuffer legen auch wenn keine Verbindung aufgebaut ist?
Grüße Martin

von Bernd K. (prof7bit)


Lesenswert?

Martin J. schrieb:
> Gibt es irgendwo ein Register, wo ich sehe, ob eine USB Verbindung da
> ist?

Spätestens alle 1ms müsste ein SOF Token ankommen, wenn das ausbleibt 
hat jemand den Stecker gezogen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Martin J. schrieb:

> Gibt es irgendwo ein Register, wo ich sehe, ob eine USB Verbindung da
> ist?

Nein, die Hardware kann das nicht tracken.

Die Software könnte schon.  Erstens weiß sie, wann sie eine Adresse
zugeteilt bekommen hat und die anderen Standard-Requests des Hosts
so durchgelaufen sind.  Davon weißt du allerdings erstmal nur, dass
der Host das USB-Gerät überhaupt eingebunden hat.  Deshalb muss es
noch lange keinen geben, der sich auf dem Host auch für das Gerät
interessiert (Layer 7 gewissermaßen).

Um herauszufinden, ob es auch eine Applikation gibt, die den Port
geöffnet hat, benutzen wir folgendes:
1
         case SET_CONTROL_LINE_STATE:
2
            DTE_present = (pRequest->wValue & 1) != 0;
3
            break;

(Keine Ahnung, wo das codemäßig in den Xmega-Treiber passt, es
sollte genügen, eine Idee dafür zu bekommen.)

> Bzw. kann ich eigentlich einfach kontinuierlich Daten in den
> Sendebuffer legen auch wenn keine Verbindung aufgebaut ist?

Natürlich nicht.

von martin (Gast)


Lesenswert?

Hallo,
hat es schon jemand geschafft den CDC-USB-Treiber unter 4K Programmgröße 
zu bekommen, so dass man den auch als bootloader mit verwenden könnte?

Im Netz gibt es zwar verschiedene Versionen von USB Bootloadern aber die 
basieren meist auf dem ASF und sind zu groß oder verwenden kein CDC und 
benötigen so einen speziellen Treiber.

von woodym (Gast)


Lesenswert?

jup... Guckst du da:

/* XBoot Extensible AVR Bootloader 
*/
/* 
*/
/* tested with ATXMEGA64A3, ATXMEGA128A1, ATXMEGA256A1, ATXMEGA32A4 
*/
/* 
*/
/* xboot.c 
*/
/* 
*/
/* Alex Forencich <alex@alexforencich.com> 
*/
/* 
*/
/* Copyright (c) 2010 Alex Forencich 
*/
/* 
*/

Ich kann dir leider die Dateien nicht geben (von hier aus kann ich das 
nicht Zippen), aber den Auszug aus dem header habe ich. Nach dem sollte 
man suchen können ;-)

bye woodym

von woodym (Gast)


Lesenswert?

hier habe ich sogar gleich den entsprechenden link:

https://github.com/alexforencich/xboot

bye woodym

von martin (Gast)


Lesenswert?

xboot ist doch aber nur ein UART, I2C und kein USB bootloader.
jedenfalls gibt es unter github keine USB Version.

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

leider sehe ich in dem USB treiber noch nicht genug durch, aber wie 
könnte man den so reduzieren, dass es ein CDC-USB treiber bleibt, aber 
kleiner als 4K benötigt.

momentan ist der Treiber CDC-USB mit einer einfachen Echo-Funktion 5,8K 
groß

von woodym (Gast)


Angehängte Dateien:

Lesenswert?

du hast vollkommen recht, der ist ohne usb....
aber der hier ist mit ;-)

ich konnte es doch von der ferne zippen.

bye woodym

von woodym (Gast)


Lesenswert?

Vielleicht noch ein paar Infos hierzu.
Ich verwende den auf dem 32A4U erfolgreich. Beim Programmieren muß man 
alles beachten was beim Programmieren des Bootloaders nötig ist (ich 
meine das muß man extra angeben).

Es gibt die Möglichkeit eine LED zu definieren die dann schön flackert 
wenn Daten übertragen werden. Um den Bootloader beim start zu aktivieren 
benötigt man irgend einen Eingangspin der sagt das der Bootloader 
angesprochen werden soll (z.B. Taster).

Wenn ich mich noch recht erinnere kann der Bootloader nicht den 
Bootloader-bereich selber Programmieren.

bye woodym

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Vielen Dank. bin schon dabei.
wie bekommst du den in den Speicher?
Wenn ich es compiliere bekomme ich eine Größe von 4240 bytes, es ist 
somit zugroß :-(

von woodym (Gast)


Lesenswert?

ich habe das mit gcc 4.7 oder 4.9 übersetzt, da passt es rein.

von woodym (Gast)


Lesenswert?

Da ich das nur einfach (ohne aufräumen) gezipt habe, sollten da sogar 
die fertigen Objekte drin sein. Das sollte reinpassen.

bye woodym

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Ich baue es mit dem aktuellen AVR Studio.
der Unterschied war, bei dir im Makefile ist noch folgende Option drin.

ifeq ($(MAKE_BOOTLOADER), yes)
LDFLAGS += -nostartfiles
endif

Was bedeutet das?

von woodym (Gast)


Lesenswert?

Wenn ich das noch richtig weiß, bedeutet diese Option das die normale 
Initialisierung nicht stattfinden soll.

Jedes normale Programm bekommt eine kleine Initialisierung davor 
gepackt. Da werden Vektoren gesetzt, Bereiche in den Speicher kopiert 
und Variablen initialisiert. Für den Bootloader wird das nicht zwingend 
benötigt wenn bei der Umsetzung bereits das alles berücksichtigt wurde. 
Das spart dann ein paar hundert Bytes ein.

bye woodym

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

Danke danke.
Es funktioniert wunderbar!!
Lange gesucht, selber probiert und jetzt endlich eine Lösung :-)
Noch ein paar kleine Erweiterungen reinpacken und schon ist's perfekt.

Noch eine Frage, das lesen (0,03s) und schreiben(1,34s) geht ja richtig 
schnell, warum ist das verifying(22,74) dagegen so relativ langsam?

: Bearbeitet durch User
von woodym (Gast)


Lesenswert?

das würde mich auch interessieren ;-)

eventuell werden hier die Datenpakete anders gesendet und das ist dann 
weniger optimal.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.