Forum: Mikrocontroller und Digitale Elektronik Attiny 2313 als SPI Slave - Übertragungsprobleme


von Dominique S. (mronak)


Lesenswert?

Hallo zusammen,

nachdem ich das Forum schon einige Zeit anonym stalke ist nun die Zeit 
gekommen hier eine Frage zu stellen :-/.

Ich versuche einen ATTiny2313 als SPI Slave zu betreiben, gefüttert von 
einem Arduino Uno als SPI Master. Die Daten kommen jedoch nicht oder 
nicht korrekt an. Wenn ich in der Interrupt Routine des Slaves debugge 
(LED an/aus) dann wird diese aufgerufen. Jedoch kommen die Daten die ich 
per SPI übertrage nicht an.

Zu den Problemen im Detail noch mehr weiter unten.

Der Test besteht daraus, auf dem Uno das Drücken eines Pushbuttons an 
Pin 3 auszulesen, eine LED am eigenen Pin 2 umzuschalten (drücken -> an, 
erneut drücken -> aus) und via SPI Kommandos an den ATtiny zu schicken 
(an = 0b10011001, aus = 0x00) der dann seinerseits ebenfalls eine LED 
togglen soll.

Das Beispiel ist relativ dumm, aber ich dachte ich fange mit etwas 
simplem an bevor ich "echte" Befehle und Daten via SPI übertrage.

Ich habe viel Zeit mit der Suche verbracht aber ATTinys als SPI Slave 
scheinen relativ rar zu sein.

Hier mal der Code für den Master:
1
#include <SPI.h>
2
3
#define PIN_SS_ATTINY2313 10
4
#define PIN_LED 2
5
#define PIN_BTN 3
6
7
#define CMD_LEDON 0b10011001
8
#define CMD_LEDOFF 0x00
9
10
byte bitOrder = MSBFIRST;
11
int btnState; int lastBtnState; int ledState;
12
13
void setup() {
14
  pinMode(PIN_LED, OUTPUT);
15
  pinMode(PIN_BTN, INPUT);
16
  pinMode(PIN_SS_ATTINY2313, OUTPUT);
17
  
18
  digitalWrite(PIN_SS_ATTINY2313, HIGH);
19
  
20
  SPI.begin();
21
  SPI.setDataMode(SPI_MODE0);
22
  SPI.setBitOrder(bitOrder);
23
  SPI.setClockDivider(SPI_CLOCK_DIV128); // 128kKz
24
  
25
  ledOff();
26
}
27
28
void loop() {
29
  lastBtnState = btnState;
30
  btnState = digitalRead(PIN_BTN);
31
  
32
  if (lastBtnState == 0 && btnState == 1) {
33
    if (ledState == LOW) {
34
      ledOn();
35
    } else {
36
      ledOff();
37
    }
38
  }
39
}
40
41
void ledOn() {
42
  digitalWrite(PIN_LED, HIGH);
43
  ledState = HIGH;
44
45
  digitalWrite(PIN_SS_ATTINY2313, LOW);
46
  SPI.transfer(CMD_LEDON);
47
  digitalWrite(PIN_SS_ATTINY2313, HIGH);
48
}
49
50
void ledOff() {
51
  digitalWrite(PIN_LED, LOW);
52
  ledState = LOW;
53
54
  digitalWrite(PIN_SS_ATTINY2313, LOW);
55
  SPI.transfer(CMD_LEDOFF);
56
  digitalWrite(PIN_SS_ATTINY2313, HIGH);
57
}

Wenn ich den Uno mit dem SPI Master code an einen 74HCT595 anschließe 
(der macht ja auch nur SPI Mode 0) und 8 LEDs an die einzelnen Ausgänge 
des Shiftregisters anschließe dann schalten alle LEDs korrekt, ich gehe 
daher naiver Weise mal davon aus das der Master funktioniert.

Und der Code für den Slave am ATtiny 2313:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
 
