/** * The data returned is coming in at 228000 from the fm / rds library. If we return 1000 samples per call * then we need to be called 228 times a second. * Here is what we need to do each pass * 1. Use the PiRds library to take in the file and prep for FM modulation (Add RDS & pilot tone & separate the stereo etc) * 2. FM Modulate using the GNURadio implementation of FM modulation. * 3. Upsample to a higher sample rate so we can have higher bandwidth using the method from http://www.dspguru.com/dsp/faqs/multirate/interpolation * 4. Frequency shift up to he appropriate location based on our current tuned frequency and the location of our station. */ int Transmitter::doWork() { TRACE("Entered Method"); // Only do work if our frequency is within the bandwidth of the tuner. TRACE("Checking if there is any reason to do work."); if (abs(centerFrequency - tunedFrequency) > 0.5 * MAX_OUTPUT_SAMPLE_RATE) { TRACE("Transmitter is not in tuned range. Returning all zeros."); basebandCmplxUpSampledTuned.resize(numSamples*10, std::complex<float>(0.0,0.0)); return 0; } else { TRACE("Receiving samples from fm_mpx_get_samples() for file: "); if( fm_mpx_get_samples(&mpx_buffer[0], &rds_content, &rds_sig_info, &fm_mpx_status_struct) < 0 ) { ERROR("Error occurred adding RDS data to sound file."); return -1; } TRACE("Scaling samples"); mpx_buffer /= 10.; TRACE("FM Modulating the real data"); fm.modulate(mpx_buffer, basebandCmplx); TRACE("Polyphase filtering for upsampling"); for (int i = 0; i < 10; ++i) { polyphaseFilters[i]->run(); for (int ii = 0; ii < basebandCmplx_polyPhaseout.size(); ++ii) { basebandCmplxUpSampled[i + 10*ii] = basebandCmplx_polyPhaseout[ii]; } } { boost::mutex::scoped_lock lock(tunerMutex); TRACE("Tuning to the relative frequency"); tuner.run(); } TRACE("Exited Method"); return 0; } }
/* Simple test program */ int main(int argc, char **argv) { if(argc < 4) { fprintf(stderr, "Error: missing argument.\n"); fprintf(stderr, "Syntax: rds_wav <in_audio.wav> <out_mpx.wav> <text>\n"); return EXIT_FAILURE; } // YLB Stuff is here struct rds_struct rds_status_struct; struct fm_mpx_struct fm_mpx_status_struct; fm_mpx_status_struct.phase_38 = 0; fm_mpx_status_struct.phase_19 = 0; fm_mpx_status_struct.audio_index = 0; fm_mpx_status_struct.audio_len = 0; fm_mpx_status_struct.fir_index = 0; unsigned int i; for (i = 0; i < FIR_SIZE; i++) { fm_mpx_status_struct.fir_buffer_mono[i] = 0; fm_mpx_status_struct.fir_buffer_stereo[i] = 0; } rds_status_struct.pi = 0x1234; set_rds_ps(argv[3], &rds_status_struct); set_rds_rt(argv[3], &rds_status_struct); // Done with YLB stuff //set_rds_ps(argv[3]); //set_rds_rt(argv[3]); char *in_file = argv[1]; if(strcmp("NONE", argv[1]) == 0) in_file = NULL; // if(fm_mpx_open(in_file, LENGTH) != 0) { if(fm_mpx_open(in_file, LENGTH, &fm_mpx_status_struct) != 0) { printf("Could not setup FM mulitplex generator.\n"); return EXIT_FAILURE; } // Set the format of the output file SNDFILE *outf; SF_INFO sfinfo; sfinfo.frames = LENGTH; sfinfo.samplerate = 228000; sfinfo.channels = 1; sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; sfinfo.sections = 1; sfinfo.seekable = 0; // Open the output file char *out_file = argv[2]; if (! (outf = sf_open(out_file, SFM_WRITE, &sfinfo))) { fprintf(stderr, "Error: could not open output file %s.\n", out_file); return EXIT_FAILURE; } float mpx_buffer[LENGTH]; for(int j=0; j<40; j++) { if( fm_mpx_get_samples(mpx_buffer, &rds_status_struct, &fm_mpx_status_struct) < 0 ) break; // scale samples for(int i=0; i<LENGTH; i++) { mpx_buffer[i] /= 10.; // printf("%f, ", mpx_buffer[i]); } exit(0); if(sf_write_float(outf, mpx_buffer, LENGTH) != LENGTH) { fprintf(stderr, "Error: writing to file %s.\n", argv[1]); return EXIT_FAILURE; } } if(sf_close(outf) ) { fprintf(stderr, "Error: closing file %s.\n", argv[1]); } fm_mpx_close(&fm_mpx_status_struct); return EXIT_SUCCESS; }
int tx(uint32_t carrier_freq, 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 // on process exit! for (int i = 0; i < 64; i++) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = terminate; sigaction(i, &sa, NULL); } dma_reg = map_peripheral(DMA_VIRT_BASE, DMA_LEN); pwm_reg = map_peripheral(PWM_VIRT_BASE, PWM_LEN); clk_reg = map_peripheral(CLK_VIRT_BASE, CLK_LEN); gpio_reg = map_peripheral(GPIO_VIRT_BASE, GPIO_LEN); // Use the mailbox interface to the VC to ask for physical memory. mbox.handle = mbox_open(); if (mbox.handle < 0) fatal("Failed to open mailbox. Check kernel support for vcio / BCM2708 mailbox.\n"); printf("Allocating physical memory: size = %d ", NUM_PAGES * 4096); if(! (mbox.mem_ref = mem_alloc(mbox.handle, NUM_PAGES * 4096, 4096, MEM_FLAG))) { fatal("Could not allocate memory.\n"); } // TODO: How do we know that succeeded? printf("mem_ref = %u ", mbox.mem_ref); if(! (mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref))) { fatal("Could not lock memory.\n"); } printf("bus_addr = %x ", mbox.bus_addr); if(! (mbox.virt_addr = mapmem(BUS_TO_PHYS(mbox.bus_addr), NUM_PAGES * 4096))) { fatal("Could not map memory.\n"); } printf("virt_addr = %p\n", mbox.virt_addr); // GPIO4 needs to be ALT FUNC 0 to output the clock 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; dma_cb_t *cbp = ctl->cb; uint32_t phys_sample_dst = CM_GP0DIV; 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++) { ctl->sample[i] = 0x5a << 24 | freq_ctl; // Silence // Write a frequency sample cbp->info = BCM2708_DMA_NO_WIDE_BURSTS | BCM2708_DMA_WAIT_RESP; cbp->src = mem_virt_to_phys(ctl->sample + i); cbp->dst = phys_sample_dst; cbp->length = 4; cbp->stride = 0; cbp->next = mem_virt_to_phys(cbp + 1); cbp++; // Delay cbp->info = BCM2708_DMA_NO_WIDE_BURSTS | BCM2708_DMA_WAIT_RESP | BCM2708_DMA_D_DREQ | BCM2708_DMA_PER_MAP(5); cbp->src = mem_virt_to_phys(mbox.virt_addr); cbp->dst = phys_pwm_fifo_addr; cbp->length = 4; cbp->stride = 0; cbp->next = mem_virt_to_phys(cbp + 1); cbp++; } cbp--; cbp->next = mem_virt_to_phys(mbox.virt_addr); // Here we define the rate at which we want to update the GPCLK control // register. // // Set the range to 2 bits. PLLD is at 500 MHz, therefore to get 228 kHz // we need a divisor of 500000 / 2 / 228 = 1096.491228 // // This is 1096 + 2012*2^-12 theoretically // // However the fractional part may have to be adjusted to take the actual // frequency of your Pi's oscillator into account. For example on my Pi, // the fractional part should be 1916 instead of 2012 to get exactly // 228 kHz. However RDS decoding is still okay even at 2012. // // So we use the 'ppm' parameter to compensate for the oscillator error float divider = (500000./(2*228*(1.+ppm/1.e6))); uint32_t idivider = (uint32_t) divider; uint32_t fdivider = (uint32_t) ((divider - idivider)*pow(2, 12)); printf("ppm corr is %.4f, divider is %.4f (%d + %d*2^-12) [nominal 1096.4912].\n", ppm, divider, idivider, fdivider); pwm_reg[PWM_CTL] = 0; udelay(10); clk_reg[PWMCLK_CNTL] = 0x5A000006; // Source=PLLD and disable udelay(100); // theorically : 1096 + 2012*2^-12 clk_reg[PWMCLK_DIV] = 0x5A000000 | (idivider<<12) | fdivider; udelay(100); clk_reg[PWMCLK_CNTL] = 0x5A000216; // Source=PLLD and enable + MASH filter 1 udelay(100); pwm_reg[PWM_RNG1] = 2; udelay(10); pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD; udelay(10); pwm_reg[PWM_CTL] = PWMCTL_CLRF; udelay(10); pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1; udelay(10); // Initialise the DMA dma_reg[DMA_CS] = BCM2708_DMA_RESET; udelay(10); dma_reg[DMA_CS] = BCM2708_DMA_INT | BCM2708_DMA_END; dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); dma_reg[DMA_DEBUG] = 7; // clear debug error flags dma_reg[DMA_CS] = 0x10880001; // go, mid priority, wait for outstanding writes uint32_t last_cb = (uint32_t)ctl->cb; // Data structures for baseband data float data[DATA_SIZE]; int data_len = 0; int data_index = 0; // Initialize the baseband generator if(fm_mpx_open(audio_file, DATA_SIZE) < 0) return 1; // Initialize the RDS modulator char myps[9] = {0}; set_rds_pi(pi); set_rds_rt(rt); uint16_t count = 0; uint16_t count2 = 0; int varying_ps = 0; if(ps) { set_rds_ps(ps); printf("PI: %04X, PS: \"%s\".\n", pi, ps); } else { printf("PI: %04X, PS: <Varying>.\n", pi); varying_ps = 1; } printf("RT: \"%s\"\n", rt); // Initialize the control pipe reader if(control_pipe) { if(open_control_pipe(control_pipe) == 0) { printf("Reading control commands on %s.\n", control_pipe); } else { printf("Failed to open control pipe: %s.\n", control_pipe); control_pipe = NULL; } } printf("Starting to transmit on %3.1f MHz.\n", carrier_freq/1e6); for (;;) { // Default (varying) PS if(varying_ps) { if(count == 512) { snprintf(myps, 9, "%08d", count2); set_rds_ps(myps); count2++; } if(count == 1024) { set_rds_ps("RPi-Live"); count = 0; } count++; } if(control_pipe && poll_control_pipe() == CONTROL_PIPE_PS_SET) { varying_ps = 0; } usleep(5000); uint32_t cur_cb = mem_phys_to_virt(dma_reg[DMA_CONBLK_AD]); 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 free_slots = this_sample - last_sample; if (free_slots < 0) free_slots += NUM_SAMPLES; while (free_slots >= SUBSIZE) { // get more baseband samples if necessary if(data_len == 0) { if( fm_mpx_get_samples(data) < 0 ) { terminate(0); } data_len = DATA_SIZE; data_index = 0; } float dval = data[data_index] * (DEVIATION / 10.); data_index++; 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); if (last_sample == NUM_SAMPLES) last_sample = 0; free_slots -= SUBSIZE; } last_cb = (uint32_t)mbox.virt_addr + last_sample * sizeof(dma_cb_t) * 2; } return 0; }