Skip navigation

Tag Archives: Matrix

I was just recently aquired an 8×8 RGB LED matrix, similar to this one. I though it would be good practice to wire up the Papilio board to it an write up some VHDL to drive the display.

2013-10-02 23.55.48

As you can see on the datasheet, this is a common anode display, meaning that the positive voltage will be supplied to the 8 common terminals to operate. The pinout is pretty simple, 1 through 8 are the blue cathodes, which I connected to pins B0-B7 on the board. 9 through 16 are the green cathodes, which are connected to pins A8-A15 board. Pins 21 through 28, are the red cathodes and are connected to the boards B15-B8 pins. Pins 17,18,19,20,29,30,31,32 are the anodes and are connected to pins A0-A7 on the board. I included some small 360 ohm resistors on the anodes, just to limit the current. However the LEDs are not very bright as such. Its much brighter, and I *think* it’s safe to drive the outputs directly from the pins. Otherwise some simple buffer drivers I’m sure would produce a much brighter result.

As for the code, I started off with some of the same stuff from the last LED matrix. The PWM drive is the same, but I changed the counter from 8 bit to 4 bit. It simply counts up, and as long as the counter is less than the set value, the output bit is on.


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;


entity PWM is
    Port ( Value_In : in  integer range 0 to 15 ; --input for pwm value
           Bit_Out : out  STD_LOGIC;					--pwm output
			  clk : in STD_LOGIC);							--system clock
end PWM;

architecture Behavioral of PWM is

begin

	pwm: process(clk)
		variable count: integer range 0 to 15 := 0; --variable, internal to process, 8 bit counter
	begin
		if rising_edge(clk) then 
			--increment count variable (note variable uses := not <=
			count := count + 1;
			
			if count < Value_In then 
			   --while the counter is less than value, set bit
				--this ensures duty cycle is proportional to value
				Bit_Out <= '1';
			else
				--off during other half of cycle, while counter is greater than value
				Bit_Out <= '0';
			end if;		
				
		end if;
	
	end process;
	


end Behavioral;

For the main code, I decided to just have it display all one color, where the RGB values are cycled through at different rates. I also tried to pull out as much as I could from the main process block, to avoid some of the confusion of values being set after the process completes, which caused actions to be one step behind where I thought they would be. The way I understand it, statements in the architecture block are “wired up” so that changes propagate almost instantly. I added three processes to handle the cycling of the RGB values. The signal ‘counter’ just serves as a delay to time the scanning process. The signal ‘row’ is a 24 bit mask for the cathode pins, where the 0 indicates the pin that is active. The signal ‘col’ is an 8 bit mask for the anode pins, where 1 indicates the active pin. The signal ‘rgb’ is a 3 bit flag which is rotated to keep track of which value, red, green, or blue, should be applied to the currently active cathode pin. ‘col’ is rotated left whenever ‘counter’ rolls back to zero, ‘row’ and ‘rgb’ are rotated left when after ‘col’ is rotated back to its original value. A WITH … SELECT syntax is used to map the different color values into ‘value’ depending on the value of ‘rgb’. ‘value’ is mapped into the PW generator, and ‘pwm_bit’ is mapped as its output. A WHEN … ELSE style assignment is used to enable or disable the ‘col’ flags on the output, which is what ties the PWM drive to the output.


--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 (15 downto 0); -- maps to pins 0 to 16 of port A
				B : out STD_LOGIC_VECTOR (15 downto 0); --
			  clk : in STD_LOGIC); --clock signal
end First;

--architecture definition
architecture Behavioral of First is

--component definition for the PWM
component PWM is
    Port ( Value_In : in  integer range 0 to 15 ;
           Bit_Out : out  STD_LOGIC;
			  clk : in STD_LOGIC);
end component;
			  
 --Interial signals
 --counter to control state and delay
 signal counter : STD_LOGIC_VECTOR(8 downto 0) := (others => '0'); 
 --signal for LED "rows", anodes. 0 provides ground for selected row
 signal row : STD_LOGIC_VECTOR(23 downto 0) := "111111111111111111111110";				
 --signal for LED "columns", common cathodes. 1 provides + volts for selected column.
 signal col : STD_LOGIC_VECTOR(7 downto 0) := "00000001";				--signal for LED columns
 
 --rgb flag, for selecting R,G,B values to send to selected output
 signal rgb : STD_LOGIC_VECTOR(2 downto 0) := "001";

 --rolling color values																	
 signal red_value : integer range 0 to 15 := 0;		 
 signal green_value : integer range 0 to 15 := 0;					
 signal blue_value : integer range 0 to 15 := 0;					
 --used to retrieve value from array, mapped to PWM input
 signal value : integer range 0 to 15 := 0;	
 
 --counters for timing color changes
 signal red_counter : STD_LOGIC_VECTOR(20 downto 0) := (others => '0');
 signal green_counter : STD_LOGIC_VECTOR(19 downto 0) := (others => '0');
 signal blue_counter : STD_LOGIC_VECTOR(18 downto 0) := (others => '0');
	
 --signal to be mapped to PWM output	
 signal pwm_bit : STD_LOGIC;												

