Difference between revisions of "IQModulation"
(22 intermediate revisions by the same user not shown) | |||
Line 8: | Line 8: | ||
These MATLAB examples are designed to be played back either directly in an SDR application (as a RAW IQ file) or transmitted using, for example, a hackRF using hackRF_transfer.exe. This allows us to sanity check our waveforms and even see things like bandwidth, carrier level, and filtering etc. | These MATLAB examples are designed to be played back either directly in an SDR application (as a RAW IQ file) or transmitted using, for example, a hackRF using hackRF_transfer.exe. This allows us to sanity check our waveforms and even see things like bandwidth, carrier level, and filtering etc. | ||
+ | |||
+ | Remember | ||
+ | *I is the REAL axis (I stands for In-Phase because somebody thought this was a good name) | ||
+ | *Q is the IMAGINARY axis (Q stands for Quadrature because its shifted by 90 degrees, by a quadrant) | ||
+ | * A DC offset will become a carrier once the baseband signal is shifted to an output frequency. | ||
+ | * A spinning vector is a single frequency. Pos frequencies anti-clockwise, negative frequencies clockwise. | ||
+ | * A constant vector of ANY direction is a DC offset, though the phase of the wave will be shifted by its angle (it will not be a pure cos or sin term, it will have a combination of both) | ||
+ | ** Generic Receiver (TM) will probably assume you are using cos for your carrier - it will lock onto your DC offset and place it at 0 degrees (I = 1). Keep this in mind when transmitting digital data as it will rotate your entire constellation to make this happen! | ||
+ | ** To be safe, only allow carrier to be In-Phase (I = pos DC offset). Maybe that name is starting to make sense now! | ||
+ | |||
<br /> | <br /> | ||
<br /> | <br /> | ||
<hr /> | <hr /> | ||
+ | |||
=== DSB === | === DSB === | ||
− | The most 'natural' wave to generate is "Double Side Band". This is basically AM | + | The most 'natural' wave to generate is "Double Side Band". This is basically your raw audio data shifted up into the RF. It is a raw form of AM, and does NOT have a carrier wave. This means that the instantaneous output power is directly proportional to the input signal's instantaneous amplitude -- silence on the input means the transmitter is effectively turned off. |
==== Summary ==== | ==== Summary ==== | ||
Line 25: | Line 36: | ||
* If we input pure sinusoids (like we will get from an audio signal), we will have NO CARRIER. | * If we input pure sinusoids (like we will get from an audio signal), we will have NO CARRIER. | ||
+ | ==== DSB Code ==== | ||
<pre> | <pre> | ||
%% Generate DSB | %% Generate DSB | ||
Line 31: | Line 43: | ||
Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF | Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF | ||
Ts = 1/Fs; %Sample Period | Ts = 1/Fs; %Sample Period | ||
− | t = | + | t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) |
%Generate our input signal, which MUST be sampled at (or resampled to) Fs | %Generate our input signal, which MUST be sampled at (or resampled to) Fs | ||
%We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep | %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep | ||
− | %Feel free to play here! | + | %Feel free to play around here! |
+ | %Constant Frequency | ||
fs = 1000; %Signal frequency | fs = 1000; %Signal frequency | ||
Signal = sin(2*pi*fs.*t); | Signal = sin(2*pi*fs.*t); | ||
− | %fs = 1000; %Signal frequency | + | %Single frequency, faded in and out sinusodially |
− | %Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here | + | %%fs = 1000; %Signal frequency |
+ | %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here | ||
− | %fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz | + | %Frequency sweep upwards |
− | %Signal = sin(2*pi*fs.*t); | + | %%fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array |
+ | %%Signal = sin(2*pi.*fs.*t); | ||
− | I = K*Signal | + | %Want more fun? Load some real audio data! |
+ | %%Fsbackup = Fs; | ||
+ | %%load Handel; %This will overwrite Fs! | ||
+ | %%Faudio = Fs; | ||
+ | %%Fs = Fsbackup; | ||
+ | %%clear Fsbackup; | ||
+ | %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 | ||
+ | %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros | ||
+ | |||
+ | I = K*Signal; %Modulate I | ||
Q = 0; %Do not modulate Q to preserve symmetric upper and lower sideband (neg and pos frequencies) | Q = 0; %Do not modulate Q to preserve symmetric upper and lower sideband (neg and pos frequencies) | ||
dataStream = I + Q*1i; | dataStream = I + Q*1i; | ||
− | %% Format data for playback on hackrf | + | %% Format data for playback on hackrf, interleave I and Q |
idxo=1; | idxo=1; | ||
− | scale = max(real(dataStream)); | + | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); |
for idx=1:numel(t) | for idx=1:numel(t) | ||
dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); | dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); | ||
Line 60: | Line 84: | ||
%% Write to RAW file | %% Write to RAW file | ||
− | fhandle = fopen(' | + | fhandle = fopen('DSB.iq','w'); |
fwrite(fhandle,dataStreamFile,'int8'); | fwrite(fhandle,dataStreamFile,'int8'); | ||
fclose(fhandle); | fclose(fhandle); | ||
+ | |||
+ | %% Write to wav file, stadard 16-bit PCM | ||
+ | filename = 'output.wav'; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; | ||
+ | audiowrite(filename,dataStreamAudio,Fs); | ||
+ | </pre> | ||
+ | |||
+ | <br /> | ||
+ | <br /> | ||
+ | <hr /> | ||
+ | |||
+ | === AM === | ||
+ | |||
+ | If you try to tune to the DSB signal but are off by a few Khz or even a few HZ, you will notice that the audio sounds distorted and horrible. Without a carrier, we have no way to lock into the true signal frequency, especially during periods of silence. There are complex ways to estimate the carrier and lock, but AM gets around this by adding a DC offset to the signal. Remember that a DC offset is just a zero frequency component at a constant level. This will become the carrier when it gets shifted up the spectrum by the SDR hardware. | ||
+ | |||
+ | ==== Summary ==== | ||
+ | |||
+ | We will modulate the amplitude of I with the input signal by the modulation constant K, but we will also add a DC (0Hz) term to be the carrier. | ||
+ | * Pc = relative carrier power. If your signal is 1 and Pc is 1 you will have a 50% power split between carrier and signal. | ||
+ | ** Real shortwave stations use more power in the carrier to help lock on when band conditions are weak. | ||
+ | ** This means you might see the carrier but not the signal at all from a weak/distant station! | ||
+ | * K = Gain => Gain can be whatever it needs to be here, it will only affect the average output power. | ||
+ | * I = K*Signal+Pc => The real axis will be set to the input signal times a gain constant. Pc is the DC offset which is our carrier (centered at 0Hz) | ||
+ | * Q = 0 => we will NOT modulate the imaginary axis at all. | ||
+ | * Because we set Q to 0, we will see both positive AND negative parts of all frequency components. (See Euler's equation for cos(x) in terms of e^jw) | ||
+ | * If we input pure sinusoids (like we will get from an audio signal), we will have a carrier split between the signal's average power and the Pc term. | ||
+ | |||
+ | ==== AM Code ==== | ||
+ | <pre> | ||
+ | %% Generate AM | ||
+ | clear all; | ||
+ | Pc = 1; %carrier power (ratio of signal level to this number will determine power split) | ||
+ | K = 1; %Modulation Constant | ||
+ | Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF | ||
+ | Ts = 1/Fs; %Sample Period | ||
+ | t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) | ||
+ | |||
+ | %Generate our input signal, which MUST be sampled at (or resampled to) Fs | ||
+ | %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep | ||
+ | %Feel free to play around here! | ||
+ | |||
+ | %Constant Frequency | ||
+ | fs = 1000; %Signal frequency | ||
+ | Signal = sin(2*pi*fs.*t); | ||
+ | |||
+ | %Single frequency, faded in and out sinusodially | ||
+ | %%fs = 1000; %Signal frequency | ||
+ | %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here | ||
+ | |||
+ | %Frequency sweep upwards | ||
+ | %%fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array | ||
+ | %%Signal = sin(2*pi.*fs.*t); | ||
+ | |||
+ | %Want more fun? Load some real audio data! | ||
+ | %%Fsbackup = Fs; | ||
+ | %%load Handel; %This will overwrite Fs! | ||
+ | %%Faudio = Fs; | ||
+ | %%Fs = Fsbackup; | ||
+ | %%clear Fsbackup; | ||
+ | %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 | ||
+ | %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros | ||
+ | |||
+ | I = K*Signal+Pc; %Modulate I | ||
+ | Q = 0; %Do not modulate Q to preserve symmetric upper and lower sideband (neg and pos frequencies) | ||
+ | dataStream = I + Q*1i; | ||
+ | |||
+ | %% Format data for playback on hackrf, interleave I and Q | ||
+ | idxo=1; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | for idx=1:numel(t) | ||
+ | dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); | ||
+ | dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); | ||
+ | idxo = idxo+2; | ||
+ | end | ||
+ | |||
+ | %% Write to RAW file | ||
+ | fhandle = fopen('AM.iq','w'); | ||
+ | fwrite(fhandle,dataStreamFile,'int8'); | ||
+ | fclose(fhandle); | ||
+ | |||
+ | %% Write to wav file, stadard 16-bit PCM | ||
+ | filename = 'AM.wav'; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; | ||
+ | audiowrite(filename,dataStreamAudio,Fs); | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | <br /> | ||
+ | <br /> | ||
+ | <hr /> | ||
+ | === SSB === | ||
+ | |||
+ | Single-sideband removes the negative (for upper) frequency components and the carrier and is therefore a very efficient means of transmitting voice. It is often used by mobile voice stations as a clearly audible conversation can be had using only 3Khz of spectrum. | ||
+ | |||
+ | ==== Summary ==== | ||
+ | |||
+ | We will modulate the amplitude of I with the input signal by the modulation constant K, then derive Q from the Hilbert transform. | ||
+ | * K = Gain => Gain can be whatever it needs to be here, it will only affect the average output power. | ||
+ | * I = K*Signal+Pc => The real axis will be set to the input signal times a gain constant. Pc is the DC offset which is our carrier (centered at 0Hz) | ||
+ | * Q = 0 => we will NOT modulate the imaginary axis at all. | ||
+ | * The Hibert transform is used to remove the negative frequencies. | ||
+ | * We will have a carrier if we added in a DC term (Pc) to our Signal! Do we want this? Is it useful? | ||
+ | ** Although usually not done, adding a carrier might have some value! | ||
+ | ** Tuning to an SSB broadcoast DOES mean that you have to make some assumptions about the expected signal. For human males, this is usually not a problem. For children's voices, high female voices, and musical content, it can be very difficult to tune correctly to the signal. | ||
+ | ** Adding in a carrier would allow a receiver to lock onto the absolute frequency of the transmitter, at the expense of signal output power, since the full output power of the transmitter is no longer going to ONLY the voice signal. | ||
+ | |||
+ | ==== SSB Code ==== | ||
+ | <pre> | ||
+ | %% Generate SSB (USB) | ||
+ | clear all; | ||
+ | K = 1; %Modulation Constant | ||
+ | Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF | ||
+ | Ts = 1/Fs; %Sample Period | ||
+ | t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) | ||
+ | |||
+ | %Generate our input signal, which MUST be sampled at (or resampled to) Fs | ||
+ | %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep | ||
+ | %Feel free to play around here! | ||
+ | |||
+ | %Constant Frequency | ||
+ | fs = 1000; %Signal frequency | ||
+ | Signal = sin(2*pi*fs.*t); | ||
+ | |||
+ | %Single frequency, faded in and out sinusodially | ||
+ | %%fs = 1000; %Signal frequency | ||
+ | %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here | ||
+ | |||
+ | %Frequency sweep upwards | ||
+ | %%fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array | ||
+ | %%Signal = sin(2*pi.*fs.*t); | ||
+ | |||
+ | %Want more fun? Load some real audio data! | ||
+ | %%Fsbackup = Fs; | ||
+ | %%load Handel; %This will overwrite Fs! | ||
+ | %%Faudio = Fs; | ||
+ | %%Fs = Fsbackup; | ||
+ | %%clear Fsbackup; | ||
+ | %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 | ||
+ | %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros | ||
+ | |||
+ | I = K*Signal; %Modulate I | ||
+ | dataStream = hilbert(I); %Q is derived from I by the Hilbert transform | ||
+ | |||
+ | %% Format data for playback on hackrf, interleave I and Q | ||
+ | idxo=1; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | for idx=1:numel(t) | ||
+ | dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); | ||
+ | dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); | ||
+ | idxo = idxo+2; | ||
+ | end | ||
+ | |||
+ | %% Write to RAW file | ||
+ | fhandle = fopen('SSB.iq','w'); | ||
+ | fwrite(fhandle,dataStreamFile,'int8'); | ||
+ | fclose(fhandle); | ||
+ | |||
+ | %% Write to wav file, stadard 16-bit PCM | ||
+ | filename = 'SSB.wav'; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; | ||
+ | audiowrite(filename,dataStreamAudio,Fs); | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | <br /> | ||
+ | <br /> | ||
+ | <hr /> | ||
+ | === PM === | ||
+ | |||
+ | Phase Modulation is a type of angle modulation, where we directly modify the angle of a constant magnitude vector, but not its length. | ||
+ | |||
+ | ==== Summary ==== | ||
+ | |||
+ | We will modulate the angle of a vector by the amplitude of the input signal. The angle size in radians per volt will be proportional to the the modulation constant K | ||
+ | * K = Modulation Constant => This is in radians per volt | ||
+ | * I = The cos of the instantaneous vector angle - the amplitude of the IQ vector is always 1 | ||
+ | * Q = The sin of the instantaneous vector angle - the amplitude of the IQ vector is always 1 | ||
+ | |||
+ | ==== PM Code ==== | ||
+ | |||
+ | <pre> | ||
+ | %% Generate PM | ||
+ | clear all; | ||
+ | K = 0.001; %Modulation Constant in radians per volt | ||
+ | Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF | ||
+ | Ts = 1/Fs; %Sample Period | ||
+ | t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) | ||
+ | |||
+ | %Generate our input signal, which MUST be sampled at (or resampled to) Fs | ||
+ | %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep | ||
+ | %Feel free to play around here! | ||
+ | |||
+ | %Constant Frequency | ||
+ | %%fs = 1000; %Signal frequency | ||
+ | %%Signal = sin(2*pi*fs.*t); | ||
+ | |||
+ | %Single frequency, faded in and out sinusodially | ||
+ | %%fs = 1000; %Signal frequency | ||
+ | %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here | ||
+ | |||
+ | %Frequency sweep upwards | ||
+ | fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array | ||
+ | Signal = sin(2*pi.*fs.*t); | ||
+ | |||
+ | %Want more fun? Load some real audio data! | ||
+ | %%Fsbackup = Fs; | ||
+ | %%load Handel; %This will overwrite Fs! | ||
+ | %%Faudio = Fs; | ||
+ | %%Fs = Fsbackup; | ||
+ | %%clear Fsbackup; | ||
+ | %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 | ||
+ | %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros | ||
+ | |||
+ | I = cos(K*Signal); | ||
+ | Q = sin(K*Signal); | ||
+ | dataStream = I + Q*1i; | ||
+ | |||
+ | |||
+ | %% Format data for playback on hackrf, interleave I and Q | ||
+ | idxo=1; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | for idx=1:numel(t) | ||
+ | dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); | ||
+ | dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); | ||
+ | idxo = idxo+2; | ||
+ | end | ||
+ | |||
+ | %% Write to RAW file | ||
+ | fhandle = fopen('PM.iq','w'); | ||
+ | fwrite(fhandle,dataStreamFile,'int8'); | ||
+ | fclose(fhandle); | ||
+ | |||
+ | %% Write to wav file, stadard 16-bit PCM | ||
+ | filename = 'PM.wav'; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; | ||
+ | audiowrite(filename,dataStreamAudio,Fs); | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | <br /> | ||
+ | <br /> | ||
+ | <hr /> | ||
+ | |||
+ | === FM === | ||
+ | |||
+ | FM is another type of angle modulation, but instead of the absolute angle being varied to produce a set phase offset for a given amplitude, the rate of change of the angle is varied to produce a set frequency offset for a given amplitude. The vector spins up to a positive value as the signal input grows, then spins down to zero as the signal decreases to zero, then spins backwards as the signal goes negative. | ||
+ | |||
+ | ==== Summary ==== | ||
+ | |||
+ | We will modulate the angular velocity of a vector by using the amplitude of the input signal. The angular velocity will be set by the modulation constant K in radians/sec/volt, or (2pi)(Hz/Volt) | ||
+ | * K = Modulation Constant => This is in radians per second per volt, or. K can be expressed in Hz / volt if multiplied by 2*pi before using. | ||
+ | * I = The cos of the instantaneous vector angle - the amplitude of the IQ vector is always 1 | ||
+ | * Q = The sin of the instantaneous vector angle - the amplitude of the IQ vector is always 1 | ||
+ | |||
+ | ==== FM Code ==== | ||
+ | |||
+ | <pre> | ||
+ | %% Generate FM | ||
+ | clear all; | ||
+ | K = 0.001; %Modulation Constant in radians per volt | ||
+ | Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF | ||
+ | Ts = 1/Fs; %Sample Period | ||
+ | t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) | ||
+ | |||
+ | %Generate our input signal, which MUST be sampled at (or resampled to) Fs | ||
+ | %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep | ||
+ | %Feel free to play around here! | ||
+ | |||
+ | %Constant Frequency | ||
+ | %%fs = 1000; %Signal frequency | ||
+ | %%Signal = sin(2*pi*fs.*t); | ||
+ | |||
+ | %Single frequency, faded in and out sinusodially | ||
+ | %%fs = 1000; %Signal frequency | ||
+ | %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here | ||
+ | |||
+ | %Frequency sweep upwards | ||
+ | fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array | ||
+ | Signal = sin(2*pi.*fs.*t); | ||
+ | |||
+ | %Want more fun? Load some real audio data! | ||
+ | %%Fsbackup = Fs; | ||
+ | %%load Handel; %This will overwrite Fs! | ||
+ | %%Faudio = Fs; | ||
+ | %%Fs = Fsbackup; | ||
+ | %%clear Fsbackup; | ||
+ | %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 | ||
+ | %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros | ||
+ | |||
+ | omega = 0; | ||
+ | for idx=1:numel(t) | ||
+ | if idx > 1 | ||
+ | omega(idx) = omega(idx-1)+K*Signal(idx); | ||
+ | else | ||
+ | omega(idx) = Signal(idx); | ||
+ | end | ||
+ | end | ||
+ | I = cos(omega); | ||
+ | Q = sin(omega); | ||
+ | dataStream = I + Q*1i; | ||
+ | |||
+ | |||
+ | %% Format data for playback on hackrf, interleave I and Q | ||
+ | idxo=1; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | for idx=1:numel(t) | ||
+ | dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); | ||
+ | dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); | ||
+ | idxo = idxo+2; | ||
+ | end | ||
+ | |||
+ | %% Write to RAW file | ||
+ | fhandle = fopen('FM.iq','w'); | ||
+ | fwrite(fhandle,dataStreamFile,'int8'); | ||
+ | fclose(fhandle); | ||
+ | |||
+ | %% Write to wav file, stadard 16-bit PCM | ||
+ | filename = 'FM.wav'; | ||
+ | scale = max([abs(real(dataStream)) abs(imag(dataStream))]); | ||
+ | dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; | ||
+ | audiowrite(filename,dataStreamAudio,Fs); | ||
</pre> | </pre> |
Latest revision as of 11:50, 2 June 2017
Problem Statement
Standard methods of explaining modulation seem to fall flat on their faces when we start to talk about things like singlesideband, in fact, when we talk about sidebands at all. A much more natural way to understand modulation is to describe it at the baseband level using a unit circle. At this level, a single frequency is represented by a rotating vector of constant speed (angular velocity). An unmoving vector is simply a DC offset, thus, a carrier wave.
One of the traps here is that this does involve complex numbers, and therefore, imaginary amplitudes and negative frequencies. Regardless, I believe that this is the easiest way to understand and synthesize modulated waveforms for transmission in zero-IF systems which include most SDRs (Software Defined Radios).
What do these examples do?
These MATLAB examples are designed to be played back either directly in an SDR application (as a RAW IQ file) or transmitted using, for example, a hackRF using hackRF_transfer.exe. This allows us to sanity check our waveforms and even see things like bandwidth, carrier level, and filtering etc.
Remember
- I is the REAL axis (I stands for In-Phase because somebody thought this was a good name)
- Q is the IMAGINARY axis (Q stands for Quadrature because its shifted by 90 degrees, by a quadrant)
- A DC offset will become a carrier once the baseband signal is shifted to an output frequency.
- A spinning vector is a single frequency. Pos frequencies anti-clockwise, negative frequencies clockwise.
- A constant vector of ANY direction is a DC offset, though the phase of the wave will be shifted by its angle (it will not be a pure cos or sin term, it will have a combination of both)
- Generic Receiver (TM) will probably assume you are using cos for your carrier - it will lock onto your DC offset and place it at 0 degrees (I = 1). Keep this in mind when transmitting digital data as it will rotate your entire constellation to make this happen!
- To be safe, only allow carrier to be In-Phase (I = pos DC offset). Maybe that name is starting to make sense now!
DSB
The most 'natural' wave to generate is "Double Side Band". This is basically your raw audio data shifted up into the RF. It is a raw form of AM, and does NOT have a carrier wave. This means that the instantaneous output power is directly proportional to the input signal's instantaneous amplitude -- silence on the input means the transmitter is effectively turned off.
Summary
We will modulate the amplitude of I with the input signal by the modulation constant K
- K = Gain => Gain can be whatever it needs to be here, it will only affect the average output power.
- I = K*Signal => The real axis will be set to the input signal times a gain constant.
- Q = 0 => we will NOT modulate the imaginary axis at all.
- Because we set Q to 0, we will see both positive AND negative parts of all frequency components. (See Euler's equation for cos(x) in terms of e^jw)
- If we input pure sinusoids (like we will get from an audio signal), we will have NO CARRIER.
DSB Code
%% Generate DSB clear all; K = 1; %Modulation Constant Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF Ts = 1/Fs; %Sample Period t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) %Generate our input signal, which MUST be sampled at (or resampled to) Fs %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep %Feel free to play around here! %Constant Frequency fs = 1000; %Signal frequency Signal = sin(2*pi*fs.*t); %Single frequency, faded in and out sinusodially %%fs = 1000; %Signal frequency %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here %Frequency sweep upwards %%fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array %%Signal = sin(2*pi.*fs.*t); %Want more fun? Load some real audio data! %%Fsbackup = Fs; %%load Handel; %This will overwrite Fs! %%Faudio = Fs; %%Fs = Fsbackup; %%clear Fsbackup; %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros I = K*Signal; %Modulate I Q = 0; %Do not modulate Q to preserve symmetric upper and lower sideband (neg and pos frequencies) dataStream = I + Q*1i; %% Format data for playback on hackrf, interleave I and Q idxo=1; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); for idx=1:numel(t) dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); idxo = idxo+2; end %% Write to RAW file fhandle = fopen('DSB.iq','w'); fwrite(fhandle,dataStreamFile,'int8'); fclose(fhandle); %% Write to wav file, stadard 16-bit PCM filename = 'output.wav'; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; audiowrite(filename,dataStreamAudio,Fs);
AM
If you try to tune to the DSB signal but are off by a few Khz or even a few HZ, you will notice that the audio sounds distorted and horrible. Without a carrier, we have no way to lock into the true signal frequency, especially during periods of silence. There are complex ways to estimate the carrier and lock, but AM gets around this by adding a DC offset to the signal. Remember that a DC offset is just a zero frequency component at a constant level. This will become the carrier when it gets shifted up the spectrum by the SDR hardware.
Summary
We will modulate the amplitude of I with the input signal by the modulation constant K, but we will also add a DC (0Hz) term to be the carrier.
- Pc = relative carrier power. If your signal is 1 and Pc is 1 you will have a 50% power split between carrier and signal.
- Real shortwave stations use more power in the carrier to help lock on when band conditions are weak.
- This means you might see the carrier but not the signal at all from a weak/distant station!
- K = Gain => Gain can be whatever it needs to be here, it will only affect the average output power.
- I = K*Signal+Pc => The real axis will be set to the input signal times a gain constant. Pc is the DC offset which is our carrier (centered at 0Hz)
- Q = 0 => we will NOT modulate the imaginary axis at all.
- Because we set Q to 0, we will see both positive AND negative parts of all frequency components. (See Euler's equation for cos(x) in terms of e^jw)
- If we input pure sinusoids (like we will get from an audio signal), we will have a carrier split between the signal's average power and the Pc term.
AM Code
%% Generate AM clear all; Pc = 1; %carrier power (ratio of signal level to this number will determine power split) K = 1; %Modulation Constant Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF Ts = 1/Fs; %Sample Period t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) %Generate our input signal, which MUST be sampled at (or resampled to) Fs %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep %Feel free to play around here! %Constant Frequency fs = 1000; %Signal frequency Signal = sin(2*pi*fs.*t); %Single frequency, faded in and out sinusodially %%fs = 1000; %Signal frequency %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here %Frequency sweep upwards %%fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array %%Signal = sin(2*pi.*fs.*t); %Want more fun? Load some real audio data! %%Fsbackup = Fs; %%load Handel; %This will overwrite Fs! %%Faudio = Fs; %%Fs = Fsbackup; %%clear Fsbackup; %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros I = K*Signal+Pc; %Modulate I Q = 0; %Do not modulate Q to preserve symmetric upper and lower sideband (neg and pos frequencies) dataStream = I + Q*1i; %% Format data for playback on hackrf, interleave I and Q idxo=1; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); for idx=1:numel(t) dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); idxo = idxo+2; end %% Write to RAW file fhandle = fopen('AM.iq','w'); fwrite(fhandle,dataStreamFile,'int8'); fclose(fhandle); %% Write to wav file, stadard 16-bit PCM filename = 'AM.wav'; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; audiowrite(filename,dataStreamAudio,Fs);
SSB
Single-sideband removes the negative (for upper) frequency components and the carrier and is therefore a very efficient means of transmitting voice. It is often used by mobile voice stations as a clearly audible conversation can be had using only 3Khz of spectrum.
Summary
We will modulate the amplitude of I with the input signal by the modulation constant K, then derive Q from the Hilbert transform.
- K = Gain => Gain can be whatever it needs to be here, it will only affect the average output power.
- I = K*Signal+Pc => The real axis will be set to the input signal times a gain constant. Pc is the DC offset which is our carrier (centered at 0Hz)
- Q = 0 => we will NOT modulate the imaginary axis at all.
- The Hibert transform is used to remove the negative frequencies.
- We will have a carrier if we added in a DC term (Pc) to our Signal! Do we want this? Is it useful?
- Although usually not done, adding a carrier might have some value!
- Tuning to an SSB broadcoast DOES mean that you have to make some assumptions about the expected signal. For human males, this is usually not a problem. For children's voices, high female voices, and musical content, it can be very difficult to tune correctly to the signal.
- Adding in a carrier would allow a receiver to lock onto the absolute frequency of the transmitter, at the expense of signal output power, since the full output power of the transmitter is no longer going to ONLY the voice signal.
SSB Code
%% Generate SSB (USB) clear all; K = 1; %Modulation Constant Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF Ts = 1/Fs; %Sample Period t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) %Generate our input signal, which MUST be sampled at (or resampled to) Fs %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep %Feel free to play around here! %Constant Frequency fs = 1000; %Signal frequency Signal = sin(2*pi*fs.*t); %Single frequency, faded in and out sinusodially %%fs = 1000; %Signal frequency %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here %Frequency sweep upwards %%fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array %%Signal = sin(2*pi.*fs.*t); %Want more fun? Load some real audio data! %%Fsbackup = Fs; %%load Handel; %This will overwrite Fs! %%Faudio = Fs; %%Fs = Fsbackup; %%clear Fsbackup; %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros I = K*Signal; %Modulate I dataStream = hilbert(I); %Q is derived from I by the Hilbert transform %% Format data for playback on hackrf, interleave I and Q idxo=1; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); for idx=1:numel(t) dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); idxo = idxo+2; end %% Write to RAW file fhandle = fopen('SSB.iq','w'); fwrite(fhandle,dataStreamFile,'int8'); fclose(fhandle); %% Write to wav file, stadard 16-bit PCM filename = 'SSB.wav'; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; audiowrite(filename,dataStreamAudio,Fs);
PM
Phase Modulation is a type of angle modulation, where we directly modify the angle of a constant magnitude vector, but not its length.
Summary
We will modulate the angle of a vector by the amplitude of the input signal. The angle size in radians per volt will be proportional to the the modulation constant K
- K = Modulation Constant => This is in radians per volt
- I = The cos of the instantaneous vector angle - the amplitude of the IQ vector is always 1
- Q = The sin of the instantaneous vector angle - the amplitude of the IQ vector is always 1
PM Code
%% Generate PM clear all; K = 0.001; %Modulation Constant in radians per volt Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF Ts = 1/Fs; %Sample Period t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) %Generate our input signal, which MUST be sampled at (or resampled to) Fs %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep %Feel free to play around here! %Constant Frequency %%fs = 1000; %Signal frequency %%Signal = sin(2*pi*fs.*t); %Single frequency, faded in and out sinusodially %%fs = 1000; %Signal frequency %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here %Frequency sweep upwards fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array Signal = sin(2*pi.*fs.*t); %Want more fun? Load some real audio data! %%Fsbackup = Fs; %%load Handel; %This will overwrite Fs! %%Faudio = Fs; %%Fs = Fsbackup; %%clear Fsbackup; %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros I = cos(K*Signal); Q = sin(K*Signal); dataStream = I + Q*1i; %% Format data for playback on hackrf, interleave I and Q idxo=1; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); for idx=1:numel(t) dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); idxo = idxo+2; end %% Write to RAW file fhandle = fopen('PM.iq','w'); fwrite(fhandle,dataStreamFile,'int8'); fclose(fhandle); %% Write to wav file, stadard 16-bit PCM filename = 'PM.wav'; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; audiowrite(filename,dataStreamAudio,Fs);
FM
FM is another type of angle modulation, but instead of the absolute angle being varied to produce a set phase offset for a given amplitude, the rate of change of the angle is varied to produce a set frequency offset for a given amplitude. The vector spins up to a positive value as the signal input grows, then spins down to zero as the signal decreases to zero, then spins backwards as the signal goes negative.
Summary
We will modulate the angular velocity of a vector by using the amplitude of the input signal. The angular velocity will be set by the modulation constant K in radians/sec/volt, or (2pi)(Hz/Volt)
- K = Modulation Constant => This is in radians per second per volt, or. K can be expressed in Hz / volt if multiplied by 2*pi before using.
- I = The cos of the instantaneous vector angle - the amplitude of the IQ vector is always 1
- Q = The sin of the instantaneous vector angle - the amplitude of the IQ vector is always 1
FM Code
%% Generate FM clear all; K = 0.001; %Modulation Constant in radians per volt Fs = 1e6; %Sample Rate. 1e6 is the minimum sample rate accepted by HackRF Ts = 1/Fs; %Sample Period t = 0:Ts:10; %Time axis (0 to 10 seconds at Ts interval) %Generate our input signal, which MUST be sampled at (or resampled to) Fs %We have options! Constant frequency, sinusoidally fading in and out at a constant frequency, and frequency sweep %Feel free to play around here! %Constant Frequency %%fs = 1000; %Signal frequency %%Signal = sin(2*pi*fs.*t); %Single frequency, faded in and out sinusodially %%fs = 1000; %Signal frequency %%Signal = cos(2*pi*(1/10).*t) .* sin(2*pi*fs.*t); %This is our input signal, which MUST be sampled at Fs as presented here %Frequency sweep upwards fs = linspace(1e3,10e3,numel(t)); %Signal frequency, from 1khz to 10khz linearly spaced array Signal = sin(2*pi.*fs.*t); %Want more fun? Load some real audio data! %%Fsbackup = Fs; %%load Handel; %This will overwrite Fs! %%Faudio = Fs; %%Fs = Fsbackup; %%clear Fsbackup; %%Signal = resample(y/mean(abs(y)),Fs,Faudio); %audio is NOT sampled at the right rate, resample to our Fs and normalize avg amplitude to 1 %%Signal = [Signal' zeros(1,(Fs*max(t))-numel(Signal)+1)]; %extend the 8 second audio clip to our max(t) using zeros omega = 0; for idx=1:numel(t) if idx > 1 omega(idx) = omega(idx-1)+K*Signal(idx); else omega(idx) = Signal(idx); end end I = cos(omega); Q = sin(omega); dataStream = I + Q*1i; %% Format data for playback on hackrf, interleave I and Q idxo=1; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); for idx=1:numel(t) dataStreamFile(idxo) = int8(real(dataStream(idx))*(127/scale)); dataStreamFile(idxo+1) = int8(imag(dataStream(idx))*(127/scale)); idxo = idxo+2; end %% Write to RAW file fhandle = fopen('FM.iq','w'); fwrite(fhandle,dataStreamFile,'int8'); fclose(fhandle); %% Write to wav file, stadard 16-bit PCM filename = 'FM.wav'; scale = max([abs(real(dataStream)) abs(imag(dataStream))]); dataStreamAudio = [int16(real(dataStream).*(2^15/scale)); int16(imag(dataStream).*(2^15/scale))]'; audiowrite(filename,dataStreamAudio,Fs);