Forum: Mikrocontroller und Digitale Elektronik LPC1768: SSP per DMA auf verschiedene Slaves


von Random .. (thorstendb) Benutzerseite


Lesenswert?

Moin Leutz,

wer kennt sich mit dem LPC1768 aus?

Am SSP0 hängen in meiner Schaltung drei DACs. Jeder DAC braucht ein 
eigenes /CS, und hat zwei Kanäle (werden über die oberen Bits im DATA 
ausgewählt).

Im Moment habe ich den SSP so aufgesetzt, dass ich die /CS per 
Pin-toggle selbst generiere, und dazwischen jeweils die Daten an das SSP 
Data Register schicke.

Problem an dem ganzen ist, dass das Laden des Data Registers (also die 
Spanne vom /CS down zum ersten CLK) fast genau so lange dauert, wie das 
senden der 16Bits.
Dementsprechend viel Luft habe ich zwischen den Paketen, dementsprechend 
viel Rechenleistung bzw. -verschwendung habe ich am Proz.


Ziel wäre es, das ganze per DMA zu erledigen. Allerdings müsste dann das 
SPI oder DMA Peripheral die Erzeugung der richtigen /CS übernehmen, da 
ich dem DMA ja nur einen Datensatz aus 6x 16Bit übergeben möchte.


Wer hat Erfahrung mit DMA und da einen guten Tipp für mich?
Vielen Dank!


VG,
/th.

von Jim M. (turboj)


Lesenswert?

> Problem an dem ganzen ist, dass das Laden des Data Registers (also die
> Spanne vom /CS down zum ersten CLK) fast genau so lange dauert, wie das
> senden der 16Bits.

Das klingt nicht logisch, denn 16 Bits dauern mindestens 32 Takte bei 
SSP Master. Zeig doch mal den Code.

von Random .. (thorstendb) Benutzerseite


Angehängte Dateien:

Lesenswert?

1
        switch(ch) {
2
          case DAC_A:
3
            LPC_GPIO0->FIOCLR = (1 << CSAB);
4
            LPC_SSP0->DR = (data & 0x0fff) | DAC_A;
5
            while (LPC_SSP0->SR & SSP_BSY);
6
            LPC_GPIO0->FIOSET = (1 << CSAB);
7
            break;
8
          
9
          case DAC_B:
10
            LPC_GPIO0->FIOCLR = (1 << CSAB);
11
            LPC_SSP0->DR = (data & 0x0fff) | DAC_B;
12
            while (LPC_SSP0->SR & SSP_BSY);
13
            LPC_GPIO0->FIOSET = (1 << CSAB);
14
            break;
15
          
16
          case DAC_C:
17
            LPC_GPIO0->FIOCLR = (1 << CSCD);
18
            LPC_SSP0->DR = (data & 0x0fff) | DAC_A;
19
            while (LPC_SSP0->SR & SSP_BSY);
20
            LPC_GPIO0->FIOSET = (1 << CSCD);
21
            break;
22
          
23
          case DAC_D:
24
            LPC_GPIO0->FIOCLR = (1 << CSCD);
25
            LPC_SSP0->DR = (data & 0x0fff) | DAC_B;
26
            while (LPC_SSP0->SR & SSP_BSY);
27
            LPC_GPIO0->FIOSET = (1 << CSCD);
28
            break;
29
          
30
          case DAC_E:
31
            LPC_GPIO0->FIOCLR = (1 << CSEF);
32
            LPC_SSP0->DR = (data & 0x0fff) | DAC_A;
33
            while (LPC_SSP0->SR & SSP_BSY);
34
            LPC_GPIO0->FIOSET = (1 << CSEF);
35
            break;
36
          
37
          case DAC_F:
38
            LPC_GPIO0->FIOCLR = (1 << CSEF);
39
            LPC_SSP0->DR = (data & 0x0fff) | DAC_B;
40
            while (LPC_SSP0->SR & SSP_BSY);
41
            LPC_GPIO0->FIOSET = (1 << CSEF);
42
            break;
43
          
44
          default:
45
            break;
46
        }

von Jim M. (turboj)


Lesenswert?

Der Receiver FIFO wird nicht geleert in Deinem Code. Das könnte Probleme 
bereiten. Wenn man den gelesenen Wert nicht benötigt, genügt ein 
simples:
1
LPC_SSP0->DR;

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Danke, das werd ich mal ausprobieren.
Der recv (MISO) ist nicht angeschlossen (die DACs haben nur Data In), 
könnte das zu "irritationen" des SSP führen?

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Jim Meba schrieb:
> Der Receiver FIFO wird nicht geleert in Deinem Code. Das könnte Probleme
> bereiten. Wenn man den gelesenen Wert nicht benötigt, genügt ein
> simples:
>
1
LPC_SSP0->DR;

**noch mal ausgrab**
Leider keine Änderung des Timings, wenn ich das Register auslese.

Noch jemand eine Idee?

von Lutz (Gast)


Lesenswert?

