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
volatileintdata;
15
16
// Initialize SPI-Interface
17
voidinit_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
intmain(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
elseif(data=='6')
59
{
60
PORTB=(1<<PB3);
61
PORTB&=~(1<<PB2);
62
}
63
}
64
65
return0;
66
}
Nun folgt der Code für das Raspberry:
1
#include<bcm2835.h>
2
#include<stdio.h>
3
4
intmain()
5
{
6
if(!bcm2835_init())
7
return1;
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
intdata='5';
17
inti,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
return0;
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?
>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?
>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.
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
>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.
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?
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?
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.
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.
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.
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.
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
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".
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.
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.
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
volatileintdata;
15
16
// Initialize SPI-Interface
17
voidinit_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
intmain(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
elseif(data=='6')
61
{
62
PORTB=(1<<PB3);
63
PORTB&=~(1<<PB2);
64
}
65
}
66
67
return0;
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
;-)