Forum: Mikrocontroller und Digitale Elektronik Display und MMC Karte an SPI


von Michael (Gast)


Lesenswert?

gutn abend

ich habe an meinem atmega ein nokia 6100 display mit der glcd
bibliothek angeschlossen, funktioniert auch alles toll, jetzt wollte
ich noch über die spi eine mmc karte mit der bibliotjekl von ulrich
radig einbinden, die beiden verstehen sich aber nicht, beim
initialisieren hängt der atmel irgendwie, er reagiert nicht mehr auf
tasteneingaben und so
aus der bibliothek von ulrich habe ich die spi initialisierung
entfernt, da sie ja sonst dioppelt initialisiert wird und da wurde das
display dann immer gleich schwarz...
angeschlossen ist die mmc richtig, habe ich ueberprueft, cs von der mmc
hängt an portb 4, cs des display an portb 0

hat jemand von euch eine idee

von Hagen (Gast)


Lesenswert?

Als erstes solltest du davon ausgehen das immer nur die MMC oder GLCD
das SPI benutzt. Eine der beiden Libraries sollte vor und nach dem
Zugriff das SPI auf die nötigen Werte initialisieren, sprich
Geschwindigkeit, Polarität usw. Zb. könntest du alle MMC Routinen so
umschreiben das du vor deren Nutzung das SPI einstellst, und nach deren
Nutzung wieder zurückstellst wie es die GLCD benötigt.

Das ChipSelect Signal in der GLCD wird autom. durch den
Interrupthandler zurückgestzt, Für die MMC Library kannst du das selbe
machen. Öffne Datei glcd00.S und hier:

// glcd library
        .section .text

// SPI Interrupt to reset CS of LCD and deactivate SPI

#if defined (SIG_SPI)
        .global  SIG_SPI
SIG_SPI:
#else
#error "This chip has no SPI interrupts defined"
#endif
        sbi     LCD_PORT, LCD_CS
        cbi     LCD_SPCR, SPE
-->> hier MMC-CS Signal zurücksetzen
        reti

Die Art und weise wie das SPI in der GLCD benutzt wird ist sehr
speziell. Um mehr Rechenzeit zu bekommen wartet die GLCD beim Senden
über das SPI nicht auf dessen fertige Übertragung. Stattdessen benutzt
sie den SPI Interrupt um das CS Signal bei Fertigstellung
zurückzusetzen. Vor dem Senden von Daten über das SPI wartet die
Routine nun darauf das das CS Signal inaktiv wird. Somit wird
sichergestellt das das nächste Byte erst bei fertiger Übertragung
gesendet wird. Nun, auf einem 16 MHz AVR mit 8Mhz SPI werden somit 16
CPU Takte an Zeit gespart, da nun der AVR dieses 16 takte NICHT warten
muß nach dem Senden eines Bytes. In dieser Zeit kann er schon die
nächsten Datenbytes aus dem Speicher holen oder zB. Berechnungen
durchführen.

Ich selber bastle zur Zeit an einer eigenen MMC Library. Diese wird
dann exakt wie die GLCD vorgehen.

gruß Hagen

von Michael (Gast)


Lesenswert?

wann wird den etwa deine mmc lib fertiggestellt,dann könnte ich eventuel
bis dahin warten

von Hagen (Gast)


Lesenswert?

Die MMC Basisroutinen sind schon fertig, und ich erreiche mit einem 16
MHz ATMega162 ca. 400Kb/sec an Lesegeschwindigkeit.
Allerdings habe ich diese Routinen noch nicht so angepasst das sie
mitder GLCD zusammen funktioniert. Für mein aktuelles Projekt benötige
ich dies auch nicht.

Wann ich aber die MMC Ansteuerung komplett fertig bekomme weiß ich
jetzt noch nicht.

