Forum: Projekte & Code SPI Kopplung von ATmega8 und ATtiny 2313


von Huldreich (Gast)


Angehängte Dateien:

Lesenswert?

Für ein neues Projekt suchte ich nach einer Möglichkeit, einen Atmega8 
mit zwei ATtiny2313 per SPI zu koppeln. In den Foren finden sich viele 
Anregungen, aber die vollständige Lösung war nicht dabei.
Hier deshalb mal mein Testaufbau mit Schaltung und Source für die MCUs 
in Assembler.

Der Aufbau ist getestet und funktioniert.
Bezüglich der Beschaltung der MCUs bin ich nicht ganz sicher, ob hier 
alles optimal gewählt ist. Für Hinweise bin ich dankbar.

Huldreich

von Huldreich (Gast)


Angehängte Dateien:

Lesenswert?

So einfach war es denn doch nicht, so hatte ich viele Wochen zu tun, bis 
der Versuchsaufbau wirklich stand.
Da ATTiny kein SPI hat, ist USI zu verwenden. Dies funktionierte beim 
ersten Byte auch gut, ab dem zweiten kam es zu Fehlern. Deswegen habe 
ich mir eine Art Handshake ausgedacht. Zur Kontrolle habe ich zunächst 
zwei LCD-Displays angeschlossen.

ATMega steuert die Slaves per Slaveselect (PortC1 oder C2) und erhält 
die Antwort als Pegelwechsel über PINC3.
Die vollständige Source habe ich angehängt, ist aber noch nicht geordnet 
und daher mühsam zu lesen, deswegen habe ich die entscheidenden Passagen 
hier herausgenommen, zunächst der ATMega8
1
; Pulsdauer für ein Servo übertragen, die Daten liegen in R15 (Servonummer), R24 (Lowbyte) und R25 (Highbyte)  
2
;------------------------------------------------------------------------
3
Warte_100us:push R16
4
5
;Warteroutine 100 µs
6
 
7
  ret
8
;------------------------------------------------------------------------
9
ByteTransf:  sbic  PINC,3    ; Antwort v. ATTiny durch low-Signal auf PIN C3
10
    rjmp   ByteTransf  ; ATTiny noch nicht bereit
11
    rcall  SPI_senden  ; Byte senden
12
fertig:    sbis  PORTC,3    ; Antwort v. ATTiny durch high-Signal auf PIN C3
13
    rjmp   fertig    ; ATTiny noch nicht bereit
14
    ret
15
;------------------------------------------------------------------------  
16
17
Datentransfer:  mov  R18,R15
18
    swap   R18
19
    or  R18,R25    ; das obere Nibbel in R15 und R25 ist immer 0 - zur Übertragung in ein Byte gepackt
20
    rcall  ByteTransf
21
    rcall  Warte_100us
22
    mov  R18,R24
23
    rcall  ByteTransf
24
    rcall  Warte_100us
25
    ret
26
;------------------------------------------------------------------------  
27
Daten_An_Slave:  sbrs  R15,3    ; welcher Slave?
28
     rjmp  Daten_An_Slave1  ; Servonummer 1-6
29
    ldi  R16,0b11110111
30
     and  R15,R16    ; Bit 3 löschen
31
Daten_An_Slave2:cbi   PORTC,2    ; Slaveselect durch PORTC2 auf low
32
    rcall  Datentransfer
33
    sbi   PORTC,2   ; deselektieren  
34
    ret   
35
Daten_An_Slave1:cbi   PORTC,1    ; Slaveselect durch PORTC1 auf low
36
    rcall  Datentransfer
37
    sbi   PORTC,1   ; deselektieren  
38
    ret
und jetzt der ATTiny 2313
1
mainloop:  ;wdr
2
    sbic  PINB,0      ; warten bis Slaveselect, low-Pegel an SS startet den Ablauf
3
    rjmp   mainloop
4
    sbi   DDRB,1      ; B1 als Ausgang für die Dauer der Datenübertragung
5
    rcall  SPI_Start
6
    cbi  PORTB,1      ; Pegeländerung an B1 zeigt dem Master die Bereitschaft an
7
    rcall  SPI_Empfang
8
    mov  R16,R17      ; oberes Nibbel Servonummer, unteres Nibbel Highbyte der Pulsdauer
9
    swap  r17      ; Nibbel vertauschen
10
    andi  r16,0b00001111    ; oberes Nibbel ausblenden, es verbleibt das Highbyte der Servopulsdauer
11
    andi  r17,0b00001111    ; vertauschtes oberes Nibbel ausblenden, es verbleibt die Servonummer  
12
    mov  r1,r17      ; Servonummer
13
    mov  r2,r16      ; Highbyte der Servopulsdauer
14
    
15
    sbi  PORTB,1      ; Pegeländerung: fertig
16
    rcall  SPI_Start
