This weekend I had some extra time to work on signal generation. I decided to use MATLAB to encode Slow Scan Television, a fax machine-like way of encoding images for radio processes. I was not wholly successful, but produced meaningful results! Take a look.

My code, as always, is uploaded on Github here. There are a couple of important documents and tests in there as well.


SSTV uses essentially frequency modulation to create messages. Each frequency, from about 1000-2500 Hz, has a unique identity corresponding in the SSTV language. For images, color images use an RGB matrix with their luminance 0-255 that is translated to a frequency 1500-2300 Hz in 3 separate scans of Red, Green, and Blue. This will follow later.

I used a paper from Hamvention 2000 as a “recipe” for creating signals. First, I had to select a sampling frequency. Since all SSTV signals are in the range of about 1000-2500Hz, I used the Nyquist Theorem to select a sampling frequency of 8 kHz; it is comfortably over the 5 kHz minimum. I then used the typical EM wave equation to create my sinusoids of varying length and frequency:

fs = 8000; %Sampling rate = 8000 Hz
dur = duration in seconds
t = 0:1/fs:dur; %create a timestep 1 second in length using the sampling rate
f = message frequency %range of 1000-2500 Hz for sstv
Signal = cos(2*pi*f*t) %generates a vector for the sinusoid

Since the frequency changes constantly for the entire signal, I appended the individual tones of sstv (explained further) to my endproduct “Message Signal.” There are several ways to do this, but I found created an array, then converting the array to a row vector turned out to be the most efficient:

for k=1:7 %do this 7 times
    Repeated_signal(k,:) = cos(2*pi*f*k*t); %create a sinusoid with f*k frequency
end %store it in the kth row (and however many columns) of Repeated_signal array

Repeated_signal = reshape(Repeated_Signal.',1,[]); %concentate each signal to the end

I used this function about 4 times. I encountered difficulties in trying to reuse the array as a matrix, since I reshaped it into a vector. Before I used these arrays, I would clear to ensure I was working with an array and not a vector.

Header / VIS Code

SSTV transmissions begin with a calibration header and a VIS code. Below is the setup for the code:

Calibration Header

This performed pretty linearly. The only difficulty occurred on the VIS Code: using the de2bi() function creates a vector in LSB. I used the following code to convert my binary string to a string of frequencies:

VIS_decimal = 55; %55 for SC2-180
VIS_binary = de2bi(VIS_decimal,7);

%Start the iterative process
VIS_code = zeros(7,length(t));

for k=1:7
    VIS_code(k,:) = cos(2*pi*(1300-(VIS_binary(k)*200)*t)); %use 1100 for 1, 1300 for 0

VIS_code = reshape(VIS_code.',1,[]);

Finally, the calibration concluded with mass addition:

%Combine the signal

Calibration_Header = [Leader_tone, break_tone, Leader_tone, VIS_start, VIS_code, parity_bit, VIS_stop];

%end of sequence


I chose SC2-180 for its simplicity. It uses the following modulation scheme:

The sync and porch pulses, like the headers, were very linear to implement. To create luminance frequencies for the image, I scaled each value (0-255 in 3 arrays) to match the frequency range of 1500-2300 Hz. The array is stored as a uint8, meaning it won’t go high enough for the frequency range of interest.

Image_array = imread('image.png');
%First, restructure image to display correct luminance range
%Range = 2300-1500 Hz = 800 Hz
Img_luminance = double(Image_array) ./ 255 * 800 + 1500;

Next, I sequentially made scans of each line in Red, Green, and Blue iterations. I used the same reshape() function from before:

for n=1:256 %256 lines RGB_line = zeros(3,len*320); for k=1:3 %3 colors R_array = zeros(320,len); %convert the vector back into an array for m=1:320 %320 rows

     R\_array(m,:) = cos(2\*pi\*Img\_luminance(n,m,k)\*t\_rgb); %1 color for the whole line

 R\_array = reshape(R\_array.', 1, \[\]);
 RGB\_line(k,1:len\*320) = R\_array; %red, green, blue for the whole line
RGB\_line = reshape(RGB\_line.',1,\[\]);
RGB\_array(n,1:len\*320\*3) = RGB\_line; end

Finally, I took the array with scan data and combined it sequentially with the sync and porch information:

%Build scan line

Scan_line_ex = [Sync_pulse, Porch, RGB_array(1,:)]; %get the length of a typical scan line
Image_scan = zeros(256,length(Scan_line_ex));

for n=1:256
    Scan_line = [Sync_pulse, Porch, RGB_array(n,:)];
    Image_scan(n,:) = Scan_line;
Image_scan = reshape(Image_scan.',1,[]);

All I had to do now was combine it with the calibration header, and play it using sound().

WRASSE = [Calibration_Header, Image_scan];



Scottie has a different signal “recipe” shown here:

This took a slightly different approach. Pulses occur in the middle of color scans, so I had to introduce them while I was scanning lines. Additionally, the color scans occur Green,Blue,Red, which is out of order from RGB. I used this process to accomplish it:

for n=1:256
    RGB_line = zeros(3,len*320);
    for k=1:3 %in red, green, blue order.
       R_array = zeros(320,len);
       for m=1:320  

        R_array(m,:) = cos(2*pi*Img_luminance(n,m,k)*t_rgb); %1 color for the whole line

       R_array = reshape(R_array.', 1, []);
       RGB_line(k,:) = R_array; %red, green, blue for the whole line
%Build this packet
    Scottie_line = [Separator, RGB_line(2,:), Separator, RGB_line(3,:), Sync_pulse, Porch,     RGB_line(1,:)];
	Scottie_image(n,:) = Scottie_line;

I made a (3,:) matrix that has my entire line, then I inserted pieces of that matrix into the entire Scottie_line. Combining the signal with the calibration header resulted in about the same final result. But how does it work in testing?


Considering it was Mozart’s 265th birthday on the 27th, I used the following 320x256 image for my tests. If I had done this last year, I would have used Beethoven!

Image Used for SSTV Tests

I loaded the image in using imread(). I then used MMSSTV and a virtual cable program to simulate my soundcard as a radio receiver to decode the SSTV Signal.

SC2-180 Signal

I started with SC2-180 first. I received the following result when I forced the mode to “SC2-180” on the program using fs = 8000:

First SSTV Test

Unfortunately, it looked like my encoding scheme is incorrect. My signal was the correct length, but I have to take a further look at how SC2 is encoded. I noticed large differences in my signal based on the sampling frequency value, so I will have to take a look there.

However, I tried it again with the “Auto” feature selected. It interpreted the sc2-180 signal as “ML 180” and produced the following result. I am not sure what modulation scheme ML 180 follows, and cannot even find it online.

Reception using ML 180

It has a slant and incredible color discrepancy. However, it hit the target. The signal’s audio recording can be found here. On my research, I found that the ML modes have increased resolution - this image is much higher than any other received ones.

Scottie-1 Signal

I tried the same method and still received poor, but readable, results. First, forcing selection of “Scottie-1” on MMSTV created the following output:

Incorrect Scottie-1 Decode

And letting MMSTV pick “Auto” resulted in selection of the ML-240 mode. Still, I have little information on these signals except that they have better resolution capability.

ML-240 Mode

Once again, just barely readable. The image came out distorted - Mozart looks like he is watching himself on the right side. Additionally, the calibration header sounds widely different from standard headers shown on the recording. My only guess is that I am not modulation/demodulating this signal via SSB.


This was an enjoyable weekend exercise to understand modulation practices at work. I plan on using part of this code/theory with a Hamshield Mini to create a SSTV beacon for our annual weather balloon.

If you have any advice on scottie/sc2 modes or my calibration header, please let me know. I’d like to get this fully operational!