Forum: Compiler & IDEs SPI Clock Problem


von Walter Reuther (Gast)


Lesenswert?

Hallo zusammen,

habe auf meinen AT90CAN folgendes Programm aufgespielt.
1
/* ADC initialisieren */
2
void ADC_Init(void)
3
{
4
  
5
  
6
  //SPI initialisieren
7
  // Set MOSI, SCK and SS output, all others input 
8
  DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
9
  // Enable SPI, Master, set clock rate fck/16 / MSB first 
10
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
11
  
12
  SPCR &= ~(1<<CPOL);
13
  SPCR &= ~(1<<CPHA);
14
  //ADC deaktivieren
15
  PORT_SPI |= (1<<DD_SS);
16
  
17
}

In der main rufe ich am Anfang ADC_Init() auf.

Wenn ich das Programm auf den Controller per ICE mk2 übertrage kann ich 
mit dem Oszi einen kurzen Clock-Block (8 Perioden) sehen.
Ist die Übertragung beendet, liefert der SCK-Pin kein Clocksignal mehr?

Hat jemand eine Idee wo der Fehler liegen könnten.

Schon mal vielen Dank!

Gruss
Walter

von Walter Reuther (Gast)


Lesenswert?

Hier noch die Portinitialisierung
1
#define F_CPU  16000000      // clock frequency in Hz
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <stdio.h>
5
6
#define DD_MOSI PINB2
7
#define DD_MISO PINB3
8
#define DD_SCK  PINB1
9
#define DD_SS   PINB0
10
#define DDR_SPI DDRB
11
#define PORT_SPI PORTB

von Stefan E. (sternst)


Lesenswert?

Walter Reuther schrieb:
> Ist die Übertragung beendet, liefert der SCK-Pin kein Clocksignal mehr?
>
> Hat jemand eine Idee wo der Fehler liegen könnten.

Welcher Fehler?
Bei SPI ist immer nur während einer Übertragung ein Clocksignal 
vorhanden.

von Walter Reuther (Gast)


Lesenswert?

Hallo Stefan,

hab ich gerade auch gemerkt.
Die SPI Übertragung läuft gerade. Jedoch passen die Werte nicht.

Ich nutze einen TMP121 - Sensor
http://www.ti.com/general/docs/lit/getliterature.tsp?literatureNumber=sbos273c

Mit folgender Routine lese ich meine SPI aus.

Hat jemand eine Ahnung was da nicht passen könnte?
1
//ADC Einzelmessung 
2
int ADC_Read(void)
3
{
4
  volatile int data;
5
  data=0;
6
  
7
  //ADC aktivieren
8
  PORT_SPI &= ~(1<<DD_SS);
9
 
10
  
11
  // Start transmission 
12
  SPDR = 0xFF;
13
  while(!(SPSR & (1<<SPIF)));
14
  data = SPDR;
15
  
16
  //ADC deaktivieren
17
  PORT_SPI |= (1<<DD_SS);
18
  _delay_ms(1000);
19
  
20
  return data;
21
  
22
}

Main - Aufruf und Senden an UART
1
test = (float)ADC_Read();
2
dtostrf(test, 6, 3, x);
3
   
4
   uart_transmit_frame(x);
5
   uart_transmit_frame("\r\n");

von dummy (Gast)


Lesenswert?

>Hat jemand eine Ahnung was da nicht passen könnte?

Wie willst du 12Bit in einem Byte übertragen?
Du hast SPI überhaupt nicht verstanden.

von Walter Reuther (Gast)


Lesenswert?

Deswegen frage ich ja, ob mir jemand helfen könnte.

Ich weis schon, dass der SPI(Atmel) nur ein 8-bit langes Register 
besitzt und der Sensor 16-bit (2 zeros 1 sign 15 daten) rüberschiebt.

Nur wie kann ich es in meinem Programm umsetzen. Zudem muss ich nach 
einem CS 16 Clk-Takte ausgeben und nicht wie jetzt 8.

