//
//
// Name: ATVTxController.c
//
//
// Revision History:
//
// M. Gray 1 Mar 2004 V1.00 Initial release.
//
//
// COPYRIGHT (c) 2004 Michael Gray, KD7LMO
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Hardware specific configuration.
#include <18f252.h>
#fuses HS,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOLVP
// These compiler directives set the clock and baud rate information.
#use delay(clock=4897000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
#use i2c (master, scl=PIN_C5, sda=PIN_C4)
#use fast_io(B)
#use fast_io(C)
// We define types that are used for all variables. These are
// declared because compilers have different sizes for int and long.
/// 8-bit signed number.
typedef int int8_t;
/// 8-bit unsigned number.
typedef unsigned int uint8_t;
/// 16-bit signed number.
typedef signed long int16_t;
/// 16-bit unsigned number.
typedef unsigned long uint16_t;
// Public methods for each class.
/// Power control pin for the S-Band power amplifier
#define IO_PA PIN_C1
/// Status LED.
#define IO_LED PIN_C2
/// Wireless RF receiver input.
#define IO_RX PIN_C3
uint8_t camASCIIToText(uint8_t text);
void camInit();
void camSendCommand (uint8_t *command, uint8_t length);
void camSetTime (uint8_t hours, uint8_t minutes);
void camSetTitle (uint8_t x, uint8_t y, uint8_t color, char *title);
void camSetZoom (uint16_t zoom);
boolean camWaitAckCompletion();
uint16_t sysCRC16 (uint8_t *buffer, uint16_t length);
void sysProcessCommand(uint8_t *command);
void sysRun();
void timerInterrupt();
uint8_t timerGetChar();
uint8_t timerGetTick();
void timerSetLEDTimeout();
boolean timerSyncChar(uint8_t sync);
boolean tunerIsLocked();
void tunerSetFreq(uint16_t freq);
void tunerSetState (boolean state);
/*
* Class to handle the Sony FCB-IX47 camera.
*/
static char CAM_DEFAULT_TITLE[] = "FM-ATV KD7LMO V1.0";
static uint8_t CAM_ADDRESS_SET[] = { 0x88, 0x30, 0x01, 0xFF };
static uint8_t CAM_IF_CLEAR[] = { 0x88, 0x01, 0x00, 0x01, 0xFF };
static uint8_t CAM_TITLE_CLEAR[] = { 0x81, 0x01, 0x04, 0x74, 0x00, 0xFF };
static uint8_t CAM_TITLE_ON[] = { 0x81, 0x01, 0x04, 0x74, 0x02, 0xFF };
static uint8_t CAM_TIME_ON[] = { 0x81, 0x01, 0x04, 0x72, 0x02, 0xFF };
static uint8_t CAM_MANUAL_FOCUS[] = { 0x81, 0x01, 0x04, 0x38, 0x03, 0xFF };
static uint8_t CAM_AF_TRIGGER[] = { 0x81, 0x01, 0x04, 0x18, 0x01, 0xFF };
static uint8_t CAM_WB_OUTDOOR[] = { 0x81, 0x01, 0x04, 0x35, 0x02, 0xFF };
static uint8_t ACK_SEQUENCE[] = { 0x90, 0x40, 0xff, 0x90, 0x50, 0xff };
/// Values used to set the color of the on screen text display.
enum CAM_COLOR {
CAM_WHITE = 0x00,
CAM_YELLOW,
CAM_VIOLET,
CAM_RED,
CAM_CYAN,
CAM_GREEN,
CAM_BLUE
};
/// Camera zoom set to widest angle of view.
#define CAM_ZOOM_WIDE 0x0000
/// Cameara zoom set to nearest angle of view (without using digital zoom).
#define CAM_ZOOM_TELE 0x4000
/**
* Convert ASCII character to the Sony Camera character mapping. For example, A = 0x00, B=0x01, etc.
*
* @param text ASCII to convert
*
* @return Sony Camera character
*/
uint8_t camASCIIToText(uint8_t text)
{
// Convert the block of letters A to Z.
if (text >= 'A' && text <= 'Z')
return text - 'A';
// Convert the block of letters a to z. (The camera doesn't support lower case, so we just make them all upper case.
if (text >= 'a' && text <= 'z')
return text - 'a';
// Convert the block of numbers 1 to 9.
if (text >= '1' && text <= '9')
return (0x1e + text - '1');
// The rest we do character by character.
switch (text) {
case ' ':
return 0x1b;
case '0':
return 0x27;
case '$':
return 0x41;
case '\"':
return 0x49;
case ':':
return 0x4a;
case '\'':
return 0x4b;
case '.':
return 0x4c;
case ',':
return 0x4d;
case '/':
return 0x4e;
case '-':
return 0x4f;
default:
return 0x46;
} // END switch
}
/**
* Setup the camera interface and default operation conditions.
*/
void camInit()
{
// Send the required address and interface clear command first.
camSendCommand (CAM_ADDRESS_SET, sizeof(CAM_ADDRESS_SET));
camSendCommand (CAM_IF_CLEAR, sizeof(CAM_IF_CLEAR));
// Set manual focus mode.
camSendCommand (CAM_MANUAL_FOCUS, sizeof(CAM_MANUAL_FOCUS));
// Set the white balance to a steady state.
camSendCommand (CAM_WB_OUTDOOR, sizeof(CAM_WB_OUTDOOR));
// Zoom to wide angle.
camSetZoom (CAM_ZOOM_WIDE);
// Clear any previous title and then turn on the title function.
camSendCommand (CAM_TITLE_CLEAR, sizeof(CAM_TITLE_CLEAR));
camSendCommand (CAM_TITLE_ON, sizeof(CAM_TITLE_ON));
// Set the time to 9:00 AM, our nominal flight time
camSetTime (9, 0);
camSendCommand (CAM_TIME_ON, sizeof(CAM_TIME_ON));
// Set a default title with software version information.
camSetTitle (3, 1, CAM_BLUE, CAM_DEFAULT_TITLE);
}
/**
* Send the binary block <b>command</b> of <b>length</b> bytes to the camera via the
* RS-232 control port
*
* @param command pointer to buffer of binary data to send
* @param length of buffer in bytes
*/
void camSendCommand (uint8_t *command, uint8_t length)
{
uint8_t i;
for (i = 0; i < length; ++i)
putc (command[i]);
delay_ms(100);
}
/**
* Display the text <b>title</b> at coordinates (<b>x</b>,<b>y</b>) in the
* color <b>color</b> where the coordinates (0,0) are in the upper, left-hand
* portion of the screen.
*
* @param x coordinate
* @param y coordinate
* @param color enumeration
* @param title NULL terminate string
*/
void camSetTitle (uint8_t x, uint8_t y, uint8_t color, char *title)
{
uint8_t i, command[16];
// Range check the data based on the camera limitations.
if (x > 0x17 || y > 0x0a || color > 6)
return;
// Generate and send the 'Title Set 1' command packet.
command[0] = 0x81;
command[1] = 0x01;
command[2] = 0x04;
command[3] = 0x73;
command[4] = 0x00;
command[5] = y;
command[6] = x;
command[7] = color;
memset (command + 8, 0, 7);
command[15] = 0xff;
camSendCommand(command, 16);
// Generate and send the 'Title Set 2' command packet.
command[4] = 0x01;
memset (command + 5, camASCIIToText(' '), 10);
for (i = 5; i < 15 && *title != 0; ++i, ++title)
command[i] = camASCIIToText(*title);
camSendCommand(command, 16);
// Generate and send the 'Title Set 3' command packet.
command[4] = 0x02;
memset (command + 5, camASCIIToText(' '), 10);
for (i = 5; i < 15 && *title != 0; ++i, ++title)
command[i] = camASCIIToText(*title);
camSendCommand(command, 16);
}
/**
* Set the on screen time display.
*
* @param hours time in hours
* @param minutes time in minutes
*/
void camSetTime (uint8_t hours, uint8_t minutes)
{
uint8_t command[15];
// Ignore invalid requests.
if (hours > 24 || minutes > 60)
return;
command[0] = 0x81; // Camera address
command[1] = 0x01; // Set time command sequence.
command[2] = 0x04;
command[3] = 0x70;
command[4] = 0x00; // Year
command[5] = 0x04;
command[6] = 0x00; // Month
command[7] = 0x01;
command[8] = 0x00; // Day
command[9] = 0x01;
command[10] = hours / 10; // Hour
command[11] = hours % 10;
command[12] = minutes / 10; // Minute
command[13] = minutes % 10;
command[14] = 0xff;
camSendCommand (command, 15);
}
/**
* Set the camera zoom in the range of 0x0000 (wide) to 0x4000 (telephoto).
*
* @param zoom linear zoom factor
*/
void camSetZoom (uint16_t zoom)
{
uint8_t command[9];
command[0] = 0x81; // Camera address
command[1] = 0x01; // Set time command sequence.
command[2] = 0x04;
command[3] = 0x47;
command[4] = (zoom >> 12) & 0x0f;
command[5] = (zoom >> 8) & 0x0f;
command[6] = (zoom >> 4) & 0x0f;
command[7] = zoom & 0x0f;
command[8] = 0xff;
camSendCommand (command, 9);
}
/**
* Wait for the ACK and completion of the command.
*
* @return true if ACK and completion sequence received; otherwise false
*/
boolean camWaitAckCompletion()
{
uint8_t value, ackIndex, startTime;
startTime = timerGetTick();
ackIndex = 0;
for (;;) {
if (kbhit()) {
value = getch();
if (value == ACK_SEQUENCE[ackIndex]) {
++ackIndex;
if (ackIndex == sizeof(ACK_SEQUENCE))
return true;
} else
ackIndex = 0;
} // END if kbhit
// If we didn't get the ACK sequence within 2000mS, we will give up.
if (startTime - timerGetTick() > 20)
return false;
} // END while
}
/*
* Class to handle the system functions.
*/
/// State machine waiting for frame sync byte 1.
#define MODE_FRAMESYNC_1 1
/// State machine waiting for frame sync byte 2.
#define MODE_FRAMESYNC_2 2
/// State machine waiting for frame sync byte 3.
#define MODE_FRAMESYNC_3 3
/// State machine waiting for frame sync byte 4.
#define MODE_FRAMESYNC_4 4
/// State machine waiting for command byte.
#define MODE_COMMAND 5
/// State machine waiting for length of incoming command.
#define MODE_LENGTH 6
/// State machine waiting for 0 .. n data bytes.
#define MODE_DATA 7
/// State machine waiting for CRC byte 1.
#define MODE_CRC_1 8
/// State machine waiting for CRC byte 2.
#define MODE_CRC_2 9
/// Remote command to reboot processor.
#define COMMAND_REBOOT 0x30
/// Remote command to display text at coordinates (x, y) in color c.
#define COMMAND_TITLE 0x31
/// Remote command to execute auto focus camera sequence.
#define COMMAND_FOCUS 0x32
/// Remote command to set the frequency.
#define COMMAND_SET_FREQ 0x33
/// Remote command to set the PA state.
#define COMMAND_SET_PA 0x34
/// Remote command to set the time.
#define COMMAND_SET_TIME 0x35
/// Index into buffer that specifies location of command byte.
#define INDEX_COMMAND 0
/// Index into buffer that specifies location of command length.
#define INDEX_LENGTH 1
/// Index into buffer that specifies start of data bytes.
#define INDEX_DATA_START 2
/// The maximum number of data bytes accepted in a command.
#define MAX_DATA 32
/**
* Calculate the CRC-16 CCITT of <b>buffer</b> that is <b>length</b> bytes long.
*
* @param buffer Pointer to data buffer.
* @param length number of bytes in data buffer
*
* @return CRC-16 of buffer[0 .. length]
*/
uint16_t sysCRC16 (uint8_t *buffer, uint16_t length)
{
uint8_t i, bit, value;
uint16_t crc;
crc = 0xffff;
for (i = 0; i < length; ++i) {
value = buffer[i];
for (bit = 0; bit < 8; ++bit) {
crc ^= (value & 0x01);
crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 );
value = value >> 1;
} // END for
} // END for
return crc ^ 0xffff;
}
/**
* Process the wireless command in the binary buffer <b>commandBuffer</b>. The buffer
* contains the following entries:<br><br>
*
* INDEX_COMMAND - Binary command<br>
* INDEX_LENGTH - Length of the command in bytes<br>
* INDEX_DATA_START - Command specific data<br>
*
* @param pointer to command buffer
*/
void sysProcessCommand(uint8_t *command)
{
uint16_t freq;
switch (command[INDEX_COMMAND]) {
case COMMAND_TITLE:
// NULL terminate the string.
command[command[INDEX_LENGTH] + 2] = 0;
camSetTitle (0, 0, CAM_WHITE, command + INDEX_DATA_START);
break;
case COMMAND_FOCUS:
camSendCommand (CAM_AF_TRIGGER, sizeof(CAM_AF_TRIGGER));
break;
case COMMAND_SET_FREQ:
freq = (uint16_t) (command[INDEX_DATA_START] << 8);
freq |= (uint16_t) command[INDEX_DATA_START + 1];
tunerSetFreq (freq);
break;
case COMMAND_SET_PA:
if (command[INDEX_DATA_START] == 0x00)
tunerSetState (false);
if (command[INDEX_DATA_START] == 0xff)
tunerSetState (true);
break;
case COMMAND_SET_TIME:
camSetTime (command[INDEX_DATA_START], command[INDEX_DATA_START + 1]);
break;
} // END switch
}
/**
* Main run task.
*/
void sysRun()
{
uint8_t mode, accum, commandIndex, commandBuffer[MAX_DATA + 4];
uint16_t crc;
boolean runFlag;
// Setup the state machine.
runFlag = true;
mode = MODE_FRAMESYNC_1;
accum = 0;
while (runFlag) {
switch (mode) {
case MODE_FRAMESYNC_1:
if (timerSyncChar(0x18))
mode = MODE_FRAMESYNC_2;
break;
case MODE_FRAMESYNC_2:
if (timerGetChar() != 0x47)
mode = MODE_FRAMESYNC_1;
else
mode = MODE_FRAMESYNC_3;
break;
case MODE_FRAMESYNC_3:
if (timerGetChar() != 0x70)
mode = MODE_FRAMESYNC_1;
else
mode = MODE_FRAMESYNC_4;
break;
case MODE_FRAMESYNC_4:
if (timerGetChar() != 0x25)
mode = MODE_FRAMESYNC_1;
else
mode = MODE_COMMAND;
break;
case MODE_COMMAND:
commandBuffer[INDEX_COMMAND] = timerGetChar();
mode = MODE_LENGTH;
break;
case MODE_LENGTH:
commandBuffer[INDEX_LENGTH] = timerGetChar();
commandIndex = INDEX_DATA_START;
if (commandBuffer[INDEX_LENGTH] > MAX_DATA)
mode = MODE_FRAMESYNC_1;
else if (commandBuffer[INDEX_LENGTH] == 0)
mode = MODE_CRC_1;
else
mode = MODE_DATA;
break;
case MODE_DATA:
commandBuffer[commandIndex] = timerGetChar();
if (++commandIndex == commandBuffer[INDEX_LENGTH] + 2)
mode = MODE_CRC_1;
break;
case MODE_CRC_1:
commandBuffer[commandIndex++] = timerGetChar();
mode = MODE_CRC_2;
break;
case MODE_CRC_2:
// Save the LSB of the CRC.
commandBuffer[commandIndex] = timerGetChar();
// Calcualte the CRC of the buffer.
crc = sysCRC16 (commandBuffer, commandIndex - 1);
// If the CRC is valid process the command.
if ((crc >> 8) == commandBuffer[commandIndex - 1] && (crc & 0xff) == commandBuffer[commandIndex]) {
timerSetLEDTimeout();
output_high (IO_LED);
if (commandBuffer[INDEX_COMMAND] == COMMAND_REBOOT)
runFlag = false;
else
sysProcessCommand(commandBuffer);
}
// No matter what, start the search over.
mode = MODE_FRAMESYNC_1;
break;
} // END switch
} // END while
}
/*
* Class to handle the timer functions and process the incoming wireless bit stream.
*/
/// Counts the length of time the input bit remains in the same state.
uint8_t subBitCount;
/// Number of bits we have accumulated before we manchester decode the input stream.
uint8_t decodeCount;
/// The last two bits we have received. We look at these bits to decode the manchester bit stream.
uint8_t decodeBits;
/// The last byte we decoded from the input stream. We must process this is data in 16-bit times.
uint8_t outputByte;
/// The number of bits we have accumulated in rxByte. Once we have 8, we have a complete byte.
uint8_t bitCount;
/// Temporary register to hold each data bit as we receive it.
uint8_t rxByte;
/// Accumulator that shifts each received bit through it until we sync with the unique word.
uint8_t accum;
/// The number of bits lefts in outputByte.
uint8_t shiftCount;
/// Flag that is set when outputByte contains the latest 8-bits of data.
boolean dataReady;
/// Set to the last input state from the receiver.
boolean lastState;
/// Rolling 8-bit value that updates every 100mS.
uint8_t timeTick;
/// Counter that turns off LED after we receive and cross payload packet.
uint16_t ledTimeOut;
/**
* Process the timer 0 interrupt that occurs every 209uS.
*/
#INT_RTCC
void timerInterrupt()
{
// Check the state of the input pin.
if (input(IO_RX)) {
if (!lastState) {
subBitCount = 2;
lastState = true;
} else
if (subBitCount != 0)
if (--subBitCount == 0) {
subBitCount = 4;
// We have detected a 1 bit, so save it.
decodeBits = (decodeBits << 1) | 0x01;
++decodeCount;
} // END if
} else {
if (lastState) {
subBitCount = 2;
lastState = false;
} else
if (subBitCount != 0)
if (--subBitCount == 0) {
subBitCount = 4;
// We have detected a bit, so save it.
decodeBits = (decodeBits << 1);
++decodeCount;
} // END if
} // END if-else
// Manchester decode the bit stream.
if (decodeCount == 2) {
switch (decodeBits) {
// Resync the bit stream.
case 0x00:
decodeCount = 1;
break;
// A 0 to 1 transition indicates we have a 0 data bit.
case 0x01:
// Reset the counter that tracks the number of bits we look at.
decodeCount = 0;
// Clear the bit accumulator.
decodeBits = 0x00;
// Save a 0 in the output regsiter.
rxByte = rxByte << 1;
// Once we have a full byte, save it and start over.
if (++bitCount == 8) {
outputByte = rxByte;
rxByte = 0x00;
bitCount = 0;
dataReady = true;
} // END if
break;
// A 1 to 0 transition indicates we have a 1 data bit.
case 0x02:
// Reset the counter that tracks the number of bits we look at.
decodeCount = 0;
// Clear the bit accumulator.
decodeBits = 0x00;
// Save a 1 in the output register
rxByte = (rxByte << 1) | 0x01;
// Once we have a full byte, save it and start over.
if (++bitCount == 8) {
outputByte = rxByte;
rxByte = 0x00;
bitCount = 0;
dataReady = true;
} // END if
break;
// Resync the bit stream.
case 0x03:
decodeCount = 1;
decodeBits = 0x01;
break;
} // END switch
} // END if
// Turn off the LED after 250mS.
if (ledTimeOut != 0)
if (--ledTimeOut == 0)
output_low (IO_LED);
}
/**
* Wait for and return an incoming character from the wireless link.
*
* @return last character received
*/
uint8_t timerGetChar()
{
uint8_t i, value;
// When the flag is true, we have read 8-bits from the wireless interface.
while (!dataReady);
dataReady = false;
value = 0;
for (i = 0; i < 8; ++i) {
accum = (accum << 1) | ((outputByte & 0x80) ? 0x01 : 0x00);
outputByte = outputByte << 1;
if (i == shiftCount)
value = accum;
}
return value;
}
/**
* Rolling timer tick that updates every 100mS
*
* @return timer tick
*/
uint8_t timerGetTick()
{
return timeTick;
}
/**
* Initialize the timer variables and control.
*/
void timerInit()
{
accum = 0x00;
bitCount = 0;
dataReady = false;
decodeBits = 0x00;
decodeCount = 0;
ledTimeOut = 0x0000;
outputByte = 0x00;
shiftCount = 0;
rxByte = 0x00;
timeTick = 0;
// Set the timer to fire at regular intervals.
set_timer0(0);
setup_timer_0( RTCC_INTERNAL | RTCC_DIV_1 | RTCC_8_BIT );
}
void timerSetLEDTimeout()
{
ledTimeOut = 1196;
}
/**
* Check the next 8 incoming bits on the wireless link and search for the <b>sync</b>
* byte.
*
* @param sync byte pattern to search for
*
* @return true if found in 8-bit times; otherwise false
*/
boolean timerSyncChar(uint8_t sync)
{
uint8_t i;
// When the flag is true, we have read 8-bits from the wireless interface.
while (!dataReady);
dataReady = false;
shiftCount = 8;
// Shift through 8-bits to see if we have a match.
for (i = 0; i < 8; ++i) {
accum = (accum << 1) | ((outputByte & 0x80) ? 0x01 : 0x00);
outputByte = outputByte << 1;
if (accum == sync)
shiftCount = i;
} // END for
if (shiftCount == 8)
return false;
else
return true;
}
/*
* Class to handle the 2.4GHz tuner PLL. The tuner utilizes the Mitel SA5055 2.6GHz
* synthesizer.
*/
/**
* Determine if the tuner PLL is locked
*
* @return true if locked; otherwise false
*/
boolean tunerIsLocked()
{
uint8_t returnCode;
// Get the PLL controller status byte.
i2c_start();
i2c_write(0xc3);
returnCode = i2c_read();
i2c_stop();
// Bit 6 is the PLL locked flag.
if ((returnCode & 0x40) == 0x40)
return TRUE;
else
return FALSE;
}
/**
* Set the transmit frequency.
*
* @param freq in MHz
*/
void tunerSetFreq(uint16_t freq)
{
// Only allow operation in the 13cm band.
if (freq < 2400 || freq > 2450)
return;
// Tell the I2C device to connect
i2c_start();
// Address the SA5055 PLL.
i2c_write(0xc2);
// Calcualte the divider based the 4MHz reference clock.
freq = 8 * freq;
// Write the divider value.
i2c_write(freq >> 8);
i2c_write(freq & 0xff);
i2c_write(0xce);
i2c_write(0x00);
i2c_stop();
}
/**
* Control the power amplifier.
*
* @state true to enable; otherwise false
*/
void tunerSetState (boolean state)
{
if (state)
output_high (IO_PA);
else
output_low (IO_PA);
}
int main()
{
// Set the output state before we configure the port direction.
output_b (0x00);
output_c (0x00);
// Configure the I/O port direction.
set_tris_b (0x00);
set_tris_c (0x88);
// Turn on the LED to indicate we have started to boot.
output_high (IO_LED);
// Initialize the classes.
timerInit();
// Wait for the hardware and power converters to start.
delay_ms(250);
// Set default frequency.
tunerSetFreq (2418);
// Wait for the PLL to lock.
while (!tunerIsLocked());
// Wait for the camera subsystem to boot.
delay_ms(6000);
// Turn on the PA after everything else is running.
tunerSetState (true);
// Initialize the camera interface.
camInit();
// Turn off the LED to indicate we are going to a run state.
output_low (IO_LED);
// Enable all the desired interrupts.
enable_interrupts(INT_TIMER0);
enable_interrupts(GLOBAL);
// Wait for wireless commands.
sysRun();
// When the application stops, just reboot.
reset_cpu();
return 0;
}