PMODamp3

From Hamsterworks Wiki!

Revision as of 09:46, 4 May 2016 by User (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

This FPGA Project was completed in Jan 2016.

The Digilent PMODamp3 is a small power amplifier module, which accepts an I2S bistream direction. It uses bridged amplifiers, so even from a 3.3 V power supply the output is a couple of Watts.

This project just generates the appropriate clocks, and outputs a low-volume sine wave. It is based on a Verilog design by Julian Loiacono - you can find his project at https://github.com/jcloiacon/synth.

The waveform is relatively quiet - it is 375 Hz sine wave at about -60 db when the +12db/0db jumper is in installed.

Pmod amp3.jpg

Pmod amp3 jumpers.jpg

NOTE: Because of the bridged amplifiers, the sleeve of the 3.5mm socket is not connected to ground. Don't try to use a mono -> stereo adapter - it won't work!

Pmod amp3 schematic.jpg

Contents

Clocking

This project only has one real clock domain - that of the I2S master clock.

It is generated s 100Mhz * 7 / 57 = 12,280,701 Hz, and as the sample clock is 1/256th of this, the sample rate is 47,971 Hz - close enough to 48,000 Hz for me.

Source files

pmod_amp3_test.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz> 
-- 
-- Module Name: pmod_amp3_test - Behavioral
--
-- Description: Send a sine wave out over I2S to the PMODamp3
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

Library UNISIM;
use UNISIM.VComponents.all;

entity pmod_amp3_test is
    Port ( clk100         : in  STD_LOGIC;
           pmod_i2s_sd    : out STD_LOGIC := '0';
           pmod_i2s_mclk  : out STD_LOGIC;
           pmod_i2s_bclk  : out STD_LOGIC;
           pmod_i2s_lrclk : out STD_LOGIC;
           pmod_i2s_sdat  : out STD_LOGIC);
end pmod_amp3_test;

architecture Behavioral of pmod_amp3_test is
    component i2s_clock_generator is
    Port ( clk100    : in  STD_LOGIC;
           i2s_mclk  : out STD_LOGIC;
           i2s_bclk  : out STD_LOGIC;
           i2s_lrclk : out STD_LOGIC);
    end component;

    component osc_sine is
    Port ( mclk   : in STD_LOGIC;
           lrclk  : in STD_LOGIC;
           sample : out STD_LOGIC_VECTOR (15 downto 0));
    end component;

    component powerup_controller is
    Port ( mclk         : in  STD_LOGIC;
           powerup      : out STD_LOGIC);
    end component;

    component i2s_transmitter is
    Port ( mclk         : in  STD_LOGIC;
           bclk         : in  STD_LOGIC;
           lrclk        : in  STD_LOGIC;
           sample_left  : in  STD_LOGIC_VECTOR (15 downto 0);
           sample_right : in  STD_LOGIC_VECTOR (15 downto 0);
           sdat         : out STD_LOGIC);
    end component;

    signal i2s_mclk    : STD_LOGIC;
    signal i2s_bclk    : STD_LOGIC;
    signal i2s_lrclk   : STD_LOGIC;
    signal i2s_sdat    : STD_LOGIC;
    signal i2s_powerup : STD_LOGIC;
    signal sample      : STD_LOGIC_VECTOR(15 downto 0);
begin

    ------------------------------------------
    -- Generate the different clocks. Only
    -- i2s_mclk should be used as a clock!
    ------------------------------------------
generate_clock: i2s_clock_generator PORT MAP (
    clk100 => clk100,
    i2s_mclk  => i2s_mclk,
    i2s_bclk  => i2s_bclk,
    i2s_lrclk => i2s_lrclk);

    ------------------------------------------
    -- Generate a quiet sine wave from a table
    ------------------------------------------
i_osc_sine : osc_sine Port map ( 
    mclk   => i2s_mclk,
    lrclk  => i2s_lrclk,
    sample => sample);

    ------------------------------------------
    -- Convert the samples into an I2S bitstream
    ------------------------------------------
i_i2s_transmitter: i2s_transmitter port map (
    mclk         => i2s_mclk,
    bclk         => i2s_bclk,
    lrclk        => i2s_lrclk,
    sample_left  => sample,
    sample_right => sample,
    sdat         => i2s_sdat);

i_powerup_controller: powerup_controller port map (
    mclk    => i2s_mclk,
    powerup => i2s_powerup);

    -------------------------------------------------------------
    -- Send it to the PMOD's interface 
    -------------------------------------------------------------
    -- Use a DDR output register to send out the I2S master clock
mclk_ODDR : ODDR generic map(
      DDR_CLK_EDGE => "OPPOSITE_EDGE", -- "OPPOSITE_EDGE" or "SAME_EDGE" 
      INIT => '0',                     -- Initial value for Q port ('1' or '0')
      SRTYPE => "SYNC")                -- Reset Type ("ASYNC" or "SYNC")
   port map (
      Q  => pmod_i2s_mclk, -- 1-bit DDR output
      C  => i2s_mclk,      -- 1-bit clock input
      CE => '1',           -- 1-bit clock enable input
      D1 => '1',           -- 1-bit data input (positive edge)
      D2 => '0',           -- 1-bit data input (negative edge)
      R  => '0',           -- 1-bit reset input
      S  => '0'            -- 1-bit set input
   );    

    pmod_i2s_sd    <= i2s_powerup;   -- Active low shutdown signal 
    pmod_i2s_bclk  <= i2s_bclk;
    pmod_i2s_lrclk <= i2s_lrclk;
    pmod_i2s_sdat  <= i2s_sdat;
end Behavioral;

i2s_clock_generator.vhd

----------------------------------------------------------------------------------
-- Module Name: i2s_clock_generator - Behavioral
--
-- Description: Generate the I2S clk from the 100MHz clock.
--              I was aiming for a 48.000Hz sample rate.
--              This is about 0.05% out
--
--              100,000,000 * 7 / 57 = 12,280,702 Hz (mclk)
--                              /  8 =  1,535,088 Hz (bclk)
--                              / 32 =     47,972 Hz (lrclk)
-- 
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

library UNISIM;
use UNISIM.VComponents.all;

entity i2s_clock_generator is
    Port ( clk100 : in STD_LOGIC;
           i2s_mclk : out STD_LOGIC;
           i2s_bclk : out STD_LOGIC;
           i2s_lrclk : out STD_LOGIC);
end i2s_clock_generator;

architecture Behavioral of i2s_clock_generator is
    signal counter           : unsigned(7 downto 0) := (others => '0');
    signal clkfb             : std_logic;
    signal i2s_mclk_internal : std_logic;
begin

MMCM : MMCME2_BASE
   generic map (
      BANDWIDTH => "OPTIMIZED",  -- Jitter programming (OPTIMIZED, HIGH, LOW)
      CLKFBOUT_MULT_F => 7.0,    -- Multiply value for all CLKOUT (2.000-64.000).
      CLKFBOUT_PHASE => 0.0,     -- Phase offset in degrees of CLKFB (-360.000-360.000).
      CLKIN1_PERIOD => 10.0,      -- Input clock period in ns to ps resolution (i.e. 33.333 is 30 MHz).

      -- CLKOUT0_DIVIDE - CLKOUT6_DIVIDE: Divide amount for each CLKOUT (1-128)
      CLKOUT0_DIVIDE_F => 57.0,   -- Divide amount for CLKOUT0 (1.000-128.000).
      CLKOUT1_DIVIDE => 57,
      CLKOUT2_DIVIDE => 1,
      CLKOUT3_DIVIDE => 1,
      CLKOUT4_DIVIDE => 1,
      CLKOUT5_DIVIDE => 1,
      CLKOUT6_DIVIDE => 1,
      -- CLKOUT0_DUTY_CYCLE - CLKOUT6_DUTY_CYCLE: Duty cycle for each CLKOUT (0.01-0.99).
      CLKOUT0_DUTY_CYCLE => 0.5,
      CLKOUT1_DUTY_CYCLE => 0.5,
      CLKOUT2_DUTY_CYCLE => 0.5,
      CLKOUT3_DUTY_CYCLE => 0.5,
      CLKOUT4_DUTY_CYCLE => 0.5,
      CLKOUT5_DUTY_CYCLE => 0.5,
      CLKOUT6_DUTY_CYCLE => 0.5,
      -- CLKOUT0_PHASE - CLKOUT6_PHASE: Phase offset for each CLKOUT (-360.000-360.000).
      CLKOUT0_PHASE => 0.0,
      CLKOUT1_PHASE => 0.0,
      CLKOUT2_PHASE => 0.0,
      CLKOUT3_PHASE => 0.0,
      CLKOUT4_PHASE => 0.0,
      CLKOUT5_PHASE => 0.0,
      CLKOUT6_PHASE => 0.0,
      CLKOUT4_CASCADE => FALSE,  -- Cascade CLKOUT4 counter with CLKOUT6 (FALSE, TRUE)
      DIVCLK_DIVIDE => 1,        -- Master division value (1-106)
      REF_JITTER1 => 0.0,        -- Reference input jitter in UI (0.000-0.999).
      STARTUP_WAIT => FALSE      -- Delays DONE until MMCM is locked (FALSE, TRUE)
   )
   port map (
      -- Control Ports: 1-bit (each) input: MMCM control ports
      PWRDWN    => '0',       -- 1-bit input: Power-down
      RST       => '0',             -- 1-bit input: Reset
      -- Status Ports: 1-bit (each) output: MMCM status ports
      LOCKED    => open,       -- 1-bit output: LOCK

      -- Clock Inputs: 1-bit (each) input: Clock input
      CLKIN1    => clk100,       -- 1-bit input: Clock

      -- Clock Outputs: 1-bit (each) output: User configurable clock outputs
      CLKOUT0   => i2s_mclk_internal, -- 1-bit output: CLKOUT0
      CLKOUT0B  => open,     -- 1-bit output: Inverted CLKOUT0
      CLKOUT1   => open,     -- 1-bit output: CLKOUT1
      CLKOUT1B  => open,     -- 1-bit output: Inverted CLKOUT1
      CLKOUT2   => open,     -- 1-bit output: CLKOUT2
      CLKOUT2B  => open,     -- 1-bit output: Inverted CLKOUT2
      CLKOUT3   => open,     -- 1-bit output: CLKOUT3
      CLKOUT3B  => open,     -- 1-bit output: Inverted CLKOUT3
      CLKOUT4   => open,     -- 1-bit output: CLKOUT4
      CLKOUT5   => open,     -- 1-bit output: CLKOUT5
      CLKOUT6   => open,     -- 1-bit output: CLKOUT6
      -- Feedback Clocks: 1-bit (each) output: Clock feedback ports
      CLKFBOUT  => clkfb,    -- 1-bit output: Feedback clock
      CLKFBOUTB => open,     -- 1-bit output: Inverted CLKFBOUT
      -- Feedback Clocks: 1-bit (each) input: Clock feedback ports
      CLKFBIN   => clkfb     -- 1-bit input: Feedback clock
   );

   ---------------------------------------------------------------
   -- Use a counter to generate the bit clock and left/right clock
   ---------------------------------------------------------------
counter_process: process(i2s_mclk_internal)
    begin
        if rising_edge(i2s_mclk_internal) then
            counter   <= counter + 1;
        end if;
    end process;

    ----------------------------------------------------------
    -- The other i2s clocks are derived from the mclk. They 
    -- are not used as proper clocks in the FPGA (i.e. they
    -- will not be used to directly clock logic in the design)
    ----------------------------------------------------------
    i2s_mclk  <= i2s_mclk_internal;
    i2s_bclk  <= std_logic(counter(2));  -- mclk / 8
    i2s_lrclk <= std_logic(counter(7));  -- mclk / 256
end Behavioral;

osc_sine.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz> 
-- 
-- Module Name: osc_sine - Behavioral
--
-- Description: Generate an low-volume sine wave, at around 400 Hz
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity osc_sine is
    Port ( mclk   : in STD_LOGIC;
           lrclk  : in STD_LOGIC;
           sample : out STD_LOGIC_VECTOR (15 downto 0));
end osc_sine;

architecture Behavioral of osc_sine is
    signal index : unsigned(7 downto 0) := (others => '0');
    ---------------------------------------------------------------------------
    -- A 128 entry sine wave - at 48kHz it will be a 48,000 / 128 = 375 Hz tone
    ---------------------------------------------------------------------------
    type a_lookup_table is array(0 to 127) of signed(15 downto 0);
    signal lookup_table : a_lookup_table := (
        to_signed(  0,16), to_signed(  1,16), to_signed(  3,16), to_signed(  4,16), 
        to_signed(  6,16), to_signed(  7,16), to_signed(  9,16), to_signed( 10,16),
        to_signed( 12,16), to_signed( 13,16), to_signed( 15,16), to_signed( 16,16),
        to_signed( 17,16), to_signed( 19,16), to_signed( 20,16), to_signed( 21,16),
        to_signed( 22,16), to_signed( 23,16), to_signed( 24,16), to_signed( 25,16),
        to_signed( 26,16), to_signed( 27,16), to_signed( 28,16), to_signed( 28,16),
        to_signed( 29,16), to_signed( 30,16), to_signed( 30,16), to_signed( 30,16),
        to_signed( 31,16), to_signed( 31,16), to_signed( 31,16), to_signed( 31,16),
        to_signed( 31,16), to_signed( 31,16), to_signed( 31,16), to_signed( 31,16),
        to_signed( 31,16), to_signed( 30,16), to_signed( 30,16), to_signed( 30,16),
        to_signed( 29,16), to_signed( 28,16), to_signed( 28,16), to_signed( 27,16),
        to_signed( 26,16), to_signed( 25,16), to_signed( 24,16), to_signed( 23,16),
        to_signed( 22,16), to_signed( 21,16), to_signed( 20,16), to_signed( 19,16),
        to_signed( 17,16), to_signed( 16,16), to_signed( 15,16), to_signed( 13,16),
        to_signed( 12,16), to_signed( 10,16), to_signed(  9,16), to_signed(  7,16),
        to_signed(  6,16), to_signed(  4,16), to_signed(  3,16), to_signed(  1,16),
        to_signed(  0,16), to_signed( -2,16), to_signed( -4,16), to_signed( -5,16),
        to_signed( -7,16), to_signed( -8,16), to_signed(-10,16), to_signed(-11,16),
        to_signed(-13,16), to_signed(-14,16), to_signed(-16,16), to_signed(-17,16),
        to_signed(-18,16), to_signed(-20,16), to_signed(-21,16), to_signed(-22,16),
        to_signed(-23,16), to_signed(-24,16), to_signed(-25,16), to_signed(-26,16),
        to_signed(-27,16), to_signed(-28,16), to_signed(-29,16), to_signed(-29,16),
        to_signed(-30,16), to_signed(-31,16), to_signed(-31,16), to_signed(-31,16),
        to_signed(-32,16), to_signed(-32,16), to_signed(-32,16), to_signed(-32,16),
        to_signed(-32,16), to_signed(-32,16), to_signed(-32,16), to_signed(-32,16),
        to_signed(-32,16), to_signed(-31,16), to_signed(-31,16), to_signed(-31,16),
        to_signed(-30,16), to_signed(-29,16), to_signed(-29,16), to_signed(-28,16),
        to_signed(-27,16), to_signed(-26,16), to_signed(-25,16), to_signed(-24,16),
        to_signed(-23,16), to_signed(-22,16), to_signed(-21,16), to_signed(-20,16),
        to_signed(-18,16), to_signed(-17,16), to_signed(-16,16), to_signed(-14,16),
        to_signed(-13,16), to_signed(-11,16), to_signed(-10,16), to_signed( -8,16),
        to_signed( -7,16), to_signed( -5,16), to_signed( -4,16), to_signed( -2,16));
    signal lr_last : std_logic := '0';
begin

process(mclk)
    begin
        if rising_edge(mclk) then
            sample <= std_logic_vector(lookup_table(to_integer(index)));
            if lr_last = '0' and lrclk = '1' then
                index <= index+1;
            end if;
            lr_last <= lrclk;
        end if;
    end process;

end Behavioral;

i2s_transmitter.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz> 
-- 
-- Module Name: i2s_transmitter - Behavioral
--
-- Description: Convert the 16-bit samples to an I2S bitstream, synced to the 
--              supplied mclk, bclk and lrclk. The value of 'sample_left' and 
--              'sample_right' on the first '0' cycle of 'lrclk' are sent.  
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity i2s_transmitter is
    Port ( mclk         : in  STD_LOGIC;
           bclk         : in  STD_LOGIC;
           lrclk        : in  STD_LOGIC;
           sample_left  : in  STD_LOGIC_VECTOR (15 downto 0);
           sample_right : in  STD_LOGIC_VECTOR (15 downto 0);
           sdat         : out STD_LOGIC);
end i2s_transmitter;

architecture Behavioral of i2s_transmitter is
    signal lrclk_last : std_logic                     := '0';
    signal bclk_last  : std_logic                     := '0';
    signal to_send    : STD_LOGIC_VECTOR(31 downto 0) := (others => '0');
begin

mclk_proc: process(mclk)
    begin
        if rising_edge(mclk) then                        
            -- Shift the bits out on the falling edge of bclk
            if bclk_last = '1' and bclk = '0' then
                sdat    <= to_send(to_send'high);
                to_send <= to_send(to_send'high-1 downto 0) & '0';
            end if;

            -- Grab the samples on the falling edge of LRCLK
            if lrclk_last = '1' and lrclk = '0' then
                to_send <= sample_left & sample_right;
            end if;
                       
            bclk_last  <= bclk;
            lrclk_last <= lrclk;  
        end if;
    end process;

end Behavioral;

powerup_controller.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz> 
-- 
-- Module Name: powerup_controller - Behavioral
--
-- Description: Assert powerup after a short delay
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity powerup_controller is
    Port ( mclk    : in STD_LOGIC;
           powerup : out STD_LOGIC := '0');
end powerup_controller;

architecture Behavioral of powerup_controller is
    signal counter:  unsigned(9 downto 0) := (others => '0');
begin

    powerup <= counter(counter'high);
    
mclk_proc: process(mclk)
    begin
        if rising_edge(mclk) then
            if counter(counter'high) = '0' then
                counter <= counter + 1;
            end if;
        end if;
    end process;
end Behavioral;

basys3.xdc

# Constraints for the Basys3 board, PMOD B
#
# Clock signal
set_property PACKAGE_PIN W5      [get_ports clk100]
set_property IOSTANDARD LVCMOS33 [get_ports clk100]
create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports clk100]

##Pmod Header JB
##Sch name = JB1
set_property PACKAGE_PIN A14     [get_ports pmod_i2s_lrclk]
set_property IOSTANDARD LVCMOS33 [get_ports pmod_i2s_lrclk]
##Sch name = JB2
set_property PACKAGE_PIN A16     [get_ports pmod_i2s_sdat]
set_property IOSTANDARD LVCMOS33 [get_ports pmod_i2s_sdat]
#Sch name = JB4
set_property PACKAGE_PIN B16     [get_ports pmod_i2s_bclk]
set_property IOSTANDARD LVCMOS33 [get_ports pmod_i2s_bclk]
##Sch name = JB9
set_property PACKAGE_PIN C15     [get_ports pmod_i2s_mclk]
set_property IOSTANDARD LVCMOS33 [get_ports pmod_i2s_mclk]
##Sch name = JB10
set_property PACKAGE_PIN C16     [get_ports pmod_i2s_sd]
set_property IOSTANDARD LVCMOS33 [get_ports pmod_i2s_sd]

Personal tools