278 lines
11 KiB
VHDL
278 lines
11 KiB
VHDL
|
-- RGB LEDs x96 simple interface
|
||
|
-- author: xavier.jimenez@epfl.ch
|
||
|
-- file: rgb_led96.vhd
|
||
|
-- A simple interface to control 96 rgb leds.
|
||
|
-- Monochrome version: every LED will have the same color.
|
||
|
|
||
|
LIBRARY ieee;
|
||
|
USE ieee.std_logic_1164.all;
|
||
|
USE ieee.std_logic_arith.all;
|
||
|
USE ieee.std_logic_unsigned.all;
|
||
|
|
||
|
package pack is
|
||
|
function f_ceil_log2 (constant n : natural)
|
||
|
return natural;
|
||
|
end pack;
|
||
|
|
||
|
package body pack is
|
||
|
-- computes ceil(log2(N))
|
||
|
function f_ceil_log2 (constant n : natural) return natural is
|
||
|
variable n_v : natural;
|
||
|
variable ret : natural;
|
||
|
begin
|
||
|
assert n > 0
|
||
|
report "pack.f_ceil_log2(n): input n should be greater than 0."
|
||
|
severity error;
|
||
|
|
||
|
ret := 0;
|
||
|
n_v := 2*n-1;
|
||
|
while n_v > 1 loop
|
||
|
ret := ret + 1;
|
||
|
n_v := n_v / 2;
|
||
|
end loop;
|
||
|
|
||
|
return ret;
|
||
|
end f_ceil_log2;
|
||
|
end pack;
|
||
|
|
||
|
|
||
|
LIBRARY ieee;
|
||
|
USE ieee.std_logic_1164.all;
|
||
|
USE ieee.std_logic_unsigned.all;
|
||
|
USE ieee.std_logic_arith.all;
|
||
|
|
||
|
USE work.pack.all;
|
||
|
|
||
|
entity rgb_led96 is
|
||
|
|
||
|
generic (
|
||
|
-- input clock frequency in Hz
|
||
|
CLK_FREQ : natural := 50000000;
|
||
|
|
||
|
-- desired frame rate in Hz
|
||
|
FRAME_RATE : natural := 100;
|
||
|
|
||
|
DEFAULT_COLOR : std_logic_vector(23 downto 0) := X"0000FF"
|
||
|
);
|
||
|
|
||
|
port (
|
||
|
-- global
|
||
|
clk : in std_logic;
|
||
|
reset : in std_logic;
|
||
|
|
||
|
-- LEDs
|
||
|
LEDs : in std_logic_vector(95 downto 0);
|
||
|
|
||
|
-- a global color, 8 bits per channel, red by default
|
||
|
color : in std_logic_vector(23 downto 0) := DEFAULT_COLOR; --X"0000FF";
|
||
|
|
||
|
-- RGB LEDS matrix
|
||
|
LED_SelC_n : out std_logic_vector(11 downto 0);
|
||
|
LED_Sel_R : out std_logic_vector(7 downto 0);
|
||
|
LED_Sel_G : out std_logic_vector(7 downto 0);
|
||
|
LED_Sel_B : out std_logic_vector(7 downto 0);
|
||
|
LED_reset : out std_logic
|
||
|
);
|
||
|
|
||
|
end entity;
|
||
|
|
||
|
-- Architecture Body
|
||
|
architecture arch of rgb_led96 is
|
||
|
|
||
|
constant C_ROWS : natural := 8;
|
||
|
constant C_COLUMNS : natural := 12;
|
||
|
constant C_PWM_BITS : natural := 15;
|
||
|
|
||
|
-- time spent reseting a line (fixed to 1us)
|
||
|
constant C_PERIOD_RST : natural := CLK_FREQ/1000000;
|
||
|
|
||
|
-- maximum time period per line to satisfy frame rate
|
||
|
constant C_MAX_PERIOD : natural := CLK_FREQ/FRAME_RATE/C_COLUMNS - 1;
|
||
|
|
||
|
-- pwm cycle count during a display phase.
|
||
|
-- We fix the pwm on 2**15 clock cycles and want a display phase aligned to it
|
||
|
-- total line refresh is made of: 2 transitional cycles, a reset phase and the display phase.
|
||
|
constant C_PWM_CYCLES : natural := (C_MAX_PERIOD-C_PERIOD_RST-2)/(2**C_PWM_BITS);
|
||
|
|
||
|
-- display period
|
||
|
constant C_DISPLAY_PERIOD : natural := (2**C_PWM_BITS)*C_PWM_CYCLES;
|
||
|
|
||
|
-- we get the bitwidth of the period counter
|
||
|
constant C_PERIOD_WIDTH : natural := f_ceil_log2(C_DISPLAY_PERIOD);
|
||
|
|
||
|
type t_matrix is array(0 to 11) of std_logic_vector(7 downto 0);
|
||
|
signal s_led_matrix : t_matrix;
|
||
|
|
||
|
-- a register to store the pwm threshold for the one value and each channel
|
||
|
signal reg_pwm_thres : std_logic_vector(3*16-1 downto 0);
|
||
|
|
||
|
-- period counter
|
||
|
signal reg_period_cnt : std_logic_vector(C_PERIOD_WIDTH-1 downto 0);
|
||
|
|
||
|
-- current col index
|
||
|
signal reg_col_cnt : std_logic_vector(3 downto 0);
|
||
|
|
||
|
-- current col vector (single bit active on a 24-bit vector)
|
||
|
signal reg_sel_col : std_logic_vector(11 downto 0);
|
||
|
|
||
|
-- gamma 2.2 array to translate RGB to pwm duty cycles
|
||
|
type t_array16 is array (0 to 255) of std_logic_vector(15 downto 0);
|
||
|
signal gamma_table : t_array16 :=
|
||
|
( X"0000", X"0001", X"0002", X"0003", X"0004", X"0005", X"0006", X"0007", X"0008", X"0009", X"000A", X"000B", X"000C", X"000D", X"000E", X"000F", X"0010"
|
||
|
, X"0011", X"0012", X"0013", X"0014", X"0015", X"0016", X"0017", X"0018", X"0019", X"001B", X"001D", X"0020", X"0022", X"0025", X"0028", X"002B"
|
||
|
, X"002E", X"0031", X"0034", X"0037", X"003B", X"003E", X"0042", X"0046", X"0049", X"004D", X"0052", X"0056", X"005A", X"005F", X"0063", X"0068"
|
||
|
, X"006D", X"0072", X"0077", X"007C", X"0081", X"0087", X"008C", X"0092", X"0098", X"009E", X"00A4", X"00AA", X"00B0", X"00B6", X"00BD", X"00C4"
|
||
|
, X"00CA", X"00D1", X"00D8", X"00E0", X"00E7", X"00EE", X"00F6", X"00FE", X"0105", X"010D", X"0115", X"011E", X"0126", X"012E", X"0137", X"0140"
|
||
|
, X"0149", X"0152", X"015B", X"0164", X"016D", X"0177", X"0181", X"018A", X"0194", X"019E", X"01A8", X"01B3", X"01BD", X"01C8", X"01D3", X"01DD"
|
||
|
, X"01E9", X"01F4", X"01FF", X"020A", X"0216", X"0222", X"022D", X"0239", X"0246", X"0252", X"025E", X"026B", X"0277", X"0284", X"0291", X"029E"
|
||
|
, X"02AC", X"02B9", X"02C6", X"02D4", X"02E2", X"02F0", X"02FE", X"030C", X"031B", X"0329", X"0338", X"0346", X"0355", X"0365", X"0374", X"0383"
|
||
|
, X"0393", X"03A2", X"03B2", X"03C2", X"03D2", X"03E2", X"03F3", X"0403", X"0414", X"0425", X"0436", X"0447", X"0458", X"046A", X"047B", X"048D"
|
||
|
, X"049F", X"04B1", X"04C3", X"04D6", X"04E8", X"04FB", X"050D", X"0520", X"0533", X"0547", X"055A", X"056D", X"0581", X"0595", X"05A9", X"05BD"
|
||
|
, X"05D1", X"05E6", X"05FA", X"060F", X"0624", X"0639", X"064E", X"0664", X"0679", X"068F", X"06A4", X"06BA", X"06D1", X"06E7", X"06FD", X"0714"
|
||
|
, X"072A", X"0741", X"0758", X"0770", X"0787", X"079E", X"07B6", X"07CE", X"07E6", X"07FE", X"0816", X"082F", X"0847", X"0860", X"0879", X"0892"
|
||
|
, X"08AB", X"08C5", X"08DE", X"08F8", X"0912", X"092C", X"0946", X"0960", X"097B", X"0995", X"09B0", X"09CB", X"09E6", X"0A01", X"0A1D", X"0A38"
|
||
|
, X"0A54", X"0A70", X"0A8C", X"0AA8", X"0AC5", X"0AE1", X"0AFE", X"0B1B", X"0B38", X"0B55", X"0B73", X"0B90", X"0BAE", X"0BCC", X"0BEA", X"0C08"
|
||
|
, X"0C26", X"0C45", X"0C63", X"0C82", X"0CA1", X"0CC0", X"0CDF", X"0CFF", X"0D1F", X"0D3E", X"0D5E", X"0D7E", X"0D9F", X"0DBF", X"0DE0", X"0E01"
|
||
|
, X"0E22", X"0E43", X"0E64", X"0E85", X"0EA7", X"0EC9", X"0EEB", X"0F0D", X"0F2F", X"0F51", X"0F74", X"0F97", X"0FBA", X"0FDD", X"1000");
|
||
|
signal s_gamma_addr : std_logic_vector(7 downto 0);
|
||
|
signal reg_gamma_data : std_logic_vector(15 downto 0);
|
||
|
|
||
|
-- FSM
|
||
|
type t_state is (TRANS1, RST, TRANS2, DISPLAY);
|
||
|
signal state, next_state : t_state;
|
||
|
|
||
|
begin
|
||
|
|
||
|
-- led matrix mapping
|
||
|
g_matrix_i : for i in 0 to 11 generate
|
||
|
s_led_matrix(i) <= LEDs((i+1)*8-1 downto i*8);
|
||
|
end generate;
|
||
|
|
||
|
-- col activation
|
||
|
LED_SelC_n <= not reg_sel_col when state = DISPLAY else (others => '1');
|
||
|
|
||
|
-- row activation
|
||
|
process (reg_period_cnt, state, reg_pwm_thres, s_led_matrix, reg_col_cnt)
|
||
|
variable v_pwm : std_logic_vector(C_PWM_BITS-1 downto 0);
|
||
|
begin
|
||
|
-- by default, each channel at 0
|
||
|
LED_Sel_R <= (others=>'0');
|
||
|
LED_Sel_G <= (others=>'0');
|
||
|
LED_Sel_B <= (others=>'0');
|
||
|
v_pwm := reg_period_cnt(C_PWM_BITS-1 downto 0);
|
||
|
if (state = DISPLAY) then
|
||
|
if (reg_pwm_thres(46 downto 32) > v_pwm) then
|
||
|
LED_Sel_R <= s_led_matrix(conv_integer(reg_col_cnt));
|
||
|
end if;
|
||
|
-- green
|
||
|
if (reg_pwm_thres(30 downto 16) > v_pwm) then
|
||
|
LED_Sel_G <= s_led_matrix(conv_integer(reg_col_cnt));
|
||
|
end if;
|
||
|
-- blue
|
||
|
if (reg_pwm_thres(14 downto 0) > v_pwm) then
|
||
|
LED_Sel_B <= s_led_matrix(conv_integer(reg_col_cnt));
|
||
|
end if;
|
||
|
end if;
|
||
|
end process;
|
||
|
|
||
|
-- combinatorial gamma address
|
||
|
process (color, state, reg_period_cnt)
|
||
|
begin
|
||
|
-- the address to the gamma table
|
||
|
-- by default we point on the first channel
|
||
|
s_gamma_addr <= color(7 downto 0); -- (blue)
|
||
|
-- then during the first two cycle of the reset phase, we switch to the other two
|
||
|
if (state = RST) then
|
||
|
if (reg_period_cnt <= 1) then
|
||
|
if (reg_period_cnt(0) = '0') then
|
||
|
s_gamma_addr <= color(15 downto 8); -- (green)
|
||
|
else
|
||
|
s_gamma_addr <= color(23 downto 16); -- (red)
|
||
|
end if;
|
||
|
end if;
|
||
|
end if;
|
||
|
end process;
|
||
|
|
||
|
-- loading the pwm threshold reference for the 1s during the reset state.
|
||
|
process (clk) -- no need for reset
|
||
|
begin
|
||
|
if (rising_edge(clk)) then
|
||
|
reg_gamma_data <= gamma_table(conv_integer(s_gamma_addr));
|
||
|
-- the reg column
|
||
|
if (state = RST) then
|
||
|
if (reg_period_cnt <= 2) then
|
||
|
-- for green and blue the value must be doubled
|
||
|
case reg_period_cnt(1 downto 0) is
|
||
|
when "00" | "01" =>
|
||
|
-- (blue and green delayed by 1 cycle (reading gamma has 1-cycle latency))
|
||
|
reg_pwm_thres <= reg_gamma_data(14 downto 0) & '0' & reg_pwm_thres(3*16-1 downto 16);
|
||
|
when others =>
|
||
|
reg_pwm_thres <= reg_gamma_data & reg_pwm_thres(3*16-1 downto 16);
|
||
|
end case;
|
||
|
end if;
|
||
|
end if;
|
||
|
end if;
|
||
|
end process;
|
||
|
|
||
|
-- registers and counters
|
||
|
process (reset, clk)
|
||
|
begin
|
||
|
if (reset = '1') then
|
||
|
state <= TRANS1;
|
||
|
reg_period_cnt <= (others => '0');
|
||
|
reg_col_cnt <= (others => '0');
|
||
|
reg_sel_col <= (0 => '1', others => '0');
|
||
|
elsif rising_edge(clk) then
|
||
|
state <= next_state;
|
||
|
-- period counter
|
||
|
case state is
|
||
|
when TRANS1 | TRANS2 =>
|
||
|
reg_period_cnt <= (others => '0');
|
||
|
when RST | DISPLAY =>
|
||
|
reg_period_cnt <= reg_period_cnt + 1;
|
||
|
when others =>
|
||
|
end case;
|
||
|
-- new column
|
||
|
if (state = TRANS1) then
|
||
|
-- we activate the next column
|
||
|
reg_sel_col <= reg_sel_col(10 downto 0) & reg_sel_col(11);
|
||
|
-- we update the column index after a reset, during trans2
|
||
|
if (reg_col_cnt = 11) then
|
||
|
reg_col_cnt <= (others => '0');
|
||
|
else
|
||
|
reg_col_cnt <= reg_col_cnt + 1;
|
||
|
end if;
|
||
|
end if;
|
||
|
end if;
|
||
|
end process;
|
||
|
|
||
|
-- Simple state machine to switch between reset and display periods.
|
||
|
-- Between each transition we deactivate every signal for 1 cycle to
|
||
|
-- ensure that the RGB_reset signal cannot be activated along with a
|
||
|
-- column selection (shortcut)
|
||
|
process (state, reg_period_cnt)
|
||
|
begin
|
||
|
LED_reset <= '0';
|
||
|
next_state <= state;
|
||
|
case state is
|
||
|
when TRANS1 =>
|
||
|
next_state <= RST;
|
||
|
|
||
|
when RST =>
|
||
|
LED_reset <= '1';
|
||
|
if (reg_period_cnt = C_PERIOD_RST-1) then
|
||
|
next_state <= TRANS2;
|
||
|
end if;
|
||
|
|
||
|
when TRANS2 =>
|
||
|
next_state <= DISPLAY;
|
||
|
|
||
|
when DISPLAY =>
|
||
|
if (reg_period_cnt = C_DISPLAY_PERIOD-1) then
|
||
|
next_state <= TRANS1;
|
||
|
end if;
|
||
|
when others =>
|
||
|
end case;
|
||
|
end process;
|
||
|
end architecture;
|