Forum: Mikrocontroller und Digitale Elektronik Probleme mit SPI Übertragung (ATMega88 x2)


von Frederik K. (n0ll4k)


Lesenswert?

Moin,

ich stehe vor folgendem Problem. Ich habe mir 2 ATMega88 auf eine 
Lochrasterplatine gesetzt und möchte das diese per SPI untereinander 
kommunizieren und mir dann ein Messergebnis über die serielle 
Schnittstelle vom Master ausgeben. Konkret geht es darum die interne 
Temperaturmessung etwas genauer zu betrachten.

Momentan habe ich beide Controller über einen MAX232 mit 2 
Schnittstellen verbunden. Dies soll aber momentan nur zum testen der SPI 
Schnittstelle dienen.

Problem ist aber das die Zeichen die ich mit dem Master sende nicht 
korrekt übertragen werden. Ab und dann kommt nach 5-6 Versuchen mal 
etwas korrektes an.

Beide Prozessoren laufen mit internen RC Oszilator.

Quelltext Master:
1
#include <stdint.h>
2
#include <stdlib.h>
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include "adhandle.h"
6
7
#ifndef F_CPU
8
#define F_CPU 1000000UL
9
#endif
10
#include <util/delay.h>
11
#define sendByte(ch)    (UDR0=ch)
12
#define getByte()      (UDR0)
13
14
#define DDR_SPI DDRB
15
#define DD_MOSI DDB3
16
#define DD_SCK  DDB5
17
#define DD_SS  DDB2
18
#define SS    PB2
19
20
char getTemp = 0x00;
21
int16_t temperature;
22
uint16_t adTemp;
23
uint8_t i;
24
char ReceivedByte;
25
char sendZero = 0x00;
26
27
ISR(USART_RX_vect){
28
  if(getByte() == 0x54)
29
    getTemp = 0x01;
30
  else{
31
    sendZero = 0x01;
32
    ReceivedByte = getByte();
33
  }
34
}
35
void initUART( void ){
36
  UCSR0B |= ( 1<<RXEN0 ) | ( 1<<TXEN0 );
37
  UCSR0B |= ( 1<<RXCIE0 );
38
  UCSR0C |= ( 1<<UCSZ00 ) | ( 1<<UCSZ01 );
39
  UCSR0A |= ( 1<<U2X0 );
40
  UBRR0 = 12;
41
42
}
43
44
void UART_putc(unsigned char c){
45
  while((UCSR0A & (1 << UDRE0)) == 0){};
46
  sendByte(c);
47
}
48
49
50
void init_spiMaster( void ){
51
  /* Set MOSI and SCK output, all others input */
52
  DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
53
  /* Enable SPI, Master, set clock rate fck/16 */
54
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
55
}
56
57
void spiMasterTransmit(char cData)
58
{
59
/* Start transmission */
60
SPDR = cData;
61
/* Wait for transmission complete */
62
while(!(SPSR & (1<<SPIF)))
63
;
64
}
65
int main( void ){
66
67
   //
68
  // Initialisierung des A/D-Wandlers
69
  //
70
  // ADC aktivieren und Prescaler auf 8 setzen
71
  ADCSRA |= ( 1<<ADEN ) | ( 1<<ADPS1 ) | ( 1<<ADPS0 );
72
  // Free Running Mode aktivieren, Reservierte Bits mit 0 beschreiben
73
  ADCSRB = 0x00; 
74
   initUART();
75
  init_spiMaster();
76
  sei();
77
  
78
  for(;;){    
79
    if( getTemp == 0x01){
80
      adTemp = readADC(0xC8);
81
      _delay_ms(20);
82
      adTemp = readADC(0xC8);
83
      temperature = ((adTemp-335)*88)/100;
84
      getTemp = 0x00;
85
      char one = (adTemp & 0xFF00) >> 8;
86
      char two = (adTemp & 0x00FF);
87
      char three = (temperature & 0xFF00) >> 8;
88
      char four = (temperature & 0x00FF);
89
      UART_putc(one);
90
      UART_putc(two);
91
      UART_putc(three);
92
      UART_putc(four);
93
      PORTB &= ~( 1<<SS);
94
      _delay_ms(1);
95
      spiMasterTransmit(0x00);
96
      _delay_ms(1);
97
      PORTB |= ( 1<<SS);
98
    }else if(sendZero == 0x01){
99
      UART_putc(ReceivedByte);
100
      sendZero = 0x00;
101
      PORTB &= ~( 1<<SS);
102
      _delay_ms(1);
103
      spiMasterTransmit(ReceivedByte);
104
      _delay_ms(1);
105
      PORTB |= ( 1<<SS);
106
    }else{
107
      i++;
108
      if ( i >= 200 )
109
        i = 0;
110
    }
111
  }
112
}


