I've always been fascinated by the technology in amateur radio, and APRS is no exception. By combining a transceiver with a GPS, text messages, or weather information, a local map is created that updates in real time. Internet gateways also exist that republish received signals online, making it possible for anyone to see the APRS network with or without radio hardware. I love the idea of live-updating signals and important notices being available with very little infrastructure, and wanted to make it possible for these signals to be visualized in an intuitive and fun way. Here's what I came up with: a microcontroller-based, easily expandable map to serve as an interesting art installation or a functional indicator! As an amateur radio station travels from place to place, LEDs will shift to indicate its location.
This is a prototype, so please stay tunedCoronavirus made it difficult for me to acquire all of the materials I wanted to bring this project to its full potential. This project is completely functional and will take minimal modification to come to complete fruition, but the nicer-looking exterior I wanted to create, including additional shift register to expand the scope of the map are absent. I plan to update this project in the near future with the visual elements complete, but for the time being, it works as intended!
The intended visionAn overhead map of the Chicago area is split into a grid. An LED is placed underneath and illuminates when an APRS signal is present in its coordinate bounds.
Why shift?One of my chief concerns in creating this project was allowing for expandability. In the future, I want to add an additional switch and display to enable the user to switch between individual signals processed directly from an APRS I-gate server. The Particle Argon has very few digital and analog pins available, so I wanted to use as few pins as possible for the LED array. The solution to this is a shift register.
At the risk of gravely oversimplifying, the shift register allows us to independently control 8 outputs by passing in an 8 bit number, meaning any decimal number up to 128.
The shift register 'sees' our input in binary, and sets each pin to HIGH based on the individual bits of the input. For instance, 2, written in binary as 01000000, will turn on the second output, output 1, and leave the rest off. 128, written as 00000001, will turn on the last output, output 7. A number like 23 is written in binary as 10111000, so outputs 0, 2, 3, and 4 will be activated.
Here's how we control our shift register in Arduino. We open the latch by writing low to it, then use the command shiftOut to pass our valueSent into the shift register (starting with the most significant bit, or MSBFIRST), and then close the latch.
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, valueSent);
digitalWrite(latchPin, HIGH);
Each of these outputs are in turn wired to its own LED. If we wanted to use 16 LEDs instead of 8, we would use the overflow pin on the shift register. For now, though, we'll stick with our 8.
Code breakdownFirst, we'll set up our variables, libraries, and outputs. The core of our project is the APRS.fi API. We'll use Particle's built-in API interface to issue and interface with it. We subscribe to the Particle cloud with Particle.subscribe(). Here's what our webhook looks like:
And the setup for the rest of our code:
#include <SparkTime.h>
#include <ArduinoJson.h>
// create json doc in stack (or heap IDC it's up to you and doesn't really matter in this case)
StaticJsonDocument<500> doc;
// define pins for shift register
int dataPin = 8;
int latchPin = 7;
int clockPin = 6;
void setup() {
delay(5000);
// configure pins for shift register
pinMode(dataPin, OUTPUT);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
// get ready to query
Particle.subscribe("hook-response/query", myHandler);
// open serial
Serial.begin(9600);
}
Then, we'll set up our loop to issue a query for the callsign we want every two minutes. This uses the Time.minute() function provided in the SparkTime library.
void loop() {
// every 2 minutes, update station position
if (Time.minute() % 2 == 0){
Particle.publish("query", "URCALL");
}
}
In our handler for the API call, we'll deserialize the JSON using the ArduinoJson library. You can use their tool to check your own JSON to parse.
void myHandler(const char *event, const char *data) {
const char* json = data;
// now we need to deserialize the json string so we can do cool and fun things with it
deserializeJson(doc, json);
JsonObject entries_0 = doc["entries"][0];
// now we retrieve GPS coordinates from the JsonDocument
float latitude = entries_0["lat"];
float longitude = entries_0["lng"];
// now we confirm that any of that actually worked
Serial.println(latitude);
Serial.println(longitude);
// test output
squareIden(latitude, longitude);
}
In our hander function, we call a new function called squareIden(). We'll use this to identify what gridsquare the callsign exists in, and set that value to the integer 'square'. Remember, we only want one grid illuminated at a time, so the values of square are chosen for their equivalent values in binary.
void squareIden(float lat, float lon){
// we will now position the APRS signal into one of the map squares
int square = 0;
if (lon < -87.7 && lon > -87.5) {
if (lat > 41.9 && lat < 42) {
square = 8;
} else if (lat > 41.8 && lat < 41.9) {
square = 128;
}
} else if (lon > -87.9 && lon < -87.7) {
if (lat > 41.9 && lat < 42) {
square = 4;
} else if (lat > 41.8 && lat < 41.9) {
square = 64;
}
} else if (lon > -88.1 && lon < -87.9) {
if (lat > 41.9 && lat < 42) {
square = 2;
} else if (lat > 41.8 && lat < 41.9) {
square = 32;
}
} else if (lon > -88.3 && lon < -87.1) {
if (lat > 41.9 && lat < 42) {
square = 1;
} else if (lat > 41.8 && lat < 41.9) {
square = 16;
}
}
// and output it
makeOutput(square);
Finally, the makeOutput function takes the determined gridsquare and sends it to your shift register.
void makeOutput(int square){
int valueSent = square;
// send data through
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, valueSent);
digitalWrite(latchPin, HIGH);
// verify square position
Serial.print("Wrote ");
Serial.println(valueSent);
}
DemonstrationHere's the whole thing in action. We'll have our Argon check the position of a station.
By checking our serial monitor or API call, we can see that its coordinates are 41.96 and -87.68...
meaning it should exist in the 4th grid square.
And look, there it goes! Light #4 illuminates.
As a proof of concept, I cut out, glued, and taped an overhead of the Chicago area, including exaggerated forms of parks and forest preserves. This is to scale with the existing map design.
I had to use whatever was in the house, so I tested the opacity of the different papers to make the best choice per LED.
Then I cut out strips of paper, glued together a box, and added an extra plane within to keep the LEDs in place.
I then extended the LEDs with female to male jumpers, but you can do this in any method you wish.
Tada -- glowing map!
- A better process for the cardstock map
- Another 8 gridsquares with an additional shift register.
- Directly parsing the APRS-IS stream to support multiple stations illuminating multiple LEDs.
Comments