Forum: Mikrocontroller und Digitale Elektronik Auslesen von Magnetischem Encoder SPI


von Sebastian T. (sebastian_tsch)


Lesenswert?

Hi,

Ich möchte über SPI einen AS5048A auslesen 
(http://ams.com/eng/Products/Magnetic-Position-Sensors/Angle-Position-On-Axis/AS5048A). 
Ich habe es mit und ohne DMA versucht und unterschiedliche Ergebnisse 
erhalten:

Ohne DMA scheint es mir den Korrekten Wert anzugeben, jedoch ist der 
Wert nur zwischen 8192 und 16384, also nur die halbe Auflösung. Dabei 
habe ich über die SPI Schnittstelle einfach ein 0x3FFF geschickt, was 
mir nach Datenblatt die 14Bit Auflösung zurückschicken sollte. Damit 
könnte ich aber noch leben.

Sobald ich aber den DMA verwende, passiert etwas merkwürdiges. Die 
Auflösung scheint zwischen 2 Werten hin und her zu springen (zufällig, 
manchmal auch 3mal einen Wert aus dem ersten Intervall etc.), ein Wert 
liegt im Intervall [0,8191], der andere im Intervall [8192,16384]. Was 
ich mache: Isch schreibe in meinen TX Buffer einfach ein 0x3FFF rein und 
lesen nach Bestätigung durch einen Interrupt den RX Buffer aus. Das 
Problem ist, dass ich nicht einfach +8192 für Werte aus dem ersten 
Intervall nehmen kann, da die Differenz zum zweiten Intervall nicht 
linear ist.

Im Datenblatt auf Seite 2 und 3 wird ja ein Vorgehen beschrieben, doch 
ich möchte eigentlich nur den Winkel und dafür sollte 0x3FFF reichen.

Jemand eine Idee?

Edit: Ich habe gerade gesehen, dass die Werte nicht nur zwischen den 
Intervallen springen, sondern auch innerhalb, z.B zwischen 10000 und 
15000.

Grüsse Sebastian T.

: Bearbeitet durch User
von MWS (Gast)


Lesenswert?

Sebastian T. schrieb:
> Dabei
> habe ich über die SPI Schnittstelle einfach ein 0x3FFF geschickt, was
> mir nach Datenblatt die 14Bit Auflösung zurückschicken sollte.

Nach dem hier:

>> For a single READ command two transmission sequences are necessary.
>> The first package written to the AS5048 contains the READ command
>> (MSB-1 high) and the address the chip has to access,

entspricht ein 3fff dem Schreibkommando auf das Winkel-Register, statt 
zu lesen und der Rückgabewert ist demgemäß irgendwas.

> Im Datenblatt auf Seite 2 und 3 wird ja ein Vorgehen beschrieben, doch
> ich möchte eigentlich nur den Winkel und dafür sollte 0x3FFF reichen.

Da finde ich nichts. Ggf. anders DB?

von Sebastian T. (sebastian_tsch)


Lesenswert?

MWS schrieb:
> Da finde ich nichts. Ggf. anders DB?

Ich verwende die Application Note:

http://ams.com/eng/content/view/download/318035

MWS schrieb:
> entspricht ein 3fff dem Schreibkommando auf das Winkel-Register, statt
> zu lesen und der Rückgabewert ist demgemäß irgendwas.

Ich bekomme eben ziemlich sinnvolle Daten, daher weiß ich nicht woher 
die Störung kommt. Ich habe es auch schon nach dem Application Note 
versucht und die 4x 16bit Befehle in den DMA Buffer geschrieben, doch es 
kam das gleiche raus.

von MWS (Gast)


Lesenswert?

Sebastian T. schrieb:
> Ich bekomme eben ziemlich sinnvolle Daten

In die Leitplanke fahren, ist auch ziemlich auf der Straße :-)
Du würdest nicht fragen, wäre alles in Ordnung.

Gehst Du denn so wie in der AN vor? Also Read-Bit zu 3fff setzen, Parity 
daraus berechnen und per SPI senden. Nur ein 3fff rauszuklopfen reicht 
nicht.

von Klaus (Gast)


Angehängte Dateien:

Lesenswert?

