DSP-Screen

Der DSP wird hauptsächlich für die Echtzeitbearbeitung im Audiobereich eingesetzt (z.B. Filter, Fast-Fourier-Transformation ...). Dieser Beitrag soll zeigen, daß man den DSP auch für Aufgaben im Videobereich benutzen kann.

Der DSP soll so programmiert werden, daß er die Funktion eines Video-Controllers übernehmen kann. Ein (einfaches) Videosystem benötigt folgende Baugruppen:

Die wichtigste Kenngröße eines Videosystems ist der Pixel-Takt. Je höher dieser Takt ist, um so mehr Pixel lassen sich innerhalb einer Zeile darstellen. Es ist also sinnlos, einen Shifter softwaremäßig zu realisieren, da sonst die Auflösung zu gering wird. Tja, der DSP hat aber gar keinen Shifter? Hat er doch, nur unter einem anderen Namen: SSI (Synchronous Serial Interface)!

Das SSI wird für die schnelle serielle Kommunikation mit anderen Systemen benutzt (z.B. mit dem CODEC). Die maximale Übertragungsrate beträgt 4 Millionen Bits pro Sekunde. Da der DSP im Falcon mit 32 MHz getaktet wird, können bis zu 8 Millionen Bits pro Sekunde übertragen werden. Dies entspricht einem Pixel-Takt von 8 MHz. 8 MHz Pixel-Takt sind für ein Videosystem nicht besonders hoch, aber wenn man das normale TV-Zeitverhalten benutzt, sind 320 Pixel pro Zeile möglich (der ST arbeitet bei der niedrigen Auflösung auch mit 8 MHz Pixelclock).

Somit ergeben sich folgende Daten für den DSP-Screen:

Zeilenfrequenz: 15625 Hz
Bildfrequenz: 50 Hz
Auflösung: 320 x 200 Pixel
Farben: 2

Ein Bild mit 320 x 200 Pixeln braucht einen Bildspeicher von 8000 Bytes. Die Word-Länge des SSI wird auf 16 Bit programmiert. Da ein DSP-Word 3 Bytes hat, ergibt sich eine etwas ungewöhnliche Bildspeicherorganisation: Von den DSP-Words wird das oberste und mittlere Byte benutzt. Das unterste Byte wird nicht verwendet. Der Bildspeicher hat also eine Größe von 4000 DSP-Words.

Die Routinen für den Bildaufbau sollen im Interrupt ablaufen, damit das Hauptprogramm während der horizontalen und vertikalen Austastlücken weiter arbeiten kann. Zeitbasis für den Interrupt ist die Zeilenfrequenz. Dafür wird der Timer des SCI auf eine Frequenz von 15625 Hz programmiert und der Timerinterrupt eingeschaltet. In diesen Interrupt-Routinen werden die Synchronimpulse erzeugt und die Daten vom Bildspeicher in das SSI geladen.

Die Synchronimpulse...

... können leider nicht einfach auf eine als parallel-I/O definierte Leitung des Port C ausgegeben werden, sie wären dann nicht exakt mit den Videodaten synchronisiert, was zu einem unruhigen Bild führt. In den Synchron-Modes des SSI gibt es aber Flags, die so gepuffert sind, daß sie genau mit den TX/RX-Daten umschalten. Hier wird das Flag OFO verwendet. Somit stehen die Synchronimpulse an dem SCO-Anschluß des DSP-Connectors zur Verfügung.

Austastung

Das SSI darf nur dann Daten ausgeben, wenn das Bild dargestellt werden soll. Sonst muß immer Low-Pegel ausgegeben werden (Dunkeltastung). Man darf aber nicht das TE (Transmit Enable)-Flag zurücksetzen. da sonst die Synchronimpulsausgabe über das OFO-Flag nicht mehr funktioniert. Deshalb wird zum Schluß einer Zeile immer 0 in das TX-Register geladen. Nachdem diese 0 ausgegeben worden ist, gibt es einen Transmitter-Underrun-Error, da keine neue Daten in das TX-Register gelangt sind. Das ist aber nicht weiter schlimm, das SSI gibt einfach die alten Daten (die 0!) erneut aus. Somit bleibt der Transmitter-Ausgang solange auf Low, bis neue Daten zur Verfügung stehen.

