Forum: Mikrocontroller und Digitale Elektronik ATXXMega128A1 und Hardware-SPI


von Bastel K. (bastel_k)


Lesenswert?

Hallo Leute,

ich habe ein Problem mit meinem XMega und der SPI-Schnittstelle. An ihr 
ist ein Beschleunigungssensor angeschlossen, der auch wunderbar 
funktioniert, wenn die Bits von Hand rein und raus shifte.

Das Protokoll ist eigentlich recht einfach. Man shiftet 8 Bits rein, die 
das auszulesende Register beschreiben und dann kann man direkt 8 Bits 
raus shiften, die dann der Inhalt des entsprechenden Registers sind.

Dieses Vorgehen funktioniert auch wunderbar mit der XMega-eigenen SPI 
oder wenn man eben das SPI softwaremäßig macht.

Allerdings möchte ich um Zeit zu sparen einen Vorteil des SPI-Protokolls 
mit dem Beschleunigungssensors nutzen. Man kann nämlich durch Setzen von 
Bit 7 der Registeradresse dem Sensor sagen, dass man gleich mehrere 
aufeinander folgende Register raus shiften will. Das heißt ein Byte rein 
shiften und so viele man will raus shiften. Durch dieses Vorgehen kann 
ich Zeit sparen um alle drei Achsen des Sensors auszulesen. Jede Achse 
hat 16 Bit, d.h. ich muss ein Byte, die Registeradresse, rein shiften 
und dann 6 raus shiften.

Mit Software-SPI geht das auch wunderbar, aber um den Overhead der 
Bitmanipulationen zu verringern und in Zukunft dann auch noch DMA zu 
benutzen, brauche ich die Hardware-SPI. Leider will die mir immer nur 
ein Register auslesen und alle anderen sind dann einfach 0. In seltenen 
Fällen klappte es sogar, aber ich weiß nicht warum, weil ich nichts am 
Code geändert habe.

Neben der Initialisierung des SPI sieht der Code vereinfacht so aus:
1
#define USE_HW_SPI
2
#define USE_INTERRUPT
3
//#define USE_WORKAROUND
4
5
#ifdef USE_INTERRUPT
6
  volatile extern uint8_t spieTransferBusy;
7
  #define SPIE_BUSY() spieTransferBusy = true;
8
  #define SPIE_WAIT_BEGIN() while (spieTransferBusy);
9
  #define SPIE_WAIT_END()
10
  #define SPIE_WAIT() while (spieTransferBusy);
11
#else
12
  #define SPIE_BUSY()
13
  #define SPIE_WAIT_BEGIN()
14
  #define SPIE_WAIT_END() while (!(SPIE.STATUS & SPI_IF_bm));
15
  #define SPIE_WAIT() while (!(SPIE.STATUS & SPI_IF_bm));
16
#endif
17
18
#ifdef USE_INTERRUPT
19
20
volatile uint8_t spieTransferBusy = false;
21
22
ISR(SPIE_INT_vect) {
23
  spieTransferBusy = false;
24
}
25
26
#endif
27
28
#ifdef USE_HW_SPI
29
    uint8_t read(char register_address) {
30
      uint8_t read_address = ACC_RW_BIT | register_address;
31
      uint8_t register_value = 0;
32
33
      port->OUTCLR = _BV(CS);
34
35
      // Darauf warten, dass SPI bereit für neue Daten ist
36
      SPIE_WAIT_BEGIN(); SPIE_BUSY();
37
      // Registeradresse rein schreiben
38
      SPIE.DATA = read_address;
39
      // Darauf warten, dass SPI bereit für neue Daten ist
40
      SPIE_WAIT(); SPIE_BUSY();
41
      // 0 rein schreiben, weil wir nur lesen wollen
42
      SPIE.DATA = 0;
43
      // Darauf warten, dass SPI bereit für neue Daten ist
44
      SPIE_WAIT();
45
      // Daten auslesen
46
      register_value = SPIE.DATA;
47
48
      port->OUTSET = _BV(CS);
49
50
      return register_value;
51
    }
52
53
    void readXYZ(int16_t &x, int16_t &y, int16_t &z) {
54
      uint8_t read_address;
55
      read_address = ACC_RW_BIT | ACC_MB_BIT | ACC_DATAX0;
56
57
58
#  ifdef USE_WORKAROUND
59
      // Register einzeln auslesen
60
      read_address = ACC_DATAX0;
61
      x = read(read_address++);
62
      x |= read(read_address++) << 8;
63
      y = read(read_address++);
64
      y |= read(read_address++) << 8;
65
      z = read(read_address++);
66
      z |= read(read_address) << 8;
67
#  else
68
      // Aufeinander folgende Register nacheinander auslesen
69
      port->OUTCLR = _BV(CS);
70
71
      SPIE_WAIT_BEGIN(); SPIE_BUSY();
72
      SPIE.DATA = read_address;
73
      SPIE_WAIT(); SPIE_BUSY();
74
      SPIE.DATA = 0; SPIE_WAIT();
75
      x = SPIE.DATA; SPIE_BUSY();
76
      SPIE.DATA = 0; SPIE_WAIT();
77
      x |= SPIE.DATA << 8; SPIE_BUSY();
78
      SPIE.DATA = 0; SPIE_WAIT();
79
      y = SPIE.DATA; SPIE_BUSY();
80
      SPIE.DATA = 0; SPIE_WAIT();
81
      y |= SPIE.DATA << 8; SPIE_BUSY();
82
      SPIE.DATA = 0; SPIE_WAIT();
83
      z = SPIE.DATA; SPIE_BUSY();
84
      SPIE.DATA = 0; SPIE_WAIT();
85
      z |= SPIE.DATA << 8;
86
87
      port->OUTSET = _BV(CS);
88
#  endif
89
    }
