/********************************************************
  SSTV WIRELESS CAMERA @ToriSaanP
  PD50 - GPS version - 2021.04.21 v1.32
  ARDUINO "DUE" + ETHERNET SHIELD R3 + AD9850 DDS
  ETHERNET SHIELD ACTS JUST AS A "SD CARD SHIELD"
  --- THIS CODE IS IN THE PUBLIC DOMAIN --- 
  
  JPEG DECODE SECTION IS BASED ON MAKOTO KURAUCHI'S WORK
  https://github.com/MakotoKurauchi/JPEGDecoder
/********************************************************/

#include <arduino.h>
#include <SPI.h>
#include <SD.h>
#include <DueTimer.h>
#include "JPEGDecoder.h"
#include <Adafruit_GPS.h>

//arduino TX2 - GPS module RX
//arduino RX2 - GPS module TX

File myFile1;
#define chipSelect 4

#define  CLK   6
#define  FQ    7
#define  DATA  8
#define  RST   9

char fileName[8];
byte fileNumber;

byte sortBuf[15360]; //320(px)*16(lines)*3(bytes) R,G,B
byte yuvBuf[10240]; //320(px)*8(pairs)*4(bytes) Y0,RY,BY,Y1

volatile byte sTimes;
volatile byte sSq;
volatile int  line;
volatile int  ti;
volatile long syncTime;
volatile byte lineBuf[1280];

char info1[13] = "JI3BNB LIVE ";    // ***** INFORMATION HEADER: MAX 12 CAHARCTERS *****
char info2[13]; //GPS
char info3[13]; //GPS

boolean vox = 1;   //VOX TONE ENABLE
boolean colorBar = 1;  //COLOR BAR ENABLE
boolean gps = 1;   //GPS ENABLE
boolean test = 0;  //TEST PATTERN

#define GPSECHO true
#define GPSSerial Serial2
Adafruit_GPS GPS(&GPSSerial);
uint32_t timer = millis();

