Receiving Samples with Go¶
In this tutorial we show you how to perform basic functionality with the AIR-T using Golang. You will learn how to interact with the AIR-T radio drivers, stream signal data from the radio to the Tegra, and create a one-time plot of the wireless spectrum. The AIR-T does not come preloaded with go, so you will need to install it yourself first. Below you will find the steps to install and the initial source code.
Installing Go on the AIR-T¶
- Open a terminal on the AIR-T
-
Use the commands below to install go and set up the environment:
curl -OL https://golang.org/dl/go1.17.4.linux-arm64.tar.gz sudo rm -rf usr/local/go sudo tar -C /usr/local -xzf go1.17.4.linux-arm64.tar.gz export PATH=$PATH:/usr/local/go/bin
-
To verify installation:
go version
Go Code¶
The source code to receive samples is below. You will need to copy this into your go source code directory and se up a new project for this example (including a go.mod file for imports).
package main
import "C"
import (
"fmt"
"log"
"math"
"math/cmplx"
"os"
"github.com/mjibson/go-dsp/fft"
"github.com/pothosware/go-soapy-sdr/pkg/device"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/plotutil"
"gonum.org/v1/plot/vg"
"gonum.org/v1/plot/vg/draw"
"gonum.org/v1/plot/vg/vgimg"
)
func main() {
// Data transfer settings
var rx_chan uint = 0 // RX1 = 0, RX2 = 1
var N uint = 16384 // Number of complex samples per transfer
var fs = 31.25e6 // Radio sample Rate
var freq = 2.4e9 // LO tuning frequency in Hz
var use_agc = true // Use or don't use the AGC
var timeout_us uint = (5e6)
var rx_bits float64 = 16 // The AIR-T's ADC is 16 bits
//Initialize the AIR-T receiver using SoapyAIRT
args := map[string]string{"driver": "SoapyAIRT"}
dev, err := device.Make(args) // Create AIR-T instance
dev.SetSampleRate(device.DirectionRX, rx_chan, fs) // Set sample rate
dev.SetFrequency(device.DirectionRX, rx_chan, freq, nil) // Tune the LO
dev.SetGainMode(device.DirectionRX, rx_chan, use_agc) // Set the gain mode
// Open the stream and activate it for receiving CS16 data
stream, err := dev.SetupSDRStreamCS16(device.DirectionRX, []uint{0}, nil)
if err != nil {
log.Fatal(fmt.Printf("SetupStream fail: error: %v\n", err))
}
if err := stream.Activate(0, 0, 0); err != nil {
log.Fatal(fmt.Printf("Activate fail: error: %v\n", err))
}
// Prepare an array for receiving the data.
buffers := make([][]int16, 1)
buffers[rx_chan] = make([]int16, 2*N) // array needs to have space for both channels
flags := make([]int, 1)
// Read the data
timeNs, numElemsRead, err := stream.Read(buffers, N, flags, timeout_us)
fmt.Printf("flags=%v, numElemsRead=%v, timeNs=%v, err=%v\n", flags, numElemsRead, timeNs, err)
// Close the stream
if err := stream.Deactivate(0, 0); err != nil {
log.Fatal(fmt.Printf("Deactivate fail: error: %v\n", err))
}
if err := stream.Close(); err != nil {
log.Fatal(fmt.Printf("Close fail: error: %v\n", err))
}
// Close the device
err = dev.Unmake()
if err != nil {
log.Fatal(err)
}
//////////////////////////////////////////////////////////////////////
/// Plot & Analyze Signal ///
//////////////////////////////////////////////////////////////////////
//Convert interleaved shorts to complex 64 values normalized [-1,1]
s0 := make([]float64, 0)
var s []complex128
for idx := 0; idx < len(buffers[0]); idx++ {
s0 = append(s0, float64(buffers[0][idx])/math.Pow(2, rx_bits-1))
}
for idx := 0; idx < len(s0); idx += 2 {
s = append(s, (complex(s0[idx], s0[idx+1])))
}
// Take the fourier transform of the signal
S := fft.FFT(s)
// FFT Shift
S = append(S[len(S)/2:], S[0:len(S)/2]...)
// Now that we have all the data we can plot it
plots := make([][]*plot.Plot, 2)
// Time Domain Plot
pts, ptsI := make(plotter.XYs, N), make(plotter.XYs, N)
for idx := 0; idx < len(s); idx++ {
pts[idx].Y, ptsI[idx].Y = float64(real(s[idx])), float64(imag(s[idx]))
pts[idx].X, ptsI[idx].X = float64(idx)/fs/1e-6, float64(idx)/fs/1e-6
}
p := plot.New()
plots[0] = make([]*plot.Plot, 1)
p.Title.Text, p.X.Label.Text, p.Y.Label.Text = "Time Domain Plot", "Time(us)", "Normalized Amplitude"
plotutil.AddLines(p, "I", pts, "Q", ptsI)
plots[0][0] = p
// Frequency Domain Plot
ptsF := make(plotter.XYs, N)
for idx := 0; idx < len(S); idx++ {
ptsF[idx].X = (freq + (fs/float64(N))*float64(idx) - fs/2 + fs/float64(N)) / 1e9
ptsF[idx].Y = 20 * math.Log10(cmplx.Abs(S[idx])/float64(N))
}
plots[1] = make([]*plot.Plot, 1)
p2 := plot.New()
p2.Title.Text = "Frequency Domain Plot"
p2.X.Label.Text, p2.Y.Label.Text = "Freq(GHz)", "Amplitude"
p2.Y.Min, p2.Y.Max = -140, 0
plotutil.AddLines(p2, "f", ptsF)
plots[1][0] = p2
// Draw subplots and save to file
img := vgimg.New(12*vg.Inch, 8*vg.Inch)
dc := draw.New(img)
t := draw.Tiles{
Rows: 2,
Cols: 1,
PadX: vg.Millimeter,
PadY: vg.Millimeter,
PadTop: vg.Points(2),
PadBottom: vg.Points(2),
PadLeft: vg.Points(2),
PadRight: vg.Points(2),
}
canvases := plot.Align(plots, t, dc)
plots[0][0].Draw(canvases[0][0])
plots[1][0].Draw(canvases[1][0])
w, _ := os.Create("hello_world_go.png")
png := vgimg.PngCanvas{Canvas: img}
png.WriteTo(w)
}
Output Plot¶
Last update: December 1, 2023