Die Interrupts

Es gibt 5 verschiedene Timer-Interrupt-Routinen, wobei eine nur einmal zur Initialisierung benötigt wird und die anderen vier immer wieder aufgerufen werden.

timer_init:
Diese Interrupt-Routine wird nur einmal zur Initialisierung des SSI ausgeführt. Wenn das SSI nicht während des Timer-Interrupts initialisiert wird, kann es evtl. zur einer falschen Synchronisation kommen.

timer 1:
Diese Routine erzeugt den vertikalen Synchronimpuls. Er sollte nach TV-Norm 2.5 mal Zeilendauer (64 ps) = 160 ps lang sein, aber mit 128 ps (2 Zeilen) gibt es keine Probleme, und das Programm wird einfacher. Das Register r6 wird mit der Anfangsadresse des Bildspeichers geladen.

timer2:
Diese Routine legt die Größe des oberen Borders fest. Es werden nur die Horizontalsynchronimpulse erzeugt. Dieser Synchronimpuls hat eine Dauer von 4 ps. Nach dem Einschalten wird einfach solange gewartet, bis das SSI zwei Words ausgegeben hat. Da ein Word in 2 ps (1 durch 8 MHz mal 16) ausgegeben wird, werden genau 4ps erreicht.

timer3:
Nach der Ausgabe des Horizontalimpulses werden nach einer Wartezeit Bilddaten ausgegeben. Als Zeiger auf den Bildspeicher wird das Register r6 benutzt. Für 320 Pixel werden 20 (320/16) Words gebraucht. Zum Schluß wird noch eine 0 ausgegeben, um die Dunkeltastung zu aktivieren.

timer4:
Erzeugt die Horizontalimpulse für den unteren Border.

Insgesamt ergibt sich folgender Ablauf:

Int.-Nr Funktion Anzahl der Zeilen
1 Vertikal-Sync 2
2 Top Border 55
3 Bild darstellen 200
4 Low Border 56
- Summe 313

Ein Bild besteht also aus 313 Zeilen, damit ergibt sich die Bildfrequenz von 15625 / 313 = ~50 Hz.

Da die Register des DSP nicht so allgemeingültig wie z.B. die 68000er Register sind, können logische und arithmetische Operationen nur mit den Akkumulatoren (A oder B) ausgeführt werden. Eine Interrupt-Routine soll möglichst wenig Register verwenden, um unnötiges Register retten zu vermeiden. Als Zeilenzähler wird deshalb das Register r7 verwendet. Um ein Adreßregister um 1 zu erhöhen, benutzt man den Befehl „move (rx)+“ (Address Register Update). Um die End wertabfrage zu vereinfachen, wird folgender Trick angewendet: das Register wird immer mit dem Wert 256-n geladen. Beim n-ten lnkrementieren wird dann das Bit 8 gesetzt, welches dann einfach über ein JSET #8,rx,adr ausgewertet werden kann.

Die Programme

„DSP_SCR.PRG“ lädt das DSP-Programm, ,DSP_SCR. A5 6“ und kopiert dann den 8x8-Zeichensatz in den DSP. Das Hauptprogramm ist eine einfache Bildschirmroutine. Folgende Sonderzeichen werden unterstützt:

$07: Bildschirm löschen
$0d: Zeilenschaltung; wenn die letzte Zeile erreicht ist, scrollt das Bild um 1 Zeile nach oben. Das Zeichen wird im oberen Byte des Hostinterfaces erwartet.

Um ein Zeichen auszugeben, muß folgendes Programmstück in Supervisormodus ausgeführt werden:

Zeichen ist in d0

not_empty:
    btst    #1,ffffa202.w ;Host empty ?
    beq.s   not_erapty  ;nein
    move.b  d0,$ffffa205.w ;TXH, Zeichen ausgeben 
    clr.b   $ffffa206.w ;TXM
    clr.b   $ffffa207.w ;TXL

