.include "m168def.inc" ; Rev History ; 27.12.24 Init - 2024 jobstens.de ; 28.12.24 V1 ; 28.12.24 V1.1: Samples per include ; 28.12.24 V1.2: SampleLength aktiviert, Samplerate variabel, C-Bits besser konfigurierbar. ; 29.12.24 V1.2.1: Emphasis per Jumper aktivierbar ; 02.01.25 V1.3: Biphasemarc-Encoder und Einschränkung auf 16 Bit eingebaut ; 02.01.25 V1.3_ADC: Daten werden vom AVR-ADC geholt (10Bit Mono) ; 44100 * 256 = 11289600 = 16 Takte / Byte bzw. 2 Takte / Bit ; ; ; [BYTE 1][BYTE 2][BYTE 3][BYTE 4][BYTE 5][BYTE 6][BYTE 7][BYTE 8] auf der als MSPI arbeitenden USART. ; ; ___---_-__--__--__--__--__--__--__--__--__--__--__--__--__--__-- ; ; PPPPPPPPEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASSSSSSSS ; P = Preamble ; E = Extended Audio (17-24 Bit) ; A = 16 Bit PCM Audio ; S = Subcode (VVUUCCPP) ; ; ; Preable - 8 Slots ; ; B Block 11101000 (E8/17) = Links (Kanal A) ; M Frame 11100010 (E2/47) = Links (Kanal A) ; W 11100100 (E4/27) = Rechts (Kanal B) und weitere Kanäle ; ; BWMWMW...BWMW ; ; Audio ; LSB First 24 Bit (48 Slots) ; ; Subcode - 8 Slots ; ## ## ##### ##### ###### ##### ###### ; ### ### ## ## ## ## ## ## ## ## ## ; #### #### ####### ## ###### ## ## ##### ; ## ### ## ## ## ## ## ## ## ## ## ## ; ## # ## ## ## ##### ## ## ##### ###### .macro CheckUDRE LDS tmp2, UCSR0A ; 2 Word 2 Cycles SBRS tmp2, 5 ; 1 Word 1 Cycles (f) / 3 Cycles (t) Skip if Bit 5 is set RJMP -3 ; 1 Word 2 Cycles Execute if Bit 5 is NOT set .endmacro ; Channelstatusmacro ; Jedes Byte: 0bPPCCUUVV (Parity, Channestatus, Userdata, Validity) - wobei 0b00110011 = 0000 ist ; Da Validity und Userbit immer 0 sind und Parity immer automatisch gebildet wird, Gibt es nur 2 Bitmuster: ; C = 0: 0b00110011 ; C = 1: 0b11010011 ; Mit C = 0 wird der gesamte Bereich formatiert, daher müssen nur 1 gesetzt werden .macro SetChannelBit LDI BlkCnt, @0 LDI tmp, 0b11010011 ST X, tmp .endmacro ; ##### ##### ### ## ###### ###### ##### ### ## ###### ###### ; ## ## ## ## #### ## ## ## ## ## #### ## ## ## ; ## ## ## ## ## ## ##### ## ####### ## ## ## ## ##### ; ## ## ## ## ## #### ## ## ## ## ## #### ## ## ; ##### ##### ## ### ###### ## ## ## ## ### ## ###### ;.equ COPYPROTECT = 1 ; Muss nur definiert werden, wenn Copyprotection gewünscht. ;.equ EMPHASIS = 1 ; Muss nur definiert werden, wenn Emphasis (50/15µs) gewünscht. - Wird per Jumper gesetzt! .equ MODE16BIT = 1 ; Muss nur definiert werden, wenn 16Bit Daten anstelle 24 Bit gewünscht sind - MUSS für ADC gesetzt sein! .equ SampleRate = 44100 ; Hz ; Setzt auch gleich die Bits im Subcode richtig. Zumindest für 32, 44.1 und 48kHz .equ Clock = SampleRate*256 ; 8.192MHz f. 32kHz; 11.2896MHz f. 44.1kHz; 12.288MHz f. 48kHz; 16.384MHz f. 64kHz; 22.5792MHz f. 88.2kHz .equ Baudrate = SampleRate*128 .equ BaudValue = (Clock / (2*Baudrate))-1 ; Formel gilt nur bei MSPI .equ PreambleB = 0x17 ;0xE8 ; Blockstart (L) ; LSB first! .equ PreambleM = 0x47 ;0xE2 ; Framestart (L) .equ PreambleW = 0x27 ;0xE4 ; Subframestart (R) .equ BlockLength = 192 .equ SampleLength = 8 ; 441 ; Memory .equ SubcodeData = 0x100 ; 192 Bytes Subcode ab hier - muss x00 sein! .equ BMCTable = 0x200 ; 16 Bytes - muss x00 sein! ; ###### ###### ##### ## ###### ###### ###### ###### ###### ; ## ## ## ## ## ## ## ## ## ## ## ; ###### ##### ## ## ## ##### ## ##### ###### ##### ; ## ## ## ## ## ## ## ## ## ## ## ## ; ## ## ###### ###### ## ###### ## ###### ## ## ###### ; Register - 16Bit Zugriff ; LDD LD ADIW MOVW Außerdem: ; STD ST SBIW ;r31:r30 - Z : X X X X LPM, IJMP, ICALL, XCH, SPM, LAC, LAS, LAT ;r29:r28 - Y : X X X X ;r27:r26 - X : X X X ;r25:r24 X X ;rn+1:rn X ;r1:r0 X MUL.., SPM ; Z wird für Samplezeiger genutzt (LPM!) ; Y wird für Übersetzungstabelle benötigt ; X wird für Subcodezeiger genutzt .def BlkCnt = r26 ; == XL .def SmplCntH = r25 .def SmplCnt = r24 .def SmpLENhelpH = r20 .def SmpLENhelpL = r19 .def VUCP = r18 ; 4 Bit Subcode .def tmp2 = r17 .def tmp = r16 ; r7 - MSN (Nibble - aber BMC in 8 Bit) ; 24 Bit Audiodaten für beide Kanäle ; r2 - LSN ; ###### ## ## ### ## ; ## ## ## ## #### ## ; ###### ## ## ## ## ## ; ## ## ## ## ## #### ; ## ## ###### ## ### .org 0x0000 rjmp init ; Da wir ohne jeglichen IRQ arbeiten müssen, geht es hier sogleich ohne IRQ Table los! version: .db 13,10,"SPDIF Testgenerator V1.0",13,10,"2024 jobstens.de",13,10,"Build %YEAR%-%MONTH%-%DAY% %HOUR%:%MINUTE%",13,10,"File %SOURCE%" ,13,10 ; ## ### ## ## ###### ; ## #### ## ## ## ; ## ## ## ## ## ## ; ## ## #### ## ## ; ## ## ### ## ## init: ; Stackpointer init (Auf die letzte Speicherstelle setzen) ldi tmp, HIGH(RAMEND) out SPH, tmp ldi tmp, LOW(RAMEND) out SPL, tmp ; Ports SBI DDRC, 2 ; Pin25: Test CBI DDRC, 2 ; Pin23: Emphasis Jumper zu GND (Pin22) SBI PORTC, 0 ; Pin23: PullUp an SBI DDRD, 1 ; SPDIF Output (TxD, SPI USART) ; ADC LDI tmp, 0x20 STS DIDR0, tmp LDI tmp, 0xD4 ; 11010100 STS ADCSRA, tmp LDI tmp, 0xA5 ; 10100101 STS ADMUX, tmp ; Initialisierung der SPI USART ; UDR0 = Datenregister - Put Data into it ; UCSR0A = Nur lesen. Bit 5 ist gesetzt, wenn ein Byte in UDR0 abgelegt werden kann. ; UCSR0B = 0b00001000 = 0x08 = nur TxD einschalten ; UCSR0C = 0b11000100 = 0xC0 = MSPI, LSB first ; UBRR0H:UBRR0L = 0:1 LDI tmp, HIGH(BaudValue) STS UBRR0H, tmp LDI tmp, LOW(BaudValue) STS UBRR0L, tmp LDI tmp, 0x08 ; Tx enable STS UCSR0B, tmp LDI tmp, 0xC4 ; MSPI, LSB first STS UCSR0C, tmp ; BMC vom ROM ins RAM, da Z bereits belegt und LPM daher nicht geht und aus dem RAM geht auch schneller LDI YH, HIGH(BMCTable) LDI YL, LOW(BMCTable) LDI ZH, HIGH(BMCTableROM*2) LDI ZL, LOW(BMCTableROM*2) CopyBMC: LPM tmp, Z+ ST Y+, tmp CPI YL, 16 BRNE CopyBMC ; LSB für 16-Bit Mode auf 0 setzen LDI tmp, 0x33 MOV r2, tmp MOV r3, tmp ; ## ## ##### ## ### ## ; ### ### ## ## ## #### ## ; #### #### ####### ## ## ## ## ; ## ### ## ## ## ## ## #### ; ## # ## ## ## ## ## ### ; ####################################################################################################################### ; Channelstatus bauen ; ####################################################################################################################### ; Channelstatus (Subcode) blank ziehen LDI XH, HIGH(SubcodeData) ; Zeiger auf Anfang LDI XL, LOW(SubcodeData) LDI tmp, 0b00110011 ; V = 0 ; U = 0 ; C = 0 ; P = 0 formatC: ST X+, tmp CPI XL, 192 BRNE formatC LDI XH, HIGH(SubcodeData) ; Zeiger auf Anfang LDI XL, LOW(SubcodeData) ; Jedes Byte: 0bPPCCUUVV - wobei 0b00110011 = 0000 ist ; Da Validity und Userbit immer 0 sind und Parity immer automatisch gebildet wird, Gibt es nur 2 Bitmuster: ; C = 0: 0b00110011 ; C = 1: 0b11010011 ; s.a. https://www.mikrocontroller.net/articles/S/PDIF#Channel-Status_/_Subcode ; Channelstatus bauen ; evtl. noch durch Jumpersettings von außen einstellbar machen!? ; 2: C-Bit (CopyBit) ; 0 = Kopierschutz aktiv ; 1 = Kopierschutz inaktiv .ifndef COPYPROTECT SetChannelBit 2 .endif ; 3: Emphasis (50/15µs) ; 0 = Emphasis inaktiv ; 1 = Emphasis aktiv .ifdef EMPHASIS SetChannelBit 3 .endif ; 8-14: Category code. 0000001 = Experimental SetChannelBit 14 ; 15: L-Bit - mal auf 1 gesetzt SetChannelBit 15 ; 24-27: Fs. 0000 = 44.1kHz, 0100 = 48kHz, 1100 = 32kHz .if SampleRate==48000 SetChannelBit 25 .endif .if SampleRate==32000 SetChannelBit 24 SetChannelBit 25 .endif ; 28-29: Taktabweichung. 01 = ...? SetChannelBit 29 ; ####################################################################################################################### ; /Channelstatus bauen ; ####################################################################################################################### ; Startbedingungen (funktioniert zwar auch ohne - aber so ist's schöner) LDI tmp, PreambleB LDI SmplCnt, 0 LDI SmplCntH, 0 LDI XH, HIGH(SubcodeData) ; Zeiger auf Anfang ; BlkCnt LDI XL, LOW(SubcodeData) LDI YH, HIGH(BMCTable) ; Zeiger auf BMC Tabelle. nur YH - YL wird unten gesetzt LDI ZH, HIGH(SampleBase*2) ; Zeiger auf Anfang ; SMPLCNT LDI ZL, LOW(SampleBase*2) ; SMPLCNT LDI SmpLENhelpH, HIGH(SampleLength) LDI SmpLENhelpL, LOW(SampleLength) ; ## ##### ##### ###### ; ## ## ## ## ## ## ## ; ## ## ## ## ## ###### ; ## ## ## ## ## ## ; ###### ##### ##### ## ; Einrückung: ; A = Verwaltungscode ; B = Zeitkritischer Code f. Ausgabe ; C = Abteilung bei Verwaltung ; A B C loop: ; ####################################################################################################################### ; links ; ####################################################################################################################### CheckUDRE ; 5*x Cycles STS UDR0, tmp ; Preamble verschickt (2Cyc) CheckUDRE ; 5*x Cycles STS UDR0, r2 ; 4 LSB 24-Bit Audio L verschickt (2Cyc) ; 9 Cyc ;LPM tmp, Z+ ; MSB Audio für nächsten durchlauf holen (3Cyc) ; GET_SAMPLE LDI tmp, 0x80 ; MSBit gesetzt EOR tmp, r6 ; Nun die oberen 8 Bit des ADC MOV YL, tmp ; (1Cyc) ; MAKE_BMC ANDI YL, 0x0F ; unteres Nibble (4LSB) (1Cyc) ; MAKE_BMC LD r6, Y ; hole BMC für 4 Bit (1Cyc) ; MAKE_BMC tst r5 ; Vorheriges Byte (auf MSB) testen (1Cyc) ; MAKE_BMC BRPL bmc6 ; Branch wenn MSB von r7 == 0 (t=2Cyc, f=1Cyc) ; MAKE_BMC COM r6 ; wenn rn-1.MSB gesetzt ist, muss rn invertiert werden (1Cyc) ; MAKE_BMC bmc6: ; MAKE_BMC CheckUDRE ; 5*x Cycles STS UDR0, r3 ; 4 LSB 20-Bit Audio L verschickt (2Cyc) ; 7 Cyc MOV YL, tmp ; (1Cyc) ; MAKE_BMC SWAP YL ; Nibbles vertauschen (1Cyc) ; MAKE_BMC ANDI YL, 0x0F ; oberes Nibble (4LSB) (1Cyc) ; MAKE_BMC LD r7, Y ; hole BMC für 4 Bit (1Cyc) ; MAKE_BMC tst r6 ; r4 (auf MSB) testen (1Cyc) ; MAKE_BMC BRPL bmc7 ; Branch wenn MSB von r7 == 0 (t=2Cyc, f=1Cyc) ; MAKE_BMC COM r7 ; wenn r4.MSB gesetzt ist, muss r5 invertiert werden (1Cyc) ; MAKE_BMC bmc7: ; MAKE_BMC CheckUDRE ; 5*x Cycles STS UDR0, r4 ; 4 LSB 16-Bit Audio L verschickt (2Cyc) ; 2Cyc LD VUCP, X+ ; Hole Subcode und inc. gleichzeitig Zeiger UND BlkCnt (2Cyc) ; SUBCODE , BLKCNT CheckUDRE ; 5*x Cycles STS UDR0, r5 ; 4 Bit Audio L verschickt (2Cyc) ; Jumper f. Emphasis 5Cyc LDI tmp, 0b00110011 ; C = 0 ; (1Cyc) ; MAKE SUBCODE SBIS PINC, 0 ; Wird Jumper gesetzt (=GND =L) ist Emphasis ein. SBIC benutzen, wenn dies bei H der Fall sein soll LDI tmp, 0b11010011 ; C = 1 ; (1Cyc) ; MAKE SUBCODE STS (SubcodeData+3), tmp ; (2Cyc) ; MAKE SUBCODE CheckUDRE ; 5*x Cycles STS UDR0, r6 ; 4 Bit Audio L verschickt (2Cyc) ; Ganzer Block entweder 3Cyc tst r7 ; r7 (auf MSB) testen (1Cyc) ; SUBCODE BRPL sb0end ; Branch wenn MSB von r7 == 0 (t=2Cyc, f=1Cyc) ; SUBCODE COM VUCP ; wenn r7.MSB gesetzt ist, muss VUCP invertiert werden (1Cyc) ; SUBCODE sb0end: ; SUBCODE CheckUDRE ; 5*x Cycles STS UDR0, r7 ; 4 MSB Audio L verschickt (2Cyc) ; Ganzer Block 3Cyc ANDI VUCP, 0x7F ; Parity immer richtig (1Cyc) ; SUBCODE ADIW SmplCnt, 1 ; ++ (2Cyc) ; SMPLCNT CheckUDRE ; 5*x Cycles STS UDR0, VUCP ; 4 Bit VUCP verschickt (2Cyc) ; ####################################################################################################################### ; rechts ; ####################################################################################################################### ; Ganzer Block entweder 4Cyc (true) oder 5Cyc (false) CP SmplCnt, SmpLENhelpL ; (1Cyc) ; SMPLCNT CPC SmplCntH, SmpLENhelpH ; (1Cyc) ; SMPLCNT BRLO scend ; SampleLength < SmplCnt (t=2Cyc, f=1Cyc) ; SMPLCNT LDI SmplCntH, 0 ; SampleLength erreicht, Reset (1Cyc) ; SMPLCNT LDI SmplCnt, 0 ; (1Cyc) ; SMPLCNT scend: ; SMPLCNT LDI tmp, PreambleW ; Rechts ist IMMER W (1Cyc) CheckUDRE ; 5*x Cycles STS UDR0, tmp ; Preamble verschickt (2Cyc) CheckUDRE ; 5*x Cycles STS UDR0, r2 ; 4 LSB 24-Bit Audio R verschickt (2Cyc) ; Ganzer Block entweder 4Cyc (true) oder 5Cyc (false) MOV tmp, SmplCnt ; (1Cyc) ; SMPLCNT OR tmp, SmplCntH ; (1Cyc) ; SMPLCNT BRNE sc2end ; Branch if tmp != 0 (t=2Cyc, f=1Cyc) ; SMPLCNT LDI ZH, HIGH(SampleBase*2) ; Wenn tmp == 0 Zeiger auf Anfang (1Cyc) ; SMPLCNT LDI ZL, LOW(SampleBase*2) ; (1Cyc) ; SMPLCNT sc2end: ; SMPLCNT CheckUDRE ; 5*x Cycles STS UDR0, r3 ; 4 LSB 20-Bit Audio R verschickt (2Cyc) .ifndef MODE16BIT ; 6 Cyc LPM tmp, Z+ ; LSB Audio für nächsten durchlauf holen (3Cyc) ; GET_SAMPLE MOV YL, tmp ; (1Cyc) ; MAKE_BMC ANDI YL, 0x0F ; unteres Nibble (4LSB) (1Cyc) ; MAKE_BMC LD r2, Y ; hole BMC für 4 Bit - bei r2 muss hier nichts mehr passieren (1Cyc) .endif CheckUDRE ; 5*x Cycles STS UDR0, r4 ; 4 LSB 16-Bit Audio R verschickt (2Cyc) .ifndef MODE16BIT ; 7 Cyc MOV YL, tmp ; (1Cyc) ; MAKE_BMC SWAP YL ; Nibbles vertauschen (1Cyc) ; MAKE_BMC ANDI YL, 0x0F ; oberes Nibble (4LSB) (1Cyc) ; MAKE_BMC LD r3, Y ; hole BMC für 4 Bit (1Cyc) ; MAKE_BMC tst r2 ; r2 (auf MSB) testen (1Cyc) ; MAKE_BMC BRPL bmc3 ; Branch wenn MSB von r7 == 0 (t=2Cyc, f=1Cyc) ; MAKE_BMC COM r3 ; wenn r2.MSB gesetzt ist, muss r3 invertiert werden (1Cyc) ; MAKE_BMC bmc3: ; MAKE_BMC .endif LDI tmp, 0xD4 ; 11010100 ; START ADC STS ADCSRA, tmp ; START ADC CheckUDRE ; 5*x Cycles STS UDR0, r5 ; 4 Bit Audio R verschickt (2Cyc) ; 9 Cyc ;LPM tmp, Z+ ; Audio für nächsten durchlauf holen (3Cyc) ; GET_SAMPLE LDS tmp, ADCL ; 2 Bit LSB vom ADC holen MOV YL, tmp ; (1Cyc) ; MAKE_BMC ANDI YL, 0x0F ; unteres Nibble (4LSB) (1Cyc) ; MAKE_BMC LD r4, Y ; hole BMC für 4 Bit (1Cyc) ; MAKE_BMC tst r3 ; Vorheriges Byte (auf MSB) testen (1Cyc) ; MAKE_BMC BRPL bmc4 ; Branch wenn MSB von r7 == 0 (t=2Cyc, f=1Cyc) ; MAKE_BMC COM r4 ; wenn rn-1.MSB gesetzt ist, muss rn invertiert werden (1Cyc) ; MAKE_BMC bmc4: ; MAKE_BMC CheckUDRE ; 5*x Cycles STS UDR0, r6 ; 4 Bit Audio R verschickt (2Cyc) LDS r6, ADCH ; 8 MSB vom ADC schon mal sichern CheckUDRE ; 5*x Cycles STS UDR0, r7 ; 4 MSB Audio R verschickt (2Cyc) ; 7 Cyc MOV YL, tmp ; (1Cyc) ; MAKE_BMC SWAP YL ; Nibbles vertauschen (1Cyc) ; MAKE_BMC ANDI YL, 0x0F ; oberes Nibble (4LSB) (1Cyc) ; MAKE_BMC LD r5, Y ; hole BMC für 4 Bit (1Cyc) ; MAKE_BMC tst r4 ; r4 (auf MSB) testen (1Cyc) ; MAKE_BMC BRPL bmc5 ; Branch wenn MSB von r7 == 0 (t=2Cyc, f=1Cyc) ; MAKE_BMC COM r5 ; wenn r4.MSB gesetzt ist, muss r5 invertiert werden (1Cyc) ; MAKE_BMC bmc5: ; MAKE_BMC CheckUDRE ; 5*x Cycles STS UDR0, VUCP ; 4 Bit VUCP verschickt (2Cyc) ; ####################################################################################################################### ; Ganzer Block entweder 4Cyc (true) oder 5Cyc (false) LDI tmp, PreambleM ; normalerweise kommt als nächstes M (1Cyc) ; BLKCNT CPI BlkCnt, BlockLength ; (1Cyc) ; BLKCNT BRLO loop2 ; Branch (t=2Cyc, f=1Cyc) ; BLKCNT CLR BlkCnt ; Blockstart = 0 (1Cyc) ; BLKCNT LDI tmp, PreambleB ; Bei Blockstart jedoch B (1Cyc) ; BLKCNT loop2: RJMP loop ; (2Cyc) ; ###### ## ## ###### ## ## ###### ; ## ## ## ## ## ## ## ; ## ####### ##### ##### #### ; ## ## ## ## ## ## ## ; ## ## ## ###### ## ## ###### ; Bitte bedienen Sie sich! ; Nachfolgend die Tabelle für 4 Datenbits auf 8 Kanalbits ; LSB BMC ist IMMER 1. Byte wird bei vorhergehender 0 Invertiert. BMCTableROM: ; Data BMC .db 0x33, 0xCD ; 0000 00110011 ; 0001 11001101 .db 0xCB, 0x35 ; 0010 11001011 ; 0011 00110101 .db 0xD3, 0x2D ; 0100 11010011 ; 0101 00101101 .db 0x2B, 0xD5 ; 0110 00101011 ; 0111 11010101 .db 0xB3, 0x4D ; 1000 10110011 ; 1001 01001101 .db 0x4B, 0xB5 ; 1010 01001011 ; 1011 10110101 .db 0x53, 0xAD ; 1100 01010011 ; 1101 10101101 .db 0xAB, 0x55 ; 1110 10101011 ; 1111 01010101 ; 3 Byte = ein 24 Bit Sample - nun als signed int 24 - LSB first ; oder - sofern aktiviert ; 2 Byte = ein 16 Bit Sample - nun als signed int 16 - LSB first SampleBase: NOP ; Die Daten kommen aus dem ADC. Die Marke ist nur deshalb noch da, um nicht den ganzen Code zu zerpflügen.