Forum: Mikrocontroller und Digitale Elektronik Raspberry Pi kommuniziert mit ATtiny2313 über SPI


von Jens K. (mister232)


Lesenswert?

Nachdem ihr mir bei der Kommunikation zwischen einem ATmega32 und einem 
ATtiny2313 so gut geholfen habt, folgt schon gleich das nächste Problem. 
Nun soll nämlich ein Raspberry Pi mit dem Tiny über SPI kommunizieren. 
Das Programm auf dem Tiny sieht wie folgt aus und funktioniert ja 
bereits:
1
/*
2
 * SPI_Slave.c
3
 *
4
 * Created: 07.07.2013 10:59:29
5
 *  Author: Jens
6
 */ 
7
8
// This program is for an SPI-Slave Tiny device
9
10
#include <avr/io.h>
11
#include <avr/interrupt.h>
12
#include <util/delay.h>
13
14
volatile int data;
15
16
// Initialize SPI-Interface
17
void init_spi()
18
{
19
  // Set DO as output and DI, USCK as input and set LED-Ports as output
20
  DDRB = (1<<DDB6) |  (1<<DDB2) | (1<<DDB3);
21
  DDRB &= ~(1<<DDB5) | ~(1<<DDB7);
22
  
23
  //Enable USI overflow interrupt, set three wire mode and set
24
  //  clock to External, positive edge.
25
  USICR = (1<<USIOIE) | (1<<USIWM0) | (1<<USICS1);
26
  
27
  //Clear overflow flag
28
  USISR = (1<<USIOIF);
29
  
30
  // Global enable interrupts
31
  sei();
32
}
33
34
ISR(USI_OVERFLOW_vect) 
35
{
36
  // Return data register
37
  data = USIDR;
38
  
39
    // Reset counter-overflow Flag   
40
   USISR = (1<<USIOIF);  
41
}
42
43
int main(void)
44
{
45
  init_spi();
46
  
47
  // If data is received via SPI, check if it is an '5' or an '6'. 
48
  // Then turn on the corresponding LED
49
    while(1)
50
  {  
51
    // Check if "data" includes an '5' -> Turn on LED1 PB2
52
    if(data == '5')
53
    {
54
        PORTB = (1<<PB2);
55
        PORTB &= ~(1<<PB3);
56
    }
57
    // Check if "data" includes an '6' -> Turn on LED2 PB3
58
    else if(data == '6')
59
    {
60
        PORTB = (1<<PB3);
61
        PORTB &= ~(1<<PB2);
62
    }
63
    }
64
  
65
  return 0;
66
}
Nun folgt der Code für das Raspberry:
1
#include <bcm2835.h>
2
#include <stdio.h>
3
4
int main()
5
{
6
      if (!bcm2835_init())
7
        return 1;
8
9
    bcm2835_spi_begin();
10
    bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);      // The default
11
    bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);                   // The default
12
    bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_65536); // The default
13
    bcm2835_spi_chipSelect(BCM2835_SPI_CS0);                      // The default
14
    bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW);      // the default
15
    
16
    int data = '5';
17
    int i, j;
18
19
    for(i = 0; i < 50; i++)
20
    {
21
  if(data == '5')
22
  {
23
    bcm2835_spi_transfer(data);
24
    data = '6';
25
    printf("5 sended...\n");
26
  }
27
  else
28
  {
29
    bcm2835_spi_transfer(data);
30
    data = '5';
31
    printf("6 sended...\n");
32
  }
33
        // Wait before sending the next byte
34
  for(j = 0; j < 50000000; j++){}
35
    }
36
37
    bcm2835_spi_end();
38
    bcm2835_close();
39
40
    return 0;
41
}

Es sei mir verziehen, dass noch nicht so viele Kommentare drin sind. 
Habe den Code gerade aus der BCM2835-Seite zusammengebaut und bin noch 
am Testen.

Starte ich das Programm, so wechseln die LEDs am ATtiny2313 nur ganz 
selten, wenn überhaupt. Woran kann das liegen? Falscher Takt? Brauche 
ich einen externen Taktgeber am Tiny?

von Jens K. (mister232)


Lesenswert?

Keine Raspberry-Freaks hier?

von holger (Gast)


Lesenswert?

>Keine Raspberry-Freaks hier?

