OpenCores
URL https://opencores.org/ocsvn/ws2812/ws2812/trunk

Subversion Repositories ws2812

[/] [ws2812/] [trunk/] [rtl/] [VHDL/] [rgb_pixel_pack.vhd] - Rev 2

Compare with Previous | Blame | View Log

--------------------------------------------------------------------------
-- Package of dds components
--
--
 
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
package rgb_pixel_pack is
 
  component ws2812_LED_chain_driver
    generic (
      SYS_CLK_RATE : real; -- underlying clock rate
      ADR_BITS     : natural; -- Must equal or exceed BIT_WIDTH(N_LEDS)+2.
      N_LEDS       : natural  -- Number of LEDs in chain
    );
    port (
 
      -- System Clock, Reset and Clock Enable
      sys_rst_n  : in  std_logic;
      sys_clk    : in  std_logic;
      sys_clk_en : in  std_logic;
 
      -- Selection of color information
      c_adr_o    : out unsigned(ADR_BITS-1 downto 0);
      c_dat_i    : in  unsigned(7 downto 0);
 
      -- Output
      sdat_o     : out std_logic
    );
  end component;
 
  component lpd8806_LED_chain_driver
    generic (
      SCLK_FREQ    : real;    -- Desired lpd8806 serial clock frequency
      UPDATE_FREQ  : real;    -- Desired LED chain update frequency
      SYS_CLK_RATE : real;    -- underlying clock rate
      N_LEDS       : integer  -- Number of LEDs in chain
    );
    port (
 
      -- System Clock, Reset and Clock Enable
      sys_rst_n  : in  std_logic;
      sys_clk    : in  std_logic;
      sys_clk_en : in  std_logic;
 
      -- Selection of color information
      c_adr_o    : out unsigned(7 downto 0);
      c_dat_i    : in  unsigned(6 downto 0);
 
      -- Output
      sclk_o     : out std_logic;
      sdat_o     : out std_logic
    );
  end component;
 
  component spi_byte_writer
    generic (
      SCLK_FREQ    : real;    -- Desired lpd8806 serial clock frequency
      SYS_CLK_RATE : real     -- underlying clock rate
    );
    port (
 
      -- System Clock, Reset and Clock Enable
      sys_rst_n  : in  std_logic;
      sys_clk    : in  std_logic;
      sys_clk_en : in  std_logic;
 
      -- Data to send.
      sel_i      : in  std_logic;
      we_i       : in  std_logic;
      dat_i      : in  unsigned(7 downto 0);
 
      -- Output
      ssel_o     : out std_logic;
      sclk_o     : out std_logic;
      sdat_o     : out std_logic
    );
  end component;
 
end rgb_pixel_pack;
 
package body rgb_pixel_pack is
end rgb_pixel_pack;
 