begin 

	--port map for linking to the PWM
	PW1 : PWM port map(Value_In => value, 
							 Bit_out => pwm_bit,
							 clk => clk);
							 

	--route row masks to outputs
	A(15 downto 8) <=  row(22) & row(19) & row(16) & row(13) & row(10) & row(7) & row(4) & row(1); -- green
	B(15 downto 8) <= row(21) & row(18) & row(15) & row(12) & row(9) & row(6) & row(3) & row(0); -- red
	B(7 downto 0) <= row(23) & row(20) & row(17) & row(14) & row(11) & row(8) & row(5) & row(2); -- blue
	
	--route column masks to outputs, only if PWM bit is on
	A(7 downto 0) <= col WHEN pwm_bit = '1' 
								ELSE "00000000";
	
	--route proper RGB value to PWM generator
	WITH rgb SELECT
		value <= red_value WHEN "001",
					green_value WHEN "010",
					blue_value WHEN "100",
				   0 WHEN OTHERS;
					
					
	--Process Definition
	scan_matrix: 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(22 downto 0) & row(23);
					-- increment row count
					rgb <= rgb(1 downto 0) & rgb(2);

				end if;
				
			end if;
					
     end if; 	  	  
   end process;
						
		
	--Process Definition
	red: process(clk)
   begin
		-- triggers action on rising edge of clock signal
     if rising_edge(clk) then
			red_counter <= red_counter + 1;
			if red_counter = 0 then 
				red_value <= red_value + 1;
			end if;
	  end if;
	end process;
	  
	  	--Process Definition
	blue: process(clk)
   begin
		-- triggers action on rising edge of clock signal
     if rising_edge(clk) then
			blue_counter <= blue_counter + 1;
			if blue_counter = 0 then 
				blue_value <= blue_value + 1;
			end if;
	  end if;
	end process;
	
		--Process Definition
	green: process(clk)
   begin
		-- triggers action on rising edge of clock signal
     if rising_edge(clk) then
			green_counter <= green_counter + 1;
			if green_counter = 0 then 
				green_value <= green_value + 1;
			end if;
	  end if;
	end process;
	
	
		
	
end Behavioral;

Continuing with my learning experiments with the LED Matrix attached to the Papilio dev board, today I am adding PWM output to generate different light intensities. If your not familiar with PWM (pulse width modulation), it’s just a simple digital to analog conversion technique where the pin is switched on and off very fast, so the output effectively becomes an analog average between the time it’s on and the time is off. This is done by changing the duty cycle, which is the ratio to on time versus off time. In our VHDL project, this is a good opportunity to explore adding a new file to the project, a new entity, and interfacing multiple entities together. I added the new file for the PWM entity through the ISE Project Navigator, and followed the wizard, basically just giving the name of the file, entity, and the ports. The ports for this entity are pretty simple, we just need a value in to set the output of the PWM, the output bit, and the system clock in. I added a single process to the architecture, and this time decided to use a variable for internal counting instead of a signal. See this question about signals versus variables. Also being a little different from the code of parts 1 and 2, I am using the integer type, with the range specification. The operation of the PWM is really simple, each rising clock edge, the count variable is incremented. When the count is greater than the set value, the output is turned on, else its turned off. No other logic is needed since the count will keep rolling over back to zero. The higher the set value, the longer the bit stays on, and the higher the output. Just to note, with our clock period set at 31.25nS, and our count variable declared as 0 to 255, our wave will be exactly 256 clock cycles long, or 8uS. That comes out to be a 125KHz PWM frequency. Here’s the code for that part. I deleted out some of the automatic comments the wizard inserted in the file.



library IEEE;
use IEEE.STD_LOGIC_1164.ALL;


entity PWM is
    Port ( Value_In : in  integer range 0 to 255 ; --input for pwm value
           Bit_Out : out  STD_LOGIC;					--pwm output
			  clk : in STD_LOGIC);							--system clock