17
    cbi  PORTB,1      ; Pegeländerung an B1 zeigt dem Master die Bereitschaft an
18
    rcall  SPI_Empfang
19
      
20
; Auslassung: die Zeilen dienen dem Speichern und Anzeigen der empfangenen Daten
21
    
22
    sbi  PORTB,1      ; Pegeländerung: fertig
23
    cbi   DDRB,1      ; B1 wieder als Eingang
24
    rjmp   mainloop
Eigentlich hätte das sofort funktionieren müssen; es geht jedoch nur mit 
der Warteroutine 100µs für den ATMega, sonst wird ab dem zweiten Byte 
nur Müll oder gar nichts empfangen.
Wer kann das erklären?
Viele Grüße
Huldreich

von Steffen H. (avrsteffen)


Angehängte Dateien:

Lesenswert?

Hallo Huldreich

Einen tiny2313 für eine vollständige SPI Kommunikation zu verwenden ist 
keine so gute Idee. Wenn du allerdings auf das zurücksenden von Daten 
per 'DO' verzichten kannst, dann hab ich da was für dich. Ich verwende 
es in einer Positionsanzeige mit 3x8 7-Segmentanzeigen. Der tiny2313 
arbeitet hierbei als Slave und nimmt die Daten vom SPI entgegen. 
Momentan läuft der SPI Takt des Masters mit 16Mhz/64 = 250kHz. Der SPI 
Slave ala tiny2313 hat einen /CS Eingang. Die SPI Auswertung erfolgt im 
Interrupt. Und wenn alle Bytes empfangen wurden, wird ein Flag im 
GPIOR0-Register gesetzt welches man in der Hauptschleife abfragen kann. 
Der Vorteil der Interruptbehandlung des SPI liegt darin, dass man wärend 
des SPI-Transfers weiterhin andere Aufgaben erledigen kann.

Hier die Defines:
1
;***** Register-Variablen
2
.def  SREG_SAVE     = r0 ; Sicherungskopie des SREG-Registers im Interrupt
3
4
; Konstanten
5
.equ  SPI_RD_BYTES = 12 ; zu empangene Bytes per SPI
6
7
; Flags in GPOIR0
8
.equ  SPI_ON       = 2  ; 0= SPI Off  |  1= SPI On
9
.equ  SPI_OVERRUN  = 3  ; 0= kein SPI Data OverRun  |  1= SPI Data OverRun
10
.equ  SPI_UNDERRUN = 4  ; 0= kein SPI Data UnderRun |  1= SPI Data UnderRun
11
.equ  NEW_SPI_DATA = 7  ; Flag zeigt an, dass neue Daten von dem USI-SPI zur Verfuegung stehen
12
13
; Port defines
14
.equ  USI_SPI_PORT = PORTB ; fest vorgeschrieben
15
.equ  usi_SPI_SCK  = 7     ; fest vorgeschrieben
16
.equ  usi_SPI_DO   = 6     ; fest vorgeschrieben
17
.equ  usi_SPI_DI   = 5     ; fest vorgeschrieben
18
.equ  usi_SPI_CS   = 4     ; 0..4 von PORTB frei waehlbar
19
20
; Variablen im SRAM
21
.dseg
22
  SPI_pRD_BUFFER:  .BYTE 2             ; SPI read pointer
23
  SPI_RD_BUFFER:   .BYTE SPI_RD_BYTES  ; SPI read buffer
24
  MEM_BUFFER:      .BYTE SPI_RD_BYTES  ; Arbeitspeicher

Hier mal noch ein hilfreiches Makro um einen Pointer zu laden:
1
;--- Macros -------------------------------------------------------
2
.macro   load_p         ; lädt 16Bit @1 nach @0 High und @0 Low
3
   ldi     @0H,HIGH(@1)
4
   ldi     @0L,LOW(@1)
5
.endmacro

Hier die Interruptvektoren:
1
.cseg            ;Beginn eines Code-Segmentes
2
.org $000         rjmp  RESET          ; Startadresse 
3
.org PCIaddr      rjmp  PCINT          ; Pin Change Interrupt Handler
4
.org USI_OVFaddr  rjmp  USI_OVERFLOW   ; USI Overflow Handler

Wenn die /CS Leitung vom Master auf Low gezogen wurde wird dies erkannt 
und dem SPI Interrupt das abspeichern der empfangenen Bytes angewiesen. 
Im SPI Interrupt wird geprüft, dass nicht zuviel Bytes gesendet werden. 
(festgelegte Anzahl zu empfangender Bytes per Frame in 'SPI_RD_BYTES') 
Andernfalls wird das 'OVERRUN'-Flag gesetzt und das SPI Frame verworfen. 
Sollte nun vom Master die /CS Leitung wieder auf High gezogen, dann wird 
dies wieder im Pin-Change-Interrupt erkannt und geprüft, ob auch schon 
alle Bytes eines SPI-Frames empfangen wurde. Sollte das der Fall sein, 
wird das 'NEW_SPI_DATA'-Flag im GPOIR0 gesetzt und damit angezeigt, dass 
ein neuer Datensatz im 'SPI_RD_BUFFER' im SRAM liegt. Darauf kann dann 
die Hauptschleife reagieren.

