Saturday, June 3, 2017

Step by step install of the SDRPlay RSP2 under Ubuntu 16.04 and other debian based linux.


    The SDRPlay RSP2 is an excellent bit of kit.   A highly sensitive SDR with multiple antenna inputs,  input filtering, 10 Mhz bandwidth, and a range from 2Ghz all the way down to 1Khz.   On the windows side, they provide a very nice SDR program called SDR uno.

   Those of us not running windows, however, have a few choices in software.   GQRX is a popular program, built on GNU-radio.  There's a couple of others that escape my mind, but the newcomer, CubucSDR, is starting to stand out.


   After much searching and experimenting,  I have come up with a list of steps that will build and install the needed support frameworks and software to get you up and running with CubicSDR and your SDRPlay.  
CubicSDR manual is here, worth reading through, you might get confused in the software without it.
http://cubicsdr.readthedocs.io/en/latest/index.html

    There are a few software packages that are needed for the SDRPlay to work.   SoapySDR is a framework for talking to several SDR hardware types, Cubic relies on it.   We'll also need the specific soapy module for the SDRPlay, and a few other packages needed to build cubic.

   I have created two scripts that largely automate the installation.   You can download them using the following link:

                    Click to download. Let me know if this link is broken.


And here's a video where I talk about this whole project.


  Now, the instructions.

   You will need to create a folder on your desktop named 'sdrplay' and put these script files there. You will also need to download the driver for linux from the SDRPlay site and also place it in that folder.  Do not install it yet!

   Open a terminal and change into your newly created directory with the command:

cd ~/Desktop/sdrplay

    Now, you can run the first script.  This will install build software from ubuntu's repositories, along with dependencies needed by the packages we will be building.   It will also clone sever github projects, including soapySDR and CubicSDR.

   The script MUST be run as root, so use sudo to execute it:

sudo ./Install_script-pt1.sh

   It will take awhile and will ask you to type 'y' once for the installs.   Once it's completed,  you will need to run the installer from the SDRPlay site.  Again, you need to make sure you run their script as root with sudo.  If it's not executable,  you can run:

sudo chmod +x ./{scriptname}

(where {scriptname} is the name of the downloaded file)

It will show you a license agreement you need to spacebar through and hit 'y' to accept.

   After their script is done, you can run the install script part two, again with sudo:

sudo ./Install_script-pt2.sh

  This one will also take awhile while building CubicSDR.   Once done, you should be all ready to go!   CubicSDR won't automatically be added to any menus, you'll have to do that yourself.  You can also run it from within the terminal by simply typing CubicSDR.    If you had the SDRPlay plugged in during this installation,  unplug it for a few seconds and then plug it back in.  When you run Cubic, it should find the unit and list it for selection.

ADDENDUM:  These scripts also work on the raspberry Pi 3 with the current version of raspian.  Obviously you need to download the raspberry pi version of the hardware driver from the SDRPlay site.  
There's just one extra step to get pulse audio working under raspian.   You need to modify one of the autostart scripts.
In a terminal,  type:

sudo pico /etc/xdg/autostart/pulseaudio.desktop

Scroll down and find the line:

Exec=start-pulseaudio-x11

Change that line to:

Exec=/bin/sh -c "Sleep 5; start-pulseaudio-x11"

Ctrl-o to write the changes,  Ctrl-x to exit.  Now reboot the pi and the pulse audio server will start.  This probably fixes other things that rely on pulse as well.

Have fun!

Kevin,  KB9RLW

