Forum: Projekte & Code [ASM] USI tiny SPI Slave im Interrupt Mode


von Steffen H. (avrsteffen)


Angehängte Dateien:

Lesenswert?

Hallo werte µC-Gemeinde


Einen tiny2313 für eine vollständige SPI Kommunikation zu verwenden ist
keine so gute Idee. Wenn man allerdings auf das zurücksenden von Daten
per 'DO' verzichten kann, dann hab ich da was für euch.

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 (PC_INT). 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

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.