Ich verwende den kleinen Bruder, den AS5040, und der liefert wunderbar 
stabile Werte. Er hat ein nicht ganz so komplexes Interface wie der 
5048, der sollte aber genauso funktionieren, wenn man nur den Winkel 
will. Ich hab das in SW gemacht, war mir schnell genug.

Also: CS auf low, 16 mal mit dem Clock wackeln und dabei nach 
Timingdiagram MISO einlesen, CS wieder high. Dabei MOSI immer high 
lassen oder gleich einen Pullup. Die erste Messung nach einem Reset ist 
Rotte, ab dann läufts. Die untersten 14 Bits sind dann der Winkel, die 
beiden oberen Parity und Errorflag. Sollte man beide auswerten, um z.B. 
zu sehen, ob der Magnet passend sitzt.

Das sollte mit dem 5048 genauso funktionieren, wenn ich das Datenblatt 
richtig verstanden habe.

MfG Klaus

von Sebastian T. (sebastian_tsch)


Lesenswert?

Klaus schrieb:
> Ich verwende den kleinen Bruder, den AS5040, und der liefert wunderbar
> stabile Werte. Er hat ein nicht ganz so komplexes Interface wie der
> 5048, der sollte aber genauso funktionieren, wenn man nur den Winkel
> will. Ich hab das in SW gemacht, war mir schnell genug.
>
> Also: CS auf low, 16 mal mit dem Clock wackeln und dabei nach
> Timingdiagram MISO einlesen, CS wieder high. Dabei MOSI immer high
> lassen oder gleich einen Pullup. Die erste Messung nach einem Reset ist
> Rotte, ab dann läufts. Die untersten 14 Bits sind dann der Winkel, die
> beiden oberen Parity und Errorflag. Sollte man beide auswerten, um z.B.
> zu sehen, ob der Magnet passend sitzt.
>
> Das sollte mit dem 5048 genauso funktionieren, wenn ich das Datenblatt
> richtig verstanden habe.
>
> MfG Klaus

Danke, das scheint zu funktionieren. Ich bin mir sicher, das ich das 
schon vorher so gemacht habe und es hat nicht geklappt, aber jetzt geht 
es.

Grüsse Sebastian

von MWS (Gast)


Lesenswert?

Sebastian T. schrieb:
> Danke, das scheint zu funktionieren.

Kein Wunder, bei Dauer-High ist auch das Read-Bit gesetzt und so schrieb 
ich's bereits.

Damit es wenigstens weitere Suchende verstehen:
Du hast 0x3fff geschickt, das ist die Winkeladresse mit Read=0, somit 
ein Schreibbefehl, dass da Unsinn rauskommt, war klar. Nur eben Dir 
nicht.

Sebastian T. schrieb:
> habe ich über die SPI Schnittstelle einfach ein 0x3FFF geschickt

von Sebastian T. (sebastian_tsch)


Lesenswert?

MWS schrieb:
> Du hast 0x3fff geschickt, das ist die Winkeladresse mit Read=0, somit
> ein Schreibbefehl, dass da Unsinn rauskommt, war klar. Nur eben Dir
> nicht.

Ich habe eben auch vorher schon 0xFFFF gesendet und dann mit &=0x3FFF 
die Parity Bits abgeschnitten und trotzdem nur die Hälfte empfangen. 
Warum es jetzt geht ist mir noch immer nicht klar, ich habe verschiedene 
neue Projekte erstelle, welche ich nun durchsuche um den Fehler zu 
finden.

von MWS (Gast)


Lesenswert?

Sebastian T. schrieb:
> Ich habe eben auch vorher schon 0xFFFF gesendet und dann mit &=0x3FFF
> die Parity Bits abgeschnitten

Du hast keine Parity-Bit*s* abgeschnitten, da gibt es nur eines, Du hast 
damit das Read-Bit gelöscht und einen Schreib- statt eines Lesebefehls 
auf das Winkelregister geschickt.

Und ein 0xffff entspräche einem Dauer-High, also dem 3 wire mode,  der 
ja offenbar funktioniert, also hätte es auch mit 0xffff geklappt.

von Sebastian T. (sebastian_tsch)


Lesenswert?

