thortest.jpg

THOR from ad9850


Summary:
Generating digimode transmissions like THOR can be done directly on radio frequency with cheap dds-chip like ad9850 and Arduino, or similar system. With setup mentioned and code described in this page, THOR-transmissions can be generated at any frequency lower than about 40 Mh.

Late autumn forestwalk, testing THOR-transmitter.
.
VIDE.JPG
This code produces THOR-transmission with ad9850-dds chip controlled by Arduino. I have used modified version of
code from webshed , made by David Mills, to control ad9850, and build THOR-modulator on top of that. I have used exactly same hardware connections as on example on webshed. (Altough nowadays I use hardware SPI instead of shiftOut-function, but for slow THOR-transmissions it doesn't really matter.)
This code will not work with original webshed-ad9850.driver, but use the modified one introduced on WSPR from AD9850 -page.



Picture: This is my Very Integrated Development Environment....mostly all is on steel kabin from Ikea, which is assembled 90 degree wrong and can be closed also.There is a car battery behind brown panel powering everything when main power failures.BTW this is our livingroom...as you can see from walls, Finland is still in medieval age...

This is only signal source, to "real" transmitter you may want to have also some amplifier and filters. Tx power generated from ad9850 is around 10mw. (Altought it is absolutely enough for many interesting projects.)
If you want to try transmit THOR with your Arduino, copy hardware connections from webshedand function from this page. You need to call this funtion inside loop(). For example:

thor("This is message), 28500000, 8)

...should sent "This is message" at 28,5 Mhz, at baudrate of 8.
There is new line character ( char(10) ) added at start and end of transmission, for comfortable reading.
This function is tested with Arduino Mega2560 and Teensy 3.1. I see no reason why it would not work with Uno.

How to transmit digimodes in so very neat way...(at your dreams...)


Usually digimodes are transmitted with normal ssb-transmitter, by injecting audio signal from computer sound card to microphone connection or similar in rig. So digital modulation is done first on audio frequency and this audio is shifted up to radio frequency on ssb-transmitter. Only reason for this is to use already existing hardware, there are some possible difficulties to overcome: rf-feedback to audio, need for digimode interface with proper isolation, and so on.

And (in theory) ssb-transmitter is just overly complicated (=expensive+powerhungry+big) for that work.
In real life our hacks are bigger, more complicated and power-wasting as usual rigs of course, and with more trouble all kind, and just so much more interesting.

This experiment is based on my DominoEX -project.
In principle THOR is very much like Domino, but with added strong error correction.
There is more technical introduction of THOR-mode on website of W1HKJ. (Link goes to Fldigi user manual. Fldigi is digimode program I have used to receive signal generated from this project, for testing it.)
Work is made on Arduino IDE 1.0.5. Other versions may need changes for example to use PROGMEM I believe.


//THOR-modulator
 
/*
Function for produsing THOR transmissions with ad9850 DDS-module and teensy or Arduino (tested with Mega2560 and teensy 3.1)
This function have to be called from loop() with parameters thor("String message", long txfrequency, byte baudrate)
for example :  thor("KISS you", 28500000, 8);
Frequency is in hertzs.
Baudrates are 4,5,8,11,16 and 22 which is default and used also if wrong baudrate is given.
 
This function requires also driver snippet for ad9850, which is available from KISS ACTION GROUP website.
Tarmo Huttunen OH6ECF 2015
You can use this code as you like. If you develop it better, please share it.
*/
 
//Varicode characters are stored on this funny-looking array
prog_uint16_t PROGMEM varicodeTable[] ={
1884,//0   <NUL>
1888,//1   <SOH>
1896,//2  <STX>
1900,//3  <ETX>
1904,//4  <EOT>
1908,//5  <ENQ>
1912,//6  <ACK>
1916,//7  <BEL>
168,//8  <BS>
1952,//9  <TAB>
1952,//10 <LF>
1960,//11  <VT>
1964,//12  <FF>
172,//13  <CR>
1968, //14  <SO>
1972,//15  <SI>
1976,//16  <DLE>
1980,//17  <DC1>
1984,//18  <DC2>
2000,//19  <DC3>
2004,//20  <DC4>
2008,//21  <NAK>
2012,//22  <SYN>
2016,//23  <ETB>
2024,//24  <CAN>
2028,//25  <EM>
2032,//26  <SUB>
2036,//27  <ESC>
2040,//28  <FS>
2044,//29  <GS>
2048,//30  <RS>
2560,//31  <US>
4,//32   <SPACE>
448,//33  !
508,//34  "
728,//35  #
680, //36  $
672,//37  %
512, //38  &
444, //39  '
500, //40   (
496,//41  )
692,//42  *
480,//43  +
160,//44  ,
472,//45  -
468,//46  .
488,//47  /
224, //48  0
240, //49  1
320, //50  2
340,//51  3
372,//52  4
352,//53   5
364,//54  6
416,//55  7
384,//56  8
428,//57  9
492,//58  :
504,//59  ;
704,//60  <
476,//61  =
700,//62  >
464,//63  ?
640,//64  @
188,//65  A
256,//66  B
212,//67  C
220,//68  D
184,//69  E
248,//70  F
336,//71  G
344,//72  H
192,//73  I
436,//74  J
380,//75  K
244,//76  L
232,//77  M
252,//78  N
208,//79  O
236,//80  P
432,//81  Q
216,//82  R
180,//83  S
176,//84  T
348,//85  U
424,//86  V
360,//87  W
368,//88  X
376,//89  Y
440,//90  Z
744,//91  (
720,//92  \
748,//93  )
724,//94  ^
688,//95   _
684,//96  `
20,//97  a  //INTENTIONALLY DUBLICATED LINE
20,//97  a  //INTENTIONALLY DUBLICATED LINE
96,//98  b
56,//99  c
52,//100  d
8,//101  e
80,//102  f
88,//103  g
48,//104  h
24,//105  i
128,//106  j
112,//107   k
44,//108  l
64,//109  m
28,//110  n
16,//111  o
84,//112   p
120,//113  q
32,//114  r
40,//115  s
12, //116  t
60,//117  u
108,//118  v
104,//119  w
116,//120  x
92,//121   y
124,//122  z
732,//123  {
696,//124  |
736,//125  }
752,//126  ~
2688,//127  <DEL>
2720,//128  ?
2728,//129  ‚
2732,//130  ‚
2736,//131   ƒ
2740,//132   „
2744,//133   …
2748,//134  †
2752,//135  ‡
2768,//136  ˆ
2772,//137   ‰
2776,//138  Š
2780,//139  ‹
2784,//140  Œ
2792,//141  //I DO not KNOW WHAT THIS IS
2796,//142  Z
2800,//143   //I DO not KNOW WHAT THIS IS
2804,//144  //I DO not KNOW WHAT THIS IS
2808,//145  ‘
2812,//146  ’
2816,//147   “
2880,//148   ”
2896,//149  •
2900,//150  –
2904,//151  —
2908,//152  ˜
2912,//153   ™
2920,//154  š
2924,//155  ›
2928,//156  œ
2932,//157   //I DO not KNOW WHAT THIS IS
2936,//158  z
2940,//159  Ÿ
756,//160   //I DO not KNOW WHAT THIS IS
760,//161   ¡
764,//162   ¢
768,//163  £
832,//164   ¤
848,//165  ¥
852,//166  ¦
856,//167  §
860,//168  ¨
864,//169  ©
872,//170  ª
876,//171  «
880,//172  ¬
884,//173  //I DO not KNOW WHAT THIS IS
888,//174  ®
892,//175  ¯
896,//176  °
928,//177   ±
936,//178  ²
940,//179  ³
944,//180  ´
948,//181  µ
952,//182   ¶
956,//183  ·
960,//184   ¸
976,//185  ¹
980,//186  º
984,//187  »
988,//188  ¼
992,//189  ½
1000,//190  ¾
1004,//191   ¿
1008,//192  À
1012,//193  Á
1016,//194  Â
1020,//195  Ã
1024,//196  Ä
1280,//197  Å
1344,//198   Æ
1360,//199  Ç
1364,//200  È
1368,//201  É
1372,//202  Ê
1376,//203   Ë
1384,//204  Ì
1388,//205  Í
1392,//206  Î
1396,//207  Ï
1400,//208   Ð
1404,//209  Ñ
1408,//210  Ò
1440,//211   Ó
1448,//212  Ô
1452,//213  Õ
1456,//214  Ö
1460,//215  ×
1464,//216  Ø
1468,//217  Ù
1472,//218  Ú
1488,//219  Û
1492,//220  Ü
1496,//221  Ý
1500,//222   Þ
1504,//223  ß
1512,//224  à
1516,//225   á
1520,//226   â
1524,//227  ã
1528,//228  ä
1532,//229  å
1536,//230  æ
1664,//231  ç
1696,//232   è
1704,//233  é
1708,//234  ê
1712,//235  ë
1716,//236  ì
1720,//237  í
1724,//238  î
1728,//239  ï
1744,//240  ð
1748,//241  ñ
1752,//242  ò
1756,//243  ó
1760,//244  ô
1768,//245  õ
1772,//246  ö
1776,//247  ÷
1780,//248  ø
1784,//249  ù
1788,//250  ú
1792,//251  û
1856,//252  n
1872,//253  ý
1876,//254  þ
1880,//255
 
};
 
 
 
void thor(String txString, unsigned long frequency, byte baud){
 
  unsigned long thorTimer = millis();
  byte next = 0; //used to count frequency of next symbol
  boolean dibit1;
  boolean dibit2;
  byte fecOut = B00000000;
  unsigned long fecIn = 0;
  int inFec = 0;//counting bits on fecIn
  byte bitInCounter = 0;
  byte bitsInChar = 0;
  //These are used as shift registers on interliever
  byte interleaver1 = 0;
  int interleaver2 = 0;
  long interleaver3 = 0;
  long interleaver4 = 0;
  //
  byte toTx = 0;
  byte thorOn = 1;
  unsigned int nextChar = 0;//actual char, 0 if ready to tx
  //byte nextCharStart = 0; //If actual char is over 8 bit long, this is the start of char
  byte idleTime = 0; //counter for idle timer
  int round1 = 0;
  unsigned int symbolTime; //length of one tone
  float spacing;
 
  String thorMessage = txString;
  thorMessage = char(10)  +  thorMessage + char(10);
  int length = thorMessage.length();
  AD9850_reset();
  AD9850_init();
 
 
 
 
  switch(baud){
        case 4: {
          symbolTime = 256;
          spacing = 7.8125;
          frequency -= 86;
          break;
        }
         case 5: {
          symbolTime = 186;
          spacing = 10.7666;
          frequency -= 122;
          break;
        }
        case 8: {
          symbolTime = 128;
          spacing = 15.625;
          frequency -= 173;
          break;
        }
        case 11: {
          symbolTime = 93;
          spacing = 10.766;
          frequency -= 131;
          break;
        }
        case 16:{
          symbolTime = 64;
          spacing = 15.625;
          frequency -= 177;
          break;
        }
        //default here is THOR22
        default:{
          symbolTime = 46;
          spacing = 21.533;
          frequency -= 262;
          break;
        }
  }
 
 
 
  while((length + 10) > round1){
 
    if (nextChar == 0){
      byte k = byte(thorMessage[round1]);
      nextChar = pgm_read_word_near(varicodeTable + k);
 
      //Counting how many bits there are in current varicode character
      //Result will be stored to bitsInChar
      bitsInChar = 0;
      byte x = 0;
      byte currentBit = 0;
 
      while (currentBit == 0){
        currentBit = bitRead(nextChar,(15-x));
        x++;
        }
      bitsInChar = 17 -x;
      round1++;
     }
 
     //Transmitting happends in this long if-block
     if (thorTimer <= millis()){
        thorTimer = thorTimer + symbolTime;
     //FEC
     //If there is no character to send, idle (<NUL>) will be transmitted
        if (inFec < 1 && nextChar == 0){
           if (idleTime > 10){
            thorOn = 0;
            //Serial.print("transmitter off");
            //Serial.print("\n");
            AD9850_reset();
        }
        else   {nextChar = pgm_read_word_near(0);
          idleTime++;
        }
      }
 
     // If there is next character ready, then check if there is yet enough room for it in fecIn
     if ((nextChar != 0) && (21-inFec > bitsInChar)){
 
     //Writing next char to fecIn
      for (byte x = 0; x < bitsInChar; x++){
        bitWrite(fecIn, (inFec+7), bitRead(nextChar,(bitsInChar-x-1)));
        inFec++;
      }
      nextChar = 0;
     }
  //ACTUAL FEC-CODING:
 
       for (byte x=0; x<2; x++){
         dibit1 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,1)) ^ (bitRead(fecIn,0));
         fecOut = fecOut << 1;
         bitWrite(fecOut, 0, dibit1);
 
         dibit2 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,5)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,0));
         fecOut = fecOut << 1;
         bitWrite(fecOut, 0, dibit2);
 
         fecIn = fecIn >> 1;
         inFec--;
 
         if (inFec < 0) {
           inFec = 0;
         }
       }
 
 
        //INTERLEAVER is simply bit shifter. or "delayer"
        //TO INTERLIEVER:
        bitWrite(toTx,3, bitRead(fecOut,3)); //No delay for 1.bit, it is goes straight to toTx
        bitWrite(interleaver2, 0, bitRead(fecOut,2));//delay 10 bits, or shifts
        bitWrite(interleaver3, 0, bitRead(fecOut,1));//delay 20 bits, or shifts
        bitWrite(interleaver4, 0, bitRead(fecOut,0));//delay 30 bits, or shifts
        //FROM INTERLIEVER:
       // toTx,3, is already filled
        bitWrite(toTx,2,(bitRead(interleaver2,10)));
        bitWrite(toTx,1,(bitRead(interleaver3,20)));
        bitWrite(toTx,0,(bitRead(interleaver4,30)));
        //Interleaver is bitshifter, so lets shift it:
        interleaver2=  interleaver2 << 1;
        interleaver3 = interleaver3 << 1;
        interleaver4 = interleaver4 << 1;
 
        // toTx is now ready to be transmitted, and it contains 4-bit gray-code, corresponding to tone difference for next symbol or "tone"
        //those 4 bits are at places 0-3 on toTx, which is actually byte ofcourse. Bytes at places 4-7 are simply 0.
        switch (toTx){
        //Converting gray code to actual tone (or freq)
        //Graycode is readed as decimal, thats why "wrong" sequency of case-desimals
        // Adding two (next+2) is due to nature of IFK+ -modulation used at THOR
 
        //Tone0 0000
        case  0: next = next+2; break;
        //Tone1 0001
        case 1:next = next+3; break;
        //Tone 2: 0011
        case 3:next = next+5; break;
        //Tone3: 0010
        case 2:next = next+4; break;
        //tone4: 0110
        case 6:next = next+8; break;
        //tone5 0111
        case 7:next = next+9; break;
        //tone6 0101
        case 5:next = next+7; break;
        //tone7 0100
        case 4:next = next+6; break;
        //tone8 1100
        case 12:next = next+14; break;
        //tone 9 1101
        case 13:next = next+15; break;
        //tone 10 1111
        case 15:next = next+17; break;
        //tone11 1110
        case 14:next = next+16; break;
        //tone12 1010
        case 10:next = next+12; break;
        //tone13 1011
        case 11:next = next+13; break;
        //tone 14 1001
        case 9:next = next+11; break;
        //tone15 1000
        case 8:next = next+10; break;
        }
 
         if (next > 17){
           next = next - 18;
         }
        //Actual frequency change, modulation, happends now
        if (thorOn == 1){
              SetFrequency(frequency, float((next * spacing)));
        }
    }
  }
  SetFrequency(0);
}