Es gibt da aber ein generelles Problem. Während beim LCD das CS Signal
durchaus jederzeit deaktiviert werden darf, darf dies bei einer MMC/SD
Karte aber nicht erfolgen. Das CS Signal bei MMC/SD Karten sollte
solange wie man Daten senden/lesen will im kompletten aktviert bleiben.
Die GLCD deaktiviert es aber nach jedem gesendeten Byte.

Gruß hagen

von Hagen (Gast)


Lesenswert?

Ich habe das jetzt nochmal genauer angeschaut.

Die GLCD steuert so an:

SPCR = (1 << MSTR) | (1 << CPOL) | (1 << CPHA) | (1 << SPIE)
SPSR = (1 << SPI2X)

wobei aber nach der Übertragung eines Bytes durch den Interrupthandler
automatisch

SPCR &= ~(1 << SPE)

gesetzt wird, also das SPI deaktiviert wird. Die Senderoutinen der GLCD
warten aber solange bis eben SPE in SPCR gelöscht wurde. D.h. sie senden
nur wenn das SPI deaktiviert ist.

Die MMC wird im allgemeinen aber so initialisiert:

SPCR = (1 << MSTR) | (1 << SPE)
SPSR = (1 << SPI2X)

D.h. also andere Polarität, sofortige und dauerhafte Aktivierung des
SPI und keinen Interrupthandler !!

Du könntest nun versuchen die GLCD leicht abzuändern:

#if defined (SIG_SPI)
        .global  SIG_SPI
SIG_SPI:
#else
#error "This chip has no SPI interrupts defined"
#endif
        sbi     LCD_PORT, LCD_CS
//        cbi     LCD_SPCR, SPE   <- auskommentieren
        reti

        .global glcdDispSend
glcdDispSend:                           // used T1 as SPI transmission
data, T flag indicate 9'th MSB
//        sbic    LCD_SPCR, SPE           // don't touch
D0,D1,T0,__tmp_reg__,__zero_reg__
//        rjmp    glcdDispSend        auskommentieren, statt dessen
// auf inaktives CS warten

        sbis    LCD_PORT, LCD_CS
        rjmp    glcdDispSend
        cbi     LCD_SPCR, SPE

        cbi     LCD_PORT, LCD_CS
        cbi     LCD_PORT, LCD_SCL
        cbi     LCD_PORT, LCD_SDA
        brts    glcdDispSend1
        sbi     LCD_PORT, LCD_SDA
glcdDispSend1:
        sbi     LCD_PORT, LCD_SCL
        sbi     LCD_SPCR, SPE
        out     LCD_SPDR, T1
        ret


        .global glcdDisplayRead         // used T0 stack 0 return
P0H:P0L:P1H:P1L
glcdDisplayRead:
        mov     T0, P0

        .global glcdDispRead
glcdDispRead:
        clr     P1L
        clr     P1H
        clr     P0L
        clr     P0H
glcdDispRead1:
//        sbic    LCD_SPCR, SPE           // wait for SPI finished
//        rjmp    glcdDispRead1

        sbis    LCD_PORT, LCD_CS
        rjmp    glcdDispRead1
        cbi     LCD_SPCR, SPE

        cbi     LCD_DDR, LCD_SDA
        cbi     LCD_PORT, LCD_CS
glcdDispRead2:
        cbi     LCD_PORT, LCD_SCL
        add     P1L, P1L
        adc     P1H, P1H
        adc     P0L, P0L
        adc     P0H, P0H
        sbi     LCD_PORT, LCD_SCL
        sbic    LCD_PIN, LCD_SDA
        inc     P1L
        dec     T0
        brne    glcdDispRead2
        sbi     LCD_PORT, LCD_CS
        sbi     LCD_DDR, LCD_SDA
        ret

D.h. statt nun nach jeder Übertragung eines Bytes das SPI in der ISR zu
deaktivieren wird dort nur das CS des LCD's deaktiviert. In den beiden
obigen Sende/Lese Routinen wird nicht gewartet bis SPE = 0 ist sondern
bis CS = 1 ist. Das Problem kommt daher das das LCD eine 9 Bit serielle
Datenübertragung benötigt, d.h. das erste Bitwird manuell in Software
übertragen. Damit dies funktioniert MUSS das SPI deaktiviert sein.

