Skip navigation

In the second part of this article, we’re going to try and get a little more interesting stuff going on with our FPGA powered LED matrix. To start, we will try to get a simple image to display on the matrix. To do this we will need a way to store the image. VHDL allows for array types so we can accomplish this by creating an eight element array of eight bit vectors, giving us one bit per pixel. We can then use each of these vectors as a mask value to AND with the col signal. This is a little confusing (at least in my head), as each col value contains a bit for each row in the column, so we AND that with the col_mask value, which is essential the pixels for a row of the display. Starting with the same setup from last time, we are adding these new signals to the architecture.


 type display_t is array(7 downto 0) of STD_LOGIC_VECTOR(7 downto 0);
 signal display : display_t := ("00000000" ,    -- A simple image to display
											"01100110" , 	-- stored in an array
											"01100110" ,
											"00000000" ,
											"00000000" ,
											"11000011" ,
											"00111100" ,
											"00000000");

 signal col_mask : STD_LOGIC_VECTOR(7 downto 0) := "00000000";		--mask value used to activate only desired pixels
 signal row_count : unsigned(2 downto 0) := "000";						--pointer for array

Note that for the array, you have do declare an array type first, then declare an array of that type. Like many languages, we are allowed to instantiate the array with some values. Notice the nice smiley face. Also note that the array is declared 7 downto 0, meaning the first element essentially is the last one in the instantiation list. Since row 0 of our display is at the bottom, this means the image should appear just as the bits appear in the code. I also added a row_count signal, declared as unsigned because it will be used numerically. This will just count up as we scan the matrix rows and serve as a pointer to our array. Also added is the col_mask signal. This signal stores the current row loaded from the array. Below is the added code to increment the row_count and retrieve the col_mask. Note that the row_count is declared as 3 bits, meaning it will automatically rollover back to zero after 7.


					-- increment row count
					row_count <= row_count + 1;
					-- get column mask from current position in array
					col_mask <= display(conv_integer(row_count));

And then of course we AND the mask to the output.


			--combine column with mask to only display selected pixels
			A(15 downto 8) <= (col AND col_mask);

Here is the complete code for this example.

--Standard includes
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

--Entity definiton
entity First is
    Port ( A : out  STD_LOGIC_VECTOR (16 downto 0); -- maps to pins 0 to 16 of port A
			  clk : in STD_LOGIC); --clock signal
end First;

--architecture definition
architecture Behavioral of First is
 --Interial signals
 signal counter : STD_LOGIC_VECTOR(8 downto 0) := (others => '0'); --counter to control state and delay
 signal row : STD_LOGIC_VECTOR(7 downto 0) := "11111110";				--signal for LED rows
 signal col : STD_LOGIC_VECTOR(7 downto 0) := "00000001";				--signal for LED columns
 type display_t is array(7 downto 0) of STD_LOGIC_VECTOR(7 downto 0);
 signal display : display_t := ("00000000" ,    -- A simple image to display
											"01100110" , 	-- stored in an array
											"01100110" ,
											"00000000" ,
											"00000000" ,
											"11000011" ,
											"00111100" ,
											"00000000");

 signal col_mask : STD_LOGIC_VECTOR(7 downto 0) := "00000000";		--mask value used to activate only desired pixels
 signal row_count : unsigned(2 downto 0) := "000";						--pointer for array

begin
	--Process Definition
	count: process(clk)
   begin
		-- triggers action on rising edge of clock signal
     if rising_edge(clk) then
	    --increment counter
       counter <= counter+1;
		 --clock period is 31.25ns, counter is 9 bits, should scan whole matrix in < 1ms
			--trigger each time counter rolls over back to zero
			if counter = 0 then
				-- Left Rotate col
				col <= col(6 downto 0) & col(7);
				-- Trigger when last column becomes active
				if col = "10000000" then
					-- Left rotate row
					row <= row(6 downto 0) & row(7);
					-- increment row count
					row_count <= row_count + 1;
					-- get column mask from current position in array
					col_mask <= display(conv_integer(row_count));
				end if;
			end if;
			--copy signals to outputs
			A(7 downto 0) <= row;
			--combine column with mask to only display selected pixels
			A(15 downto 8) <= (col AND col_mask);
     end if;
   end process;


end Behavioral;

This give the following output.

Now if you noticed, it looks good, but that’s not the image we intended. Its shifted up by one row! See my question on Stack Overflow for an explanation of where I went wrong. Apparently signals are updated at the end of the process, so the value is definitely updated after the value is retrieved from the array. The fix is pretty simple, I just instantiate my row_count to 1 to compensate.

 signal row_count : unsigned(2 downto 0) := "001";						--pointer for array

Now it displays correctly.