//FONTS v1.1
const uint8_t fonts[45][11] = {
  {0x00, 0x18, 0x24, 0x62, 0x62, 0x62, 0x7E, 0x62, 0x62, 0x62, 0x00}, //00: A
  {0x00, 0x7C, 0x32, 0x32, 0x32, 0x3C, 0x32, 0x32, 0x32, 0x7C, 0x00}, //01: B
  {0x00, 0x3C, 0x62, 0x62, 0x60, 0x60, 0x60, 0x62, 0x62, 0x3C, 0x00}, //02: C
  {0x00, 0x7C, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x7C, 0x00}, //03: D
  {0x00, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x7E, 0x00}, //04: E
  {0x00, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x00}, //05: F
  {0x00, 0x3C, 0x62, 0x62, 0x60, 0x60, 0x66, 0x62, 0x62, 0x3C, 0x00}, //06: G
  {0x00, 0x62, 0x62, 0x62, 0x62, 0x7E, 0x62, 0x62, 0x62, 0x62, 0x00}, //07: H
  {0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00}, //08: I
  {0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x4C, 0x4C, 0x4C, 0x38, 0x00}, //09: J
  {0x00, 0x62, 0x64, 0x68, 0x70, 0x68, 0x64, 0x62, 0x62, 0x62, 0x00}, //10: K
  {0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00}, //11: L
  {0x00, 0x42, 0x62, 0x76, 0x6A, 0x62, 0x62, 0x62, 0x62, 0x62, 0x00}, //12: M
  {0x00, 0x42, 0x62, 0x72, 0x6A, 0x66, 0x62, 0x62, 0x62, 0x62, 0x00}, //13: N
  {0x00, 0x3C, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x3C, 0x00}, //14: O
  {0x00, 0x7C, 0x62, 0x62, 0x62, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x00}, //15: P
  {0x00, 0x3C, 0x62, 0x62, 0x62, 0x62, 0x62, 0x6A, 0x6A, 0x3C, 0x08}, //16: Q
  {0x00, 0x7C, 0x62, 0x62, 0x62, 0x7C, 0x68, 0x64, 0x62, 0x62, 0x00}, //17: R
  {0x00, 0x3C, 0x62, 0x60, 0x60, 0x3C, 0x06, 0x06, 0x46, 0x3C, 0x00}, //18: S
  {0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, //19: T
  {0x00, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x3C, 0x00}, //20: U
  {0x00, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x22, 0x14, 0x08, 0x00}, //21: V
  {0x00, 0x62, 0x62, 0x62, 0x62, 0x62, 0x6A, 0x76, 0x62, 0x42, 0x00}, //22: W
  {0x00, 0x42, 0x62, 0x74, 0x38, 0x1C, 0x2E, 0x46, 0x42, 0x42, 0x00}, //23: X
  {0x00, 0x42, 0x62, 0x74, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, //24: Y
  {0x00, 0x7E, 0x06, 0x0E, 0x0C, 0x18, 0x30, 0x70, 0x60, 0x7E, 0x00}, //25: Z
  {0x00, 0x3C, 0x62, 0x62, 0x66, 0x6A, 0x72, 0x62, 0x62, 0x3C, 0x00}, //26: 0
  {0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, //27: 1
  {0x00, 0x3C, 0x46, 0x06, 0x06, 0x1C, 0x20, 0x60, 0x60, 0x7E, 0x00}, //28: 2
  {0x00, 0x3C, 0x46, 0x06, 0x06, 0x1C, 0x06, 0x06, 0x46, 0x3C, 0x00}, //29: 3
  {0x00, 0x0C, 0x1C, 0x2C, 0x4C, 0x4C, 0x7E, 0x0C, 0x0C, 0x0C, 0x00}, //30: 4
  {0x00, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x06, 0x06, 0x46, 0x3C, 0x00}, //31: 5
  {0x00, 0x3C, 0x62, 0x60, 0x60, 0x7C, 0x62, 0x62, 0x62, 0x3C, 0x00}, //32: 6
  {0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00}, //33: 7
  {0x00, 0x3C, 0x62, 0x62, 0x62, 0x3C, 0x62, 0x62, 0x62, 0x3C, 0x00}, //34: 8
  {0x00, 0x3C, 0x46, 0x46, 0x46, 0x3E, 0x06, 0x06, 0x46, 0x3C, 0x00}, //35: 9
  {0x00, 0x00, 0x02, 0x06, 0x0E, 0x1C, 0x38, 0x70, 0x60, 0x40, 0x00}, //36: /
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00}, //37: -
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00}, //38: .
  {0x00, 0x3C, 0x46, 0x06, 0x06, 0x0C, 0x10, 0x00, 0x30, 0x30, 0x00}, //39: ?
  {0x00, 0x18, 0x18, 0x18, 0x18, 0x10, 0x10, 0x00, 0x18, 0x18, 0x00}, //40: !
  {0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00}, //41: :
  {0x00, 0x3C, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, //42: 
  {0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, //43: '
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}  //44: space
};

void shortPulse (char PIN)
{
  digitalWrite(PIN, 1);
  digitalWrite(PIN, 0);
}

void setFreq(double freq)
{
  //--calculate
  int32_t d_Phase = freq * pow(2, 32) / 125000000;
  //--send first 32bit
  for (int i=0; i<32; i++, d_Phase>>=1)
  {
    if(d_Phase & 1 == 1)
    {
      digitalWrite(DATA, HIGH); //--data
    }
    else
    {
      digitalWrite(DATA, LOW); //--data
    }
    shortPulse(CLK);
  }
  //--send rest 8bit
  digitalWrite(DATA, LOW); //--data
  for (int i=0; i<8; i++)
  {
    shortPulse(CLK);
  }
  //--finish
  shortPulse(FQ);
}

void setup()
{
  char ch;
  
  Serial.begin(9600); //For debug
  Serial1.begin(38400); //Camera Serial
  delay(300);
  while(Serial.available())
  {
    ch = Serial.read(); //flush
  }
  while(Serial1.available())
  {
    ch = Serial1.read(); //flush
  }
  Serial.println("SERIAL READY");
  
  GPS.begin(9600);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PGCMD_ANTENNA);
  delay(1000);
  GPSSerial.println(PMTK_Q_RELEASE);
  Serial.println("GPS READY");
  
  pinMode(10, OUTPUT);
  pinMode(31, OUTPUT); //LED(ERROR)
  pinMode(33, OUTPUT); //LED(SHOT,SEND)
  digitalWrite(31, LOW);
  digitalWrite(33, LOW);
  
  if (!SD.begin(chipSelect))
  {
    Serial.println("ERROR: INSERT SD CARD AND RESTART");
    digitalWrite(31, HIGH);
    return ;
  }
  Serial.println("SD CARD INITIALIZED");
  Serial.println();
  
  pinMode(47, INPUT);
  pinMode(49, INPUT);
  digitalWrite(47, 1); //INTERNAL PULL UP
  digitalWrite(49, 1); //INTERNAL PULL UP
  
  pinMode(CLK, OUTPUT);
  pinMode(FQ, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RST, OUTPUT);
  //--dds reset
  shortPulse(RST);
  shortPulse(CLK);
  //--change mode
  shortPulse(FQ);
  
  //--cam reset
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x26);
  Serial1.print((char)0x00);
  delay(2500);
  
  while(!Serial1.available()){ }
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  //--cam setSize
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x54);
  Serial1.print((char)0x01);
  Serial1.print((char)0x11); //320*240:
  delay(100);
  
  while(!Serial1.available()){ }
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  //--cam compRatio
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x31);
  Serial1.print((char)0x05);
  Serial1.print((char)0x01);
  Serial1.print((char)0x01);
  Serial1.print((char)0x12);
  Serial1.print((char)0x04);
  Serial1.print((char)0x36); //SMALLER VALUE: LESS COMPRESSED (DEFAULT: 0x36)
  delay(100);
  
  while(!Serial1.available()){ }
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  //--cam motionDetectEnable
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x42);
  Serial1.print((char)0x03);
  Serial1.print((char)0x00);
  Serial1.print((char)0x01);
  Serial1.print((char)0x01);
  delay(100);
  
  while(!Serial1.available()){ }
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  Serial.println("JPEG CAMERA INITIALIZED");
  Serial.println();

  //PD90 532(uS/px)
  Timer1.attachInterrupt(timer1_interrupt).start(286);        // ***** PD50 286(uS/px) +/- SLANT ADJUST *****
  delay(100);
}