The scripts are pasted below.  you can follow through their steps manually with copy/paste into your terminal. (don't forget sudo).  Or you can copy each in it's entirety and paste it into an editor, save it, mark it as executable and then run it.

#/*    script 1 below /*

#! /bin/bash
# This script should install and build all needed stuff for
# using the SDRPlay on Ubuntu 16.04 or later.  It must be
# run as root.
#
# First we install needed dependancies.

apt-get install git build-essential automake cmake g++ swig
apt-get install libgtk2.0-dev libpulse-dev libpython-dev python-numpy.
apt-get install mesa-utils libeglw1-mesa libglw1-mesa-dev
apt-get install freeglut3-dev freeglut3

# Now we'll git the projects we need

git clone https://github.com/jgaeddert/liquid-dsp
wget https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.0/wxWidgets-3.1.0.tar.bz2
tar -xvjf wxWidgets-3.1.0.tar.bz2
git clone https://github.com/pothosware/SoapySDR.git
git clone https://github.com/pothosware/SoapySDRPlay.git
git clone https://github.com/pothosware/SoapyRemote.git
git clone https://github.com/cjcliffe/CubicSDR.git

# Build liquid-dsp

cd liquid-dsp
./bootstrap.sh
./configure --enable-fftoverride
make -j4
make install
ldconfig
cd ..

# Build wxwidgets

cd wxWidgets-3.1.0/
mkdir -p ~/Develop/wxWidgets-staticlib
./autogen.sh
./configure --with-opengl --disable-shared --enable-monolithic --with-libjpeg --with-libtiff --with-libpng --with-zlib --disable-sdltest --enable-unicode --enable-display --enable-propgrid --disable-webkit --disable-webview --disable-webviewwebkit --prefix=`echo ~/Develop/wxWidgets-staticlib` CXXFLAGS="-std=c++0x" --with-libiconv=/usr
make -j4
make install
cd ..

echo "."
echo "First part of installation done.   Now run the SDRPlay file you"
echo "downloaded from their site.  Be sure to use sudo to run it!"

#/* end of script 1 /*


#/* script 2 below /*

#! /bin/bash
# This script should install and build all needed stuff for
# using the SDRPlay on Ubuntu 16.04 or later.  It must be
# run as root.
#
# This is part two.
# Here we build SoapySDR

cd SoapySDR
mkdir build
cd build
cmake ..
make -j4
make install
ldconfig
cd ..
cd ..

# Now we build the SDRPlay module for Soapy

cd SoapySDRPlay
mkdir build
cd build
cmake ..
make
make install
cd ..
cd ..

# And we build SoapyRemote

cd SoapyRemote
mkdir build
cd build
cmake ..
make
make install
cd ..
cd ..

# And finally,  we build Cubic.  This takes awhile!

cd CubicSDR
mkdir build
cd build
cmake ../ -DCMAKE_BUILD_TYPE=Release -DwxWidgets_CONFIG_EXECUTABLE=~/Develop/wxWidgets-staticlib/bin/wx-config
make
make install
cd ..
cd ..
rm -R ~/Develop

# now we change permissions on these root-owned folders so the user can
# delete them at their leisure.

chmod -R 0777 ./*

# And we're done.   Cubic should work with the SDRPlay.  The user can
# run it from the terminal with CubicSDR or add this command to a menu.

#/* end of script 2 /*

Monday, May 1, 2017

The Arduino based Super Simple Keyer

     I decided to build my own keyer.  I needed a new Arduino project and this seemed like the perfect one.    I had a few ideas for the design.

     I wanted a keyer that would take multiple inputs,  paddles, straight key, and computer keyboard.  The idea being you can send however you like, without needing to change around cables, just send.

     There are many arduino morse projects around already.  I suppose I could have just downloaded an existing one, dropped it in and ran with it.  But I wanted to tackle the challenge of coding it myself.



     Once I'd sorted the hardware out,  I started to work on the software.   It turned out to be a bigger challenge than I'd expected!   It's tricky, the timing of reading the paddles and keying.   I came up with an interesting solution to timing when it comes to keying the rig.

     You see, the main loop in the arduino sketch can vary in how fast it executes, based on how much work it has to do.   My solution was to use a timer in the chip and schedule an interrupt.

     All processors have timers for this.   They run at a set speed and can interrupt the processor, forcing it to execute a small routine at precise intervals.  This is useful in robotics, for example, like in a flying drone.  You would need adjustments to the motor speeds to be occurring fast and regularly.

    So, in my keyer,  there is an interrupt that is running a keying routine once every millisecond.  That's 1000 times per second,  precisely.   That allows me to control the timing of the keying exactly and produce clean, consistent Morse out to the radio.    This routing watches a buffer that I load with events using the characters period,  dash, and comma.    Period and dash are obviously the Morse elements, and the comma is a delay equivalent to a dot.

    So to send an 'A' for example,  you'd load the event buffer with  .,-,   dot comma dash comma.   The interrupt routine would immediately key the transmitter with cleanly spaced precise Morse.

    The hardware is extremely simple.  The schematic is below.   The pins chosen are arbitrary and can be changed to suit your build if needed.  They are referenced in the first few lines of the code and you change those to reflect the pins you use.   The keying is handled by a simple transistor switch.  You could use an opto-isolator if you wish,  but I've had no trouble keying modern rigs this way.





   The software is below.  Simply copy and paste it into the Arduino IDE to upload it to your board.  Upon startup it will briefly key the radio for a fraction of a second and then be ready to accept input.  I wired an additional input jack for a straight key.  That was simply parallel to the keying output and done for convenience.

    For keyboard input, the code monitors the serial input.  When the Arduino is powered by a USB connection to your computer, it will create a serial port.   Simply run a terminal program and connect it to that port to allow you to send keyboard CW.  You want to set your terminal program to 9600 baud, no parity, 1 stop bit and 8 data bits.  Type to send at the current speed.  Simple.

   The code is up to version 1.4.   Paddle routines have been heavily worked on,  2 memories have been added.    shft-alt-1 to load memory one,  alt-1 to send memory one.  Same for memory 2,  shft-alt-2 and alt-2.

Code pasted below:

/*

  Super Simple Keyer Ver 1.4  Arduino based keyer
  Written Apr. 30, 2017 by Kevin Loughin, KB9RLW

Ver 1
  Initial release, keyer working ok, not iambic

Ver 1.2
  First iambic code, dependent on timing, not perfect

Ver 1.3
  Rewrite of keyer logic, iambic and dot dash buffering working!

Ver 1.4
  Tweaked paddles a bit.  Added 2 memories.  shft-alt-1 to set mem 1, shft-alt-2 to set memory 2
  alt-1 to send memory 1, alt-2 to send memory 2.
  Tweaked character spacing for keyboard decode. Added BT using the minus sign.

*/

//  Pins we're going to use.  Change as needed.
const int wpmraw = A7;  //Pot sense for speed
const int dotin = 4;  //dot sense pin
const int dashin = 2; //dash sense pin
const int keyout = 13; //keying output

// declaring variables we'll use in program

int delayval = 150;  //delay between elements in milliseconds
String event = "";  //event contains the elements currently being sent, .=dot -=dash ,=space
String memory1 = ""; //user memory 1
String memory2 = ""; //user memory 2
String membuff; // buffer for memory sending
int dotlen = 150;  //current dot length in milliseconds
int dashlen = (dotlen * 3);  //dash length in milliseconds
int active = 0; //keying handler flag
int delaycount = 0; // delay counter in interrup handler
int dotstate = 1; // dot paddle input pins reading
int dotread = 0;
int dashstate = 1; //dash paddle input pins reading
int lastdotstate = 1;
int lastdashstate = 1;
int dashread;
int lastdashdebounce;
int lastdotdebounce;
int eventbuffer = 0;  // event buffering
int dashhalf;
int lastchar = 1;
int c;
int dotbuffer;
int dashbuffer;
int dashdelay;
int dotdelay;
int flag;

void setup() {
  // Turn off keying pin right away

  pinMode (keyout, OUTPUT);
  digitalWrite(keyout, LOW);


  // setup input pins
  pinMode (dotin, INPUT);
  pinMode (dashin, INPUT);
  digitalWrite(dotin, HIGH);
  digitalWrite(dashin, HIGH);
  Serial.begin(9600);

  //  setup the interrupt
    OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
}


//  interrupt handler
SIGNAL(TIMER0_COMPA_vect)
{
 if (  event.length() > 0  ) //do we have anything to do?
 {
   if ( !active ) //are we not currently sending?
   {
     if ( event.charAt(0) == '.')  //do we need to send a dot?
     {
      digitalWrite(keyout, HIGH); //key on
      delaycount = dotlen;
      active = 1;
      lastchar = 1;
     }
     if ( event.charAt(0) == '-' ) //do we need to send a dash?
     {
      digitalWrite(keyout, HIGH); //key on
      delaycount = dashlen;
      active = 1;
      lastchar = 2;
   
     }
     if ( event.charAt(0) == ',' )  //do we need to delay between elements?
     {
       delaycount = dotlen;
      digitalWrite(keyout, LOW); //key off
      active = 1;
      }

     }
   
    }
    if ( active ) // we're already active, count and reset when done
     {
       delaycount--;
        if ( !delaycount )  //are we done with this element?
        {
          active = 0;
          digitalWrite(keyout, LOW); //just make sure the key is off
          event.remove(0, 1);
        }
      }
  }


void loop()
{
  // read the speed and set timing

dotlen = ( 200 - (.146 * analogRead(wpmraw)) );
dashlen = ( 3 * dotlen );

//  Read paddle inputs

          // read the dash pin
  dashread = digitalRead(dashin);
   if ( dashread != lastdashstate )
      lastdashdebounce = millis();
   
    if ( (millis() - lastdashdebounce) > 40 )
    {
      if ( dashread != dashstate )
        dashstate = dashread;
    }
    lastdashstate = dashread;
 
  // read the dot pin
  dotread = digitalRead(dotin);
 if (dotread != lastdotstate)
      lastdotdebounce = millis();
   
    if ((millis() - lastdotdebounce) > 40)
    {
      if (dotread != dotstate)
        dotstate = dotread;
    }
    lastdotstate = dotread;


// Keyer logic

while (!dotstate || !dashstate )  //   if paddles pressed, do
  {
    if ( event.length() < 3 )  // don't bother unless there's no more than 1 chars in buffer
    {

      if ( !dotstate && !dashstate ) // both paddles?
      {
        if ( lastchar == 1 && ( delaycount < dashlen ))
        {
          event = String(event + "-,");
          lastchar = 2;break;
        }
        if ( lastchar == 2 && ( delaycount < dashlen ))
        {
          event = String(event + ".,");
          lastchar = 1;break;
        }
      }

      if ( !dotstate && dashstate )  // only dot paddle
      {
        if ( lastchar == 1 && ( delaycount < dashlen) && event.length() < 1)
        {
          event = String(event + ".,");break;
        }
        if ( lastchar == 2 && event.length() < 3)
        {
          event = String(event + ".,");break;
        }
      }

      if ( dotstate && !dashstate )  // only dash paddle
      {
        if ( lastchar == 1 && (delaycount < dotlen))
        {
          event = String(event + "-,");break;
        }
        if ( lastchar == 2 && (delaycount < (dotlen * 2)) & event.length() < 2)
        {
          event = String(event + "-,");break;
        }
      }
    }
break;
  }
delay(12);

//  Keyboard routines

while(Serial.available())
 {
  c = Serial.read();
  Serial.write(c);
  // check for special keys
  if ( c == 27 ) // alt key pressed
    {
      flag = 1;
      c = Serial.read();
    }
  if ( flag && c == 33 ) // shft-alt-1, memory 1 define
    {
    loadmemory(1);
    flag = 0;
    c = 0;
    }
  if ( flag && c == 64 ) // shift-alt-2, memory 2 define
    {
      loadmemory(2);
      flag = 0;
      c = 0;
    }
  if ( flag && c == 49 ) // alt 1, send memory 1
    {
      sendmemory(1);
      flag = 0;
      c = 0;
    }
  if ( flag && c == 50 ) // alt 2, send memory 2
    {
      sendmemory(2);
      flag = 0;
      c = 0;
    }
 
  decoder(c);  // call the keyboard decoder to load this event
 }
}

void loadmemory(int num) // interact with user to load a memory
{
  String inData;
  char received;
  Serial.println( "." );
  Serial.print( "Enter the contents for memory " );
  Serial.println( num );
  Serial.println();

    while ( received != 13 )
    {
      if (Serial.available())
      {
        received = Serial.read();
        Serial.write(received);
        inData += received;
      }
    }
  if ( num == 1 ) // loading memory1
    {
      memory1 = inData;
    }
  if ( num == 2 ) // loading memory2
    {
      memory2 = inData;
    }
  Serial.println();
  Serial.print( "Memory " );
  Serial.print( num );
  Serial.println( " loaded with" );
  Serial.println( inData );
  inData == "";
}

void sendmemory(int num) // send memory number num
{
  if ( num == 1 )
    membuff = memory1;
  else
    membuff = memory2;
  Serial.println( "." );
  Serial.println( membuff );
  for(int leng = membuff.length(); leng > 0; leng--)
    {
      decoder(membuff.charAt( membuff.length() - leng ));
    }
}
void decoder(int in) // function to decode char for sending and load send buffer
{
       switch(in)
  {
      case ' ' : event = String(event + ",,"); break;
      case 'a' : event = String(event + ".,-,,,"); break;
      case 'b' : event = String(event + "-,.,.,.,,,"); break;
      case 'c' : event = String(event + "-,.,-,.,,,"); break;
      case 'd' : event = String(event + "-,.,.,,,"); break;
      case 'e' : event = String(event + ".,,,"); break;
      case 'f' : event = String(event + ".,.,-,.,,,"); break;
      case 'g' : event = String(event + "-,-,.,,,"); break;
      case 'h' : event = String(event + ".,.,.,.,,,"); break;
      case 'i' : event = String(event + ".,.,,,"); break;
      case 'j' : event = String(event + ".,-,-,-,,,"); break;
      case 'k' : event = String(event + "-,.,-,,,"); break;
      case 'l' : event = String(event + ".,-,.,.,,,"); break;
      case 'm' : event = String(event + "-,-,,,"); break;
      case 'n' : event = String(event + "-,.,,,"); break;
      case 'o' : event = String(event + "-,-,-,,,"); break;
      case 'p' : event = String(event + ".,-,-,.,,,"); break;
      case 'q' : event = String(event + "-,-,.,-,,,"); break;
      case 'r' : event = String(event + ".,-,.,,,"); break;
      case 's' : event = String(event + ".,.,.,,,"); break;
      case 't' : event = String(event + "-,,,"); break;
      case 'u' : event = String(event + ".,.,-,,,"); break;
      case 'v' : event = String(event + ".,.,.,-,,,"); break;
      case 'w' : event = String(event + ".,-,-,,,"); break;
      case 'x' : event = String(event + "-,.,.,-,,,"); break;
      case 'y' : event = String(event + "-,.,-,-,,,"); break;
      case 'z' : event = String(event + "-,-,.,.,,,"); break;
      case 'A' : event = String(event + ".,-,,,"); break;
      case 'B' : event = String(event + "-,.,.,.,,,"); break;
      case 'C' : event = String(event + "-,.,-,.,,,"); break;
      case 'D' : event = String(event + "-,.,.,,,"); break;
      case 'E' : event = String(event + ".,,,"); break;
      case 'F' : event = String(event + ".,.,-,.,,,"); break;
      case 'G' : event = String(event + "-,-,.,,,"); break;
      case 'H' : event = String(event + ".,.,.,.,,,"); break;
      case 'I' : event = String(event + ".,.,,,"); break;
      case 'J' : event = String(event + ".,-,-,-,,,"); break;
      case 'K' : event = String(event + "-,.,-,,,"); break;
      case 'L' : event = String(event + ".,-,.,.,,,"); break;
      case 'M' : event = String(event + "-,-,,,"); break;
      case 'N' : event = String(event + "-,.,,,"); break;
      case 'O' : event = String(event + "-,-,-,,,"); break;
      case 'P' : event = String(event + ".,-,-,.,,,"); break;
      case 'Q' : event = String(event + "-,.,-,-,,,"); break;
      case 'R' : event = String(event + ".,-,.,,,"); break;
      case 'S' : event = String(event + ".,.,.,,,"); break;
      case 'T' : event = String(event + "-,,,"); break;
      case 'U' : event = String(event + ".,.,-,,,"); break;
      case 'V' : event = String(event + ".,.,.,-,,,"); break;
      case 'W' : event = String(event + ".,-,-,,,"); break;
      case 'X' : event = String(event + "-,.,.,-,,,"); break;
      case 'Y' : event = String(event + "-,.,-,-,,,"); break;
      case 'Z' : event = String(event + "-,-,.,.,,,"); break;
      case '0' : event = String(event + "-,-,-,-,-,,,"); break;
      case '1' : event = String(event + ".,-,-,-,-,,,"); break;
      case '2' : event = String(event + ".,.,-,-,-,,,"); break;
      case '3' : event = String(event + ".,.,.,-,-,,,"); break;
      case '4' : event = String(event + ".,.,.,.,-,,,"); break;
      case '5' : event = String(event + ".,.,.,.,.,,,"); break;
      case '6' : event = String(event + "-,.,.,.,.,,,"); break;
      case '7' : event = String(event + "-,-,.,.,.,,,"); break;
      case '8' : event = String(event + "-,-,-,.,.,,,"); break;
      case '9' : event = String(event + "-,-,-,-,.,,,"); break;
      case '.' : event = String(event + ".,-,.,-,.,-,,,"); break;
      case '-' : event = String(event + "-,.,.,.,-,,,"); break;
      case '/' : event = String(event + "-,.,.,-,.,,,"); break;
  }
}



Thursday, January 5, 2017

Giving a voice to someone who's lost the ability to speak.

As they say, necessity can be the mother of invention.  

    In December of 2016 I lost my best friend of over 30 years.   He had suffered a major stroke and was in a coma for just over a week.   He came out of the coma, but could not speak, and could only move his hands a bit and feet.   He was in there, he could communicate by squeezing his hand, but that was all.

   I immediately dreamed up a way of giving him a voice back.   This project is the result.

   Unfortunately,  my friend had a second stroke and didn't survive.   I decided to go ahead with the project though,  I'm hopeful that it can help someone out there somewhere.   I call it,  The CW Voice Box.

   Morse code is one of the oldest ways of sending letters with a simple on off signal.   Short patterns of long and short tones.  An ideal way for someone who can only squeeze, bite, or blink to be able to spell out words.    This project is exactly that.  You key in Morse code, it speaks the letters.   A voice for the voiceless.

   There are other devices out there to accomplish this, it's not a new idea.   However,  those devices are expensive!   With the technology we have today,  you can build my voice box for around 45 bucks.    Maybe a little more if you want a fancy case.

The parts list:

     Computer:    Raspberry Pi zero.     I recommend the starter pack over at Adafruit: $24.95
Adafruit Raspi budget pack.

     USB sound card:     There's many available on Amazon,  here's one that works:   $5.99
USB sound card

     8 Gig micro SD card.    $7

     5 volt audio amp:   There's lots of these to choose from,  one example below:  $7
Audio amp

     A speaker of some type.   I'll leave that up to you.

     You'll also need some bits of wire, some kind of a case, and an input device.    Regarding input devices....    What you need will depend entirely upon the capabilities of the person you are trying to help.    There are commercial squeeze bulb switches,   bite switches,   and other devices available.   I'll leave the input method up to you.    The only requirement is that the person needs to be able to input long and short signals in a pattern.

     Putting it all together:

     I've already done the hard work on the software.   I have built a Raspian image for your Pi zero that will boot directly into the morse decoding speech software.   You can download the image from the link below.

Custom Raspian image zip file.

(Many thanks to bitchen.com for offering to host the file.)

    Write this image to the micro SD card and put it in the pi.

     The starter pack will come with a 2Amp power supply,  plug that into the pi.
      Using the USB adapter, you can plug in the usb audio dongle to the second micro usb port on the pi.
      Your input device will need to connect to GPIO pin 7 on the pi, and also to ground.

      The audio amp will need 5 volts.   You can tap that from pin 2 on the pi.  It also needs a ground connection.

      Audio from the USB dongle goes to the input of the amp, and obviously the speaker will connect to it's output.

      When you plug in the power supply,  the pi should boot and within about 20 seconds it will speak the word  "Ready".    you're good to go.   Simply start keying in Morse and it will speak each letter, number, and some punctuation.

     It will reliably decode Morse from around 5 words per minute speed, up to about 10 words per minute speed.   To get an idea of the speed,  watch the demo towards the end of my video about the project,  linked below.


Full video.


    The software is built upon a great educational python program on the raspberry pi site.   I modified the software to make it compatibly with python3,  re-ordered it's lookup table for speed, and integrated espeak speech synthesis.    The raspian image was further modified to fix espeak errors with alsa, and set to boot to command line and run the decoder on boot.

    The original morse code decoder project on the raspberry site is linked below.

Morse decode I built upon.

My modified source code follows:

----------------------------------------------------

#!/usr/bin/python3
# modified code based on a tutorial project on the raspberry pi organizations web site.
# original project can be found here:
# https://www.raspberrypi.org/learning/morse-code-virtual-radio/worksheet/
import pygame
import time
from RPi import GPIO
import _thread
from array import array
from pygame.locals import *
from morse_lookup import *
from espeak import espeak

pygame.mixer.pre_init(44100, -16, 1, 1024)
pygame.init()

class ToneSound(pygame.mixer.Sound):
    def __init__(self, frequency, volume):
        self.frequency = frequency
        pygame.mixer.Sound.__init__(self, self.build_samples())
        self.set_volume(volume)

    def build_samples(self):
        period = int(round(pygame.mixer.get_init()[0] / self.frequency))
        samples = array("h", [0] * period)
        amplitude = 2 ** (abs(pygame.mixer.get_init()[1]) - 1) - 1
        for time in range(period):
            if time < period / 2:
                samples[time] = amplitude
            else:
                samples[time] = -amplitude
        return samples

def wait_for_keydown(pin):
    while GPIO.input(pin):
        time.sleep(0.01)

def wait_for_keyup(pin):
    while not GPIO.input(pin):
        time.sleep(0.01)

def decoder_thread():
    global key_up_time
    global buffer
    new_word = False
    while True:
        time.sleep(.01)
        key_up_length = time.time() - key_up_time
        if len(buffer) > 0 and key_up_length >= 1.5:
            new_word = True
            bit_string = "".join(buffer)
            try_decode(bit_string)
            del buffer[:]
        elif new_word and key_up_length >= 4.5:
            new_word = False
            sys.stdout.write(" ")
            sys.stdout.flush()

tone_obj = ToneSound(frequency = 800, volume = .03)

pin = 7
GPIO.setmode(GPIO.BOARD)
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

DOT = "."
DASH = "-"

key_down_time = 0
key_down_length = 0
key_up_time = 0
buffer = []

_thread.start_new_thread(decoder_thread, ())

print("Ready")
espeak.synth("Ready")

while True:
    wait_for_keydown(pin)
    key_down_time = time.time() #record the time when the key went down
    tone_obj.play(-1) #the -1 means to loop the sound
    wait_for_keyup(pin)
    key_up_time = time.time() #record the time when the key was released
    key_down_length = key_up_time - key_down_time #get the length of time it was held down for
    tone_obj.stop()
    buffer.append(DASH if key_down_length > 0.3 else DOT)


------------------------------

And the morse_lookup.py source as well.

---------------------------------

#!/usr/bin/python
import sys
from espeak import espeak

morse_code_lookup = {
    ".":    "E",
    "-":    "T",
    ".-":    "A",
    "---":    "O",
    "..":    "I",
    "-.":    "N",
    "...":    "S",
    "....":    "H",
    ".-.":    "R",
    "-..":    "D",
    ".-..":    "L",
    "-.-.":    "C",
    "..-":    "U",
    "--":    "M",
    ".--":    "W",
    "..-.":    "F",
    "--.":    "G",
    "-.--":    "Y",
    ".--.":    "P",
    "-...":    "B",
    "...-":    "V",
    "-.-":    "K",
    ".---":    "J",
    "-..-":    "X",
    "--.-":    "Q",
    "--..":    "Ze",
    ".----":    "1",
    "..---":    "2",
    "...--":    "3",
    "....-":    "4",
    ".....":    "5",
    "-....":    "6",
    "--...":    "7",
    "---..":    "8",
    "----.":    "9",
    "-----":    "0",
    "..--..":    "question mark",
    ".-.-.-":    "period"
}

def try_decode(bit_string):
    if bit_string in morse_code_lookup.keys():
#        sys.stdout.write(morse_code_lookup[bit_string])
        espeak.synth(morse_code_lookup[bit_string])
#        sys.stdout.flush()

------------------------------------
 


    Please share,  I hope this project will get out there and find someone who can use it.    I would be absolutely thrilled to hear back from someone who was able to give a voice back to somebody who'd lost theirs.

    Thanks,

Kevin.

Sunday, August 14, 2016

Cheap and easy to build digital modes USB interface for Ham Radio.

The DuinoVOX  Arduino powered digital modes USB interface for ham radio.



   I wanted a better computer to radio interface than what I was using,  straight connection from the computers audio jacks.    Being unemployed for awhile, I couldn't afford $100 to buy a Signalink commercial interface, so I built my own.  Also, I wanted to use an Arduino in a ham radio project.  It came out so well,  that I've decided to publish it here.   If you build it and like it, please let me know.
 Below is the schematic.   I did a youtube video on the project,  watchable at this link.

https://www.youtube.com/watch?v=FHshPd52l-w




Circuit Description:
 
   The USB sound dongle can be purchased on Amazon.com for less than $10.  Search for "USB sound adapter" and you'll find dozens of them.   Look for one with a split plastic case so you can take it apart.  You'll need to tap +5V and ground at the USB connector, the outside pins of the 4 pin connector.
   You can used 1/8" jacks to plug in to it, or desolder the existing jacks and tap audio right off the PCB with thin coax or other shielded cable.
   The audio coming from the USB sound device is low voltage.  1.7 volts P-to-P max volume.  Since the Arduino will only sense positive voltage,  that's 900mv at best.   Depending on the point in time that you sample the signal, you can catch it anywhere from zero on up to .9 V within the waveform.  Not great.
   So,  Q1 is a simple amplifier that takes the input signal up to the full 5V on peaks.
   The amplified signal passes through D1 and charges C4.   D1 prevents the cap from discharging back through the transistor, so you get an accumulated voltage up to around 4.4V DC during a full volume signal.  This gives an excellent range on the input to the Arduino, and allows for sensitivity adjustment to be done in software.
  R6 bleeds the capacitor down when the audio stops.
  Q2 takes the PTT signal from the Arduino and grounds the radios PTT line to key it into transmit.

BUILD NOTES:

C1 and C2,  I used non polarized caps since I had them on hand, but regular electrolytics will work fine.  If you're concerned about isolation, you could use two 600:600 Ohm transformers instead.
C4 is not critical.  If you don't have a .22,  put two .1 in parallel
R7 and R8 don't have to be 10K, can be anything from 10K up, don't have to match either.
C3 can be anything from 1uf up to 10uf

One important tip on using this interface.  On your computer, you'll want to set the playback volume for the interface sound card to maximum,  then use the drive control knob to set the audio level to your rig.  This gives the vox circuit plenty of audio to work with.

The Arduino source code is pasted below.  You should be able to copy paste it right into the Arduino IDE.   If you have trouble,  let me know and I'll email the file to you.   Thanks and 73.

If you build this and love it,  and you'd like to leave me a tip,  my email registered with paypal is loughkb@yahoo.com

Monday, October 19, 2015

How to properly shuffle virtual cards in software.

Last night, during one of many bouts with insomnia, I was attempting to bring on sleep by playing a mindless game,  Solitaire.   I say mindless since it requires only the tiniest bit of skill to play.  All you have to do is not miss a play.  The game will be won if it is winnable,  it depends on the order the cards come out of the deck.

This particular version of the game,  installed from the Chrome web store,  looked beautiful,  had wonderful card animation, nice sound, and terrible card shuffling!  After 30 lost games in a row,  I was quite annoyed, and further from sleep than when I began.

How do I know it was bad shuffling?   Well it was obvious by the regular discovery of two or three of the same number of card in succession.  Three Aces in a row,  or fives, etc.  This poor shuffle, at least in Solitaire,  leads to unwinnable games far more often than winnable games.

A poor shuffle might be advantageous in Poker,  to one player at least, but still a bad thing in the bigger picture.

I see this often.  Poor shuffling in a card game program.    I always remember my high school days and a certain programming contest a few friends and I had.

It was 1983.  My high school had several Apple ][+ computers in the library, as well as a couple in the vocational electronics class I was taking.   This was the early days of personal computers and almost nobody had one at home.   

Myself and a few friends, classic nerds of the day, were very much in love with these little machines.  We often fought over who would get time on them after class.  Our parents had become accustom to us staying after school until the janitor kicked us out and we trudged home to a cold dinner.   We amused ourselves with programming contests we’d come up with.  Each hoping to achieve the position of alpha male in our geeky clique.

The contest of one particular week grew out of a problem one of our group was having with trying to write a good blackjack game.    You see, we were programming in BASIC at the time, and it was not a very fast language.   The Apple ][+ had a CPU running at around 1Mhz.  BASIC, being an interpreted language was slow,  very slow,  unbelievably slow by today's standards.   Akin to comparing a formula one car to a cart drawn by a donkey.

Doug,  the kid working on the blackjack game, was rather cocky.  He was similar to Dr. Sheldon Cooper on The Big Bang Theory.  Very smart, yes, but so incredibly full of himself that we tolerated him only.   His problem was that it took well over two minutes for his card shuffle to complete at the beginning of the game.   He proposed a contest to come up with a better sorting routine.

By the end of the week, when we gathered to present our results, I was the clear winner and not by a narrow margin!    There were four of us, not counting Doug.   Doug had managed to get his shuffle down to one minute and thirty two seconds while sacrificing a bit of randomness.  Faster, but the shuffle was poor with many cards adjacent to their brothers.

The other three guys varied with one being a bit slower, and the other two beating Doug by just a couple of seconds.   All three also had somewhat poor shuffles.

My shuffle took all of four seconds.    Four,  yes,  only four seconds, in BASIC, on an Apple ][+.   Additionally, my shuffled deck was thoroughly shuffled with hardly a single instance of card pairs left. For awhile thereafter, Doug wasn't so cocky.

No,  I didn’t cheat, or use machine language.  It was a method that I came up with that I wish all programmers would use in modern card games.   Not for the speed, that hardly matters on today's incredibly fast machines.  No,  I wish programmers would use my method for the thorough randomness of the shuffled deck.

Here’s how it works.   Now,  I can’t present this in C, or Python,  .NET, or any modern language.   I’m not going to bore you with BASIC since it’s basically a dead language now.   I’m going to attempt to convey the idea,  it’s simple enough.

So first you need the deck to be shuffled.   In BASIC I simply loaded an array with numbers to represent the cards,  one through fifty two. I guess you'd use a table or something today.

Now, to shuffle them  you need a simple loop.   The loop with run fifty two times with a counter that’s incremented during each pass.   This counter will point to the position in the deck we’re working with.

For each pass of the loop,  you take the current card and exchange it with a random position in the deck.   So for pass one,  we’re working with the first card in the deck.  Pick a random number between one and fifty two and swap those two ‘cards’.

After only one pass through the loop, you’ll already have a deck that is shuffled better than what most modern card games end up with.   And it will happen incredibly fast.   Now, run the loop two or three times and you have a properly and thoroughly shuffled deck of cards.

Easy.   Simple.   So please,  programmers,  do this and properly shuffle your cards.  It will make for a much more realistic flow of the card game.

Thanks.

Wednesday, April 10, 2013

A new adventure, 3D printing

This is only the first of many posts to come.   I've ordered a 3D printer, of sorts.  More like a pile of parts.

The most common 3D printer design among hobbyists, is the completely open Prusa Mendel.  One company sells all of the common parts for a base model as a kit.  Not a bad deal if you're inclined towards building things.   Their main page is nwreprap.com if you are interested in going this route.

I will be chronicling my build in this blog, with updates and photos.  After the project is complete, I will continue to post updates about modifications, improvements, and projects I accomplish with the 'printer'.

People call them 3D printers, but really, they are additive cnc machines.  Moving a tool head, the extruder, around using the same technique and programming code that industrial milling machines use.

I'm currently waiting for the parts to arrive.  Spending my time researching, planning, and learning software.   I'll dedicate a post this weekend to software, there's a lot of ground to cover there.  

Hopefully, I'll be able to start the build next week.  Stay tuned!


Sunday, November 25, 2012

Cheap and silent desktop Linux box!


In the tech news in the last couple of weeks, there was an announcement of an intel branded mini-pc.  There have been many of these small desktop machines in the last few years.  Very small footprints, low power consumption, most are silent due to a fanless design.

The appeal of such small machines is obvious.  Taking negligible desk space, they can sit out of the way, or even be hidden.  They can be mounted to the back of a monitor for use as industrial signage, or a pseudo all-in-one design for the desktop.  They are ideal for limited space installations like in mobile homes, or a small collage dorm room.

They're considered cheap, yet are still a bit pricey for a lot of us.  Many of them seem to settle around the $300 mark.

I've found an alternative that is widely available and much cheaper, less than half in most cases.  The early intel Mac minis.

Back in 2006, Apple produced their first intel based mac mini design.  Quite a capable box at the time, Apple was always using cutting edge hardware in their designs.  This model contains an intel core duo dual core 1.66Ghz cpu, 1Gigabit ethernet, wifi, bluetooth, four usb ports, firewire 400, and DVI video connector.

Full specs listed here:

http://www.everymac.com/systems/apple/mac_mini/specs/mac_mini_cd_1.66.html

The last version of Apples OS that could run on this model was Snow Leopard, 10.6.x.   Since the last two versions of their OS, there has been a glut of these machines showing up on eBay, going for as low as $130 to just over $150.   I picked one up with a bad hard disk for under $80!

Replacing the hard disk is not too complicated, I'll list the steps at the end of this article to help anyone along if they pick up one cheap.  These make a GREAT Linux box,  I'm writing this on mine right now loaded with Debian testing.

In my case, I had a unit with a bad HD, as mentioned, so I installed a 32Gig SSD I'd picked up a year or so ago.  Installing your favorite Linux is easy, Apples firmware has an emulated bios for their "bootcamp" method of dual booting windows on their machines.  When you power up, hold down the 'opt' key if you have a mac keyboard, or the left hand 'alt' key if you have a PC keyboard.  The machine will come up to a graphic menu that allows you to choose your boot device.  Insert your linux CD/DVD, and after a few seconds you'll see a CD icon appear with "Windows" under it.  Arrow over to select it, or click with the mouse and the CD will boot.

From that point, you go through an installation just as you would on any PC box, and after the install is done and it reboots, it will come right up.

These machines are a bit pokey under Apples OS, but under Linux, they perform extremely well.   All hardware works just fine on any recent Linux kernel.  OpenGL performance is not bad at all, making all 3D games that I've tried run smoothly.  I've even played full 1080p video files full screen without stutter.  The machine is nearly silent as the internal fan can't be heard unless you put your ear right down next to the vents on the back.

So there you go.  For not much money, you can have a tiny desktop Linux PC that is fully capable of just about anything you might need to do.  Enjoy.

Here are the steps to get the mini apart for HD replacement.

1) Remove the case.  For this you need a special tool.  Apple sells it for $30.  Since it's basically just a 1" wide putty knife, you can get one at the hardware store for a couple dollars.
   Insert the knife into the seam around the bottom edge of the machine and pry.  It will pop loose, and you just work your way around.

