You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
9.4 KiB
286 lines
9.4 KiB
/*
|
|
* radioBFSKmodulator_F32.h
|
|
*
|
|
* BFSK Modulator including control line low-pass filtering
|
|
* By Bob Larkin W7PUA 17 March 2022
|
|
*
|
|
* Thanks to PJRC and Pau Stoffregen for the Teensy processor, Teensyduino
|
|
* and Teensy Audio. Thanks to Chip Audette for the F32 extension
|
|
* work. Alll of that makes this possible. Bob
|
|
*
|
|
* Copyright (c) 2020 Bob Larkin
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice, development funding notice, and this permission
|
|
* notice shall be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
Notes:
|
|
Modem type: V.23 forward channel
|
|
Data rates: 600-/1200-bps half duplex
|
|
Symbol rate: 600/1200 baud (1 bit/symbol)
|
|
Sample rate: 9.6 kHz
|
|
Mean frequency: Mode 1 (600 bps) 1500 Hz 1 = 1300 Hz 0 = 1700 Hz
|
|
Mode 2 (1200 bps) 1700 Hz 1 = 1300 Hz 0 = 2100 Hz
|
|
Frequency deviation: Mode 1 ±200 Hz
|
|
Mode 2 ±400 Hz
|
|
Tx power spectrum: As per V.2 not exceeding -13 dBm
|
|
Receiver: FSK receiver structure based on quadrature baseband time-delay
|
|
multiply
|
|
Rx performance: 600 bps Flat channel: 11-dB SNR for 1e-5 block error rate
|
|
(1000 bits/block)
|
|
1200 bps Flat channel: 16.5-dB SNR for 1e-5 block error rate
|
|
(1000 bits/block)
|
|
|
|
Modem type: V.23 backward channel
|
|
Data rate: 75-bps half duplex
|
|
Symbol rate: 75 baud (1 bit/symbol)
|
|
Sample rate: 9.6 kHz
|
|
Mean frequency: 420 Hz 1 = 390 Hz 0 = 450 Hz
|
|
Frequency deviation: ±30 Hz
|
|
*/
|
|
/*
|
|
* Binary Frequency Shift Keying modulator
|
|
* Very general as this can be used to send 1 to 32 bit words at any
|
|
* rate and frequencies only constrained by fs/2 limitations. The bit
|
|
* meanings are just 0 or 1. So, start bits, parity and ending bits
|
|
* are all just part of the transmitted data. For 8N1 select
|
|
* 10 bits here and construct a 0 start bit and a 1 ending bit.
|
|
* For instance: If ch8 is an 8-bit character, we transmit
|
|
* 0X200 | (ch8 << 1).
|
|
*
|
|
* After a bit is determined, there is an optional low-pass FIR filter to limit
|
|
* the transmit bandwidth. This produces a non-binary frequency that
|
|
* is set by a phase increment.
|
|
*
|
|
* The update() uses about 13 microseconds for 128 points with no LPF
|
|
* on the control signal. The LPF adds roughly 3 icroseconds per
|
|
* FIR coefficient being used.
|
|
*
|
|
* 2 April 2023 -Corrected to handle outputs from full buffer. RSL
|
|
* Added int16_t getBufferSpace(). RSL
|
|
*/
|
|
|
|
/* Here is an Octave/Matlab .m file to design the Gaussian LP FIR filter
|
|
% FIR Gaussian Pulse-Shaping Filter Design - Based roughly on
|
|
% https://www.mathworks.com/help/signal/ug/fir-gaussian-pulse-shaping-filter-design.html
|
|
%
|
|
symbolRate = 1200.0
|
|
Ts = 1.0/symbolRate; % Symbol time (sec)
|
|
span = 4; % Filter span in symbols
|
|
sampleRate = 48000.0
|
|
overSampleFactor = sampleRate/symbolRate % For printing info
|
|
% nFIR is constrained. Check smallest coefficient and if too big, adjust span
|
|
nFIR = span*sampleRate/symbolRate;
|
|
if rem(nFIR, 2) == 0
|
|
nFIR = nFIR + 1;
|
|
endif
|
|
nFIR
|
|
%
|
|
% Important BT parameter:
|
|
BT = 0.4
|
|
a = Ts*sqrt(0.5*log(2))/BT;
|
|
B = sqrt(log(2)/2)./(a);
|
|
B3dB = B % For printing info
|
|
t = linspace(-span*Ts/2, span*Ts/2, nFIR)';
|
|
cf32f = zeros(nFIR); % FIR coefficients
|
|
cf32f = sqrt(pi)/a*exp(-(pi*t/a).^2);
|
|
cf32f = (cf32f./sum(cf32f)) % Column vector, normalized for DC gain of 1
|
|
*/
|
|
|
|
/* Gaussian FSK LPF Filter A Sample filter per AIS radios.
|
|
* Paste from here into INO program, or design a different LPF.
|
|
* This filter corresponds to:
|
|
* symbolRate = 9600
|
|
* sampleRate = 48000
|
|
* overSampleFactor = 5
|
|
* nFIR = 21
|
|
* BT = 0.40000
|
|
* B3dB = 3840.0
|
|
* Atten at 9600 Hz = 18.8 dB
|
|
float32_t gaussFIR9600_48K[21] = {
|
|
0.0000000029f,
|
|
0.0000000934f,
|
|
0.0000020700f,
|
|
0.0000318610f,
|
|
0.0003406053f,
|
|
0.0025289299f,
|
|
0.0130411453f,
|
|
0.0467076746f,
|
|
0.1161861408f,
|
|
0.2007307811f,
|
|
0.2408613913f,
|
|
0.2007307811f,
|
|
0.1161861408f,
|
|
0.0467076746f,
|
|
0.0130411453f,
|
|
0.0025289299f,
|
|
0.0003406053f,
|
|
0.0000318610f,
|
|
0.0000020700f,
|
|
0.0000000934f,
|
|
0.0000000029f}; */
|
|
|
|
#ifndef _radioBFSKmodulator_f32_h
|
|
#define _radioBFSKmodulator_f32_h
|
|
|
|
#include "AudioStream_F32.h"
|
|
#include "arm_math.h"
|
|
#include "mathDSP_F32.h"
|
|
#include <math.h>
|
|
|
|
class RadioBFSKModulator_F32 : public AudioStream_F32 {
|
|
//GUI: inputs:0, outputs:1 //this line used for automatic generation of GUI node
|
|
//GUI: shortName: BFSKmodulator
|
|
public:
|
|
// Option of AudioSettings_F32 change to block size or sample rate:
|
|
RadioBFSKModulator_F32(void) : AudioStream_F32(0, NULL) {
|
|
// Defaults
|
|
}
|
|
RadioBFSKModulator_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
|
|
setSampleRate_Hz(settings.sample_rate_Hz);
|
|
block_size = settings.audio_block_samples;
|
|
}
|
|
|
|
#define DATA_BUFFER_EMPTY 0
|
|
#define DATA_BUFFER_PART 1
|
|
#define DATA_BUFFER_FULL 2
|
|
|
|
// As viewed from the INO, does the bufffer have space?
|
|
bool bufferHasSpace(void) {
|
|
checkBuffer();
|
|
if(stateBuffer == DATA_BUFFER_FULL)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
int16_t getBufferSpace(void) {
|
|
return 64-checkBuffer();
|
|
}
|
|
|
|
// As viewed from the INO, put a data word into the buffer to send.
|
|
// Returns true if successful.
|
|
bool sendData(uint32_t data) {
|
|
checkBuffer();
|
|
if(stateBuffer == DATA_BUFFER_FULL)
|
|
return false;
|
|
else
|
|
{
|
|
indexIn++;
|
|
dataBuffer[indexIn & indexMask] = data;
|
|
checkBuffer();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void bufferClear(void) {
|
|
indexIn = 0LL;
|
|
indexOut = 0LL;
|
|
}
|
|
|
|
// Sets all parameters for BFSK modulation
|
|
// Returns number of audio samplesPerDataBit
|
|
uint32_t setBFSK(float32_t _bitRate, uint16_t _numBits, float32_t _f0, float32_t _f1) {
|
|
bitRate = _bitRate;
|
|
numBits = _numBits;
|
|
// Transmission rates are quantized by sample rate. On this transmit side,
|
|
// it seems best to error on the side of sending too fast. Thus ceilf()
|
|
samplesPerDataBit = (uint16_t)(ceilf((sample_rate_Hz/bitRate) - 0.0000001));
|
|
|
|
freq[0] = _f0;
|
|
freq[1] = _f1;
|
|
phaseIncrement[0] = kp*freq[0];
|
|
phaseIncrement[1] = kp*freq[1];
|
|
deltaPhase = kp*(freq[1] - freq[0]);
|
|
return samplesPerDataBit;
|
|
}
|
|
|
|
// The amplitude, a, is the peak, as in zero-to-peak. This produces outputs
|
|
// ranging from -a to +a.
|
|
void amplitude(float32_t a) {
|
|
if (a < 0.0f) a = 0.0f;
|
|
magnitude = a;
|
|
}
|
|
|
|
// Low pass filter on frequency control line. Set to NULL to omitfilter.
|
|
void setLPF(float32_t* _FIRdata, float32_t* _FIRcoeff, uint16_t _numCoeffs) {
|
|
FIRdata = _FIRdata;
|
|
if(_FIRcoeff == NULL || _numCoeffs == 0)
|
|
{
|
|
FIRcoeff = NULL;
|
|
numCoeffs = 0;
|
|
return;
|
|
}
|
|
FIRcoeff = _FIRcoeff;
|
|
numCoeffs = _numCoeffs;
|
|
if(numCoeffs != 0)
|
|
indexFIRMask = (uint64_t)(powf(2, (ceilf(logf(numCoeffs)/logf(2.0f)))) - 1.000001f);
|
|
}
|
|
|
|
void setSampleRate_Hz(const float &fs_Hz) {
|
|
sample_rate_Hz = fs_Hz;
|
|
kp = 512.0f/sample_rate_Hz;
|
|
phaseIncrement[0] = kp*freq[0];
|
|
phaseIncrement[1] = kp*freq[1];
|
|
deltaPhase = kp*(freq[1] - freq[0]);
|
|
samplesPerDataBit = (uint32_t)(0.5 + sample_rate_Hz/bitRate);
|
|
}
|
|
|
|
int16_t checkBuffer(void);
|
|
virtual void update(void);
|
|
|
|
#define DATA_BUFFER_EMPTY 0
|
|
#define DATA_BUFFER_PART 1
|
|
#define DATA_BUFFER_FULL 2
|
|
|
|
private:
|
|
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE_EXACT;
|
|
uint16_t block_size = 128;
|
|
float32_t freq[2] = {1300.000f, 2100.000f};
|
|
float32_t kp = 512.0f/sample_rate_Hz;
|
|
float32_t phaseIncrement[2] = {kp*freq[0], kp*freq[1]};
|
|
float32_t deltaPhase = kp*(freq[1] - freq[0]);
|
|
float32_t bitRate = 1200.0f;
|
|
float32_t currentPhase = 0.0f;
|
|
float32_t magnitude = 1.0f;
|
|
|
|
uint32_t currentWord = 0UL;
|
|
uint16_t numBits = 10;
|
|
uint16_t bitSendCount = 0;
|
|
|
|
uint32_t dataBuffer[64]; // Make 64 a define??
|
|
// By using a 64-bit index we never need to wrap around.
|
|
// We create a circular 64-word buffer with the mask.
|
|
int64_t indexIn = 0ULL; // Next word to be entered to buffer
|
|
int64_t indexOut = 0ULL; // Next word to be sent
|
|
int64_t indexMask = 0X003F; // Goes with 64
|
|
uint16_t stateBuffer = DATA_BUFFER_EMPTY;
|
|
uint16_t vc = 0;
|
|
|
|
uint32_t samplePerBitCount = 0;
|
|
// The next for 1200 bit/sec and 44100 sample rate is 37 samples,
|
|
// or actually 1192 bits/sec.
|
|
// For 9600 bit/sec and 96000 sample rate is 10 samples.
|
|
uint32_t samplesPerDataBit = (uint32_t)(0.5 + sample_rate_Hz/bitRate);
|
|
|
|
float32_t* FIRdata = NULL;
|
|
float32_t* FIRcoeff = NULL;
|
|
uint16_t numCoeffs = 0;
|
|
uint64_t indexFIRlatest = 0; // Next word to be entered to buffer
|
|
uint64_t indexFIRMask = 0X001F; // Goes with 32
|
|
};
|
|
#endif
|
|
|