Quelltext Slave:
1
#include <stdint.h>
2
#include <stdlib.h>
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include "adhandle.h"
6
7
8
9
#ifndef F_CPU
10
#define F_CPU 1000000UL
11
#endif
12
#include <util/delay.h>
13
#define sendByte(ch)    (UDR0=ch)
14
#define getByte()      (UDR0)
15
16
#define DDR_SPI DDRB
17
#define DD_MISO DDB4
18
19
20
char getTemp = 0x00;
21
int16_t temperature;
22
uint16_t adTemp;
23
uint8_t i;
24
char ReceivedByte;
25
char sendZero = 0x00;
26
char spiChar;
27
28
ISR(USART_RX_vect){
29
char ReceivedByte;
30
  if(getByte() == 0x54)
31
    getTemp = 0x01;
32
  else
33
    sendZero = 0x01;
34
}
35
ISR(SPI_STC_vect){
36
//PORTC |= (1<<PC0);
37
 spiChar = SPDR;
38
}
39
40
void initUART( void ){
41
42
  UCSR0B |= ( 1<<RXEN0 ) | ( 1<<TXEN0 );
43
  UCSR0B |= ( 1<<RXCIE0 );
44
45
  UCSR0C |= ( 1<<UCSZ00 ) | ( 1<<UCSZ01 );
46
47
  UCSR0A |= ( 1<<U2X0 );
48
49
  UBRR0 = 12;
50
51
}
52
void SPI_SlaveInit(void)
53
{
54
  /* Set MISO output, all others input */
55
  DDR_SPI = (1<<DD_MISO);
56
  /* Enable SPI */
57
  SPCR = (1<<SPIE)|(1<<SPE);
58
}
59
60
61
void UART_putc(unsigned char c){
62
  while((UCSR0A & (1 << UDRE0)) == 0){};
63
  sendByte(c);
64
}
65
66
int main( void ){
67
68
   //
69
  // Initialisierung des A/D-Wandlers
70
  //
71
  // ADC aktivieren und Prescaler auf 8 setzen
72
  ADCSRA |= ( 1<<ADEN ) | ( 1<<ADPS1 ) | ( 1<<ADPS0 );
73
  // Free Running Mode aktivieren, Reservierte Bits mit 0 beschreiben
74
  ADCSRB = 0x00; 
75
   initUART();
76
  SPI_SlaveInit();
77
  sei();
78
  
79
  for(;;){    
80
    if( getTemp == 0x01){
81
      adTemp = readADC(0xC8);
82
      _delay_ms(20);
83
      adTemp = readADC(0xC8);
84
      temperature = ((adTemp-335)*88)/100;
85
      getTemp = 0x00;
86
      char one = (adTemp & 0xFF00) >> 8;
87
      char two = (adTemp & 0x00FF);
88
      char three = (temperature & 0xFF00) >> 8;
89
      char four = (temperature & 0x00FF);
90
      UART_putc(one);
91
      UART_putc(two);
92
      UART_putc(three);
93
      UART_putc(four);
94
    }else if(sendZero == 0x01){
95
      UART_putc(spiChar);
96
      sendZero = 0x00;
97
    }else{
98
      i++;
99
      if ( i >= 200 )
100
        i = 0;
101
    }
102
  }
103
}

von Fabian B. (fabs)


Lesenswert?

Interner RC und RS232... du hast dein Problem gefunden.

Gruß
Fabian

von Frederik K. (n0ll4k)


Lesenswert?

komischerweise funktionier das bei beiden.

Echo Programm läuft ohne probleme.

von Christian D. (chris83)


Lesenswert?

