Skip navigation

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.

One Comment

  1. Dear,

    Is it also possible to combine RGB LED matrix with your Code from FPGA + LED Matrix, Part 2?

    I am trying to get it working, but it doesn’t work.


Leave a Reply

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

 characters available