Useless Robots

Robotics, Electronics, and Embedded Systems

LED Matrix

Adafruit LED matrix control w/ Verilog [part 3]

In this post, I modified the data-path and RAM to be parameterized so they can handle LED Matrices up to sizes of 64×32. I also created a controller that utilizes the write functions in the data-path in order to change what is imaged on the LED matrix. The controller utilizes a UART receive module running at 1,000,000 baud to receive a data stream from a computer and write the data stream to the RAM.

Final Setup

In my final setup, I will have my FPGA board connected to the LED matrix via an 8×2 pin female-female connector. The board will also be connected to a computer via a serial cable. I will use the pyserial module to use python the interface with the FPGA. This way I can use many pre-existing libraries to generate interesting things on the LED Matrix (patterns, text, etc).

Here are the materials used in the final setup:

Modifications to datapath

In order to increase the flexibility of the project, I made the data-path module parameterized in terms of the number of columns and rows of the LED Matrix to be driven. The size of the RAM was also increased (the size of RAM should be increased to accommodate how many LED matrices you would like to chain together).

module led_matrix_data_path #(parameter R=6,parameter C=5)(
	input CLK, 
	input RESET, 
	input CE, 
	input WE, 
	input [R-1:0] row0, 
	input [C-1:0] col0, 
	input [2:0] color0,
	input [R-1:0] row1,
	input [C-1:0] col1,
	input [2:0] color1,
	output [2:0] RGB0,
	output [2:0] RGB1
	);
	
	reg [R+C-2:0] addr;
	always @(posedge CLK, posedge RESET) begin
		if(RESET) begin
			addr <= 0;
		end
		else if(CE) begin
			addr <= addr + 8'b1;
		end
		else begin
			addr <= addr;
		end
	end
	
	
	two_port_ram_64 color_matrix(
		.address_a((WE) ? {col0,row0} : {1'b0, addr}),
		.address_b((WE) ? {col1,row1} : {1'b1, addr}),
		.clock(CLK),
		.data_a(color0),
		.data_b(color1),
		.wren_a(WE),
		.wren_b(1'b0),
		.q_a(RGB0),
		.q_b(RGB1)
	);
endmodule
//////////////////////////////////////////////////////////////////////
// File Downloaded from http://www.nandland.com
//////////////////////////////////////////////////////////////////////
// This file contains the UART Receiver.  This receiver is able to
// receive 8 bits of serial data, one start bit, one stop bit,
// and no parity bit.  When receive is complete o_rx_dv will be
// driven high for one clock cycle.
// 
// Set Parameter CLKS_PER_BIT as follows:
// CLKS_PER_BIT = (Frequency of i_Clock)/(Frequency of UART)
// Example: 10 MHz Clock, 115200 baud UART
// (10000000)/(115200) = 87
 
module uart_rx 
  #(parameter CLKS_PER_BIT)
  (
   input        i_Clock,
   input        i_Rx_Serial,
   output       o_Rx_DV,
   output [7:0] o_Rx_Byte
   );
/////
///... more code
/////
endmodule

UART receiver

For the UART receiver module, I utilized a Verilog file from nandland.com. I parameterized their file according to their instructions taking my clock rate (25MHz) and dividing by the baud rate (1,000,000) to get the “CLKS_PER_BIT” parameter. This parameter is used when the uart_rx module is instantiated in the LED Matrix controller.

LED Matrix Controller

The purpose of the LED Matrix controller is to take the stream of data coming from serial and to write it to the RAM. The serial data stream consists of solely color data (one byte = color for one pixel). The data is written incrementally to the RAM (i.e. The first byte received appears at pixel (0,0) the next at (0,1) and so on).

In order to compensate for the periods when the RAM is being read (and hence busy) data that is received during these periods is stored into a hardware FIFO queue. When the RAM is no longer busy and data isn’t currently being received, then the FIFO is read from, and the data is written to the RAM.

The controller also has a control signal “0xff” that will cause the display to reset its internal address register and purge the FIFO. This control signal can be used every time a new frame is sent via serial to ensure new data is received and written correctly.

module led_matrix_controller #(parameter R=6,parameter C=5)(
	input RESET,
	input CLK,
	input busy,
	input RX,
	output reg WE,
	output reg [R-1:0] row0, 
	output reg [C-1:0] col0, 
	output reg [2:0] color0,
	output reg [R-1:0] row1,
	output reg [C-1:0] col1,
	output reg [2:0] color1
);

wire valid;
wire [7:0] rx_data;
uart_rx #(.CLKS_PER_BIT(25)) serial
(
   .i_Clock(CLK),
   .i_Rx_Serial(RX),
   .o_Rx_DV(valid),
   .o_Rx_Byte(rx_data)
);

wire [15:0] fifo_in_data;
wire [15:0]fifo_out_data;
wire fifo_clr;
wire fifo_empty;
wire rdreq;
wire wrreq;

busy_data_fifo busy_data(
	.clock(CLK),
	.data(fifo_in_data),
	.rdreq(rdreq),
	.sclr(fifo_clr),
	.wrreq(wrreq),
	.empty(fifo_empty),
	.full(),
	.q(fifo_out_data),
	.usedw()
);

assign wrreq = valid && (rx_data != 8'hff) && busy;
assign fifo_in_data = {addr,rx_data[2:0]};
assign rdreq = ~busy && ~valid && ~fifo_empty;
//If we receive the control signal, purge the fifo
assign fifo_clr = valid && (rx_data == 8'hff);


reg [R+C-1:0] addr;
reg write_ram;
reg prev_busy;
reg prev_valid;
always @(posedge CLK) begin 
   prev_busy <= busy;
	prev_valid <= valid;
	//Write RAM if not busy, the data is valid, and not ff signal
	if((~busy) && (valid != prev_valid) && valid && (rx_data != 8'hff)) begin
		WE <= 1;
		row0 <= addr[R-1:0];
		col0 <= addr[R+C-1:R];
		color0 <= rx_data[2:0];
		addr <= addr + 1;
	end
	//If we are busy, we should store the data in FIFO to use latter
	else if(valid && (rx_data != 8'hff)) begin
		addr <= addr + 1; 
	end
	//If the ff control signal was received, reset the address to zero
	else if(valid && (rx_data == 8'hff)) begin
		addr <= 0;
	end
	//If we aren't busy/not receiving data, write FIFO data to RAM
	else if(~busy && !valid && !fifo_empty) begin
		WE <= 1;
		row0 <= fifo_out_data[R+2:3];
		col0 <= fifo_out_data[R+C+2:R+3];
		color0 <= fifo_out_data[2:0];
	end
	else begin
		WE <= 0;
 	end
end

endmodule

The Result

By running a simple python script I can make a cool randomized pattern flash on the screen.

import serial
import random
import time
import sys

ser = serial.Serial('/dev/ttyUSB0', 1000000, timeout = 0) 
while True:
    rand_colors = []
    w, h = 32, 64;
    rand_blocks = [[0 for x in range(w)] for y in range(h)]
    for r in range(0,int(h/4)):
        for c in range(0,int(w/4)):
            r_val = random.randint(0,3)
            if r_val == 0:
                r_val = int(sys.argv[1])
            else:
                r_val = 0
            for off_r in range(0,4):
                for off_c in range(0,4):
                    rand_blocks[(r*4)+off_r][(c*4)+off_c] = r_val
    for i in range(0, w):
        for j in range(0, h):
            rand_colors.append(rand_blocks[j][i])

    rand_colors.insert(0, 255)
    byte_vals = bytearray(rand_colors)
    ser.write(byte_vals)
    time.sleep(0.01)

Leveraging other python libraries such as PIL and numpy, I can make the LED matrix display text as well.

Share this post

Leave a Reply

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