void timer1_interrupt(void)
{
  if(sSq == 3)
  {
    if(ti < 1280)
    {
      setFreq(1500 + 3.13 * lineBuf[ti]);
    }
    else if(ti == 1280)
    {
      setFreq(1200);
      syncTime = micros();
      sSq = 2;
    }
    ti++;
  }
}

void jpegTake(void)
{
  byte start_addr_m;
  byte start_addr_l;
  int  byteCount;
  byte incomingByte;
  byte last2bytes[2];
  boolean jpegEnd;
  boolean err;
  
  String string1 = String("S");
  String string2 = String("");
  String string3 = String(".JPG");
  String string4 = String("");
  
  Serial.println("NEW FILE");
  
  //--cam takePhoto
  digitalWrite(33, HIGH);
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x36);
  Serial1.print((char)0x01);
  Serial1.print((char)0x00);
  delay(100);
  
  while(!Serial1.available()){ }
  digitalWrite(33, LOW);
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  //--cam readSize
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x34);
  Serial1.print((char)0x01);
  Serial1.print((char)0x00);
  delay(100);
  
  while(!Serial1.available()){ }
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
  
  //--cam readContent
  start_addr_m = 0x00;
  start_addr_l = 0x00;
  jpegEnd = false;
  
  //--jpeg fileSave
  string2 = String(fileNumber);
  string4 = String(string1 + string2 + string3);
  string4.toCharArray(fileName, 8);
  
  err = false;
  myFile1 = SD.open(fileName, FILE_WRITE | O_TRUNC);
  if(myFile1)
  {
  
    while(!jpegEnd)
    {
      /*
      Serial.print("start_addr: ");
      Serial.print(start_addr_m, HEX);
      Serial.print(" ");
      Serial.print(start_addr_l, HEX);
      Serial.println();
      delay(10);
      */
      Serial1.print((char)0x56);
      Serial1.print((char)0x00);
      Serial1.print((char)0x32);
      Serial1.print((char)0x0C);
      Serial1.print((char)0x00);
      Serial1.print((char)0x0A);
      Serial1.print((char)0x00);
      Serial1.print((char)0x00);
      
      Serial1.print((char)start_addr_m);  //MM
      Serial1.print((char)start_addr_l);  //MM
      
      Serial1.print((char)0x00);
      Serial1.print((char)0x00);
      Serial1.print((char)0x00);  //KK
      Serial1.print((char)0x20);  //KK (DEC:32)
      Serial1.print((char)0x00);  //XX
      Serial1.print((char)0x0A);  //XX
      
      while(!Serial1.available()){ }
      delay(22); //10ms at least
      
      byteCount = 0;
      while(Serial1.available())
      {
        incomingByte = Serial1.read();
        /*
        Serial.print(incomingByte, HEX);
        Serial.print(" ");
        */
        if(byteCount >=5 && byteCount <=36)
        {
          myFile1.write(incomingByte);
          
          last2bytes[1] = incomingByte;
          if(last2bytes[0] == 0xFF && last2bytes[1] == 0xD9)
          {
            /*
            Serial.println("CAUGHT FFD9!");
            */
            jpegEnd = true;
          }
          last2bytes[0] = last2bytes[1];
        }
        byteCount++;
      }
      /*
      Serial.println();
      */
      if(start_addr_l == 0xE0)
      {
        start_addr_l = 0x00;
        start_addr_m += 0x01;
      }
      else
      {
        start_addr_l += 0x20; //DEC:32
      }
    }
  }
  else
  {
    Serial.println("CAN'T OPEN FILE");
    err = true;
  }
  myFile1.close();
  
  if(err == true)
  {
    digitalWrite(31, HIGH);
    while(1);
  }
  else
  {
    Serial.println(fileName);
    Serial.println("JPEG FILE SAVED");
    Serial.println();
  }
  
  //--cam stop
  Serial1.print((char)0x56);
  Serial1.print((char)0x00);
  Serial1.print((char)0x36);
  Serial1.print((char)0x01);
  Serial1.print((char)0x03);
  delay(100);
  
  while(!Serial1.available()){ }
  while(Serial1.available())
  {
    Serial.print(Serial1.read(), HEX);
    Serial.print(" ");
  }
  Serial.println();
}