Hier der Pin Change Interrupt der /CS Leitung:
1
.org INT_VECTORS_SIZE
2
; Programm Code follows from here
3
PCINT:
4
   in    SREG_SAVE,SREG
5
   push  r16
6
   sbic  USI_SPI_PORT-2,usi_SPI_CS
7
   rjmp  usi_spi_transfer_end
8
usi_spi_transfer_start:
9
   sbi   GPIOR0,SPI_ON          ; SPI Auswertung einschalten
10
   in    r16,USI_SPI_PORT-1
11
   sbr   r16,(1<<usi_SPI_DO)
12
   out   USI_SPI_PORT-1,r16
13
   rjmp  pcint_end
14
usi_spi_transfer_end:
15
   cbi   GPIOR0,SPI_ON           ; SPI Auswertung ausschalten
16
   in    r16,USI_SPI_PORT-1
17
   cbr   r16,(1<<usi_SPI_DO)
18
   out   USI_SPI_PORT-1,r16  
19
   push  r17
20
   push  XL
21
   push  XH
22
   lds   XL,(SPI_pRD_BUFFER+0)   ; LSB
23
   lds   XH,(SPI_pRD_BUFFER+1)   ; MSB
24
   ldi   r16,byte1(SPI_RD_BUFFER); SPI_RD_BUFFER initialisieren
25
   ldi   r17,byte2(SPI_RD_BUFFER)
26
   sts   (SPI_pRD_BUFFER+0),r16
27
   sts   (SPI_pRD_BUFFER+1),r17
28
   sbis  GPIOR0,SPI_OVERRUN
29
   rjmp  usi_spi_transfer_complied
30
   cbi   GPIOR0,SPI_OVERRUN
31
   rjmp  usi_spi_end
32
usi_spi_transfer_complied:
33
   sbic  GPIOR0,NEW_SPI_DATA
34
   rjmp  usi_spi_end
35
   ldi   r16,byte1(SPI_RD_BUFFER+SPI_RD_BYTES)
36
   ldi   r17,byte2(SPI_RD_BUFFER+SPI_RD_BYTES)
37
   cp    XL,r16    ; Überpruefung auf Data underrun
38
   cpc   XH,r17    ; Überpruefung auf Data underrun
39
   brlo  usi_spi_end
40
   sbi   GPIOR0,NEW_SPI_DATA
41
   rjmp  usi_spi_end
42
usi_spi_end:
43
   pop   XH
44
   pop   XL
45
   pop   r17
46
pcint_end:
47
   pop   r16
48
   out   SREG,SREG_SAVE
49
   reti

Hier der USI Overflow Interrupt für ein empfangenes Byte:
1
USI_OVERFLOW:
2
   in    SREG_SAVE,SREG
3
   sbi   USISR,USIOIF
4
   push  r16
5
   push  r17
6
   push  XL
7
   push  XH
8
   sbis  GPIOR0,SPI_ON
9
   rjmp  usi_overflow_end
10
   lds   XL,(SPI_pRD_BUFFER+0)    ; LSB
11
   lds   XH,(SPI_pRD_BUFFER+1)    ; MSB
12
   ldi   r16,byte1(SPI_RD_BUFFER+SPI_RD_BYTES)
13
   ldi   r17,byte2(SPI_RD_BUFFER+SPI_RD_BYTES)
14
   cp    XL,r16     ; Überpruefung auf Data overrun
15
   cpc   XH,r17     ; Überpruefung auf Data overrun
16
   brsh  usi_spi_rd_overun
17
;store spi data byte
18
   in    r16, USIDR
19
   st    X+,r16
20
   sts   (SPI_pRD_BUFFER+0),XL     ; LSB
21
   sts   (SPI_pRD_BUFFER+1),XH     ; MSB
22
   rjmp  usi_overflow_end
23
24
usi_spi_rd_overun:
25
   sbi   GPIOR0,SPI_OVERRUN
26
   cbi   GPIOR0,SPI_ON
27
28
usi_overflow_end:
29
   pop   XH
30
   pop   XL
31
   pop   r17
32
   pop   r16
33
   out   SREG,SREG_SAVE
34
   reti

Hier die USI SPI Initialisierung:
1
init_usi_spi:
2
   cbi    GPIOR0,SPI_ON
3
   cbi    GPIOR0,SPI_OVERRUN
4
   cbi    GPIOR0,SPI_UNDERRUN
5
   cbi    GPIOR0,NEW_SPI_DATA