Some Notes...

VaricodeTable

Kuvakaappaus-thor9.png

Varicode characters are defined on array of ints on PROGMEM. (Picture) Defining array is done such a odd-looking way in order to avoid mistakes, or at least to make possible to correct them.
Characters are taken from here. (Work of Nino Porcino IZ8BLY & M. Greenman ZL1BPU) where they are defined in varying lenght of bitrows. Those bitrows are converted to desimal values and stored to int. (Because in Arduino IDE only byte can be defined like B01010... not int, but there are varicode characters longer than eight bits, so int must be used to store them.)
When I was making this array, making one row was about ten-step process. And there are 256 rows...
Don't be confused when you find out that these are "MFSK-varicode characters", yes they are. Same are used on THOR.

Oddity report: character 'a' on row number 97 had to be dublicated. Otherwise characters with line numbers greater than 97 would not work correct, but 'a' would turn to be 'b' , 'b' would turn to be 'c' and so on ( or was it opposite..?). Reason is currently unknown.


[[code]]
 
**First shift register, fecIn:** Unsigned long f<span style="line-height: 1.5;">ecIn is used as shift register. FEC-coder takes bits on "right" side of fecIn. Variable inFec is used to keep count of how many bits are in use at fecIn, and what place next bits should be inserted.</span>
If next character is not 0, and if there is enough room for it in fecIn, b<span style="line-height: 1.5;">its of varicode characters are placed on fecIn. Variable bitsInChar is used to discard mentioned unused start-zeros on int nextChar.</span>
Note that nextChar is variable for the actual next character but when it is set to 0 (after varicode is inserted to fecIn), it means it is ready to be filled with next char.
 
 
<span style="line-height: 1.5;">**Forward Error Correction:** I found needed documentation for FEC [[http://www.qsl.net/zl1bpu/MFSK/FECcoder.htm| here]]. (Work of </span><span style="font-family: arial,helvetica; font-size: 14px; line-height: 1.5;">Nino Porcino IZ8BLY & M. Greenman ZL1BPU). </span><span style="font-family: arial,helvetica; font-size: 13.600000381469727px; line-height: 1.5;">If you look that page, you will find algorithm which is actually at use in FEC-coder.</span>
<span style="font-family: arial,helvetica; font-size: 13.600000381469727px; line-height: 1.5;"> Shortly for one varicode bit inserted in shift register fecIn, scetch produces two FEC-coded bits to be sent to interliever. (FEC is reason why speed of THOR11 is only half of Domino11).</span>
ACTUAL FEC-CODING:

for (byte x=0; x<2; x++){
dibit1 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,1)) ^ (bitRead(fecIn,0));
fecOut = fecOut << 1;
bitWrite(fecOut, 0, dibit1);

dibit2 = (bitRead(fecIn,6)) ^ (bitRead(fecIn,5)) ^ (bitRead(fecIn,4)) ^ (bitRead(fecIn,3)) ^ (bitRead(fecIn,0));
fecOut = fecOut << 1;
bitWrite(fecOut, 0, dibit2);

fecIn = fecIn >> 1; inFec--;

if (inFec < 0) {inFec = 0;}}
After all FEC-coded bits will appear to variable fecOut, which again is shift register. Interliever will take bytes from there:
 