2) Disconnect two cables.  At the back, there is a ribbon cable connected to a small board at the back of the CD rom drive.  It has a ZIF connector.  You pop to small clip up at each side and the ribbon lifts out.
   At the front, next to the small coin battery, is a two wire cable with a snap connector into the main board.  Pop it out carefully.  This is the fan temperature sensor, and if you forget to plug it back in, the fan will run full speed.

3) Remove the wifi antenna.  At the rear corner is the wifi antenna board.  Under it you will find two plastic clips.  Squeeze them in slightly and you can life off the antenna board.  Remove the spring and set it aside.

4) Remove four screws holding the chassis down on the main board.  At each of the four corners of the chassis  there is a small Phillips head screw.  One is at the bottom of a tube, one is right out in the open, and the remaining two are tucked down inside the corner of the plastic chassis.  A penlight will help on those hidden two.

5) Lift off the chassie.  This is only a little tricky.  There is an interconnected socket between the two, and the wifi antenna cable snakes down under the fan exhaust at the back.  Just work the chassis loose with a gentle rocking and lifting motion, keeping the antenna cable from getting snagged.

6) Once the chassis is out, turn it over and you'll see the 2.5" sata HD right there.  Four screws and you can work it loose from the connectors and lift it out.  Drop in your replacement by lining it up with the sata connectors and gently pressing it in until the screw holes line up.

7) re-assemble in reverse.  Take care to guide the wifi cable around the fans exhaust, *carefully* or you'll pop it loose from the wifi board.  With the holes lined up, you'll be able to work the interconnection in.  The ribbon cable at the back will slide into the ZIF connector and has a line drawn across it that will line up with the edge of the connector when it's all the way in.  Alternately press down the little clip edges while holding the cable in.
Don't forget that fan connector up front by the battery!
The two screws with the hidden holes are just a bit tricky.  A small Phillips jewelers screwdriver that has been magnetized helps there.
Finally, guide the case back on, watch the flexible ground at the back and use the guides around the back connector areas.  Press it down and it will snap back on.