void charToRAM(int infoNo)
{
  int i,j,k,x,y;
  int pxSkip;
  
  for(i = 0; i < 12; i++)
  {
    byte fontNumber;
    char ch;
    
    switch(infoNo)
    {
      case 0:
        ch = info1[i];
        break;
      case 1:
        ch = info2[i];
        break;
      case 2:
        ch = info3[i];
        break;
    }
    
    for(y = 0; y < 11; y++)
    {
      for(x = 0; x < 8; x++)
      {
        pxSkip = 16 + (320 * (y + 3)) + (3 * 8 * i) + (3 * x); //Width: x3
        
        uint8_t mask;
        mask = pow(2, 7 - x);

        fontNumber = 44; //default is SPACE
        if(ch >= 65 && ch <= 90) //A to Z
        {
          fontNumber = ch - 65;
        }
        else if(ch >= 48 && ch <= 57) //0 to 9
        {
          fontNumber = ch - 22;
        }
        else
        {
          //Start from No. 36
          byte specialChar[] = {'/', '-', '.', '?', '!', ':', '', '\'', ' '};
          for(k = 0; k < 9; k++)
          {
            if(ch == specialChar[k])
            {
              fontNumber = k + 36;
            }
          }
        }
        
        if((fonts[fontNumber][y] & mask) != 0)
        {
          for(j = 0; j < 9; j++)
          {
            sortBuf[(3 * pxSkip) + j] = 0xFF;
          }
        }
      }
    }
  }
}

void owColorBar(int lines)
{
  int i;
  
  for(i = 0; i < (320 * lines); i++)
  {
    if(i % 320 < 64) //Red
    {
      sortBuf[3 * i + 0] = 0xFF;
      sortBuf[3 * i + 1] = 0x00;
      sortBuf[3 * i + 2] = 0x00;
    }
    else if(i % 320 < 128) //Green
    {
      sortBuf[3 * i + 0] = 0x00;
      sortBuf[3 * i + 1] = 0xFF;
      sortBuf[3 * i + 2] = 0x00;
    }
    else if(i % 320 < 192) //Blue
    {
      sortBuf[3 * i + 0] = 0x00;
      sortBuf[3 * i + 1] = 0x00;
      sortBuf[3 * i + 2] = 0xFF;
    }
    else if(i % 320 < 256) //White
    {
      sortBuf[3 * i + 0] = 0xFF;
      sortBuf[3 * i + 1] = 0xFF;
      sortBuf[3 * i + 2] = 0xFF;
    }
    else if(i % 320 < 320) //Black
    {
      sortBuf[3 * i + 0] = 0x00;
      sortBuf[3 * i + 1] = 0x00;
      sortBuf[3 * i + 2] = 0x00;
    }
  }
}

