FPGAheli

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was completed in March 2012.

Contents

Summary

I 'gifted' myself a cheap Infrared toy helicopter for Christmas, with the intention of hacking the protocol. I purchased a Syma S107 based on the details of the protocol published in this post http://www.rcgroups.com/forums/showthread.php?t=1417249&page=3, but have only just finished playing with it enough that I wanted to start hacking with it.

I've successfully built an FPGA interface for it. A video of it configured on a Digilent Basys 2 development board http://youtu.be/89HvwYWg838

Capturing the commands

For most Infrared projects codes can be captured using a IR receiver module (see http://www.ladyada.net/learn/sensors/ir.html) for a really good write-up.

I tried this, but the only IR sensor I had didn't give any useful signal - it would pick up my TV remote, but not the helicopter control - either the pulses were too short or too long for the receiver or the IR signal's frequency was outside of the receiver's bandpass filter.

So I opened the transmitter and inspected it. The output is a chain of three LR LEDs, a resister and an NPN surface mount transistor driven by an unlabelled 14 pin DIP IC. The negative/ground trace on the PCB is to the centre pin on the left (pin 11) and the transistor is driven by the top right (pin 8).

After a quick check of voltage levels I hooked up my cheap and cheerful Dangerous Prototypes Logic Analyzer (http://dangerousprototypes.com/category/logic-analyzer/), and captured a few frames. Here's the setup:

Heli-sample.png

Captured and decoded frames

Heli-frames.png

S = Start symbol, E = End Symbol, s = short pulse
S 001110110     0111111 00011010 00110001 E
S 001110110 s10 0111111 00111111 00110001 E 
S 001111110     0111111 00000000 00110001 E
S 001110110     0111111 00011010 00110001 E
S 001110110 s10 0111111 00111111 00110001 E
S 001111110     0111111 00000000 00110001 E

Unlike the post on RC Groups, this transmitter has two different frames - one in three has three extra bits in it (well, one short bit and two data bits). I have no idea what they do, but suspect they may be for a different model with more features.

Close-up of a pulse

Heli-pulse.png

As you can see in the close-up of a pulse, the 38.5KHz carrier and the stream of symbols being transmitted is not synchronised - giving 'short' bits at the front and end of a pulse, making any observed timings open to error unless both the first and last pulse are 'clipped'.

Measured Timings

By sampling the signal at 10MHz I was able to get the following specifications for the signals.

Attribute Timing Notes
Frame Frequency
Carrier High 12.9us 645 counts @ 50MHz
Carrier Length 25.8us 1290 counts @ 50Mhz
Frame TX interval 118,764us
Start symbol High 1965us
Start symbol Total 3935us
One symbol high 305.5us
One symbol length 993.3us 6 cycles took 5.959ms
Zero symbol High 296.1us Average of four
Zero symbol length 593.9us 3 cycles took 1.785ms


These differ a bit from that in the RC Groups forum post, but make a lot more sense with the symbol for '1' being 1.5x that of an '0' . I guess that their number were obtained by using an IR Receiver method. Mine are better :-)

Implementing in VHDL

This was quite easy - but took a few iterations to get something that I was happy with. I converted everything to timinig based on integer multiples of twice the carrier frequency, and ran everything on a 50MHz clock (the onboard clock of a Basys2).

helicopter.vhd

-- FPGA Interface for Syma S107 helicopter
--
-- Author: Mike Field <hamster@snap.net.nz>
--
-- Based on capture at 10MHz
-- =========================
-- 
-- CarrierHigh     12.9us      645  @ 50MHz
-- CarrierLength   25.8us     1290 @ 50Mhz
--
--             (in 12.9us half-cycles)
-- Frame Length   118.764ms  9206.5
-- 
-- 
-- Start bit High  1965us      152
-- Start bit Total 3935us      304
-- 
-- One bit high    305.5us      23
-- One Bit length  993.3us      77  6 cycles is 5.959ms 
-- 
-- Zero bit High   296.1us      23  Average of four
-- Zero bit length   593.9us      46  3 cycles is 1.785ms
-- 
--   --Left--     --Fwd--  -Throt-  -Trim-
-- S 010011100 000 0000001 01111110 01001010 E  High Fwd
--
-- Sample Frames
-- ==============
-- S 001110110     0111111 00011010 00110001 E
-- S 001110110 S10 0111111 00111111 00110001 E 
-- S 001111110     0111111 00000000 00110001 E
-- S 001110110     0111111 00011010 00110001 E
-- S 001110110 S10 0111111 00111111 00110001 E
-- S 001111110     0111111 00000000 00110001 E
--

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity helicopter is
    Port ( clk_50m  : in  STD_LOGIC;
           led_out  : out STD_LOGIC;        
           channel  : in  STD_LOGIC;
           yaw        : in  STD_LOGIC_VECTOR(6 downto 0) := (others => '0');
           pitch     : in  STD_LOGIC_VECTOR(6 downto 0);
           throttle : in  STD_LOGIC_VECTOR(6 downto 0);
           adjust   : in  STD_LOGIC_VECTOR(6 downto 0)
           );
end helicopter;

architecture Behavioral of helicopter is
   -- The frame is assembled into this signal
   signal frame              : STD_LOGIC_VECTOR(31 downto 0);
   -- The current frame being sent - acts as a shift register, loaded at start of frame
   signal frame_to_send      : STD_LOGIC_VECTOR(31 downto 0) := (others => '0');
   
   -- Counters for the various signal times
   signal carierCounter : STD_LOGIC_VECTOR(10 downto 0) := (others => '0');
   signal frameCounter  : STD_LOGIC_VECTOR(13 downto 0) := "10001000000000";
   signal perBitCounter : STD_LOGIC_VECTOR( 6 downto 0) := (others => '0');
   signal bitCounter    : STD_LOGIC_VECTOR( 5 downto 0) := "111111";
   
   -- current state of the carrier
   signal carrier : std_logic := '0';
   
   -- Carrier frequency is 50,000,000 / 1314 = 38kHz, 50% duty cycle
   constant pulseLengthCarrier : natural := 645;

   -- Length of the zero and one tokens are different!
   constant pulseLengthZero : STD_LOGIC_VECTOR( 6 downto 0) := "0101110"; -- 46
   constant pulseLengthOne  : STD_LOGIC_VECTOR( 6 downto 0) := "1001101"; -- 77
   constant pulseLengthHigh : STD_LOGIC_VECTOR( 6 downto 0) := "0010111"; -- 23

   -- Length of the 'on' time for the Zero and One tokens.
   
   -- Details of when the frame is resent, and what the start token looks like
   constant frameLength      : natural := 9206;
   constant frameStartHigh   : natural := 152; 
   constant frameStartLength : natural := 304; 

   -- helper signals
   signal pulseLength   : STD_LOGIC_VECTOR( 6 downto 0);
   
begin
   frame         <= '0' & yaw & '0' & pitch & channel & throttle & '0' & adjust;
   pulseLength   <= pulseLengthZero  when frame_to_send(31) = '0' else pulseLengthOne;

   process(clk_50m)
   begin 
      if rising_edge(clk_50m) then
         led_out  <= '0';

         if bitCounter < 32 and perBitCounter < pulseLengthHigh then
            led_out  <= carrier;
         end if;

         -- The start pulse (but we look at it as the end of the frame)
         if frameCounter >= frameLength-frameStartLength and frameCounter < frameLength-frameStartLength+frameStartHigh then
            led_out  <= carrier;
         end if;

         -- Is this a rising edge of the carrier?
         if carierCounter = pulseLengthCarrier-1 then
            carrier       <= not carrier;
            carierCounter <= (others => '0');
            -- Time to send a new bit?
            if perBitCounter = pulseLength-1 then
               -- special note - we send the first '1' that gets appended to the frame_to_send (we send 33 bits!)
               frame_to_send <= frame_to_send(30 downto 0) & '1';
               perBitCounter <= (others => '0');
               if bitcounter < 33 then
                 bitcounter <= bitcounter + 1;
               end if;
            else
               perBitCounter <= perBitCounter + 1;
            end if;

            -- Time to send a new frame?
            if frameCounter = frameLength-1 then
               frameCounter  <= (others => '0');
               bitcounter    <= (others => '0');
               perbitcounter <= (others => '0');
               frame_to_send <= frame;
            else 
               frameCounter <= frameCounter + 1;
            end if;         
         else
            carierCounter <= carierCounter + 1;
         end if;
      end if;
   end process;
end Behavioral;

helicopter_top.vhd

Here's the top-level VHDL module I used for testing. It will be pretty easy to hook push buttons up to the yaw and pitch if you desired... or maybe interface to a UART.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity helicopter_top is
    Port ( clk_50m  : in  STD_LOGIC;
           led_out  : out STD_LOGIC;
           switches : in  STD_LOGIC_VECTOR(6 downto 0));
end helicopter_top;

architecture Behavioral of helicopter_top is
   COMPONENT helicopter
   PORT(
      clk_50m : IN std_logic;
      channel : IN std_logic;
      yaw : IN std_logic_vector(6 downto 0);
      pitch : IN std_logic_vector(6 downto 0);
      throttle : IN std_logic_vector(6 downto 0);
      adjust : IN std_logic_vector(6 downto 0);          
      led_out : OUT std_logic
      );
   END COMPONENT;
begin
   Inst_helicopter: helicopter PORT MAP(
      clk_50m  => clk_50m,
      led_out  => led_out,
      channel  => '0',
      yaw      => "0111111",
      pitch    => "0111111",
      throttle => switches,
      adjust   => "0111111"
   );
end Behavioral;

The electronics for the IR output

For output I connected an IR LED to an I/O pin on the FPGA, with two 330 Ohm resistors in parallel. The IR LED used is not very bright and has a very directional beam on it, so the heli quickly moves out of reception. If you want to do this for a real project I recommend using multiple bright, wide angle LEDs with a NPN transistor switching them.

Future work

At the moment I've only hooked the throttle up to the switches. I should also hook the push-buttons to the left/right front/back. Then I could actually fly the thing!

For maximum reliability I would also like to play with the timings. This should be using a 38kHz carrier, but isn't. Does the helicopter have a very wide filter on its IR sensor so a precision clock isn't needed in the transmitter (keeping price down? Is my transmitter 2% or 3% our of spec? By playing with the constants could reception and range be improved?

What do the extra two bits do? They don't seem to be needed? Maybe they are for other models to control lights, guns or cameras?

Personal tools