RS232 control for TVOne products

Dependents:   SPK-DVIMXR

spk_tvone_mbed.cpp

Committer:
tobyspark
Date:
2012-10-07
Revision:
6:767acf32fed5
Parent:
5:4b0bf9a724a4

File content as of revision 6:767acf32fed5:

// *spark audio-visual
// RS232 Control for TV-One products
// Good for 1T-C2-750, others will need some extra work

/* Copyright (c) 2011 Toby Harris, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "spk_tvone_mbed.h"
#include "mbed.h"

SPKTVOne::SPKTVOne(PinName txPin, PinName rxPin, PinName signWritePin, PinName signErrorPin, Serial *debugSerial)
{
    // Create Serial connection for TVOne unit comms
    // Creating our own as this is exclusively for TVOne comms
    serial = new Serial(txPin, rxPin);
    serial->baud(57600);
    
    if (signWritePin != NC) writeDO = new DigitalOut(signWritePin);
    else writeDO = NULL;
    
    if (signErrorPin != NC) errorDO = new DigitalOut(signErrorPin);
    else errorDO = NULL;
    
    // Link up debug Serial object
    // Passing in shared object as debugging is shared between all DVI mixer functions
    debug = debugSerial;
}

bool SPKTVOne::command(uint8_t channel, uint8_t window, int32_t func, int32_t payload) 
{
  if (debug) debug->printf("TVOne command: IN. ");
  
  char i;
  Timer timer;
  
  // TASK: Sign start of serial command write
  if (writeDO) *writeDO = 1;

  // TASK: Clear read buffer, and make sure we're not still receiving.

  if (serial->readable())
  {
    timer.start();
    if (debug) debug->printf("Serial incoming: ");
    while (timer.read_ms() < 30)
    {
        if (serial->readable())
        {
            if (debug) debug->printf("%c", (char)serial->getc());
            else serial->getc();
            timer.reset();
        }
    }
    if (debug) debug->printf("\r\n");
    timer.stop();
    timer.reset();
  }

  // TASK: Create the bytes of command

  uint8_t cmd[8];
  uint8_t checksum = 0;

  // CMD
  cmd[0] = 1<<2; // write
  // CHA
  cmd[1] = channel;
  // WINDOW
  cmd[2] = window;
  // OUTPUT & FUNCTION
  //            cmd[3]  cmd[4]
  // output 0 = 0000xxx xxxxxxx
  // function = xxxXXXX XXXXXXX
  cmd[3] = func >> 8;
  cmd[4] = func & 0xFF;
  // PAYLOAD
  cmd[5] = (payload >> 16) & 0xFF;
  cmd[6] = (payload >> 8) & 0xFF;
  cmd[7] = payload & 0xFF;

  // TASK: Write the bytes of command to RS232 as correctly packaged 20 characters of ASCII

  for (i=0; i<8; i++) 
  {
    checksum += cmd[i];
  }
  
  // MBED library sub-par: printf will hang on rx interrupt, have to sprintf and loop putc instead.
  // FFS. Still not fixed. 
  char buffer[21];
  sprintf(buffer,"F%02X%02X%02X%02X%02X%02X%02X%02X%02X\r", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], checksum);
  for (i = 0; i < 20; i++)
  {
    __disable_irq();
    serial->putc(buffer[i]);
    __enable_irq();
  }
    
  // TASK: Check the unit's return string, to enable return to main program as soon as unit is ready

  // Handling the timing of this return is critical to effective control.
  // Returning the instant something is received back overloads the processor, as does anything until the full 20 char acknowledgement.
  // TVOne turn out to say that receipt of the ack doesn't guarantee the unit is ready for the next command. 
  // According to the manual, operations typically take 30ms, and to simplify programming you can throttle commands to every 100ms.
  // 100ms is too slow for us. Going with returning after 30ms if we've received an acknowledgement, returning after 100ms otherwise.

  int ack[20];
  int safePeriod = 100;
  int clearPeriod = 30;
  bool ackReceived = false;
  bool success = false;

  timer.reset();
  timer.start();
  i = 0;
  while (timer.read_ms() < safePeriod) {
    if (serial->readable())
        {
            ack[i] = serial->getc();
            i++;
            if (i >= 20) 
            {
                ackReceived = true;
                if (ack[0] == 'F' && ack[1] == '4') // TVOne start of message, acknowledgement with no error, rest will be repeat of sent command
                {
                    success = true;
                }
            }
        }
    if (ackReceived && (timer.read_ms() > clearPeriod)) break;
  }
  timer.stop();
  
  // TASK: Sign end of write

  if (writeDO) *writeDO = 0;
  
  if (!success) {
        if (errorDO) {
            signErrorTimeout.detach();
            signErrorTimeout.attach(this, &SPKTVOne::signErrorOff, 0.25);
            *errorDO = 1;
        }
        
        if (debug) {
            debug->printf("Serial command write error. Time from write finish: %ims \r\n", timer.read_ms());
        }
  };

  if (debug) debug->printf("OUT. Success = %s \r\n", success ? "true" : "false"); 
  
  return success;
}

void SPKTVOne::setCustomResolutions() 
{
  int32_t unlock = 0;
  int32_t lock = 1;
  
  // Lock front panel
  command(0, 0, kTV1FunctionAdjustFrontPanelLock, lock);
  
  // Set resolutions
  set1920x480(kTV1ResolutionTripleHeadVGAp60);
  set1600x600(kTV1ResolutionDualHeadSVGAp60);
  set2048x768(kTV1ResolutionDualHeadXGAp60);
  
  // Unlock front panel
  command(0, 0, kTV1FunctionAdjustFrontPanelLock, unlock);
}

bool SPKTVOne::setHDCPOn(bool state) 
{
  bool ok = false;

  // Turn HDCP off on the output
  ok =       command(0, kTV1WindowIDA, kTV1FunctionAdjustOutputsHDCPRequired, state);
  ok = ok && command(0, kTV1WindowIDA, kTV1FunctionAdjustOutputsHDCPStatus, state);
  // Likewise on inputs A and B
  ok = ok && command(0, kTV1WindowIDA, kTV1FunctionAdjustSourceHDCPAdvertize, state);
  ok = ok && command(0, kTV1WindowIDB, kTV1FunctionAdjustSourceHDCPAdvertize, state);
  ok = ok && command(0, kTV1WindowIDA, kTV1FunctionAdjustSourceHDCPStatus, state);
  ok = ok && command(0, kTV1WindowIDB, kTV1FunctionAdjustSourceHDCPStatus, state);
  
  return ok;
}

void SPKTVOne::set1920x480(int resStoreNumber) 
{
  command(0, 0, kTV1FunctionAdjustResolutionImageToAdjust, resStoreNumber);
  command(0, 0, kTV1FunctionAdjustResolutionInterlaced, 0);
  command(0, 0, kTV1FunctionAdjustResolutionFreqCoarseH, 31400);
  command(0, 0, kTV1FunctionAdjustResolutionFreqFineH, 31475);
  command(0, 0, kTV1FunctionAdjustResolutionActiveH, 1920);
  command(0, 0, kTV1FunctionAdjustResolutionActiveV, 480);
  command(0, 0, kTV1FunctionAdjustResolutionStartH, 192); 
  command(0, 0, kTV1FunctionAdjustResolutionStartV, 32); 
  command(0, 0, kTV1FunctionAdjustResolutionCLKS, 2400); 
  command(0, 0, kTV1FunctionAdjustResolutionLines, 525);
  command(0, 0, kTV1FunctionAdjustResolutionSyncH, 240);
  command(0, 0, kTV1FunctionAdjustResolutionSyncV, 5); 
  command(0, 0, kTV1FunctionAdjustResolutionSyncPolarity, 0);
}

void SPKTVOne::set1600x600(int resStoreNumber) 
{
  command(0, 0, kTV1FunctionAdjustResolutionImageToAdjust, resStoreNumber);
  command(0, 0, kTV1FunctionAdjustResolutionInterlaced, 0);
  command(0, 0, kTV1FunctionAdjustResolutionFreqCoarseH, 37879);
  command(0, 0, kTV1FunctionAdjustResolutionFreqFineH, 37879);
  command(0, 0, kTV1FunctionAdjustResolutionActiveH, 1600);
  command(0, 0, kTV1FunctionAdjustResolutionActiveV, 600);
  command(0, 0, kTV1FunctionAdjustResolutionStartH, 160); 
  command(0, 0, kTV1FunctionAdjustResolutionStartV, 1); 
  command(0, 0, kTV1FunctionAdjustResolutionCLKS, 2112); 
  command(0, 0, kTV1FunctionAdjustResolutionLines, 628);
  command(0, 0, kTV1FunctionAdjustResolutionSyncH, 192);
  command(0, 0, kTV1FunctionAdjustResolutionSyncV, 14); 
  command(0, 0, kTV1FunctionAdjustResolutionSyncPolarity, 0);
}

void SPKTVOne::set2048x768(int resStoreNumber) 
{
  command(0, 0, kTV1FunctionAdjustResolutionImageToAdjust, resStoreNumber);
  command(0, 0, kTV1FunctionAdjustResolutionInterlaced, 0);
  command(0, 0, kTV1FunctionAdjustResolutionFreqCoarseH, 48363);
  command(0, 0, kTV1FunctionAdjustResolutionFreqFineH, 48363);
  command(0, 0, kTV1FunctionAdjustResolutionActiveH, 2048);
  command(0, 0, kTV1FunctionAdjustResolutionActiveV, 768);
  command(0, 0, kTV1FunctionAdjustResolutionStartH, 368); 
  command(0, 0, kTV1FunctionAdjustResolutionStartV, 24); 
  command(0, 0, kTV1FunctionAdjustResolutionCLKS, 2688); 
  command(0, 0, kTV1FunctionAdjustResolutionLines, 806);
  command(0, 0, kTV1FunctionAdjustResolutionSyncH, 224);
  command(0, 0, kTV1FunctionAdjustResolutionSyncV, 11); 
  command(0, 0, kTV1FunctionAdjustResolutionSyncPolarity, 0);
}

void SPKTVOne::signErrorOff() {
    *errorDO = 0;
}