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
Aus: ST-Computer 10 / 1993, Seite 124

Links

Copyright-Bestimmungen: siehe Über diese Seite