6
;Port und Pins für USI SPI initialisieren
7
   in      r16,USI_SPI_PORT-1
8
   cbr     r16, (1<<usi_SPI_SCK)|(1<<usi_SPI_DI)|(1<<usi_SPI_CS)|(1<<usi_SPI_DO)
9
   out     USI_SPI_PORT-1,r16
10
   in      r16,USI_SPI_PORT
11
   sbr     r16, (1<<usi_SPI_CS)
12
   out     USI_SPI_PORT,r16
13
; Pointer auf SPI_RD_BUFFER initialisieren  
14
   load_p  X,(SPI_RD_BUFFER)
15
   sts     (SPI_pRD_BUFFER+0),XL
16
   sts     (SPI_pRD_BUFFER+1),XH
17
; USI SPI einschalten    
18
   in      r16, USICR
19
   sbr     r16, (1<<USIOIE)|(1<<USIWM0)|(1<<USICS1)
20
   cbr     r16, (1<<USIWM1)|(1<<USICS0)|(1<<USICLK)
21
   out     USICR, r16
22
; Pin-Cange-Interrupt Pin auswaehlen
23
   in      r16, PCMSK
24
   sbr     r16, (1<<usi_SPI_CS)
25
   out     PCMSK, r16
26
; Pin-Change-Interrupt einschalten
27
   in      r16, GIMSK
28
   sbr     r16, (1<<PCIE)
29
   out     GIMSK, r16
30
   ret

Jetzt noch das Unterprogramm 'memcpy' zur Speicherkopie. Die sollte man 
machen, da ja der 'SPI_RD_BUFFER' in einem weiteren SPI-Interrupt wieder 
verändert werden kann. So kann man mit der Kopie erstmal ganz beruhigt 
arbeiten.
1
memcpy:
2
   ldi      r17,SPI_RD_BYTES
3
   load_p   X,(SPI_RD_BUFFER)
4
   load_p   Y,(MEM_BUFFER)
5
   cli      ; Atomarer Zugriff begin
6
memcpy_loop:
7
   ld       r16,X+
8
   st       Y+,r16
9
   dec      r17
10
   brne  memcpy_loop
11
   sei      ; Atomarer Zugriff ende
12
   cbi      GPIOR0,NEW_SPI_DATA
13
   ret

Und so kommt man nun an die SPI Daten ran:
1
; RESET = Programmbeginn
2
RESET:
3
   rcall   init_usi_spi    ; USI SPI initialisieren (PORTs, INTs und SRAM)
4
; Hauptschleife
5
main:
6
   sbic    GPIOR0,NEW_SPI_DATA
7
   rcall   new_spi_data_receive
8
;  .....
9
;  ..... tue irgendwas in der Hauptschleife
10
;  .....
11
   rjmp    main
12
13
new_spi_data_receive:
14
   rcall   memcpy    ; ab hier stehen die SPI Data Bytes im 'MEM_BUFFER'
15
;  ......            ; zur weiteren Verarbeitung bereit
16
17
   ret

Ich glaub dass ist jetzt ganz schön lang geworden. Wenn noch Fragen sind 
oder Ihr Anregungen/Verbesserungen habt, nur her damit.

Grüße Steffen

von Huldreich (Gast)


Lesenswert?

Hallo Steffen,

vielen Dank für Code und Erläuterung.
Da mein Programm eigentlich funktioniert und dann -Frust- eben doch 
wieder nicht ganz richtig, hatte ich bereits die Idee einer 
vollständigen Interruptsteuerung, diese jedoch nicht weiter verfolgt und 
dann das Projekt vorerst ganz zurückgestellt.

Deine Lösung, die Daten zwischenzupeichern, ist eleganter als meine.

Ich werde auf jeden Fall mein Programm nach Deiner Anregung umschreiben 
und dann berichten. (Kann ein paar Wochen dauern)

Viele Grüße
Huldreich

von Huldreich (Gast)


Lesenswert?

Hallo Steffen,

das Studium Deines Schreibens bringt einen autodidaktischen Hobbyisten 
wirklich weiter; vielleicht kannst Du mir einige Fragen beantworten:

1. Lt. Datenblatt kann GPOIR0 für globale Variablen genutzt werden. Hast 
Du Deine Festlegungen (.equ  SPI_ON       = 2 ........) selbst definiert 
oder irgendwo abgeleitet. Wird dieses Register wirklich nicht intern von 
der MCU benutzt?

2. Warum hast Du allgemeine Formulierungen benutzt (z.B. USI_SPI_PORT-2 
anstelle von PINB)? Daran habe ich einige Zeit gerätselt, da mir diese 
Schreibweise unbekannt war.

3. Warum wird in:
usi_spi_transfer_start:
   sbi   GPIOR0,SPI_ON          ; SPI Auswertung einschalten
   in    r16,USI_SPI_PORT-1
   sbr   r16,(1<<usi_SPI_DO)
   out   USI_SPI_PORT-1,r16