Die sind beim Abendessen.

>  // Wait before sending the next byte
>  for(j = 0; j < 50000000; j++){}

Alter vergiss die Kacke. Benutz ne Slave Select
Leitung für SPI sonst kommt der nie wieder in Sync
wenn er einmal raus ist. Da nützt dein Delay gar nichts.

>Falscher Takt?

Wie schnell ist der denn?

von holger (Gast)


Lesenswert?

>Benutz ne Slave Select
>Leitung für SPI sonst kommt der nie wieder in Sync

>The USI Three-wire mode is compliant to the Serial Peripheral Interface >(SPI) 
mode 0 and 1, but
>does not have the slave select (SS) pin functionality.

Oh, der hat ja gar keine;) Nimm nen ATMeg88 oder deinen
ATmega32.

von Jens K. (mister232)


Lesenswert?

Aber zwischen nem Atmega32 und nem Tiny2313 ging es ja auch ohne SS. Das 
Delay soll nur dazu dienen, dass man das blinken auch sehen kann. Den 
Takt dachte ich stelle ich mit dem
1
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_65536);
ein?

von holger (Gast)


Lesenswert?

>Aber zwischen nem Atmega32 und nem Tiny2313 ging es ja auch ohne SS.

Zufall. Wenn auf der SPI Clock Leitung auch nur ein Glitch kommt
verrutscht deine Übertragung um ein oder bei mehreren Glitches
um mehrere Bit. Da hilft dir ein Delay gar nichts. Du wirst ohne
Slave Select Leitung nie wieder in Sync kommen. Es sei denn du
synchronisierst Master und Slave durch Bitbanging auf der Clock
Leitung erst mal.

Mach es richtig oder lass es sein.

von Moritz A. (moritz_a)


Lesenswert?

Application Note dazu ist http://www.atmel.com/Images/doc2582.pdf, 
leider etwas kurz gehalten. Der Beispielcode dazu ist 
http://www.atmel.com/dyn/resources/prod_documents/AVR319.zip.

> When using the USI as a SPI slave, you also need to watch
> the incoming SS line with for instance an interrupt line if
> required.
> The driver in this application note does not use the SS line.

Was spricht denn dagegen, SS auf einen Interruptpin zu legen, und in der 
ISR das USI-Modul sauber zu initialisieren bzw zu deaktivieren, je nach 
Flanke?

von Jens K. (mister232)


Lesenswert?

Und wie würde soetwas aussehen? Würde ich dann die Daten in der ISR für 
den externen Interrupt abholen und die andere ISR weglassen oder wie? 
Vielleicht ein Codeschnippsel?

von Jens K. (mister232)


Lesenswert?

Beim Master setzte ich den SS ja einfach bevor ich sende und rücksetzte 
ihn, sobald ich die 5 oder 6 übertragen habe oder?

von Moritz A. (moritz_a)


Lesenswert?

Codeschnipsel habe ich nicht, aber so in etwa würde ich es wohl 
implementieren:

Flanke !SS H-L
SPI aktiviert, einmal
1
ISR(INT0_vect) {
2
  // clear overflow flag, reset counter
3
  USISR = (1<<USIOIF);
4
}
Damit ist sichergestellt dass du wirklich "bei 0" startest, und bei 
Glitches nicht um ein bit im byte verschoben bist. Wie viele byte du 
überträgst bevor du !SS einmal toggelst musst du testen, am Anfang 
kannst du es ja mal wirklich nach jedem machen.

Flanke !SS L-H

Eigentlich musst du nix machen, da du ja nach 8bit automatisch den 
overflow interrupt bekommst und die Daten ausliest. Wenn du das ganze 
universell und sauber halten willst setzt du hier DO noch auf input ohne 
pullup, dass dir bei einem anderen Slave am gleichen Bus nicht die 
Kommunikation zerstört wird.

von Jens K. (mister232)


Lesenswert?

Okay, ich werde es nachher mal überarbeiten und hier posten. Bin bis 
Sonntag nicht zu Hause und kann es somit nicht testen. Vielleicht bekomm 
ich es mit eurer Hilfe aber soweit hin, dass ich das Programm nur noch 
reinflashen muss.

von Moritz A. (moritz_a)


Lesenswert?

