Viele Geräte werden heutzutage nicht nur mit einer Fernbedienung geliefert, einige besitzen sogar einen Fernbedienungseingang. Der Control-S-Bus von Sony (auch SIRCS genannt) ist ein TTL-kompatibler Bus, d.h. er arbeitet mit Signalen von 0 und 5 Volt. Damit ist er sehr einfach mit einem Computer steuerbar. Dieser Artikel beschreibt das Format eines Control-S-Befehls samt zugehöriger Steuer-Software. Damit läßt sich der ST z.B. als Schnittcomputer oder als komfortabler Timer für die nächsten 100 Jahre benutzen, obwohl das Gerät dann wahrscheinlich veraltet ist.
Dabei ist der Hardware-Aufwand minimal, mit weniger als 10 Mark ist man dabei. Natürlich ist bei solchen Basteleien entsprechend vorsichtig vorzugehen, und wir können weder garantieren, daß das vorgestellte Programm mit Ihrem Gerät zusammenarbeitet, noch ob es Rechner oder Videorekorder beschädigt!
Allerdings läuft bei mir die Schaltung einwandfrei, und ein Widerstand sorgt dafür, daß z.B. bei Kurzschluß nach dem Ohmschen Gesetz der Port im ST mit nur
I = U/R = 5V / 5600 Ω = 0.9 mA
belastet wird. Damit geht’s schon an die Hardware. Auf der Seite des zu steuernden Gerätes braucht man nur einen 3.5mm-Mono-Klinkenstecker, auf der Seite des ST einen 25pol. Sub-D-Stecker, sofern man das Gerät mit dem Centronicsport steuern möchte.
Natürlich kann man dazu auch irgendeinen anderen Port benutzen, in der Steuer-Software müssen dann nur die beiden Routinen set_one und set_zero am Ende des Assemblerlistings geändert werden.
Als Steuerleitung kann ein beliebiges zweipoliges Kabel benutzt werden, besser ist allerdings ein abgeschirmtes Monokabel. Die Abschirmung wird dabei an eine der Masseleitungen am Sub-D-Stecker gelötet (Anschluß 18-25). Der 5.6KΩ-Widerstand wird an Daten-Bit 0 des Drucker-Ports gelegt (Anschluß 2). Anschließend muß die innere Leitung des Kabels an das andere Ende des Widerstandes gelötet werden. Damit sind die Arbeiten auf der Rechnerseite des Kabels beendet. Am anderen Ende wird das Innere des Kabels an den inneren Anschluß des Klinkensteckers gelötet, die Abschirmung kommt an den äußeren Anschluß des Steckers. Und das war’s.
Dieser Klinkenstecker kommt in die CONTROL-S-IN-Buchse des zu steuernden Gerätes, NICHT an die CONTROL-S-OUT-Buchse!
Da im Gerät der CONTROL-S-Eingang und der Fernbedienungssensor auf eine Leitung verodert sind, ist die Fernbedienung wirkungslos, wenn der Drucker-Port dauerhaft auf High geschaltet wird! Aber nach dem Reset schaltet der ST den Drucker-Port ohnehin auf Low. Und Schaden würde das Gerät dabei sowieso nicht nehmen (hat es bei mir zumindest nicht getan), also keine Bange.
Hier nun eine Beschreibung des Sony-Control-S-Formats. Keine Angst, wenn nicht alles verstanden wird, das Programm läuft auch so. Die Routine send_sony() übernimmt die ganze Arbeit und kann zu eigenen Programmen dazugebunden werden. In Tabelle 1 stehen die Gerätecodes und in Tabelle 2 die mir bekannten Befehle. Ein Teil dieser Befehle stammt aus den Sony-Control-S-Protocol-Specifications von Scott Coleman, den Rest habe ich mit einem Oszilloskop selbst herausgefunden. Wenn also der eine oder andere Befehl hier nicht aufgeführt ist, hilft nur: ausprobieren. Auch kann ein Befehl auf diesem oder jenem Gerät eine andere Bedeutung haben oder überhaupt nicht funktionieren. Denn sowohl Scott Coleman als auch ich hatten keine offizielle Sony-Dokumentation zur Verfügung, alle Angaben beruhen auf experimentell ermittelten Werten.
Aber jetzt zur Sache. Ein Befehlswort besteht dabei aus 12 Bits, wovon die ersten 7 Bits den eigentliche Befehl darstellen und die restlichen 5 Bits das Gerät angeben, für das der Befehl bestimmt ist. Damit können 128 Befehle an 32 Geräte verschickt werden, wobei ja Befehlscodes für verschiedene Geräte auch unterschiedliche Bedeutung haben können. Nun werden diese 12 Bits aber nicht einfach so verschickt, sondern werden codiert. Zunächst geht das Signal für 2.4 ms auf High, dann für 0.6 ms auf Low. Das war sozusagen das „Start-Bit“. Jetzt werden die 12 Bits des Befehls-/Gerätecodes gesendet, jeweils das niederwertigste Bit zuerst. Ein Bit ist dabei folgendermaßen codiert:
0: 0.6ms High, 0.6ms Low
1: 1,2ms High, 0.6ms Low
TV 1
VTR1 2 Betamax
VTR2 4 Betamax
VTR2 7 Video 8
VTR3 11 VHS
Tabelle 1: Gerätecodes
Damit wird sichergestellt, daß der Code gleichstromfrei ist, das Signal muß also nach spätestens 1,2ms seinen Zustand ändern.
Bevor der Befehlscode noch einmal bzw. ein anderer Befehlscode gesendet werden darf, muß zunächst gewartet werden, bis 45ms vergangen sind, seit das Signal beim Start-Bit zum ersten Mal auf High gegangen ist.
Damit ein Code vom Gerät erkannt wird, muß mindestens 3mal das gleiche Befehlswort erkannt werden, sonst wird von einem Übertragungsfehler ausgegangen.
Der Routine send_sony() wird nur übergeben, welches Gerät welchen Befehl wie oft erhalten soll. Das Beispielprogramm steuert die wichtigsten Funktionen eines Videorekorders und ist als Accessory oder als Programm lauffähig. Unter Verwendung der Projektdatei und des Turbo-C Compilers 2.0 sollte es kein Problem sein, das Programm zum Laufen zu bringen. Gegebenenfalls muß nur die Zeile
#define VTR VTR3
im SONY.C-Listing auf das entsprechende System angepaßt werden.
Literatur:
Sony-Control-S-Protocol-Specifications by Scott Coleman
0 | Taste 1 |
1 | Taste 2 |
2 | Taste 3 |
3 | Taste 4 |
4 | Taste 5 |
5 | Taste 6 |
6 | Taste 7 |
7 | Taste 8 |
8 | Taste 9 |
9 | Taste 10/Taste 0 |
10 | Taste -/—-, Taste 11, Taste * |
11 | Taste 12, Taste CH/Enter/# |
12 | Taste 13, Taste 1- |
13 | Taste 14, Taste 2- |
16 | Programm + |
17 | Programm - |
18 | Lautstärke + |
19 | Lautstärke - |
20 | Zeitraffer X 2 / X3 |
21 | Power |
22 | Eject |
23 | Audio Monitor (L-CH/R-CH/Stereo) |
24 | Stop |
25 | Pause |
26 | Play |
27 | Rewind |
28 | FF |
29 | Record |
32 | Freeze Frame > |
34 | X 1/10 play |
35 | X 1/5 play |
38 | High Double Speed ( X7 ~ X15X ) |
40 | Review (Rücklauf mit Bild) |
41 | Cue (Vorlauf mit Bild) |
42 | TV/VTR |
45 | VTR from TV (ANT-VTR) |
46 | Power on |
47 | Power off |
48 | Einzelbild rückwärts/langsame Rückwärtswiedergabe |
49 | Einzelbild vorwärts/langsame Vorwärtswiedergabe |
51 | X 1 |
58 | Zurückspulen, dann Play |
60 | aux |
61 | Zeitlupe + |
62 | Zeitlupe - |
64 | Function Memory |
66 | Cursor auf |
67 | Cursor ab |
70 | Zähler Reset |
71 | Counter Memory on/off |
72 | Index write |
73 | Index erase |
74 | Shuttle > |
75 | Shuttle < |
77 | Menu |
78 | TV/VTR (ANT-TV) |
79 | Input Select |
81 | Execute |
83 | index (scan) |
85 | Edit Monitor |
88 | Rec Mode (SP/LP) |
89 | Tape return |
90 | Data Screen / Display on/off |
91 | Open/Close |
92 | Timer on screen |
97 | Cursor rechts |
98 | Cursor links |
99 | Timer löschen |
100 | Timer check |
104 | Audio Insert |
105 | Video Insert |
106 | edit play (Assemble) |
107 | mark |
108 | Start |
110 | PiP off |
111 | PiP shift |
116 | TV scan |
119 | Picture in picture (PiP) on |
Tabelle 2: Befehlscodes
SONY.PRG
=
TCSTART.O
SONY.C
SEND.S
TCSTDLIB.LXB
TCGEMLIB LIB
#define DIALOG 0 /* TREE */
#define POWER 1 /* OBJECT in TREE #0 */
#define PAUSE 3 /* OBJECT in TREE #0 */
#define PLAY 4 /* OBJECT in TREE #0 */
#define REC 5 /* OBJECT in TREE #0 */
#define REW 6 /* OBJECT in TREE #0 */
#define STOP 7 /* OBJECT in TREE #0 */
#define FF 8 /* OBJECT in TREE #0 */
#define EJECT 9 /* OBJECT in TREE #0 */
#define FERTIG 10 /* OBJECT in TREE #0 */
#define PRGUP 11 /* OBJECT in TREE #0 */
#define PRGDOWN 12 /* OBJECT in TREE #0 */
OBJECT rs_object[] = {
-1, 1, 13, G_BOX, NONE, OUTLINED,
0x2H00L, 0,0, 34,12,
2, -1, -1, G_BUTTON, 0x5, NORMAL,
(long)"POWER", 2,1, 8,1,
3, -1, -1, G_STRING, NONE, NORMAL,
(long)"SIRCS-Fernbedienung", 13,1, 19,1,
4, -1, -1, G_BUTTON, 0x5, NORMAL,
(long)"PAUSE", 3,4, 8,1,
5, -1, -1, G_BUTTON, 0x5, NORMAL,
(long)"PLAY", 13,4, 8,1,
6, -1, -1, G_BUTTON, 0x5, NORMAL,
(long)"REC", 23,4, 8,1,
7, -1, -1, G_BUTTON, TOUCHEXIT, NORMAL,
(long)"REW", 3,6, 8,1,
8, -1, -1, G_BUTTON, 0x5, NORMAL,
(long)"STOP", 13,6, 8,1,
9, -1, -1, G_BUTTON, TOUCHEXIT, NORMAL,
(long)"FF", 23,6, 8,1,
10, -1, -1, G_BUTTON, 0x5, NORMAL,
(long)"EJECT", 3,8, 8,1,
11, -1, -1, G_BUTTON, 0x7, NORMAL,
(long)"FERTIG", 13,10, 8,1,
12, -1, -1, G_BUTTON, TOUCHEXIT, NORMAL,
(long)"+", 29,8. 2,1,
13, -1, -1, G_BUTTON, TOUCHEXXT, NORMAL,
(long)"-", 29,10, 2,1,
0, -1, -1, G_STRING, LASTOB, NORMAL,
(long)"Programm ", 19,8, 9,1);
LONG rs_trindex[] = {
0L};
struct foobar {
WORD dummy;
WORD *image,
} rs_imdope[] = {
0};
#define NUM_OBS 14
#define NUM_TREE 1
* Senden eines Befehls an ein Gerät mit
* Sony SIRCS (Control-S) Anschluß
* von Oliver Scholz
* (c) MAXON Computer 1991
MFP equ $FFFFFA01
INT_ENA_A equ MFP+$6
INT_PEND_A equ MFP+$A
INT_MASK_A equ MFP+$12
TA_CTRL equ MFP+$18
TA_DATA equ MFP+$1E
SOUND equ $FFFF8800
* C-Deklaration:
* VOID send_sony(WORD device, WORD command,
* WORD times)
*
.globl send_sony
send_sony:
move d0,device
move d1,command
move d2,times
movem.l d2-d7/a2-a6,-(sp)
send_loop:
tst times ;entsprechend oft senden
beq done
pea do_send
move #38,-(sp) ;im Supervisormodus
trap #14 ;ausführen
addq.l #6,sp
sub #1,times
bra send_loop
done:
movem.l (sp)+,d2-d7/a2-a6
rts
.super
do_send:
move sr,-(sp)
ori #$700,sr ; Interrupts sperren
clr.b TA_CTRL ; Timer anhalten
bclr.b #5,INT_ENA_A ; Interrupt disablen
bclr.b #5,INT_MASK_A ; Interrupt maskieren
bset.b #5,INT_ENA_A ; Interrupt enablen
move (sp)+,sr ; Interrupts frei!
* Sony SIRCS Wort senden
clr counter ; Zeitzähler löschen
bsr set_one ; Ausgang auf High
bsr wait0_6 ; 2.4 ms warten
bsr wait0_6
bsr wait0_6
bsr wait0_6
bsr set_zero ; und auf Low:
bsr wait0_6 ; das war der Startschuß
move command,d0 ; Kommando senden
move #6,d1 ; 7 Bits
bsr cmd_loop
move device,d0 ; Zielgeratecode
move #4,d1 ; senden (5 Bits)
bsr cmd_loop
fill:
bsr wait0_6
cmp #75,counter ; auffüllen, bis 45ms
blt fill
rts
* Bits an Gerät senden
* Anzahl der Bits minus 1 in D1.w
* Die Bits selbst in D0.w
* Leitung für jedes 1-Bit 1.2ms auf High,
* für Null-Bits nur 0.6ms auf High
* danach 0.6ms auf Low
cmd_loop:
bsr set_one ; Leitung auf high
bsr wait0_6
btst #0,d0 ; unterstes Bit
beq cmd_zero ; Bit ist Null
bsr wait0_6
cmd_zero:
bsr set_zero
bsr wait0_6
lsr #1,d0
dbf d1,cmd_loop
rts
* 0.6 ms warten
wait0_6:
move.b #$DF,INT_PEND_A ; Flag löschen
move.b #92,TA_DATA ; Timer setzen
move.b #3,TA_CTRL ; und einschalten
wait:
btst.b #5,INT_PEND_A ; Warten, bis Flag
beq wait ; gesetzt
clr.b TA_CTRL ; Timer wieder stop
add #1,counter ; bisher gewartete
rts ; Intervalle: erhöhen
* Leitung auf eins setzen
set_one:
move.b #$F,SOUND ; Port B schreiben
move.b #1,SOUND+2
rts
set_zero:
move.b #$F,SOUND
move.b #0,SOUND+2
rts
.bss
counter: ds.w 1
device: ds.w 1
command: ds.w 1
times: ds.w 1
/*
* Control-S-Steuerprogramm für Videorekorder
* von Oliver Scholz
* (c) 1991 MAXON Computer 1991
*/
#include "portab.h"
#include "aes.h"
#lnclude "stdlib.h"
#include "sony.h"
#include "sony.rsh"
#define TV 1
#define VTR1 2
#define VTR2 7
#define VTR3 11
/* VTR1 für Betamax */
/* VTR2 für Video 8 */
/* VTR3 für VHS */
#define VTR VTR3
#define TRUE 1
#define FALSE 0
VOID send_sony(WORD device, WORD command, WORD times);
OBJECT *get_traddr(WORD tree_index),
VOID do_dialog(OBJECT *dial);
VOID redo_obj(OBJECT *tree, WORD index);
extern _app;
VOID main(VOID)
{
OBJECT *dialog;
WORD i,dummy;
WORD event;
WORD msgbuff[8];
WORD ap_id, mn_id;
/* Programm initialisieren */
ap_id = appl_init();
if(ap_id == -1) exit(-1);
/* Dialogadresse berechnen */
for (i=0; 1<NUM_OBS; i++)
rsrc_obfix(rs_object,i);
dialog=get_traddr(DIALOG);
if(_app)
{
/* Applikation */
graf_mouse(ARROW,0L);
do_dialog(dialog);
}
else
{ /* Accessory */
mn_id = menu_register( ap_id," Fernbedienung" );
/* keine Touchexit Knöpfe.. */
dialog[REW].ob_flags=SELECTABLE|EXIT;
dialog[FF].ob_flags=SELECTABLE|EXIT;
dialog[PRGUP].ob_flags=SELECTABLE|EXIT;
dialog[PRGDOWN].ob_flags=SELECTABLE|EXIT;
do
{
event = evnt_multi(MU_MESAG,
0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
msgbuff,
0, 0,
&dummy, &dummy,
&dummy,
&dummy, &dummy,
&dummy );
if(event & MU_MESAG)
if((msgbuff[0] == AC_DPEN) && (msgbuff[4] == mn_id))
do_dialog(dialog);
}
while (TRUE);
}
appl_exit();
exit (0);
}
/* Adresse eines Baumes ermitteln */
OBJECT *get_traddr (WORD tree_index)
{
WORD i,j;
for (i=0,j=0; i<=tree_index; i++)
while (rs_object[j++] ob_next!=-1);
return(&rs_object[—-j]);
}
VOID do_dialog(OBJECT *dial)
{
WORD cx,cy,cw,ch;
WORD xflag, exitobj;
form_center(dial,&cx,&cy,&cw,&ch);
form_dial(FMD_START,cx,cy,cw,ch,cx,cy,cw,ch);
xflag = FALSE;
objc_draw(dial,ROOT,MAX_DEPTH,cx,cy,cw,ch);
do
{
exitobj = form_do(dial, 0) & 0x7fff;
dial[exitobj],ob_state &= ~SELECTED;
switch(exitobj)
{
case FERTIG:
xflag = TRUE;
break;
case POWER:
send_sony(VTR,21,3);
break;
case PAUSE:
send_sony(VTR,25,3);
break;
case PLAY:
send_sony(VTR,26,3);
break;
case REC:
send_sony(VTR, 29, 3);
break;
case REW:
send_sony(VTR,27,3);
break;
case STOP:
send_sony(VTR,24,3);
break;
case EJECT:
send_sony(VTR,22,3);
break;
case FF:
send_sony(VTR,28,3);
break;
case PRGUP:
send_sony(VTR,16,3);
break;
case PRGDOWN:
send_sony(VTR,17,3);
break;
}
redo_obj(dial,exitobj);
} while(!xflag);
form_dial (FMD_FINISH,cx,cy,cw,ch,cx,cy, cw,ch);
}
VOID redo_obj(OBJECT *tree, WORD index)
{ WORD x, y, w, h;
objc_offset(tree,index,&x,&y);
w = tree[index].ob_width,
h = tree[index].ob_height;
objc_draw (tree, ROOT,MAX_DEPTH,x,y,w,h);
}