auf usi_SPI_DO zugegriffen. Du hattest doch oben geschrieben, dass der 
ATTiny nur Daten empfangen aber nicht senden soll.

4. Warum ist am Ende von usi_spi_transfer_complied:
der Sprungbefehl rjmp  usi_spi_end eingefügt? Das ist doch die nächste 
Anweisung.

5. Warum muss in der ISR für USI_OVERFLOW: in der zweiten Zeile das Flag 
gelöscht werden. Geht das nicht automatisch?

6. Warum wird unter init_usi_spi: in der 8. Zeile usi_SPI_DO als Input 
initialisiert? Das sollte doch eigentlich gar nicht benutzt werden, 
s.o..

7. Reine Verständnisfrage: Drei Zeilen weiter unten: damit wird der 
Pullupwiderstand (usi_SPI_CS) aktiviert?

8. Unter ; USI SPI einschalten:
   sbr     r16, (1<<USIOIE)|(1<<USIWM0)|(1<<USICS1)
   cbr     r16, (1<<USIWM1)|(1<<USICS0)|(1<<USICLK)
   out     USICR, r16
   lautet meine Formulierung
   ldi   R16,0b00011000   ; (1<<USIWM0)|(1<<USICS1)
   out   USICR,R16
Sollte bis auf Bit 6 identisch sein. Kannst Du das für mich prüfen? Ich 
bin bei Adressierung von Bits und Masken immer unsicher.

Viele Grüße
U.

von Steffen H. (avrsteffen)


Lesenswert?

Hallo

Huldreich schrieb:
> 1. Lt. Datenblatt kann GPOIR0 für globale Variablen genutzt werden. Hast
> Du Deine Festlegungen (.equ  SPI_ON       = 2 ........) selbst definiert
> oder irgendwo abgeleitet. Wird dieses Register wirklich nicht intern von
> der MCU benutzt?
Über GPIOR0 (0x13) kannst man frei verfügen und dieses wird nicht von 
der CPU benutzt. Da beim ATtiny2313 die Adresse für dieses Register 
innerhalb der Adessen 0x01..0x1F liegt (nicht MEMORY MAPPED) kann man 
einzelne Bits ganz einfach mit sbi oder cbi verändern und die 
Skipbefehle sbis oder sbic verwenden.
1
.equ  SPI_ON       = 2
..habe ich selber definiert. Damit kann man im Programm besser sehen, 
was mit dem Bit2 gemeint ist.


Huldreich schrieb:
> 2. Warum hast Du allgemeine Formulierungen benutzt (z.B. USI_SPI_PORT-2
> anstelle von PINB)? Daran habe ich einige Zeit gerätselt, da mir diese
> Schreibweise unbekannt war.
Es ist doch einfacher in der Zuweisung deines Ports nur den Port 
anzugeben und das/die Bits der entsprechenden Signale als immer noch 
DDRx und PINx. Schau mal in deine 'tn2313.def'. Da sieht man schön, wie 
die Adressvergabe des PORT/DDR/PIN bei allen ATMEL AVR's immer gleich 
ist.
Deswegen kann man einfach nur den PORT angeben und wenn man das 
Dir-Register zieht man einfach von der PORT-Adresse 1 ab, bzw. bei der 
PIN-Adresse halt 2 abziehen.


Huldreich schrieb:
> 3. Warum wird in:
> usi_spi_transfer_start:
>    sbi   GPIOR0,SPI_ON          ; SPI Auswertung einschalten
>    in    r16,USI_SPI_PORT-1
>    sbr   r16,(1<<usi_SPI_DO)
>    out   USI_SPI_PORT-1,r16
> auf usi_SPI_DO zugegriffen. Du hattest doch oben geschrieben, dass der
> ATTiny nur Daten empfangen aber nicht senden soll.
Ich hatte es mal vorgesehen, allerdings wieder verworfen. Denn es muss 
garantiert werden, dass eine bestimmte Zeit eingehalten werden muss, 
wenn die /CS-Flanke von 1->0 geht bis der erste CLK des SPI kommt. Denn 
in dieser Zeit muss der PCINT Interrupt eimal ausgeführt worden sein 
um das SPDR mit dem Wert zu füllen, der zurückgesendet werden soll. Hier 
wird nur einfach dafür gesorgt, dass DO ein Eingang bleibt.

Huldreich schrieb:
> 4. Warum ist am Ende von usi_spi_transfer_complied:
> der Sprungbefehl rjmp  usi_spi_end eingefügt? Das ist doch die nächste
> Anweisung.
1
   ..
2
   brlo  usi_spi_end
3
   sbi   GPIOR0,NEW_SPI_DATA
4
   rjmp  usi_spi_end    ; <<---- Der hier?