90
#endif
Es ist jetzt schwer einen komplett lauffähigen Code aus dem ganzen 
Projekt heraus zu nehmen. Wenn das so nicht reicht, kann ich's aber 
versuchen und das ganze auch als ZIP anhängen.

Sieht jemand irgendwo das Problem? Denn manchmal, zum Beispiel gerade 
eben, geht es ja und manchmal nicht. Wenn ich das Projekt nur kompiliere 
und auf den XMega lade, geht es manchmal und manchmal nicht. Das ist 
sehr verwirrend. Ich hab schon so viel ausprobiert. Aber da es ja 
manchmal geht, sollte am Code ja nichts falsch sein. Hat vielleicht der 
XMega da auch manchmal so seine Macken? Das wäre natürlich fatal...

Grüße,
Nicolas

von Timmo H. (masterfx)


Lesenswert?

Also ich nutze den SPI bei meinem Xmega so:
1
void SPI_init(){  
2
  PORTC.DIR = (1<<PIN4) | (1<<PIN5) | (1<<PIN7); //SS & MOSI & SCK
3
  PORTC.OUTCLR = (1<<PIN7);
4
  SPIC.CTRL = (1<<SPI_MASTER_bp) | (1<<SPI_ENABLE_bp) | SPI_PRESCALER_DIV64_gc; //prescaler 64 => 500 kHz
5
6
}
7
8
unsigned char SPI_transfer( unsigned char value )
9
{
10
11
  SPIC.DATA = value;
12
  /* Wait for transmission complete */
13
  while(!(SPIC.STATUS & SPI_IF_bm));
14
  return SPIC.DATA;
15
}
16
17
void SPI_Write_Reg(uint8_t reg, uint8_t value)                 
18
{
19
20
  CSN_ON();
21
22
  SPI_transfer(reg);  // select register
23
  SPI_transfer(value);        // ..and write value to it..
24
25
  CSN_OFF();
26
}      
27
28
uint8_t SPI_Read_Reg(uint8_t reg)                               
29
{                                                           
30
  uint8_t value;
31
32
  CSN_ON();
33
34
  SPI_transfer(reg);            // Select register to read from..
35
  value = SPI_transfer(0);    // ..then read register value
36
37
  CSN_OFF();
38
39
40
  return(value);        // return register value
41
}   
42
43
void SPI_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t length)     
44
{                                                           
45
  uint8_t status,byte_ctr;  
46
47
    CSN_ON();                                                
48
49
  status = SPI_transfer(reg);           // Select register to write, and read status UINT8
50
                                                            
51
  for(byte_ctr=0;byte_ctr<length;byte_ctr++)           
52
    pBuf[byte_ctr] = SPI_transfer(0);    // Perform SPI_RW to read UINT8 from RFM70 
53
                                         
54
  CSN_OFF();
55
               
56
}     
57
58
void SPI_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t length)    
59
{                                                           
60
  uint8_t status, byte_ctr;                              
61
62
  CSN_ON();
63
64
  status = SPI_transfer(reg);    // Select register to write to and read status UINT8
65
66
  for(byte_ctr=0; byte_ctr<length; byte_ctr++) // then write all UINT8 in buffer(*pBuf) 
67
    SPI_transfer(*pBuf++); 
68
    
69
                                  
70
  CSN_OFF();
71
72
}
Das wars auch schon

von Bastel K. (bastel_k)


Lesenswert?

Ja, ungefähr den selben Code habe ich schon an vielen Stellen gefunden. 
Aber um später DMA benutzen zu können, braucht man auf jeden Fall einen 
Interrupt. Der wird in deinem Code ja nicht benutzt.

Ansonsten macht mein Code im Grunde genau das selbe wie deine. Ich lese 
bloß SPIx.DATA nicht aus, wenn ich es auch nicht brauche. Ich versuche 
das mal mit einer 'dummy'-Variablen zu umgehen.

Muss denn vor dem nächsten Schreiben in SPIx.DATA immer daraus gelesen 
werden?

Nach den ersten Tests geht es zumindest ohne Interrupt immer. Aber mit 
Interrupt ging es noch gar nicht, obwohl das auch mal zumindest 
sporadisch geklappt hatte. :-/

von Timmo H. (masterfx)


Lesenswert?

Dann schau dir doch mal "AVR1309: Using the XMEGA SPI" an. Da sind auch 
Interrupt-Beispiele.
Mir war die Lib immer zu fett, darum nutze ich das von mir abgespeckte.

von Basti M. (counterfeiter)


Lesenswert?

DMA und Interrupt ist nicht das selbe... der DMA nutzt zwar die 
Interruptquelle als Trigger, aber der Interrupt muss deswegen nichts 
abgearbeitet werden... sonst macht das ja keinen Sinn...
Außerdem geht Interrupt aufn XMega eh nur mit dem USART... also gleich 
mal damit versuchen, wenns später eh noch DMA werden soll...
Die Pins sind jetzt leider falsch belegt, aber ich denke, man kann sie 
in den neuen U Typen einfach ummappen...

Grüße

Basti

von Gerhard G. (g_g)


Lesenswert?

Hallo,

schau dir mal den Code an: Auslesen eines LTC2400 24 Bit

SPI kann mit oder ohne Interrupt verendet werden (#define SPID_USEINT)

Deine 32 Bit sind dann kein Problem mehr.


http://www.basteln-mit-avr.de/atxmega128a3.html#PT1000

Gruß G.G.

von Bastel K. (bastel_k)


Lesenswert?

G. G. schrieb:
> schau dir mal den Code an: Auslesen eines LTC2400 24 Bit

Danke, das schau ich mir mal an.

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.