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 :)