kopia lustrzana https://github.com/JamesP6000/PiCW
Added documentation.
rodzic
804afa8e29
commit
74bda60502
|
@ -1,3 +1,5 @@
|
|||
PiCW
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
Install required packages:
|
||||
sudo apt-get install git g++-4.7 make grep mawk ntp
|
||||
Note that this code requires g++-4.7 which is not installed by default in
|
||||
Raspbian!
|
||||
|
||||
Make sure you are using the latest kernel by updating your system. The latest
|
||||
kernel includes fixes wich improve NTP ppm measurement accuracy:
|
||||
sudo apt-get update
|
||||
sudo apt-get dist-upgrade
|
||||
|
||||
Get code/ compile:
|
||||
rm -rf PiCW
|
||||
git clone https://github.com/JamesP6000/PiCW.git
|
||||
cd PiCW
|
||||
make
|
||||
|
||||
Install to /usr/local/bin:
|
||||
sudo make install
|
||||
|
||||
Uninstall:
|
||||
sudo make uninstall
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Issues From original WsprryPi code:
|
||||
|
||||
Two users were reporting that the program never stops transmitting, even
|
||||
when intervals for disabled tx are programmed. The problem was in both
|
||||
cases fixed by flashing a new image on the SD card with a freshly downloaded
|
||||
image: 2013-02-09-wheezy-raspbian.zip. No apt-get upgrade or firmware
|
||||
upgrade was performed. After this WsprryPi TX was running successfully.
|
||||
|
||||
One user reported his RPi died while in WsprryPi service caused by excessive
|
||||
RF voltage (90V) on GPIO4 created by a 100 watts AM transmitter 50ft away
|
||||
from the antenna. After the damage exessive current was consumed by RPi (1.1A
|
||||
from 5V supply), caused by short-circuiting in the 3.3V logic of the BCM2835
|
||||
SOC. On his replacement RPi, he is planning to add galvanic isolation and
|
||||
buffering.
|
||||
|
273
PiCW.cpp
273
PiCW.cpp
|
@ -1,22 +1,23 @@
|
|||
// 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.
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
// Authors:
|
||||
// Oliver Mattos, Oskar Weigl, Dan Ankers (MD1CLV), Guido (PE1NNZ),
|
||||
// Michael Tatarinov, James Peroulas (AB0JP)
|
||||
|
||||
#include <map>
|
||||
#include <deque>
|
||||
|
@ -102,22 +103,6 @@ volatile unsigned *allof7e = NULL;
|
|||
#define DMABASE (0x7E007000)
|
||||
#define PWMBASE (0x7e20C000) /* PWM controller */
|
||||
|
||||
// g++ 4.7 in c++11 mode had trouble with this struct. I removed it
|
||||
// and used bit shifts to create the word to be written.
|
||||
/*
|
||||
struct GPCTL {
|
||||
char SRC : 4;
|
||||
char ENAB : 1;
|
||||
char KILL : 1;
|
||||
char : 1;
|
||||
char BUSY : 1;
|
||||
char FLIP : 1;
|
||||
char MASH : 2;
|
||||
unsigned int : 13;
|
||||
char PASSWD : 8;
|
||||
};
|
||||
*/
|
||||
|
||||
struct CB {
|
||||
volatile unsigned int TI;
|
||||
volatile unsigned int SOURCE_AD;
|
||||
|
@ -172,64 +157,6 @@ void freeRealMemPage(void* vAddr) {
|
|||
free(vAddr);
|
||||
}
|
||||
|
||||
void txon()
|
||||
{
|
||||
SETBIT(GPFSEL0 , 14);
|
||||
CLRBIT(GPFSEL0 , 13);
|
||||
CLRBIT(GPFSEL0 , 12);
|
||||
|
||||
// Set GPIO drive strength, more info: http://www.scribd.com/doc/101830961/GPIO-Pads-Control2
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 0; //2mA -3.4dBm
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 1; //4mA +2.1dBm
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 2; //6mA +4.9dBm
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 3; //8mA +6.6dBm(default)
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 4; //10mA +8.2dBm
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 5; //12mA +9.2dBm
|
||||
//ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 6; //14mA +10.0dBm
|
||||
ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 7; //16mA +10.6dBm
|
||||
|
||||
//struct GPCTL setupword = {6/*SRC*/, 1, 0, 0, 0, 3,0x5a};
|
||||
//ACCESS(CM_GP0CTL) = *((int*)&setupword);
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
// MASH
|
||||
(3<<9) |
|
||||
// Flip
|
||||
(0<<8) |
|
||||
// Busy
|
||||
(0<<7) |
|
||||
// Kill
|
||||
(0<<5) |
|
||||
// Enable
|
||||
(1<<4) |
|
||||
// SRC
|
||||
(6<<0)
|
||||
;
|
||||
}
|
||||
|
||||
void txoff()
|
||||
{
|
||||
//struct GPCTL setupword = {6/*SRC*/, 0, 0, 0, 0, 1,0x5a};
|
||||
//ACCESS(CM_GP0CTL) = *((int*)&setupword);
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
// MASH
|
||||
(1<<9) |
|
||||
// Flip
|
||||
(0<<8) |
|
||||
// Busy
|
||||
(0<<7) |
|
||||
// Kill
|
||||
(0<<5) |
|
||||
// Enable
|
||||
(0<<4) |
|
||||
// SRC
|
||||
(6<<0)
|
||||
;
|
||||
}
|
||||
|
||||
// Transmit tone tone_freq for tsym seconds.
|
||||
//
|
||||
// TODO:
|
||||
|
@ -306,7 +233,23 @@ void unSetupDMA(){
|
|||
//printf("exiting\n");
|
||||
struct DMAregs* DMA0 = (struct DMAregs*)&(ACCESS(DMABASE));
|
||||
DMA0->CS =1<<31; // reset dma controller
|
||||
txoff();
|
||||
// Turn off GPIO clock
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
// MASH
|
||||
(1<<9) |
|
||||
// Flip
|
||||
(0<<8) |
|
||||
// Busy
|
||||
(0<<7) |
|
||||
// Kill
|
||||
(0<<5) |
|
||||
// Enable
|
||||
(0<<4) |
|
||||
// SRC
|
||||
(6<<0)
|
||||
;
|
||||
}
|
||||
|
||||
void handSig(const int h) {
|
||||
|
@ -440,7 +383,6 @@ void setupDMA(
|
|||
DMA0->CS =(1<<0)|(255 <<16); // enable bit = 0, clear end flag = 1, prio=19-16
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Set up memory regions to access GPIO
|
||||
//
|
||||
|
@ -512,21 +454,23 @@ void setup_gpios(
|
|||
|
||||
void print_usage() {
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << " PiCW [options] \"MORSE TEXT TO SEND\"" << std::endl;
|
||||
std::cout << " PiCW [options] \"text to send in Morse code\"" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << " -h --help" << std::endl;
|
||||
std::cout << " Print out this help screen." << std::endl;
|
||||
std::cout << " -f --freq f" << std::endl;
|
||||
std::cout << " Specify the frequency to be used for the transmission" << std::endl;
|
||||
std::cout << " Specify the frequency to be used for the transmission." << std::endl;
|
||||
std::cout << " -w --wpm w" << std::endl;
|
||||
std::cout << " Specify the transmission speed in Words Per Minute" << std::endl;
|
||||
std::cout << " Specify the transmission speed in Words Per Minute (default 20 WPM)." << std::endl;
|
||||
std::cout << " -p --ppm ppm" << std::endl;
|
||||
std::cout << " Known PPM correction to 19.2MHz RPi nominal crystal frequency." << std::endl;
|
||||
std::cout << " -s --self-calibration" << std::endl;
|
||||
std::cout << " Call ntp_adjtime() periodically to obtain the PPM error of the crystal." << std::endl;
|
||||
std::cout << " -d --ditdit" << std::endl;
|
||||
std::cout << " Transmit an endless series of dits. Can be used to measure TX spectrum" << std::endl;
|
||||
std::cout << " Transmit an endless series of dits. Can be used to measure TX spectrum." << std::endl;
|
||||
std::cout << " -t --test-tone" << std::endl;
|
||||
std::cout << " Continuously transmit a test tone at the requested frequency." << std::endl;
|
||||
}
|
||||
|
||||
void parse_commandline(
|
||||
|
@ -539,7 +483,8 @@ void parse_commandline(
|
|||
double & ppm,
|
||||
bool & self_cal,
|
||||
std::string & str,
|
||||
bool & ditdit
|
||||
bool & ditdit,
|
||||
bool & test_tone
|
||||
) {
|
||||
// Default values
|
||||
tone_freq=NAN;
|
||||
|
@ -548,6 +493,7 @@ void parse_commandline(
|
|||
self_cal=false;
|
||||
str="";
|
||||
ditdit=false;
|
||||
test_tone=false;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
|
@ -556,6 +502,7 @@ void parse_commandline(
|
|||
{"ppm", required_argument, 0, 'p'},
|
||||
{"self-calibration", no_argument, 0, 's'},
|
||||
{"ditdit", no_argument, 0, 'd'},
|
||||
{"test-tone", no_argument, 0, 't'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
|
@ -606,6 +553,9 @@ void parse_commandline(
|
|||
case 'd':
|
||||
ditdit=true;
|
||||
break;
|
||||
case 't':
|
||||
test_tone=true;
|
||||
break;
|
||||
case '?':
|
||||
/* getopt_long already printed an error message. */
|
||||
ABORT(-1);
|
||||
|
@ -633,12 +583,17 @@ void parse_commandline(
|
|||
ABORT(-1);
|
||||
}
|
||||
if ((!str.empty())&&ditdit) {
|
||||
MARK;
|
||||
std::cout << str << std::endl;
|
||||
MARK;
|
||||
std::cerr << "Error: cannot transmit text when ditdit mode is requested" << std::endl;
|
||||
ABORT(-1);
|
||||
}
|
||||
if ((!str.empty())&&test_tone) {
|
||||
std::cerr << "Error: cannot transmit text when test-tone mode is requested" << std::endl;
|
||||
ABORT(-1);
|
||||
}
|
||||
if (test_tone&&ditdit) {
|
||||
std::cerr << "Error: cannot request test-tone and ditdit modes at the same time" << std::endl;
|
||||
ABORT(-1);
|
||||
}
|
||||
|
||||
// Print a summary of the parsed options
|
||||
std::cout << "PiCW parsed command line options:" << std::endl;
|
||||
|
@ -647,7 +602,9 @@ void parse_commandline(
|
|||
temp << tone_freq/1e6 << " MHz";
|
||||
std::cout << " TX frequency: " << temp.str() << std::endl;
|
||||
temp.str("");
|
||||
std::cout << " WPM: " << wpm << std::endl;
|
||||
if (!test_tone) {
|
||||
std::cout << " WPM: " << wpm << std::endl;
|
||||
}
|
||||
if (self_cal) {
|
||||
temp << " ntp_adjtime() will be used to periodically calibrate the transmission frequency" << std::endl;
|
||||
} else if (ppm) {
|
||||
|
@ -655,6 +612,8 @@ void parse_commandline(
|
|||
}
|
||||
if (ditdit) {
|
||||
std::cout << "Will transmit an endless series of dits. CTRL-C to exit." << std::endl;
|
||||
} else if (test_tone) {
|
||||
std::cout << "Will transmit continuous tone on frequency. CTRL-C to exit." << std::endl;
|
||||
} else {
|
||||
std::cout << "Message to be sent:" << std::endl;
|
||||
std::cout << '"' << str << '"' << std::endl;
|
||||
|
@ -746,12 +705,15 @@ void tone_main(
|
|||
}
|
||||
}
|
||||
|
||||
// The rise and fall ramps are stored as collections of time/value pairs.
|
||||
class time_value {
|
||||
public:
|
||||
std::chrono::duration <double> time;
|
||||
unsigned int value;
|
||||
};
|
||||
|
||||
// Rectangular ramp that simply goes high or low in the middle of the ramp
|
||||
// period.
|
||||
void rectangle(
|
||||
const double & width_secs,
|
||||
std::vector <time_value> & rise,
|
||||
|
@ -787,24 +749,7 @@ void rectangle(
|
|||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Raised cosine rise/ fall ramps.
|
||||
void raised_cosine(
|
||||
const double & width_secs,
|
||||
std::vector <time_value> & rise,
|
||||
|
@ -852,8 +797,7 @@ void set_current(
|
|||
value=8;
|
||||
}
|
||||
if (value==0) {
|
||||
//struct GPCTL setupword = {6/*SRC*/, 0, 0, 0, 0, 1,0x5a};
|
||||
//ACCESS(CM_GP0CTL) = *((int*)&setupword);
|
||||
// Turn off output
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
|
@ -871,9 +815,9 @@ void set_current(
|
|||
(6<<0)
|
||||
;
|
||||
} else {
|
||||
// Set drive strength
|
||||
ACCESS(PADS_GPIO_0_27) = 0x5a000018 + ((value - 1)&0x7);
|
||||
//struct GPCTL setupword = {6/*SRC*/, 1, 0, 0, 0, 3,0x5a};
|
||||
//ACCESS(CM_GP0CTL) = *((int*)&setupword);
|
||||
// Turn on output
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
|
@ -913,7 +857,7 @@ void send_dit_dah(
|
|||
const std::chrono::duration <double> jitter_rise(dis(gen));
|
||||
const std::chrono::duration <double> jitter_fall(dis(gen));
|
||||
|
||||
// Calculate the rise and fall ramps.
|
||||
// Calculate the rise and fall ramps, if needed.
|
||||
static bool initialized=false;
|
||||
static std::chrono::duration <double> ramp_time_prev(0);
|
||||
static std::vector <time_value> rise;
|
||||
|
@ -935,7 +879,7 @@ void send_dit_dah(
|
|||
initialized=true;
|
||||
}
|
||||
|
||||
// Pulse will be timed relative to the current time.
|
||||
// Dit or dah 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.
|
||||
|
@ -978,8 +922,21 @@ void am_main(
|
|||
std::map <char,std::string> & morse_table,
|
||||
std::atomic <double> & wpm,
|
||||
std::atomic <bool> & busy,
|
||||
const bool & ditdit
|
||||
const bool & ditdit,
|
||||
const bool & test_tone
|
||||
) {
|
||||
// In the case of a test tone, set the drive strength to maximum and
|
||||
// turn on output. Nothing else.
|
||||
if (test_tone) {
|
||||
set_current(8);
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (terminate) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool prev_char_whitespace=true;
|
||||
std::chrono::time_point <std::chrono::high_resolution_clock,std::chrono::duration <double>> earliest_tx_time=std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
@ -1043,14 +1000,12 @@ void am_main(
|
|||
} else {
|
||||
tx_pattern=morse_table[tx_char];
|
||||
}
|
||||
bool printed=false;
|
||||
for (unsigned int t=0;t<tx_pattern.length();t++) {
|
||||
std::this_thread::sleep_until(earliest_tx_time);
|
||||
if (terminate) {
|
||||
return;
|
||||
}
|
||||
if ((!ditdit)&&(!printed)) {
|
||||
printed=true;
|
||||
if ((!ditdit)&&(t==0)) {
|
||||
std::cout << tx_char;
|
||||
std::cout.flush();
|
||||
}
|
||||
|
@ -1065,11 +1020,11 @@ void am_main(
|
|||
t=0;
|
||||
}
|
||||
}
|
||||
earliest_tx_time+=std::chrono::duration <double> (3*dot_duration_sec);
|
||||
|
||||
earliest_tx_time+=std::chrono::duration <double> (2*dot_duration_sec);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the morse code table.
|
||||
void morse_table_init(
|
||||
std::map <char,std::string> & morse_table
|
||||
) {
|
||||
|
@ -1127,16 +1082,14 @@ void morse_table_init(
|
|||
}
|
||||
|
||||
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;
|
||||
bool ditdit;
|
||||
std::string str;
|
||||
bool ditdit;
|
||||
bool test_tone;
|
||||
parse_commandline(
|
||||
argc,
|
||||
argv,
|
||||
|
@ -1145,7 +1098,8 @@ int main(const int argc, char * const argv[]) {
|
|||
ppm_init,
|
||||
self_cal,
|
||||
str,
|
||||
ditdit
|
||||
ditdit,
|
||||
test_tone
|
||||
);
|
||||
|
||||
// Initial configuration
|
||||
|
@ -1167,53 +1121,14 @@ int main(const int argc, char * const argv[]) {
|
|||
ABORT(-1);
|
||||
}
|
||||
|
||||
//txon();
|
||||
// Configure GPIO4
|
||||
SETBIT(GPFSEL0 , 14);
|
||||
CLRBIT(GPFSEL0 , 13);
|
||||
CLRBIT(GPFSEL0 , 12);
|
||||
// Set GPIO drive strength, more info: http://www.scribd.com/doc/101830961/GPIO-Pads-Control2
|
||||
/*
|
||||
ACCESS(PADS_GPIO_0_27) = 0x5a000018 + 0; //2mA -3.4dBm
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
// MASH
|
||||
(3<<9) |
|
||||
// Flip
|
||||
(0<<8) |
|
||||
// Busy
|
||||
(0<<7) |
|
||||
// Kill
|
||||
(0<<5) |
|
||||
// Enable
|
||||
(0<<4) |
|
||||
// SRC
|
||||
(6<<0)
|
||||
;
|
||||
*/
|
||||
struct PageInfo constPage;
|
||||
struct PageInfo instrPage;
|
||||
struct PageInfo instrs[1024];
|
||||
setupDMA(constPage,instrPage,instrs);
|
||||
//txoff();
|
||||
/*
|
||||
ACCESS(CM_GP0CTL) =
|
||||
// PW
|
||||
(0x5a<<24) |
|
||||
// MASH
|
||||
(1<<9) |
|
||||
// Flip
|
||||
(0<<8) |
|
||||
// Busy
|
||||
(0<<7) |
|
||||
// Kill
|
||||
(0<<5) |
|
||||
// Enable
|
||||
(0<<4) |
|
||||
// SRC
|
||||
(6<<0)
|
||||
;
|
||||
*/
|
||||
|
||||
// Morse code table.
|
||||
std::map <char,std::string> morse_table;
|
||||
|
@ -1242,7 +1157,6 @@ int main(const int argc, char * const argv[]) {
|
|||
while (!tone_thread_ready) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
//std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// Start AM thread
|
||||
std::atomic <bool> terminate_am_thread;
|
||||
|
@ -1260,7 +1174,8 @@ int main(const int argc, char * const argv[]) {
|
|||
std::ref(morse_table),
|
||||
std::ref(wpm),
|
||||
std::ref(am_thread_busy),
|
||||
ditdit
|
||||
ditdit,
|
||||
test_tone
|
||||
);
|
||||
|
||||
// Push text into AM thread
|
||||
|
@ -1275,8 +1190,8 @@ int main(const int argc, char * const argv[]) {
|
|||
queue_signal.notify_one();
|
||||
}
|
||||
|
||||
// In ditdit mode, can only exit using ctrl-c.
|
||||
while (ditdit) {
|
||||
// In ditdit or test-tone mode, can only exit using ctrl-c.
|
||||
while (ditdit||test_tone) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
Raspberry Pi bareback LF/MF/HF/VHF CW (Morse code) transmitter
|
||||
|
||||
Makes a very simple Morse Code transmitter from your RasberryPi by connecting
|
||||
GPIO port 4 to Antenna (and LPF). Operates on LF, MF, HF and VHF bands from 0
|
||||
to 250 MHz.
|
||||
|
||||
******
|
||||
Installation / update:
|
||||
******
|
||||
Simple instructions assuming you have all the prerequisites installed:
|
||||
git clone https://github.com/JamesP6000/PiCW.git
|
||||
cd PiCW
|
||||
make
|
||||
|
||||
See the accompanying BUILD file for more details.
|
||||
|
||||
******
|
||||
Example usage:
|
||||
******
|
||||
Brief help screen
|
||||
PiCW --help
|
||||
|
||||
Send the Morse code message "TEST DE N9NNN" on carrier frequency 10.140 MHz
|
||||
using the default rate of 20 WPM:
|
||||
sudo PiCW --freq 10.140e6 TEST DE N9NNN
|
||||
|
||||
As above, but this time use NTP to calibrate the TX frequency:
|
||||
sudo PiCW --freq 10.140e6 --self-calibration TEST DE N9NNN
|
||||
|
||||
Transmit an endless series of dits at 60 WPM. Can be used to measure the
|
||||
worst case frequency domain performance of the transmitter.
|
||||
sudo PiCW --freq 10.140e6 --ditdit --wpm 60
|
||||
|
||||
******
|
||||
"PiCW --help" output:
|
||||
******
|
||||
Usage:
|
||||
PiCW [options] "text to send in Morse code"
|
||||
|
||||
Options:
|
||||
-h --help
|
||||
Print out this help screen.
|
||||
-f --freq f
|
||||
Specify the frequency to be used for the transmission.
|
||||
-w --wpm w
|
||||
Specify the transmission speed in Words Per Minute (default 20 WPM).
|
||||
-p --ppm ppm
|
||||
Known PPM correction to 19.2MHz RPi nominal crystal frequency.
|
||||
-s --self-calibration
|
||||
Call ntp_adjtime() periodically to obtain the PPM error of the crystal.
|
||||
-d --ditdit
|
||||
Transmit an endless series of dits. Can be used to measure TX spectrum.
|
||||
-t --test-tone
|
||||
Continuously transmit a test tone at the requested frequency.
|
||||
|
||||
******
|
||||
Radio licensing / RF:
|
||||
******
|
||||
In order to transmit legally, a HAM Radio License is REQUIRED for running
|
||||
this experiment. The output is a square wave so a low pass filter is REQUIRED.
|
||||
Connect a low-pass filter (via decoupling C) to GPIO4 (GPCLK0) and a ground
|
||||
pin of your Raspberry Pi, then connect an antenna to the LPF. The GPIO4 and
|
||||
GND pins are found on header P1 pin 7 and 9 respectively, the pin closest to
|
||||
P1 label is pin 1 and its 3rd and 4th neighbour is pin 7 and 9 respectively.
|
||||
See this link for pin layout: http://elinux.org/RPi_Low-level_peripherals
|
||||
Examples of low-pass filters can be found here:
|
||||
http://www.gqrp.com/harmonic_filters.pdf
|
||||
|
||||
The expected power output is 10mW (+10dBm) in a 50 Ohm load. This looks
|
||||
neglible, but when connected to a simple dipole antenna this may result in
|
||||
reception reports ranging up to several thousands of kilometers.
|
||||
|
||||
As the Raspberry Pi does not attenuate ripple and noise components from the
|
||||
5V USB power supply, it is RECOMMENDED to use a regulated supply that has
|
||||
sufficient ripple supression. Supply ripple might be seen as mixing products
|
||||
centered around the transmit carrier typically at 100/120Hz.
|
||||
|
||||
DO NOT expose GPIO4 to voltages or currents that are above the specified
|
||||
Absolute Maximum limits. GPIO4 outputs a digital clock in 3V3 logic, with a
|
||||
maximum current of 16mA. As there is no current protection available and a DC
|
||||
component of 1.6V, DO NOT short-circuit or place a resistive (dummy) load
|
||||
straight on the GPIO4 pin, as it may draw too much current. Instead, use a
|
||||
decoupling capacitor to remove DC component when connecting the output to
|
||||
dummy loads, transformers, antennas, etc. DO NOT expose GPIO4 to electro-
|
||||
static voltages or voltages exceeding the 0 to 3.3V logic range. Connecting
|
||||
an antenna directly to GPIO4 may damage your RPi due to transient voltages
|
||||
such as lightning or static buildup as well as RF from other transmitters
|
||||
operating into nearby antennas. Therefore it is RECOMMENDED to add some form
|
||||
of isolation, e.g. by using a RF transformer, a simple buffer/driver/PA
|
||||
stage, two schottky small signal diodes back to back.
|
||||
|
||||
******
|
||||
Calibration:
|
||||
******
|
||||
Frequency calibration is HIGHLY recommended to ensure that your
|
||||
transmissions lie within the CW band you are targetting.
|
||||
|
||||
NTP calibration:
|
||||
NTP automatically tracks and calculates a PPM frequency correction. If your
|
||||
Pi is connected to the internet and you are running NTP, you can use the
|
||||
--self-calibration option to have PiCW periodically querry NTP for the latest
|
||||
frequency correction. Some residual frequency error may still be present
|
||||
due to delays in the NTP measurement loop. This method works best if your
|
||||
Pi has been on for a long time, the crystal's temperature has stabilized,
|
||||
and the NTP control loop has converged.
|
||||
|
||||
AM calibration:
|
||||
A practical way to calibrate is to tune the transmitter on the same frequency
|
||||
of a medium wave AM broadcast station. Keep tuning until you zero beat (the
|
||||
constant audio tone disappears when the transmitter is exactly on the same
|
||||
frequency as the broadcast station), and determine the frequency difference
|
||||
with the broadcast station. This is the frequency error that can be applied
|
||||
for correction while tuning on a WSPR frequency.
|
||||
|
||||
Suppose your local AM radio station is at 780kHz. Use the --test-tone option
|
||||
to produce different tones around 780kHz (eg 780100 Hz) until you can
|
||||
successfully zero beat the AM station. If the zero beat tone specified on the
|
||||
command line is F, calculate the PPM correction required as:
|
||||
ppm=(F/780000-1)*1e6 In the future, specify this value as the argument to the
|
||||
--ppm option on the command line.
|
||||
|
||||
******
|
||||
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 transmission will
|
||||
interfere with CW transmissions. Sound can be permanently disabled
|
||||
by editing /etc/modules and commenting out the snd-bcm2835 device.
|
||||
|
||||
******
|
||||
Reference documentation:
|
||||
******
|
||||
http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
|
||||
http://www.scribd.com/doc/127599939/BCM2835-Audio-clocks
|
||||
http://www.scribd.com/doc/101830961/GPIO-Pads-Control2
|
||||
https://github.com/mgottschlag/vctools/blob/master/vcdb/cm.yaml
|
||||
https://www.kernel.org/doc/Documentation/vm/pagemap.txt
|
||||
|
||||
******
|
||||
History/Credits:
|
||||
******
|
||||
Credits go to Oliver Mattos and Oskar Weigl who implemented PiFM based on
|
||||
the idea of exploiting RPi DPLL as FM transmitter.
|
||||
http://www.icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter
|
||||
|
||||
Dan MD1CLV combined this effort with WSPR encoding algorithm from F8CHK,
|
||||
resulting in WsprryPi a WSPR beacon for LF and MF bands.
|
||||
https://github.com/DanAnkers/WsprryPi
|
||||
|
||||
Guido PE1NNZ <pe1nnz@amsat.org> extended this effort with DMA based PWM
|
||||
modulation of fractional divider that was part of PiFM, allowing to operate
|
||||
the WSPR beacon also on HF and VHF bands. In addition time-synchronisation
|
||||
and double amount of power output was implemented.
|
||||
https://github.com/threeme3/WsprryPi
|
||||
|
||||
James Peroulas <james@peroulas.com> added several command line options, a
|
||||
makefile, improved frequency generation precision so as to be able to
|
||||
precisely generate a tone at a fraction of a Hz, and a self calibration
|
||||
feature where the code attempts to derrive frequency calibration information
|
||||
from an installed NTP deamon.
|
||||
https://github.com/JamesP6000/WsprryPi
|
||||
|
||||
Michael Tatarinov for adding a patch to get PPM info directly from the
|
||||
kernel.
|
||||
|
||||
James Peroulas <james@peroulas.com> created PiCW.
|
||||
https://github.com/JamesP6000/PiCW
|
||||
|
16
makefile
16
makefile
|
@ -3,15 +3,13 @@ prefix=/usr/local
|
|||
all: PiCW
|
||||
|
||||
PiCW: PiCW.cpp
|
||||
g++-4.7 -D_GLIBCXX_DEBUG -std=c++11 -Wall -Werror -fmax-errors=5 -lm PiCW.cpp -oPiCW -pthread
|
||||
g++-4.7 -D_GLIBCXX_DEBUG -std=c++11 -Wall -Werror -fmax-errors=5 -lm PiCW.cpp -pthread -oPiCW
|
||||
|
||||
#.PHONY: install
|
||||
#install: wspr
|
||||
# install -m 0755 wspr $(prefix)/bin
|
||||
# install -m 0755 gpioclk $(prefix)/bin
|
||||
.PHONY: install
|
||||
install: PiCW
|
||||
install -m 0755 PiCW $(prefix)/bin
|
||||
|
||||
#.PHONY: uninstall
|
||||
#uninstall:
|
||||
# rm -f $(prefix)/bin/wspr
|
||||
# rm -f $(prefix)/bin/gpioclk
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
rm -f $(prefix)/bin/PiCW
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
% Octave/ Matlab code used to explore the frequency domain effects of various
|
||||
% pulse shaping functions.
|
||||
|
||||
pkg load signal
|
||||
|
||||
fs=1e6;
|
||||
wpm=60;
|
||||
ramp_excess=.3;
|
||||
|
||||
% Derive some values
|
||||
period=1200/wpm*.001*2;
|
||||
ramp_time=period/2*ramp_excess;
|
||||
flat_time=period/2*(1-ramp_excess);
|
||||
|
@ -13,6 +17,7 @@ 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);
|
||||
|
||||
% Different ramp functions
|
||||
%ramp_func=@(x)x;
|
||||
%ramp=ramp_func(linspace(0,1,n_ramp));
|
||||
ramp_func=@(x)(-cos(x*pi)+1)/2;
|
||||
|
@ -30,12 +35,15 @@ ramp=ramp_func(linspace(0,1,n_ramp));
|
|||
ramp=ramp-ramp(1);
|
||||
ramp=ramp/max(ramp)*1;
|
||||
|
||||
% Create signal
|
||||
sig=[ramp ones(1,n_flat) fliplr(ramp) zeros(1,n_samp-n_flat-2*n_ramp)];
|
||||
%sig=sig-mean(sig);
|
||||
|
||||
% Quantize
|
||||
sig=round(sig*8)/8;
|
||||
sig=sig+randn(1,length(sig))/10000;
|
||||
|
||||
% Plot frequency response
|
||||
f=linspace(0,2*fs,n_samp+1);
|
||||
f=f(1:end-1);
|
||||
m=abs((fft(sig))).^2;
|
||||
|
@ -44,6 +52,7 @@ plot(f,10*log10(m));
|
|||
xlim([0 2000]);
|
||||
ylim([-80 0]);
|
||||
|
||||
% Calculate occupied bandwidth
|
||||
tot_pwr=sum(m(1:floor(length(m)/2)));
|
||||
bw_percentage=0.999;
|
||||
for obw=1:length(f)
|
||||
|
@ -52,5 +61,6 @@ for obw=1:length(f)
|
|||
end
|
||||
end
|
||||
|
||||
% Occupied bandwidth is from -2*fs/n_samp*obw to +2*fs/n_samp*obw.
|
||||
fprintf('TX %5.2f%% cutoff frequency offset: %6.2f Hz\n',bw_percentage*100,2*fs/n_samp*obw);
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue