Who are we:

Software Systems / Sierra Madre specialized in creating custom software for use in television facilities. This includes software to control audio and video recorders; switchers and mixers; time code processing; RT-11 disk support; and control software for satellite systems.

 

Samples:

The programs available on our web site are samples of the work we specialize in, and at the same time, provide useful utilities. Because of the commercial nature of our work, we don't make source listings of our programs available.

 

This paper:

Due to frequent requests for the source listings of our sample programs (no we don't provide them, see above), and requests for help in writing code to control a device using the Sony protocol, we've put together this paper which outlines the basic steps needed to control a device using the Sony RS-422 Protocol.

 

The Sony Protocol:

The Sony protocol is copyrighted, so we will only provide a few examples to show how its used. While the protocol is available on the Internet, I'd suggest, that if you're working on a serious project, that you contact the product manager of the device you're working with and ask for a copy of the protocol. You may find this especially useful as not all devices support all of the available commands.

Of all the protocols I've had experience with, I find the Sony protocol among the simplest and easiest to work with.

 

Sample code:

The snippets of code that follow are written in 'C', for 32-bit windows (Windows 95/98/NT), but these basic components will be needed regardless of the OS, language or hardware. We have implemented code to control Sony protocol devices using a wide variety of CPU's (8051, PDP-11, Z80, 68000, PowerPC and x86); OS's (none, DOS, Windows, Mac and Linux); and using a variety of languages ('C', assembler, Visual Basic).

The methods shown here are only one way of implementing the functions, they are not necessarily the best, and they lack error checking.

 

Opening and closing the Serial port:

First you need to open, and configure, a serial port for communications with the device. The Sony protocol requires no flow control (hardware or software).

To keep track of whether the port is open, I use a global variable, which I initialize to a known value. In this case, I use a value that indicates the handle is invalid. This same value will be returned if the port can't be opened.

 

// global value
HANDLE hVtrPort = INVALID_HANDLE_VALUE;

Before opening the port, be sure and check that you don't already have it open. Where this code is hard coded to open COM1, you may want to include a method of choosing any available communications port.

 

// open com1
hVtrPort = CreateFile("COM1",
		GENERIC_READ |
		GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

Once the port is open, you need to establish the communications parameters:

 

DCB lpdcb;

// get current params
GetCommState(hVtrPort, &lpdcb);

// set the sony params
lpdcb.BaudRate = (UINT) CBR_38400;
lpdcb.Parity = (BYTE) ODDPARITY;
lpdcb.StopBits = (BYTE) ONESTOPBIT;
lpdcb.ByteSize = (BYTE) 8;
lpdcb.fBinary = 1;

// and set 'em
SetCommState(hVtrPort, &lpdcb);

Finally, if you're using a port-powered RS232/422 adapter you need to be sure and set the modem bits (RTS, DTR) high:

 

// assume a port-powered 232/422 adapter
EscapeCommFunction(hVtrPort, SETDTR);
EscapeCommFunction(hVtrPort, SETRTS);

 

Closing the port:

 

// close the port
CloseHandle(hVtrPort);

// show its closed
hVtrPort = INVALID_HANDLE_VALUE;

Don't forget to close the port when you exit your program.

 

Sony Commands:

Here are a few of the basic commands. Note, these commands include a precalculated checksum (last byte of each command). I've done this for simplicity, but bare in mind, for commands like cue-up with data, you'll need to calculate the checksum including the actual data.

For more detailed information consult your copy of the Sony protocol.

 


// transport commands
// commands have precalculated checksums
BYTE  sony_stop[] =   {0x020, 0x000, 0x020};
BYTE  sony_play[] =   {0x020, 0x001, 0x021};

// data request commands
BYTE  sony_lt[] =     {0x061, 0x00c, 0x001, 0x06e};
BYTE  sony_stat[] =   {0x061, 0x020, 0x007, 0x088};
BYTE  sony_type[] =   {0x000, 0x011, 0x011};

One caution: The third byte in the status request command (0x007 in this example), is the number of bytes of status you want returned by the device. I have seen cases where this byte is ignored and the device returns a fixed number of bytes, so use a big enough buffer to avoid an overflow condition.

 

Sending the command:

You need someway of sending the command to the machine. This example passes the complete command to the device driver:

 

int nRc;
DWORD ReturnedByteCount;

// clear this and that
PurgeComm(hVtrPort, PURGE_TXABORT |
                    PURGE_RXABORT |
                    PURGE_TXCLEAR |
                    PURGE_RXCLEAR);

// send something
nRc = WriteFile(hVtrPort, (void *) sonycmnd,
		(*sonycmnd & 0x0f) + 3,
			&ReturnedByteCount, NULL);

Note in the above routine, sonycmnd is any of the Sony commands, and that the number of bytes to be transmitted is determined from the low nibble of the first byte [(*sonycmnd & 0x0f) + 3].

While this method sends the complete string to the serial driver, you could also send it a byte at a time, while at the same time calculating the checksum.

If you're using Visual Basic, some older versions of MSComm may send an extra byte if you pass it a variant. If this happens, the device will see this as the start of another command, then time out, and return a NAK.

 

Getting the return data:

Sony devices are very well behaved. They only speak after being spoken to. My normal technique is to send a command on one frame, and then check for the response on the following frame.

This routine reads all bytes currently available:

 

COMSTAT comstat;
DWORD dwErrorMask;
BYTE bytemp[64];
DWORD nToRead;
BOOL nRc;
DWORD ReturnedByteCount;
int i;

// anything to read, any errors?
ClearCommError(hVtrPort, &dwErrorMask, &comstat);
if(dwErrorMask)
	return;

// make sure we don't read more
// than we have room for!
if(comstat.cbInQue > (DWORD) sizeof(bytemp))
	nToRead = sizeof(bytemp);
else
	nToRead = comstat.cbInQue;

// any?
if(nToRead == 0)
	return;

// get the response
nRc = ReadFile(hVtrPort, (void *) bytemp,
		nToRead, &ReturnedByteCount, NULL);

// sort out the return stuff...
if(nRc == FALSE)
	return;

While this reads the entire returned string in one go, this could be done byte-by-byte if that seems better in your case.

Now that we have the return string, we need to see if its valid, then sort out what to do with it.

We start by seeing if the return data's checksum is correct. Note the following code used to validate the checksum, with a small change, can be used to generate the checksum before/while a command is sent.

 

BYTE bychksum;
char sztemp[64];

// checksum ok?
// sum all bytes except the checksum
for(i = 1, bychksum = bytemp[0];
		i<((bytemp[0] & 0x0f) + 2); i++)
    bychksum += bytemp[i];

// match?
if(bytemp[i] != bychksum)
    // checksum error... worrisome
    return;

Next we look for the ACK and NAK responses. NAK means the device didn't like what we sent, or doesn't support the command (this is not always true, some devices return ACK for unsupported command). ACK means there is no return data, and that the command was valid.

 

// NAK?
if((*bytemp == 0x011) && (*(bytemp + 1) == 0x012))
    // vtr doesn't like the command
    // some error handling might be nice
    return;

// ACK?
if((*bytemp == 0x010) && (*(bytemp + 1) == 0x001))
    // command ok, no return data
    return;

From here, anything else we get back includes some sort of data. Sort out what you need.

 

#define SONY_DF 0x040

BYTE	bymask[] = {0x03f, 0x07f, 0x07f, 0x03f};

// machine type
if((*bytemp == 0x012) && (*(bytemp + 1) == 0x011))
    {
    // process machine type
    // this displays the return as xx yy
    wsprintf(sztemp, "%2.2x %2.2x",
                bytemp[2],
                bytemp[3]);
    SetDlgItemText(hWnd, IDC_MAIN_TIME, sztemp);
    return;
    }

// status/time code?
if((*bytemp & 0x0f0) == 0x070)
    {
    // status?
    if(*(bytemp + 1) == 0x020)
      {
      // show the status
      // in the real world, you'll have to decode
      // the bits' what's of importance to you
      // displays the first 4 bytes of returned data
      wsprintf(sztemp, "%2.2x %2.2x %2.2x %2.2x ...",
                bytemp[2],
                bytemp[3],
                bytemp[4],
                bytemp[5]);
      SetDlgItemText(hWnd, IDC_MAIN_TIME, sztemp);
      return;
      }
    else
      {
      // bravo! maybe something useful
      // some kind of time code...
      // in our case, it can only be time
      // this displays the time as 00:00:00:00 N
      wsprintf(sztemp, "%2.2x:%2.2x:%2.2x:%2.2x %c",
                (bytemp[5] & bymask[3]),
                (bytemp[4] & bymask[2]),
                (bytemp[3] & bymask[1]),
                (bytemp[2] & bymask[0]),
                (char)
                  (((bytemp[2] & SONY_DF) == 0)? 'N' : 'D'));
      SetDlgItemText(hWnd, IDC_MAIN_TIME, sztemp);
      }
    }

When you request time code from the device, in addition to the time (which is BCD), there are extra bits used to indicate Drop-Frame and Field Identification. Notice in the above code that these bits are masked off before using the time [(bytemp[2] & bymask[0])], and they are also used to determine the time code type.

 

Odds and Ends:

Generally, its easiest if you use the system timer, or sync interrupt, to send the commands to the machine. I normally store the commands in a first in, first out queue. When the interrupt occurs, if there is a command waiting in the queue, I send it, if the queue is empty, I send a request for time, status or machine type.

Ordinarily, I'll ask for device type 1:32 interrupts, status 1:8 interrupts and time code at all other times. You can adjust this as required by your project.

I prefer to send just one command for each interrupt.

Generally with the IBM-PC, the fastest timer availible is approximately 18 tick's per second.

 

Problem solving:

Whenever possible, I like to test the hardware, cables, and RS232/422 adapters by using a known good program, such as our WSONY II. This insures that the system is capable of controlling the device before I write a line of my own code.

If you're using a laptop computer with a port-powered RS232/422 adapter, and find you can control the machine, but don't get any data back, check to see if the RS232/422 adapter is getting enough voltage from the port.

I like to use an oscilloscope to look at the data on the RS422 lines going to and returning from the device. Compare what you see with your code with the known good program.

 

VB 6:

As a result of the many requests, here are the VB 6 basics.

Opening and closing the port:

 

' select the port
MSComm.CommPort = iVtrPort
        
' baud rate and what have you
MSComm.Settings = "38400,O,8,1"
        
' read 'em all
MSComm.InputMode = comInputModeBinary
MSComm.InputLen = 0
        
' open the new port
MSComm.PortOpen = True
        
' set the power bits
MSComm.DTREnable = True
MSComm.RTSEnable = True

 

' close the port
MSComm.PortOpen = False

 
Setting and sending a command:

 

Dim snyCmd As Variant
Dim i as Integer

' set play string
snyCmd = Array(&H20&, &h01&, &h21&)

' send play string
For i = 0 To ((snyCmd(0) And &HF&) + 2)
    MSComm.Output = Chr$(snyCmd(i))
Next i

 
Reading data from the port:

 
Dim retData As Variant

' anything waiting?
If (MSComm.InBufferCount = 0) Then
    ' nope
    Exit Sub
End If

' yep, fetch it
retData = MSComm.Input

 

Feedback:

Did you find this paper useful? Catch us in an error? Have a suggestion for the next release? Let us know what you think by dropping us an E-Mail note to: