Spartan-6 Microboard: FM All-Band CQ Transmitter

Here we go! This time we’re starting something a little bit more advanced. Lets check out the digital clock manager (DCM), which allows us to make custom clocks that run faster or slower than the actual physical clock. So what are we going to do with this? We’re going to make an FM transmitter that can transmit on any amateur radio band* and, of course, the standard FM radio band.

* please note this is totally illegal if you’re not a licensed HAM. So if you get caught, that’s on you.

I got most of this code from hamsterworks. It was a short simple piece of code that would type out S O S on 91MHz. I made it slightly less ridiculous I having a type out CQ (this is used to seek out contacts.) At least that way, my neighbors aren’t taking an axe to their dashboards trying to find a little man screaming for help. My neighbors are weird like that.

I put a ~1M long piece of 22Ga wire in the output pin, and get at least 10M range. No idea what kind of power I’m putting out (microWatts at most) and I fixed the frequency drift by finding the exact frequency given by the DCM.

Since this code had to be modified (only slightly, I won’t lie) I did some of my best commenting; I know that would have helped me. (Mind the scrollbar at the bottom.)


library IEEE;

entity fm_xmit is
    Port ( clk : in  STD_LOGIC;
           antenna : out  STD_LOGIC;
	   rst : in STD_LOGIC;
	   sw : in std_logic_vector(3 downto 0)	
end fm_xmit;

architecture Behavioral of fm_xmit is
   component fast_clock
   port ( CLK_IN1  : in  std_logic; --These are the signals from our Digital Clock Manager
          CLK_OUT1 : out std_logic; --Make sure the names are the exact same (not case sensitive)
	  RESET    : in std_logic   --Or this won't work at all
   end component;

   signal clk320            : std_logic;
   signal shift_ctr         : unsigned (4 downto 0) := (others => '0');
   signal phase_accumulator : unsigned (31 downto 0) := (others => '0');
   signal beep_counter      : unsigned (19 downto 0):= (others => '0'); -- gives a 305Hz beep signal
   signal message           : std_logic_vector(33 downto 0) := "1110101110100001110111010111000000"; --gives CQ in morse

	signal band_160m			: std_logic_vector (3 downto 0) := "1111"; --These are the 
	signal band_80m				: std_logic_vector (3 downto 0) := "1110"; --positions of 
	signal band_60m				: std_logic_vector (3 downto 0) := "1101"; --the 4 onboard
	signal band_40m				: std_logic_vector (3 downto 0) := "1100"; --dip switches,
	signal band_30m				: std_logic_vector (3 downto 0) := "1011"; --giving us a
	signal band_20m				: std_logic_vector (3 downto 0) := "1010"; --binary band 
	signal band_17m				: std_logic_vector (3 downto 0) := "1001"; --select.
	signal band_15m				: std_logic_vector (3 downto 0) := "1000";
	signal band_12m				: std_logic_vector (3 downto 0) := "0111";
	signal band_10m				: std_logic_vector (3 downto 0) := "0110";
	signal band_6m				: std_logic_vector (3 downto 0) := "0101";
	signal band_2m				: std_logic_vector (3 downto 0) := "0100";
	signal band_1_25m			: std_logic_vector (3 downto 0) := "0011";
	signal band_fm				: std_logic_vector (3 downto 0) := "0000";
	shared VARIABLE upper_side_signal: 	INTEGER; --these variables allow us to calculate
	shared VARIABLE lower_side_signal: 	INTEGER; --our clock divider ratio from the base 
	shared VARIABLE current_freq: 		INTEGER; --320MHZ (319996800Hz) to get our square
	shared VARIABLE center_signal: 		INTEGER; --wave period right


clock320 : fast_clock PORT MAP(
clk_in1 => CLK,
clk_out1 => CLK320,
reset => rst

   antenna <= std_logic(phase_accumulator(31));

   process(clk320, sw)
	IF    (sw = band_160m) 		THEN 	current_freq := 1810000; --these are the standard morse
	ELSIF (sw = band_80m) 		THEN 	current_freq := 3560000; --calling frequencies in the USA
	ELSIF (sw = band_60m) 		THEN 	current_freq := 5403500; --modify them to whatever you like
	ELSIF (sw = band_40m) 		THEN 	current_freq := 7040000; --in Hz.
	ELSIF (sw = band_30m) 		THEN	current_freq := 10106000;
	ELSIF (sw = band_20m) 		THEN 	current_freq := 14060000;
	ELSIF (sw = band_17m) 		THEN 	current_freq := 18080000;
	ELSIF (sw = band_15m) 		THEN 	current_freq := 21060000;
	ELSIF (sw = band_12m) 		THEN 	current_freq := 24910000;
	ELSIF (sw = band_10m) 		THEN 	current_freq := 28060000;
	ELSIF (sw = band_6m) 		THEN 	current_freq := 50090000;
	ELSIF (sw = band_2m) 		THEN 	current_freq := 144100000;
	ELSIF (sw = band_1_25m) 	THEN 	current_freq := 222100000;
	ELSIF (sw = band_fm) 		THEN 	current_freq := 100100000; --100.1 MHz, open freq here
    current_freq := 91000000;

	upper_side_signal := (current_freq/319996800*(2**32)) + 75000; --This exact clock frequency fixes
	lower_side_signal := (current_freq/319996800*(2**32)) - 75000; --the 'drift' shown in the video
	center_signal := (current_freq/31996800*(2**32));

      if rising_edge(clk320) then
         if beep_counter = x"FFFFF" then
            if shift_ctr = "00000" then
               message <= message(0) & message(33 downto 1);
            end if;
            shift_ctr <= shift_ctr + 1;
         end if;      

         if message(0) = '1' then
            if beep_counter(19) = '1' then
               phase_accumulator <= phase_accumulator + upper_side_signal; --+75kHz signal               
               phase_accumulator <= phase_accumulator + lower_side_signal; -- -75kHz signal
            end if;
            phase_accumulator <= phase_accumulator + center_signal; -- center frequency signal
         end if;

         beep_counter <= beep_counter+1;
      end if;
   end process;
end Behavioral;


NET "clk" LOC = C10;
NET "antenna" LOC = F15;
NET "rst" LOC = V4;
NET "sw[0]" LOC = B3 | PULLDOWN;
NET "sw[1]" LOC = A3 | PULLDOWN;
NET "sw[2]" LOC = B4 | PULLDOWN;
NET "sw[3]" LOC = A4 | PULLDOWN;

nRF24L01+ Module Documentation



Arduino Library Download
Copy to ‘Libraries’ folder within your main Arduino folder.


MISO -> 12
MOSI -> 11
SCK -> 13


CE -> 8
CSN -> 7


byte cePin
CE Pin controls RX / TX, default 8.

byte csnPin
CSN Pin (Chip select not), default 7.

byte channel
RF Channel 0 – 127 or 0 – 84 in the US, default 0.

byte payload
Size in bytes, default 16, max 32.
Note: channel and payload must be the same for all nodes.


void init(void)
Initialize the module, set the pin modes for the configurable pins and initialize the SPI module.
Mirf.csnPin = 9;
Mirf.cePin = 7;
void setRADDR(byte *addr)
Set the receiving address. Addresses are 5 bytes long.
Mirf.setRADDR((byte *)"addr1");
void setTADDR(byte *addr)
Set the sending address.
Mirf.setTADDR((byte *)"addr1");
void config(void)
Set channel and payload width. Power up in RX mode and flush RX fifo.
Mirf.payload = 32; = 2;
bool dataReady(void)
Is there data ready to be received?.
//Get the data to play with.
void getData(byte *data)
Get the received data. 'data' should be an array of bytes Mirf.payload long.
byte data[Mirf.payload]
void send(byte *data)
Send data. 'data' should be Mirf.payload bytes long.
bool isSending(void)
Return true if still trying to send. If the chip is still in transmit mode then this method will return the chip to receive mode.
//Chip is now in receive mode.
NB: Lots more information is available from the status registers regarding acknowledgement or failure status. See Mirf.cpp:218.
bool rxFifoEmpty(void)
Is the RX Fifo Empty.
bool txFifoEmpty(void)
Is the TX Fifo Empty.
byte getStatus(void)
Return the status register.
void powerUpRx(void)
Power up chip and set to receive mode. Also clear sending interrupts.
void powerUpTx(void)
Power up tx mode.


See examples folder in zip file.
Arduino Library Download

FM RF Data Link


In preparation for testing for my technicians class radio license, I’ve been designing a data modem for my avr chips. The current plan is to use a tuned FM transmitter, driven by an op-amp square wave generator, modulated by a microcontroller. This is an improvement (in both power and computational resources) over the original idea of summing sinewaves on the Tx side and Fast Fournier Transform on the Rx side. Seems pretty wasteful, now doesnt it?

Blog at

%d bloggers like this: