Superior signal purity by modulation fractional PLL

master
SaucySoliton 2017-01-07 07:37:52 +00:00
rodzic 3a309befa1
commit 65cc5be41d
2 zmienionych plików z 301 dodań i 28 usunięć

Wyświetl plik

@ -11,8 +11,10 @@ This version modulates the PLL instead of the clock divider for superior signal
![](doc/spectrum.png) ![](doc/spectrum.png)
TODO list TODO list
watchdog for PLL settings (the GPU changes them sometimes, like brownout) watchdog for PLL settings (the GPU changes them sometimes, like brownout)
( force_turbo=1 may help prevent this) ( force_turbo=1 may help prevent this)
measure PLL loop filter response measure PLL loop filter response
It is based on the FM transmitter created by [Oliver Mattos and Oskar Weigl](http://www.icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter), and later adapted to using DMA by [Richard Hirst](https://github.com/richardghirst). Christophe Jacquet adapted it and added the RDS data generator and modulator. The transmitter uses the Raspberry Pi's PWM generator to produce VHF signals. It is based on the FM transmitter created by [Oliver Mattos and Oskar Weigl](http://www.icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter), and later adapted to using DMA by [Richard Hirst](https://github.com/richardghirst). Christophe Jacquet adapted it and added the RDS data generator and modulator. The transmitter uses the Raspberry Pi's PWM generator to produce VHF signals.

Wyświetl plik

@ -146,7 +146,7 @@
#define PWM_BASE_OFFSET 0x0020C000 #define PWM_BASE_OFFSET 0x0020C000
#define PWM_LEN 0x28 #define PWM_LEN 0x28
#define CLK_BASE_OFFSET 0x00101000 #define CLK_BASE_OFFSET 0x00101000
#define CLK_LEN 0xA8 #define CLK_LEN 0x1300
#define GPIO_BASE_OFFSET 0x00200000 #define GPIO_BASE_OFFSET 0x00200000
#define GPIO_LEN 0x100 #define GPIO_LEN 0x100
@ -170,9 +170,16 @@
#define PWMCLK_DIV 41 #define PWMCLK_DIV 41
#define CM_GP0DIV (0x7e101074) #define CM_GP0DIV (0x7e101074)
#define CM_PLLCFRAC (0x7e102220)
#define CORECLK_CNTL (0x08/4)
#define CORECLK_DIV (0x0c/4)
#define GPCLK_CNTL (0x70/4) #define GPCLK_CNTL (0x70/4)
#define GPCLK_DIV (0x74/4) #define GPCLK_DIV (0x74/4)
#define EMMCCLK_CNTL (0x1C0/4)
#define EMMCCLK_DIV (0x1C4/4)
#define PLLC_CTRL (0x1120/4)
#define PLLC_FRAC (0x1220/4)
#define PWMCTL_MODE1 (1<<1) #define PWMCTL_MODE1 (1<<1)
#define PWMCTL_PWEN1 (1<<0) #define PWMCTL_PWEN1 (1<<0)
@ -188,9 +195,10 @@
#define PLLFREQ 500000000. // PLLD is running at 500MHz #define PLLFREQ 500000000. // PLLD is running at 500MHz
// The deviation specifies how wide the signal is. Use 25.0 for WBFM // The deviation specifies how wide the signal is.
// (broadcast radio) and about 3.5 for NBFM (walkie-talkie style radio) // Use 75kHz for WBFM (broadcast radio)
#define DEVIATION 25.0 // and about 2.5kHz for NBFM (walkie-talkie style radio)
#define DEVIATION 75000
typedef struct { typedef struct {
@ -226,6 +234,135 @@ struct control_data_s {
static struct control_data_s *ctl; static struct control_data_s *ctl;
static void
print_clock_tree(void)
{
if( clk_reg==NULL ) return;
#define PLLA_CTRL (0x1100/4)
#define PLLA_FRAC (0x1200/4)
#define PLLA_DSI0 (0x1300/4)
#define PLLA_CORE (0x1400/4)
#define PLLA_PER (0x1500/4)
#define PLLA_CCP2 (0x1600/4)
#define PLLB_CTRL (0x11e0/4)
#define PLLB_FRAC (0x12e0/4)
#define PLLB_ARM (0x13e0/4)
#define PLLB_SP0 (0x14e0/4)
#define PLLB_SP1 (0x15e0/4)
#define PLLB_SP2 (0x16e0/4)
#define PLLC_CTRL (0x1120/4)
#define PLLC_FRAC (0x1220/4)
#define PLLC_CORE2 (0x1320/4)
#define PLLC_CORE1 (0x1420/4)
#define PLLC_PER (0x1520/4)
#define PLLC_CORE0 (0x1620/4)
#define PLLD_CTRL (0x1140/4)
#define PLLD_FRAC (0x1240/4)
#define PLLD_DSI0 (0x1340/4)
#define PLLD_CORE (0x1440/4)
#define PLLD_PER (0x1540/4)
#define PLLD_DSI1 (0x1640/4)
#define PLLH_CTRL (0x1160/4)
#define PLLH_FRAC (0x1260/4)
#define PLLH_AUX (0x1360/4)
#define PLLH_RCAL (0x1460/4)
#define PLLH_PIX (0x1560/4)
#define PLLH_STS (0x1660/4)
#define XOSC_CTRL (0x1190/4)
printf("PLLC_DIG0=%08x\n",clk_reg[(0x1020/4)]);
printf("PLLC_DIG1=%08x\n",clk_reg[(0x1024/4)]);
printf("PLLC_DIG2=%08x\n",clk_reg[(0x1028/4)]);
printf("PLLC_DIG3=%08x\n",clk_reg[(0x102c/4)]);
printf("PLLC_ANA0=%08x\n",clk_reg[(0x1030/4)]);
printf("PLLC_ANA1=%08x\n",clk_reg[(0x1034/4)]);
printf("PLLC_ANA2=%08x\n",clk_reg[(0x1038/4)]);
printf("PLLC_ANA3=%08x\n",clk_reg[(0x103c/4)]);
printf("PLLC_DIG0R=%08x\n",clk_reg[(0x1820/4)]);
printf("PLLC_DIG1R=%08x\n",clk_reg[(0x1824/4)]);
printf("PLLC_DIG2R=%08x\n",clk_reg[(0x1828/4)]);
printf("PLLC_DIG3R=%08x\n",clk_reg[(0x182c/4)]);
printf("GNRIC CTL=%08x DIV=%8x ",clk_reg[ 0],clk_reg[ 1]);
printf("VPU CTL=%08x DIV=%8x\n",clk_reg[ 2],clk_reg[ 3]);
printf("SYS CTL=%08x DIV=%8x ",clk_reg[ 4],clk_reg[ 5]);
printf("PERIA CTL=%08x DIV=%8x\n",clk_reg[ 6],clk_reg[ 7]);
printf("PERII CTL=%08x DIV=%8x ",clk_reg[ 8],clk_reg[ 9]);
printf("H264 CTL=%08x DIV=%8x\n",clk_reg[10],clk_reg[11]);
printf("ISP CTL=%08x DIV=%8x ",clk_reg[12],clk_reg[13]);
printf("V3D CTL=%08x DIV=%8x\n",clk_reg[14],clk_reg[15]);
printf("CAM0 CTL=%08x DIV=%8x ",clk_reg[16],clk_reg[17]);
printf("CAM1 CTL=%08x DIV=%8x\n",clk_reg[18],clk_reg[19]);
printf("CCP2 CTL=%08x DIV=%8x ",clk_reg[20],clk_reg[21]);
printf("DSI0E CTL=%08x DIV=%8x\n",clk_reg[22],clk_reg[23]);
printf("DSI0P CTL=%08x DIV=%8x ",clk_reg[24],clk_reg[25]);
printf("DPI CTL=%08x DIV=%8x\n",clk_reg[26],clk_reg[27]);
printf("GP0 CTL=%08x DIV=%8x ",clk_reg[28],clk_reg[29]);
printf("GP1 CTL=%08x DIV=%8x\n",clk_reg[30],clk_reg[31]);
printf("GP2 CTL=%08x DIV=%8x ",clk_reg[32],clk_reg[33]);
printf("HSM CTL=%08x DIV=%8x\n",clk_reg[34],clk_reg[35]);
printf("OTP CTL=%08x DIV=%8x ",clk_reg[36],clk_reg[37]);
printf("PCM CTL=%08x DIV=%8x\n",clk_reg[38],clk_reg[39]);
printf("PWM CTL=%08x DIV=%8x ",clk_reg[40],clk_reg[41]);
printf("SLIM CTL=%08x DIV=%8x\n",clk_reg[42],clk_reg[43]);
printf("SMI CTL=%08x DIV=%8x ",clk_reg[44],clk_reg[45]);
printf("SMPS CTL=%08x DIV=%8x\n",clk_reg[46],clk_reg[47]);
printf("TCNT CTL=%08x DIV=%8x ",clk_reg[48],clk_reg[49]);
printf("TEC CTL=%08x DIV=%8x\n",clk_reg[50],clk_reg[51]);
printf("TD0 CTL=%08x DIV=%8x ",clk_reg[52],clk_reg[53]);
printf("TD1 CTL=%08x DIV=%8x\n",clk_reg[54],clk_reg[55]);
printf("TSENS CTL=%08x DIV=%8x ",clk_reg[56],clk_reg[57]);
printf("TIMER CTL=%08x DIV=%8x\n",clk_reg[58],clk_reg[59]);
printf("UART CTL=%08x DIV=%8x ",clk_reg[60],clk_reg[61]);
printf("VEC CTL=%08x DIV=%8x\n",clk_reg[62],clk_reg[63]);
printf("PULSE CTL=%08x DIV=%8x ",clk_reg[100],clk_reg[101]);
printf("PLLT CTL=%08x DIV=????????\n",clk_reg[76]);
printf("DSI1E CTL=%08x DIV=%8x ",clk_reg[86],clk_reg[87]);
printf("DSI1P CTL=%08x DIV=%8x\n",clk_reg[88],clk_reg[89]);
printf("AVE0 CTL=%08x DIV=%8x\n",clk_reg[90],clk_reg[91]);
printf("SDC CTL=%08x DIV=%8x ",clk_reg[106],clk_reg[107]);
printf("ARM CTL=%08x DIV=%8x\n",clk_reg[108],clk_reg[109]);
printf("AVE0 CTL=%08x DIV=%8x ",clk_reg[110],clk_reg[111]);
printf("EMMC CTL=%08x DIV=%8x\n",clk_reg[112],clk_reg[113]);
// Sometimes calculated frequencies are off by a factor of 2
// ANA1 bit 14 may indicate that a /2 prescaler is active
printf("PLLA PDIV=%d NDIV=%d FRAC=%d ",(clk_reg[PLLA_CTRL]>>16) ,clk_reg[PLLA_CTRL]&0x3ff, clk_reg[PLLA_FRAC] );
printf(" %f MHz\n",19.2* ((float)(clk_reg[PLLA_CTRL]&0x3ff) + ((float)clk_reg[PLLA_FRAC])/((float)(1<<20))) );
printf("DSI0=%d CORE=%d PER=%d CCP2=%d\n\n",clk_reg[PLLA_DSI0],clk_reg[PLLA_CORE],clk_reg[PLLA_PER],clk_reg[PLLA_CCP2]);
printf("PLLB PDIV=%d NDIV=%d FRAC=%d ",(clk_reg[PLLB_CTRL]>>16) ,clk_reg[PLLB_CTRL]&0x3ff, clk_reg[PLLB_FRAC] );
printf(" %f MHz\n",19.2* ((float)(clk_reg[PLLB_CTRL]&0x3ff) + ((float)clk_reg[PLLB_FRAC])/((float)(1<<20))) );
printf("ARM=%d SP0=%d SP1=%d SP2=%d\n\n",clk_reg[PLLB_ARM],clk_reg[PLLB_SP0],clk_reg[PLLB_SP1],clk_reg[PLLB_SP2]);
printf("PLLC PDIV=%d NDIV=%d FRAC=%d ",(clk_reg[PLLC_CTRL]>>16) ,clk_reg[PLLC_CTRL]&0x3ff, clk_reg[PLLC_FRAC] );
printf(" %f MHz\n",19.2* ((float)(clk_reg[PLLC_CTRL]&0x3ff) + ((float)clk_reg[PLLC_FRAC])/((float)(1<<20))) );
printf("CORE2=%d CORE1=%d PER=%d CORE0=%d\n\n",clk_reg[PLLC_CORE2],clk_reg[PLLC_CORE1],clk_reg[PLLC_PER],clk_reg[PLLC_CORE0]);
printf("PLLD PDIV=%d NDIV=%d FRAC=%d ",(clk_reg[PLLD_CTRL]>>16) ,clk_reg[PLLD_CTRL]&0x3ff, clk_reg[PLLD_FRAC] );
printf(" %f MHz\n",19.2* ((float)(clk_reg[PLLD_CTRL]&0x3ff) + ((float)clk_reg[PLLD_FRAC])/((float)(1<<20))) );
printf("DSI0=%d CORE=%d PER=%d DSI1=%d\n\n",clk_reg[PLLD_DSI0],clk_reg[PLLD_CORE],clk_reg[PLLD_PER],clk_reg[PLLD_DSI1]);
printf("PLLH PDIV=%d NDIV=%d FRAC=%d ",(clk_reg[PLLH_CTRL]>>16) ,clk_reg[PLLH_CTRL]&0x3ff, clk_reg[PLLH_FRAC] );
printf(" %f MHz\n",19.2* ((float)(clk_reg[PLLH_CTRL]&0x3ff) + ((float)clk_reg[PLLH_FRAC])/((float)(1<<20))) );
printf("AUX=%d RCAL=%d PIX=%d STS=%d\n\n",clk_reg[PLLH_AUX],clk_reg[PLLH_RCAL],clk_reg[PLLH_PIX],clk_reg[PLLH_STS]);
}
static void static void
udelay(int us) udelay(int us)
{ {
@ -312,7 +449,7 @@ map_peripheral(uint32_t base, uint32_t len)
#define DATA_SIZE 5000 #define DATA_SIZE 5000
int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt, float ppm, char *control_pipe) { int tx(uint32_t carrier_freq, uint32_t divider, char *audio_file, uint16_t pi, char *ps, char *rt, float ppm, char *control_pipe) {
// Catch all signals possible - it is vital we kill the DMA engine // Catch all signals possible - it is vital we kill the DMA engine
// on process exit! // on process exit!
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
@ -349,25 +486,69 @@ int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt,
printf("virt_addr = %p\n", mbox.virt_addr); printf("virt_addr = %p\n", mbox.virt_addr);
uint32_t freq_ctl;
if( divider ) // PLL modulation
{
// switch the core over to PLLA
clk_reg[CORECLK_DIV] = (0x5a<<24) | (4<<12) ; // core div 4
udelay(100);
clk_reg[CORECLK_CNTL] = (0x5a<<24) | (1<<4) | (4); // run, src=PLLA
// switch the EMMC over to PLLD
// FIXME should also adjust divider to keep same frequency
// or possibly not if the gpu decides to change clocks due to low voltage
// TODO put clocks back to their original state when we are done?
int clktmp;
clktmp = clk_reg[EMMCCLK_CNTL];
clk_reg[EMMCCLK_CNTL] = (0xF0F&clktmp) | (0x5a<<24) ; // clear run
udelay(100);
clk_reg[EMMCCLK_CNTL] = (0xF00&clktmp) | (0x5a<<24) | (6); // src=PLLD
udelay(100);
clk_reg[EMMCCLK_CNTL] = (0xF00&clktmp) | (0x5a<<24) | (1<<4) | (6); // run , src=PLLD
// Adjust PLLC frequency
freq_ctl = (unsigned int)(((carrier_freq*divider)/19.2e6)*((double)(1<<20))) ; // todo PPM
clk_reg[PLLC_CTRL] = (0x5a<<24) | (0x21<<12) | (freq_ctl>>20 ); // integer part
freq_ctl&=0xFFFFF;
clk_reg[PLLC_FRAC] = (0x5a<<24) | (freq_ctl&0xFFFFC) ; // fractional part
udelay(1000);
// Program GPCLK integer division
clktmp = clk_reg[GPCLK_CNTL];
clk_reg[GPCLK_CNTL] = (0xF0F&clktmp) | (0x5a<<24) ; // clear run
udelay(100);
clk_reg[GPCLK_DIV] = (0x5a<<24) | (divider<<12);
udelay(100);
clk_reg[GPCLK_CNTL] = (0x5a<<24) | (5); // src=PLLC
udelay(100);
clk_reg[GPCLK_CNTL] = (0x5a<<24) | (1<<4) | (5); // run , src=PLLC
}
else // MASH modulation
{
// Program GPCLK to use MASH setting 1, so fractional dividers work
clk_reg[GPCLK_CNTL] = 0x5A << 24 | 6; // src 6 = PLLD
udelay(100);
clk_reg[GPCLK_CNTL] = 0x5A << 24 | 1 << 9 | 1 << 4 | 6;
// Calculate the frequency control word
// The fractional part is stored in the lower 12 bits
freq_ctl = ((float)(PLLFREQ / carrier_freq)) * ( 1 << 12 ); //PLLD=500MHz
}
// GPIO4 needs to be ALT FUNC 0 to output the clock // GPIO4 needs to be ALT FUNC 0 to output the clock
gpio_reg[GPFSEL0] = (gpio_reg[GPFSEL0] & ~(7 << 12)) | (4 << 12); gpio_reg[GPFSEL0] = (gpio_reg[GPFSEL0] & ~(7 << 12)) | (4 << 12);
// Program GPCLK to use MASH setting 1, so fractional dividers work
clk_reg[GPCLK_CNTL] = 0x5A << 24 | 6;
udelay(100);
clk_reg[GPCLK_CNTL] = 0x5A << 24 | 1 << 9 | 1 << 4 | 6;
ctl = (struct control_data_s *) mbox.virt_addr; ctl = (struct control_data_s *) mbox.virt_addr;
dma_cb_t *cbp = ctl->cb; dma_cb_t *cbp = ctl->cb;
uint32_t phys_sample_dst = CM_GP0DIV; uint32_t phys_sample_dst = divider ? CM_PLLCFRAC : CM_GP0DIV;
uint32_t phys_pwm_fifo_addr = PWM_PHYS_BASE + 0x18; uint32_t phys_pwm_fifo_addr = PWM_PHYS_BASE + 0x18;
// Calculate the frequency control word
// The fractional part is stored in the lower 12 bits
uint32_t freq_ctl = ((float)(PLLFREQ / carrier_freq)) * ( 1 << 12 );
for (int i = 0; i < NUM_SAMPLES; i++) { for (int i = 0; i < NUM_SAMPLES; i++) {
ctl->sample[i] = 0x5a << 24 | freq_ctl; // Silence ctl->sample[i] = 0x5a << 24 | freq_ctl; // Silence
// Write a frequency sample // Write a frequency sample
@ -405,12 +586,12 @@ int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt,
// //
// So we use the 'ppm' parameter to compensate for the oscillator error // So we use the 'ppm' parameter to compensate for the oscillator error
float divider = (500000./(2*228*(1.+ppm/1.e6))); float srdivider = (500000./(2*228*(1.+ppm/1.e6)));
uint32_t idivider = (uint32_t) divider; uint32_t idivider = (uint32_t) srdivider;
uint32_t fdivider = (uint32_t) ((divider - idivider)*pow(2, 12)); uint32_t fdivider = (uint32_t) ((srdivider - idivider)*pow(2, 12));
printf("ppm corr is %.4f, divider is %.4f (%d + %d*2^-12) [nominal 1096.4912].\n", printf("ppm corr is %.4f, divider is %.4f (%d + %d*2^-12) [nominal 1096.4912].\n",
ppm, divider, idivider, fdivider); ppm, srdivider, idivider, fdivider);
pwm_reg[PWM_CTL] = 0; pwm_reg[PWM_CTL] = 0;
udelay(10); udelay(10);
@ -480,6 +661,21 @@ int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt,
printf("Starting to transmit on %3.1f MHz.\n", carrier_freq/1e6); printf("Starting to transmit on %3.1f MHz.\n", carrier_freq/1e6);
float deviation_scale_factor;
if( divider ) // PLL modulation
{ // note samples are [-10:10]
deviation_scale_factor= 0.1 * (divider*DEVIATION/ (19.2e6/((double)(1<<20))) ) ; // todo PPM
}
else // MASH modulation
{
double normdivider=((double)PLLFREQ / (double)carrier_freq) * ( 1 << 12 ); //PLLD=500MHz
double lowdivider=((double)PLLFREQ / (((double)carrier_freq)+(DEVIATION))) * ( 1 << 12 );
deviation_scale_factor= 0.1 * (normdivider-lowdivider);
}
//printf("deviation_scale_factor = %f \n", deviation_scale_factor);
for (;;) { for (;;) {
// Default (varying) PS // Default (varying) PS
if(varying_ps) { if(varying_ps) {
@ -505,7 +701,6 @@ int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt,
int last_sample = (last_cb - (uint32_t)mbox.virt_addr) / (sizeof(dma_cb_t) * 2); int last_sample = (last_cb - (uint32_t)mbox.virt_addr) / (sizeof(dma_cb_t) * 2);
int this_sample = (cur_cb - (uint32_t)mbox.virt_addr) / (sizeof(dma_cb_t) * 2); int this_sample = (cur_cb - (uint32_t)mbox.virt_addr) / (sizeof(dma_cb_t) * 2);
int free_slots = this_sample - last_sample; int free_slots = this_sample - last_sample;
if (free_slots < 0) if (free_slots < 0)
free_slots += NUM_SAMPLES; free_slots += NUM_SAMPLES;
@ -519,15 +714,23 @@ int tx(uint32_t carrier_freq, char *audio_file, uint16_t pi, char *ps, char *rt,
data_index = 0; data_index = 0;
} }
float dval = data[data_index] * (DEVIATION / 10.); float dval = data[data_index]*deviation_scale_factor;
int intval;
if( divider ) // PLL modulation
{
intval = ( (int)((floor)(dval)) & ~0x3 ) ;
// clock settings from boot code seem to always leave 2 lsb clear
}
else // MASH modulation
{
intval = (int)((floor)(dval)) ;
}
data_index++; data_index++;
data_len--; data_len--;
int intval = (int)((floor)(dval));
//int frac = (int)((dval - (float)intval) * SUBSIZE);
ctl->sample[last_sample++] = (0x5A << 24 | freq_ctl) + intval; //(frac > j ? intval + 1 : intval); ctl->sample[last_sample++] = ( 0x5A << 24 | freq_ctl) + intval;
if (last_sample == NUM_SAMPLES) if (last_sample == NUM_SAMPLES)
last_sample = 0; last_sample = 0;
@ -547,6 +750,7 @@ int main(int argc, char **argv) {
char *ps = NULL; char *ps = NULL;
char *rt = "PiFmRds: live FM-RDS transmission from the RaspberryPi"; char *rt = "PiFmRds: live FM-RDS transmission from the RaspberryPi";
uint16_t pi = 0x1234; uint16_t pi = 0x1234;
uint32_t mash = -1;
float ppm = 0; float ppm = 0;
@ -580,14 +784,81 @@ int main(int argc, char **argv) {
} else if(strcmp("-ctl", arg)==0 && param != NULL) { } else if(strcmp("-ctl", arg)==0 && param != NULL) {
i++; i++;
control_pipe = param; control_pipe = param;
} else if(strcmp("-mash", arg)==0 ) {
i++;
mash=1;
} else { } else {
fatal("Unrecognised argument: %s.\n" fatal("Unrecognised argument: %s.\n"
"Syntax: pi_fm_rds [-freq freq] [-audio file] [-ppm ppm_error] [-pi pi_code]\n" "Syntax: pi_fm_rds [-freq freq] [-audio file] [-ppm ppm_error] [-pi pi_code]\n"
" [-ps ps_text] [-rt rt_text] [-ctl control_pipe]\n", arg); " [-ps ps_text] [-rt rt_text] [-ctl control_pipe] [-mash]\n", arg);
} }
} }
// Choose an integer divider for GPCLK0
//
// There may be improvements possible to this algorithm.
double xtal_freq_recip=1.0/19.2e6; // todo PPM correction
int best_divider=0;
// Optional loop to print tuning solutions for all FM frequencies
//for( carrier_freq=76000000 ; carrier_freq<108000000 ; carrier_freq+=100000 )
{
int solution_count=0;
printf("carrier:%3.2f ",carrier_freq/1e6);
int divider,min_int_multiplier,max_int_multiplier, fom, int_multiplier, best_fom=0;
double frac_multiplier;
best_divider=0;
for( divider=2;divider<20;divider+=1) // do odd dividers have worse 2nd harmonic?
{
if( carrier_freq*divider < 760e6 ) continue; // widest accepted frequency range
if( carrier_freq*divider > 1300e6 ) break;
max_int_multiplier=((int)((double)(carrier_freq+100000)*divider*xtal_freq_recip));
min_int_multiplier=((int)((double)(carrier_freq-100000)*divider*xtal_freq_recip));
if( min_int_multiplier!=max_int_multiplier ) continue; // don't cross integer boundary
solution_count++; // if we make it here the solution is acceptable,
fom=0; // but we want a good solution
if( carrier_freq*divider > 900e6 ) fom++; // prefer freqs closer to 1000
if( carrier_freq*divider < 1100e6 ) fom++;
if( carrier_freq*divider > 800e6 ) fom++; // accepted frequency range
if( carrier_freq*divider < 1200e6 ) fom++;
frac_multiplier=((double)(carrier_freq)*divider*xtal_freq_recip);
int_multiplier = (int) frac_multiplier;
frac_multiplier = frac_multiplier - int_multiplier;
if( (frac_multiplier>0.2) && (frac_multiplier<0.8) ) fom++; // prefer mulipliers away from integer boundaries
if( divider%2 == 0 ) fom+=2; // prefer even dividers
// do odd dividers have worse 2nd harmonic?
//printf(" multiplier:%f divider:%d VCO: %4.1fMHz\n",carrier_freq*divider*xtal_freq_recip,divider,(double)carrier_freq*divider/1e6);
if( fom > best_fom )
{
best_fom=fom;
best_divider=divider;
}
}
printf(" multiplier:%f divider:%d VCO: %4.1fMHz\n",carrier_freq*best_divider*xtal_freq_recip,best_divider,(double)carrier_freq*best_divider/1e6);
int errcode = tx(carrier_freq, audio_file, pi, ps, rt, ppm, control_pipe); if( solution_count==0)
printf("No tuning solution found. (76.8 and 96.0 are not supported.)\n");
}// Optional loop to print tuning solutions
if( best_divider==0) return -1 ;
if( mash!=-1 )
{
best_divider=0; // if divider=0, use MASH divider instead of PLL modulation
}
int errcode = tx(carrier_freq, best_divider, audio_file, pi, ps, rt, ppm, control_pipe);
terminate(errcode); terminate(errcode);
} }