Ich versuche nun alle Register zu empfangen, um mit den parity bits die 
eine Fehlerhafte Übertragung zu erkennen, was etwa alle 100-200 samples 
vorkommt. Das Problem ist, dass ich irgendwie immer falsche parity bits 
empfange und daher die Daten, auch wenn Sie korrekt sein mögen, falsch 
sind. Hier mein Code:
1
#include "SPI_DMA.h"
2
3
uint16_t SPIBufferRX[BUFFER_SIZE] = {0};
4
uint16_t SPIBufferTX[BUFFER_SIZE] = {0};
5
uint16_t Data_Buffer[4] = {0};
6
uint8_t dataReady=1;
7
uint8_t transferReady=1;
8
uint8_t transferState = 1;
9
10
void init_SPI_DMA_All() {
11
  init_SPI_RCC();
12
  init_SPI_GPIO();
13
  init_SPI_SPI();
14
  init_SPI_DMA();
15
}
16
17
void setTXBuffer(uint8_t s) {
18
  uint16_t dat;
19
  switch(s) {
20
    case 1:
21
      dat = SPI_CMD_READ | SPI_REG_AGC;
22
      dat |= spiCalcEvenParity(dat) << 15;
23
      SPIBufferTX[0]=dat;
24
      break;
25
    case 2:
26
      dat = SPI_CMD_READ | SPI_REG_MAG;
27
      dat |= spiCalcEvenParity(dat) << 15;
28
      SPIBufferTX[0]=dat;
29
      break;
30
    case 3:
31
      dat = SPI_CMD_READ | SPI_REG_DATA;
32
      dat |= spiCalcEvenParity(dat) << 15;
33
      SPIBufferTX[0]=dat;
34
      break;
35
    case 4:
36
      dat = 0x0000; // NOP command.
37
      SPIBufferTX[0]=dat;
38
      break;
39
    default:
40
      break;
41
  }
42
}
43
44
uint8_t spiCalcEvenParity(uint16_t value) {
45
  uint8_t cnt = 0;
46
  uint8_t i;
47
  for (i = 0; i < 16; i++) {
48
    if (value & 0x1) cnt++;
49
    value >>= 1;
50
  }
51
  uint8_t parity = cnt & 0x1;
52
  return parity;
53
}
54
55
void init_SPI_RCC() {
56
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
57
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
58
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
59
}
60
61
void init_SPI_GPIO() {
62
  GPIO_InitTypeDef GPIO_InitStructure;
63
64
  GPIO_StructInit(&GPIO_InitStructure);
65
  GPIO_InitStructure.GPIO_Pin = SCK_PIN | MISO_PIN | MOSI_PIN;
66
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
67
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
68
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
69
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
70
  GPIO_Init(SPI_PORT, &GPIO_InitStructure);
71
72
  GPIO_PinAFConfig(SPI_PORT, GPIO_PinSource3, GPIO_AF_SPI3);
73
  GPIO_PinAFConfig(SPI_PORT, GPIO_PinSource4, GPIO_AF_SPI3);
74
  GPIO_PinAFConfig(SPI_PORT, GPIO_PinSource5, GPIO_AF_SPI3);
75
76
  GPIO_InitStructure.GPIO_Pin = SS_PIN;
77
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
78
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
79
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
80
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
81
  GPIO_Init(SPI_PORT, &GPIO_InitStructure);
82
83
  GPIO_SetBits(SPI_PORT, SS_PIN);        // Set SS pin
84
}
85
86
void init_SPI_SPI() {
87
  SPI_InitTypeDef SPI_InitStructure;
88
89
  SPI_StructInit(&SPI_InitStructure);
90
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
91
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
92
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
93
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
94
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
95
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
96
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
97
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//MSB
98
  SPI_InitStructure.SPI_CRCPolynomial = 0;
99
  SPI_Init(SPI3, &SPI_InitStructure);
100
}
101
102
void init_SPI_DMA(void) {
103
104
  NVIC_InitTypeDef NVIC_InitStructure;
105
  DMA_InitTypeDef DMA_InitStructure;
106
107
  DMA_Cmd(DMA_STREAM_TX, DISABLE);
108
  DMA_Cmd(DMA_STREAM_RX, DISABLE);
109
  DMA_DeInit(DMA_STREAM_TX);
110
  DMA_DeInit(DMA_STREAM_RX);
111
112
  //RX
113
  DMA_StructInit(&DMA_InitStructure);
114
  DMA_InitStructure.DMA_Channel = DMA_CHANNEL;                                   //SPI3 Tx DMA is DMA1/Stream4/Channel0
115
  DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI3->DR);             //Set the SPI3 Tx
