Cheap Analogue Input

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was completed in September 2014.

One area lacking in most FPGAs is the ability to read analogue signals. However, here is a way to interface a potentiometer using just one pin and three passive components. This was inspired by the analogue joystick/paddle interface on the early Apple computers - one of the many tricks The Woz used to give it a groundbreaking feature set.

Apple schematic.jpg

It is a simply system.

Cheap adc schematic.jpg

I'm using a wiring harness salvaged a long time ago out of a four-axis joystick, and is used along with a breadboard wing.

Here is the design with the pot at min setting

Cheap adc fpga 1.jpg

... and here at max setting

Cheap adc fpga 2.jpg

For this project the FPGA pin is used as bidirectional pin. To complete an acquisition the pin is set to active low. Any charge in the capacitor flows into the FPGA via the 180 ohm resistor until it reaches the 'low' level. Then the output is set to high-Z mode, and the capacitor charges from the 3.3 V rail, via the 1.8k resistor and the 100k potentiometer, until it reaches 3.3 V. While the is Capacitor charges the FPGA counts how many clock cycles it takes for the capacitor to charge, and remembers when the input becomes 'true'. This value then gets output via the seven segment display.

One of the many downsides with this method is that the values will differ quite widely with the tolerance of the components, especially the capacitor. With my parts it goes from 0x0218 to about 0x93xx. The best way to deal with this is to keep track of the maximum and minimum values, and then scale reading the appropriately. However, if you want a simple inexpensive way to add an analogue input to a project for a few cents then this is the way to go - it is perfect for a game of Pong!

The values were selected from my junk box, and pretty much any values can be made to work by using a longer or shorter counter.

Contents

Source Code

cheap_analogue.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- Module Name:    cheap_analogue - Behavioral 
--
-- Description: A very cheap way to add an analog input
--
--    3V3 --- 10K Pot -- 1.8K ---+--- 5.6n --- Gnd
--                               |
--                              180 Ohm
--                               |
--                               |
--                            FPGA Pin
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity cheap_analogue is
    Port ( clk: in  STD_LOGIC;
           cheap_analog_input : inout STD_LOGIC;
           value           : out  STD_LOGIC_VECTOR (15 downto 0));
end cheap_analogue;

architecture Behavioral of cheap_analogue is
   signal count : unsigned(17 downto 0);
   signal this_value : unsigned(15 downto 0);
begin


