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.