From 82cff5eaf138c6d101167e0cc9f24753e23d5c11 Mon Sep 17 00:00:00 2001 From: James Peroulas Date: Wed, 14 Jan 2015 00:02:18 -0700 Subject: [PATCH] For backup purposes. Does not compile yet. --- PiCW.cpp | 1452 +++++++++++++++++++++++++++++++++++++++++++++++++ makefile | 17 + pulse_shape.m | 56 ++ 3 files changed, 1525 insertions(+) create mode 100644 PiCW.cpp create mode 100644 makefile create mode 100644 pulse_shape.m diff --git a/PiCW.cpp b/PiCW.cpp new file mode 100644 index 0000000..7d3d3aa --- /dev/null +++ b/PiCW.cpp @@ -0,0 +1,1452 @@ +// See accompanying README and BUILD files for descriptions on how to use this +// code. + +/* +License: + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//using namespace std; + +#define ABORT(a) exit(a) +// Used for debugging +#define MARK std::cout << "Currently in file: " << __FILE__ << " line: " << __LINE__ << std::endl + +// PLLD clock frequency. +// There seems to be a 2.5ppm offset between the NTP measured frequency +// error and the frequency error measured by a frequency counter. This fixed +// PPM offset is compensated for here. +#define F_PLLD_CLK (500000000.0*(1-2.500e-6)) +// 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) + +// WSRP nominal symbol time +//#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 + +#define BCM2708_PERI_BASE 0x20000000 +#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */ +#define PAGE_SIZE (4*1024) +#define BLOCK_SIZE (4*1024) + +// This must be declared global so that it can be used by the atexit +// function. +volatile unsigned *allof7e = NULL; + +// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y) +#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) +#define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3)) +#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) + +#define GPIO_SET *(gpio+7) // sets bits which are 1 ignores bits which are 0 +#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0 +#define GPIO_GET *(gpio+13) // sets bits which are 1 ignores bits which are 0 + +#define ACCESS(base) *(volatile int*)((long int)allof7e+base-0x7e000000) +#define SETBIT(base, bit) ACCESS(base) |= 1< & terminate, + const double & tone_freq, + const double & tsym, + const vector & dma_table_freq, + const double & f_pwm_clk, + struct PageInfo instrs[], + struct PageInfo & constPage, + int & bufPtr +) { + const int f0_idx=0; + const int f1_idx=1; + const double f0_freq=dma_table_freq[f0_idx]; + const double f1_freq=dma_table_freq[f1_idx]; + // 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 << 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; + while ((!terminate)&&(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(DMABASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].p)) usleep(100); + ((struct CB*)(instrs[bufPtr].v))->SOURCE_AD = (long int)constPage.p + f0_idx*4; + + // Wait for n_f0 PWM clocks + bufPtr++; + while( ACCESS(DMABASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].p)) usleep(100); + ((struct CB*)(instrs[bufPtr].v))->TXFR_LEN = n_f0; + + // Set GPIO pin to transmit f1 + bufPtr++; + while( ACCESS(DMABASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].p)) usleep(100); + ((struct CB*)(instrs[bufPtr].v))->SOURCE_AD = (long int)constPage.p + f1_idx*4; + + // Wait for n_f1 PWM clocks + bufPtr=(bufPtr+1) % (1024); + while( ACCESS(DMABASE + 0x04 /* CurBlock*/) == (long int)(instrs[bufPtr].p)) usleep(100); + ((struct CB*)(instrs[bufPtr].v))->TXFR_LEN = n_f1; + + // Update counters + n_pwmclk_transmitted+=n_pwmclk; + n_f0_transmitted+=n_f0; + } +} + +void unSetupDMA(){ + printf("exiting\n"); + struct DMAregs* DMA0 = (struct DMAregs*)&(ACCESS(DMABASE)); + DMA0->CS =1<<31; // reset dma controller + txoff(); +} + +void handSig(const int h) { + exit(0); +} + +double bit_trunc( + const double & d, + const int & lsb +) { + return floor(d/pow(2.0,lsb))*pow(2.0,lsb); +} + +// Setup the DMA table to produce the frequency we need. +// For PiFM, this table had 1024 entries but for this application, we only +// use the first two. The remaining values are filled with dummy data. +void setupDMATab( + const double & tone_freq, + const double & plld_actual_freq, + vector & dma_table_freq, + struct PageInfo & constPage +){ + // Make sure that the tone 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; + stringstream temp; + temp << setprecision(6) << fixed << " Warning: center frequency has been changed to " << center_freq_actual/1e6 << " MHz" << endl; + cout << temp.str(); + cout << " because of hardware limitations!" << endl; + } + */ + + // We only really need two tuning words... + // TODO: It seems to be safe to change the fractional part of the divisor + // while the clock generator is enabled. Check to see that it is also safe + // to change the integer part. If it is not safe to change the integer part, + // then there will be some frequencies which are not synthesizeable. + vector tuning_word(1024); + double div=bit_trunc(plld_actual_freq/tone_freq,-12)+pow(2.0,-12); + tuning_word[0]=((int)(div*pow(2.0,12))); + div-=pow(2.0,-12); + tuning_word[1]=((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))); + } + + // Create DMA table of tuning words. WSPR tone i will use entries 2*i and + // 2*i+1 to generate the appropriate tone. + /* + dma_table_freq.resize(1024); + double tone0_freq=center_freq_actual-1.5*tone_spacing; + 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 + 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))); + } + } +} + +void setupDMA( + struct PageInfo & constPage, + struct PageInfo & instrPage, + struct PageInfo instrs[] +){ + atexit(unSetupDMA); + signal (SIGINT, handSig); + signal (SIGTERM, handSig); + signal (SIGHUP, handSig); + signal (SIGQUIT, handSig); + + // Allocate a page of ram for the constants + getRealMemPage(&constPage.v, &constPage.p); + + // 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 + getRealMemPage(&instrPage.v, &instrPage.p); + + // 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].p = (void*)((long int)instrPage.p + sizeof(struct CB)*i); + instr0->SOURCE_AD = (unsigned long int)constPage.p+2048; + instr0->DEST_AD = PWMBASE+0x18 /* FIF1 */; + instr0->TXFR_LEN = 4; + instr0->STRIDE = 0; + //instr0->NEXTCONBK = (int)instrPage.p + 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; + instr0->STRIDE = 4; + instr0->TI = (1<<26/* no wide*/) ; + } + + if (instrCnt!=0) ((struct CB*)(instrs[instrCnt-1].v))->NEXTCONBK = (long int)instrs[instrCnt].p; + instr0++; + instrCnt++; + } + } + // Create a circular linked list of instructions + ((struct CB*)(instrs[1023].v))->NEXTCONBK = (long int)instrs[0].p; + + // set up a clock for the PWM + ACCESS(CLKBASE + 40*4 /*PWMCLK_CNTL*/) = 0x5A000026; // Source=PLLD and disable + usleep(1000); + //ACCESS(CLKBASE + 41*4 /*PWMCLK_DIV*/) = 0x5A002800; + ACCESS(CLKBASE + 41*4 /*PWMCLK_DIV*/) = 0x5A002000; // set PWM div to 2, for 250MHz + ACCESS(CLKBASE + 40*4 /*PWMCLK_CNTL*/) = 0x5A000016; // Source=PLLD and enable + usleep(1000); + + // set up pwm + ACCESS(PWMBASE + 0x0 /* CTRL*/) = 0; + usleep(1000); + ACCESS(PWMBASE + 0x4 /* status*/) = -1; // clear errors + usleep(1000); + // Range should default to 32, but it is set at 2048 after reset on my RPi. + ACCESS(PWMBASE + 0x10)=32; + ACCESS(PWMBASE + 0x20)=32; + ACCESS(PWMBASE + 0x0 /* CTRL*/) = -1; //(1<<13 /* Use fifo */) | (1<<10 /* repeat */) | (1<<9 /* serializer */) | (1<<8 /* enable ch */) ; + usleep(1000); + ACCESS(PWMBASE + 0x8 /* DMAC*/) = (1<<31 /* DMA enable */) | 0x0707; + + //activate dma + struct DMAregs* DMA0 = (struct DMAregs*)&(ACCESS(DMABASE)); + DMA0->CS =1<<31; // reset + DMA0->CONBLK_AD=0; + DMA0->TI=0; + DMA0->CONBLK_AD = (unsigned long int)(instrPage.p); + DMA0->CS =(1<<0)|(255 <<16); // enable bit = 0, clear end flag = 1, prio=19-16 +} + + +// +// Set up a memory regions to access GPIO +// +void setup_io( + int & mem_fd, + char * & gpio_mem, + char * & gpio_map, + volatile unsigned * & gpio +) { + /* open /dev/mem */ + if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { + printf("can't open /dev/mem \n"); + exit (-1); + } + + /* mmap GPIO */ + + // Allocate MAP block + if ((gpio_mem = (char *)malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) { + printf("allocation error \n"); + exit (-1); + } + + // Make sure pointer is on 4K boundary + if ((unsigned long)gpio_mem % PAGE_SIZE) + gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE); + + // Now map it + gpio_map = (char *)mmap( + gpio_mem, + BLOCK_SIZE, + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FIXED, + mem_fd, + GPIO_BASE + ); + + if ((long)gpio_map < 0) { + printf("mmap error %ld\n", (long int)gpio_map); + exit (-1); + } + + // Always use volatile pointer! + gpio = (volatile unsigned *)gpio_map; + + +} + +void setup_gpios( + volatile unsigned * & gpio +){ + int g; + // Switch GPIO 7..11 to output mode + + /************************************************************************\ + * You are about to change the GPIO settings of your computer. * + * Mess this up and it will stop working! * + * It might be a good idea to 'sync' before running this program * + * so at least you still have your code changes written to the SD-card! * + \************************************************************************/ + + // Set GPIO pins 7-11 to output + for (g=7; g<=11; g++) { + INP_GPIO(g); // must use INP_GPIO before we can use OUT_GPIO + //OUT_GPIO(g); + } + +} + +// Convert string to uppercase +/* +void to_upper(char *str) +{ while(*str) + { + *str = toupper(*str); + str++; + } +} +*/ + +// Encode call, locator, and dBm into WSPR codeblock. +/* +void wspr(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]; + strncpy(buf, call, 16); + 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; + } + } + + 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){ + // 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'); + } + 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] = {n1>>20, n1>>12, n1>>4, ((n1&0x0f)<<4)|((n2>>18)&0x0f), +n2>>10, n2>>2, (n2&0x03)<<6, 0, 0, 0, 0}; + + // convolutional encoding K=32, r=1/2, Layland-Lushbaugh polynomials + int k = 0; + 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++; + } + } + } + + // 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 vector + } +} +*/ + +// Wait for the system clock's minute to reach one second past 'minute' +/* +void wait_every(int minute) +{ + time_t t; + struct tm* ptm; + for(;;){ + time(&t); + ptm = gmtime(&t); + if((ptm->tm_min % minute) == 0 && ptm->tm_sec == 0) break; + usleep(1000); + } + usleep(1000000); // wait another second +} +*/ + +void print_usage() { + cout << "Usage:" << endl; + cout << " PiCW [options] \"MORSE TEXT TO SEND\"" << endl; + cout << endl; + cout << "Options:" << endl; + cout << " -h --help" << endl; + cout << " Print out this help screen." << endl; + cout << " -f --freq f" << endl; + cout << " Specify the frequency to be used for the transmission" << endl; + cout << " -w --wpm w" << endl; + cout << " Specify the transmission speed in Words Per Minute" << endl; + cout << " -p --ppm ppm" << endl; + cout << " Known PPM correction to 19.2MHz RPi nominal crystal frequency." << endl; + cout << " -s --self-calibration" << endl; + cout << " Call ntp_adjtime() periodically to obtain the PPM error of the crystal." << endl; +} + +// From StackOverflow: +// http://stackoverflow.com/questions/478898/how-to-execute-a-command-and-get-output-of-command-within-c +/* +std::string exec(const char * cmd) { + FILE* pipe = popen(cmd, "r"); + if (!pipe) return "ERROR"; + char buffer[128]; + std::string result = ""; + while (!feof(pipe)) { + if (fgets(buffer, 128, pipe) != NULL) + result += buffer; + } + pclose(pipe); + return result; +} +*/ + +void parse_commandline( + // Inputs + const int & argc, + char * const argv[], + // Outputs + double & tone_freq, + double & wpm, + double & ppm, + bool & self_cal, + std::string & str +) { + // Default values + tone_freq=0; + wpm=20; + ppm=0; + self_cal=false; + str=""; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"freq", required_argument, 0, 'f'}, + {"wpm", required_argument, 0, 'w'}, + {"ppm", required_argument, 0, 'p'}, + {"self-calibration", no_argument, 0, 's'}, + {0, 0, 0, 0} + }; + + while (1) { + /* getopt_long stores the option index here. */ + int option_index = 0; + int c = getopt_long (argc, argv, "hf:w:p:s", + 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. + cout << "Check code!" << endl; + ABORT(-1); + break; + case 'h': + print_usage(); + ABORT(-1); + break; + case 'f': + freq=strtod(optarg,&endp); + if ((optarg==endp)||(*endp!='\0')) { + cerr << "Error: could not parse frequency" << endl; + ABORT(-1); + } + break; + case 'w': + wpm=strtod(optarg,&endp); + if ((optarg==endp)||(*endp!='\0')) { + cerr << "Error: could not parse wpm value" << endl; + ABORT(-1); + } + break; + case 'p': + ppm=strtod(optarg,&endp); + if ((optarg==endp)||(*endp!='\0')) { + cerr << "Error: could not parse ppm value" << endl; + ABORT(-1); + } + break; + case 's': + self_cal=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 (optind200) { + cerr << "Warning: absolute ppm value is greater than 200 and is being ignored!" << endl; + } else { + if (ppm!=ppm_new) { + cout << " Obtained new ppm value: " << ppm_new << endl; + } + 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; + + return (diff<0); +} +*/ + +/* +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 %m-%d-%Y %T", gmtime(&curtime)); + printf("%s.%03ld", buffer, (tv->tv_usec+500)/1000); +} +*/ + +// This thread manages the tone being produced. If the desired frequency +// changes, or if the PPM value is updated, this thread will take appropriate +// measures to ensure that the tone being produced is as close as possible +// to the frequency that is desired. +void tone_main( + std::atomic & terminate, + const bool & self_cal, + const double & ppm_init, + std::atomic & freq, + struct PageInfo instrs[], + struct PageInfo & constPage +) { + // Initialize + double ppm=ppm_init; + if (self_cal) { + update_ppm(ppm); + } + double ppm_old=ppm; + double freq_old=freq; + vector & dma_table_freq, + setupDMATab(freq_old,F_PLLD_CLK*(1-ppm_old/1e6),dma_table_freq,constPage); + int bufPtr=0; + + while (!terminate) { + // Read the current values of the atomics. + double freq_new=freq; + double ppm_new=ppm; + + // Update table if necessary. + if ( + (ppm_new!=ppm_old) || + (freq_newdma_table_freq[1]) + ) { + setupDMATab(freq_new,F_PLLD_CLK*(1-ppm_new/1e6),dma_table_freq,constPage); + } + + // Transmit for a small amount of time before checking for updates to + // frequency or PPM. + double tx_time_secs=1.0; + txSym( + terminate, + freq_new, + tx_time_secs, + dma_table_freq, + F_PWM_CLK_INIT, + instrs, + constPage, + bufPtr + ); + + freq_old=freq_new; + ppm_old=ppm_new; + } +} + +class time_value { + public: + double time; + unsigned int value; +}; + +// Raised cosine pulse shapes. +// Rise: +//y=(-cos(t*pi)+1)/2; +//2*y-1=-cos(t*pi); +//1-2*y=cos(t*pi); +//acos(1-2*y)=t*pi; +//acos(1-2*y)/pi=t; +// Fall: +//y=1-(-cos(t*pi)+1)/2; +//y-1=-(-cos(t*pi)+1)/2; +//2*y-2=-(-cos(t*pi)+1); +//2-2*y=-cos(t*pi)+1; +//1-2*y=-cos(t*pi); +//2*y-1=cos(t*pi); +//acos(2*y-1)=t*pi; +//acos(2*y-1)/pi=t; +// Instead of uniform sampling of the x axis, we're using uniform +// sampling of the y axis. +void raised_cosine( + const double & width_secs, + std::vector & rise, + std::vector & fall +) { + rise.resize(0); + rise.reserve(10); + fall.resize(0); + fall.reserve(10); + { + time_value rec; + rec.value=0; + rec.time=0; + rise.push_back(rec); + rec.value=8; + fall.push_back(rec); + } + for (double y=0.5/8.0;y<1;y+=1.0/8.0) { + time_value rec; + rec.value=round(y*8.0+0.5); + rec.time=acos(1-2*y)/M_PI*width_secs; + rise.push_back(rec); + } + for (double y=7.5/8.0;y>0;y-=1.0/8.0) { + time_value rec; + rec.value=round(y*8.0-0.5); + rec.time=acos(2*y-1)/M_PI*width_secs; + fall.push_back(rec); + } + { + time_value rec; + rec.value=8; + rec.time=width_secs; + rise.push_back(rec); + rec.value=0; + fall.push_back(rec); + } +} + +// Adjust the drive current on the pin. +void set_current( + unsigned int value +) { + if (value>8) { + value=8; + } + if (value==0) { + struct GPCTL setupword = {6/*SRC*/, 0, 0, 0, 0, 1,0x5a}; + ACCESS(CM_GP0CTL) = *((int*)&setupword); + } else { + ACCESS(PADS_GPIO_0_27) = 0x5a000018 + value - 1; + struct GPCTL setupword = {6/*SRC*/, 1, 0, 0, 0, 3,0x5a}; + ACCESS(CM_GP0CTL) = *((int*)&setupword); + } +} + +// Send either a dit or a dah +void send_dit_dah( + std::atomic & terminate, + const char & sym, + const double & dot_duration_sec, + std::mt19937 & gen +) { + // Setting ramp_excess to 0 will produce hard keying. A ramp_excess value + // of 1.0 will produce a dit that has a ramp going up, a ramp going down, + // and no flat portion. + const double ramp_excess=0.3; + const double ramp_time=dot_duration_sec*ramp_excess; + const flat_time=dot_duration_sec*(1-ramp_excess)+((sym=='-')?(2*dot_duration_sec):(0)); + // Jitter adjusts the timing of the rising and falling ramp. This serves + // to spread out the harmonics that are created. + const double jitter_factor=0.1; + std::uniform_real_distribution<> dis(0,jitter_factor*dot_duration_sec); + const double jitter1=dis(gen); + const double jitter2=dis(gen); + + // Calculate the rise and fall ramps. + static bool initialized=false; + static double ramp_time_prev=0; + static std::vector rise; + static std::vector fall; + if ((!initialized)||(ramp_time_prev!=ramp_time)) { + raised_cosine( + ramp_time, + rise, + fall + ); + initialized=true; + } + + // Pulse will be timed relative to the current time. + std::chrono::high_resolution_clock::time_point ref=std::chrono::high_resolution_clock::now(); + + // Delay the rising ramp. + std::this_thread::sleep_until(ref+jitter1); + if (terminate) { + return; + } + // Rising ramp. + for (auto & tv:rise) { + std::this_thread::sleep_until(ref+jitter1+tv.time); + if (terminate) { + return; + } + set_current(tv.value); + } + // Keep transmitting at full power until after the flat portion and after + // the second jitter delay. + std::this_thread::sleep_until(ref+jitter1+ramp_time+flat_time+jitter2); + if (terminate) { + return; + } + // Falling ramp. + for (auto & tv:fall) { + std::this_thread::sleep_until(ref+jitter1+ramp_time+flat_time+jitter2+tv.time); + if (terminate) { + return; + } + set_current(tv.value); + } +} + +// This is the thread that modulates the carrier and produces the dits and +// dahs. +void am_main( + std::atomic & terminate, + std::deque & queue, + std::mutex & queue_mutex, + std::condition_variable & queue_signal, + const std::map & morse_table, + std::atomic & wpm + std::atomic & busy; +) { + bool prev_char_whitespace=true; + std::chrono::high_resolution_clock::time_point earliest_tx_time=std::chrono::high_resolution_clock::now(); + + std::random_device rd; + std::mt19937 gen(rd()); + + while (true) { + busy=false; + + char tx_char='\0'; + { + std::unique_lock lock(queue_mutex); + if (terminate) { + return; + } + while (queue.empty()) { + queue_signal.wait_for(lock,std::chrono::milliseconds(100)); + if (terminate) { + return; + } + } + tx_char=queue.front(); + queue.pop_front(); + busy=true; + } + + const double dot_duration_sec=1.2/wpm; + + // Handle whitespace. + if ((tx_char==' ')||(tx_char=='\n')) { + if (prev_char_whitespace) { + // Ignore multiple whitespaces. + continue; + } else { + earliest_tx_time+=std::chrono::nanoseconds(4*dot_duration*1e6); + prev_char_whitespace=true; + continue; + } + } + prev_char_whitespace=false; + + if (morse_table.find(tx_char)==morse_table.end()) { + // We should never get here... + MARK; + ABORT(-1); + } + + // See if we have already waited enough. + if (std::chrono::high_resolution_clock::now()>=earliest_tx_time) { + earliest_tx_time=std::chrono::high_resolution_clock::now(); + } + + // Send the dits and dahs + const string tx_pattern=morse_table[tx_char]; + for (unsigned int t=0;t & morse_table +) { + morse_table.clear(); + morse_table['A']=".-"; + morse_table['B']="-..."; + morse_table['C']="-.-."; + morse_table['D']="-.."; + morse_table['E']="."; + morse_table['F']="..-."; + morse_table['G']="--."; + morse_table['H']="...."; + morse_table['I']=".."; + morse_table['J']=".---"; + morse_table['K']="-.-"; + morse_table['L']=".-.."; + morse_table['M']="--"; + morse_table['N']="-."; + morse_table['O']="---"; + morse_table['P']=".--."; + morse_table['Q']="--.-"; + morse_table['R']=".-."; + morse_table['S']="..."; + morse_table['T']="-"; + morse_table['U']="..-"; + morse_table['V']="...-"; + morse_table['W']=".--"; + morse_table['X']="-..-"; + morse_table['Y']="-.--"; + morse_table['Z']="--.."; + morse_table['0']="-----"; + morse_table['1']=".----"; + morse_table['2']="..---"; + morse_table['3']="...--"; + morse_table['4']="....-"; + morse_table['5']="....."; + morse_table['6']="-...."; + morse_table['7']="--..."; + morse_table['8']="---.."; + morse_table['9']="----."; + morse_table['.']=".−.−.−"; + morse_table[',']="−−..−−"; + morse_table[':']="−−−..."; + morse_table['?']="..−−.."; + morse_table[''']=".−−−−."; + morse_table['-']="−....−"; + morse_table['/']="−..−."; + morse_table['(']="−.−−."; + morse_table[')']="−.−−.−"; + morse_table['"']=".−..−."; + morse_table['=']="−...−"; + morse_table['+']=".−.−."; + morse_table['*']="−..−"; + morse_table['@']=".––.–."; +} + +int main(const int argc, char * const argv[]) { + // Initialize the RNG + //srand(time(NULL)); + + // Parse arguments + double freq_init; + double wpm_init; + double ppm_init; + bool self_cal; + string str; + parse_commandline( + argc, + argv, + freq_init, + wpm_init, + ppm_init, + self_cal, + str + ); + + std::atomic tone_freq; + tone_freq=freq_init; + std::atomic wpm; + wpm=wpm_init; + + // Initial configuration + int mem_fd; + char *gpio_mem, *gpio_map; + volatile unsigned *gpio = NULL; + setup_io(mem_fd,gpio_mem,gpio_map,gpio); + setup_gpios(gpio); + allof7e = (unsigned *)mmap( + NULL, + 0x01000000, //len + PROT_READ|PROT_WRITE, + MAP_SHARED, + mem_fd, + 0x20000000 //base + ); + if ((long int)allof7e==-1) { + cerr << "Error: mmap error!" << endl; + ABORT(-1); + } + txon(); + struct PageInfo constPage; + struct PageInfo instrPage; + struct PageInfo instrs[1024]; + setupDMA(constPage,instrPage,instrs); + txoff(); + + std::map & morse_table; + morse_table_init(morse_table); + + // Start tone thread. + std::atomic terminate_tone_thread; + terminate_tone_thread=false; + std::thread tone_thread( + tone_main( + terminate_tone_thread, + self_cal, + ppm_init, + tone_freq, + instrs, + constPage + ) + ); + + // Start AM thread + std::atomic terminate_am_thread; + terminate_am_thread=false; + std::deque & queue; + std::mutex & queue_mutex; + std::condition_variable & queue_signal; + std::atomic am_thread_busy; + am_thread_busy=false; + std::thread am_thread( + am_main( + terminate_am_thread, + queue, + queue_mutex, + queue_signal, + morse_table, + wpm, + am_thread_busy + ) + ); + + // Push text into AM thread + { + std::unique_lock lock(queue_mutex); + for (unsigned int t=0;t lock(queue_mutex); + if (queue.empty()) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Wait for final character to be transmitted. + while (am_thread_busy) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Terminate subthreads + terminate_am_thread=true; + terminate_tone_thread=true; + if (am_thread.joinable()) { + am_thread.join(); + } + if (tone_thread.joinable()) { + tone_thread.join(); + } + + /* + if (mode==TONE) { + // Test tone mode... + double wspr_symtime = WSPR_SYMTIME; + double tone_spacing=1.0/wspr_symtime; + + stringstream temp; + temp << setprecision(6) << fixed << "Transmitting test tone on frequency " << test_tone/1.0e6 << " MHz" << endl; + cout << temp.str(); + cout << "Press CTRL-C to exit!" << endl; + + txon(); + int bufPtr=0; + vector dma_table_freq; + // Set to non-zero value to ensure setupDMATab is called at least once. + 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 << setprecision(30) << dma_table_freq[0] << endl; + //cout << setprecision(30) << dma_table_freq[1] << endl; + //cout << setprecision(30) << dma_table_freq[2] << endl; + //cout << setprecision(30) << dma_table_freq[3] << endl; + if (center_freq_actual!=test_tone+1.5*tone_spacing) { + cout << " Warning: because of hardware limitations, test tone will be transmitted on" << endl; + stringstream temp; + temp << setprecision(6) << fixed << " frequency: " << (center_freq_actual-1.5*tone_spacing)/1e6 << " MHz" << endl; + cout << temp.str(); + } + ppm_prev=ppm; + } + txSym(0, center_freq_actual, tone_spacing, 60, dma_table_freq, F_PWM_CLK_INIT, instrs, constPage, bufPtr); + } + + // Should never get here... + + } else { + // WSPR mode + + // Create WSPR symbols + unsigned char symbols[162]; + wspr(callsign.c_str(), locator.c_str(), tx_power.c_str(), symbols); + + printf("Ready to transmit (setup comlete)...\n"); + 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); + double wspr_symtime = (wspr15) ? 8.0 * WSPR_SYMTIME : 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); + } + + // Status message before transmission + stringstream temp; + temp << setprecision(6) << fixed; + temp << "Desired center frequency for " << (wspr15?"WSPR-15":"WSPR") << " transmission: "<< center_freq_desired/1e6 << " MHz" << endl; + cout << temp.str(); + + // Wait for WSPR transmission window to arrive. + if (no_delay) { + cout << " Transmitting immediately (not waiting for WSPR window)" << endl; + } else { + printf(" Waiting for next WSPR transmission window...\n"); + wait_every((wspr15) ? 15 : 2); + } + + // Update crystal calibration information + if (self_cal) { + update_ppm(ppm); + } + + // Create the DMA table for this center frequency + 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; + } + + // Send the message! + //cout << "TX started!" << endl; + if (center_freq_actual){ + // Print a status message right before transmission begins. + struct timeval tvBegin, tvEnd, tvDiff; + gettimeofday(&tvBegin, NULL); + cout << " TX started at: "; + timeval_print(&tvBegin); + cout << endl; + + 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 << endl; + //cout << sched_end-elapsed << 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); + } + n_tx++; + + // Turn transmitter off + txoff(); + + gettimeofday(&tvEnd, NULL); + 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 { + cout << " Skipping transmission" << endl; + usleep(1000000); + } + + // Advance to next band + band=(band+1)%nbands; + if ((band==0)&&!repeat) { + break; + } + if ((terminate>0)&&(n_tx>=terminate)) { + break; + } + + } + } + */ + + return 0; +} + diff --git a/makefile b/makefile new file mode 100644 index 0000000..c240b34 --- /dev/null +++ b/makefile @@ -0,0 +1,17 @@ +prefix=/usr/local + +all: PiCW + +PiCW: PiCW.cpp + g++ -std=c++11 -Wall -Werror -lm PiCW.cpp -oPiCW + +#.PHONY: install +#install: wspr +# install -m 0755 wspr $(prefix)/bin +# install -m 0755 gpioclk $(prefix)/bin + +#.PHONY: uninstall +#uninstall: +# rm -f $(prefix)/bin/wspr +# rm -f $(prefix)/bin/gpioclk + diff --git a/pulse_shape.m b/pulse_shape.m new file mode 100644 index 0000000..910ba6d --- /dev/null +++ b/pulse_shape.m @@ -0,0 +1,56 @@ +pkg load signal + +fs=1e6; +wpm=60; +ramp_excess=.3; + +period=1200/wpm*.001*2; +ramp_time=period/2*ramp_excess; +flat_time=period/2*(1-ramp_excess); +n_samp=round(period*fs); +n_ramp=round(ramp_time*fs); +n_flat=round(flat_time*fs); +printf('on_time = %5.2f ms\n',period/2*1000); +printf('ramp = %5.2f ms\n',ramp_time*1000); + +%ramp_func=@(x)x; +%ramp=ramp_func(linspace(0,1,n_ramp)); +ramp_func=@(x)(-cos(x*pi)+1)/2; +ramp=ramp_func(linspace(0,1,n_ramp)); +%ramp_func=@(x)repmat(1,1,length(x)); +%ramp=ramp_func(linspace(0,1,n_ramp)); +%ramp_func=@(x)exp(-(x-1).^2/(2*0.3^2)); +%ramp=ramp_func(linspace(0,1,n_ramp)); +%ramp=transpose(hamming(2*n_ramp+1)); +%ramp=ramp(1:n_ramp); +%ramp=transpose(chebwin(2*n_ramp+1,100)); +%ramp=ramp(1:n_ramp); +%ramp_func=@(x)(erf((x-0.5)*3.0)+1)/2; +%ramp=ramp_func(linspace(0,1,n_ramp)); +ramp=ramp-ramp(1); +ramp=ramp/max(ramp)*1; + +sig=[ramp ones(1,n_flat) fliplr(ramp) zeros(1,n_samp-n_flat-2*n_ramp)]; +%sig=sig-mean(sig); + +sig=round(sig*8)/8; +sig=sig+randn(1,length(sig))/10000; + +f=linspace(0,2*fs,n_samp+1); +f=f(1:end-1); +m=abs((fft(sig))).^2; +m=m/max(m); +plot(f,10*log10(m)); +xlim([0 2000]); +ylim([-80 0]); + +tot_pwr=sum(m(1:floor(length(m)/2))); +bw_percentage=0.999; +for obw=1:length(f) + if (sum(m(1:obw))/tot_pwr>bw_percentage) + break; + end +end + +fprintf('TX %5.2f%% cutoff frequency offset: %6.2f Hz\n',bw_percentage*100,2*fs/n_samp*obw); +