In den MMC Routinen muß man also EXPLIZIT vor jedem Zugriff das SPI
auf

SPCR = (1 << MSTR) | (1 << SPE)

setzen, und bevor man auf's LCD zugreift auf

SPCR = (1 << MSTR) | (1 << CPOL) | (1 << CPHA) | (1 << SPIE)

setzen. Den letzen Schritt würde ich bei der Deaktivierung der MMC
Übertragung durchführen.

Gruß Hagen

von Michael (Gast)


Lesenswert?

mit den ändrungn gehts nicht
habe den mmc kram rausgelassen und nur die änderungen an der glcd lib
gemacht, das display bleibt jetzt von anfang an komplett schwarz

von Hagen (Gast)


Lesenswert?

Hm, das ist der Unterschied zwischen Theorie und Praxis ;)

Mit der originalen GLCD ist es nur wichtig das die MMC Routinen vor dem
Zugriff

SPCR = (1 << MSTR) | (1 << SPE)

setzen, und nach dem Zugriff

SPCR = (1 << MSTR) | (1 << CPOL) | (1 << CPHA) | (1 << SPIE)

setzen. Dann dürfte es funktionieren.
Leider ist es ohne große Änderungen sehr schwierig die GLCD zu ändern.
Dies liegt eben an dem 9Bit SPI das per Software 1Bit und Hardware 8Bit
überträgt.
Alternativ wäre es möglich die GLCD so umzubasteln das sie ohne
SPI-Interrupt auskommt. Dazu muß glcdDisplayInit(), glcdDisplaySend()
und glcdDisplayRead() modifiziert werden. Allerdings würde die
Performance darunter leiden, da nun bei jedem Datenbyte (9Bit) auf die
fertige Übertragung des SPI's gewartet werden muß. Sofort danach würde
man das SPI auf die Einstellungen der MMC Karte zurücksetzen.

Ich persönlich würde aber die MMC Routinen anpassen, da diese in diesem
Falle nicht so Geschwindigkeitsoptinmiert sein müssen. Soll heisen, pro
Datenbyte in der MMC Übertragung ist der nötige Overhead vor und nach
der Ansteuerung der MMC mit dem Einstellen des SPI's relativ
unkritisch.

Im Falle der GCLD mit MMC würde ich zwei Makros/Funktionen bauen:

#define MMC_SPI_ON()  while (SPCR & (1 << SPE)) do; SPCR = (1 << MSTR)
| (1 << SPE);
#define MMC_SPI_OFF() SPCR = (1 << MSTR) | (1 << CPOL) | (1 << CPHA) |
(1 << SPIE);

D.h. im normalen Modus ist das SPI für die GLCD konfiguriert, und nur
bei den Zugriffen auf die MMC wird das SPI umgestellt. Somit kann man
nur eines der beiden Geräte zu einem Zeitpunkt ansteuern.

Gruß hagen

von Michael (Gast)


Lesenswert?

okay, danke für deine ausführliche hilfe, werde ich gleich mal testen

von Michael (Gast)


Lesenswert?

das funktioniert irgendwie nicht, das Display zeigt etwas an, danach
versuche ich die mmc zu initialisieren und dann wird das Display
schwarz, bevor ich das Display wieder anspreche habe ich
SPCR = (1 << MSTR) | (1 << CPOL) | (1 << CPHA) | (1 << SPIE)
gesetzt

von Hagen (Gast)


Lesenswert?

Wie schnell nacheinander greifst du aufs Display und danach auf die MMC
zu ?

Die GLCD geht in etwa so vor:

- warte das SPI fertig ist
- sende 9Bit

Die meisten MMC Routinen gehen so vor:

- sende 8 Bit
- warte bis fertig