-------------------------------------------------------------------------------
-- WS2812 "GRB" LED chain driver module
-------------------------------------------------------------------------------
--
-- Author: John Clayton
-- Update: Oct. 19, 2013 Started Coding, wrote description.
--
-- Description
-------------------------------------------------------------------------------
-- This module outputs a serial stream of NRZ data pulses which
-- are intended for driving a chain of WS2812 LED PWM driver ICs.
-- Actually, the WS2811 is the driver as a separate IC, and the WS2812 is
-- a complete RGB LED pixel with the driver built right in.  As costs drop
-- the popularity of this device is rising.
--
-- The datasheet seems to indicate some very specific timing requirements.
--
-- A '1' bit is apparently 0.35us high, followed by 0.8us low.
-- A '0' bit is apparently 0.7us high, followed by 0.6us low.
--
-- The datasheet also states that Th+Tl = 1.25us +/- 600 ns.
--
-- Well, based on that, this module chooses to use 1.25 microseconds
-- per bit time, which corresponds to 800kbps.  Internally, the system clock
-- rate is divided by 800000, and the result is the number of clock cycles
-- per bit available for creating the serial bitstream.
--
-- Then, constants are determined based on CLKS_PER_BIT according to the
-- following formulae:
--
--   CLKS_T0h = 0.28*CLKS_PER_BIT
--   CLKS_T1h = 0.54*CLKS_PER_BIT
--
-- This means that the timing will be closer or farther from ideal,
-- depending on the SYS_CLK_RATE.  For example:
--
--   Fsys_clk = 50 MHz
--   CLKS_PER_BIT = 62.5 which is rounded down to 62.
--   CLKS_T0h = 17.36 which is rounded down to 17.
--   CLKS_T1h = 33.48 which is rounded down to 33.
--   This leaves 45 clocks for the T0l time.
--   This leaves 29 clocks for the T1l time.
--
--   For this example, the bit time is 1.24us.
--   The T0h is 0.34us and T0l is 0.9us.
--   The T1h is 0.66us and T1l is 0.58us.
--
-- The lower the SYS_CLK_RATE, the tougher it is to meet the stated timing
-- requirements of the WS2812 device.  Using this scheme, the lowest clock
-- rate supported is 20 MHz.
--
-- The LEDs are driven with 24-bit color, that is 8 bits for red
-- intensity, 8 bits for green intensity and 8 bits for blue intensity.
--
-- I stopped short of calling it an "RGB LED driver" since WS2811/WS2812
-- receives the data in the order "GRB."  The ordering of the color
-- bits should not really matter anyway, so let's just be very accepting,
-- shall we not?
--
-- After the entire sequence of serial bits is driven out for updating the
-- GRB colors of the LEDs, then a reset interval of RESET_CLKS is given,
-- where RESET_CLKS is set by constants.
--
-- Interestingly, it seems that sending extra color information will not
-- affect the LED string in a negative way.  So, for example, if there
-- are only eight devices in the string, and the module is configured to
-- send out data for 10 devices, then the first eight devices will run
-- just fine.
--
-- Yes, that is correct, the LEDs closest to the source of sdat_o get lit
-- first, then they become "passthrough" so that the next one gets lit, and
-- so forth.
--
-- This module latches color information from an external source.
-- It provides the c_adr_o signal to specify which information
-- should be selected.  In this way, the module can be used with a variable
-- numbers of WS2811/WS2812 devices in the chain.
--
-- Just to keep things on an even keel, the c_adr_o address
-- advances according to the following pattern:
--
-- 0,1,2,4,5,6,8,9,A,C,D,E...
--
-- What is happening is that one address per LED is getting skipped
-- or "wasted" in order to start with each LEDs green value on an
-- even multiple N*4, where N is the LED number, beginning with zero
-- for the "zeroth" LED.  Then the red values are at N*4+1, while
-- the blue values are at N*4+2.  The N*4+3 values are simply skipped.
-- Isn't that super organized?
--
-- This unit runs continuously, the only way to stop it is to lower
-- the sys_clk_en input.
--
 
 
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.MATH_REAL.ALL;
 
library work;
use work.dds_pack.all;
use work.convert_pack.all;
 
  entity ws2812_LED_chain_driver is
    generic (
      SYS_CLK_RATE : real := 50000000.0; -- underlying clock rate
      ADR_BITS     : natural := 8; -- Must equal or exceed BIT_WIDTH(N_LEDS)+2.
      N_LEDS       : natural := 8  -- Number of LEDs in chain
    );
    port (
 
      -- System Clock, Reset and Clock Enable
      sys_rst_n  : in  std_logic;
      sys_clk    : in  std_logic;
      sys_clk_en : in  std_logic;
 
      -- Selection of color information
      c_adr_o    : out unsigned(ADR_BITS-1 downto 0);
      c_dat_i    : in  unsigned(7 downto 0);
 
      -- Output
      sdat_o     : out std_logic
    );
    end ws2812_LED_chain_driver;
 
architecture beh of ws2812_LED_chain_driver is
 
-- Constants
constant LED_BIT_RATE   : real := 800000.0;
constant CLKS_PER_BIT   : natural := integer(floor(SYS_CLK_RATE/LED_BIT_RATE));
constant CLKS_T0_H      : natural := integer(floor(0.28*SYS_CLK_RATE/LED_BIT_RATE));
constant CLKS_T1_H      : natural := integer(floor(0.54*SYS_CLK_RATE/LED_BIT_RATE));
constant SUB_COUNT_BITS : natural := bit_width(CLKS_PER_BIT);
constant STRING_BYTES   : natural := 3*N_LEDS;
constant RESET_TIME     : real := 0.000050; -- "time" data type could have been used...
constant RESET_BCOUNT   : natural := integer(floor(RESET_TIME*LED_BIT_RATE));
constant RESET_BITS     : natural := timer_width(RESET_BCOUNT);
 
-- Signals
signal reset_count : unsigned(RESET_BITS-1 downto 0);
signal sub_count   : unsigned(SUB_COUNT_BITS-1 downto 0);
signal bit_count   : unsigned(2 downto 0);
signal byte_count  : unsigned(bit_width(STRING_BYTES)-1 downto 0);
signal c_adr       : unsigned(ADR_BITS-1 downto 0);
signal c_dat       : unsigned(7 downto 0);
 
