Compare commits
6 Commits
audio-test
...
tap-test
| Author | SHA1 | Date | |
|---|---|---|---|
| f4a9078dd2 | |||
| e7448a2e65 | |||
| 6caa6c7d82 | |||
| 1b03b13e3c | |||
| b4650361b4 | |||
| 97ba2ea3e2 |
625
Cargo.lock
generated
625
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,3 +11,4 @@ hound = "3.5.1"
|
||||
plotters = "0.3.7"
|
||||
rand = "0.9.2"
|
||||
tokio = { version = "1.47.1", features = ["full", "macros", "net", "sync", "time"] }
|
||||
tun-tap = "0.1.4"
|
||||
|
||||
12
run_net.sh
Executable file
12
run_net.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
echo "running as $1"
|
||||
|
||||
cargo b --release
|
||||
sudo setcap cap_net_admin+eip target/release/rdsp
|
||||
./target/release/rdsp 0 > /dev/ttyACM0 &
|
||||
sleep .5
|
||||
sudo ip a a $1/24 dev radio0
|
||||
sudo ip -6 addr flush radio0
|
||||
sudo ip link set radio0 up
|
||||
sudo ip -6 addr flush radio0
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
ffmpeg -re -i audio/modulated.wav -f s16le -acodec pcm_s16le udp://127.0.0.1:8080
|
||||
@ -1,43 +1,44 @@
|
||||
// Utilities for impulse response design
|
||||
pub mod design
|
||||
{
|
||||
pub mod design {
|
||||
use crate::complex::Complex32;
|
||||
use crate::fft::FFT;
|
||||
use crate::windows::{self, Window};
|
||||
use crate::complex::Complex32;
|
||||
|
||||
// Completely stolen from sdrpp dsp code
|
||||
pub fn estimate_fir_length(transition_width: f32, sample_rate: f32) -> f32 {
|
||||
3.8 * sample_rate / transition_width
|
||||
}
|
||||
|
||||
///Designs a impulse response from a desired transfer function using windowing technique
|
||||
pub fn ir_from_transfer_function(transfer_function: &[Complex32], ir_length: usize, window: Window) -> Vec<Complex32>
|
||||
{
|
||||
pub fn ir_from_transfer_function(
|
||||
transfer_function: &[Complex32],
|
||||
window: Window,
|
||||
) -> Vec<Complex32> {
|
||||
let tf_len = transfer_function.len();
|
||||
let mut ifft = FFT::new_inv(tf_len);
|
||||
|
||||
|
||||
// Compute ideal convolution kernel/impulse response
|
||||
ifft.execute(transfer_function);
|
||||
|
||||
// Shorten and window
|
||||
let mut ir = vec![];
|
||||
|
||||
for i in 0..ir_length
|
||||
{
|
||||
for i in 0..tf_len {
|
||||
// Get value within ifft result (centering/trimming)
|
||||
let k = (tf_len - (ir_length / 2) + i) % tf_len;
|
||||
let k = (tf_len - (tf_len / 2) + i) % tf_len;
|
||||
// Windowing
|
||||
ir.push(
|
||||
ifft.get_output()[k] * window(i as f32 / ir_length as f32) / tf_len as f32
|
||||
);
|
||||
ir.push(ifft.get_output()[k] * window(i as f32 / tf_len as f32) / tf_len as f32);
|
||||
}
|
||||
|
||||
ir
|
||||
}
|
||||
|
||||
pub fn frequency_response(impulse_response: &[Complex32]) -> Vec<Complex32>
|
||||
{
|
||||
pub fn frequency_response(impulse_response: &[Complex32]) -> Vec<Complex32> {
|
||||
let len = impulse_response.len();
|
||||
let mut fft = FFT::new(len, windows::rectangular);
|
||||
// Recenter impulse response
|
||||
let mut centered_ir = vec![];
|
||||
for i in 0..len
|
||||
{
|
||||
let mut centered_ir = vec![];
|
||||
for i in 0..len {
|
||||
let k = (len / 2) + i;
|
||||
centered_ir.push(impulse_response[k % len]);
|
||||
}
|
||||
|
||||
27
src/iq.rs
27
src/iq.rs
@ -1,15 +1,13 @@
|
||||
use std::f32::consts::PI;
|
||||
use crate::{complex::Complex32, filtering::fir::FIRFilter, math::map, nco::Nco, windows};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
pub struct IQSampler
|
||||
{
|
||||
pub struct IQSampler {
|
||||
local_oscillator: Nco,
|
||||
low_pass_i: FIRFilter,
|
||||
low_pass_q: FIRFilter,
|
||||
}
|
||||
|
||||
impl IQSampler
|
||||
{
|
||||
impl IQSampler {
|
||||
pub fn new(center_freq: f32) -> Self {
|
||||
// Design a lowpass filter that cuts off at the center freq
|
||||
// Estimate FIR length :
|
||||
@ -17,14 +15,17 @@ impl IQSampler
|
||||
|
||||
// Ideal transfer function :
|
||||
let mut transfer_function = vec![Complex32::zero(); fir_length];
|
||||
let bin_id = map(center_freq, 0., PI, 0., transfer_function.len() as f32 / 2.).floor() as usize;
|
||||
for i in 0..bin_id
|
||||
{
|
||||
let bin_id =
|
||||
map(center_freq, 0., PI, 0., transfer_function.len() as f32 / 2.).floor() as usize;
|
||||
for i in 0..bin_id {
|
||||
transfer_function[i] = Complex32::new(1., 0.);
|
||||
transfer_function[fir_length - 1 - i] = Complex32::new(1., 0.);
|
||||
}
|
||||
|
||||
let ir = crate::filtering::impulse_response::design::ir_from_transfer_function(&transfer_function, fir_length, windows::blackmann);
|
||||
let ir = crate::filtering::impulse_response::design::ir_from_transfer_function(
|
||||
&transfer_function,
|
||||
windows::blackmann,
|
||||
);
|
||||
let mut low_pass_i = FIRFilter::new(&ir);
|
||||
let mut low_pass_q = FIRFilter::new(&ir);
|
||||
low_pass_i.normalize_dc();
|
||||
@ -33,12 +34,11 @@ impl IQSampler
|
||||
IQSampler {
|
||||
local_oscillator: Nco::new(center_freq),
|
||||
low_pass_i,
|
||||
low_pass_q
|
||||
low_pass_q,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample(&mut self, input_sample: f32) -> Complex32
|
||||
{
|
||||
pub fn sample(&mut self, input_sample: f32) -> Complex32 {
|
||||
let i_mixed = self.local_oscillator.cexp().re * input_sample;
|
||||
let q_mixed = self.local_oscillator.cexp().im * input_sample;
|
||||
self.local_oscillator.step();
|
||||
@ -46,8 +46,7 @@ impl IQSampler
|
||||
// TODO: Could use one filter for both I and Q
|
||||
Complex32::new(
|
||||
self.low_pass_i.next(Complex32::new(i_mixed, 0.)).re,
|
||||
self.low_pass_q.next(Complex32::new(q_mixed, 0.)).re
|
||||
self.low_pass_q.next(Complex32::new(q_mixed, 0.)).re,
|
||||
) * 2.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
134
src/main.rs
134
src/main.rs
@ -20,6 +20,7 @@ use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::VecDeque,
|
||||
env::{self, args},
|
||||
f32::consts::PI,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{BufWriter, Read, Sink, Write, stdout},
|
||||
@ -36,12 +37,21 @@ use tokio::{
|
||||
sync::mpsc::{UnboundedSender, error::TryRecvError, unbounded_channel},
|
||||
time::timeout,
|
||||
};
|
||||
use tun_tap::Iface;
|
||||
|
||||
use crate::{
|
||||
bfsk::BFSKMod,
|
||||
complex::Complex32,
|
||||
filtering::{dc_block::DCBlocker, fir::FIRFilter},
|
||||
filtering::{
|
||||
dc_block::DCBlocker,
|
||||
fir::FIRFilter,
|
||||
impulse_response::{
|
||||
self,
|
||||
design::{estimate_fir_length, frequency_response},
|
||||
},
|
||||
},
|
||||
iq::IQSampler,
|
||||
math::map,
|
||||
nco::Nco,
|
||||
squelch::Squelch,
|
||||
ted::elg::ELGate,
|
||||
@ -110,6 +120,7 @@ impl SampleSender for WavSampleSender {
|
||||
struct FSKReceiver {
|
||||
eye_sender: Sender<Vec<f32>>,
|
||||
phase_lowpass: FIRFilter,
|
||||
baseband_filter: FIRFilter,
|
||||
elg: ELGate,
|
||||
last_byte: u8,
|
||||
frame_constructor: FrameConstructor,
|
||||
@ -121,22 +132,43 @@ impl FSKReceiver {
|
||||
fn new(eye_sender: Sender<Vec<f32>>) -> Self {
|
||||
let samples_per_symbol = (SAMPLE_RATE as f32) / (BAUD_RATE as f32);
|
||||
|
||||
let mut phase_lowpass = FIRFilter::new(&vec![
|
||||
Complex32::new(1., 0.);
|
||||
samples_per_symbol as usize / 2
|
||||
]);
|
||||
let mut phase_lowpass =
|
||||
FIRFilter::new(&vec![Complex32::new(1., 0.); samples_per_symbol as usize]);
|
||||
phase_lowpass.normalize_dc();
|
||||
//let mut dc_block = DCBlocker::new(0.999);
|
||||
//let mut dc_block = DCBlocker::new(1.);
|
||||
|
||||
let loop_i = 0.03;
|
||||
let loop_p = 0.1;
|
||||
let mut loop_ir = vec![Complex32::new(loop_i, 0.); samples_per_symbol as usize / 2];
|
||||
let mut loop_ir = vec![Complex32::new(loop_i, 0.); samples_per_symbol as usize / 4];
|
||||
loop_ir.push(Complex32::new(loop_p, 0.));
|
||||
let elg = ELGate::new(samples_per_symbol, FIRFilter::new(&loop_ir));
|
||||
|
||||
// Baseband filter
|
||||
let bbf_length = estimate_fir_length(100., SAMPLE_RATE as f32).floor() as usize;
|
||||
let mut frequency_response = vec![Complex32::zero(); bbf_length].into_boxed_slice();
|
||||
let cutoff_bin = map(
|
||||
hz_to_rad_per_sample(DEVIATION + 300., SAMPLE_RATE as f32),
|
||||
0.,
|
||||
2. * PI,
|
||||
0.,
|
||||
bbf_length as f32,
|
||||
)
|
||||
.floor() as usize;
|
||||
|
||||
// Design transfer function
|
||||
for i in 0..cutoff_bin {
|
||||
frequency_response[i] = Complex32::new(1., 0.);
|
||||
frequency_response[bbf_length - 1 - i] = Complex32::new(1., 0.);
|
||||
}
|
||||
|
||||
Self {
|
||||
//iq_sampler: IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32)),
|
||||
phase_lowpass,
|
||||
baseband_filter: FIRFilter::new(&impulse_response::design::ir_from_transfer_function(
|
||||
&frequency_response,
|
||||
windows::blackmann,
|
||||
)),
|
||||
elg,
|
||||
last_byte: 0x00u8,
|
||||
frame_constructor: FrameConstructor::new(),
|
||||
@ -148,10 +180,12 @@ impl FSKReceiver {
|
||||
|
||||
async fn receive(&mut self, iq: Complex32) -> Result<Option<Frame>, FrameConstructionError> {
|
||||
// Frame reconstruction
|
||||
let dphi = self
|
||||
.phase_lowpass
|
||||
.next_real((self.last_sample * iq.conj()).arg());
|
||||
self.last_sample = iq;
|
||||
let filtered_bb = self.baseband_filter.next(iq);
|
||||
// let dphi = self
|
||||
// .phase_lowpass
|
||||
// .next_real((self.last_sample * filtered_bb.conj()).arg());
|
||||
let dphi = (self.last_sample * filtered_bb.conj()).arg();
|
||||
self.last_sample = filtered_bb;
|
||||
if let Some((bit_sample, eye)) = self.elg.next_eye(dphi) {
|
||||
let _ = self.eye_sender.send(eye).await;
|
||||
self.last_byte >>= 1;
|
||||
@ -173,7 +207,7 @@ impl FSKReceiver {
|
||||
let frame_opt = self.frame_constructor.add_byte(self.last_byte);
|
||||
self.bit_count = Some(0);
|
||||
//print!("{}", last_byte as char);
|
||||
print!(".{:x}.", self.last_byte);
|
||||
eprint!(".{:x}.", self.last_byte);
|
||||
let _ = std::io::stdout().flush();
|
||||
return frame_opt;
|
||||
}
|
||||
@ -232,7 +266,7 @@ impl Transceiver {
|
||||
|
||||
let receiving = Arc::new(RwLock::new(false));
|
||||
tokio::spawn(async move {
|
||||
let mut squelch = Squelch::new(200, 0.1);
|
||||
let mut squelch = Squelch::new(200, 0.5);
|
||||
let mut iq_sampler =
|
||||
IQSampler::new(hz_to_rad_per_sample(CENTER_FREQ, SAMPLE_RATE as f32));
|
||||
|
||||
@ -254,8 +288,14 @@ impl Transceiver {
|
||||
{
|
||||
match recv.as_mut().unwrap().receive(iq).await
|
||||
{
|
||||
Ok(Some(Frame::Data(_))) => {println!("GOT DATA"); send_ack = true; recv = None; state_tx.try_send(TransceiverState::EOT);},
|
||||
Ok(Some(Frame::Ack)) => {current_message = None; recv = None; state_tx.try_send(TransceiverState::EOT);},
|
||||
Ok(Some(Frame::Data(dat))) => {
|
||||
eprintln!("GOT DATA");
|
||||
let _ = rx_stream_sender.try_send(dat);
|
||||
send_ack = false;
|
||||
recv = None;
|
||||
state_tx.try_send(TransceiverState::EOT);
|
||||
},
|
||||
//Ok(Some(Frame::Ack)) => {current_message = None; recv = None; state_tx.try_send(TransceiverState::EOT);},
|
||||
Err(()) => {recv = None;},
|
||||
_ => {}
|
||||
}
|
||||
@ -275,15 +315,15 @@ impl Transceiver {
|
||||
current_message = Some((tx_stream_receiver).recv().await.unwrap());
|
||||
}
|
||||
state_tx.try_send(TransceiverState::Listening);
|
||||
tokio::time::sleep(Duration::from_millis(500 * rand::random_range(1..6))).await;
|
||||
tokio::time::sleep(Duration::from_millis(50 * rand::random_range(1..10))).await;
|
||||
current_message.as_ref().unwrap()
|
||||
} =>
|
||||
{
|
||||
state_tx.try_send(TransceiverState::Sending);
|
||||
println!("Sending message");
|
||||
eprintln!("Sending message");
|
||||
Self::transmit(Frame::Data(message.clone()), &mut sample_sender).await;
|
||||
//current_message = None;
|
||||
println!("Sent message");
|
||||
current_message = None;
|
||||
eprintln!("Sent message");
|
||||
state_tx.try_send(TransceiverState::Waiting);
|
||||
}
|
||||
};
|
||||
@ -373,7 +413,7 @@ impl FrameConstructor {
|
||||
|
||||
pub fn add_byte(&mut self, byte: u8) -> Result<Option<Frame>, FrameConstructionError> {
|
||||
if self.frame.is_empty() && byte != 0xC4 && byte != 0x4C && !self.started {
|
||||
println!("Wrong type {:x}", byte);
|
||||
eprintln!("Wrong type {:x}", byte);
|
||||
self.started = true;
|
||||
return Err(());
|
||||
}
|
||||
@ -408,7 +448,7 @@ impl FrameConstructor {
|
||||
)));
|
||||
}
|
||||
|
||||
println!("Checksum failed");
|
||||
eprintln!("Checksum failed");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
@ -461,6 +501,10 @@ impl Frame {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Read instance
|
||||
eprintln!(
|
||||
"fir length: {}",
|
||||
impulse_response::design::estimate_fir_length(1000., 48000.)
|
||||
);
|
||||
let id = std::env::args().collect::<Vec<_>>()[1]
|
||||
.parse::<u32>()
|
||||
.expect("NO INPUT ID");
|
||||
@ -497,9 +541,13 @@ struct EguiApp {
|
||||
|
||||
eyes: VecDeque<Vec<f32>>,
|
||||
current_state: TransceiverState,
|
||||
iface: Iface,
|
||||
}
|
||||
impl EguiApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let iface = Iface::new("radio%d", tun_tap::Mode::Tap).unwrap();
|
||||
iface.set_non_blocking().unwrap();
|
||||
|
||||
let (up_sender, mut up_receiver) = channel::<Vec<f32>>(16);
|
||||
let (down_sender, down_receiver) = channel::<f32>(1024);
|
||||
|
||||
@ -507,7 +555,7 @@ impl EguiApp {
|
||||
|
||||
let instance_id = unsafe { INSTANCE_ID };
|
||||
tokio::task::spawn(async move {
|
||||
println!("Waiting for connection ...");
|
||||
eprintln!("Waiting for connection ...");
|
||||
|
||||
// let socket = Arc::new(
|
||||
// UdpSocket::bind(format!("0.0.0.0:{}", 9000 + instance_id))
|
||||
@ -562,6 +610,8 @@ impl EguiApp {
|
||||
let progression = Arc::new(AtomicU64::new(0));
|
||||
let (finished_tx, mut finished_rx) = channel::<()>(16);
|
||||
|
||||
print!("o");
|
||||
stdout().flush().unwrap();
|
||||
let send_stream = device
|
||||
.build_output_stream(
|
||||
&config,
|
||||
@ -576,8 +626,7 @@ impl EguiApp {
|
||||
|
||||
*d = stream[progression
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
||||
as usize]
|
||||
* 0.1; // TODO
|
||||
as usize];
|
||||
}
|
||||
},
|
||||
move |err| {
|
||||
@ -588,6 +637,8 @@ impl EguiApp {
|
||||
.unwrap();
|
||||
send_stream.play().unwrap();
|
||||
let _ = finished_rx.recv().await;
|
||||
print!("c");
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
@ -596,6 +647,7 @@ impl EguiApp {
|
||||
|
||||
eyes: VecDeque::new(),
|
||||
current_state: TransceiverState::Waiting,
|
||||
iface,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -605,6 +657,20 @@ impl eframe::App for EguiApp {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
let max_eyes = 100;
|
||||
|
||||
// INTERFACE
|
||||
let mut frame = [0u8; 2000];
|
||||
while let Ok(length) = self.iface.recv(&mut frame) {
|
||||
//break;
|
||||
let _ = self
|
||||
.transceiver
|
||||
.get_sender()
|
||||
.try_send(Vec::from(&frame[0..length]));
|
||||
}
|
||||
|
||||
while let Ok(frame) = self.transceiver.try_recv() {
|
||||
let _ = self.iface.send(frame.as_slice());
|
||||
}
|
||||
|
||||
while let Ok(eye) = self.transceiver.try_recv_eye() {
|
||||
self.eyes.push_back(eye);
|
||||
}
|
||||
@ -616,17 +682,17 @@ impl eframe::App for EguiApp {
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Start").clicked() {
|
||||
let snd = self.transceiver.get_sender();
|
||||
let data = (0..rand::random_range(50..250))
|
||||
.map(|_| rand::random::<char>() as u8)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = snd.send(data).await;
|
||||
});
|
||||
}
|
||||
|
||||
// if ui.button("Start").clicked() {
|
||||
// let snd = self.transceiver.get_sender();
|
||||
// let data = (0..rand::random_range(50..250))
|
||||
// .map(|_| rand::random::<char>() as u8)
|
||||
// .collect::<Vec<_>>();
|
||||
//
|
||||
// tokio::spawn(async move {
|
||||
// let _ = snd.send(data).await;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
ui.label(
|
||||
RichText::new(format!("{:?}", self.current_state))
|
||||
.size(35.)
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
use std::collections::VecDeque;
|
||||
use crate::filtering::fir::FIRFilter;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
// Crued Early late gate timing error detector
|
||||
pub struct ELGate {
|
||||
samples_per_symbol: f32,
|
||||
buffer: VecDeque<f32>, // Store baseband, matched filtered samples,
|
||||
eye_buffer: VecDeque<f32>,
|
||||
|
||||
loop_filter: FIRFilter,
|
||||
delta: f32,
|
||||
@ -18,6 +19,7 @@ impl ELGate {
|
||||
samples_per_symbol,
|
||||
loop_filter,
|
||||
buffer: VecDeque::with_capacity(2 * samples_per_symbol.ceil() as usize),
|
||||
eye_buffer: VecDeque::with_capacity(2 * samples_per_symbol.ceil() as usize),
|
||||
delta: 0.5,
|
||||
next_sample: samples_per_symbol,
|
||||
current_position: 0.,
|
||||
@ -30,8 +32,26 @@ impl ELGate {
|
||||
|
||||
pub fn next_eye(&mut self, sample: f32) -> Option<(f32, Vec<f32>)> {
|
||||
self.buffer.push_front(sample);
|
||||
self.eye_buffer.push_front(sample);
|
||||
|
||||
self.current_position += 1.;
|
||||
if self.current_position >= self.next_sample {
|
||||
// Eye stuff
|
||||
let mut eye = Vec::new();
|
||||
if self.eye_buffer.len() >= (self.samples_per_symbol / 2.) as usize {
|
||||
let start_index = (self.samples_per_symbol / 2.) as usize;
|
||||
let end_index = (start_index * 3).min(self.eye_buffer.len());
|
||||
eye = self
|
||||
.eye_buffer
|
||||
.range(start_index..)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
while self.eye_buffer.len() > start_index {
|
||||
self.eye_buffer.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Sample center, early late
|
||||
let early_id = (self.samples_per_symbol / 2. + self.samples_per_symbol * self.delta)
|
||||
.floor()
|
||||
@ -63,4 +83,3 @@ impl ELGate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user