// // // 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; }