Kann mir jemand bei der Codeumsetzung helfen.

Ich bin leider im Bereich uC nicht sehr bewandert (Komme aus der 
Schaltungstechnik).

Vielen Dank!

Gruss

von schrieb schrieb schrieb (Gast)


Lesenswert?

Walter Reuther schrieb:
1
 // Start transmission
2
   SPDR = 0xFF;
3
   while(!(SPSR & (1<<SPIF)));
4
   data = SPDR;

Walter Reuther schrieb:
> Ich weis schon, dass der SPI(Atmel) nur ein 8-bit langes Register
> besitzt und der Sensor 16-bit (2 zeros 1 sign 15 daten) rüberschiebt.
>
> Nur wie kann ich es in meinem Programm umsetzen. Zudem muss ich nach
> einem CS 16 Clk-Takte ausgeben und nicht wie jetzt 8.

Indem du den Code einfach zweimal ausführst:
1
 // Start transmission
2
   SPDR = 0xFF;
3
   while(!(SPSR & (1<<SPIF)));
4
   data = SPDR;
5
 // Start transmission
6
   SPDR = 0xFF;
7
   while(!(SPSR & (1<<SPIF)));
8
   data = SPDR;

von Walter Reuther (Gast)


Lesenswert?

Vielen Dank für die Antwort,

wie kann ich dann die beiden 8-bit zusammenfügen und als float-Wert 
(Temperaturwert) an den USART übergeben?

Gruss

von Cyblord -. (cyblord)


Lesenswert?

Walter Reuther schrieb:
> Vielen Dank für die Antwort,
>
> wie kann ich dann die beiden 8-bit zusammenfügen und als float-Wert
> (Temperaturwert) an den USART übergeben?

Zahlensysteme- und Darstellung hast du also auch nicht verstanden.

Egal was du wie umwandelst, du sendest Bytes durch deinen UART. Warum 
also nicht die 2 Bytes senden und der Empfänger kümmert sich um das 
DARSTELLEN des Wertes?

Alternativ, wenn du Text direkt an ein Terminal(programm) senden willst, 
dann musst du den Wert in einen String umwandeln und bekommst 1 Byte pro 
Buchstabe Text. Das geht dann mit itoa oder sprintf.

gruß cyblord

von Walter Reuther (Gast)


Lesenswert?

hallo cyblord.

ich hab alles mal wie folgt umgesetzt.

SPI.c
1
#define F_CPU  16000000      // clock frequency in Hz
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <stdio.h>
5
6
#define DD_MOSI PINB2
7
#define DD_MISO PINB3
8
#define DD_SCK  PINB1
9
#define DD_SS   PINB0
10
#define DDR_SPI DDRB
11
#define PORT_SPI PORTB
12
13
14
15
/* ADC initialisieren */
16
void ADC_Init(void)
17
{
18
  /* Set MOSI and SCK output, all others input */
19
  DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
20
  /* Enable SPI, Master, set clock rate fck/16 */
21
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
22
}
23
24
  
25
//ADC Einzelmessung 
26
int ADC_Read(void)
27
{
28
  volatile int data;
29
  data=0;
30
  
31
  //ADC aktivieren
32
  PORT_SPI &= ~(1<<DD_SS);
33
 
34
  // Start transmission 
35
  SPDR = 0xFF;
36
  while(!(SPSR & (1<<SPIF)));
37
  data = SPDR;
38
  
39
  // Start transmission
40
  SPDR = 0xFF;
41
  while(!(SPSR & (1<<SPIF)));
42
  data = SPDR;
43
  
44
  //ADC deaktivieren
45
  PORT_SPI |= (1<<DD_SS);
46
  _delay_ms(1000);
47
  
48
  
49
  return data;
50
  
51
}