Mangels geeigneter Interruptquelle (=> BSY) wird DMA in diesem Fall wohl 
schwierig. Es wird also so oder so in diesem Anwendungsfall irgendwo das 
Pollen von BSY erforderlich sein, da, zumindest auf den ersten Blick ins 
User Manual, BSY die einzige Informationsquelle ist, ob ein Transfer auf 
der Leitung auch abgeschlossen ist. Die ganzen FIFO-Infos (half 
full/empty, not full/empty) nützen hier ja nichts, da nach einem Frame 
CS geschaltet werden soll. Im Screenshot werden zwar die 3 DACS mit 
ihren beiden Kanälen schön nacheinander beschickt, aber das wird wohl 
nicht die Regel sein. Sonst wäre es wohl auch keine switch-Anweisung 
geworden, mit der man am Flexibelsten ist. Auch "von hinten aufzäumen" 
geht nicht, da es anscheinend auch keine Interruptquelle "RX FIFO not 
empty" oder so gibt. Das ganze scheint so konzipiert zu sein, daß man 
einen einmal angeschobenen Transfer immer weiter mit Daten befüttert 
kann. Oder anders ausgedrückt, scheint Multislave-Betrieb nicht wirklich 
effektiv  unterstützt zu sein.

Wenn ich das richtig interpretiere, sind 2, 3 und 4 im Screenshot die 
CS-Pins. In der Tat scheint nach dem CS-LOW-Signal eine halbe Framelänge 
zu vergehen, bis die Daten losgehen. Ein paar Takte für das Berechnen 
und Laden des DR sind erforderlich; das ist klar. Eine halbe Framelänge 
dürfte aber viel zu viel sein.
Was ich aber wirklich komisch finde, ist daß auch nach dem Senden wieder 
eine halbe Framelänge vergeht, bis CS wieder high geht. Eigentlich kann 
das ja nur daran liegen, daß die Daten erst so spät aus dem DR gesendet 
werden oder das Pollen des BSY dafür verantwortlich ist. Da diese halbe 
Framelänge Verzögerung aber sowohl vor als auch nach dem Senden 
auftritt, würde ich das BSY als direkte Ursache ausschließen (wird vor 
dem Senden ja gar nicht abgefragt).
Es scheint also so, daß das SSP0 die Daten aus irgendeinem Grund erst 
sehr spät rausgibt und (wegen der ziemlich gleichen Verzögerungszeit) 
aus dem selben Grund auch erst lange danach wieder Bereitschaft 
signalisiert.

Ich würde erst mal in der Konfiguration des SSP0 forschen, wo diese 
Verzögerung herkommt.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Hi,

vielen Dank für die Antwort.

Ich habe schon alles mögliche und unmögliche bei der SPI durch, 
verschiedene Flags und Möglichkeiten getestet, aber ich krieg das ganze 
nicht klein :-)
Am besten wäre DMA, aber die SSP kann nur ein /CS...


Hier mal die config:
1
void DAC_Init(void)
2
{
3
  LPC_SC->PCONP     |= (1 << 15);            // enable power to GPIO
4
  LPC_SC->PCONP     |= (1 << 21);            // SSP0 Power
5
6
  LPC_GPIO0->FIODIR |= (1<<LDAC) | (1<<CSAB) | (1<<CSCD) | (1<<CSEF); // P0.16, P0.17 are output
7
  LPC_GPIO0->FIOPIN  = (1<<LDAC) | (1<<CSAB) | (1<<CSCD) | (1<<CSEF);
8
  
9
  LPC_GPIO3->FIODIR  |= (1<<BENCH) | (1<<DATA);
10
  LPC_PINCON->PINSEL0 &= ~(0x3ul << 30);      // PIN Select: P0.15: CLK : AF2; P0.18 : MOSI : AF2
11
  LPC_PINCON->PINSEL0 |=  (0x2ul << 30);
12
  LPC_PINCON->PINSEL1 &= ~(0x3ul <<  4);
13
  LPC_PINCON->PINSEL1 |=  (0x2ul <<  4);
14
  
15
  LPC_SSP0->CR0 = (0xf<<0) | (0<<4) | (0<<6) | (0<<7) | (0x0<<8);
16
  LPC_SSP0->CR1 = (0<<0) | (0<<1) | (0<<2) | (0<<3);
17
  LPC_SSP0->CPSR = 4;
18
  
19
  LPC_SSP0->CR1 |= (1<<1);    // Enable SSP
20
}

VG,
/th.

von Frank K. (fchk)


Lesenswert?

Random ... schrieb:

> Am SSP0 hängen in meiner Schaltung drei DACs. Jeder DAC braucht ein
> eigenes /CS, und hat zwei Kanäle (werden über die oberen Bits im DATA
> ausgewählt).

[...]

> Ziel wäre es, das ganze per DMA zu erledigen. Allerdings müsste dann das
> SPI oder DMA Peripheral die Erzeugung der richtigen /CS übernehmen, da
> ich dem DMA ja nur einen Datensatz aus 6x 16Bit übergeben möchte.