Zur Demonstration habe ich ein Programm geschrieben, das die Trap#1-Aufrufe auf den DSP-Screen protokolliert (TRAPVIEW.PRG). Dieses Programm wird aus Platzgründen nicht abgedruckt, befindet sich aber auf der Monatsdiskette.

Die Hardware

Jetzt fehlt nur noch die Hardware (siehe Bild). Man braucht ein spezielles Adapterkabel, was man sich leicht selbst anfertigen kann. Die Widerstände können natürlich bei TTL-Monitoren entfallen.

Die Bildschirmroutine ist nur eine Minimalversion. Es sind natürlich auch grafische Funktionen möglich. Auch die Auflösung läßt sich noch weiter erhöhen (z.B. 384 x 240). Man kann noch viele andere Funktionen einbauen, z.B. Hardwarescrolling, farbige Darstellung usw., da der DSP-Screen ein frei programmierbares Videosystem ist.

Literatur:

Motorola DSP56000/DSP56001 - Users-Manual


/* DSP-Screen-Hauptprogramm */ /* (c)1993 by MAXON-Computer */ /* Autor: Steffen Scharfe */ /* Dateiname: DSP_SCR.C */ #include <stdio.h> #include <tos.h> extern int load_dsp ( void ); extern void write_host( long d ); extern void CopyFont( void ); void out_str( char *s ); int main( void ) { chax info[] = "DSP-Screen \ by Steffen Scharfe\x0d"; if ( ! load_dsp() ) { /* DSP-Programm laden */ printf("kann DSP-Programm nicht laden !" ); return( 1 ); } CopyFont(); out_str( info ); return( 0 ); } void out_str( char *s ) { while( *s ) write_host( (long) *s++ << 16 ); }

/* DSP-Screen-Ladeprogramm */ /* (c)1993 by MAXON-Computer */ /* Autor: Steffen Scharfe */ /* Dateiname: DSP_LOAD.C */ #include <stdio.h> #include <tos.h> #include <ext.h> #include <stdlib.h> /* Prototypen */ void write_host( long d ); int load_dsp( void ) { char buffer[3*512]; char mem; long adr, dat; int i; FILE *fp; fp = fopen( "LOADER.DSP", "r" ); if ( fp == NULL ) return{ 0 ); i = 0; while ( fscanf( fp, "%c %1X %1X\n", &mem, &adr, &dat ) != EOF ) { buffer[i++] = (char) ( dat >>16 ); buffer[i++] = (char) ( dat >> 8 ); buffer[i++] = (char) dat; if ( i > ( 3 * 512 ) ) return( 0 ); } fclose( fp ); Dsp_ExecBoot( buffer, i / 3, 1 ); write_host( 0 ); /* kein Debugger */ fp = fopen( "DSP_SCR.DSP", "r" ); if ( fp == NULL ) return( 0 ); while ( fscanf( fp, "%c %1X %1X\n", &mem, &adr, &dat ) != EOF ) { adr |= (long) mem << 16; write_host( adr ); write.host( dat ); } write_host( 0xff0000L ); /* fertig */ fclose( fp ); return( 1 ); } void write_host( long d ) { char *host_status = (char *)0xffffa202L; char *host_tx = (char *)0xffffa205L; long old_super_stack; old_super_stack = Super( 0L ); while ( ! ( *host_status & 0x02 ) ); *host_tx++ = (char) ( d >> 16 ); *host_tx++ = (char) ( d >> 8 ); *host_tx = (char) d; Super((void *) old_super_stack ); }