main.c
1
#include <avr/io.h>
2
#include <stdlib.h>
3
#include "UART.h"
4
#include "SPI.h"
5
6
#define F_CPU  16000000      // clock frequency in Hz
7
#include <avr/interrupt.h>      // include interrupt-routine
8
#include <util/delay.h>
9
    
10
11
12
13
int main(void)
14
{
15
  
16
  char x[8];
17
  float test;
18
  
19
         init_uart0();
20
   ADC_Init();
21
   
22
  
23
   
24
   while(1)
25
   {
26
  
27
   _delay_ms(3000); 
28
  
29
   test =  (float)ADC_Read();
30
   dtostrf( test, 6, 3, x);   
31
     
32
   
33
   uart_transmitt_frame(x);
34
   uart_transmitt_frame("\r\n");
35
   }
36
     
37
    
38
     
39
     return 0; // never reached
40
41
}

Leider funktioniert das so nicht.
Muss ich nicht erst die beiden 8-bit Register zu einem long int - 
Register zusammenfügen und dann nach float - casten um den Wert danach 
an den UART mittels dtostrf zu übergeben?

Ich werde noch wahnsinnig.

Kann mir jemand eine genaue Hilfestellung geben?

Vielen Dank!

Gruss

von Cyblord -. (cyblord)


Lesenswert?

Anstatt haufenweise Code zu posten, könntest du mal auf meine Frage 
eingehen, was du jetzt eigentlich genau Übertragen willst? Vielleicht 
solltest du dir das selber erstmal klarmachen?

Wenn du einen String via UART übertragen willst (was du nicht sagst, 
weil du gar nichts dazu sagst WAS DU TUN WILLST), solltest du dir als 
erstes eine Funktion machen die das tut. Dazu gibt man ihr einen Pointer 
auf ein char und die durchläuft das Array und sendet jedes Byte raus. 
Bis zur 0 dann ist Ende. So eine Funktion findest du überall im Netz. 
Das ist mal Schritt 1.

Dann Schritt 2, wie bekommst du deinen String aus deinem Messwert.

Mit uint16_t x=ADC liest du den Wert erstmal in eine geeignete Variable 
ein.

Jetzt musst du Rechnen, je nach Anschaltung deiner Spannung und der 
Formel aus dem Datenblatt (ADC=(V*1024)/Vref) rechnest du jetzt den ADC 
Wert in eine Spannung Volt um. Das kannst du mit Float machen. Oder als 
Festkommawert z.B. Spannung in mV, dann bleibts eine Ganzzahl.

So und Schritt 3, wie wandelst du dein Ergebniss (float oder Ganzzahl) 
jetzt in einen String um. Dazu nimmt man bei ganzzahlen itoa (bzw. utoa, 
ultoa) oder wenn du nen Float hast, dann am bestne mit sprintf.

Und diesen String kannst du jetzt mit deiner neuen sende_String Funktion 
schicken.

So und jetzt würde ich an deiner Stelle Schritt für Schritt vorgehen und 
erstmal Zwischenschritte testen.

gruß cyblord

von Walter Reuther (Gast)


Lesenswert?

Hallo!

Also das Senden float-Wertes funktioniert (wurde mittels 
Terminalprogramm überprüft) bereits. (Diese Funktion ist mittels 
dtostrf( test, 6, 3, x); gelöst.)

Nun zu meiner SPI:

Es handelt sich um einen Digitalen-Temperatursensor (CS, SDO, CLK) 
dieser liefert laut Datenblatt 2 zeros, 1 sign-bit, 15 data-bits.
Diesen "Digitalen-Temperaturwert" möchte ich mir vom Sensor holen in 
einen float-Wert umsetzen und dann an die UART-Sendefunktion übergeben.

Wie gesagt, die UART-Kommunikation mit dem Terminal funktioniert schon 
so wie sie soll, nur die SPI bereitet noch Schwierigkeiten.

Im AT90CAN128 ist das SPI-Register nur 8-bit lang ich muss jedoch wie 
oben beschrieben 16 - bit abholen.