Kannst Du das nicht über I2S machen? Da gibt es neben dem Bit Clock noch 
ein Frame Clock. Bit Clock und Frame Clock kannst Du in einem kleinen 
CPLD zum Erzeugen der einzelnen /CS Signale verwenden, so dass Du mit 
einem 32 Bit Stereo Sample (64 Bit) 4 16 Bit Worte ausgibts, wovon eines 
zwangsweise ein Dummywort ist, oder als Reserve für einen 4. DAC. Und 
DMA geht mit I2S auf jeden Fall.

Ist zwar nicht im Sinne des Erfinders, sollte aber problemlos 
funktionieren.

fchk

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Lutz schrieb:
> Ein paar Takte für das Berechnen
> und Laden des DR sind erforderlich; das ist klar. Eine halbe Framelänge
> dürfte aber viel zu viel sein.

Jap, das denke ich auch.
Die CPU läuft auf 100MHz, die SSP auf 25 (/4). Ich hätte erwartet, 
zwischen dem /CS LOW und den SPI CLK, sowie dem ende des SPI 
Datenpaketes und dem /CS high kein delay sehen zu können.

Normalerweise ist das Reg schreiben doch parallel, während die SPI das 
aus dem geichen Reg seriell rausklappet (P in, S out). Aber es sieht so 
aus, als wenn die SPI das da seriell reinklappert, intern (ca. 1/4 des 
frames Vorlauf).
Der Nachlauf sieht dann so aus, als wenn die SPI ein "empfangenes" 
Datenwort wieder in das DR klappert.

Irgendwie muss man dem das doch abgewöhnen können ...

von Frank K. (fchk)


Lesenswert?

PS: Ich sehe gerade, das der LPC auch auf den SSPs einen FS unterstützt. 
Das wäre Dein Weg.

fchk

von Random .. (thorstendb) Benutzerseite


Lesenswert?

@fchk:
Danke für den Tipp, aber das würde die Sache überkomplizieren.

Da könnte man besser nen Atmel SAM3 nehmen, der kann Multislave mit DMA. 
Aber der läuft nur bei 84MHz und ich hab zusätzlich noch ziemlich viel 
Ethernet Traffic :-)

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Frank K. schrieb:
> PS: Ich sehe gerade, das der LPC auch auf den SSPs einen FS unterstützt.
> Das wäre Dein Weg.
>
> fchk

Was hat das mit dem FS auf sich`?

von Frank K. (fchk)


Lesenswert?

Random ... schrieb:
> Frank K. schrieb:
>> PS: Ich sehe gerade, das der LPC auch auf den SSPs einen FS unterstützt.
>> Das wäre Dein Weg.
>>
>> fchk
>
> Was hat das mit dem FS auf sich`?

genau wie bei I2S: den SSP im Framed-SPI Modus (SSP) laufen lassen und 
die CS-Signale aus FS und SCLK mit einem CPLD erzeugen. Du brauchst nur 
einen Zähler, der mit SCLK weiterzählt, durch eine Flanke von FS auf 0 
zurückgesetzt wird, und einen 4-aus-2 Decoder für die einzelnen 
CS-Signale. Wenn Du willst, kannst Du das auch aus einzelnen 74HC... 
zusammenbauen.

fchk

von Lutz (Gast)


Lesenswert?

Random ... schrieb:
> LPC_GPIO0->FIOPIN  = (1<<LDAC) | (1<<CSAB) | (1<<CSCD) | (1<<CSEF);
Wenn denn am gesamtem Port 0 nichts weiter ist, kein Problem. Sollte das 
Programm später mal erweitert werden, wäre FIOSET statt FIOPIN wohl 
sicherer. FIOPIN läßt bei mir immer den Alarm aufleuchten und ich 
versuche, es zu vermeiden. Hierbei aber natürlich völlig irrelevant.

Random ... schrieb:
> Normalerweise ist das Reg schreiben doch parallel, während die SPI das
> aus dem geichen Reg seriell rausklappet (P in, S out). Aber es sieht so
> aus, als wenn die SPI das da seriell reinklappert, intern (ca. 1/4 des
> frames Vorlauf).
> Der Nachlauf sieht dann so aus, als wenn die SPI ein "empfangenes"
> Datenwort wieder in das DR klappert.
Wenn es denn tatsächlich so ist, wäre das wohl ein bug ...
Kann mir aber kaum vorstellen, daß der schon so lange rumspukt. Es müßte 
sich doch aber mit dem Debugger schön durchsteppen und klären lassen.

@Frank: Das mit dem CPLD würde wohl gehen. Aber wozu hat man schon so 
ein "Schlachtschiff" am Laufen? Würde auch viel mehr Aufwand bedeuten, 
zumal es ja "eigentlich" auch ohne gehen müßte.

von Frank K. (fchk)


Lesenswert?

Lutz schrieb:

> @Frank: Das mit dem CPLD würde wohl gehen. Aber wozu hat man schon so
> ein "Schlachtschiff" am Laufen? Würde auch viel mehr Aufwand bedeuten,
> zumal es ja "eigentlich" auch ohne gehen müßte.

Es geht ja offensichtlich nicht ohne - Schlachtschiff hin oder her! 
Logikgatter sind immer schneller als Softwareschleifen, und zwar um 
Größenordnungen.

fchk

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.