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.
|