void convToYUV(void)
{
  int pxSkip;
  int k,l;
  
  for(k = 0; k < 8; k++) //8(pairs)
  {
    for(l = 0; l < 320; l++)
    {
      pxSkip = (320 * 2) * k; //320(px)x2(lines)
      byte odR = sortBuf[(3 * pxSkip) + (3 * l) + 0];
      byte odG = sortBuf[(3 * pxSkip) + (3 * l) + 1];
      byte odB = sortBuf[(3 * pxSkip) + (3 * l) + 2];
      byte evR = sortBuf[(3 * pxSkip) + (3 * (l + 320)) + 0];
      byte evG = sortBuf[(3 * pxSkip) + (3 * (l + 320)) + 1];
      byte evB = sortBuf[(3 * pxSkip) + (3 * (l + 320)) + 2];
      
      float avR = (odR + evR) / 2.00;
      float avG = (odG + evG) / 2.00;
      float avB = (odB + evB) / 2.00;
                 
      float Y0 =  16.0 + (0.004 * (( 65.738 * odR) + (129.057 * odG) + ( 25.064 * odB)));
      float Y1 =  16.0 + (0.004 * (( 65.738 * evR) + (129.057 * evG) + ( 25.064 * evB)));
      float RY = 128.0 + (0.004 * ((112.439 * avR) + (-94.154 * avG) + (-18.285 * avB)));
      float BY = 128.0 + (0.004 * ((-37.945 * avR) + (-74.494 * avG) + (112.439 * avB)));
      
      pxSkip = (320 * 4) * k; //320(px)x4(bytes)
      yuvBuf[pxSkip + (4 * l) + 0] = (byte)Y0;
      yuvBuf[pxSkip + (4 * l) + 1] = (byte)RY;
      yuvBuf[pxSkip + (4 * l) + 2] = (byte)BY;
      yuvBuf[pxSkip + (4 * l) + 3] = (byte)Y1;
    }
  }
}

void fetchGPS(void)
{
  String gpsStr1  = String(""); //34.47
  String gpsStr2  = String(""); //N
  String gpsStr3  = String(""); //135.31
  String gpsStr4  = String(""); //E
  String gpsStr5  = String(""); //58
  String gpsLine1 = String("");
  String gpsLine2 = String("");

  int gpsFix = 0;
  int notFix = 0;
  while(1)
  {
    char c = GPS.read();
    if(GPSECHO)
    {
      if(c)
      {
        Serial.print(c);
      }
    }
    if(GPS.newNMEAreceived())
    {
      Serial.print(GPS.lastNMEA());
      if(!GPS.parse(GPS.lastNMEA()))
      {
        //return;
      }
    }
    if(millis() - timer > 2000)
    {
      timer = millis();
      Serial.println();
      Serial.print("Fix: "); Serial.println((int)GPS.fix);
      if(GPS.fix)
      {
        Serial.print(GPS.latitude, 0); Serial.print(GPS.lat); Serial.print(", ");
        Serial.print(GPS.longitude, 0); Serial.println(GPS.lon);
        Serial.print("Altitude: "); Serial.println((int)GPS.altitude);
        Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
        Serial.println();
        
        gpsFix++;
        if(gpsFix == 5)
        {
          //Latitude
          gpsStr1 = String(GPS.latitude / 100, 2);
          if(GPS.latitude / 100 < 10)
          {
            gpsStr1 = String(" " + gpsStr1);
          }
          gpsStr2 = String(GPS.lat); //N or S
          
          //Upper line
          gpsStr1.replace('.', ''); gpsStr1.concat('\'');
          gpsLine1 = String(" " + gpsStr1 + gpsStr2 + " ALT");
          gpsLine1.toCharArray(info2, 13);
          
          //Longitude
          gpsStr3 = String(GPS.longitude / 100, 2);
          if(GPS.longitude / 100 < 10)
          {
            gpsStr3 = String("  " + gpsStr3);
          }
          else if(GPS.longitude / 100 < 100)
          {
            gpsStr3 = String(" " + gpsStr3);
          }
          gpsStr4 = String(GPS.lon); //E or W
          
          //Altitude
          gpsStr5 = String((int)GPS.altitude);
          if(GPS.altitude == 0.00)
          {
            gpsStr5 = String("---");
          }
          else if(GPS.altitude < 10)
          {
            gpsStr5 = String("  " + gpsStr5);
          }
          else if(GPS.altitude < 100)
          {
            gpsStr5 = String(" " + gpsStr5);
          }
          else if(GPS.altitude >= 1000)
          {
            gpsStr5 = String("OVR");
          }
          
          //Lower line
          gpsStr3.replace('.', ''); gpsStr3.concat('\'');
          gpsLine2 = String(gpsStr3 + gpsStr4 + " " + gpsStr5);
          gpsLine2.toCharArray(info3, 13);
          
          Serial.println(gpsLine1);
          Serial.println(gpsLine2);
          Serial.println();
          
          break;
        }
      }
      else
      {
        notFix++;
        if(notFix == 7)
        {
          gpsLine1 = String("GPS:        ");
          gpsLine2 = String("TIMEOUT     ");
          gpsLine1.toCharArray(info2, 13);
          gpsLine2.toCharArray(info3, 13);
          
          Serial.println(gpsLine1);
          Serial.println(gpsLine2);
          Serial.println();
          
          break;
        }
      }
    }
  }
}

