Forum: Mikrocontroller und Digitale Elektronik Attiny13 SoftSPI Slave


von Armin W. (wolf01705)


Angehängte Dateien:

Lesenswert?

Hallo,

bei meinem momentanen Projekt mit dem Attiny13 (SoftSPI Slave) hat sich 
irgendwo ein Fehler eingeschlichen, welchen ich auch nach mehreren 
Versuchen nicht finden kann.

Versuchsaufbau:

- Arduino Uno (SoftSPI.ino) als Master, welcher das über SPI empfangene 
Byte im seriellen Monitor ausgibt
1
#include <SPI.h>
2
#define SS 9
3
4
int i = 0;
5
6
void setup() {
7
  Serial.begin(9600);
8
  // put your setup code here, to run once:
9
  pinMode(SS, OUTPUT);
10
  pinMode(10, OUTPUT);
11
  digitalWrite(SS, HIGH);
12
}
13
14
void loop() {
15
  // put your main code here, to run repeatedly:
16
  digitalWrite(SS, LOW);
17
  //delay(500);
18
  SPI.beginTransaction(SPISettings(100, MSBFIRST, SPI_MODE2));
19
  i = SPI.transfer(0);
20
  SPI.endTransaction();
21
  digitalWrite(SS, HIGH);
22
  Serial.print(i);
23
  delay(500);
24
}

- Attiny13 (main.c) als Slave, welcher ständig 193 per SPI sendet
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define MOSI PB4
5
#define MISO PB2
6
#define SCK PB1 //INTO
7
#define SS PB3 //PCINT3
8
9
#define Data 0b11000001
10
11
volatile uint8_t SPI_input = 0;
12
volatile uint8_t SPI_output = Data;
13
14
/*SPI MODE = 2*/
15
16
ISR(INT0_vect) {
17
    /*Wenn sinkende Flanke auf SCK, dann MOSI auslesen und als LSB in SPI_input speichern, dann eins nach links Schieben (MSB-Modus)
18
     Danach MSB von SPI_output auslesen und auf MISO legen, SPI_output eins nach links schieben*/  
19
    if(PINB & (1 << MOSI)) { //Wenn MOSI == HIGH
20
        SPI_input |= (1 << 0);
21
    }
22
    SPI_input << 1;
23
    if(SPI_output & (1 << 7)) { //Wenn MSB SPI_output == 1
24
        PORTB |= (1 << MISO);
25
    } else {
26
        PORTB &= ~(1 << MISO);
27
    }
28
    SPI_output << 1;
29
}
30
31
void SPI() {
32
    SPI_output = Data; //Wichtig! SPI_output nach schieben wiederherstellen (Anwendungsspezifisch)
33
}
34
35
ISR(PCINT0_vect) {
36
    /*Wenn SS auf Low gezogen wird INT0 aktivieren, wenn SS auf HIGH gezogen wird INT0 deaktivieren und SPI() ausführen*/
37
    if(PINB & (1 << SS)) { /*Wenn SS == HIGH --> SPI()*/
38
        GIMSK &= ~(1 << INT0);
39
        SPI();
40
    } else { //Wenn SS == LOW --> INT0
41
        GIMSK |= (1 << INT0);
42
    }
43
}
44
45
void init_SPI() {
46
    /*PCINT3 aktivieren und INT0 auf sinkende Flanke umstellen*/
47
    PCMSK |= (1 << PCINT3);
48
    MCUSR |= (1 << ISC01);
49
    GIMSK |= (1 << PCIE);
50
}
51
52
int main(void) {
53
    DDRB |= (1 << MISO);
54
    init_SPI();
55
    sei(); //Interrupts aktivieren!
56
    while(1) {
57
        //nix
58
    }
59
}

Problem: Bei einem Byte beginnend mit 0/1 gibt der Arduino 0/255 
unabhängig des restlichen Bytes aus.

Die Verkabelung habe ich schon mehrmals geprüft, ist also soweit korrekt
(MISO -- MISO, MOSI -- MOSI, ...).

Kann jemand denn Fehler im Arduino-/Attiny-Programm finden?

von Motlib (Gast)