Dann bin ich mal gespannt, ich habe hier nämlich auch ein Projekt 
liegen, bei dem ich genau das selbe machen will. Nur braucht das noch 
etwas bis ich dazu kommen werde.

von Peter D. (peda)


Lesenswert?

Bisher sind das alles nur Spielereien, die Du machst. Sie haben 
keinerlei praktischen Wert.
Niemand überträgt nur ein Byte und wartet danach 500ms.

Nutzbringend wäre das ganze erst dann, wenn Du auf dem Raspberry Pi das 
SPI per DMA machst, d.h. auch mal eine sinnvolle Datenrate überträgst.
Dann wirst Du merken, daß das SPI des AVR Dich total ausbremst bzw. 
massig Daten verliert.
Das SPI des AVR eignet sich einfach nicht als Slave. Da kannst Du noch 
soviel rumprogrammieren, es geht nicht.

Besser ist daher die UART (gepuffert) oder das I2C (Handshake über SCL) 
zu nehmen.

von Jens K. (mister232)


Lesenswert?

Du hast recht, noch sind das nur Spielereien um SPI kennenzulernen. 
Später bekommt das Raspberry ein Funkmodul, welches es über SPI 
anspricht. Auf der anderen Seite sind mehrere Tinys, welche auch ein 
Funkmodul besitzen. Diese steuern dann verschiedene Sachen wie z.B. 
Steckdosen oder Rolladen an. Das Raspberry erhält dann eine schöne 
grafische Oberfläche und einen Touchscreen. Das ganze soll dann eine 
Heimautomatisierung werden, ist aber eher ein Projekt auf mehrere Jahre, 
da ich nun in zwei Wochen erstmal meine Bachelorarbeit schreibe

von Peter D. (peda)


Lesenswert?

Jens K. schrieb:
> Auf der anderen Seite sind mehrere Tinys, welche auch ein
> Funkmodul besitzen.

Dann sind aber die Funkmodule SPI-Slave und alle AVRs SPI-Master.
Wozu also sich abquälen mit Dingen, die Du nirgends brauchst?

Ansonsten, für längere Drahtverbindungen (>50cm) ist CAN-Bus die erst 
Wahl.
SPI wäre nur der Garant für: "Das wird nie was".

von Moritz A. (moritz_a)


Lesenswert?

Hier habe ich erfolgreich mit 
http://www.mikrocontroller.net/articles/NRF24L01_Tutorial gearbeitet, 
und die SPI-Lib so angepasst dass sie auch mit einem attiny2313a als 
Master tut, siehe 
https://github.com/maugsburger/rfdht22/blob/master/src/lib

von Jens K. (mister232)


Lesenswert?

Den AVR als Slave möchte ich nutzen um zu testen ob das SPI am Radpberry 
richtig funktioniert. Sonst weiß ich ja später nicht ob ich das 
Funkmodul falsch ansteure oder ob das Raspberry nur Müll sendet.

von Moritz A. (moritz_a)


Lesenswert?

Zum Testen vom SPI am Raspi tut doch auch ein einfaches Schieberegister, 
das agiert deutlich vorhersehbarer. Einfach an jeden Pin eine LCD, und 
du müsstest sehen ob das Muster das du rausschiebst auch passend 
ankommt.

von Jens K. (mister232)


Lesenswert?

Nichts desto trotz hier der Code für den Tiny2313 Slave:
1
/*
2
 * SPI_Slave.c
3
 *
4
 * Created: 07.07.2013 10:59:29
5
 *  Author: Jens
6
 */ 