Diese geänderte Vorgehensweise hat mehrere Gründe und auch Vorteile.
Bei 8Mhz SPI -> 16Mhz MCU Takt, werden nach out SPDR, Data, sprich dem
senden eines Bytes über SPI, per hardware in den nächsten 16 MCU
Taktzyklen das Byte übertragen. Würde man jetzt auf die Fertigstellung
der Übertragung warten so verschenkt man 16 Taktzyklen an Rechenpower.
Innerhalb dieser 16 Taktzyklen kann aber zB. in der GLCD Funktion
glcdLine() schon komplett der nächste zu setzende Pixel berechnet
werden. Ergo die Gesamtperformance steigt. Die meisten Funktionen in
der GLCD haben so "kurze" wiederholte Berechnungsfunktionen, zB.
Bitmaps, Fontroutinen, Ellipsen etc. pp. und das Display wird bei 8Mhz
SPI auch fast mit vollen 8Mhz angesteuert. Eben aus dem Grunde weil die
Software 16 MCU Takte Zeit hat bis zumnächsten Datenbyte.


Fazit: nachdem die GLCD komplett fertig übertragen hat muß
1.) LCD_CS auf 1 sein
2.) SPE in SPCR auf 0 sein.

Du solltest also in MMC_SPI_ON() warten, etwa so:

void MMC_SPI_ON(void) {

   while ((SPCR & (1 << SPE)) != 0) do ;
   SPCR = (1 << MSTR) || (1 << SPE);
}

void MMC_SPI_OFF(void) {

   SPCR = (1 << MSTR) || (1 << CPOL) || (1 << CPHA) || (1 << SPIE);
}


Gruß Hagen

von Hagen (Gast)


Lesenswert?

oder sogar so

void MMC_SPI_ON(void) {

   if ((SPCR & (1 << SPIE)) != 0) {
     while ((SPCR & (1 << SPE)) != 0) do ;
   }
   SPCR = (1 << MSTR) || (1 << SPE);
}

Gruß hagen

von Michael (Gast)


Lesenswert?

ich greife eigentlcih staendig im Wechsel auf beide zu.
der MCU Takt ist 8 Mhz

von Michael (Gast)


Lesenswert?

ich greife eigentlcih staendig im Wechsel auf beide zu.
der MCU Takt ist 8 Mhz.
bei dem letzen meckert der compiler...

von Michael (Gast)


Lesenswert?

ich habe nochmal getestet, wenn ich die befehle so ausfuehre geht es,
allerdings habe ich nicht versucht auf die mmc zuzugreifen, das werde
ich gleich nochmal testen, dabei ist er immer abgestuerzt

von Michael (Gast)


Lesenswert?

also die initialisierung funktioniert schonmal, scheint also vorran
zugehen. vielen dank, dadran hätte ich alleine bestimmt mehrere wochen
gesucht :-)

von Alexander Höller (Gast)


Lesenswert?

Hallo,

die MMC Lib. unterstützt doch eh auch Software SPI  ... das LCD also
ans Hardware SPI und die MMC an "normale" I/O Pins mit Software SPI
!

mfG
aleX

von Hagen (Gast)


Lesenswert?

Naja,man könnte auch die GLCD per Soft-SPI ansteuern, oder sogar beides.
Nur, wenn man schon Hardware-SPI zur Verfügung hat,und damit leben kann
das immer nur ein Gerät zu einem Zeitpunkt angesteuert werden kann,
dann sollte man auch beides mit Hardware ansteuern.

Ich vermute mal das Michael mit obigen Vorschlägen schon weiterkommen
wird.

Gruß Hagen

von Michael (Gast)


Lesenswert?

Software SPI hört sich finde ich langsam an, zusätzlich kommt noch ein
mp3 decoder mit dran und das alles per software spi, der arme atmega

von Michael (Gast)


Lesenswert?

die obigen vorschläge funktionieren bis jetzt soweit, das die mmc karte
initialisiert wird, lesen und schreiben geht noch nicht, aber das
schaffe ich mit den tipps von oben sicherlich bald