Lesenswert?

In deinem INT0 Interrupt handler shiftest Du SPI_input und SPI_output, 
verwirfst aber die Ergebnisse. Du musst <<= anstelle << verwenden.

von Armin W. (wolf01705)


Lesenswert?

Danke, das Ergebnis ändert sich jedoch nur dahingehend, das der Arduino 
0/255 nun ohne Bezug zu den Sendedaten ausgibt.

von Sascha R. (srt2018)


Lesenswert?

Armin W. schrieb:
> void setup() {
>   Serial.begin(9600);
>   // put your setup code here, to run once:
>   pinMode(SS, OUTPUT);
>   pinMode(10, OUTPUT);
>   digitalWrite(SS, HIGH);
> }

In setup() fehlt SPI.begin() Aufruf.

von Armin W. (wolf01705)


Lesenswert?

Auch das behebt den Fehler leider nicht.
1
#include <SPI.h>
2
#define SS 9
3
4
int i = 0;
5
6
void setup() {
7
  Serial.begin(9600);
8
  // put your setup code here, to run once:
9
  pinMode(SS, OUTPUT);
10
  pinMode(10, OUTPUT);
11
  digitalWrite(SS, HIGH);
12
  SPI.begin();
13
}
14
15
void loop() {
16
  // put your main code here, to run repeatedly:
17
  //delay(500);
18
  SPI.beginTransaction(SPISettings(100, MSBFIRST, SPI_MODE2));
19
  digitalWrite(SS, LOW);
20
  delay(20);
21
  i = SPI.transfer(0);
22
  SPI.endTransaction();
23
  digitalWrite(SS, HIGH);
24
  Serial.println(i);
25
  delay(500);
26
}

Der momentane Attiny-Code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define MOSI PB4
5
#define MISO PB2
6
#define SCK PB1 //INTO
7
#define SS PB3 //PCINT3
8
9
#define Data 0b11000001
10
11
volatile uint8_t SPI_input = 0;
12
volatile uint8_t SPI_output = Data;
13
14
/*SPI MODE = 2*/
15
16
ISR(INT0_vect) {
17
    /*Wenn sinkende Flanke auf SCK, dann MOSI auslesen und als LSB in SPI_input speichern, dann eins nach links Schieben (MSB-Modus)
18
     Danach MSB von SPI_output auslesen und auf MISO legen, SPI_output eins nach links schieben*/  
19
    if(PINB & (1 << MOSI)) { //Wenn MOSI == HIGH
20
        SPI_input |= (1 << 0);
21
    }
22
    SPI_input <<= 1;
23
    if(SPI_output & (1 << 7)) { //Wenn MSB SPI_output == 1
24
        PORTB |= (1 << MISO);
25
    } else {
26
        PORTB &= ~(1 << MISO);
27
    }
28
    SPI_output <<= 1;
29
}
30
31
void SPI() {
32
    SPI_output = Data; //Wichtig! SPI_output nach schieben wiederherstellen (Anwendungsspezifisch)
33
    PORTB |= (1 << PB0);
34
}
35
36
ISR(PCINT0_vect) {
37
    /*Wenn SS auf Low gezogen wird INT0 aktivieren, wenn SS auf HIGH gezogen wird INT0 deaktivieren und SPI() ausführen*/
38
    if(PINB & (1 << SS)) { /*Wenn SS == HIGH --> SPI()*/
39
        GIMSK &= ~(1 << INT0);
40
        SPI();
41
    } else { //Wenn SS == LOW --> INT0
42
        GIMSK |= (1 << INT0);
43
    }
44
    PORTB &= ~(1 << PB0);
45
}
46
47
void init_SPI() {
48
    /*PCINT3 aktivieren und INT0 auf sinkende Flanke umstellen*/
49
    PCMSK |= (1 << PCINT3);
50
    MCUSR |= (1 << ISC01);
51
    GIMSK |= (1 << PCIE);
52
}
53
54
int main(void) {
55
    DDRB |= (1 << MISO) | (1 << PB0);
56
    init_SPI();
57
    sei(); //Interrupts aktivieren!
58
    while(1) {
59
        //nix
60
    }
61
}

