Saturday, December 23, 2023

APRS monitor with Raspberry PI

 APRS is a digital communication mode using a VHF radio, a modem, and a computer. Packets are sent over the air in a manner similar to the internet. It's used to send text messages, email, weather reports, and positions of emergency response assets.

Years ago I bought a muli-color LCD display from Adafruit for my Raspberry Pi 2. I finally got around to assembling it and was looking for an application. I figured if I wrote an app to monitor and decode APRS packets it would be an opportunity to better understand this interesting protocol.

The first part of this system consists of a Baofeng BF-F8HP radio and a interface board that I described in an earlier post. The Raspberry Pi 2 has no audio input, so I had to use a USB sound card dongle.  This used up one of the Pi's two USB ports. I was going to plug the Pi's other port into the interface board's "Push to Talk" (PTT) port, and then get the Pi on the network using an Ethernet cable, but since the code I'm running is very experimental, I thought it more prudent to use a WiFi dongle on the second port and keep the Pi on my guest network. Although PTT is not needed for this part of the project, I should be able to add it later using the Pi's GPIO pins.

I installed Direwolf,  a Terminal Node Controller (or modem), on the Pi with "sudo apt install direwolf". The sound card configuration in direwolf/config file looks like this:

ADEVICE  plughw:1,0
ACHANNELS 1

I started ~/direwolf/direwolf but it wasn't decoding the received messages. There turned out to be two problems with the soundcard dongle. One was that it couldn't handle the nearly 4 volt DC offset coming from the Baofeng, and the other issue was that the dongle was expecting microphone level signals. To handle the offset I added 0.15 µF capacitor to the signal line. Next I cut the signal level down by a factor of 20 by making a voltage divider using a 470 ohm resistor and a 10K ohm resistor.

Now for the Python stuff. I wanted to make a networked connection to Direwolf's so-called KISS (Keep It Simple Stupid) interface. I reality, I don't think it's that simple! I looked at two ways to access this interface. Using the Python KISS library, or just opening a TCP socket. 

Capturing packets in KISS 

    ki = kiss.TCPKISS(host='localhost', port=8001)
    ki.start()
    ki.read(callback=print_frame2)

Capturing Packets with a TCP socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', TCP_PORT)
sock.connect(server_address)
while(True):
    data = sock.recv(1024)

I settled on the KISS library because I was hoping that it would handle much of the packet assembly and disassembly. It was a little trickier that I though. I had to import the parse functions from both the aprs library and the aprslib library. These two functions do slightly different things. The aprs function really does a decode, and the aprslib function does the actual parsing.

        decoded_msg = str(aprs.parse_frame(msg))
        decoded_msg = decoded_msg.replace('*','')
        print('Decoded Message  = ' + decoded_msg)
        parsed_msg = aprslib.parse(decoded_msg)
        prettyprint.prettyprint(parsed_msg)

But in the end they turn this into a key-value dictionary:

b'\x82\xa0\xa8fbh`\x82\x90l\x8e\xa4@l\x96\x90l\x86\x9e\x9a\xe2\xae\x92\x88\x8ab@\xe0\x96\x90l\x84\x8c\x88\xe3\x03\xf0$GPRMC,054034,A,2048.6686,N,15622.0367,W,011,344,191223,,*00/Mobile in Maui Hawaii|#t%{|!wo^!'

I found that there were a few cases in which these functions were unable to parse a message. That will be something for me to figure out later.

Next I wanted to use the LCD display to show the SSID (station callsign + an identifying number) of the calling station, the time the message was received, and the location from which the message was sent. The SSID comes from the "from" key in the parsed message. The time comes from the system clock. The latitude and longitude are in the parsed data, but I wanted to show the name of the nearest town. For this, I found that a website that offers "reverse geo-coding". You supply the coordinates and it returns the name of the nearest town.  

Here's how the current state of the project looks:



Next: 

Add meaning to the colors. Currently the screen backlight color is random. Each type of message (position, wx report, text) should have an assigned color.

Add the ability to transmit. 



Saturday, November 11, 2023

Adding Trace Capture to the DSO Shell Oscilloscope

Introduction

The DSO Shell oscilloscope is about the least expensive oscilloscope you'll find. Its 200 kHz bandwidth excludes radio frequency work, but I think it's a pretty fun tool to have on the bench. One interesting, but partially implemented feature is that it can send the contents of a captured trace through its serial data port. To fully implement this you need a way to connect your computer to the 3.3 volt serial interface. That's pretty easy to solve with a CP2102 USB to UART module. The next problem is that to send the data, you have to simultaneously press the V/DIV and the ADJ buttons. It would be great if you could send a signal from your computer to trigger a trace capture. That's what this project does.

Opto-Isolators

To simulate pushing the V/DIV and  ADJ buttons, the transistor sides of 4N35 opto-isolators were soldered across SW1 and pins 4 and 5 of SW6. Then the diode sides were connected from ground to the DTR signal  the CP2102 module via 560 Ohm resistors. In the schematic below the line marked SD (Send Data) goes to DTR. DTR is the Data Terminal Ready signal that tells whatever device is connected to the computer that it may start sending data.





You can see in the photo that the chip is installed "dead bug" style (upside down) by the V/DIV switch pins.


For the ADJ switch pins, leave some space by the on/off switch for the plastic standoff on the bottom half of the case.

USB Interface

When I first connected the data SD lines to the CP2102 I discovered that DTR signal was enabled by default. So as soon as I plugged the 'scope into the computer it started sending data! Oops! I should have tried wiring the opto-isolators from the 3V3 pin to DTR instead of DTR to GND. Oh well, an easy enough fix was to use a PNP transistor to invert the DTR signal.
And I happened to have a 2N2907 in a nice metal TO-18 case with gold-plated leads.

I then cut a slot in the side of the case and mounted the CP2102 with 5-minute epoxy.


Software

As a starting point, I used Avra Mitra's excellent dso150PCplot.py script. I've added a function to trigger the Send Data signal.

def trigSD():
ser.setDTR(True)
sleep(0.1)
ser.setDTR(False)

This function is first used to find which USB port the oscilloscope is connected to. The modified script iterates through all ports, toggling DTR until it finds one that returns a text string containing "RecordLength". 

Then, each time the user taps the "Enter" key, a new trace is sent to the computer and displayed. 

The code can be found on GitHub.

Here's an example of a plot of the calibration signal. The bottom shows a time domain trace, and to top shows an FFT of trace below. Note that the amplitude of the third harmonic is about 1/3 of the fundamental. The math checks out! 

Future enhancements

For the hardware, it would be great if I could power the DSO with the USB cable. The device draws only 120 mA at 9V which would require only about 250 mA on the USB supply line.  That, however, would require a step-up switching power supply. It might just barely fit inside the case, but upon consideration, putting a noisy switching power supply so close to the internal analog circuits may not be a good idea. Better to use a USB break-out board for the switcher outside the case and loop back to the power plug.

For the software, I'd like to add a log/log option to the FFT. I'd also like to print the trace statistics and scope settings which are conveniently sent along with the actual trace data.