diff --git a/README b/README.md similarity index 93% rename from README rename to README.md index c0424d9..1cd5963 100644 --- a/README +++ b/README.md @@ -1,4 +1,4 @@ -Raspberry Pi bareback LF/MF/HF/VHF WSPR transmitter +# Raspberry Pi bareback LF/MF/HF/VHF WSPR transmitter Makes a very simple WSPR beacon from your RasberryPi by connecting GPIO port to Antenna (and LPF), operates on LF, MF, HF and VHF bands from @@ -7,22 +7,22 @@ port to Antenna (and LPF), operates on LF, MF, HF and VHF bands from Compatible with the original Raspberry Pi, the Raspberry Pi 2/3, and the Pi Zero. -!!!!!! -2017-04-21 -Do note that some users have been reporting lockups with recent OS versions. -I have not been able to reproduce the problems on my RPI1 and RPI3 running -the latest Jessie-Lite. -https://github.com/JamesP6000/WsprryPi/issues/6#issuecomment-296233932 -!!!!!! +This version uses https://github.com/F5OEO/librpitx +**You need to add gpu_freq=250 in /boot/config.txt. Without that transmiting is unstable"** + ****** Installation / update: ****** Download and compile code: sudo apt-get install git - git clone https://github.com/JamesP6000/WsprryPi.git + git clone https://github.com/F5OEO/WsprryPi.git cd WsprryPi + git clone https://github.com/F5OEO/librpitx.git + cd librpitx/src make + cd ../../ + make Install to /usr/local/bin: sudo make install @@ -176,14 +176,6 @@ Calibration: set correction by specifying --test-tone 780000 --ppm on the command line and confirming that the Pi is still zero beating the AM station. -****** -PWM Peripheral: -****** - The code uses the RPi PWM peripheral to time the frequency transitions - of the output clock. This peripheral is also used by the RPi sound system - and hence any sound events that occur during a WSPR transmission will - interfere with WSPR transmissions. Sound can be permanently disabled - by editing /etc/modules and commenting out the snd-bcm2835 device. ****** Example usage: @@ -248,6 +240,7 @@ Credits: Retzler AndrĂ¡s (HA7ILM) for the massive changes that were required to incorporate the mailbox code so that the RPi2 and RPi3 could be supported. + Evariste COURJAUD F5OEO : adapt to use librpitx and simplify code [1] PiFM code from http://www.icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter [2] Original WSPR Pi transmitter code by Dan: diff --git a/makefile b/makefile index 0a48b7a..b58801c 100644 --- a/makefile +++ b/makefile @@ -1,28 +1,19 @@ prefix=/usr/local -CFLAGS += -Wall -CXXFLAGS += -D_GLIBCXX_DEBUG -std=c++11 -Wall -Werror -fmax-errors=5 +CFLAGS += -Wall -Wno-unused-variable +CXXFLAGS += -Wall -Wall -Wno-unused-variable -std=c++11 LDLIBS += -lm -ifeq ($(findstring armv6,$(shell uname -m)),armv6) -# Broadcom BCM2835 SoC with 700 MHz 32-bit ARM 1176JZF-S (ARMv6 arch) -PI_VERSION = -DRPI1 -else -# Broadcom BCM2836 SoC with 900 MHz 32-bit quad-core ARM Cortex-A7 (ARMv7 arch) -# Broadcom BCM2837 SoC with 1.2 GHz 64-bit quad-core ARM Cortex-A53 (ARMv8 arch) -PI_VERSION = -DRPI23 -endif + all: wspr gpioclk -mailbox.o: mailbox.c mailbox.h - $(CC) $(CFLAGS) -c mailbox.c wspr: mailbox.o wspr.cpp mailbox.h - $(CXX) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $(PI_VERSION) mailbox.o wspr.cpp -owspr + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) wspr.cpp librpitx/src/librpitx.a -owspr gpioclk: gpioclk.cpp - $(CXX) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $(PI_VERSION) gpioclk.cpp -ogpioclk + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) gpioclk.cpp -ogpioclk clean: $(RM) *.o gpioclk wspr diff --git a/wspr.cpp b/wspr.cpp index a32f931..32658cc 100644 --- a/wspr.cpp +++ b/wspr.cpp @@ -17,6 +17,7 @@ // ha7ilm: added RPi2 support based on a patch to PiFmRds by Cristophe // Jacquet and Richard Hirst: http://git.io/vn7O9 +// F5OEO : adapt to librpitx for cleaner spectrum #include #include @@ -44,304 +45,44 @@ #include #include #include +#include "librpitx/src/librpitx.h" -#ifdef __cplusplus -extern "C" { -#include "mailbox.h" -} -#endif /* __cplusplus */ - - -// Note on accessing memory in RPi: -// -// There are 3 (yes three) address spaces in the Pi: -// Physical addresses -// These are the actual address locations of the RAM and are equivalent -// to offsets into /dev/mem. -// The peripherals (DMA engine, PWM, etc.) are located at physical -// address 0x2000000 for RPi1 and 0x3F000000 for RPi2/3. -// Virtual addresses -// These are the addresses that a program sees and can read/write to. -// Addresses 0x00000000 through 0xBFFFFFFF are the addresses available -// to a program running in user space. -// Addresses 0xC0000000 and above are available only to the kernel. -// The peripherals start at address 0xF2000000 in virtual space but -// this range is only accessible by the kernel. The kernel could directly -// access peripherals from virtual addresses. It is not clear to me my -// a user space application running as 'root' does not have access to this -// memory range. -// Bus addresses -// This is a different (virtual?) address space that also maps onto -// physical memory. -// The peripherals start at address 0x7E000000 of the bus address space. -// The DRAM is also available in bus address space in 4 different locations: -// 0x00000000 "L1 and L2 cached alias" -// 0x40000000 "L2 cache coherent (non allocating)" -// 0x80000000 "L2 cache (only)" -// 0xC0000000 "Direct, uncached access" -// -// Accessing peripherals from user space (virtual addresses): -// The technique used in this program is that mmap is used to map portions of -// /dev/mem to an arbitrary virtual address. For example, to access the -// GPIO's, the gpio range of addresses in /dev/mem (physical addresses) are -// mapped to a kernel chosen virtual address. After the mapping has been -// set up, writing to the kernel chosen virtual address will actually -// write to the GPIO addresses in physical memory. -// -// Accessing RAM from DMA engine -// The DMA engine is programmed by accessing the peripheral registers but -// must use bus addresses to access memory. Thus, to use the DMA engine to -// move memory from one virtual address to another virtual address, one needs -// to first find the physical addresses that corresponds to the virtual -// addresses. Then, one needs to find the bus addresses that corresponds to -// those physical addresses. Finally, the DMA engine can be programmed. i.e. -// DMA engine access should use addresses starting with 0xC. -// -// The perhipherals in the Broadcom documentation are described using their bus -// addresses and structures are created and calculations performed in this -// program to figure out how to access them with virtual addresses. +clkgpio *clk = NULL; +ngfmdmasync *ngfmtest = NULL; #define ABORT(a) exit(a) // Used for debugging #define MARK std::cout << "Currently in file: " << __FILE__ << " line: " << __LINE__ << std::endl - -// PLLD clock frequency. -// For RPi1, after NTP converges, these is a 2.5 PPM difference between -// the PPM correction reported by NTP and the actual frequency offset of -// the crystal. This 2.5 PPM offset is not present in the RPi2 and RPi3. -// This 2.5 PPM offset is compensated for here, but only for the RPi1. -#ifdef RPI23 -#define F_PLLD_CLK (500000000.0) -#else -#ifdef RPI1 -#define F_PLLD_CLK (500000000.0*(1-2.500e-6)) -#else -#error "RPI version macro is not defined" -#endif -#endif -// Empirical value for F_PWM_CLK that produces WSPR symbols that are 'close' to -// 0.682s long. For some reason, despite the use of DMA, the load on the PI -// affects the TX length of the symbols. However, the varying symbol length is -// compensated for in the main loop. -#define F_PWM_CLK_INIT (31156186.6125761) +typedef enum +{ + WSPR, + TONE +} mode_type; // WSRP nominal symbol time -#define WSPR_SYMTIME (8192.0/12000.0) +#define WSPR_SYMTIME (8192.0 / 12000.0) // How much random frequency offset should be added to WSPR transmissions // if the --offset option has been turned on. #define WSPR_RAND_OFFSET 80 #define WSPR15_RAND_OFFSET 8 -// Choose proper base address depending on RPI1/RPI23 macro from makefile. -// PERI_BASE_PHYS is the base address of the peripherals, in physical -// address space. -#ifdef RPI23 -#define PERI_BASE_PHYS 0x3f000000 -#define MEM_FLAG 0x04 -#else -#ifdef RPI1 -#define PERI_BASE_PHYS 0x20000000 -#define MEM_FLAG 0x0c -#else -#error "RPI version macro is not defined" -#endif -#endif - -#define PAGE_SIZE (4*1024) -#define BLOCK_SIZE (4*1024) - -// peri_base_virt is the base virtual address that a userspace program (this -// program) can use to read/write to the the physical addresses controlling -// the peripherals. This address is mapped at runtime using mmap and /dev/mem. -// This must be declared global so that it can be called by the atexit -// function. -volatile unsigned *peri_base_virt = NULL; - -// Given an address in the bus address space of the peripherals, this -// macro calculates the appropriate virtual address to use to access -// the requested bus address space. It does this by first subtracting -// 0x7e000000 from the supplied bus address to calculate the offset into -// the peripheral address space. Then, this offset is added to peri_base_virt -// Which is the base address of the peripherals, in virtual address space. -#define ACCESS_BUS_ADDR(buss_addr) *(volatile int*)((long int)peri_base_virt+(buss_addr)-0x7e000000) -// Given a bus address in the peripheral address space, set or clear a bit. -#define SETBIT_BUS_ADDR(base, bit) ACCESS_BUS_ADDR(base) |= 1<=mbox.pool_size) { - std::cerr << "Error: unable to allocated more pages!" << std::endl; - ABORT(-1); - } - unsigned offset = mbox.pool_cnt*4096; - *vAddr = (void*)(((unsigned)mbox.virt_addr) + offset); - *bAddr = (void*)(((unsigned)mbox.bus_addr) + offset); - //printf("getRealMemoryPageFromPool bus_addr=%x virt_addr=%x\n", (unsigned)*pAddr,(unsigned)*vAddr); - mbox.pool_cnt++; -} - -// Free the memory pool -void deallocMemPool() { - if(mbox.virt_addr!=NULL) { - unmapmem(mbox.virt_addr, mbox.pool_size*4096); - } - if (mbox.mem_ref!=0) { - mem_unlock(mbox.handle, mbox.mem_ref); - mem_free(mbox.handle, mbox.mem_ref); - } -} - // Disable the PWM clock and wait for it to become 'not busy'. -void disable_clock() { - // Check if mapping has been set up yet. - if (peri_base_virt==NULL) { - return; - } - // Disable the clock (in case it's already running) by reading current - // settings and only clearing the enable bit. - auto settings=ACCESS_BUS_ADDR(CM_GP0CTL_BUS); - // Clear enable bit and add password - settings=(settings&0x7EF)|0x5A000000; - // Disable - ACCESS_BUS_ADDR(CM_GP0CTL_BUS) = *((int*)&settings); - // Wait for clock to not be busy. - while (true) { - if (!(ACCESS_BUS_ADDR(CM_GP0CTL_BUS)&(1<<7))) { - break; - } - } +void disable_clock() +{ } // Turn on TX -void txon() { - // Set function select for GPIO4. - // Fsel 000 => input - // Fsel 001 => output - // Fsel 100 => alternate function 0 - // Fsel 101 => alternate function 1 - // Fsel 110 => alternate function 2 - // Fsel 111 => alternate function 3 - // Fsel 011 => alternate function 4 - // Fsel 010 => alternate function 5 - // Function select for GPIO is configured as 'b100 which selects - // alternate function 0 for GPIO4. Alternate function 0 is GPCLK0. - // See section 6.2 of Arm Peripherals Manual. - SETBIT_BUS_ADDR(GPIO_BUS_BASE , 14); - CLRBIT_BUS_ADDR(GPIO_BUS_BASE , 13); - CLRBIT_BUS_ADDR(GPIO_BUS_BASE , 12); - - // Set GPIO drive strength, more info: http://www.scribd.com/doc/101830961/GPIO-Pads-Control2 - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 0; //2mA -3.4dBm - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 1; //4mA +2.1dBm - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 2; //6mA +4.9dBm - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 3; //8mA +6.6dBm(default) - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 4; //10mA +8.2dBm - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 5; //12mA +9.2dBm - //ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 6; //14mA +10.0dBm - ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 7; //16mA +10.6dBm - - disable_clock(); +void txon() +{ - // Set clock source as PLLD. - struct GPCTL setupword = {6/*SRC*/, 0, 0, 0, 0, 3,0x5a}; + // ACCESS_BUS_ADDR(PADS_GPIO_0_27_BUS) = 0x5a000018 + 7; //16mA +10.6dBm - // Enable clock. - setupword = {6/*SRC*/, 1, 0, 0, 0, 3,0x5a}; - ACCESS_BUS_ADDR(CM_GP0CTL_BUS) = *((int*)&setupword); + disable_clock(); } // Turn transmitter on -void txoff() { - //struct GPCTL setupword = {6/*SRC*/, 0, 0, 0, 0, 1,0x5a}; - //ACCESS_BUS_ADDR(CM_GP0CTL_BUS) = *((int*)&setupword); +void txoff() +{ disable_clock(); } @@ -352,234 +93,39 @@ void txoff() { // do not know which DMA table entry is being processed by the DMA engine. #define PWM_CLOCKS_PER_ITER_NOMINAL 1000 void txSym( - const int & sym_num, - const double & center_freq, - const double & tone_spacing, - const double & tsym, - const std::vector & dma_table_freq, - const double & f_pwm_clk, - struct PageInfo instrs[], - struct PageInfo & constPage, - int & bufPtr -) { - const int f0_idx=sym_num*2; - const int f1_idx=f0_idx+1; - const double f0_freq=dma_table_freq[f0_idx]; - const double f1_freq=dma_table_freq[f1_idx]; - const double tone_freq=center_freq-1.5*tone_spacing+sym_num*tone_spacing; - // Double check... - assert((tone_freq>=f0_freq)&&(tone_freq<=f1_freq)); - const double f0_ratio=1.0-(tone_freq-f0_freq)/(f1_freq-f0_freq); - //cout << "f0_ratio = " << f0_ratio << std::endl; - assert ((f0_ratio>=0)&&(f0_ratio<=1)); - const long int n_pwmclk_per_sym=round(f_pwm_clk*tsym); - - long int n_pwmclk_transmitted=0; - long int n_f0_transmitted=0; - //printf("",(unsigned)&instrs[bufPtr]); - while (n_pwmclk_transmittedn_pwmclk_per_sym) { - n_pwmclk=n_pwmclk_per_sym-n_pwmclk_transmitted; - } - - // Calculate number of clocks to transmit f0 during this iteration so - // that the long term average is as close to f0_ratio as possible. - const long int n_f0=round(f0_ratio*(n_pwmclk_transmitted+n_pwmclk))-n_f0_transmitted; - const long int n_f1=n_pwmclk-n_f0; - - // Configure the transmission for this iteration - // Set GPIO pin to transmit f0 - bufPtr++; - while( ACCESS_BUS_ADDR(DMA_BUS_BASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].b)) usleep(100); - ((struct CB*)(instrs[bufPtr].v))->SOURCE_AD = (long int)constPage.b + f0_idx*4; - - // Wait for n_f0 PWM clocks - bufPtr++; - while( ACCESS_BUS_ADDR(DMA_BUS_BASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].b)) usleep(100); - ((struct CB*)(instrs[bufPtr].v))->TXFR_LEN = n_f0; - - // Set GPIO pin to transmit f1 - bufPtr++; - while( ACCESS_BUS_ADDR(DMA_BUS_BASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].b)) usleep(100); - ((struct CB*)(instrs[bufPtr].v))->SOURCE_AD = (long int)constPage.b + f1_idx*4; - - // Wait for n_f1 PWM clocks - bufPtr=(bufPtr+1) % (1024); - while( ACCESS_BUS_ADDR(DMA_BUS_BASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].b)) usleep(100); - ((struct CB*)(instrs[bufPtr].v))->TXFR_LEN = n_f1; - - // Update counters - n_pwmclk_transmitted+=n_pwmclk; - n_f0_transmitted+=n_f0; - } - //printf("",(unsigned)instrs[bufPtr].v,(unsigned)instrs[bufPtr].b); + const int &sym_num, + const double ¢er_freq, + const double &tone_spacing, + const double &tsym, + const std::vector &dma_table_freq, + const double &f_pwm_clk, + struct PageInfo instrs[], + struct PageInfo &constPage, + int &bufPtr) +{ } // Turn off (reset) DMA engine -void unSetupDMA(){ - // Check if mapping has been set up yet. - if (peri_base_virt==NULL) { - return; - } - //cout << "Exiting!" << std::endl; - struct DMAregs* DMA0 = (struct DMAregs*)&(ACCESS_BUS_ADDR(DMA_BUS_BASE)); - DMA0->CS =1<<31; // reset dma controller +void unSetupDMA() +{ + txoff(); } // Truncate at bit lsb. i.e. set all bits less than lsb to zero. double bit_trunc( - const double & d, - const int & lsb -) { - return floor(d/pow(2.0,lsb))*pow(2.0,lsb); -} - -// Program the tuning words into the DMA table. -void setupDMATab( - const double & center_freq_desired, - const double & tone_spacing, - const double & plld_actual_freq, - std::vector & dma_table_freq, - double & center_freq_actual, - struct PageInfo & constPage -){ - // Make sure that all the WSPR tones can be produced solely by - // varying the fractional part of the frequency divider. - center_freq_actual=center_freq_desired; - double div_lo=bit_trunc(plld_actual_freq/(center_freq_desired-1.5*tone_spacing),-12)+pow(2.0,-12); - double div_hi=bit_trunc(plld_actual_freq/(center_freq_desired+1.5*tone_spacing),-12); - if (floor(div_lo)!=floor(div_hi)) { - center_freq_actual=plld_actual_freq/floor(div_lo)-1.6*tone_spacing; - std::stringstream temp; - temp << std::setprecision(6) << std::fixed << " Warning: center frequency has been changed to " << center_freq_actual/1e6 << " MHz" << std::endl; - std::cout << temp.str(); - std::cout << " because of hardware limitations!" << std::endl; - } - - // Create DMA table of tuning words. WSPR tone i will use entries 2*i and - // 2*i+1 to generate the appropriate tone. - double tone0_freq=center_freq_actual-1.5*tone_spacing; - std::vector tuning_word(1024); - for (int i=0;i<8;i++) { - double tone_freq=tone0_freq+(i>>1)*tone_spacing; - double div=bit_trunc(plld_actual_freq/tone_freq,-12); - if (i%2==0) { - div=div+pow(2.0,-12); - } - tuning_word[i]=((int)(div*pow(2.0,12))); - } - // Fill the remaining table, just in case... - for (int i=8;i<1024;i++) { - double div=500+i; - tuning_word[i]=((int)(div*pow(2.0,12))); - } - - // Program the table - dma_table_freq.resize(1024); - for (int i=0;i<1024;i++) { - dma_table_freq[i]=plld_actual_freq/(tuning_word[i]/pow(2.0,12)); - ((int*)(constPage.v))[i] = (0x5a<<24)+tuning_word[i]; - if ((i%2==0)&&(i<8)) { - assert((tuning_word[i]&(~0xfff))==(tuning_word[i+1]&(~0xfff))); - } - } - -} - -// Create the memory structures needed by the DMA engine and perform initial -// clock configuration. -void setupDMA( - struct PageInfo & constPage, - struct PageInfo & instrPage, - struct PageInfo instrs[] -){ - allocMemPool(1025); - - // Allocate a page of ram for the constants - getRealMemPageFromPool(&constPage.v, &constPage.b); - - // Create 1024 instructions allocating one page at a time. - // Even instructions target the GP0 Clock divider - // Odd instructions target the PWM FIFO - int instrCnt = 0; - while (instrCnt<1024) { - // Allocate a page of ram for the instructions - getRealMemPageFromPool(&instrPage.v, &instrPage.b); - - // make copy instructions - // Only create as many instructions as will fit in the recently - // allocated page. If not enough space for all instructions, the - // next loop will allocate another page. - struct CB* instr0= (struct CB*)instrPage.v; - int i; - for (i=0; i<(signed)(4096/sizeof(struct CB)); i++) { - instrs[instrCnt].v = (void*)((long int)instrPage.v + sizeof(struct CB)*i); - instrs[instrCnt].b = (void*)((long int)instrPage.b + sizeof(struct CB)*i); - instr0->SOURCE_AD = (unsigned long int)constPage.b+2048; - instr0->DEST_AD = PWM_BUS_BASE+0x18 /* FIF1 */; - instr0->TXFR_LEN = 4; - instr0->STRIDE = 0; - //instr0->NEXTCONBK = (int)instrPage.b + sizeof(struct CB)*(i+1); - instr0->TI = (1/* DREQ */<<6) | (5 /* PWM */<<16) | (1<<26/* no wide*/) ; - instr0->RES1 = 0; - instr0->RES2 = 0; - - // Shouldn't this be (instrCnt%2) ??? - if (i%2) { - instr0->DEST_AD = CM_GP0DIV_BUS; - instr0->STRIDE = 4; - instr0->TI = (1<<26/* no wide*/) ; - } - - if (instrCnt!=0) ((struct CB*)(instrs[instrCnt-1].v))->NEXTCONBK = (long int)instrs[instrCnt].b; - instr0++; - instrCnt++; - } - } - // Create a circular linked list of instructions - ((struct CB*)(instrs[1023].v))->NEXTCONBK = (long int)instrs[0].b; - - // set up a clock for the PWM - ACCESS_BUS_ADDR(CLK_BUS_BASE + 40*4 /*PWMCLK_CNTL*/) = 0x5A000026; // Source=PLLD and disable - usleep(1000); - //ACCESS_BUS_ADDR(CLK_BUS_BASE + 41*4 /*PWMCLK_DIV*/) = 0x5A002800; - ACCESS_BUS_ADDR(CLK_BUS_BASE + 41*4 /*PWMCLK_DIV*/) = 0x5A002000; // set PWM div to 2, for 250MHz - ACCESS_BUS_ADDR(CLK_BUS_BASE + 40*4 /*PWMCLK_CNTL*/) = 0x5A000016; // Source=PLLD and enable - usleep(1000); - - // set up pwm - ACCESS_BUS_ADDR(PWM_BUS_BASE + 0x0 /* CTRL*/) = 0; - usleep(1000); - ACCESS_BUS_ADDR(PWM_BUS_BASE + 0x4 /* status*/) = -1; // clear errors - usleep(1000); - // Range should default to 32, but it is set at 2048 after reset on my RPi. - ACCESS_BUS_ADDR(PWM_BUS_BASE + 0x10)=32; - ACCESS_BUS_ADDR(PWM_BUS_BASE + 0x20)=32; - ACCESS_BUS_ADDR(PWM_BUS_BASE + 0x0 /* CTRL*/) = -1; //(1<<13 /* Use fifo */) | (1<<10 /* repeat */) | (1<<9 /* serializer */) | (1<<8 /* enable ch */) ; - usleep(1000); - ACCESS_BUS_ADDR(PWM_BUS_BASE + 0x8 /* DMAC*/) = (1<<31 /* DMA enable */) | 0x0707; - - //activate dma - struct DMAregs* DMA0 = (struct DMAregs*)&(ACCESS_BUS_ADDR(DMA_BUS_BASE)); - DMA0->CS =1<<31; // reset - DMA0->CONBLK_AD=0; - DMA0->TI=0; - DMA0->CONBLK_AD = (unsigned long int)(instrPage.b); - DMA0->CS =(1<<0)|(255 <<16); // enable bit = 0, clear end flag = 1, prio=19-16 + const double &d, + const int &lsb) +{ + return floor(d / pow(2.0, lsb)) * pow(2.0, lsb); } // Convert string to uppercase void to_upper( - char *str -) { - while(*str) { + char *str) +{ + while (*str) + { *str = toupper(*str); str++; } @@ -587,133 +133,157 @@ void to_upper( // Encode call, locator, and dBm into WSPR codeblock. void wspr( - const char* call, - const char* l_pre, - const char* dbm, - unsigned char* symbols -) { + const char *call, + const char *l_pre, + const char *dbm, + unsigned char *symbols) +{ // pack prefix in nadd, call in n1, grid, dbm in n2 - char* c, buf[16]; + char *c, buf[16]; strncpy(buf, call, 16); - c=buf; + c = buf; to_upper(c); - unsigned long ng,nadd=0; - - if(strchr(c, '/')){ //prefix-suffix - nadd=2; - int i=strchr(c, '/')-c; //stroke position - int n=strlen(c)-i-1; //suffix len, prefix-call len - c[i]='\0'; - if(n==1) ng=60000-32768+(c[i+1]>='0'&&c[i+1]<='9'?c[i+1]-'0':c[i+1]==' '?38:c[i+1]-'A'+10); // suffix /A to /Z, /0 to /9 - if(n==2) ng=60000+26+10*(c[i+1]-'0')+(c[i+2]-'0'); // suffix /10 to /99 - if(n>2){ // prefix EA8/, right align - ng=(i<3?36:c[i-3]>='0'&&c[i-3]<='9'?c[i-3]-'0':c[i-3]-'A'+10); - ng=37*ng+(i<2?36:c[i-2]>='0'&&c[i-2]<='9'?c[i-2]-'0':c[i-2]-'A'+10); - ng=37*ng+(i<1?36:c[i-1]>='0'&&c[i-1]<='9'?c[i-1]-'0':c[i-1]-'A'+10); - if(ng<32768) nadd=1; else ng=ng-32768; - c=c+i+1; + unsigned long ng, nadd = 0; + + if (strchr(c, '/')) + { // prefix-suffix + nadd = 2; + int i = strchr(c, '/') - c; // stroke position + int n = strlen(c) - i - 1; // suffix len, prefix-call len + c[i] = '\0'; + if (n == 1) + ng = 60000 - 32768 + (c[i + 1] >= '0' && c[i + 1] <= '9' ? c[i + 1] - '0' : c[i + 1] == ' ' ? 38 + : c[i + 1] - 'A' + 10); // suffix /A to /Z, /0 to /9 + if (n == 2) + ng = 60000 + 26 + 10 * (c[i + 1] - '0') + (c[i + 2] - '0'); // suffix /10 to /99 + if (n > 2) + { // prefix EA8/, right align + ng = (i < 3 ? 36 : c[i - 3] >= '0' && c[i - 3] <= '9' ? c[i - 3] - '0' + : c[i - 3] - 'A' + 10); + ng = 37 * ng + (i < 2 ? 36 : c[i - 2] >= '0' && c[i - 2] <= '9' ? c[i - 2] - '0' + : c[i - 2] - 'A' + 10); + ng = 37 * ng + (i < 1 ? 36 : c[i - 1] >= '0' && c[i - 1] <= '9' ? c[i - 1] - '0' + : c[i - 1] - 'A' + 10); + if (ng < 32768) + nadd = 1; + else + ng = ng - 32768; + c = c + i + 1; } } - int i=(isdigit(c[2])?2:isdigit(c[1])?1:0); //last prefix digit of de-suffixed/de-prefixed callsign - int n=strlen(c)-i-1; //2nd part of call len + int i = (isdigit(c[2]) ? 2 : isdigit(c[1]) ? 1 + : 0); // last prefix digit of de-suffixed/de-prefixed callsign + int n = strlen(c) - i - 1; // 2nd part of call len unsigned long n1; - n1=(i<2?36:c[i-2]>='0'&&c[i-2]<='9'?c[i-2]-'0':c[i-2]-'A'+10); - n1=36*n1+(i<1?36:c[i-1]>='0'&&c[i-1]<='9'?c[i-1]-'0':c[i-1]-'A'+10); - n1=10*n1+c[i]-'0'; - n1=27*n1+(n<1?26:c[i+1]-'A'); - n1=27*n1+(n<2?26:c[i+2]-'A'); - n1=27*n1+(n<3?26:c[i+3]-'A'); - - //if(rand() % 2) nadd=0; - if(!nadd){ + n1 = (i < 2 ? 36 : c[i - 2] >= '0' && c[i - 2] <= '9' ? c[i - 2] - '0' + : c[i - 2] - 'A' + 10); + n1 = 36 * n1 + (i < 1 ? 36 : c[i - 1] >= '0' && c[i - 1] <= '9' ? c[i - 1] - '0' + : c[i - 1] - 'A' + 10); + n1 = 10 * n1 + c[i] - '0'; + n1 = 27 * n1 + (n < 1 ? 26 : c[i + 1] - 'A'); + n1 = 27 * n1 + (n < 2 ? 26 : c[i + 2] - 'A'); + n1 = 27 * n1 + (n < 3 ? 26 : c[i + 3] - 'A'); + + // if(rand() % 2) nadd=0; + if (!nadd) + { // Copy locator locally since it is declared const and we cannot modify // its contents in-place. char l[4]; strncpy(l, l_pre, 4); - to_upper(l); //grid square Maidenhead locator (uppercase) - ng=180*(179-10*(l[0]-'A')-(l[2]-'0'))+10*(l[1]-'A')+(l[3]-'0'); + to_upper(l); // grid square Maidenhead locator (uppercase) + ng = 180 * (179 - 10 * (l[0] - 'A') - (l[2] - '0')) + 10 * (l[1] - 'A') + (l[3] - '0'); } - int p = atoi(dbm); //EIRP in dBm={0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60} - int corr[]={0,-1,1,0,-1,2,1,0,-1,1}; - p=p>60?60:p<0?0:p+corr[p%10]; - unsigned long n2=(ng<<7)|(p+64+nadd); + int p = atoi(dbm); // EIRP in dBm={0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60} + int corr[] = {0, -1, 1, 0, -1, 2, 1, 0, -1, 1}; + p = p > 60 ? 60 : p < 0 ? 0 + : p + corr[p % 10]; + unsigned long n2 = (ng << 7) | (p + 64 + nadd); // pack n1,n2,zero-tail into 50 bits char packed[11] = { - static_cast(n1>>20), - static_cast(n1>>12), - static_cast(n1>>4), - static_cast(((n1&0x0f)<<4)|((n2>>18)&0x0f)), - static_cast(n2>>10), - static_cast(n2>>2), - static_cast((n2&0x03)<<6), - 0, - 0, - 0, - 0 - }; + static_cast(n1 >> 20), + static_cast(n1 >> 12), + static_cast(n1 >> 4), + static_cast(((n1 & 0x0f) << 4) | ((n2 >> 18) & 0x0f)), + static_cast(n2 >> 10), + static_cast(n2 >> 2), + static_cast((n2 & 0x03) << 6), + 0, + 0, + 0, + 0}; // convolutional encoding K=32, r=1/2, Layland-Lushbaugh polynomials int k = 0; - int j,s; + int j, s; int nstate = 0; unsigned char symbol[176]; - for(j=0;j!=sizeof(packed);j++){ - for(i=7;i>=0;i--){ - unsigned long poly[2] = { 0xf2d05351L, 0xe4613c47L }; - nstate = (nstate<<1) | ((packed[j]>>i)&1); - for(s=0;s!=2;s++){ //convolve - unsigned long n = nstate & poly[s]; - int even = 0; // even := parity(n) - while(n){ - even = 1 - even; - n = n & (n - 1); - } - symbol[k] = even; - k++; + for (j = 0; j != sizeof(packed); j++) + { + for (i = 7; i >= 0; i--) + { + unsigned long poly[2] = {0xf2d05351L, 0xe4613c47L}; + nstate = (nstate << 1) | ((packed[j] >> i) & 1); + for (s = 0; s != 2; s++) + { // convolve + unsigned long n = nstate & poly[s]; + int even = 0; // even := parity(n) + while (n) + { + even = 1 - even; + n = n & (n - 1); } - } + symbol[k] = even; + k++; + } + } } // interleave symbols const unsigned char npr3[162] = { - 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0,0,1,0,1,1,1,1,0,0,0,0,0, - 0,0,1,0,0,1,0,1,0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1,1,0,1,0, - 0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1,0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0, - 0,0,1,0,0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1,0,1,0,0,0,1,1,1, - 0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, - 0,0 }; - for(i=0;i!=162;i++){ - // j0 := bit reversed_values_smaller_than_161[i] - unsigned char j0; - p=-1; - for(k=0;p!=i;k++){ - for(j=0;j!=8;j++) // j0:=bit_reverse(k) - j0 = ((k>>j)&1)|(j0<<1); - if(j0<162) - p++; - } - symbols[j0]=npr3[j0]|symbol[i]<<1; //interleave and add sync std::vector + 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 0}; + for (i = 0; i != 162; i++) + { + // j0 := bit reversed_values_smaller_than_161[i] + unsigned char j0; + p = -1; + for (k = 0; p != i; k++) + { + for (j = 0; j != 8; j++) // j0:=bit_reverse(k) + j0 = ((k >> j) & 1) | (j0 << 1); + if (j0 < 162) + p++; + } + symbols[j0] = npr3[j0] | symbol[i] << 1; // interleave and add sync std::vector } } // Wait for the system clock's minute to reach one second past 'minute' void wait_every( - int minute -) { + int minute) +{ time_t t; - struct tm* ptm; - for(;;){ + struct tm *ptm; + for (;;) + { time(&t); ptm = gmtime(&t); - if((ptm->tm_min % minute) == 0 && ptm->tm_sec == 0) break; + if ((ptm->tm_min % minute) == 0 && ptm->tm_sec == 0) + break; usleep(1000); } usleep(1000000); // wait another second } -void print_usage() { +void print_usage() +{ std::cout << "Usage:" << std::endl; std::cout << " wspr [options] callsign locator tx_pwr_dBm f1 ..." << std::endl; std::cout << " OR" << std::endl; @@ -754,179 +324,229 @@ void print_usage() { } void parse_commandline( - // Inputs - const int & argc, - char * const argv[], - // Outputs - std::string & callsign, - std::string & locator, - std::string & tx_power, - std::vector & center_freq_set, - double & ppm, - bool & self_cal, - bool & repeat, - bool & random_offset, - double & test_tone, - bool & no_delay, - mode_type & mode, - int & terminate -) { + // Inputs + const int &argc, + char *const argv[], + // Outputs + std::string &callsign, + std::string &locator, + std::string &tx_power, + std::vector ¢er_freq_set, + double &ppm, + bool &self_cal, + bool &repeat, + bool &random_offset, + double &test_tone, + bool &no_delay, + mode_type &mode, + int &terminate) +{ // Default values - ppm=0; - self_cal=true; - repeat=false; - random_offset=false; - test_tone=NAN; - no_delay=false; - mode=WSPR; - terminate=-1; + ppm = 0; + self_cal = true; + repeat = false; + random_offset = false; + test_tone = NAN; + no_delay = false; + mode = WSPR; + terminate = -1; static struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"ppm", required_argument, 0, 'p'}, - {"self-calibration", no_argument, 0, 's'}, - {"free-running", no_argument, 0, 'f'}, - {"repeat", no_argument, 0, 'r'}, - {"terminate", required_argument, 0, 'x'}, - {"offset", no_argument, 0, 'o'}, - {"test-tone", required_argument, 0, 't'}, - {"no-delay", no_argument, 0, 'n'}, - {0, 0, 0, 0} - }; - - while (true) { + {"help", no_argument, 0, 'h'}, + {"ppm", required_argument, 0, 'p'}, + {"self-calibration", no_argument, 0, 's'}, + {"free-running", no_argument, 0, 'f'}, + {"repeat", no_argument, 0, 'r'}, + {"terminate", required_argument, 0, 'x'}, + {"offset", no_argument, 0, 'o'}, + {"test-tone", required_argument, 0, 't'}, + {"no-delay", no_argument, 0, 'n'}, + {0, 0, 0, 0}}; + + while (true) + { /* getopt_long stores the option index here. */ int option_index = 0; - int c = getopt_long (argc, argv, "hp:sfrx:ot:n", - long_options, &option_index); + int c = getopt_long(argc, argv, "hp:sfrx:ot:n", + long_options, &option_index); if (c == -1) break; - switch (c) { - char * endp; - case 0: - // Code should only get here if a long option was given a non-null - // flag value. - std::cout << "Check code!" << std::endl; + switch (c) + { + char *endp; + case 0: + // Code should only get here if a long option was given a non-null + // flag value. + std::cout << "Check code!" << std::endl; + ABORT(-1); + break; + case 'h': + print_usage(); + ABORT(-1); + break; + case 'p': + ppm = strtod(optarg, &endp); + if ((optarg == endp) || (*endp != '\0')) + { + std::cerr << "Error: could not parse ppm value" << std::endl; ABORT(-1); - break; - case 'h': - print_usage(); + } + break; + case 's': + self_cal = true; + break; + case 'f': + self_cal = false; + break; + case 'r': + repeat = true; + break; + case 'x': + terminate = strtol(optarg, &endp, 10); + if ((optarg == endp) || (*endp != '\0')) + { + std::cerr << "Error: could not parse termination argument" << std::endl; ABORT(-1); - break; - case 'p': - ppm=strtod(optarg,&endp); - if ((optarg==endp)||(*endp!='\0')) { - std::cerr << "Error: could not parse ppm value" << std::endl; - ABORT(-1); - } - break; - case 's': - self_cal=true; - break; - case 'f': - self_cal=false; - break; - case 'r': - repeat=true; - break; - case 'x': - terminate=strtol(optarg,&endp,10); - if ((optarg==endp)||(*endp!='\0')) { - std::cerr << "Error: could not parse termination argument" << std::endl; - ABORT(-1); - } - if (terminate<1) { - std::cerr << "Error: termination parameter must be >= 1" << std::endl; - ABORT(-1); - } - break; - case 'o': - random_offset=true; - break; - case 't': - test_tone=strtod(optarg,&endp); - mode=TONE; - if ((optarg==endp)||(*endp!='\0')) { - std::cerr << "Error: could not parse test tone frequency" << std::endl; - ABORT(-1); - } - break; - case 'n': - no_delay=true; - break; - case '?': - /* getopt_long already printed an error message. */ + } + if (terminate < 1) + { + std::cerr << "Error: termination parameter must be >= 1" << std::endl; ABORT(-1); - default: + } + break; + case 'o': + random_offset = true; + break; + case 't': + test_tone = strtod(optarg, &endp); + mode = TONE; + if ((optarg == endp) || (*endp != '\0')) + { + std::cerr << "Error: could not parse test tone frequency" << std::endl; ABORT(-1); + } + break; + case 'n': + no_delay = true; + break; + case '?': + /* getopt_long already printed an error message. */ + ABORT(-1); + default: + ABORT(-1); } - } // Parse the non-option parameters - unsigned int n_free_args=0; - while (optind0) { + if (terminate > 0) + { temp << " TX will stop after " << terminate << " transmissions." << std::endl; - } else if (repeat) { + } + else if (repeat) + { temp << " Transmissions will continue forever until stopped with CTRL-C" << std::endl; } - if (random_offset) { + if (random_offset) + { temp << " A small random frequency offset will be added to all transmissions" << std::endl; } - if (temp.str().length()) { + if (temp.str().length()) + { std::cout << "Extra options:" << std::endl; std::cout << temp.str(); } std::cout << std::endl; - } else { + } + else + { std::stringstream temp; - temp << std::setprecision(6) << std::fixed << "A test tone will be generated at frequency " << test_tone/1e6 << " MHz" << std::endl; + temp << std::setprecision(6) << std::fixed << "A test tone will be generated at frequency " << test_tone / 1e6 << " MHz" << std::endl; std::cout << temp.str(); - if (self_cal) { + if (self_cal) + { std::cout << "NTP will be used to calibrate the tone frequency" << std::endl; - } else if (ppm) { + } + else if (ppm) + { std::cout << "PPM value to be used to generate the tone: " << ppm << std::endl; } std::cout << std::endl; @@ -1008,8 +650,8 @@ void parse_commandline( // Call ntp_adjtime() to obtain the latest calibration coefficient. void update_ppm( - double & ppm -) { + double &ppm) +{ struct timex ntx; int status; double ppm_new; @@ -1017,126 +659,85 @@ void update_ppm( ntx.modes = 0; /* only read */ status = ntp_adjtime(&ntx); - if (status != TIME_OK) { - //cerr << "Error: clock not synchronized" << std::endl; - //return; + if (status != TIME_OK) + { + // cerr << "Error: clock not synchronized" << std::endl; + // return; } - ppm_new = (double)ntx.freq/(double)(1 << 16); /* frequency scale */ - if (abs(ppm_new)>200) { + ppm_new = (double)ntx.freq / (double)(1 << 16); /* frequency scale */ + if (abs(ppm_new) > 200) + { std::cerr << "Warning: absolute ppm value is greater than 200 and is being ignored!" << std::endl; - } else { - if (ppm!=ppm_new) { + } + else + { + if (ppm != ppm_new) + { std::cout << " Obtained new ppm value: " << ppm_new << std::endl; } - ppm=ppm_new; + ppm = ppm_new; } } /* Return 1 if the difference is negative, otherwise 0. */ // From StackOverflow: // http://stackoverflow.com/questions/1468596/c-programming-calculate-elapsed-time-in-milliseconds-unix -int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1) { - long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec); - result->tv_sec = diff / 1000000; - result->tv_usec = diff % 1000000; +int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1) +{ + long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec); + result->tv_sec = diff / 1000000; + result->tv_usec = diff % 1000000; - return (diff<0); + return (diff < 0); } -void timeval_print(struct timeval *tv) { - char buffer[30]; - time_t curtime; +void timeval_print(struct timeval *tv) +{ + char buffer[30]; + time_t curtime; - //printf("%ld.%06ld", tv->tv_sec, tv->tv_usec); - curtime = tv->tv_sec; - //strftime(buffer, 30, "%m-%d-%Y %T", localtime(&curtime)); - strftime(buffer, 30, "UTC %Y-%m-%d %T", gmtime(&curtime)); - printf("%s.%03ld", buffer, (tv->tv_usec+500)/1000); -} - -// Create the mbox special files and open mbox. -void open_mbox() { - mbox.handle = mbox_open(); - if (mbox.handle < 0) { - std::cerr << "Failed to open mailbox." << std::endl; - ABORT(-1); - } + // printf("%ld.%06ld", tv->tv_sec, tv->tv_usec); + curtime = tv->tv_sec; + // strftime(buffer, 30, "%m-%d-%Y %T", localtime(&curtime)); + strftime(buffer, 30, "UTC %Y-%m-%d %T", gmtime(&curtime)); + printf("%s.%03ld", buffer, (tv->tv_usec + 500) / 1000); } // Called when exiting or when a signal is received. -void cleanup() { - disable_clock(); - unSetupDMA(); - deallocMemPool(); - unlink(LOCAL_DEVICE_FILE_NAME); +void cleanup() +{ + if (clk != NULL) + { + delete clk; + clk = NULL; + } + if (ngfmtest != NULL) + { + delete ngfmtest; + ngfmtest = NULL; + } } // Called when a signal is received. Automatically calls cleanup(). -void cleanupAndExit(int sig) { +void cleanupAndExit(int sig) +{ std::cerr << "Exiting with error; caught signal: " << sig << std::endl; cleanup(); ABORT(-1); } -void setSchedPriority(int priority) { - //In order to get the best timing at a decent queue size, we want the kernel - //to avoid interrupting us for long durations. This is done by giving our - //process a high priority. Note, must run as super-user for this to work. - struct sched_param sp; - sp.sched_priority=priority; - int ret = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp); - if (ret) { - std::cerr << "Warning: pthread_setschedparam (increase thread priority) returned non-zero: " << ret << std::endl; - } -} - -// Create the memory map between virtual memory and the peripheral range -// of physical memory. -void setup_peri_base_virt( - volatile unsigned * & peri_base_virt -) { - int mem_fd; - // open /dev/mem - if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { - std::cerr << "Error: can't open /dev/mem" << std::endl; - ABORT (-1); - } - peri_base_virt = (unsigned *)mmap( - NULL, - 0x01000000, //len - PROT_READ|PROT_WRITE, - MAP_SHARED, - mem_fd, - PERI_BASE_PHYS //base - ); - if ((long int)peri_base_virt==-1) { - std::cerr << "Error: peri_base_virt mmap error!" << std::endl; - ABORT(-1); - } - close(mem_fd); -} - -int main(const int argc, char * const argv[]) { - //catch all signals (like ctrl+c, ctrl+z, ...) to ensure DMA is disabled - for (int i = 0; i < 64; i++) { +int main(const int argc, char *const argv[]) +{ + // catch all signals (like ctrl+c, ctrl+z, ...) to ensure DMA is disabled + for (int i = 0; i < 64; i++) + { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = cleanupAndExit; sigaction(i, &sa, NULL); } atexit(cleanup); - setSchedPriority(30); - -#ifdef RPI1 - std::cout << "Detected Raspberry Pi version 1" << std::endl; -#else -#ifdef RPI23 - std::cout << "Detected Raspberry Pi version 2/3" << std::endl; -#else -#error "RPI version macro is not defined" -#endif -#endif // Initialize the RNG srand(time(NULL)); @@ -1145,7 +746,7 @@ int main(const int argc, char * const argv[]) { std::string callsign; std::string locator; std::string tx_power; - std::vector center_freq_set; + std::vector center_freq_set; double ppm; bool self_cal; bool repeat; @@ -1155,74 +756,52 @@ int main(const int argc, char * const argv[]) { mode_type mode; int terminate; parse_commandline( - argc, - argv, - callsign, - locator, - tx_power, - center_freq_set, - ppm, - self_cal, - repeat, - random_offset, - test_tone, - no_delay, - mode, - terminate - ); - int nbands=center_freq_set.size(); - - // Initial configuration - struct PageInfo constPage; - struct PageInfo instrPage; - struct PageInfo instrs[1024]; - setup_peri_base_virt(peri_base_virt); - // Set up DMA - open_mbox(); - txon(); - setupDMA(constPage,instrPage,instrs); - txoff(); - - if (mode==TONE) { + argc, + argv, + callsign, + locator, + tx_power, + center_freq_set, + ppm, + self_cal, + repeat, + random_offset, + test_tone, + no_delay, + mode, + terminate); + int nbands = center_freq_set.size(); + + if (mode == TONE) + { + if (clk == NULL) + clk = new clkgpio; + clk->SetAdvancedPllMode(true); // Test tone mode... double wspr_symtime = WSPR_SYMTIME; - double tone_spacing=1.0/wspr_symtime; + double tone_spacing = 1.0 / wspr_symtime; std::stringstream temp; - temp << std::setprecision(6) << std::fixed << "Transmitting test tone on frequency " << test_tone/1.0e6 << " MHz" << std::endl; + temp << std::setprecision(6) << std::fixed << "Transmitting test tone on frequency " << test_tone / 1.0e6 << " MHz" << std::endl; std::cout << temp.str(); std::cout << "Press CTRL-C to exit!" << std::endl; txon(); - int bufPtr=0; - std::vector dma_table_freq; + int bufPtr = 0; + // Set to non-zero value to ensure setupDMATab is called at least once. - double ppm_prev=123456; + double ppm_prev = 123456; double center_freq_actual; - while (true) { - if (self_cal) { - update_ppm(ppm); - } - if (ppm!=ppm_prev) { - setupDMATab(test_tone+1.5*tone_spacing,tone_spacing,F_PLLD_CLK*(1-ppm/1e6),dma_table_freq,center_freq_actual,constPage); - //cout << std::setprecision(30) << dma_table_freq[0] << std::endl; - //cout << std::setprecision(30) << dma_table_freq[1] << std::endl; - //cout << std::setprecision(30) << dma_table_freq[2] << std::endl; - //cout << std::setprecision(30) << dma_table_freq[3] << std::endl; - if (center_freq_actual!=test_tone+1.5*tone_spacing) { - std::cout << " Warning: because of hardware limitations, test tone will be transmitted on" << std::endl; - std::stringstream temp; - temp << std::setprecision(6) << std::fixed << " frequency: " << (center_freq_actual-1.5*tone_spacing)/1e6 << " MHz" << std::endl; - std::cout << temp.str(); - } - ppm_prev=ppm; - } - txSym(0, center_freq_actual, tone_spacing, 60, dma_table_freq, F_PWM_CLK_INIT, instrs, constPage, bufPtr); - } - + // SetTone + clk->SetCenterFrequency(test_tone, 100); + clk->enableclk(4); + clk->SetFrequency(000); + while (true) + usleep(1000000); // Should never get here... - - } else { + } + else + { // WSPR mode // Create WSPR symbols @@ -1240,55 +819,65 @@ int main(const int argc, char * const argv[]) { */ std::cout << "Ready to transmit (setup complete)..." << std::endl; - int band=0; - int n_tx=0; - for(;;) { + int band = 0; + int n_tx = 0; + for (;;) + { // Calculate WSPR parameters for this transmission double center_freq_desired; center_freq_desired = center_freq_set[band]; bool wspr15 = - (center_freq_desired > 137600 && center_freq_desired < 137625) || \ - (center_freq_desired > 475800 && center_freq_desired < 475825) || \ - (center_freq_desired > 1838200 && center_freq_desired < 1838225); + (center_freq_desired > 137600 && center_freq_desired < 137625) || + (center_freq_desired > 475800 && center_freq_desired < 475825) || + (center_freq_desired > 1838200 && center_freq_desired < 1838225); double wspr_symtime = (wspr15) ? 8.0 * WSPR_SYMTIME : WSPR_SYMTIME; - double tone_spacing=1.0/wspr_symtime; + double tone_spacing = 1.0 / wspr_symtime; // Add random offset - if ((center_freq_desired!=0)&&random_offset) { - center_freq_desired+=(2.0*rand()/((double)RAND_MAX+1.0)-1.0)*(wspr15?WSPR15_RAND_OFFSET:WSPR_RAND_OFFSET); + if ((center_freq_desired != 0) && random_offset) + { + center_freq_desired += (2.0 * rand() / ((double)RAND_MAX + 1.0) - 1.0) * (wspr15 ? WSPR15_RAND_OFFSET : WSPR_RAND_OFFSET); } // Status message before transmission std::stringstream temp; temp << std::setprecision(6) << std::fixed; - temp << "Desired center frequency for " << (wspr15?"WSPR-15":"WSPR") << " transmission: "<< center_freq_desired/1e6 << " MHz" << std::endl; + temp << "Desired center frequency for " << (wspr15 ? "WSPR-15" : "WSPR") << " transmission: " << center_freq_desired / 1e6 << " MHz" << std::endl; std::cout << temp.str(); // Wait for WSPR transmission window to arrive. - if (no_delay) { + if (no_delay) + { std::cout << " Transmitting immediately (not waiting for WSPR window)" << std::endl; - } else { + } + else + { std::cout << " Waiting for next WSPR transmission window..." << std::endl; wait_every((wspr15) ? 15 : 2); } // Update crystal calibration information - if (self_cal) { + if (self_cal) + { update_ppm(ppm); } // Create the DMA table for this center frequency - std::vector dma_table_freq; + std::vector dma_table_freq; double center_freq_actual; - if (center_freq_desired) { - setupDMATab(center_freq_desired,tone_spacing,F_PLLD_CLK*(1-ppm/1e6),dma_table_freq,center_freq_actual,constPage); - } else { - center_freq_actual=center_freq_desired; + if (center_freq_desired) + { + center_freq_actual = center_freq_desired; + } + else + { + center_freq_actual = center_freq_desired; } // Send the message! - //cout << "TX started!" << std::endl; - if (center_freq_actual){ + // std::cout << "TX started!" << std::endl; + if (center_freq_actual) + { // Print a status message right before transmission begins. struct timeval tvBegin, tvEnd, tvDiff; gettimeofday(&tvBegin, NULL); @@ -1298,50 +887,86 @@ int main(const int argc, char * const argv[]) { struct timeval sym_start; struct timeval diff; - int bufPtr=0; - txon(); - for (int i = 0; i < 162; i++) { - gettimeofday(&sym_start,NULL); - timeval_subtract(&diff, &sym_start, &tvBegin); - double elapsed=diff.tv_sec+diff.tv_usec/1e6; - //elapsed=(i)*wspr_symtime; - double sched_end=(i+1)*wspr_symtime; - //cout << "symbol " << i << " " << wspr_symtime << std::endl; - //cout << sched_end-elapsed << std::endl; - double this_sym=sched_end-elapsed; - this_sym=(this_sym<.2)?.2:this_sym; - this_sym=(this_sym>2*wspr_symtime)?2*wspr_symtime:this_sym; - txSym(symbols[i], center_freq_actual, tone_spacing, sched_end-elapsed, dma_table_freq, F_PWM_CLK_INIT, instrs, constPage, bufPtr); + int bufPtr = 0; + int Upsample = 10000; + int SR = Upsample * 1 / wspr_symtime; + int FifoSize = 40000; + bool usePWMSample = false; + static float *FreqPWM = NULL; + + //New modulator and tx on + ngfmtest = new ngfmdmasync(center_freq_actual, SR, 14, FifoSize, true); + FreqPWM = (float *)malloc(Upsample * sizeof(float)); + + double FreqResolution = ngfmtest->GetFrequencyResolution(); + + double RealFreq = ngfmtest->GetRealFrequency(0); + if (FreqResolution > tone_spacing) + { + fprintf(stderr, "Freq resolution=%f - Tone spacing =%f Erreur tuning=%f\n", FreqResolution, tone_spacing, RealFreq); + usePWMSample = true; + } + + for (int i = 0; i < 162; i++) + { + double tone_freq = -1.5 * tone_spacing + symbols[i] * tone_spacing - RealFreq; + int Nbtx = 0; + int f1 = 0; + int Frac = ngfmtest->GetMasterFrac(0); + int IntFreq = floor(tone_freq / FreqResolution); + double ToneFreqInf = tone_freq - IntFreq; + int Step = ToneFreqInf * Upsample / FreqResolution; + + if (!usePWMSample) + { + for (int j = 0; j < Upsample; j++) + { + FreqPWM[j] = tone_freq; + } + } + else + { + // Todo : Implement PWMFrequency to obtain better frequency resolution + for (int j = 0; j < Upsample; j++) + { + FreqPWM[j] = tone_freq; + } + } + ngfmtest->SetFrequencySamples(FreqPWM, Upsample); } n_tx++; // Turn transmitter off - txoff(); - + ngfmtest->disableclk(4); + delete ngfmtest; + ngfmtest = NULL; + free(FreqPWM); // End timestamp gettimeofday(&tvEnd, NULL); std::cout << " TX ended at: "; timeval_print(&tvEnd); timeval_subtract(&tvDiff, &tvEnd, &tvBegin); - printf(" (%ld.%03ld s)\n", tvDiff.tv_sec, (tvDiff.tv_usec+500)/1000); - - } else { + printf(" (%ld.%03ld s)\n", tvDiff.tv_sec, (tvDiff.tv_usec + 500) / 1000); + } + else + { std::cout << " Skipping transmission" << std::endl; usleep(1000000); } // Advance to next band - band=(band+1)%nbands; - if ((band==0)&&!repeat) { + band = (band + 1) % nbands; + if ((band == 0) && !repeat) + { break; } - if ((terminate>0)&&(n_tx>=terminate)) { + if ((terminate > 0) && (n_tx >= terminate)) + { + break; } - } } return 0; } -