void jpegDecode(void)
{
  char str[100];
  uint8 *pImg;
  int x,y,bx,by;
  
  boolean err;
  int i,j,k;
  int pxSkip;
  
  // Decoding start
  JpegDec.decode(fileName,0);
  //JpegDec.decode("SSTV.JPG",0);
  
  // Image Information
  Serial.print("Width :");
  Serial.println(JpegDec.width);
  Serial.print("Height  :");
  Serial.println(JpegDec.height);
  Serial.print("Components:");
  Serial.println(JpegDec.comps);
  Serial.print("MCU / row :");
  Serial.println(JpegDec.MCUSPerRow);
  Serial.print("MCU / col :");
  Serial.println(JpegDec.MCUSPerCol);
  Serial.print("Scan type :");
  Serial.println(JpegDec.scanType);
  Serial.print("MCU width :");
  Serial.println(JpegDec.MCUWidth);
  Serial.print("MCU height:");
  Serial.println(JpegDec.MCUHeight);
  Serial.println("");
  
  // Serial output
  sprintf(str,"#SIZE,%d,%d",JpegDec.width,JpegDec.height);
  Serial.println(str);
  
  // Image size error
  if(JpegDec.width != 320 || JpegDec.height != 240)
  {
    digitalWrite(31, HIGH);
    Serial.println("ERROR: PICTURE SIZE SHOULD BE 320*240") ;
    while(1);
  }
  
  // Header - Color bar - Decode jpeg - GPS position
  err = false;
  myFile1 = SD.open("YUV.DAT", FILE_WRITE | O_TRUNC);
  if(myFile1)
  {
    // Add header 16(lines)
    for(i = 0; i < 5120; i++) //320(px)x16(lines)
    {
      //Background color
      sortBuf[3 * i + 0] = 0x00; //R
      sortBuf[3 * i + 1] = 0x00; //G
      sortBuf[3 * i + 2] = 0x00; //B
    }
    charToRAM(0);
    //Now complete 16(lines) RGB header data available on sortBuf[]
    
    //Convert to YUV and save to SD card
    convToYUV();
    for(k = 0; k < 10240; k++) //320(px)x4(bytes)x8(pairs)
    {
      myFile1.write(yuvBuf[k]);
    }
    
    // Decode jpeg
    i = 0;
    j = 0;
    while(JpegDec.read())
    {
      pImg = JpegDec.pImage ;
      
      for(by=0; by < JpegDec.MCUHeight; by++)
      {
        for(bx=0; bx < JpegDec.MCUWidth; bx++)
        {
          x = JpegDec.MCUx * JpegDec.MCUWidth + bx;
          y = JpegDec.MCUy * JpegDec.MCUHeight + by;
          
          if(x < JpegDec.width && y < JpegDec.height)
          {
            if(JpegDec.comps == 1) // Grayscale
            {
              //sprintf(str,"#RGB,%d,%d,%u", x, y, pImg[0]);
              //Serial.println(str);
            }
            else // RGB
            {
              //sprintf(str,"#RGB,%d,%d,%u,%u,%u", x, y, pImg[0], pImg[1], pImg[2]);
              //Serial.println(str);
              
              pxSkip = ((y - (16 * j)) * 320) + x;
              sortBuf[(3 * pxSkip) + 0] = pImg[0];
              sortBuf[(3 * pxSkip) + 1] = pImg[1];
              sortBuf[(3 * pxSkip) + 2] = pImg[2];
              
              i++;
              if(i == 5120) //320(px)x16(lines)
              {
                //Now complete 16(lines) RGB image data available on sortBuf[]
                //Overwrite color bar
                if(colorBar)
                {
                  if(j == 0)
                  {
                    owColorBar(6);
                  }
                }
                
                //Test pattern
                if(test)
                {
                  owColorBar(16);
                }
                
                //GPS position overlay
                if(gps == true)
                {
                  if(j == 12 || j == 13)
                  {
                    //Adjust background brightness
                    for(k = 0; k < 15360; k++)
                    {
                      sortBuf[k] -= 0x7F;
                      if(sortBuf[k] > 0x80)
                      {
                        sortBuf[k] = 0x00;
                      }
                    }
                  }
                  switch(j)
                  {
                    case 12:
                      charToRAM(1);
                      break;
                    case 13:
                      charToRAM(2);
                      break;
                  }
                }
                
                //Convert to YUV and save to SD card
                convToYUV();
                for(k = 0; k < 10240; k++) //320(px)x4(bytes)x8(pairs)
                {
                  myFile1.write(yuvBuf[k]);
                }
                i = 0;
                j++; //15(sections)
              }
            }
          }
          pImg += JpegDec.comps ;
        }
      }
    }
    //for(;;);
    Serial.println("JPEG FILE DECODED");
    Serial.println();
  }
  else
  {
    Serial.println("CAN'T OPEN FILE");
    err = true;
  }
  myFile1.close();
  
  if(err == true)
  {
    digitalWrite(31, HIGH);
    while(1);
  }
  else
  {
    Serial.println("DAT FILE SAVED");
    Serial.println();
  }
}