von Alexander Höller (Gast)


Lesenswert?

@ Michael:
Mal ne ganz andre Frage: Welche MMC/SD Karte verwendest du? Ich hab die
Lib. von ulrich radig mal ausprobiert mit ner SanDisk 128MB SD ... und
ich konnte die Karte einfach nicht initialisieren.

mfg
aleX

von Michael (Gast)


Lesenswert?

ich verwende eine 256MB Karte von Reichelt. Die billigste die es dort
gab.

von Hagen (Gast)


Lesenswert?

Es gibt da einige Punkte mit den MMC/SD Karten die beachtet werden
müssen. Hier mal ein Ausszug aus meiner Ansteuerung, da kann man sehen
das ich einiges anders mache. Eine SanDisk 256MB Karte funktioniert
damit, AUCH direkt nach einem Powerup. SanDisk Karten können bis zu
500ms Timeouts erzeugen, deshalb sollte man die Commandos bei Mißerfolg
immer mehrmals senden.

Gruß Hagen

uint8_t mmcInit(void) {

    MMC_PORT |= (1 << MMC_CS);
    MMC_DDR |= (1 << MMC_CS) | (1 << MMC_SCLK) | (1 << MMC_MOSI);
    MMC_DDR &= ~(1 << MMC_MISO);
    SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
    SPSR = 0;

    uint8_t result = mmcCommandExpected(0x40, 0x0101);
    if (result <= 0x01) {
      for (uint8_t i = 20; i > 0; i--) {
        result = mmcCommand(0x41);
        if (result != 0x01) break;
      }
      if (result == 0x00) {
        SPCR = (1 << SPE) | (1 << MSTR);
        SPSR = (1 << SPI2X);
        #ifdef MMC_CALC_CRC7
        result = mmcCommandParam(0x7B, 1, 0x0000);  // activate CRC
checking
        #endif
        if (result == 0) {
          result = mmcReadCSD(MMC_Buffer);

        }
      }
    }
    mmcDisable();
    return(result);
}