Frederik Krämer schrieb:
> #ifndef F_CPU
> #define F_CPU 1000000UL
> #endif

Hat der interne RC vom ATMega88 nicht 8 MHz. ?

von Karl H. (kbuchegg)


Lesenswert?

Wenn wir mal davon ausgehen, dass UART bei diesen Temperaturen mit dem 
internen RC funktioniert:

Dein Testprogramm ist mir immer noch zu kompliziert. Wenn du SPI in 
Verdacht hast, würde ich da mal extrem abspecken.

Der eine schickt alle 1 Sekunde ein bestimmtes Byte (von mir aus auch 
ein hochzählender Zähler) zum anderen und der andere gibt das empfangene 
Byte über UART aus. Je weniger Subsysteme du im Einsatz hast (ADC etc) 
und je einfacher die generelle Programmlogik ist, desto eher 
kristallisiert sich heraus, ob du ein Problem mit dem SPI hast, oder ob 
du irgendwo anders eine Race-Condition hast, die dir bisher verborgen 
blieb. (Kurz gesagt: ein Logikfehler im Programm)

von Frederik K. (n0ll4k)


Lesenswert?

Christian D. schrieb:
> Frederik Krämer schrieb:
>> #ifndef F_CPU
>> #define F_CPU 1000000UL
>> #endif
>
> Hat der interne RC vom ATMega88 nicht 8 MHz. ?

CLKDIV Fuse ist natürlich aktiviert.

@ Karl-Heinz

Werde das Programm mal abspecken und testen.


Hab grad mal mitm Oszi das SCK und MOSI angeschaut. Ab und an sah man 
mal beides zeitgleich meist aber waren die Signale versetzt.

von Christian D. (chris83)


Lesenswert?

Dann passt es ja, wobei ein externer Quarz da sicherlich die bessere 
Wahl wäre.

von Frederik K. (n0ll4k)


Lesenswert?

So hab den Code mal vereinfacht.