5
usi_spi_end:
6
   pop   XH
7
   ..
Der rjmp *usi_spi_end* kann entfernt werden. Der ist wirklich 
überflüssig. Danke für den Hinweis!


Huldreich schrieb:
> 5. Warum muss in der ISR für USI_OVERFLOW: in der zweiten Zeile das Flag
> gelöscht werden. Geht das nicht automatisch?
Nein, leider nicht. Das muss so sein. Steht auch so im Datasheet. Damit 
wird der USI-Counter wieder auf NULL gesetzt.


Huldreich schrieb:
> 6. Warum wird unter init_usi_spi: in der 8. Zeile usi_SPI_DO als Input
> initialisiert? Das sollte doch eigentlich gar nicht benutzt werden,
> s.o..
War aber mal so vorgesehen. Wenn man den PIN für DO anderweilig 
braucht kann man alles was mit DO zu tun hat einfach löschen oder 
auskommentieren.

Huldreich schrieb:
> 7. Reine Verständnisfrage: Drei Zeilen weiter unten: damit wird der
> Pullupwiderstand (usi_SPI_CS) aktiviert?
1
;Port und Pins für USI SPI initialisieren
2
   in      r16,USI_SPI_PORT-1
3
   cbr     r16, (1<<usi_SPI_SCK)|(1<<usi_SPI_DI)|(1<<usi_SPI_CS)|(1<<usi_SPI_DO)
4
   out     USI_SPI_PORT-1,r16
5
   in      r16,USI_SPI_PORT
6
   sbr     r16, (1<<usi_SPI_CS) ; <<--- Das hier??
7
   out     USI_SPI_PORT,r16
Ja, ich schalte für den /CS-Eingang den Pullup ein. Das kommt noch vom 
debuggen mit dem Logic-Analizer. Und ich glaube auch deswegen, damit man 
die SPI-Verbindung während einer Kommunikation an/ab stöpseln kann. 
Deswegen auch die Abfragen auf OVER-/UNDERRUN.

Huldreich schrieb:
> 8. Unter ; USI SPI einschalten:
>    sbr     r16, (1<<USIOIE)|(1<<USIWM0)|(1<<USICS1)
>    cbr     r16, (1<<USIWM1)|(1<<USICS0)|(1<<USICLK)
>    out     USICR, r16
>    lautet meine Formulierung
>    ldi   R16,0b00011000   ; (1<<USIWM0)|(1<<USICS1)
>    out   USICR,R16
> Sollte bis auf Bit 6 identisch sein. Kannst Du das für mich prüfen? Ich
> bin bei Adressierung von Bits und Masken immer unsicher.
Das ist schon korrekt so. Ich setze ja nurnoch das Interrupt-Bit, um 
einen Interrupt beim Überlauf des USI-Zählers zu erreichen.


Ich hoffe ich konnte weiter helfen.
Gruß Steffen

von Huldreich (Gast)


Lesenswert?

Hallo Steffen,

vielen Dank für die Mühe.
Wenn die Infos umgesetzt sind melde ich mich wieder, kann etwas dauern.

Viele Grüße
U.

von Steffen H. (avrsteffen)


Lesenswert?

Hallo

Was genau willst du denn umsetzen? 6x PWM für Modellbau-Servos die per 
SPI gesteuert werden? Wenn ja, welche auflösung soll das PWM-Signal denn 
haben (16,10 oder 8Bit)?

von Steffen H. (avrsteffen) (Gast)



Lesenswert?

Hallo

So, ich hatte mal ein wenig Zeit und hier mal meine Version von einem 
6-Kanal SERVO-PWM Treiber der via 2Byte SPI-Telegramm angesteuert wird.

Kann ihn leider noch nicht testen, da ich keinen SPI-Master zur 
Verfügung habe. Die PWM für die Servos jedenfalls steht aal glatt an den 
Ausgängen an.

Wenn es mal jemand testen könnte und mir ein positives Feedback gibt, 
werde ich es in die Codesammlung einstellen.

Vor allem müsste man mal testen, wie hoch hier der maximale SPI-Takt 
sein darf. Der tiny2313 arbeitet mit dem internen 8Mhz Osszilator. Der 
ist allerdings nicht so Temperatur/Spannungs stabil wie ein externer 
Quarz, aber wenn man ihn ein wenig trimmt
1
; RESET = Programmbeginn
2
RESET:
3
;STACK initialisieren
4
    ldi      r16,low(RAMEND)     ; Stackpointer initialisieren
5
    out      SPL,r16             ; muss in jedes Programm 
6
                                ; als erstes mit eingebunden werden
7
8
    ldi      r16,0x4A            ; --> calibration of internal OSC
9
    out      OSCCAL,r16
10
11
    sei