end PWM;

architecture Behavioral of PWM is

begin

	pwm: process(clk)
		variable count: integer range 0 to 255 := 0; --variable, internal to process, 8 bit counter
	begin
		if rising_edge(clk) then
			--increment count variable (note variable uses := not <=
			count := count + 1;

			if count < Value_In then
			   --while the counter is less than value, set bit
				--this ensures duty cycle is proportional to value
				Bit_Out <= '1';
			else
				--off during other half of cycle, while counter is greater than value
				Bit_Out <= '0';
			end if;

		end if;

	end process;



end Behavioral;

That seems like it will work. Now the trouble I had after this was trying to figure out how to tie the main entity in with this one so I could use it. I changed quite a bit of other stuff in the code to, since we now have to support multiple values for each LED, not just single bit on and off. I changed the display array to be a 2 dimensional array of 8 bit integers. The declaration and instantiation looked like this.


 type display_t is array(7 downto 0, 7 downto 0) of integer range 0 to 255; --array type for new image data, a 2d array of integers
 signal display : display_t := ((1, 1, 1, 1, 1, 1, 1, 1 ),
											(3, 3, 3, 3, 3, 3, 3, 3) ,
											(7, 7, 7, 7, 7, 7, 7, 7) ,
											(15,15,15,15,15,15,15,15) ,
											(31,31,31,31,31,31,31,31) ,
											(63,63,63,63,63,63,63,63) ,
											(127,127,127,127,127,127,127,127) ,
											(255,255,255,255,255,255,255,255 ));

With the 2D array, i also had to set a row_count and col_count to keep track of the position in the array. I also learned that I can define those as ranged integers, so they can be used to index the array directly without conversion. As for the col output, I added an if on the PWM output that would use the col mask if it was true or “00000000” if false. That statement was triggering on the same clock as the PWM so the output will be kept up to the PWM output. Our counter in this process is 9 bits, so each pixel should be getting about 2 PWM waves per scan.

Now to attach the PWM together with the main process here, I learned about the ‘port map’ and ‘component’ structures, component defined under the architecture and port map defined after the architecture’s begin statement. The component I believe acts like a port definition for the referenced entity, and the port map links the signals together.

--component definition for the PWM
component PWM is
    Port ( Value_In : in  integer range 0 to 255 ;
           Bit_Out : out  STD_LOGIC;
			  clk : in STD_LOGIC);
end component;

	--port map for linking to the PWM
	PW1 : PWM port map(Value_In => col_value,
							 Bit_out => pwm_bit,
							 clk => clk);

Looking at the port map, the signal col_value is mapped to PWM’s Value_In, so when we read the pixel value from the array into col_value, it is transferred to the input of the PWM. Likewise, pwm_bit will always be set to the value of the PWM’s Bit_out.

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

--component definition for the PWM
component PWM is
    Port ( Value_In : in  integer range 0 to 255 ;
           Bit_Out : out  STD_LOGIC;
			  clk : in STD_LOGIC);
end component;

 --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, 7 downto 0) of integer range 0 to 255; --array type for new image data, a 2d array of integers
 signal display : display_t := ((1, 1, 1, 1, 1, 1, 1, 1 ),
											(3, 3, 3, 3, 3, 3, 3, 3) ,
											(7, 7, 7, 7, 7, 7, 7, 7) ,
											(15,15,15,15,15,15,15,15) ,
											(31,31,31,31,31,31,31,31) ,
											(63,63,63,63,63,63,63,63) ,
											(127,127,127,127,127,127,127,127) ,
											(255,255,255,255,255,255,255,255 ));

 signal col_value : integer range 0 to 255 := 0;					--used to retrieve value from array, mapped to PWM input
 signal row_count : integer range 0 to 7 := 1;						--pointer for array
 signal col_count : integer range 0 to 7 := 1;						--pointer for array
 signal pwm_bit : STD_LOGIC;												--signal to be mapped to PWM output

begin

	--port map for linking to the PWM
	PW1 : PWM port map(Value_In => col_value,
							 Bit_out => pwm_bit,
							 clk => clk);

	--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);
				col_count <= col_count + 1;
				-- 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_value <=  display(row_count, col_count);

				end if;
			end if;
			--copy signals to outputs
			A(7 downto 0) <= row;
			--combine column with mask to only display selected pixels
			if pwm_bit = '1' then
				A(15 downto 8) <= (col);
			else
				A(15 downto 8) <= "00000000";
			end if;

     end if;
   end process;




end Behavioral;


And of course, the working example.

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!