7
8
// This program is for an SPI-Slave Tiny device
9
10
#include <avr/io.h>
11
#include <avr/interrupt.h>
12
#include <util/delay.h>
13
14
volatile int data;
15
16
// Initialize SPI-Interface
17
void init_spi()
18
{
19
  // Set DO, PB2 (LED1) and PB3 (LED2) as output and rest as input
20
  DDRB = (1<<DDB6) | (1<<DDB2) | (1<<DDB3);
21
  
22
  //Enable USI overflow interrupt, set three wire mode and set
23
  //  clock to External, positive edge.
24
  USICR = (1<<USIOIE) | (1<<USIWM0) | (1<<USICS1);
25
  
26
  // Global enable interrupts
27
  sei();
28
}
29
30
ISR(INT0_vect)
31
{
32
  // Reset counter-overflow Flag
33
  USISR = (1<<USIOIF);
34
}
35
36
ISR(USI_OVERFLOW_vect) 
37
{
38
  // Return data register
39
  data = USIDR;
40
  
41
    // Reset counter-overflow Flag   
42
   USISR = (1<<USIOIF);  
43
}
44
45
int main(void)
46
{
47
  init_spi();
48
  
49
  // If data is received via SPI, check if it is an '5' or an '6'. 
50
  // Then turn on the corresponding LED
51
    while(1)
52
  {  
53
    // Check if "data" includes an '5' -> Turn on LED1 PB2
54
    if(data == '5')
55
    {
56
        PORTB = (1<<PB2);
57
        PORTB &= ~(1<<PB3);
58
    }
59
    // Check if "data" includes an '6' -> Turn on LED2 PB3
60
    else if(data == '6')
61
    {
62
        PORTB = (1<<PB3);
63
        PORTB &= ~(1<<PB2);
64
    }
65
    }
66
  
67
  return 0;
68
}

Was meint ihr davon, würde das so funktionieren?
Man muss schließlich von ganz unten anfangen, bevor man irgendwelche 
großen Prozessoren mit viel aufwändigem Code und perfektem Timing 
programmiert. Da kann ruhig mal das ein odere andere Bit verloren gehen 
;-)

von Moritz A. (moritz_a)


Lesenswert?

Ich sehe jetzt nicht, dass du für den SS-Pin Interrupts aktivierst.


Packe das noch mit in spi_init()
1
    // Initialize external interrupt 0 (PD2)
2
    MCUCR |= (1<<ISC01);
3
    MCUCR &= ~(0<<ISC00);  // Set external interupt on falling edge
4
    GIMSK |= (1<<INT0);    // Activate INT0
5
    DDRD &= ~(1<<PD2);      // Interrupt Input PIN

von Jens K. (mister232)


Lesenswert?

Also so:
1
/*
2
 * SPI_Slave.c
3
 *
4
 * Created: 07.07.2013 10:59:29
5
 *  Author: Jens
6
 */ 
7
8
// This program is for an SPI-Slave Tiny device
9
10
#include <avr/io.h>
11
#include <avr/interrupt.h>
12
#include <util/delay.h>
13
14
volatile int data;
15
16
// Initialize SPI-Interface
17
void init_spi()
18
{
19
  // Set DO, PB2 (LED1) and PB3 (LED2) as output and rest as input
20
  DDRB = (1<<DDB6) | (1<<DDB2) | (1<<DDB3);
21
  
22
  //Enable USI overflow interrupt, set three wire mode and set
23
  //  clock to External, positive edge.
24
  USICR = (1<<USIOIE) | (1<<USIWM0) | (1<<USICS1);
25
  
26
  // Initialize external interrupt 0 (PD2)
27
  MCUCR |= (1<<ISC01);
28
  MCUCR &= ~(0<<ISC00);  // Set external interupt on falling edge
29
  GIMSK |= (1<<INT0);    // Activate INT0
30
  DDRD &= ~(1<<PD2);      // Interrupt Input PIN
31
32
  // Global enable interrupts
33
  sei();
34
}
35
36
ISR(INT0_vect)
37
{
38
  // Reset counter-overflow Flag
39
  USISR = (1<<USIOIF);
40
}
41
42
ISR(USI_OVERFLOW_vect) 
43
{
44
  // Return data register
45
  data = USIDR;
46
  
47
    // Reset counter-overflow Flag   
48
   USISR = (1<<USIOIF);  
49
}
50
51
int main(void)
52
{
53
  init_spi();
54
  
55
  // If data is received via SPI, check if it is an '5' or an '6'. 
56
  // Then turn on the corresponding LED
57
    while(1)
58
  {  
59
    // Check if "data" includes an '5' -> Turn on LED1 PB2
60
    if(data == '5')
61
    {
62
        PORTB = (1<<PB2);
63
        PORTB &= ~(1<<PB3);
64
    }
65
    // Check if "data" includes an '6' -> Turn on LED2 PB3
66
    else if(data == '6')
67
    {
68
        PORTB = (1<<PB3);
69
        PORTB &= ~(1<<PB2);
70
    }
71
    }
72
  
73
  return 0;
74
}

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.