Continous Signal Acquisition Example
As is mentioned in the Acquisition section, the transmission rate of the server heavily depends on the available network and the way a client processes the samples. This example shows how one can write a thread dedicated to just receiving samples and one (or more) threads dedicated to processing samples. As the example contains no visualization, there is no need for a specific RedPitaya setup.
Julia Client
This and all other examples are located in the examples
directory
using RedPitayaDAQServer
using ThreadPools
using CairoMakie
include("config.jl")
# Note that this example is intended to be used with at least three threads (start Julia with option -t 3)
rp = RedPitaya(URLs[1])
if serverMode(rp) == ACQUISITION
masterTrigger!(rp, false)
end
serverMode!(rp, CONFIGURATION)
# With a decimation of 8 a single RedPitay produces 500 Mbit/s of samples
# and samples exist within the buffer for around 2 seconds before being overwritten.
dec = 8
streamTime = 10
modulus = 12500
base_frequency = 125000000
samples_per_period = div(modulus, dec)
periods_per_frame = 2
samplesInStream = streamTime * (base_frequency/dec)
numFrames = div(samplesInStream, samples_per_period * periods_per_frame)
decimation!(rp, dec)
samplesPerPeriod!(rp, samples_per_period)
periodsPerFrame!(rp, periods_per_frame)
triggerMode!(rp, INTERNAL)
frequencyDAC!(rp, 1, 1, base_frequency / modulus)
signalTypeDAC!(rp, 1, 1, SINE)
amplitudeDAC!(rp, 1, 1, 0.5)
offsetDAC!(rp, 1, 0)
phaseDAC!(rp, 1, 1, 0.0 )
serverMode!(rp, ACQUISITION)
masterTrigger!(rp, true)
# Reading at such high data rates requires a client with a thread mostly focuesd on just receiving samples
# Reading the desired samples frame by frame introduces a large communication overhead as well as the constant conversion
# from samples to frames
#=
frames = []
readFrames = 0
while readFrames < numFrames
frame = readFrames(rp, readFrames, 1)
push!(frames, frame)
end
=#
# Reading all frames in one function call reduces communication overhead and requires only one conversion.
# However, with this approach a client application has to wait the whole duration before being able to use the frames
#=
frames = readFrames(rp, 0, numFrames)
=#
# One can use one thread dedicated to receiving just the samples (the producer) and one thread dedicated to performing computations on samples (the consumer).
# If the consumer is quick enough the producer should be able to receive samples continously, which allows the server to transmit continously.
# An example of such a pattern is shown in the following lines of code.
samples_per_frame = samples_per_period * periods_per_frame
channel = Channel{SampleChunk}(32)
# Start producer
@info "Start Producer"
producer = @tspawnat 2 readSamples(rp, 0, Int64(numFrames * samples_per_frame), channel)
# Associate life time of channel with execution time of producer
bind(channel, producer)
# Consumer
buffer = zeros(Float32, samples_per_period, 2, periods_per_frame, Int64(numFrames))
performance = Vector{Vector{PerformanceData}}()
consumer = @tspawnat 3 begin
lostData = false
sampleBuffer = nothing
fr = 1
to = 1
@info "Start Consumer"
while isopen(channel) || isready(channel)
while isready(channel)
chunk = take!(channel)
# Collect samples
samples = chunk.samples
if !isnothing(sampleBuffer)
sampleBuffer = hcat(sampleBuffer, samples)
else
sampleBuffer = samples
end
# Note that the chunk also contains status and performance values which can/should
# be evaluated to see if data was lost
perfs = chunk.performance
for (i, p) in enumerate(perfs)
if p.status.overwritten || p.status.corrupted
#@warn "RedPitaya $i lost data"
lostData = true
end
end
perfs = chunk.performance
push!(performance, perfs)
# Convert samples to frames if enough samples were transmitted
frames = nothing
framesInBuffer = div(size(sampleBuffer)[2], samples_per_frame)
if framesInBuffer > 0
samplesToConvert = view(sampleBuffer, :, 1:(samples_per_frame * framesInBuffer))
frames = convertSamplesToFrames(rp, samplesToConvert, 2, samples_per_period, periods_per_frame, framesInBuffer)
# Remove used samples
samplesInBuffer = size(sampleBuffer)[2]
if (samples_per_frame * framesInBuffer) + 1 <= samplesInBuffer
sampleBuffer = sampleBuffer[:, (samples_per_frame * framesInBuffer) + 1:samplesInBuffer]
else
sampleBuffer = nothing
end
# Add frames to buffer (or perform other computations on them)
to = fr + size(frames, 4) - 1
buffer[:, :, :, fr:to] = frames
fr = to + 1
end
end
try
wait(channel)
catch e
# NOP
end
end
@info "End Consumer"
if lostData
@info "Consumer lost data"
end
masterTrigger!(rp, false)
serverMode!(rp, CONFIGURATION)
end
if isinteractive()
@info "You can now access the data via the variable `buffer`."
else
# Wait for the consumer since otherwise Julia just terminates in non-interactive mode.
wait(consumer)
# Frame dimensions are [samples, chan, periods, frames]
plot = lines(vec(buffer[:, 1, :, :]))
save(joinpath(@__DIR__(), "images", "producerConsumer.png"), plot)
end