-----------------------------------------------------------------------------
begin
 
  c_adr_proc: Process(sys_rst_n,sys_clk)
  begin
    if (sys_rst_n = '0') then
      reset_count <= to_unsigned(RESET_BCOUNT,reset_count'length);
      sub_count  <= (others=>'0');
      byte_count <= (others=>'0');
      c_adr      <= (others=>'0');
      c_dat      <= (others=>'0');
      bit_count  <= (others=>'0');
    elsif (sys_clk'event and sys_clk='1') then
      if (sys_clk_en='1') then
        -- Sub count just keeps going all the time, during reset
        -- and during data transition.
        sub_count <= sub_count+1;
        if (sub_count=CLKS_PER_BIT-1) then
          sub_count <= (others=>'0');
        end if;
 
        -- Reset count decrements until reaching one, then
        -- it is set to zero while data is shifted out.
        -- It decrements once for each bit time.
        if (reset_count>0) then
          if (sub_count=CLKS_PER_BIT-1) then
            reset_count <= reset_count-1;
          end if;
        end if;
 
        -- When reset count reaches zero, color data shifting occurs
        if (reset_count>0) then
          c_adr <= (others=>'0');
          if (reset_count=1 and sub_count=CLKS_PER_BIT-1) then
            c_adr <= c_adr+1; -- Data from first address is loaded during reset time...
          end if;
          c_dat <= c_dat_i;
        else
          if (sub_count=CLKS_PER_BIT-1) then
            c_dat <= c_dat(c_dat'length-2 downto 0) & '0'; -- shift
            bit_count <= bit_count+1;
            if (bit_count=7) then
              c_dat <= c_dat_i;
              if (c_adr(1 downto 0)="10") then
                c_adr <= c_adr+2;
                if (byte_count=STRING_BYTES-1) then
                  byte_count <= (others=>'0');
                  reset_count <= to_unsigned(RESET_BCOUNT,reset_count'length);
                else
                  byte_count <= byte_count+1;
                end if;
              else
                c_adr <= c_adr+1;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if; -- sys_clk
  end process;
  sdat_o <= '0' when (reset_count>0) else
            '1' when (c_dat(7)='1') and (sub_count<CLKS_T1_H) else
            '1' when (sub_count<CLKS_T0_H) else
            '0';
  c_adr_o <= c_adr;
 
end beh;
 
 
-------------------------------------------------------------------------------
-- LPD8806 "GRB" LED chain driver module
-------------------------------------------------------------------------------
--
-- Author: John Clayton
-- Update: Apr.  8, 2013 Started Coding, wrote description.
--         Apr. 10, 2013 Simulated and tested in hardware.
--
-- Description
-------------------------------------------------------------------------------
-- This module outputs a serial stream of clock and data pulses which
-- are intended for driving a chain of LPD8806 LED PWM driver ICs.
--
-- This type of LED driver is commonly sold pre-built into strips
-- of LEDs, with each LPD8806 circuit driving two multi-color LEDs.
--
-- The LEDs are driven with 21-bit color, that is 7 bits for red
-- intensity, 7 bits for green intensity and 7 bits for blue intensity.
--
-- I stopped short of calling it an "RGB LED driver" since lpd8806
-- receives the data in the order "GRB."  The ordering of the color
-- bits should not really matter anyway, so just be happy, OK?
--
-- The first bit, the MSB, is set for updating the LED colors, and cleared
-- for resetting the drivers.  Therefore, after each sequence of color
-- information bits, about 24 bits of zero are sent out to reset
-- everything in order to be ready for the next update.
--
-- This module generates its own serial bit clock by using a DDS based
-- on the underlying system clock rate.  It has been suggested that
-- 2 MHz is a good number, although I'll bet many other speeds will
-- also work, both higher and lower.
--
-- Another DDS generates the update rate pulse, which kicks off
-- the updating process.  Beware, if one sets the update rate too
-- high, it may become impossible to transmit all the required color
-- bits, and so the LEDs at the end of the chain may get left out
-- of the process!
--
-- Yes, that is correct, the LEDs closest to the source of SCLK
-- and SDAT get lit first, then they become "passthrough" so that
-- the next one gets set, and so forth.
--
-- This module latches color information from an external source.
-- It provides the c_adr_o signal to specify which information
-- should be selected.  In this way, the module can be used with
-- different numbers of drivers and LED pairs in the strip.
--
-- Just to keep things on an even keel, the c_adr_o address
-- advances according to the following pattern:
--
-- 0,1,2,4,5,6,8,9,A,C,D,E...
--
-- What is happening is that one address per LED is getting skipped
-- or "wasted" in order to start with each LEDs green value on an
-- even multiple N*4, where N is the LED number, beginning with zero
-- for the "zeroth" LED.  Then the red values are at N*4+1, while
-- the blue values are at N*4+2.  The N*4+3 values are simply skipped.
-- Isn't that super organized?
--
--
 
 
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.MATH_REAL.ALL;
 
library work;
use work.dds_pack.all;
 
  entity lpd8806_LED_chain_driver is
    generic (
      SCLK_FREQ    : real;    -- Desired lpd8806 serial clock frequency
      UPDATE_FREQ  : real;    -- Desired LED chain update frequency
      SYS_CLK_RATE : real;    -- underlying clock rate
      N_LEDS       : integer  -- Number of LEDs in chain
    );
    port (
 
      -- System Clock, Reset and Clock Enable
      sys_rst_n  : in  std_logic;
      sys_clk    : in  std_logic;
      sys_clk_en : in  std_logic;
 
      -- Selection of color information
      c_adr_o    : out unsigned(7 downto 0);
      c_dat_i    : in  unsigned(6 downto 0);
 
      -- Output
      sclk_o     : out std_logic;
      sdat_o     : out std_logic
    );
    end lpd8806_LED_chain_driver;
 
architecture beh of lpd8806_LED_chain_driver is
 
-- Constants
constant N_ZERO_BYTES : natural := 3;
 
-- Signals
signal sclk            : std_logic;
signal sclk_pulse      : std_logic;
signal update_pulse    : std_logic;
signal bit_count       : unsigned(3 downto 0);
signal byte_count      : unsigned(7 downto 0); -- Widen for chains of 64+ LEDs.
signal c_adr           : unsigned(7 downto 0); -- Widen for chains of 64+ LEDs.
signal c_dat           : unsigned(7 downto 0);
 
-----------------------------------------------------------------------------
begin
 
  c_adr_proc: Process(sys_rst_n,sys_clk)
  begin
    if (sys_rst_n = '0') then
      byte_count <= (others=>'0');
      c_adr      <= (others=>'0');
      c_dat      <= (others=>'0');
      bit_count  <= (others=>'0');
    elsif (sys_clk'event and sys_clk='1') then
      if (sys_clk_en='1') then
        if (byte_count=0) then
          c_adr <= (others=>'0');
        end if;
        if (byte_count>0 and sclk_pulse='1') then
          bit_count <= bit_count-1;
          if (bit_count<8) then
            c_dat <= c_dat(6 downto 0) & '0'; -- Default is to shift color data
            if (bit_count=0) then
              byte_count <= byte_count-1;
              bit_count  <= to_unsigned(7,bit_count'length);
              -- Set color data MSB appropriately
              if (byte_count<=4) then
                c_dat <= (others=>'0');
              else
                c_dat <= '1' & c_dat_i;
              end if;
              -- Cause c_adr to skip every address.ending in "11"
              if (c_adr(1 downto 0)="10") then
                c_adr <= c_adr+2;
              else
                c_adr <= c_adr+1;
              end if;
            end if;
          end if;
        end if;
        -- Update pulse gets highest priority
        if (update_pulse='1') then
          byte_count <= to_unsigned(N_LEDS*3+1+N_ZERO_BYTES,byte_count'length);
          if (sclk_pulse='1') then
            bit_count  <= to_unsigned(7,bit_count'length);
          else
            bit_count  <= to_unsigned(8,bit_count'length); -- means "pending"
          end if;
          c_dat      <= '1' & c_dat_i;
          c_adr      <= c_adr+1;
        end if;
      end if;
    end if; -- sys_clk
  end process;
  sdat_o <= c_dat(7) when (byte_count>0 and bit_count<8) else '0';
  c_adr_o <= c_adr;
 
  -------------------------
  -- Update pulse generation
  update_gen: dds_constant_squarewave
    generic map(
      OUTPUT_FREQ  => UPDATE_FREQ,  -- Desired output frequency
      SYS_CLK_RATE => SYS_CLK_RATE, -- underlying clock rate
      ACC_BITS     => 24 -- Bit width of DDS phase accumulator
    )
    port map( 
 
      sys_rst_n    => sys_rst_n,
      sys_clk      => sys_clk,
      sys_clk_en   => sys_clk_en,
 
      -- Output
      pulse_o      => update_pulse,
      squarewave_o => open
    );
 
  -------------------------
  -- Serial clock generation
  sclk_gen: dds_constant_squarewave
    generic map(
      OUTPUT_FREQ  => SCLK_FREQ,   -- Desired output frequency
      SYS_CLK_RATE => SYS_CLK_RATE, -- underlying clock rate
      ACC_BITS     => 16 -- Bit width of DDS phase accumulator
    )
    port map( 
 
      sys_rst_n    => sys_rst_n,
      sys_clk      => sys_clk,
      sys_clk_en   => sys_clk_en,
 
      -- Output
      pulse_o      => sclk_pulse,
      squarewave_o => sclk
    );
  sclk_o <= not sclk when (byte_count>0 and bit_count<8) else '0';
 
end beh;
 
 
-------------------------------------------------------------------------------
-- SPI byte writer module
-------------------------------------------------------------------------------
--
-- Author: John Clayton
-- Update: Apr.  8, 2013 Started Coding, wrote description.
--
-- Description
-------------------------------------------------------------------------------
-- I conceived of this module while debugging a serial driver for a
-- string of LPD8806 LED drivers.  It essentially latches the input
-- data, and sends it out serially at the desired rate.  When the byte
-- is fully transmitted, it stops and waits for the next byte.
--
-- The output clock is gated by ssel_o.  If you need a continuous
-- clock, please ungate it yourself.
--
-- Unlike with asynchronous data which shift out the lsb first, this
-- unit shifts out the msb first.
--
 
 
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.MATH_REAL.ALL;
 
library work;
use work.dds_pack.all;
 
  entity spi_byte_writer is
    generic (
      SCLK_FREQ    : real;    -- Desired lpd8806 serial clock frequency
      SYS_CLK_RATE : real     -- underlying clock rate
    );
    port (
 
      -- System Clock, Reset and Clock Enable
      sys_rst_n  : in  std_logic;
      sys_clk    : in  std_logic;
      sys_clk_en : in  std_logic;
 
      -- Data to send.
      sel_i      : in  std_logic;
      we_i       : in  std_logic;
      dat_i      : in  unsigned(7 downto 0);
 
      -- Output
      ssel_o     : out std_logic;
      sclk_o     : out std_logic;
      sdat_o     : out std_logic
    );
    end spi_byte_writer;
 
architecture beh of spi_byte_writer is
 
-- Signals
signal sclk            : std_logic;
signal sclk_pulse      : std_logic;
signal ssel            : std_logic;
signal sdat            : unsigned(7 downto 0);
signal bit_count       : unsigned(3 downto 0);
 
-----------------------------------------------------------------------------
begin
 
  spi_write_proc: Process(sys_rst_n,sys_clk)
  begin
    if (sys_rst_n = '0') then
      sdat      <= (others=>'0');
      bit_count <= (others=>'0');
    elsif (sys_clk'event and sys_clk='1') then
      if (sys_clk_en='1') then
        if (sclk_pulse='1') then
          if (bit_count>0) then
            bit_count <= bit_count-1;
          end if;
          if ssel='1' then
            sdat <= sdat(6 downto 0) & '0';
          end if;
        elsif (sel_i='1' and we_i='1') then
          if (sclk_pulse='1') then
            bit_count  <= to_unsigned(8,bit_count'length);
          else
            bit_count  <= to_unsigned(9,bit_count'length); -- means "pending"
          end if;
          sdat <= dat_i;
        end if;
      end if;
    end if; -- sys_clk
  end process;
  sdat_o <= sdat(7) when ssel='1' else '0';
 
  -------------------------
  -- Serial clock generation
  sclk_gen: dds_constant_squarewave
    generic map(
      OUTPUT_FREQ  => SCLK_FREQ,   -- Desired output frequency
      SYS_CLK_RATE => SYS_CLK_RATE, -- underlying clock rate
      ACC_BITS     => 16 -- Bit width of DDS phase accumulator
    )
    port map( 
 
      sys_rst_n    => sys_rst_n,
      sys_clk      => sys_clk,
      sys_clk_en   => sys_clk_en,
 
      -- Output
      pulse_o      => sclk_pulse,
      squarewave_o => sclk
    );
  sclk_o <= not sclk when ssel='1' else '0';
  ssel   <= '1'  when (bit_count<9 and bit_count>0) else '0';
  ssel_o <= ssel;
 
end beh;
 
 
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.