Daher meine Frage wie die Lösung in der Programmierung aussehen könnte.

Ich bin leider kein "großer Programmierer" aber vll. kann mir jemand von 
euch Codehilfestellungen geben!

Vielen DANK!

von Cyblord -. (cyblord)


Lesenswert?

Walter Reuther schrieb:

> Im AT90CAN128 ist das SPI-Register nur 8-bit lang ich muss jedoch wie
> oben beschrieben 16 - bit abholen.

Das löst man einfach so, dass man den Lesevorgang 2 mal hintereinander 
macht. CS muss man sowieso manuell steuern und somit lässt man es auf Lo 
bis beide Bytes empfangen wurden.

Also:

cs_lo();
uint8_t b1=readSPI();
uint8_t b2=readSPI();
cs_hi();

Dann die beiden Bytes zu einem Int16 zusammenbasteln und weiter gehts.

Dachte bei ADC_Read sofort an den internen ADC und hab vergessen dass du 
ja einen externen über SPI hast. Ändert aber an der Vorgehensweise 
direkt nichts.

von Walter Reuther (Gast)


Lesenswert?

Was hältst du von dieser Umsetzung?
1
//TMP121 Read
2
uint16_t ADC_Read(void)
3
{
4
  volatile uint8_t data1, data2;
5
  volatile uint16_t fulldata;
6
  data1=0;
7
  data2=0;
8
  
9
  //SPI aktivieren - CS low
10
  PORT_SPI &= ~(1<<DD_SS);
11
 
12
  // Read SPI 
13
  SPDR = 0xFF;
14
  while(!(SPSR & (1<<SPIF)));
15
  data1 = SPDR;
16
  
17
  // Read SPI
18
  SPDR = 0xFF;
19
  while(!(SPSR & (1<<SPIF)));
20
  data2 = SPDR;
21
  
22
  //ADC deaktivieren - CS high
23
  PORT_SPI |= (1<<DD_SS);
24
  
25
  fulldata = ( (data1 << 8) | data2 );
26
  
27
  return fulldata;
28
  
29
}

Kann man dann die Funktion einfach in eine Floatvariable casten?

z.B.
1
long float value;
2
value = (long float)ADC_Read();

Danke!

von Karl H. (kbuchegg)


Lesenswert?

Walter Reuther schrieb:

> Kann man dann die Funktion einfach in eine Floatvariable casten?

Schon.
Aber was hast du dauernd mit deinem float?

Das ändert doch an der Zahl selber nichts, ob du die jetzt als INteger 
oder als Float  in Textform zum Terminal schickst. Alles was du tust, 
ist dem µCV Mehrarbeit aufbürden. Aber ob der jetzt aus den 258, die er 
vom ADC bekommt, jetzt erst mal 258.0 macht, nur um das dann in einen 
Text zu überführen und zum Terminal zu geben, ist doch gehupft wie 
gehatscht. 258 ist 258. Egal ob dann durch den float noch ein 
Nachkommaanteil dazu kommt, der sowieso 0 ist.


Also schieb doch den int erst mal so rüber. Damit du mal siehst, was da 
überhaupt rauskommt.
Und wenn nicht das richtige rauskommt, dann schiebt man sich die beiden 
erhaltenen Bytes data1 und data2 über die UART aufs Terminal (natürlich 
als int).

Nicht gleich alles auf einmal wollen und sich dann wundern, dass nichts 
funktioniert! Erst mal müssen die einfachen Sachen funktionieren. Dann 
kann man darauf aufbauen und die komplizierteren Sachen machen. Und 
float ist nun mal komplizierter zu verarbeiten als int.

von Hugo (Gast)


Lesenswert?

Float Variablen sind auf Mikrocontrollern nicht so geschickt.
Guck mal hier:
http://www.mikrocontroller.net/articles/Festkommaarithmetik

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.