12
    rcall    init_servo_pwm      ; Servo-PWM initialisieren (PORTs, Timer-INTs, globale Register und SRAM)
13
    rcall    init_usi_spi        ; USI SPI initialisieren (PORTs, INTs und SRAM)
kommt die PWM sehr genau.


Gruß Steffen

von Steffen H. (avrsteffen)


Angehängte Dateien:

Lesenswert?

Und hier noch ein paar Bilder zum besseren Verständnis.

von Huldreich (Gast)


Lesenswert?

Sagenhaft - ich bin geplättet.
So ähnlich habe ich mir das vorgestellt, jedoch bin ich noch lange nicht 
so weit.

Zum  tiny2313 - interne 8 Mhz: bei unveränderten Lockbits wird, wenn ich 
das richtig verstanden habe, der Takt durch 8 geteilt, d.h. wir haben 
dann noch 1 MHz. Soll das so sein?

Viele Grüße
U.

von Huldreich (Gast)


Lesenswert?

Hallo Steffen,

welches Oszilloscop (Hardware, Software) hast Du für die Darstellung 
benutzt?

Gruß
U.

von Steffen H. (avrsteffen)


Lesenswert?

Huldreich schrieb:
> welches Oszilloscop (Hardware, Software) hast Du für die Darstellung
> benutzt?
Das ist ein Logic Analyzer mit zugehöriger Software.
http://gadgetforge.gadgetfactory.net/gf/project/butterflylogic/
Eine sehr nützliche Anschaffung.

Huldreich schrieb:
> Zum  tiny2313 - interne 8 Mhz: bei unveränderten Lockbits wird, wenn ich
> das richtig verstanden habe, der Takt durch 8 geteilt, d.h. wir haben
> dann noch 1 MHz. Soll das so sein?
Nein. Man muss natürlich die CKDIV8 Fuse auschalten (CKDIV8=0). So hat 
man die internen 8Mhz.


Hat denn den jemand mal den Code testen können?


Gruß Steffen

von Peter D. (peda)


Lesenswert?

Huldreich schrieb:
> In den Foren finden sich viele
> Anregungen, aber die vollständige Lösung war nicht dabei.

Das liegt daran, daß das AVR-SPI eine Mogelpackung ist.
Man hat keinen Sendepuffer und somit hat der SPI-Slave nur einen halben 
SCK-Takt Zeit, in den Interrupt zu springen und das nächste Byte zu 
schreiben, also ein Ding der Unmöglichkeit.

Als Master ist das SPI nutzbar, aber als Slave ist es ne Katastrophe. 
Der Master muß nach jedem Byte lange Gedenkpausen einlegen.

Bei den neueren AVRs hat man etwas nachgebessert und der UART einen 
gepufferten SPI-Modus verpaßt. Allerdings hat man den Slave-Mode 
vergessen, also auch ne Sackgasse.

Bei der 8051-Fraktion war man schlauer. Z.B. der AT89LP4052 hat ein 
gepuffertes SPI. Der kann also SPI-Slave sein, ohne den Master 
haufenweise CPU-Zeit verschwenden zu lassen. Der Master kann 
ununterbrochen senden und empfangen.

Man kann sagen, die AVR-Leute haben gründlich Mist gebaut.
Und deshalb gibt es auch keine brauchbaren Lösungen.


Huldreich schrieb:
> Eigentlich hätte das sofort funktionieren müssen; es geht jedoch nur mit
> der Warteroutine 100µs für den ATMega,

Die Wartezeit muß so lang sein, wie die längst mögliche Zeit, die der 
Slave braucht. Hat der Slave noch andere Interrupts zu behandeln, kann 
das also richtig lange dauern und die 100µs noch zu kurz sein.


Peter