void waitForStart(void)
{
  byte incomingByte;
    
  setFreq(2); //Mute
  
  while(sSq == 0)
  {
    if(digitalRead(47) == 0) //TX enabled
    {
      if(digitalRead(49) == 0) //Holded
      {
        sTimes = 0;
        sSq = 1;
      }
      else if(sTimes > 0) //Send 2 times
      {
        sSq = 1;
      }
      else
      {
        delay(500);
        
        //Motion detection
        Serial1.print((char)0x56);
        Serial1.print((char)0x00);
        Serial1.print((char)0x37);
        Serial1.print((char)0x01);
        Serial1.print((char)0x01); //Start
        delay(100);
        
        while(!Serial1.available()){ }
        while(Serial1.available())
        {
          Serial.print(Serial1.read(), HEX);
          Serial.print(" ");
        }
        Serial.println();
        
        Serial.println("MOTION DETECTION STARTED");
        Serial.println();
        
        while(sSq == 0)
        { 
          while(Serial1.available())
          {
            incomingByte = Serial1.read();
            if(incomingByte == 0x39)
            {
              Serial.println(incomingByte, HEX);
              Serial.println("MOTION DETECTED!");
              Serial.println();
              sTimes = 2;
              sSq = 1;
            }
          }
          if(analogRead(3) > 850) //External trigger
          {
            Serial.println("A3 TRIGGERED!");
            Serial.println();
            sTimes = 2;
            sSq = 1;
          }
          
          delay(500);
          
          if(digitalRead(49) == 0) //Holded
          {
            sTimes = 0;
            sSq = 1;
          }
          else if(digitalRead(47) == 1) //TX disabled
          {
            sTimes = 0;
            sSq = 0;
            break;
          }
        }
        
        //Motion detection
        Serial1.print((char)0x56);
        Serial1.print((char)0x00);
        Serial1.print((char)0x37);
        Serial1.print((char)0x01);
        Serial1.print((char)0x00); //Stop
        delay(100);
        
        while(!Serial1.available()){ }
        while(Serial1.available())
        {
          Serial.print(Serial1.read(), HEX);
          Serial.print(" ");
        }
        Serial.println();
        
        Serial.println("MOTION DETECTION STOPPED");
        Serial.println();
      }
    }
    else //Disabled
    {
      sTimes = 0;
    }
  }
}