; +++++++++++++++++++++++++++++++++++ ; + DSP-Screen, DSP-Assembler-Teil + ; + (c)1993 by MAXON-Computer + ; + Autor: Steffen Scharfe + ; + Dateiname: DSP_SCR.A56 + ; +++++++++++++++++++++++++++++++++++ PBC equ $ffe0 CRA equ $ffee CRB equ $ffed PCC equ $ffe1 PCDDR equ $ffe3 PCD equ $ffe5 TX equ $ffef SCR equ $fff0 SCCR equ $fff2 SSISR equ $ffee BCR equ $fffe IPR equ $ffff HSR equ $ffe9 HRX equ $ffeb HTX equ $ffeb screen equ $1000 font equ $2000 org x:$0 cursor_x dc 0 cursor_y dc 0 org p:0 jmp $40 org p:$1c t_vector jsr timer_init org p:$40 movep #0,x:BCR ;no Waitstates movep #$c000,x:IPR ;Interrupt-Prioritaet ;Host initialisieren movep #1,x:PBC ;Init Timer fuer Zeilenfrequenz movep #$2000,x:SCR ;Timer Int an movep #$0020,x:SCCR ;15.625 kHz andi #$fc,mr ;Int an jsr get_font ;Font laden jsr cls loop jclr #0,x:HSR,* ;auf Daten warten movep x:HRX,a ;laden jsr out_char jmp loop ; Interrupt-Routinen fuer den Bildaufbau ; werden alle 64 us aufgerufen ; verwendet folgende Register ; (duerfen von dem Hauptprogramm nicht benutzt ; werden!!) ; r6: Zeiger auf den Bildspeicher ; r7: Zeilenzaehler ; n7: Hilfregister ; Init SSI Transmit, 8 MHz Pixelclock ; um die SSI Ausgabe mit dem Timerinterrupt ; zu synchronisieren timer_init movep #$4000,x:CRA ;16 Bit, 8 MHz movep #$123c,x:CRB ;Frame Sync intern movep #$178,x:PCC ;Write als SCI movep #0,x:TX jclr #6,x:SSISR,* move #t_vector+1,r7 move #timer1,n7 movem n7,p:(r7) move #256-2,r7 rti ; Vertikal-Sync, 2 Zeilen ( 128 us ) timer1 bclr #0,x:CRB ;V-Sync an movep #0,x:TX move (r7)+ jclr #8,r7,timer1_end move #t_vector+1,r7 move #timer2,n7 movem n7,p:(r7) move #256-55,r7 move #screen,r6 ;Adr des Video-RAM timer1_end rti ; Top Border, 55 Zeilen timer2 bclr #0,x:CRB ;H-Sync an movep #0,x:TX ;4 us jclr #6,x:SSISR,* movep #0,x:TX jclr #6,x:SSISR,* bset #0,x:CRB ;H~Sync aus movep #0,x:TX move (r7)+ jclr #8,r7,timer2_end move #t_vector+1,r7 move #timer3,n7 movem n7,p:(r7) move #256-200,r7 timer2_end rti ; Screen, 200 Zeilen timer3 bclr #0,x:CRB ;H-Sync an movep #0,x:TX ;4 us jclr #6,x:SSISR,* movep #0,x:TX jclr #6,x:SSISR,* bset #0,x:CRB ;H-Sync aus movep #0,x:TX do #6,LeftBorder ;12 us warten jclr #6,x:SSISR,* movep #$000000,x:TX LeftBorder do #20,Disp jclr #6,x:SSISR,* movep x:(r6)+,x:TX ;40 us fuer 1 Pixel-Zeile Disp jclr #6,x:SSISR,* movep #$000000,x:TX move (r7)+ jclr #8,r7,timer3_end move #t_vector+1,r7 move #timer4,n7 movem n7,p:(r7) move #256-56,r7 timer3_end rti ; Low Border, 56 Zeilen timer4 bclr #0,x:CRB ;H-Sync an movep #0,x:TX ;4 us jclr #6,x:SSISR,* movep #0,x:TX jclr #6,x:SSISR,* bset #0,x:CRB ;H-Sync aus movep #0,x:TX move (r7)+ jclr #8,r7,timer4_end move #t_vector+1,r7 move #timer1,n7 movem n7,p:(r7) move #256-2,r7 timer4_end rti ;--- Bildschirm loeschen cls move #0,x0 move #screen,r0 do #20*200,cls1 move x0,x:(r0)+ cls1 move x0,x:cursor_x move x0,x:cursor_y rts ;--- scrollt um 8 Pixel-Zeilen nach oben scroll move #screen,r0 ;Ziel move #screen+8*20,r1 ;Quelle do #192*20,scroll1 move x:(r1)+,x0 move x0,x:(r0)+ scroll1 clr a do #8*20,scroll2 ;letzte Zeile loeschen move a,x:(r0)+ scroll2 rts ;--- 1 Zeile nach unten, wenn letzte ; Zeile erreicht: scrollen cr clr a move a,x:cursor_x ;Zeilenanfang move x:cursor_y,a move #>24,x0 ;letzte Zeile ? cmp x0,a #>1,x0 jeq scroll ;ja add x0,a ;naechste Zeile move a,x:cursor_y rts ;--- gibt ein Zeichen aus ;--- Zeichen muss im hoeherwertigsten ; Byte von A1 stehen out_char move #$0d,x0 cmp x0,a #$07,x0 ;CR ? jeq cr ;ja cmp x0,a #$400,x0 ;CLS ? jeq cls ;ja, Bild loeschen move a1,x1 mpy x1,x0,a #>$0007f8,x0 ; 13 Bits nach rechts verschieben and x0,a #>font,x0 add x0,a move a1,r0 ;r0 zeigt auf Font-Daten move x:cursor_x,a move a,r1 nop move (r1)+ ;naechste x-Position nop move r1,x:cursor_x asr a ;/2 move a1,x0 move x:cursor_y,a asl a ;*2 move a,x1 asl a asl a ;*8 add x1,a ;*10 asl a asl a asl a asl a ;*160 move a,x1 move #>screen,a add x0,a add x1,a move a,r1 ;r1 = Screenadr move #>$ff0000,x0 ;Maske High-Byte btst #0,x:cursor_x jcs mask_ok ;cursor_x zeigt ; schon auf das naechste Zeichen move #>$00ff00,x0 ;Maske Low-Byte mask_ok move #20,n1 ;1 Zeile = 20 Words do #8,out_char1 move x:(r0)+,a1 ;Font-Daten and x0,a ;maskieren move a1,x1 move x:(r1),a1 ;Bilddaten or x1,a ;ein-odern move a1,x:(r1)+n1 ;schreiben out_char1 rts ;-- Font laden get_font move #>font,r0 do #256*8,get_font1 jclr #0,x:HSR,* ;auf Daten warten movep x:HRX,x:(r0)+ ;laden get_font1 rts