von Huldreich (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Und deshalb gibt es auch keine brauchbaren Lösungen

@ Peter: Vielen Dank, das baut mich wieder auf, dachte es liegt an mir.

@ Steffen: Habe Deinen Code teilweise analysiert, verstehe aber nicht 
alles.
1
 in      SREG_SAVE,TCNT1L            ; compensate var. irq delay
2
   sbrs    SREG_SAVE,0                 ; odd: +2 clk; even: +3 clk
3
   ld      SREG_SAVE,Z                 ; Zeit kompensieren
4
   in      SREG_SAVE,SREG
Was wird kompensiert; ist damit gemeint eine evtl. vorher ablaufende 
ISR?
Wozu dienen weiter unten die NOPs?

Könnte man evtl. vielleicht die USI-Abfrage in den Mainloop per polling 
integrieren. Dann hätte man sehr kurze ISRs ohne irgendwelche 
Kompensationen. Die USI-Abfrage müsste dann per Handshake stattfinden, 
z.B. Master sendet Slaveselect (low), wartet auf Slaveantwort (low), 
sendet erst dann nur ein Byte, legt Slaveselect auf high, wartet bis 
Slave mit high Datenempfang und -verarbeitung quittiert, dann folgt das 
nächste Byte. Ist dann nicht so elegant wie eine reine 
Interruptsteuerung aber vielleicht einfacher.

Alternativ könnte ich mir vorstellen, dass man einfache (kurze) ISRs für 
alle Interrupts erstellt und dann einen einmaligen kurzen Fehler 
(Verlängerung) für die Pulsdauer akzeptiert; das dürfte bei 50Hz 
Wiederholfrequenz nicht ins Gewicht fallen.

U.

von Steffen H. (avrsteffen)


Lesenswert?

Huldreich schrieb:
> as wird kompensiert; ist damit gemeint eine evtl. vorher ablaufende
> ISR?
> Wozu dienen weiter unten die NOPs?
Wenn die ISR angesprungen wird, dann weiß man ja nicht, ob vorher ein 
1Byte oder 2Byte Befehl abgearbeitet worden ist. Wenn es ein 
2Byte-Befehl war, dann ist schon wieder 1Systemtakt flöten gegangen. 
Deshalb diese Abfrage am Anfang der ISR.

Desweiteren wird ja der System-Clock für den Timer durch 8 geteilt. Das 
heißt ja, dass der Timer aller 8 sys-clk wieder eins hochgezählt wird. 
Diese nop's sind eigentlich nur zur kompensation dieser 8 Takte drin. 
Macht man das nicht, dann hat man um maximal 7 Takte verschobene 
Compare-Zeiten der Impulseausgabe.

Wenn man es nicht so genau braucht, dann kann man sich das alles auch 
sparen.

Huldreich schrieb:
> Master sendet Slaveselect (low), wartet auf Slaveantwort (low),
> sendet erst dann nur ein Byte, legt Slaveselect auf high, wartet bis
> Slave mit high Datenempfang und -verarbeitung quittiert, dann folgt das
> nächste Byte. Ist dann nicht so elegant wie eine reine
> Interruptsteuerung aber vielleicht einfacher.
Das ist dann aber kein SPI mehr. Und ausserdem hat man dadurch bei jedem 
Slave den man am Master hat noch eine Signalleitung mehr drin.


Huldreich schrieb:
> Alternativ könnte ich mir vorstellen, dass man einfache (kurze) ISRs für
> alle Interrupts erstellt und dann einen einmaligen kurzen Fehler
> (Verlängerung) für die Pulsdauer akzeptiert; das dürfte bei 50Hz
> Wiederholfrequenz nicht ins Gewicht fallen.
Wie gesagt, wenn man die Impulse nicht auf den Takt genau braucht, dann 
kann man die ganze Taktkompensation wegfallen lassen. Bei 8Mhz ist ein 
Takt 125ns lang. Macht bei eventuellen 7 Takten 875ns. Muss man mal 
ausprobieren, ob die Servos da eventuell zu zittern oder knurren 
anfangen.

Ich kann es leider nicht testen. Hast du es denn schonmal testen können?


Gruß Steffen

von Huldreich (Gast)


Lesenswert?

Hallo Steffen,

momentan habe ich keinen Versuchsaufbau; deswegen kann ich Dein Programm 
nicht testen.

Peter Dannegger schrieb:
> Man hat keinen Sendepuffer und somit hat der SPI-Slave nur einen halben
> SCK-Takt Zeit, in den Interrupt zu springen und das nächste Byte zu
> schreiben, also ein Ding der Unmöglichkeit.

Wenn dem so ist und außerdem saubere Servoimpulse generiert werden 
sollen, gibt es nur noch zwei Interrupts, die die Pulsdauern festlegen. 
Die Kommunikation mit dem Master kann nur per Polling mit Handshake 
erfolgen.

Mal probieren ob das dann funktioniert.

Mit bestem Gruß
U.

von Steffen H. (avrsteffen)


Lesenswert?

Peter Dannegger schrieb:
> Man hat keinen Sendepuffer und somit hat der SPI-Slave nur einen halben
> SCK-Takt Zeit, in den Interrupt zu springen und das nächste Byte zu
> schreiben, also ein Ding der Unmöglichkeit.
Damit meint der Peter aber das zurücksenden von Daten als Slave. Als 
Slave Daten empfangen klappt schon ganz gut.


Steffen

von Huldreich (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Steffen,

langsam komme ich weiter.
Per Handshake ist mir eine einwandfreie Datenübertragung gelungen.
Die beiden MCUs müssen dann eben immer kurz aufeinander warten; 
allerdings dürften dann auch bei längeren ISRs keine Daten verloren 
gehen.

Die Programmcodes sind nicht gekürzt, vielleicht hat später mal jemand 
Interesse daran.

Die wichtigen Routinen heißen beim Master Bytetransfer und 
Daten_an_Slave, beim Slave mainloop und ml2.

Viele Grüße
U.

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.