: Bearbeitet durch User
von Sascha W. (sascha-w)


Lesenswert?

Hallo,

ist:
MCUSR |= (1 << ISC01);
soll:
MCUCR |= (1 << ISC01);

Sascha

von Armin W. (wolf01705)


Lesenswert?

Danke für den Tipp, das Ergebnis ist leider immer noch das gleiche.

Kann es sein das der Arduino Uno einfach nicht langsam genug kann für 
den 1,2MHz Attiny13?

Und kurze Verständnisfrage: Im SPI-Modus 2 legt der Master seine Daten 
schon bei der ersten fallenden Flanke von SCK an MOSI, bei einer 
steigenden Flanke übernimmt er die Daten von MISO, oder?

Sonst würde ich noch einen SoftSPI-Master für den Attiny13 schreiben...

: Bearbeitet durch User
von Sascha W. (sascha-w)


Lesenswert?

Armin W. schrieb:
> Danke für den Tipp, das Ergebnis ist leider immer noch das gleiche.
:(
mach doch am UNO mal eine Verbindung MISO-MOSI, dann solltest du das 
gesendete zurückbekommen. Damit weißt du schon mal das der Master 
funktioniert - auch wenn du eine fertige Implementierung verwendest.

> Kann es sein das der Arduino Uno einfach nicht langsam genug kann für
> den 1,2MHz Attiny13?
naja, bei 100Hz Clock sollte das reichen - da kannst du ja fast schon 
mitschreiben. Vorrausgesetzt der Master geht überhaupt auf einen so 
kleinen wert einzustellen?

> Und kurze Verständnisfrage: Im SPI-Modus 2 legt der Master seine Daten
> schon bei der ersten fallenden Flanke von SCK an MOSI, bei einer
> steigenden Flanke übernimmt er die Daten von MISO, oder?
ja - sollte auch mit deinem Code passen

> Sonst würde ich noch einen SoftSPI-Master für den Attiny13 schreiben...
zwei Master??

Sascha

von Armin W. (wolf01705)


Lesenswert?

Sascha W. schrieb:
> Armin W. schrieb:
>> Danke für den Tipp, das Ergebnis ist leider immer noch das gleiche.
> :(
> mach doch am UNO mal eine Verbindung MISO-MOSI, dann solltest du das
> gesendete zurückbekommen. Damit weißt du schon mal das der Master
> funktioniert - auch wenn du eine fertige Implementierung verwendest.
>
>> Kann es sein das der Arduino Uno einfach nicht langsam genug kann für
>> den 1,2MHz Attiny13?
> naja, bei 100Hz Clock sollte das reichen - da kannst du ja fast schon
> mitschreiben. Vorrausgesetzt der Master geht überhaupt auf einen so
> kleinen wert einzustellen?
>
>> Und kurze Verständnisfrage: Im SPI-Modus 2 legt der Master seine Daten
>> schon bei der ersten fallenden Flanke von SCK an MOSI, bei einer
>> steigenden Flanke übernimmt er die Daten von MISO, oder?
> ja - sollte auch mit deinem Code passen
>
>> Sonst würde ich noch einen SoftSPI-Master für den Attiny13 schreiben...
> zwei Master??
>
> Sascha

1. Hab ich getestet, funktioniert

2. Der minimale SPI-Takt des Arduino Uno beträgt 16000000 Hz / 128 = 
125000Hz. Vieleicht doch etwas zu viel(Interrupt-Latenz, Overhead, ...)?

3. Ich meinte einen Attiny13 SPI-Master für meinen Attiny13 SPI-Slave.
Werde ich mal versuchen, denn die Taktfrequenz wäre ja dafür relativ 
egal.

von Sascha W. (sascha-w)


Lesenswert?

Armin W. schrieb:
> 2. Der minimale SPI-Takt des Arduino Uno beträgt 16000000 Hz / 128 =
> 125000Hz. Vieleicht doch etwas zu viel(Interrupt-Latenz, Overhead, ...)?
Dann hast du am tn13 ja nur 10Takte pro Bit - das wird nichts.

Sascha

von Armin W. (wolf01705)


Lesenswert?

Bin mit dem SoftSPI-Master für den Attiny13 fertig --> funktioniert 
(beide LEDs an PortB0 leuchten, wenn Daten korrekt)!

Master:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define MOSI PB4
5
#define MISO PB2
6
#define SCK PB1
7
#define SS PB3
8
9
#define Eingabe 123
10
#define Ausgabe 67
11
12
volatile uint8_t SPI_input = 0;
13
volatile uint8_t SPI_output = 0;
14
volatile uint8_t counter = 0;
15
volatile uint8_t SPI_Flag = 0;
16
17
/*SPI MODE = 2*/
18
19
ISR(TIM0_OVF_vect) {
20
    if(counter == 16) { //Wenn ein Byte übertragen
21
        cli();
22
        TCCR0B &= ~(1 << CS00); //Timer stoppen
23
        TIFR0 |= (1 << TOV0); //Eventuell aufgetrettende Interrupts löschen
24
        SPI_Flag = 2;
25
        counter = 0;
26
    } else {
27
        counter++;
28
    }
29
    /*MSB von SPI_output auf MOSI geben, SCK auf Low ziehen, SPI_output eins nach links schieben*/
30
    if(SPI_Flag == 0) {
31
        if(SPI_output & (1 << 7)) { //Wenn MSB von SPI_output gesetzt
32
            PORTB |= (1 << MOSI); //MOSI HIGH
33
        } else {
34
            PORTB &= ~(1 << MOSI);
35
        }
36
        PORTB &= ~(1 << SCK); //Fallende Flanke an SCK
37
        SPI_output <<= 1;
38
        SPI_Flag = 1;
39
    } else { //wichtig
40
        /*ZUERST SPI_input eins nach links schieben, MISO als LSB von SPI_input machen*/
41
        if(SPI_Flag == 1) {
42
            SPI_input <<= 1;
43
            PORTB |= (1 << SCK); //Steigende Flanke
44
            if(PINB & (1 << MISO)) { //Wenn MISO HIGH
45
                SPI_input |= (1 << 0); //LSB von SPI_input setzen
46
            }
47
            SPI_Flag = 0;
48
        }
49
    }
50
}
51
52
void init_SPI() {
53
    /*TimerOVF-Interrupt aktivieren*/
54
    TIMSK0 |= (1 << TOIE0);
55
}
56
57
uint8_t SPI() {
58
    /*Timer aktivieren (Prescaler == 1)*/
59
    PORTB &= ~(1 << SS);
60
    TCCR0B |= (1 << CS00);
61
    while(SPI_Flag != 2) {
62
        //nix
63
    }
64
    SPI_Flag = 0;
65
    //PORTB |= (1 << PB0); //Debug
66
    PORTB |= (1 << SS);
67
}
68
69
int main(void) {
70
    uint8_t Daten = 0;
71
    DDRB |= (1 << MOSI) | (1 << SCK) | (1 << PB0) | (1 << SS);
72
    PORTB |= (1 << SCK) | (1 << SS);
73
    init_SPI();
74
    sei(); //Interrupts aktivieren!
75
    SPI_output = Ausgabe;
76
    SPI();
77
    if(SPI_input == Eingabe) {
78
        PORTB |= (1 << PB0);
79
    } else {
80
        PORTB &= ~(1 << PB0);
81
    }
82
    while(1) {
83
        //nix
84
    }
85
}

Slave:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define MOSI PB4
5
#define MISO PB2
6
#define SCK PB1 //INTO
7
#define SS PB3 //PCINT3
8
9
#define Eingabe 67
10
#define Ausgabe 123
11
12
volatile uint8_t SPI_input = 0;
13
volatile uint8_t SPI_output = Ausgabe;
14
15
/*SPI MODE = 2*/
16
17
ISR(INT0_vect) {
18
    /*Wenn sinkende Flanke auf SCK, dann ZUERST SPI_inputeins nach links schieben, MOSI auslesen und als LSB in SPI_input speichern (MSB-Modus) Danach MSB von SPI_output auslesen und auf MISO legen, SPI_output eins nach links schieben*/  
19
    SPI_input <<= 1;
20
    if(PINB & (1 << MOSI)) { //Wenn MOSI == HIGH
21
        SPI_input |= (1 << 0);
22
    }
23
    if(SPI_output & (1 << 7)) { //Wenn MSB SPI_output == 1
24
        PORTB |= (1 << MISO);
25
    } else {
26
        PORTB &= ~(1 << MISO);
27
    }
28
    SPI_output <<= 1;
29
}
30
31
void SPI() {
32
    SPI_output = Ausgabe; //Wichtig! SPI_output nach schieben wiederherstellen (Anwendungsspezifisch)
33
    if(SPI_input == Eingabe) {
34
        PORTB |= (1 << PB0);
35
    }
36
}
37
38
ISR(PCINT0_vect) {
39
    /*Wenn SS auf Low gezogen wird INT0 aktivieren, wenn SS auf HIGH gezogen wird INT0 deaktivieren und SPI() ausführen*/
40
    if(PINB & (1 << SS)) { /*Wenn SS == HIGH --> SPI()*/
41
        GIMSK &= ~(1 << INT0);
42
        SPI();
43
        //Wichtig! SPI_output nach schieben wiederherstellen (Anwendungsspezifisch)
44
    } else { //Wenn SS == LOW --> INT0
45
        GIFR |= (1 << INTF0);
46
        GIMSK |= (1 << INT0);
47
    }
48
}
49
50
void init_SPI() {
51
    /*PCINT3 aktivieren und INT0 auf sinkende Flanke umstellen*/
52
    PCMSK |= (1 << PCINT3);
53
    MCUCR |= (1 << ISC01);
54
    GIMSK |= (1 << PCIE);
55
}
56
57
int main(void) {
58
    DDRB |= (1 << MISO) | (1 << PB0);
59
    //PORTB |= (1 << SS);
60
    init_SPI();
61
    sei(); //Interrupts aktivieren!
62
    while(1) {
63
        //nix
64
    }
65
}

Dabei hat sich ein Attiny13, nachdem avrdude einen content mismatch 
gemeldet hat, nicht mehr beim Programmer gemeldet :-( R.I.P.

Da ich jedoch noch einen Attiny13 von einem früheren Projekt hatte, 
konnte ich das ganze trotzdem noch überprüfen.

Zuletzt stellt sich mir nur noch die Frage wiso eine deaktivierung des 
Timers mit
1
TCCR0B &= ~(1 << CS00);
 keinen anschließenden Timer-Overflow-Interrupt verhindert 
(dazugehöhrige Flag wird parallel dazu deaktiviert). Erst mit
1
cli();
 kann ich das Verhindern.

Weiss jemand wiso?

von Sascha W. (sascha-w)


Lesenswert?

Armin W. schrieb:
> Zuletzt stellt sich mir nur noch die Frage wiso eine deaktivierung des
> Timers mit
1
TCCR0B &= ~(1 << CS00);
> keinen anschließenden
> Timer-Overflow-Interrupt verhindert (dazugehöhrige Flag wird parallel
> dazu deaktiviert).
> Erst mit
1
cli();
> kann ich das Verhindern.
Also mit dem Deaktivieren des Clocks stoppt der Timer und löst auch 
keine INT's mehr aus. Das TOV0 von Hand löschen zu wollen ist 
überflüssig, da dieses mit Eintritt in die ISR automatisch gelöscht 
wird.
Im übrigen verwendet man bei diesen Registern ein einfaches Write!
1
TIFR0 = (1 << TOV0);
Ein Read-Modify-Write wie bei dir würde im Zweifelsfall dazu führen, das 
ein weiteres gesetztes Flag wie z.B. OCF0A dann ebenfalls gelöscht 
würde!

Wie merkst du denn das die ISR weiterhin aufgerufen wird?

Sascha

von Armin W. (wolf01705)


Lesenswert?

Eine Debug-Led (PORTB |= (1 << LEDPIN);) leuchtet nicht (Befehl sollte 
nach SPI-Transfer ausgeführt werden).
Liegt wohl daran, das ich das Register verodere.

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.