Skip to content

Receiving Samples with Go

Go Logo

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

Output


Last update: December 1, 2023