/*

  Follow the equivalent C code of assembler procedures in file
mmc_spi_asm.S
  The assembler is more efficient as the compiled C source on WinAVR
GCC.

#define mmcEnable()         MMC_PORT &= ~(1 << MMC_CS)
#define mmcDisable()        MMC_PORT |= (1 << MMC_CS)
#define mmcRead()           SPDR
#define mmcSend(data)       SPDR = data
#define mmcWait()           while (!(SPSR & (1 << SPIF)))


#ifdef MMC_CALC_CRC7
uint8_t mmcCRC7(uint8_t crc, uint8_t data) {

    crc ^= data;
    unsigned char poly = 0x89;
    for (unsigned char i = 8; i > 0; i--) {
      if ((crc & 0x80) != 0) crc ^= poly;
      crc <<= 1;
    }
    return(crc);
}
#endif

uint8_t mmcCommand(uint8_t command) {

    return(mmcCommandParam(command, 0, 0));
}

uint8_t mmcCommandExpected(uint8_t command, uint16_t expected) {

    return(mmcCommandParam(command, 0, expected));
}

uint8_t mmcCommandParam(uint8_t command, uint32_t address, uint16_t
expected) {

    mmcDisable();
    mmcSend(data);

    uint8_t crc = 0;
#ifdef MMC_CALC_CRC7
    crc = mmcCRC7(crc, command);
    crc = mmcCRC7(crc, (uint8_t)(address >> 24));
    crc = mmcCRC7(crc, (uint8_t)(address >> 16));
    crc = mmcCRC7(crc, (uint8_t)(address >> 8));
    crc = mmcCRC7(crc, (uint8_t)(address));
    crc = crc | 1;
#else
    crc = 0x95;
#endif

    uint8_t result;
    uint8_t data = 0xFF;

    mmcWait();
    mmcEnable();
    for (uint8_t i = MMC_COMMAND_TRIALS; i > 0; i--) {
      mmcSend(command);
      mmcWait();
      mmcSend((uint8_t)(address >> 24));
      mmcWait();
      mmcSend((uint8_t)(address >> 16));
      mmcWait();
      mmcSend((uint8_t)(address >> 8));
      mmcWait();
      mmcSend((uint8_t)(address));
      mmcWait();
      mmcSend(crc);
      mmcWait();

      for (uint8_t j = MMC_RESPONSE_TRIALS; j > 0; j--) {
        mmcSend(data);
        mmcWait();
        result = mmcRead();
        if ((result & 0x80) == 0) break;
      }

      if ((result >= (uint8_t)(expected >> 8)) && (result <=
(uint8_t)(expected))) break;

      mmcDisable();
      mmcSend(data);
      for (uint8_t j = MMC_COMMAND_DELAY -1; j > 0; j--) {
        mmcWait();
        mmcSend(data);
      }
      mmcWait();
      mmcEnable();
    }
    return(result);
}

uint8_t mmcReadData(uint16_t size, uint8_t *buffer) {
// the parameter order of size and buffer is choosen this way for
better assembler optimization

    uint8_t result;
    uint8_t data = 0xFF;

    mmcSend(data);
    for (uint16_t i = MMC_READ_TRIALS -1; i > 0; i--) {
      mmcWait();
      result = mmcRead();
      mmcSend(data);
      if (result == 0xFE) goto DoRead;
    }
    return(0xFF);

  DoRead:;
    for (; size > 0; size--) {
      mmcWait();
      result = mmcRead();
      mmcSend(data);
     *buffer++ = result;
    }
    mmcWait();             // skip 16Bit CRC
    mmcRead();
    mmcSend(data);
    mmcWait();
    mmcRead();
    return(0);
}

uint8_t mmcReadSector(uint32_t sector, uint8_t *buffer) {

    uint8_t result = mmcCommandParam(0x51, sector * 512, 0x0000);
    if (result == 0x00) result = mmcReadData(buffer, 512);
    mmcDisable();
    return(result);
}

uint8_t mmcReadBuffer16(uint8_t command, uint8_t *buffer) {

    uint8_t result = mmcCommand(command);
    if (result == 0x00) result = mmcReadData(16, buffer);
    mmcDisable();
    return(result);
}

von Alexander Höller (Gast)


Lesenswert?

@Hagen:

Ich weiß, langsam kommen wir echt vom eigentlichen Thema ab. Aber
könntest du mir eventuell deinen kompletten MMC/SD Source schicken?
Kann ihn zwar derzeit leider nicht testen (beim Transport ein Teil der
Printplatte komplett zerstört ;( ) aber sieht echt gut aus ... vorallem
weil es auch mit CRC Check funktioniert.

Wäre nett, wenn dus mir (mit nem kleinem Beispiel? g) an meine eMail
Adresse schicken könntest.

mfG
aleX

von Hagen (Gast)


Angehängte Dateien:

Lesenswert?

Ok, ich habe das mal angehangen. ATMega162 16MHz mit externem 32Kb SRAM,
3.3V, direkt MMC/SD Slot angeschlossen, 3 LEDs und UART zum Debuggen.

Ich veröffentliche das nur als Studie und möchte eigentlich KEINEN
Support oder so was dazu geben. Es funktioniert alles nur wäre es schön
wenn ihr euch erst mal selber durchbeissen würdet. Ihr kennt das Problem
mit der ZEIT ja selber.

Gruß Hagen

PS: Kommentare sind vorhanden, aber auch nur das nötigste.

von Alexander Höller (Gast)


Lesenswert?

Vielen Dank ...

... ich verwend den Code sowieso nur als "Vorlage" ... is für mein
Abschlussprojekt - da muss ich eh alles selber machen ;p

Danke nochmal,
aleX

von Georg S. (Gast)


Lesenswert?

Gibt es inzwischn irgendwo eine Implementierung wo eine MMC und ein
Nokiadisplay am selben Controller hängt?

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.