// *************************************************************************** // * Copyright (C) 2024, Paul Lutus * // * * // * This program is free software; you can redistribute it and/or modify * // * it under the terms of the GNU General Public License as published by * // * the Free Software Foundation; either version 2 of the License, or * // * (at your option) any later version. * // * * // * This program is distributed in the hope that it will be useful, * // * but WITHOUT ANY WARRANTY; without even the implied warranty of * // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * // * GNU General Public License for more details. * // * * // * You should have received a copy of the GNU General Public License * // * along with this program; if not, write to the * // * Free Software Foundation, Inc., * // * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * // *************************************************************************** #![allow(non_snake_case)] // in Cargo.toml // under [dependencies]: // num = "*" use num::complex::Complex; use std::f64::consts::*; use std::io::prelude::*; use std::process::Child; use std::process::Command; use std::process::Stdio; use std::time::Instant; fn open_hackrf( freq_hz: i32, amp_enable: i32, gain_db: i32, samplerate_hz: i32, clock_correction_ppm: i32, ) -> Child { let args = [ "-t".to_string(), "-".to_string(), // i.e. stdin "-f".to_string(), freq_hz.to_string(), "-a".to_string(), amp_enable.to_string(), "-x".to_string(), gain_db.to_string(), "-s".to_string(), samplerate_hz.to_string(), "-C".to_string(), clock_correction_ppm.to_string(), ]; Command::new("hackrf_transfer") .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .expect("Failed to launch program.") } // buffer writes need unsigned 8-bit values // hackrf needs signed 8-bit values fn f64_to_u8(v: Complex) -> [u8; 2] { return [v.re as i8 as u8, v.im as i8 as u8]; } fn transmit( process: &mut Child, mf: f64, // modulation frequency md: f64, // modulation depth samplerate: i32, mode: &str, // cw, am, fm, wfm duration_sec: i32, // duration ) -> std::io::Result<()> { let handle = process.stdin.as_mut().unwrap(); let top = duration_sec * samplerate; let dt = duration_sec as f64 / top as f64; // FM modulation integral let mut fmi = 0.0; let mut t: f64 = 0.0; match mode { "cw" => { for _ in 0..top { let c = 2.0 * PI * t; let cc = Complex::new(0.0, c); // generate cos(x), i sin(x) let v = cc.exp() * 128.0; let iq = f64_to_u8(v); handle.write(&iq)?; t += dt; } } "am" => { for _ in 0..top { // need real c for modulation let c = 2.0 * PI * t; // need complex cc for i,q generation let cc = Complex::new(0.0, c); // modulation value * modulation depth let mv = (1.0 + (c * mf).cos() * md) * 0.5; // generate cos(x), i sin(x) let v = cc.exp() * 128.0 * mv; let iq = f64_to_u8(v); handle.write(&iq)?; t += dt; } } "fm" | "wfm" => { let deviation; match mode { "fm" => { deviation = 0.012; } "wfm" => { deviation = 0.2; } &_ => { panic!("Error: no mode: {mode}"); } } for _ in 0..top { let c = 2.0 * PI * t; // modulation value * modulation depth let mv = (c * mf).cos() * md; // update FM modulation integral fmi += mv * deviation; let cc = Complex::new(0.0, c + fmi); // generate cos(x), i sin(x) let v = cc.exp() * 128.0; let iq = f64_to_u8(v); handle.write(&iq)?; t += dt; } } &_ => { panic!("Error: no mode {mode}"); } } Ok(()) } fn main() { let amp_enable; // produces stronger output let gain_db; // units DB, avoid values above 40 // high_output = True : antenna connections // high_output = False : direct wired connections let high_output = false; if high_output { amp_enable = 1; gain_db = 45; } else { // these low levels are for a direct connection amp_enable = 0; gain_db = 0; } let samplerate_hz = 2000000; // samples / second, 2 MHz is the smallest accepted value let clock_correction_ppm = -6; // experimentally derived 2024.03.15 // duration of signal, seconds let duration_sec = 2; let freq_mhz = 3.0; // carrier frequency MHZ let freq_hz = (freq_mhz * 1e6) as i32; // carrier frequency HZ let start = Instant::now(); // launch hackrf_transfer, return stdin process let mut process = open_hackrf( freq_hz, amp_enable, gain_db, samplerate_hz, clock_correction_ppm, ); // now for modulation and mode let mf = 440.0; // modulation frequency let md = 1.0; // modulation depth, 1.0 = 100% let mode = "am"; transmit(&mut process, mf, md, samplerate_hz, mode, duration_sec).expect("transmit() error"); process.wait().expect("process.wait() error"); let duration = start.elapsed(); println!("Generation time: {duration:?}"); }