program GeneHF_160MHz_LM7001;
{===============================================================================
par Silicium628
versions: voir plus bas dans la partie "const" - derniere mise à jour 3 avril 2009
================================================================================
Notes de versions:
la version 3.1 depassait en taille les 8 ko max de l'ATmega8 si compilee en mode normal
ET la compilation en mode 'make and optimise' semble boguee: il se produit des erreurs a l'execution
================================================================================
Pilotage de la PLL integree LM7001 en mode FM, pas = 100, 50, et 25kHz
-Fréquences synthétisées 96 à 160 MHz avec la même self dans le VCO
-Affichage LCD 2 x 20  caractères ("splitté entre deux ports de l'ATmega)
-Sélections rapide des fréquences par bouton rotatif ('en continu')
-pas de synthese sélectionnable = 100, 50, et 25kHz
-sauts en frequences de 10MHz, 1MHz, 100kHz, 50kHz et 25kHzpar rotation du bouton pas-a-pas

  F_kHz := Facteur_N * PAS;   (Facteur_N est le coeficient de division de la frequence envoyé à la PLL)

on definit 5 modes de fonctionnement du bouton rotatif (variable 'mode' et numero de la LED allumee)
à chaque mode correspond une longueur de saut de la frequence de sortie ainsi qu'un PAS de synthese
suivant le tableau suivant:

MODE       SAUT    PAS
1          25kHz   25kHz
2          50kHz   50kHz
3          100kHz  100kHz
4          1MHz    100kHz
5          10MHz   100kHz

La diode varicap du VCO est alimenté par la combinaison de deux tensions:
-un première tension genérée classiquement par le comparateur de phase de la PLL apres filtre passe-bas
-une seconde tension generée par l'ATmega (par integration d'un signal PWM [modulation à largeur d'impultion])
afin de centrer en permanence la fenêtre de capture de la PLL sur la fréquence désirée, ce aui permet
d'étendre considérablement la plage de fréquences couvertes.

================================================================================
}

{ $W+ Warnings}            {Warnings off}

Device = mega8, VCC = 5;

Import SysTick, LCDport, ADCPort;


From System Import float;

Define
  ProcClock      = 16000000;        {Hertz}
  SysTick        = 10, Timer0;     {msec}   // ATTENTION: nécessaire pour l'horloge RTC  et LCD

  StackSize      = 128, iData;     // (voir affi en runtime)
  FrameSize      = 128, iData;

  LCDport        = PortB, 0, PortC, 0;  // control port, bit, Data port, bit  ;  voir le docuDriver.pdf (3.4.3 LCD-Split)
//The first parameter defines the control port with its first used bit. The order of the bits are fixed but
//different to the non-split driver: RS, RW, E (, Enable2 optional).
  LCDtype        = 44780;
  LCDrows        = 4;               {rows}
  LCDcolumns     = 20;              {columns per line}

  ADCchans       = [6], iData; {use only 1 Channel, ADC5}
  ADCpresc       = 16;

  Define_usr
  bouton_UP      =  %00000100;
  bouton_DOWN    =  %00001000;
  RAZ_4017       =  %00000001;
  CLK_4017       =  %00000010;

Implementation

//==============================================================================
{$IDATA}

type


const
  version        : string    = '3.2';


{ Type Declarations }


//==============================================================================
{$IDATA}

var
//  delay          : SysTimer8;
//  ticked         : boolean;

  rxAdr          : byte;
  rxCmd          : byte;
  adr1           : integer;
  cmd1           : integer;

  mode           : byte;  // = 1..5  pointeur vers longueur de sauts et numero de la LED à allumer.
  PAS            : word;  // 10M, 1M, 25, 50, 100 kHz  ; longueur des sauts effectues avec le bouton rotatif
  facteur_N      : word;  // facteur de division à envoyer à la PLL dans le registre "Programmable divider"
  F_kHz          : longword;

  memo_pos_rot   : byte;
  pos_rot        : byte;

  OCR2_real      : float;

  data           : word; //D0..D13 + T0..1             Note: D0=LSB
  commande       : byte; //B0..B2 + TB + R0..2 + S     Note: B0=LSB , S=MSB

{--------------------------------------------------------------}
{functions }

procedure init_ports;  // ports perso
// 0 = entree, 1=sortie   ;   les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
begin
  PortB:= %00000000;
  DDRB:= DDRB or %00001000;    // portB[3] = sortie (OC2)


  DDRC:= DDRC and %001111;     //PC4 en entree  (IR)  PC5 en entree (ADC5)

  DDRD:=  %11110011;
  portD:= %00001100;           // active R de pullup sur PD2 et PD3 en entrées
end;


procedure InitINTs;
begin
{  TCCR2:
wgm21,20 =11 ->Fast PMW
com21,com20=01 ->Set OC2 on Compare Match, clear OC2 at TOP (valable pour le mode Fast PWM);  voir p:116
bits2,1,0:  prescaler (010 = 1/8)
}
//               div
  TCCR2:= %01111 010;  // Timer2 utilisé. mode Fast PWM (WGM21,20 = 11); OC2 = sortie PWM   voir p:115

  TIMSK := TIMSK or  %00000000;  // INT Timer2 comp disable; INT Timer2 overflow disable;
  GICR  := GICR  or  %00000000;  // gere les INTs voir page 67 du pdf
  MCUCR := MCUCR or  %00000010;  // The falling edge of INT0 generates an interrupt request. p:67 du pdf
end;


procedure calcul_PAS;
begin
  case mode of
    1 : PAS:= 25;
      |
    2 : PAS:= 50;
      |
    3..5 : PAS:= 100;
      |
  endcase;
end;


procedure init_variables;
begin
  mode:= 3;
  calcul_PAS;
  facteur_N:= 1440;  // pour F_out = 100 * 1440 = 144000 kHz  = 144 MHz
end;


procedure clk_PLL;  // sur pin CL
begin
  udelay(1); // 10us
  portD:= portD or  %01000000;
  udelay(1); // 10us
  portD:= portD and %10111111;
  udelay(1); // 10us
end;


procedure out_PLL; // 24 bits vers LM7001; voir datasheet LM7001
var
  n              : byte;
begin
//calcul du mot de commande
// ligne ci-dessous = config pour pas=10kHz avec Qz=7.2MHz
// ATTENTION: sur le datasheet, les mots binaires sont tous écrit avec le LSB à GAUCHE. Merci  Sanyo!
// octet de commande =  S,R2,R1,R0,TB,B2,B1,B0  (écrit à l'endroit, LSB à DROITE)
  case PAS of
    10  : commande:= %1 001 0000; //S=1 (FM-IN); R2..0=001 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
        |
    25  : commande:= %1 010 0000; //S=1 (FM-IN); R2..0=010 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
        |
    50  : commande:= %1 100 0000; //S=1 (FM-IN); R2..0=100 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
        |
    100 : commande:= %1 000 0000; //S=1 (FM-IN); R2..0=000 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
        |
  endcase;

//envoi de la frequence
  portD:= portD and %10111111;  // CL = 0
//  active_CE;  (pin CE du LM7001)
  portD:= portD or  %00100000;  // ENB=1 (active le transfert entre le uC et le MC145170)
  for n:= 0 to 13 do   // on sort le LSB en premier
    if bit(facteur_N, n) then
      portD:= portD or  %10000000;
    else
      portD:= portD and %01111111;
    endif;
    clk_PLL;
  endfor;

  portD:= portD and %01111111;  // T0   (toujours =0)
  clk_PLL;
  portD:= portD and %01111111;  // T1   (toujours =0)
  clk_PLL;

// envoi de la commande
  for n:= 0 to 7 do   // on sort le LSB = B0 en premier et le MSB (S) en dernier.
// ATTENTION: sur le datasheet, les mots binaires sont tous écrit avec le LSB à GAUCHE. Merci  Sanyo!
    if bit(commande, n) then
      portD:= portD or  %10000000;
    else
      portD:= portD and %01111111;
    endif;
    clk_PLL;
  endfor;
// desactive_CE;
  portD:= portD and %11011111; // ENB=0 (desactive ENB/ ce qui effectue le transfert dans les registres internes)
end;


procedure calcul_Frequence;
begin
  F_kHz:= longword(facteur_N) * longword(PAS);
end;


procedure Affiche_facteur_N;
begin
  LCDxy(0, 3);
  LCDclrEOL;
  Write(LCDout, IntToStr(facteur_N : 3));
end;


procedure Affiche_Frequence;
begin
  calcul_Frequence;
  LCDxy(4, 0);
  LCDclrEOL;
  Write(LCDout, longToStr(F_kHz : 6 : 3) + ' MHz');
end;


procedure Affiche_mode;
var
  str_saut       : string[7];
begin
  case mode of
    1 : str_saut:= '25 kHz';
      |
    2 : str_saut:= '50 kHz';
      |
    3 : str_saut:= '100 kHz';
      |
    4 : str_saut:= '1 MHz';
      |
    5 : str_saut:= '10 MHz';
      |
  endcase;
  LCDxy(8, 1);
  LCDclrEOL;
  Write(LCDout, str_saut);

  LCDxy(11, 2);
  LCDclrEOL;
  Write(LCDout, IntToStr(PAS) + ' kHz');

end;



procedure out_PWM;
//le signal PWM sorti sur le pin OC2 est convertit en une tension continue (0..24V)
//qui permet de centrer la fenetre de capture de la PLL (VCO + LM7001) sur la frequence desirée
//La valeur est obtenue par la formule analytique OCR2 = 2*( F - 60 ) obtenue empiriquement
// voir la courbe F-f(OCR2) sur feuille de calcul Open Office jointe
var
  a              : float;
begin
  calcul_Frequence;
  a:= float(F_kHz) / 1000;
  OCR2_real:= sqr(a - 80) / 20;

if OCR2_real > 255 then OCR2_real:= 255; endif;

//  OCR2:=10;          // pour TEST accrochage de la PLL
//  mdelay(100);

  OCR2:= round(OCR2_real);

//  Affiche_OCR2;
end;


procedure eteint_toutes_LED;
begin
  portD:= portD or  RAZ_4017; // RAZ du CD4017  (éteint tout puisque les Q0 ne sont pas câblés)
  udelay(1);
  portD:= portD and not(RAZ_4017);
  udelay(1);
end;


procedure incremente_PAS;
begin
  case PAS of
    25 : PAS:= 50;
         facteur_N:= facteur_N div 2;
       |
    50 : PAS:= 100;
         facteur_N:= facteur_N div 2;
       |
  endcase;
//  Affiche_mode;
  Affiche_frequence;
  Affiche_facteur_N;
  out_PLL;
  out_PWM;
end;


procedure decremente_PAS;
begin
  case PAS of
    100 : PAS:= 50;
          facteur_N:= facteur_N * 2;
        |
    50  : PAS:= 25;
          facteur_N:= facteur_N * 2;
        |
  endcase;
//  Affiche_mode;
  Affiche_frequence;
  Affiche_facteur_N;
  out_PLL;
  out_PWM;

end;


procedure incremente_N;
begin
  case mode of
    4 : facteur_N:= facteur_N + 10;
      if facteur_N > 1600  then facteur_N:= 1600;  endif;
      |
    5 : facteur_N:= facteur_N + 100;
      if facteur_N > 1600  then facteur_N:= 1600;  endif;
      |
  else
  if F_kHz < 160000 then facteur_N:= facteur_N + 1; endif;
  endcase;
  Affiche_frequence;
  Affiche_facteur_N;
  
  out_PLL;
  out_PWM;
end;


procedure decremente_N;
begin
  case mode of
    4 : facteur_N:= facteur_N - 10;
      if facteur_N < 900  then facteur_N:= 900;  endif;
      |
    5 : facteur_N:= facteur_N - 100;
      if facteur_N < 900  then facteur_N:= 900;  endif;
      |
  else
  if F_kHz > 90000 then facteur_N:= facteur_N - 1; endif;
  endcase;
  Affiche_frequence;
  Affiche_facteur_N;
  
  out_PLL;
  out_PWM;
end;


procedure allume_LED(num : byte);
var
  n              : byte;
begin
  eteint_toutes_LED;
//  if num in [1..9] then
  
    for n:= 1 to num do
      portD:= portD or  CLK_4017; // CLK du CD4017
      udelay(1);
      portD:= portD and not(CLK_4017);
      udelay(1);
    endfor;
//  endif;
end;




procedure scrute_boutons;
var
  n              : byte;
// deux boutons poussoirs permettent de changer la longueur des sauts en frequences (que j'ai appelé "mode")
// met le mode à jour
// le tableau presente en preambule montre que le PAS doit changer entre les modes 1 et 2 ainsi que 2 et 3
begin
  n:= 0;
  if (PinD and bouton_UP ) = 0 then
    case mode of
      1    : mode:= 2;
             allume_LED(mode);
             incremente_PAS; // recalcule facteur_N en fonction du nouveau PAS
             affiche_mode;
             mdelay(200);
             inc(n);
           repeat until ((PinD and bouton_UP ) <> 0) or n > 10;
           |
      2    : mode:= 3;
             allume_LED(mode);
             incremente_PAS;
             affiche_mode;
             mdelay(200);
             inc(n);
           repeat until ((PinD and bouton_UP ) <> 0) or n > 10;
           |
      3..4 : inc(mode);
             allume_LED(mode);
             affiche_mode;
             mdelay(200);
             inc(n);
           repeat until ((PinD and bouton_UP ) <> 0) or n > 10;
           |
    endcase;

  elsif (PinD and bouton_DOWN ) = 0 then
    case mode of
      2    :  mode:= 1;
             allume_LED(mode);
             decremente_PAS; // recalcule facteur_N en fonction du nouveau PAS
             affiche_mode;
             mdelay(200);
             inc(n);
           repeat until ((PinD and bouton_DOWN ) <> 0) or n > 10;
           |
      3    :  mode:= 2;
             allume_LED(mode);
             decremente_PAS;
             affiche_mode;
             mdelay(200);
             inc(n);
           repeat until ((PinD and bouton_DOWN ) <> 0) or n > 10;
           |
      4..5 : dec(mode);

             allume_LED(mode);
             affiche_mode;
             mdelay(200);
             inc(n);
           repeat until ((PinD and bouton_DOWN ) <> 0) or n > 10;
           |
    endcase;
  endif;
if mode > 5 then mode:= 5; endif;

  // LCDxy(10, 3);
  // Write(LCDout, byteToStr(mode));  // pour TEST uniquement
end;



procedure acqui_pos_rot;
var
  acqui_ADC_rot  : word;
// lit la position d'un potentiometre à resistances CMS discretes  (63k au total)
begin
//  LCDclr;

  acqui_ADC_rot:= GetADC;  // resolution 10bits
  pos_rot:= 19;
//la structure suivante en "if then" est moins gourmande en memoire qu'un case of.

if acqui_ADC_rot > 200 then pos_rot:= 18; endif;
if acqui_ADC_rot > 300 then pos_rot:= 17; endif;
if acqui_ADC_rot > 450 then pos_rot:= 16; endif;
if acqui_ADC_rot > 550 then pos_rot:= 15; endif;
if acqui_ADC_rot > 630 then pos_rot:= 14; endif;
if acqui_ADC_rot > 670 then pos_rot:= 13; endif;
if acqui_ADC_rot > 700 then pos_rot:= 12; endif;
if acqui_ADC_rot > 735 then pos_rot:= 11; endif;
if acqui_ADC_rot > 755 then pos_rot:= 10; endif;
if acqui_ADC_rot > 775 then pos_rot:= 9; endif;
if acqui_ADC_rot > 790 then pos_rot:= 8; endif;
if acqui_ADC_rot > 810 then pos_rot:= 7; endif;
if acqui_ADC_rot > 825 then pos_rot:= 6; endif;
if acqui_ADC_rot > 838 then pos_rot:= 5; endif;
if acqui_ADC_rot > 850 then pos_rot:= 4; endif;
if acqui_ADC_rot > 859 then pos_rot:= 3; endif;
if acqui_ADC_rot > 868 then pos_rot:= 2; endif;
if acqui_ADC_rot > 874 then pos_rot:= 1; endif;
if acqui_ADC_rot > 880 then pos_rot:= 0; endif;


//  LCDxy(10, 3);
//  LCDclrEOL;
//  Write(LCDout, ByteToStr(pos_rot : 3));
end;




//==============================================================================
{ Main Program }
{$IDATA}

begin
  init_variables;
  init_ports;
  allume_LED(mode);
  InitINTs;
  out_PLL;

//  ticked := true;
  LCDclr;                                  { clear display }
  LCDcursor(false, false);                 { display on, cursor off & no blink }
//  Write(LCDout, 'RESET');

//  mdelay(300);
//  LCDclr;
  Write(LCDout, 'GeneHF 160MHz ' + version );
  mdelay(1000);
  LCDclr;

  LCDxy(0, 0);
  Write(LCDout, 'Out');
  LCDxy(0, 1);
  Write(LCDout, 'SAUTS:');
  LCDxy(0, 2);
  Write(LCDout, 'pas synth');

  Affiche_frequence;
  affiche_mode;

  EnableInts;

  loop
    scrute_boutons; // boutons poussoirs

    memo_pos_rot:= pos_rot;
    acqui_pos_rot;

    if    (memo_pos_rot = 0) and (pos_rot = 19) then decremente_N;
    elsif (memo_pos_rot = 19) and (pos_rot = 0) then incremente_N;
    else
    if pos_rot > memo_pos_rot then  incremente_N; endif;
    if pos_rot < memo_pos_rot then  decremente_N; endif;
    endif;


    mdelay(100);
  endloop;

end GeneHF_160MHz_LM7001.