Now for one more experiment in this installment, lets create a vertical scrolling effect. In the above example where I screwed it up the first time, having the wrong value for row_count shifts the image up and down. We can use this to create our scrolling effect. My first thought was to add a second process with a delay counter and just subtract one from the row_count value. However, that produces a compile error, “this signal is connected to multiple drivers.” I’m guessing that means its illegal to set a signal from multiple processes. So, instead we will add a new signal, row_offset, same type as row_count. Then we will use the second process to increment that, and just subtract it from row_count when indexing the array.

We add these two new signals.

 signal row_offset : unsigned(2 downto 0) := "000";					--offset to control vertical scrolling
 signal scroll_delay : STD_LOGIC_VECTOR(22 downto 0) := (others => '0'); --counter for vertical scroll delay

And we add this new process to the same architecture.

	vscroll: process(clk)
		begin

			if rising_edge(clk) then
				--counter is 23 bits, will rollover at .26 seconds
				scroll_delay <= scroll_delay+1;

				if scroll_delay = 0 then
					--decrement to mis-align row count, to give a vertical scrolling effect
					row_offset <= row_offset+1;
				end if;
			end if;
		end process;

The we modify the array access like this.

					-- get column mask from current position in array
					col_mask <= display(conv_integer(row_count) - conv_integer(row_offset));

I had to use the two separate conv_integer functions as the compiler complained of trying to do the math first, then calling conv_integer. Here is the complete code.

--Standard includes
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

--Entity definiton
entity First is
    Port ( A : out  STD_LOGIC_VECTOR (16 downto 0); -- maps to pins 0 to 16 of port A
			  clk : in STD_LOGIC); --clock signal
end First;

--architecture definition
architecture Behavioral of First is
 --Interial signals
 signal counter : STD_LOGIC_VECTOR(8 downto 0) := (others => '0'); --counter to control state and delay
 signal row : STD_LOGIC_VECTOR(7 downto 0) := "11111110";				--signal for LED rows
 signal col : STD_LOGIC_VECTOR(7 downto 0) := "00000001";				--signal for LED columns
 type display_t is array(7 downto 0) of STD_LOGIC_VECTOR(7 downto 0);
 signal display : display_t := ("00000000" ,    -- A simple image to display
											"01100110" , 	-- stored in an array
											"01100110" ,
											"00000000" ,
											"00000000" ,
											"11000011" ,
											"00111100" ,
											"00000000");

 signal col_mask : STD_LOGIC_VECTOR(7 downto 0) := "00000000";		--mask value used to activate only desired pixels
 signal row_count : unsigned(2 downto 0) := "001";						--pointer for array
 signal row_offset : unsigned(2 downto 0) := "000";					--offset to control vertical scrolling
 signal scroll_delay : STD_LOGIC_VECTOR(22 downto 0) := (others => '0'); --counter for vertical scroll delay

begin
	--Process Definition
	count: process(clk)
   begin
		-- triggers action on rising edge of clock signal
     if rising_edge(clk) then
	    --increment counter
       counter <= counter+1;
		 --clock period is 31.25ns, counter is 9 bits, should scan whole matrix in < 1ms
			--trigger each time counter rolls over back to zero
			if counter = 0 then
				-- Left Rotate col
				col <= col(6 downto 0) & col(7);
				-- Trigger when last column becomes active
				if col = "10000000" then
					-- Left rotate row
					row <= row(6 downto 0) & row(7);
					-- increment row count
					row_count <= row_count + 1;
					-- get column mask from current position in array
					col_mask <= display(conv_integer(row_count) - conv_integer(row_offset));
				end if;
			end if;
			--copy signals to outputs
			A(7 downto 0) <= row;
			--combine column with mask to only display selected pixels
			A(15 downto 8) <= (col AND col_mask);
     end if;
   end process;

	vscroll: process(clk)
		begin

			if rising_edge(clk) then
				--counter is 23 bits, will rollover at .26 seconds
				scroll_delay <= scroll_delay+1;

				if scroll_delay = 0 then
					--decrement to mis-align row count, to give a vertical scrolling effect
					row_offset <= row_offset+1;
				end if;
			end if;
		end process;


end Behavioral;



And here is the scrolling effect in action.

Next time we will see about making a simple animation with several frames. See you then!

2 Comments

  1. hey really nice write up, I’m actually doing something similar to this for my class project but kind of stuck, was wondering if you can help. i want to use 17 inputs (switches) to represent 17 letters, each switch will have 1 letter assigned to it. was wondering if you can help me out.

    thanks

    • Thanks for the comment!
      Off the top of my head, I’m thinking a simple modification to the scrolling display example might fit your need. Extend the display array to such that it contains all of your letters. Then you can use the row_offset to create a sliding window effect for the display rather than a scrolling effect. So instead of just incrementing the row_offset, you can check the state of your inputs and set the offset to the appropriate spot in the display array for the desired letter.


Leave a Reply

Your email address will not be published. Required fields are marked *

 characters available