/*==============================================================================
=
par Silicium628
versions: voir plus bas dans la partie "const" - derniere mise à jour 15 avril 2009
================================================================================
Notes de versions:
la version 3.1 en Pascal 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.
================================================================================
*/
// #include <math.h>
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include "dm_lcd.c"
#define bouton_UP 0b00000100
#define bouton_DOWN 0b00001000
#define RAZ_4017 0b00000001
#define clk_4017 0b00000010
char * version = "1.4";
int mode; // = 1..5 pointeur vers longueur de sauts et numero de la LED à allumer.
int PAS; // 10M, 1M, 25, 50, 100 kHz ; longueur des sauts effectues avec le bouton rotatif
unsigned long int facteur_N; // facteur de division à envoyer à la PLL dans le registre "Programmable divider"
unsigned long int F_kHz;
int memo_pos_rot;
int pos_rot;
unsigned long int data; //D0..D13 + T0..1 Note: D0=LSB
unsigned int commande; //B0..B2 + TB + R0..2 + S Note: B0=LSB , S=MSB
void init_ports (void) // ports perso
// 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
{
PORTB = 0b00000000;
DDRB |= 0b00001000; // portB[3] = sortie (OC2)
DDRC &= 0b001111; //PC4 en entree (IR) PC5 en entree (ADC5)
DDRD = 0b11110011;
PORTD = 0b00001100; // active R de pullup sur PD2 et PD3 en entrées
}
void InitADC (void)
{
ADCSRA = _BV(ADEN) | _BV(ADPS2); // Activate ADC with Prescaler 16 --> 1Mhz/16 = 62.5kHz
ADMUX = 5; // Select pin ADC5 using MUX
}
void InitINTs (void)
/*
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= 0b01111010; // Timer2 utilisé. mode Fast PWM (WGM21,20 = 11); OC2 = sortie PWM voir p:115
TIMSK |= 0b00000000; // INT Timer2 comp disable; INT Timer2 overflow disable;
GICR |= 0b00000000; // gere les INTs voir page 67 du pdf
MCUCR |= 0b00000010; // The falling edge of INT0 generates an interrupt request. p:67 du pdf
}
void lcd_gotoxy_clrEOL (int x, int y)
// place le curseur en x,y et efface jusqu'a la fin de la ligne
{
lcd_gotoxy(x, y);
int i;
for (i=x; i<20; i++)
{ lcd_puts(" "); }
lcd_gotoxy(x, y);
}
void lcd_aff_nb (unsigned long int valeur, int nb_chiffres, int nb_decimales )
//affiche un nombre en representation decimale
// 266 octets
{
unsigned char r ;
char tbl[7];
unsigned i;
for (i=1; i<=nb_chiffres; i++)
{
r=48 + valeur % 10; // modulo (reste de la division)
valeur /= 10; // quotient
tbl[i]=r;
};
for (i=1; i<=nb_chiffres; i++)
{
if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
lcd_putc(tbl[nb_chiffres +1 -i]);
}
}
void lcd_aff_bin (unsigned long int valeur, int nb_digits)
//affiche un nombre en representation binaire
// 16 bits max
{
unsigned char r ;
char tbl[17];
unsigned i;
for (i=1; i<=nb_digits; i++)
{
r= 48 + valeur % 2; // modulo (reste de la division)
valeur /= 2; // quotient
tbl[i]=r;
};
for (i=1; i<=nb_digits; i++)
{
lcd_putc(tbl[nb_digits +1 -i]);
}
}
void calcul_PAS(void)
{
switch (mode)
{
case (1): PAS= 25 ;
break;
case (2): PAS= 50;
break;
default: PAS= 100;
break;
}
}
void init_variables(void)
{
mode= 3;
calcul_PAS();
facteur_N= 1440; // pour F_out = 100 * 1440 = 144000 kHz = 144 MHz
}
void clk_PLL(void) // sur pin CL
{
_delay_us(10);
PORTD |= 0b01000000;
_delay_us(10);
PORTD &= 0b10111111;
_delay_us(10);
}
void out_PLL(void) // 24 bits vers LM7001; voir datasheet LM7001
{
int n;
//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)
switch (PAS)
{
case (10) : commande = 0b10010000; //S=1 (FM-IN); R2..0=001 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
break;
case (25) : commande = 0b10100000; //S=1 (FM-IN); R2..0=010 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
break;
case (50) : commande = 0b11000000; //S=1 (FM-IN); R2..0=100 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
break;
case (100) : commande = 0b10000000; //S=1 (FM-IN); R2..0=000 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
break;
default: {}
}
//envoi de la frequence
PORTD &= 0b10111111; // CL = 0
// active_CE; (pin CE du LM7001)
PORTD |= 0b00100000; // ENB=1 (active le transfert entre le uC et le MC145170)
unsigned long int masque;
for (n=0; n<= 13; n++) // on sort le LSB en premier
{
masque = (1 << n); // masque = 00000000000001 .. 10000000000000 le '1' se deplacant de droite a gauche, 14 bits en tout
// if bit(facteur_N, n)
if ( (facteur_N & masque) != 0) PORTD |= 0b10000000;
else PORTD &= 0b01111111;
clk_PLL();
}
PORTD &= 0b01111111; // T0 (toujours =0)
clk_PLL();
PORTD &= 0b01111111; // T1 (toujours =0)
clk_PLL();
// envoi de la commande
for (n=0; n<=7; n++) // 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!
masque = (1 << n);
// if bit(commande, n)
if (commande & masque) PORTD |= 0b10000000;
else PORTD &= 0b01111111;
clk_PLL();
}
// desactive_CE;
PORTD &= 0b11011111; // ENB=0 (desactive ENB/ ce qui effectue le transfert dans les registres internes)
}
void calcul_Frequence(void)
{
F_kHz=facteur_N * PAS;
}
void affiche_facteur_N (void)
{
lcd_gotoxy_clrEOL(0, 3);
lcd_aff_nb(facteur_N, 4, 0);
}
void affiche_frequence(void)
{
calcul_Frequence();
lcd_gotoxy_clrEOL (4, 0);
lcd_aff_nb(F_kHz ,6, 3);
lcd_puts("MHz");
}
void Affiche_OCR2(void)
{
lcd_gotoxy_clrEOL (6, 3);
lcd_puts("OCR2= ");
lcd_aff_nb(OCR2, 3, 0);
}
void affiche_mode(void)
{
lcd_gotoxy(8, 1);
switch (mode)
{
case (1) : lcd_puts("25 kHz ");
break;
case (2) : lcd_puts("50 kHz ");
break;
case (3): lcd_puts("100 kHz");
break;
case (4) : lcd_puts("1 MHz ");
break;
case (5) : lcd_puts("10 MHz ");
break;
default: {}
}
// LCDclrEOL;
lcd_gotoxy(11, 2);
// LCDclrEOL;
lcd_aff_nb(PAS ,3, 0);
lcd_puts("kHz");
}
void out_PWM(void)
//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
{
int v1;
//cette partie commentee consomme 3000 octets! donc on evite!
// (en particulier la multiplication de deux float)
//je les remplace par une approche discrete qui consomme moin de 200 octets
// float a;
// float b;
// a= F_kHz / 1000;
// b= (a - 80) / 20;
// OCR2_real = b * b;
// // if (OCR2_real > 255) {OCR2_real= 255;}
// // // OCR2= floor(OCR2_real);
calcul_Frequence();
v1=5;
if (F_kHz > 95000) { v1= 11; }
if (F_kHz > 100000) { v1= 20; }
if (F_kHz > 105000) { v1= 31; }
if (F_kHz > 110000) { v1= 40; }
if (F_kHz > 115000) { v1= 58; }
if (F_kHz > 120000) { v1= 80; }
if (F_kHz > 125000) { v1= 101; }
if (F_kHz > 130000) { v1= 125; }
if (F_kHz > 133000) { v1= 140; }
if (F_kHz > 135000) { v1= 151; }
if (F_kHz > 137000) { v1= 162; }
if (F_kHz > 140000) { v1= 180; }
if (F_kHz > 143000) { v1= 198; }
if (F_kHz > 145000) { v1= 211; }
if (F_kHz > 150000) { v1= 245; }
OCR2=v1;
Affiche_OCR2();
}
void eteint_toutes_LED (void)
{
PORTD |= RAZ_4017;
_delay_us(10);
PORTD &= ~RAZ_4017;
_delay_us(10);
}
void allume_LED (int num)
{
int n;
eteint_toutes_LED();
num++;
for(n=1; n<num; n++)
{
PORTD |= clk_4017;
_delay_us(1);
PORTD &= ~clk_4017;
_delay_us(1);
}
}
void incremente_PAS(void)
{
switch (PAS)
{
case (25) :
PAS= 50;
facteur_N /= 2;
break;
case (50) :
PAS= 100;
facteur_N /= 2;
break;
}
// Affiche_mode;
affiche_frequence();
affiche_facteur_N();
out_PLL();
out_PWM();
}
void decremente_PAS(void)
{
switch (PAS)
{
case (100) :
PAS= 50;
facteur_N *= 2;
break;
case (50) :
PAS= 25;
facteur_N *= 2;
break;
}
// Affiche_mode;
affiche_frequence();
affiche_facteur_N();
out_PLL();
out_PWM();
}
void incremente_N (void)
// fonction appelee lorsqu'on tourne le selecteur rotatif
{
switch (mode)
{
case(4) :
facteur_N= facteur_N + 10;
if (facteur_N > 1600) { facteur_N= 1600; }
break;
case(5) :
facteur_N= facteur_N + 100;
if (facteur_N > 1600) { facteur_N= 1600; }
break;
default:
if (F_kHz < 160000) {facteur_N= facteur_N + 1;}
}
affiche_frequence();
affiche_facteur_N();
out_PLL();
out_PWM();
}
void decremente_N (void)
// fonction appelee lorsqu'on tourne le selecteur rotatif
{
switch (mode)
{
case(4) :
facteur_N= facteur_N - 10;
if (facteur_N < 900) {facteur_N= 900;}
break;
case(5) :
facteur_N= facteur_N - 100;
if (facteur_N < 900) { facteur_N= 900;}
break;
default:
if (F_kHz > 90000) { facteur_N= facteur_N - 1; }
}
affiche_frequence();
affiche_facteur_N();
out_PLL();
out_PWM();
}
void scrute_boutons(void)
// 336 octets
// 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
{
int n=0;
if ( (PIND & 0b00000100) == 0) {
switch (mode)
{
case(1) :
{
mode= 2;
allume_LED(mode);
incremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
affiche_mode();
_delay_ms(200);
n++;
//while (((PIND & 0b00000100 ) != 0) | (n < 10) ) {}
}
break;
case(2) :
{
mode= 3;
allume_LED(mode);
incremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
affiche_mode();
_delay_ms(200);
n++;
//while (((PIND & 0b00000100 ) != 0) | (n < 10) ) {}
}
break;
case(3) :
case(4) :
{
mode++;
allume_LED(mode);
affiche_mode();
_delay_ms(200);
}
default: {}
}
lcd_gotoxy(0, 1);
// lcd_puts (string(mode));
} else {} ;
if ( (PIND & 0b00001000) == 0) {
switch (mode) {
case(2) :
{
mode= 1;
allume_LED(mode);
decremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
affiche_mode();
_delay_ms(200);
n++;
// while (((PIND & 0b00001000 ) != 0) | (n < 10) ) {}
}
break;
case(3) :
{
mode= 2;
allume_LED(mode);
decremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
affiche_mode();
_delay_ms(200);
n++;
// while (((PIND & 0b00001000 ) != 0) | (n < 10) ) {}
}
break;
case(4):
case(5):
{
mode--;
allume_LED(mode);
affiche_mode();
_delay_ms(200);
n++;
// while (((PIND & 0b00001000 ) != 0) | (n < 10) ) {}
}
break;
default: {}
}
} else { }
if (mode <1) mode= 1; else {};
if (mode >5) mode=5; else {};
// allume_LED(mode);
}
void acqui_pos_rot (void)
// lit la position d'un potentiometre à resistances CMS discretes (63k au total)
{
unsigned long int acqui_ADC_rot;
ADCSRA |= _BV(ADSC); //Start conversion - resolution 10bits
while (ADCSRA & _BV(ADSC) ) {} // attend la fin de la converstion
acqui_ADC_rot = ADCW; // lit la value convertie
pos_rot= 19;
//la structure suivante en "if then" est moins gourmande en memoire qu'un case of.
if (acqui_ADC_rot > 200) { pos_rot= 18; }
if (acqui_ADC_rot > 300) { pos_rot= 17; }
if (acqui_ADC_rot > 450) { pos_rot= 16; }
if (acqui_ADC_rot > 550) { pos_rot= 15; }
if (acqui_ADC_rot > 630) { pos_rot= 14; }
if (acqui_ADC_rot > 670) { pos_rot= 13; }
if (acqui_ADC_rot > 700) { pos_rot= 12; }
if (acqui_ADC_rot > 735) { pos_rot= 11; }
if (acqui_ADC_rot > 755) { pos_rot= 10; }
if (acqui_ADC_rot > 775) { pos_rot= 9; }
if (acqui_ADC_rot > 790) { pos_rot= 8; }
if (acqui_ADC_rot > 810) { pos_rot= 7; }
if (acqui_ADC_rot > 825) { pos_rot= 6; }
if (acqui_ADC_rot > 838) { pos_rot= 5; }
if (acqui_ADC_rot > 850) { pos_rot= 4; }
if (acqui_ADC_rot > 859) { pos_rot= 3; }
if (acqui_ADC_rot > 868) { pos_rot= 2; }
if (acqui_ADC_rot > 874) { pos_rot= 1; }
if (acqui_ADC_rot > 880) { pos_rot= 0; }
// LCDxy(10, 3);
// LCDclrEOL;
// Write(LCDout, ByteToStr(pos_rot : 3));
}
int main (void)
{
init_variables();
init_ports();
InitADC();
allume_LED(mode);
InitINTs();
out_PLL();
lcd_init(LCD_DISP_ON);
lcd_clrscr();
lcd_home();
// lcd_puts("APE2008 version ");
lcd_puts(version);
_delay_ms(1000);
lcd_gotoxy (0, 0);
lcd_puts("Out ");
lcd_gotoxy (0, 1);
lcd_puts("SAUTS:");
lcd_gotoxy (0, 2);
lcd_puts("pas synth");
lcd_gotoxy (8, 3);
affiche_frequence();
affiche_mode();
//unsigned long int masque_de_test;
//int n1;
while(1)
{
scrute_boutons();
memo_pos_rot= pos_rot;
acqui_pos_rot();
int suite =1; // utilise comme boolean. ah ce C peu type!
// oui bon l'instruction if du C est invraissemblablement confuse pour quelqu'un qui connait le Pascal !
// (en particulier les variantes avec if elsif endif)
// d'ou l'utilisation de ce drapeau. C'est moins pire que des goto quand meme!
if (pos_rot != memo_pos_rot) // si le selecteur a bouge
{
//quand le selecteur rotatif passe de 19 a 0, il faut continuer a incrementer et vice-versa
if ( (memo_pos_rot == 0) & (pos_rot == 19) ) { decremente_N(); suite =0; }
if ( (memo_pos_rot == 19) & (pos_rot == 0) ) { incremente_N(); suite =0; }
//le reste du temps l'incrementation va dans le sens des valeurs croissantes
if (suite)
{
if (pos_rot > memo_pos_rot) { incremente_N(); }
if (pos_rot < memo_pos_rot) {decremente_N(); }
}
}
_delay_ms(100);
//=============== pour TEST =================
// for (n1=0; n1<= 13; n1++) // on sort le LSB en premier
// {
// masque_de_test = (1 << n1);
//
// lcd_gotoxy_clrEOL (0, 1);
// lcd_aff_bin(facteur_N,16);
//
// lcd_gotoxy_clrEOL (0, 2);
// lcd_aff_bin(masque_de_test,16);
//
// lcd_gotoxy_clrEOL (0, 3);
// lcd_aff_bin(facteur_N & masque_de_test,16);
//
// lcd_gotoxy (19, 3);
//
// if (facteur_N & masque_de_test) // != 0)
// lcd_puts("i");
// else
// lcd_puts(" ");
//
// _delay_ms(1000);
// }
//=======================================
}
}