Master:
1
#include <stdint.h>
2
#include <stdlib.h>
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
6
#ifndef F_CPU
7
#define F_CPU 1000000UL
8
#endif
9
#include <util/delay.h>
10
11
#define DDR_SPI DDRB
12
#define DD_MOSI DDB3
13
#define DD_SCK  DDB5
14
#define DD_SS  DDB2
15
#define SS    PB2
16
17
void init_spiMaster( void ){
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
void spiMasterTransmit(char cData)
25
{
26
/* Start transmission */
27
SPDR = cData;
28
/* Wait for transmission complete */
29
while(!(SPSR & (1<<SPIF)))
30
;
31
}
32
int main( void ){
33
  init_spiMaster();
34
  PORTB |= ( 1<<SS );
35
  for(;;){
36
    for(int i = 0; i <100; i++){
37
      _delay_ms(10);
38
    }
39
    PORTB &= ~ ( 1<<SS );
40
    _delay_us(150);  
41
    spiMasterTransmit(0xAA);
42
    _delay_ms(1);
43
    PORTB |= ( 1<<SS );
44
  }
45
}

Slave:
1
#include <stdint.h>
2
#include <stdlib.h>
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#ifndef F_CPU
6
#define F_CPU 1000000UL
7
#endif
8
#include <util/delay.h>
9
10
#define DDR_SPI DDRB
11
#define DD_MISO DDB4
12
#define sendByte(ch)    (UDR0=ch)
13
#define getByte()      (UDR0)
14
15
char sendChar = 0x00;
16
char spiChar;
17
18
void UART_putc(char c){
19
  while((UCSR0A & (1 << UDRE0)) == 0){};
20
  sendByte(c);
21
}
22
ISR(SPI_STC_vect){
23
  sendChar = 0x01;
24
  spiChar = SPDR;
25
  UART_putc(spiChar);
26
  PORTC ^= ( 1<<PC0 );
27
}
28
29
void initUART( void ){
30
31
  UCSR0B |= ( 1<<RXEN0 ) | ( 1<<TXEN0 );
32
  UCSR0B |= ( 1<<RXCIE0 );
33
34
  UCSR0C |= ( 1<<UCSZ00 ) | ( 1<<UCSZ01 );
35
36
  UCSR0A |= ( 1<<U2X0 );
37
38
  UBRR0 = 12;
39
40
}
41
void SPI_SlaveInit(void)
42
{
43
  /* Set MISO output, all others input */
44
  DDR_SPI = (1<<DD_MISO);
45
  /* Enable SPI */
46
  SPCR = (1<<SPIE)|(1<<SPE);
47
}
48
49
50
51
52
int main( void ){
53
  //DDRB |= ( 1<<DDB0);
54
   initUART();
55
  SPI_SlaveInit();
56
  sei();
57
  
58
  for(;;){    
59
60
  }
61
  
62
}

Wenn ich jetzt die Spannung anlege bekomme ich dann die Zeichen 
angezeigt. Es wird auch immer das selbe Zeichen gesendet.

Wenn ich nun die Spannung einmal wegnehme und wieder anlege kommt ein 
anders Zeichen. Vereinzelt kommt dann auch wirklich mal 0xAA an.

von Karl H. (kbuchegg)


Lesenswert?

Frederik Krämer schrieb:

> Wenn ich nun die Spannung einmal wegnehme und wieder anlege kommt ein
> anders Zeichen. Vereinzelt kommt dann auch wirklich mal 0xAA an.

Ähm.
Das ist klar, denn SPI an sich hat ja keine Möglichkeit die 
Synchronisierung des Senders mit dem Empfänger zu gewährleisten.
Wenn sich der Empfänger in eine laufende Sendung einklinkt, sieht er nur 
Pulse auf der Clock Leitung. Aber bei welchem Clock Puls das nächste 
Byte anfängt .... kann er nicht wissen. SPI ist eine rein synchrone 
Übertragung, da gibt es keinen Timeout oder so was.

Wenn der Slave 5 Bits empfangen hat und du drehst dem Sender den Saft 
ab, dann wartet der Slave auf die nächsten 3 Bits und hängt die an die 
bereits empfangenen 5 Bits an um daraus ein Byte zu formen. Dasss diese 
3 Bits eigentlich schon der Anfang des nächsten Bytes gewesen wären, 
weiß der Empfänger nicht.

von Frederik K. (n0ll4k)


Lesenswert?

Ich nehme auch bei beiden die Spannung weg.

Zur Synchronisation ist doch eigentlich die SS Leitung Verantwortlich, 
oder wie läuft das ?

von Karl H. (kbuchegg)


Lesenswert?

Frederik Krämer schrieb:
> Ich nehme auch bei beiden die Spannung weg.

Und in welcher Reihenfolge klemmst du sie wieder an?
Zuerst Empfänger dann Sender

> Zur Synchronisation ist doch eigentlich die SS Leitung Verantwortlich,
> oder wie läuft das ?

Aus dem Datenblatt des Mega16

Data is always shifted from Master to Slave on the Master Out – Slave 
In, MOSI, line, and from Slave to Master on the Master In – Slave Out, 
MISO, line. After each data packet, the Master will synchronize the 
Slave by pulling high the Slave Select, SS, line.
When configured as a Master, the SPI interface has no automatic control 
of the SS line. This must be handled by user software before 
communication can start.


Ich seh davon nichts in deinem Code.

von Frederik K. (n0ll4k)


Lesenswert?

Sind beide über eine Spannungsversorgung versorgt. Wenn ich am Board die 
Spannung anlege bekommen beide Spannung.

Hier toggle ich den SS Pin:
1
PORTB &= ~ ( 1<<SS );
2
spiMasterTransmit(0xAA);
3
PORTB |= ( 1<<SS );

Die Delays hab ich nu mal rausgenommen jetzt bekomm ich immer 0xA8 statt 
0xAA

von Karl H. (kbuchegg)


Lesenswert?

Frederik Krämer schrieb:

> Hier toggle ich den SS Pin:

Oops.
Mein Fehler, hab ich übersehen.

Hmm. So gesehen entspricht das IMHO dem Datenblatt

von Frederik K. (n0ll4k)


Lesenswert?

Kein Problem.

Dann bin ich wenigstens nicht alleine etwas ratlos warums nicht klappt.

Vielleicht hat ja noch wer anders ne Idee sonst muss ich mir evtl mal ne 
anderen Weg suchen.

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.