116
  DMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t)SPIBufferRX;                //Set the memory location
117
  DMA_InitStructure.DMA_DIR  = DMA_DIR_PeripheralToMemory;                       //Sending data from memory to the peripheral's Tx register
118
  DMA_InitStructure.DMA_BufferSize  = BUFFER_SIZE;                              //Define the number of bytes to send
119
  DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;             //Don't increment the peripheral 'memory'
120
  DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;                        //Increment the memory location
121
  DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_HalfWord;    //Byte size memory transfers
122
  DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_HalfWord;           //Byte size memory transfers
123
  DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;                                   //Normal mode (not circular)
124
  DMA_InitStructure.DMA_Priority  = DMA_Priority_High;                           //Priority is high to avoid saturating the FIFO since we are in direct mode
125
  DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;                         //Operate in 'direct mode' without FIFO
126
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
127
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
128
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
129
  DMA_Init(DMA_STREAM_RX, &DMA_InitStructure);
130
  DMA_ITConfig(DMA_STREAM_RX, DMA_IT_TC, ENABLE);
131
132
  //TX
133
  DMA_InitStructure.DMA_Channel = DMA_CHANNEL;                                //SPI3 Tx DMA is DMA1/Stream4/Channel0
134
  DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI3->DR);             //Set the SPI3 Tx
135
  DMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t)SPIBufferTX;                 //Set the memory location
136
  DMA_InitStructure.DMA_DIR  = DMA_DIR_MemoryToPeripheral;                      //Sending data from memory to the peripheral's Tx register
137
  DMA_InitStructure.DMA_BufferSize  = BUFFER_SIZE;                              //Define the number of bytes to send
138
  DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;             //Don't increment the peripheral 'memory'
139
  DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;                        //Increment the memory location
140
  DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_HalfWord;    //Byte size memory transfers
141
  DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_HalfWord;           //Byte size memory transfers
142
  DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;                                 //Normal mode (not circular)
143
  DMA_InitStructure.DMA_Priority  = DMA_Priority_High;                           //Priority is high to avoid saturating the FIFO since we are in direct mode
144
  DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;                         //Operate in 'direct mode' without FIFO
145
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
146
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
147
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
148
  DMA_Init(DMA_STREAM_TX, &DMA_InitStructure);
149
  DMA_ITConfig(DMA_STREAM_TX, DMA_IT_TC, ENABLE);
150
151
  //Interrupt
152
  DMA_ClearITPendingBit(DMA_STREAM_RX, DMA_IT_RX_FLAG);
153
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream0_IRQn;
154
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
155
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
156
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
157
  NVIC_Init(&NVIC_InitStructure);
158
159
  DMA_ClearITPendingBit(DMA_STREAM_TX, DMA_IT_TX_FLAG);
160
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn;
161
  NVIC_Init(&NVIC_InitStructure);
162
163
  SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);
164
  SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Rx, ENABLE);
165
  SPI_Cmd(SPI3, ENABLE);
