/*************************************************
JI3BNB SSTV ENCODER
Robot B/W8 Format, TEXT MESSAGE / BMP FILE
2018.08.01 v2.01
ARDUINO "DUE" + ETHERNET SHIELD R3 + AD9850 DDS
ETHERNET SHIELD ACTS JUST AS A "SD CARD SHIELD"
--- THIS CODE IS IN THE PUBLIC DOMAIN ---
/*************************************************/
#include <SPI.h>
#include <SD.h>
#include <DueTimer.h>
File myFile;
#define chipSelect 4
#define CLK 6
#define FQ 7
#define DATA 8
#define RST 9
volatile long oFrq = 7171000; // ***** set RF OUTPUT FREQUENCY in Hz *****
volatile byte mode;
volatile byte sSq;
volatile int line;
volatile int ti;
const boolean vox = 1; // ***** USE VOX TONE OR NOT *****
const boolean aRf = 0; // ***** DDS VFO OUTPUT MODE *****
//AF: 0
//RF: 1
char line00[10] = "CQCQ SSTV"; // ***** TEXT MESSAGE 9x3 *****
char line01[10] = "DE JI3BNB";
char line02[10] = "GL PM74SS";
//FONTS
const uint8_t fonts[43][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, 0x06, 0x06, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} //42: space
};
uint8_t frameBuf[19200]; //160*120
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);
}
else
{
digitalWrite(DATA, LOW);
}
shortPulse(CLK);
}
//--send rest 8bit
digitalWrite(DATA, LOW);
for (int i=0; i<8; i++)
{
shortPulse(CLK);
}
//--finish
shortPulse(FQ);
}
void setup()
{
//--SD CARD
Serial.begin(9600) ;
pinMode(10, OUTPUT) ;
pinMode(31, OUTPUT) ; //ERROR LAMP
digitalWrite(31, LOW);
if (!SD.begin(chipSelect))
{
Serial.println("ERROR: INSERT SD CARD AND RESTART") ;
digitalWrite(31, HIGH);
return ;
}
Serial.println("SD CARD INITIALIZED") ;
pinMode(47, INPUT);
pinMode(49, INPUT);
digitalWrite(47, 1); //INTERNAL PULL UP
digitalWrite(49, 1); //INTERNAL PULL UP
//--DDS
pinMode(CLK, OUTPUT);
pinMode(FQ, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(RST, OUTPUT);
//--dds reset
shortPulse(RST);
shortPulse(CLK);
//--change mode
shortPulse(FQ);
delay(5);
Timer1.attachInterrupt(timer1_interrupt).start(352); // ***** 354(uS/px) +/- SLANT ADJUST *****
delay(100);
}
void timer1_interrupt(void)
{
int p;
if(sSq == 3)
{
if(mode == 1)
{
if(ti < 160)
{
p = (160 * line) + ti;
if(aRf == 0)
{
setFreq(1500 + 3.13 * frameBuf[p]);
}
else
{
setFreq(oFrq - (1500 + 3.13 * frameBuf[p]));
}
}
else if(ti == 160)
{
line++;
sSq = 2;
}
if(line == 120)
{
sSq = 0;
}
ti++;
}
else if(mode == 0)
{
if(line >= 40 && line <= 72)
{
if(ti < 160)
{
p = 160 * (line - 40) + ti;
if(aRf == 0)
{
if(frameBuf[p])
{
setFreq(2300);
}
else
{
setFreq(1500);
}
}
else
{
if(frameBuf[p])
{
setFreq(oFrq - 2300);
}
else
{
setFreq(oFrq - 1500);
}
}
}
else if(ti == 160)
{
line++;
sSq = 2;
}
}
else
{
if(aRf == 0)
{
switch(ti)
{
case 0:
setFreq(1500);
break;
case 40:
setFreq(1767);
break;
case 80:
setFreq(2033);
break;
case 120:
setFreq(2300);
break;
case 160:
line++;
if(line == 120)
{
sSq = 0;
}
else
{
sSq =2;
}
break;
}
}
else
{
switch(ti)
{
case 0:
setFreq(oFrq - 1500);
break;
case 40:
setFreq(oFrq - 1767);
break;
case 80:
setFreq(oFrq - 2033);
break;
case 120:
setFreq(oFrq - 2300);
break;
case 160:
line++;
if(line == 120)
{
sSq = 0;
}
else
{
sSq =2;
}
break;
}
}
}
ti++;
}
}
}
void loop()
{
int x,y,i,p;
if(sSq == 0)
{
setFreq(2);
while(sSq == 0)
{
if(digitalRead(47) == 0) // - SEND SW -
{
sSq = 1;
}
else
{
delay(30);
}
}
}
else if(sSq == 1)
{
boolean err;
if(digitalRead(49) == 1) // - MODE SW -
{
err = 0;
mode = 1; //PICTURE MODE
myFile = SD.open("sstv.bmp",FILE_READ) ;
if(myFile)
{
uint16_t addr;
uint8_t bData;
uint8_t offset;
uint8_t cDepth;
byte rgbCnt;
float pxR;
float pxG;
float pxB;
float pxGr;
uint8_t pxGr_;
for(i = 0; i < 19200; i++)
{
frameBuf[i] = 0xFF;
}
x = 0;
y = 0;
addr = 0;
rgbCnt = 0;
while(myFile.available())
{
bData = myFile.read();
//Serial.print(addr, HEX);
//Serial.print(":");
//Serial.println(bData);
//delay(300);
if(addr == 0x00)
{
if(bData != 0x42) //ASCII"B"
{
Serial.println("ERROR: THIS IS NOT BMP FILE");
err = 1;
goto fileCls;
}
}
else if(addr == 0x01)
{
if(bData != 0x4D) //ASCII"M"
{
Serial.println("ERROR: THIS IS NOT BMP FILE");
err = 1;
goto fileCls;
}
}
else if(addr == 0x0A)
{
offset = bData; //STARTING ADDRESS OF PICTURE DATA
}
else if(addr == 0x0E)
{
if(bData != 40) //--WINDOWS BITMAP(40)-- --OS/2 BITMAP(12)--
{
Serial.println("ERROR: THIS IS NOT -WINDOWS- BITMAP FILE");
err = 1;
goto fileCls;
}
}
else if(addr == 0x12)
{
if(bData != 160) //PICTURE WIDTH
{
Serial.println("ERROR: PICTURE WIDTH MUST BE 160 PX");
err = 1;
goto fileCls;
}
}
else if(addr == 0x13)
{
if(bData != 0) //HIGHER BIT
{
Serial.println("ERROR: PICTURE WIDTH MUST BE 160 PX");
err = 1;
goto fileCls;
}
}
else if(addr == 0x16)
{
if(bData != 120) //PICTURE HEIGHT
{
Serial.println("ERROR: PICTURE HEIGHT MUST BE 120 PX");
err = 1;
goto fileCls;
}
}
else if(addr == 0x17) //HIGHER BIT
{
if(bData != 0)
{
Serial.println("ERROR: PICTURE HEIGHT MUST BE 120 PX");
err = 1;
goto fileCls;
}
}
else if(addr == 0x1C)
{
cDepth = bData;
if(cDepth != 24 && cDepth != 32) //COLOR DEPTH 1,4,8,24,32 BIT
{
Serial.println("ERROR: COLOR MUST BE 24 OR 32 BIT");
err = 1;
goto fileCls;
}
}
else if(addr == 0x1E)
{
if(bData != 0) //COMPRESSION TYPE 0,1,2,3
{
Serial.println("ERROR: MUST BE -NO- COMPRESSION FILE");
err = 1;
goto fileCls;
}
}
else if(addr >= 0x0E && addr >= offset)
{
switch(rgbCnt)
{
case 0:
pxB = bData; //-- BLUE --
rgbCnt++;
break;
case 1:
pxG = bData; //-- GREEN --
rgbCnt++;
break;
case 2:
pxR = bData; //-- RED --
pxGr = ((pxB * 0.11) + (pxG * 0.59) + (pxR * 0.30)); //-- Convert to Grey Scale --
pxGr_ = (uint8_t)pxGr;
p = ((119 - y) * 160) + x;
frameBuf[p] = pxGr_;
x++;
if(x == 160)
{
y++;
x = 0;
}
if(y == 120)
{
goto fileCls;
}
if(cDepth == 32)
{
rgbCnt++;
}
else
{
rgbCnt = 0;
}
break;
case 3:
rgbCnt = 0; //-- RESERVE --
break;
}
}
addr++;
}
fileCls:
myFile.close();
}
else
{
Serial.println("ERROR: CAN'T OPEN FILE") ;
err = 1;
}
if(err == 1)
{
digitalWrite(31, HIGH); //ERROR LAMP
while(true); //STOP
}
else
{
Serial.println("BMP FILE PARSED") ;
}
}
else
{
mode = 0; //TEXT MODE
for(p = 0; p < 5280; p++) //160*33
{
frameBuf[p] = 0xFF;
}
for(i = 0; i < 27; i++)
{
byte fontNumber;
char ch;
if(i < 9)
{
ch = line00[i];
}
else if(i < 18)
{
ch = line01[i - 9];
}
else
{
ch = line02[i - 18];
}
for(y = 0; y < 11; y++)
{
for(x = 0; x < 8; x++)
{
if(i < 9)
{
p = 8 + (160 * y) + (2 * (8 * i)) + (2 * x); //Width: x2
}
else if(i < 18)
{
p = 8 + (160 * 11) + (160 * y) + (2 * (8 * (i - 9))) + (2 * x); //Width: x2
}
else
{
p = 8 + (160 * 22) + (160 * y) + (2 * (8 * (i - 18))) + (2 * x); //Width: x2
}
uint8_t mask;
mask = pow(2, 7 - x);
if(ch >= 65 && ch <= 90) //A to Z
{
fontNumber = ch - 65;
}
else if(ch >= 48 && ch <= 57) //0 to 9
{
fontNumber = ch - 22;
}
else if(ch == '/'){fontNumber = 36;}
else if(ch == '-'){fontNumber = 37;}
else if(ch == '.'){fontNumber = 38;}
else if(ch == '?'){fontNumber = 39;}
else if(ch == '!'){fontNumber = 40;}
else if(ch == ':'){fontNumber = 41;}
else if(ch == ' '){fontNumber = 42;}
else {fontNumber = 42;}
if((fonts[fontNumber][y] & mask) != 0)
{
frameBuf[p ] = 0x00;
frameBuf[p + 1] = 0x00;
}
}
}
}
}
if(mode == 0)
{
delay(800);
}
if(aRf == 0)
{
//--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 ROBOT B/W 8S is B0000010 (DECIMAL 2)
setFreq(1900);
delay(300);
setFreq(1200); //BREAK
delay(10);
setFreq(1900);
delay(300);
setFreq(1200); //START BIT
delay(30);
setFreq(1300); //BIT 0 (LSB FIRST)
delay(30);
setFreq(1100); //BIT 1
delay(30);
setFreq(1300); //BIT 2, 3, 4, 5, 6
delay(150);
setFreq(1100); //EVEN PARITY
delay(30);
setFreq(1200); //STOP BIT
delay(30);
}
else //RF OUTPUT
{
//--VIS CODE
setFreq(oFrq - 1900);
delay(300);
setFreq(oFrq - 1200); //BREAK
delay(10);
setFreq(oFrq - 1900);
delay(300);
setFreq(oFrq - 1200); //START BIT
delay(30);
setFreq(oFrq - 1300); //BIT 0 (LSB FIRST)
delay(30);
setFreq(oFrq - 1100); //BIT 1
delay(30);
setFreq(oFrq - 1300); //BIT 2, 3, 4, 5, 6
delay(150);
setFreq(oFrq - 1100); //EVEN PARITY
delay(30);
setFreq(oFrq - 1200); //STOP BIT
delay(30);
}
//--VIS DONE
line = 0;
sSq = 2;
}
else if(sSq == 2)
{
//--sync
if(aRf == 0)
{
setFreq(1200);
delayMicroseconds(10000);
}
else
{
setFreq(oFrq - 1200);
delayMicroseconds(10000);
}
//--
ti = 0;
sSq = 3;
}
}