Replies: 3 comments 8 replies
-
I'm not aware of his being done before, but it sounds really interesting. I did some experiments trying to get APRS work, but unfortunately without success - though probably due to my lack of DSP skills. I would be very interested in including this in the library if possible/practical! |
Beta Was this translation helpful? Give feedback.
-
After some modifications based on the code provided in #569 and code from multimon-ng, I was able to roughly demod AFSK1200 AX25 packet using ESP32+SX1276. I currently tested using a FM demodulated fragment from an APRS IQ sample by sigidwiki, transmitted the fragment through a UHF transceiver at 434mhz twice, and use SX1276 module which is toned to 868mhz to receive it. (unfortunately my sx1276 module is unable to receive LF bands) I was able to get this result from the serial output:
It was able to pickup the |
Beta Was this translation helpful? Give feedback.
-
An idea came across my mind while reading sx1276's datasheet, that we actually don't need to struggle with the DIO output - the FEI value, which measures the frequency error / bias, is in fact a frequency discriminator. Different from the DIO output, the FEI has much better amplitude resolution. According to the datasheet, the FEI has a freq resolution of ~61Hz(Fstep), therefore we could calculate that in a 12.5khz channel the FEI could provide 12500/fstep which equal to ~203 steps, which is equivalent to ~7 bit resolution - not much, but better than the 1 bit DIO. However, there's a sampling rate issue with FEI. the FEI is update every 4 bit (I don't find a way to change this), and the bitrate setting is limited to 2*Rxbw (I don't find a way to bypass this either), which gives us a max sampling rate at Rxbw/2, way slower than the DIO which has no limit after bitsync disabled. Fortunately, on a 12.5k channel, the FEI sample rate could reach 6.25k, barely enough to sample the 3k voice signal. So I modified the previous code to test voice receive based on FEI, it still uses timer interrupt, but transfer the samples to serial instead of any filtering / dsp. Click to expand#include <Arduino.h>
#include <RadioLib.h>
// SX1278 has the following connections:
// NSS pin: 10
// DIO0 pin: 2
// RESET pin: 9
// DIO1 pin: 3
SX1276 radio = new Module(18, 26, 23, 33);
hw_timer_t *timer;
const float target_freq = 866.0;
const float ppm = 8.5;
float freq = (float) ((target_freq * ppm) / 1e6 + target_freq);
const float rxbw = 12.5;
const float br = rxbw * 2;
// define buffer for raw samples and lowpass filtered samples.
#define RAW_BUF_LEN 4096
volatile uint16_t buffer_raw[RAW_BUF_LEN];
volatile uint16_t raw_write_pos = 0;
volatile uint16_t raw_read_pos = 0;
uint16_t availRaw() {
if (raw_write_pos > raw_read_pos)
return (raw_write_pos - raw_read_pos);
else
return (raw_write_pos + (RAW_BUF_LEN - 1) - raw_read_pos);
}
// this function is called on timer interrupt
void IRAM_ATTR onTimer() {
// read FEI value from SPI register 0x1d & 0x1e
GPIO.out_w1tc = (1 << LORA_CS);
uint8_t buf[2];
SPI.transfer(0x1d & 0x7f);
SPI.transferBytes(nullptr, buf, 2);
GPIO.out_w1ts = (1 << LORA_CS);
// store value to buffer
uint16_t raw = (buf[0] << 8) | buf[1];
buffer_raw[raw_write_pos] = raw;
raw_write_pos = (raw_write_pos + 1) % RAW_BUF_LEN;
}
void setup() {
Serial.begin(4e6);
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI);
// Serial.print(F("[SX1278] Initializing ... "));
// fdev is irrelevant while using FEI.
radio.beginFSK(freq, br, 2.5, rxbw);
radio.disableBitSync();
radio.setRSSIConfig(7);
// start direct mode reception
radio.receiveDirect();
/*
* According to SX1276 datasheet, the bitrate is no larger than rxBW * 2,
* and FEI is update every 4 bit without other options, which resulted in the max
* effective sample rate of rxBW / 2.
*/
timer = timerBegin(0, 20, true);
timerAttachInterrupt(timer, &onTimer, true);
/*
* The sampling is achieved by hardware timer interrupt, therefore the
* alarm value is timer clock frequency (APB_CLK_FREQ = 80MHz on ESP32),
* divided by the divider multiply sampling frequency, which is
* alarm_value = APB_CLK_FREQ / (divider * sample_freq).
* In this case, 80e6 / (20 * 0.5 * rxbw * 1e3) = 80e2 / rxbw.
*/
timerAlarmWrite(timer, (int) (80e2 / rxbw), true);
timerAlarmEnable(timer);
}
void loop() {
// Send samples to serial.
if (availRaw() >= 32) {
uint8_t uart_buf[64];
for (int i = 0; i < 32; ++i) {
uint16_t raw = buffer_raw[raw_read_pos];
uart_buf[2 * i] = raw & 0xFF;
uart_buf[2 * i + 1] = (raw >> 8) & 0xFF;
raw_read_pos = (raw_read_pos + 1) % RAW_BUF_LEN;
}
Serial.write(uart_buf, 64);
Serial.flush();
}
} On the computer I use a simple python script to receive the samples. Click to expandimport serial
import numpy as np
import pyaudio
ser = serial.Serial("COM3", 4000000)
sampling_rate = 6250
buffer_size = 128
audio_buffer = np.zeros(buffer_size, dtype=np.int16)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16, channels=1, rate=sampling_rate, output=True)
with open("output.bin", "wb") as file:
while True:
raw_bytes = ser.read(buffer_size * 2)
if len(raw_bytes) == buffer_size * 2:
int_samples = np.frombuffer(raw_bytes,dtype='<i2')
# int_samples = int_samples * 8
# print(int_samples)
file.write(raw_bytes)
file.flush()
# be careful of the volume while streaming to audio device!
stream.write(int_samples.tobytes())
I currently didn't try demodulate AFSK or other modes using this method yet, I guess it can be better than the DIO method? But the DIO has higher sampling rate, so I can't be sure...However I think the FEI method can open up possibility to modulation schemes require amplitude info, such as 4FSK(maybe DMR is possible, but the sample rate seems not enough), which is impossible for DIO since the DIO-lowpass method is in fact just reconstruct the waveform, it does not and can not recover amplitude info, I've made a mistake on that previously. The AFSK only depends on freq info, so DIO-lowpass can work. The samples of sstv and some audio tests stored by the python script are attached bellow. |
Beta Was this translation helpful? Give feedback.
-
The FSK direct RX mode of SX127X seems to give threshold limited raw output from the FM discriminator on the data pin (with bitsync off), though lost amplitude information, it seems possible to recover some of the low frequency amplitude (~3k) by sampling at a relatively high speed (~50-100k), then applying a low pass filter. So it may be possible to use SX127X to receive NBFM voice, SSTV, or even APRS, etc. Has anyone tried this yet?
Beta Was this translation helpful? Give feedback.
All reactions