; DSP-Screen Font-Lader ; (c)1993 by MAXON-Computer ; Autor: Steffen Scharfe ; Dateiname: FONT.S host equ $ffffa200 export CopyFont CopyFont: movem.l d3-d7/a2-a6,-(sp) pea super move.w #$26,-(sp) trap #14 addq.l #6,sp movem.l (sp)+,d3-d7/a2-a6 rts super: aline #0 move.l 4(a1),a0 ;8*8 Font move.l 76(a0),a0 ;Adr Zeichensatz moveq #0,d0 ;Zeichen lea host,a1 CopyFont1: lea (a0,d0.w),a2 ;Adresse Zeichen-Font moveq #8-1,d2 CopyChar: btst #1,2(a1) ;Host empty ? beq.s CopyChar ;nein move.b (a2),5(a1) ;TXH move.b (a2),6(a1) ;TXM clr.b 7(a1) ;TXL add.w #256,a2 dbra d2,CopyChar addq.w #1,d0 cmp.w #256,d0 bne.s CopyFont1 rts

;Projekt-File ;Dateiname: DSP_SCR.PRJ dsp_scr.prg .C [ -Y ] .L [ -L -Y ] .S [ -Y ] = ; list of modules follows ... PCSTART.O ; startup code dsp_load.o font.o dsp_scr.c PCSTDLIB.LIB ; standard library PCEXTLIB.LIB ; extended library PCTOSLIB.LIB ; TOS library

Steffen Scharfe
Links

Copyright-Bestimmungen: siehe Über diese Seite