**Interliever:**
 
Interliever spreads each symbols on transmission, to be transmitted over longer period. Therefore short interference bursts, like noise from flashes are easier to correct when receiving. After code below variable toTx will contain interlieved 4-bit graycode for transmitter to be sent. First of those 4 bits is never delayed, second bit is delayed 10 symbol, third 20 symbol and fourth 30 symbol. For example at THOR11 period of one symbol is 93ms, so fourth bit is delayed 2790ms, or 2,8 seconds. So each character on transmission is spread over time (...is this kind of time domain spread-spectrum technique?).
Interliever shift registers are numbered from 2 to 4...there was originally also interliever1, but I later realized it is not needed because first bit is never shifted. So first bit is taken from fecOut and it is directly written to toTx.
 
INTERLEAVER is simply bit shifter. or "delayer"

TO INTERLIEVER:

bitWrite(toTx,3, bitRead(fecOut,3)); No delay for 1.bit, it is goes straight to toTx
bitWrite(interleaver2, 0, bitRead(fecOut,2));delay 10 bits, or shifts
bitWrite(interleaver3, 0, bitRead(fecOut,1));delay 20 bits, or shifts
bitWrite(interleaver4, 0, bitRead(fecOut,0));delay 30 bits, or shifts
FROM INTERLIEVER:
toTx,3, is already filled
bitWrite(toTx,2,(bitRead(interleaver2,10)));
bitWrite(toTx,1,(bitRead(interleaver3,20)));
bitWrite(toTx,0,(bitRead(interleaver4,30)));
Interleaver is bitshifter, so lets shift it:
interleaver2= interleaver2 << 1;
interleaver3 = interleaver3 << 1;
interleaver4 = interleaver4 << 1;

// toTx is now ready to be transmitted, and it contains 4-bit gray-code, corresponding to tone difference for next symbol or "tone"
 
[[code]]
 



mark