void sstvEncode(void)
{
  boolean err;
  boolean head;
  int i,j;
  
  err = false;
  myFile1 = SD.open("YUV.DAT");
  if(myFile1)
  {
    head = true;
    while(myFile1.available())
    {
      //Header
      if(head == true)
      {
        digitalWrite(33, HIGH);
        //-- VOX TONE --
        if(vox == 1)
        {
          setFreq(1900);
          delay(100);
          setFreq(1500);
          delay(100);
          setFreq(1900);
          delay(100);
          setFreq(1500);
          delay(100);
          setFreq(2300);
          delay(100);
          setFreq(1500);
          delay(100);
          setFreq(2300);
          delay(100);
          setFreq(1500);
          delay(100);
        }
        //-- VIS CODE --
        
        //VIS CODE for PD50 is B1011101 (DECIMAL 93)
        setFreq(1900);
        delay(300);
        setFreq(1200); //BREAK
        delay(10);
        setFreq(1900);
        delay(300);
        setFreq(1200); //START BIT
        delay(30);
        setFreq(1100); //BIT 0 (LSB FIRST)
        delay(30);
        setFreq(1300); //BIT 1
        delay(30);
        setFreq(1100); //BIT 2,3,4
        delay(90);
        setFreq(1300); //BIT 5
        delay(30);
        setFreq(1100); //BIT 6
        delay(30);
        setFreq(1100); //EVEN PARITY
        delay(30);
        setFreq(1200); //STOP BIT
        delay(30);
        //-- VIS DONE --
        
        /*
        //VIS CODE for PD90 is B1100011 (DECIMAL 99)
        setFreq(1900);
        delay(300);
        setFreq(1200); //BREAK
        delay(10);
        setFreq(1900);
        delay(300);
        setFreq(1200); //START BIT
        delay(30);
        setFreq(1100); //BIT 0,1 (LSB FIRST)
        delay(60);
        setFreq(1300); //BIT 2,3,4
        delay(90);
        setFreq(1100); //BIT 5,6
        delay(60);
        setFreq(1300); //EVEN PARITY
        delay(30);
        setFreq(1200); //STOP BIT
        delay(30);
        //-- VIS DONE --
        */
        
        Serial.println("ENCODE!");
        line = 0;
        head = false;
        
        //Sync
        setFreq(1200);
        syncTime = micros();
      }
      
      //Read one section (= two lines) data at a time
      for(i = 0; i < 320; i++)
      {
        for(j = 0; j < 4; j++)
        {
          lineBuf[i + 320 * j] = myFile1.read();
        }
      }
      //Serial.println(micros() - syncTime); //Check reading time
      
      while(micros() - syncTime < 19200){}        // ***** 20,000(uS) +/- ODD/EVEN LINE ADJUST *****
      
      //Porch
      setFreq(1500);
      syncTime = micros();
      
      while(micros() - syncTime < 2080){}
      
      //Scan
      ti = 0; sSq = 3;
      while(sSq == 3) {};
      
      line++;
      Serial.print('.');
      if(line % 64 == 0)
      {
        Serial.println();
      }
      if(line == 128)
      {
        Serial.println();
        Serial.println("PICTURE SENT") ;
        Serial.println(sTimes);
        Serial.println();
        digitalWrite(33, LOW);
        setFreq(2);
        sSq = 0;
        
        if(sTimes > 0)
        {
          sTimes--;
        }
        else
        {
          int interval = 10;      // ***** INTERVAL (IF NECESSARY) *****
          Serial.print("INTERVAL ");
          Serial.print(interval);
          Serial.println("S");
          j = 0;
          for(i = 0; i < interval; i++)
          {
            delay(1000);
            Serial.print('.');
            j++;
            if(j % 60 == 0)
            {
              Serial.println();
            }
          }
          Serial.println();
        }
      }
    }
  }
  else
  {
    Serial.println("CAN'T OPEN FILE");
    err = true;
  }
  myFile1.close();
  
  if(err == true)
  {
    digitalWrite(31, HIGH);
    while(1);
  }
}

void loop()
{
  if(sSq == 0)
  {
    waitForStart();
  }
  else if(sSq >= 1)
  {
    if(gps)
    {
          fetchGPS();
    }
    jpegTake();
    jpegDecode();
    fileNumber++;
    if(fileNumber == 100)
    {
      fileNumber = 0;
    }
    sstvEncode();
  }
}