5
#define CTRL_PORT  DDRB
6
#define DATA_PORT  PORTB
7
#define SS_PIN    PB4
8
#define CLK_PIN    PB7
9
#define  DI_PIN    PB5
10
#define DO_PIN    PB6
11
12
// SPI mode 0 or 1 - modes 2 and 3 are NOT supported
13
#define SPI_MODE 0
14
15
#define PIN_LED PD5
16
#define CMD_LEDON 0b10011001
17
#define CMD_LEDOFF 0x00
18
19
// Some macros that make the code more readable
20
#define output_low(port,pin) port &= ~(1<<pin)
21
#define output_high(port,pin) port |= (1<<pin)
22
#define set_input(portdir,pin) portdir &= ~(1<<pin) 
23
#define set_output(portdir,pin) portdir |= (1<<pin)
24
25
boolean ledState;
26
byte cmd;
27
boolean cmdAvailable;
28
29
int main(void) {
30
  init();
31
  
32
  while (1) {
33
    if (cmdAvailable == true) {
34
      
35
      // SPI command handling
36
      if (cmd == CMD_LEDOFF) {
37
        output_low(PORTD, PIN_LED);
38
        ledState = false;
39
      } else if (cmd == CMD_LEDON) {
40
        output_high(PORTD, PIN_LED);
41
        ledState = true;
42
      }
43
44
      cmdAvailable = false;
45
    }
46
  }
47
  
48
  return 0;
49
}
50
51
void init() {
52
  // initialize the direction of PORTD #5 to be an output
53
  set_output(DDRD, PIN_LED);  
54
  
55
  output_high(PORTD, PIN_LED);
56
  ledState     = true;
57
  cmdAvailable = false;
58
59
  // DO pin is configured for output
60
  CTRL_PORT |= _BV(DO_PIN);
61
  // other pins as input
62
  CTRL_PORT &= ~_BV(DI_PIN) | ~_BV(CLK_PIN) | ~_BV(SS_PIN);
63
  // set pull ups
64
  DATA_PORT |= _BV(DI_PIN) | _BV(CLK_PIN);
65
  // enable USI overflow interrupt, set three wire mode and set
66
  //  clock to external, positive edge
67
  USICR = 0;
68
  USICR = (1<<USIOIE) | (1<<USIWM0) | (1<<USICS1) | (SPI_MODE<<USICS0);
69
  
70
  USISR = (1<<USIOIF); // clear overflow flag
71
}
72
73
/**
74
 * USI overflow intterupt - triggered when transfer complete
75
 */
76
ISR(USI_OVERFLOW_vect) {
77
  cmd           = USIDR;
78
  USISR         = (1<<USIOIF); // clear overflow flag
79
  cmdAvailable  = true;
80
}

Wie ihr seht ignoriert der Slave aktuell noch den SS Pin (also eher USI 
statt SPI) aber das sollte meines Verständnisses nach dem USI_OVERFLOW 
Interrupt keinen Abbruch tun?

Der Test "if (cmdAvailable == true)" in main() trifft nie zu, es ist als 
ob die Interrupt Routine nie ausgeführt würde.

Wenn ich allerdings in der Interrupt Routine die LED direkt toggle, ohne 
die SPI-Daten zu beachten dann toggled diese tatsächlich mit jedem 
Drücken des Pushbuttons, also wird sie "irgendwie doch" aufgerufen.

Was ich meine ist innerhalb der ISP Routine folgendes einzubauen:
1
  if (ledState == false) {
2
    output_high(PORTD, PIN_LED);
3
    ledState = true;
4
  } else {
5
    output_low(PORTD, PIN_LED);
6
    ledState = false;
7
  }

Das toggled mit jedem Drücken des Buttons am Uno die LED am Attiny, 
allerdings werden natürlich die SPI Daten ignoriert.

Was ich überhaupt nicht verstehe ist warum der Aufruf von cmdAvailable = 
true; innerhalb der Interrupt Routine nicht dazu führt dass in main() in 
die If-Schleife gesprungen wird. Ich kann doch in der Interrupt Routine 
eine globale Boolsche Variable setzen und die dann im Loop der main() 
auslesen, oder habe ich da einen größeren Denkfehler?

Ich bin nicht sicher ob ich das USI Register für SPI Mode 0 korrekt 
setze, fast glaube ich es liegt daran (neben dem Mysterium der nicht 
gesetzten Boolschen Variable) aber beim Abgleich mit dem Datenblatt 
finde ich keinen Fehler.

Am Anfang hatte ich in der init() am Ende noch einen Aufruf von sei(); 
aber nach meinem Verständnis werden die Interrupts durch Setzen von 
(1<<USIOIE) sowieso aktiviert?

Ich bin mit meinem Latein ziemlich am Ende, danke schon mal für Eure 
Hilfe :)

von C. W. (chefkoch)


Lesenswert?

Probier mal cmdAvailable als volatile zu deklarieren

von Dominique S. (mronak)


Lesenswert?

Ach f.....ielen Dank. Ja klar, das war's.

volatile auf cmdAvailable und cmd und schon gehts. Grmpf. Kopf -> Wand.

Tausend Dank!

von C. W. (chefkoch)


Lesenswert?

Bitte

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.