166
}
167
168
int SPI_DMA_getData() {
169
  if(transferReady) {
170
    uint16_t data=Data_Buffer[3]&0x3FFF;
171
    dataReady=1;
172
173
    if (((Data_Buffer[1] & 0x4000) || (Data_Buffer[2]  & 0x4000) || (Data_Buffer[3]  & 0x4000))) {
174
      return -1;
175
    } else {
176
      return data;
177
    }
178
  }
179
  return -1;
180
}
181
182
void SPI_DMA_continue(uint8_t s) {
183
  s++;
184
  if(s<=4) {
185
    transferState=s;
186
    setTXBuffer(transferState);
187
188
    Data_Buffer[s-2]=SPIBufferRX[0];
189
190
    DMA_SetCurrDataCounter(DMA_STREAM_RX, BUFFER_SIZE);
191
    DMA_SetCurrDataCounter(DMA_STREAM_TX, BUFFER_SIZE);
192
193
    GPIO_WriteBit(SPI_PORT, SS_PIN, RESET);
194
    DMA_Cmd(DMA_STREAM_TX, ENABLE);
195
    DMA_Cmd(DMA_STREAM_RX, ENABLE);
196
197
    SPI_Cmd(SPI3, ENABLE);
198
199
  } else {
200
    Data_Buffer[3]=SPIBufferRX[0];
201
    transferReady=1;
202
  }
203
204
}
205
206
void SPI_DMA_start(void) {
207
  if(transferReady && dataReady) {
208
    transferReady=0;
209
    dataReady=0;
210
211
    transferState=1;
212
    setTXBuffer(transferState);
213
214
    while((DMA_GetCmdStatus(DMA_STREAM_TX) == ENABLE) || (DMA_GetCmdStatus(DMA_STREAM_RX) == ENABLE)){
215
216
    }
217
218
    DMA_SetCurrDataCounter(DMA_STREAM_RX, BUFFER_SIZE);
219
    DMA_SetCurrDataCounter(DMA_STREAM_TX, BUFFER_SIZE);
220
221
    GPIO_WriteBit(SPI_PORT, SS_PIN, RESET);
222
    DMA_Cmd(DMA_STREAM_TX, ENABLE);
223
    DMA_Cmd(DMA_STREAM_RX, ENABLE);
224
225
    SPI_Cmd(SPI3, ENABLE);
226
  }
227
}
228
229
void DMA1_Stream0_IRQHandler(void){ //RX
230
  if(DMA_GetITStatus(DMA_STREAM_RX, DMA_IT_RX_FLAG))  {
231
    DMA_ClearITPendingBit(DMA_STREAM_RX, DMA_IT_RX_FLAG);
232
233
    SPI_Cmd(SPI3, DISABLE);
234
    GPIO_WriteBit(SPI_PORT, SS_PIN, SET);
235
    DMA_Cmd(DMA_STREAM_RX, DISABLE);
236
    DMA_Cmd(DMA_STREAM_TX, DISABLE);
237
238
    SPI_DMA_continue(transferState);
239
240
  }
241
}
242
243
void DMA1_Stream5_IRQHandler(void){ //TX
244
  if(DMA_GetITStatus(DMA_STREAM_TX, DMA_IT_TX_FLAG))  {
245
    DMA_ClearITPendingBit(DMA_STREAM_TX, DMA_IT_TX_FLAG);
246
247
  }
248
}

von Klaus (Gast)


Lesenswert?

Wow!

Wenn ich da meinen Code anschaue kriege ich ja richtig 
Minderwertigkeitskomplexe
1
    unsigned int result = 0;
2
3
    AS_CLK = 1;
4
    AS_CS = 0;
5
    __delay_us(1);
6
    for (i = 0; i < 16; i++) {
7
        AS_CLK = 0;
8
        __delay_us(1);
9
        AS_CLK = 1;
10
        __delay_us(1);
11
        result <<= 1;
12
        if (AS_DO == 1) {
13
            result |= 1;
14
        }
15
    }
16
    AS_CS = 1;

Wobei man bei 100ns Cycletime die delay_us je nach Prozessor weglassen 
oder durch ein Nop() ersetzen kann. Dann ist das Ganze in wenigen µs 
abgehandelt.

MfG Klaus

von Sebastian T. (sebastian_tsch)


Lesenswert?

Klaus schrieb:
> Wenn ich da meinen Code anschaue kriege ich ja richtig
> Minderwertigkeitskomplexe

Da wird aber weder der DMA verwendet noch die parity bit Kontrolle 
ausgeführt. Wenn ich das ganze ohne DMA und ohne parity bit Kontrolle 
mache, komme ich auf ähnliches.

von Sebastian T. (sebastian_tsch)


Lesenswert?

Der Parity Check scheint nur für die Werte aus [8192,16384] zu gehen, 
dann bei kleineren Winkelwerten leaken irgendwelche Daten durch.

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.