process(clk)
   begin
      if rising_edge(clk) then

         if count = 0 then
            value <= std_logic_vector(this_value);
         end if;

         if count(count'high) = '1' then
            -----------------------------------------------------
            -- Drive the output low to discharge the capacitor
            -----------------------------------------------------
            cheap_analog_input <= '0';            
         else
            ------------------------------------------------------------------
            -- If it is charged then capture the value
            ------------------------------------------------------------------
            if cheap_analog_input = '0' then
               this_value <= count(count'high-1 downto count'high-16);
             end if;

            ------------------------------------------------------------------
            -- While in low Z the capacitor charges, through the potentiometer
            ------------------------------------------------------------------
            cheap_analog_input <= 'Z';
            
         end if;
         count <= count + 1;
      end if;
   end process;
end Behavioral;

cheap_analogue_test.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Module Name: cheap_analogue - Behavioral 
-- Description: A test project for my cheap analogue input
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity cheap_analogue_test is
    Port ( clk32 : in  STD_LOGIC;
           seg_dp : out  STD_LOGIC;
           seg_anodes : out  STD_LOGIC_VECTOR (3 downto 0);
           seg_segments : out  STD_LOGIC_VECTOR (6 downto 0);
           cheap_analog_input : inout  STD_LOGIC);
end cheap_analogue_test;

architecture Behavioral of cheap_analogue_test is
   COMPONENT display
   PORT(
      clk          : IN std_logic;
      digit3       : IN std_logic_vector(3 downto 0);
      digit2       : IN std_logic_vector(3 downto 0);
      digit1       : IN std_logic_vector(3 downto 0);
      digit0       : IN std_logic_vector(3 downto 0);
      decimals     : IN std_logic_vector(3 downto 0);
      flash        : IN std_logic;          
      seg_dp       : OUT std_logic;
      seg_anodes   : OUT std_logic_vector(3 downto 0);
      seg_segments : OUT std_logic_vector(6 downto 0)
      );
   END COMPONENT;

   COMPONENT cheap_analogue
   PORT(
      clk                : IN std_logic;    
      cheap_analog_input : INOUT std_logic;      
      value              : OUT std_logic_vector(15 downto 0)
      );
   END COMPONENT;

   signal value : std_logic_vector(15 downto 0);

begin

   Inst_cheap_analogue: cheap_analogue PORT MAP(
      clk                => clk32,
      cheap_analog_input => cheap_analog_input,
      value              => value
   );

   Inst_display: display PORT MAP(
      clk         => clk32,
      digit3      => value(15 downto 12),
      digit2      => value(11 downto 8),
      digit1      => value( 7 downto 4),
      digit0      => value( 3 downto 0),
      decimals    => "0000",
      flash       => '0',
      seg_dp      => seg_dp,
      seg_anodes  => seg_anodes,
      seg_segments => seg_segments
   );
end Behavioral;

display.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Module Name:    display - Behavioral 
--
-- Description: Display digits on a multiplexed seven-segment display
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity display is
    Port ( clk : in  STD_LOGIC;
           digit3       : in  std_logic_vector(3 downto 0);
           digit2       : in std_logic_vector(3 downto 0);
           digit1       : in  std_logic_vector(3 downto 0);
           digit0       : in  std_logic_vector(3 downto 0);
           decimals     : in  STD_LOGIC_VECTOR (3 downto 0);
           flash        : in  STD_LOGIC;
           seg_dp       : out STD_LOGIC;
           seg_anodes   : out STD_LOGIC_VECTOR (3 downto 0);
           seg_segments : out STD_LOGIC_VECTOR (6 downto 0));
end display;

architecture Behavioral of display is
   signal counter : unsigned(23 downto 0) := (others => '0'); 
   signal seg0 : std_logic_vector(6 downto 0);
   signal seg1 : std_logic_vector(6 downto 0);
   signal seg2 : std_logic_vector(6 downto 0);
   signal seg3 : std_logic_vector(6 downto 0);
begin
   with digit0 select
      seg0 <= "1111001" when "0001",   --1
              "0100100" when "0010",   --2
              "0110000" when "0011",   --3
              "0011001" when "0100",   --4
              "0010010" when "0101",   --5
              "0000010" when "0110",   --6
              "1111000" when "0111",   --7
              "0000000" when "1000",   --8
              "0010000" when "1001",   --9
              "0001000" when "1010",   --A
              "0000011" when "1011",   --b
              "1000110" when "1100",   --C
              "0100001" when "1101",   --d
              "0000110" when "1110",   --E
              "0001110" when "1111",   --F
              "1000000" when others;   --0
 
   with digit1 select
      seg1 <= "1111001" when "0001",   --1
              "0100100" when "0010",   --2
              "0110000" when "0011",   --3
              "0011001" when "0100",   --4
              "0010010" when "0101",   --5
              "0000010" when "0110",   --6
              "1111000" when "0111",   --7
              "0000000" when "1000",   --8
              "0010000" when "1001",   --9
              "0001000" when "1010",   --A
              "0000011" when "1011",   --b
              "1000110" when "1100",   --C
              "0100001" when "1101",   --d
              "0000110" when "1110",   --E
              "0001110" when "1111",   --F
              "1000000" when others;   --0

   with digit2 select
      seg2 <= "1111001" when "0001",   --1
              "0100100" when "0010",   --2
              "0110000" when "0011",   --3
              "0011001" when "0100",   --4
              "0010010" when "0101",   --5
              "0000010" when "0110",   --6
              "1111000" when "0111",   --7
              "0000000" when "1000",   --8
              "0010000" when "1001",   --9
              "0001000" when "1010",   --A
              "0000011" when "1011",   --b
              "1000110" when "1100",   --C
              "0100001" when "1101",   --d
              "0000110" when "1110",   --E
              "0001110" when "1111",   --F
              "1000000" when others;   --0

    with digit3 select
      seg3 <= "1111001" when "0001",   --1
              "0100100" when "0010",   --2
              "0110000" when "0011",   --3
              "0011001" when "0100",   --4
              "0010010" when "0101",   --5
              "0000010" when "0110",   --6
              "1111000" when "0111",   --7
              "0000000" when "1000",   --8
              "0010000" when "1001",   --9
              "0001000" when "1010",   --A
              "0000011" when "1011",   --b
              "1000110" when "1100",   --C
              "0100001" when "1101",   --d
              "0000110" when "1110",   --E
              "0001110" when "1111",   --F
              "1000000" when others;   --0 -displayed as a blank

process(clk)
   begin
      if rising_edge(clk) then
         case counter(18 downto 15) is
            when "0000" => seg_anodes   <= "1110"; seg_segments <= seg0; seg_dp <= not decimals(0);
            when "0001" => seg_anodes   <= "1101"; seg_segments <= seg1; seg_dp <= not decimals(1); 
            when "0010" => seg_anodes   <= "1011"; seg_segments <= seg2; seg_dp <= not decimals(2);
            when "0011" => seg_anodes   <= "0111"; seg_segments <= seg3; seg_dp <= not decimals(3);
            when others => seg_anodes   <= "1111";
         end case;
         
         if flash = '1' and counter(23) = '1' then
            seg_anodes   <= "1111";
         end if;
         counter <= counter+1;
      end if;
   end process;
end Behavioral;

papilio.ucf

#----------------------------------------------------------------------------------
#-- Engineer: Mike Field <hamster@snap.net.nz>
#-- 
#--
#-- UCF file for the Papilio One FPGA board
#--
#----------------------------------------------------------------------------------

NET CLK32               LOC="P89" | IOSTANDARD=LVTTL | PERIOD=31.25ns;               

NET "cheap_analog_input" LOC="P91" | IOSTANDARD=LVTTL;  # C0

NET "seg_segments<0>" LOC="P61" | IOSTANDARD=LVTTL; # B7
NET "seg_segments<1>" LOC="P41" | IOSTANDARD=LVTTL; # B10
NET "seg_segments<2>" LOC="P66" | IOSTANDARD=LVTTL; # B5
NET "seg_segments<3>" LOC="P63" | IOSTANDARD=LVTTL; # B6
NET "seg_segments<4>" LOC="P71" | IOSTANDARD=LVTTL; # B3
NET "seg_segments<5>" LOC="P68" | IOSTANDARD=LVTTL; # B4
NET "seg_segments<6>" LOC="P54" | IOSTANDARD=LVTTL; # B9   
NET "seg_dp"          LOC="P83" | IOSTANDARD=LVTTL; # B1

NET "seg_anodes<0>"   LOC="P85" | IOSTANDARD=LVTTL;  # B0
NET "seg_anodes<1>"   LOC="P78" | IOSTANDARD=LVTTL;  # B2
NET "seg_anodes<2>"   LOC="P58" | IOSTANDARD=LVTTL;  # B8
NET "seg_anodes<3>"   LOC="P36" | IOSTANDARD=LVTTL;  # B11

Personal tools