Tweaking the 128×32 LEDDMD Verilog

There was a few problems with the code for the 128×32 LEDDMD. Every so often there was flickering at random spots on the screen. This was because of the way the display memory was updated. I already fixed it by putting a dummy state at the end of the display memory update. Doing so actually increased the speed that data could be sent to the FPGA. There is no waiting between the Latch pulls.

The last problem is that there is some slight ghosting on the screen. Due to the way the screen updates it looks like the Column data is updating slightly before the rows are updated. Because of this you get a ghost effect on the opposite side of the screen that is up one row.

I think the reason why is because there is a slight delay in the rows caused by the decoders. The current plan of attack is to buffer the Column data a couple cycles before outputting.

LEDDMD 128×32 Complete

So I finally finished the prototype of the 128 x 32 Light Emitting Diode Dot Matrix Display V1.0 (LEDDMD). The protocol to write to the display is almost exactly like a shift register. There is a clock, latch, and data lines. It works in either 1 bit mode or 8 bit mode data line modes.

In 1 bit mode there is only 1 data line and in 8 bit mode this is 8 data lines. 8 bit mode enables you to clock in an entire byte of data at a time speeding up the transfer process by a factor of 8. In either mode you can do animations smoothly. All processing of the data is done on the microcontroller. The Display stores the data and takes care of running the display.

The bulk of the hardware is in the FPGA. I am using a Cyclone II EP2C8Q208C8N FPGA breakout board. There are some darlington transistor arrays that sink the current from a single row. To expand the I/O of the FPGA some decoders are used.

Here are the links to all the code:
FPGA:
Main Routine
Memory

Propeller:
Transmission Protocol and Test

128×32 DMD Update

The verilog code is almost fully debugged. The demo code that will run on a Parallax Propeller is in the works. Right now the demo is fairly basic. Today I wrote a C program that takes a 4-bit bitmap image and strips out the header and and converts it to a 2-bit image. It then reorientates the data so the image is “correct”. Bitmap images data reads the image from bottom left to top right. This is essentially backwards. So the program corrects this which means less work for the microcontroller and faster transmission of pixels.

8×8 LED Matrix Modules in 1.9mm size

So for the new 128×32 DMD I am using a smaller dot pitch LED Matrix with a size of 1.9mm. This means they are the same size as standard DMDs used on production machines. I secured a supplier and I can provide any color LED for the DMDs. Red, Orange, Amber, Blue, Green, ect. These are currently not compatible with production machines but are intended for Home Brew Pinball machines. They are very easy to work with.

Instead of the wacky voltages of a standard DMD mine only needs a +5V supply. Standard DMDs also are hard to drive with microcontrollers because you have to fill each “level” individually and you have to do it fast enough to keep up with the refresh rate. My DMD runs off a FPGA which means you don’t have to worry about how fast you send the data to the display. The FPGA takes care of all the matrixing and color levels. All you have to do is send the data in 1byte wide chunks which is perfect for Microcontroller use. If you are doing animations you can stream data off an SD card by doing byte writes and then pushing that data directly to the DMD.

Prices will be $250 for quantities 1-9 and $240 for anything over 10. They come with the SMD parts soldered on and the FPGA pre-programmed. You will need to solder the LED modules on. The LED modules will be tested to ensure that they are in full working order.

More Verilog Code

Sorry for the massive amount of code recently. To test to see if the RAM code works I wrote a program for the 16×96 display in RESET_VECTOR. If this works then I will order the PCB for the 32×128 DMD. If anyone has any suggestions please feel free to comment. This is the first time I have tried interfacing RAM on an FPGA before.


module LED_Matrix_16x96
(
  clk,row,col,SDRAM_CS,SRAM_CS,LAT,INPUT,SCLK
);

input  wire    clk;
input  wire    LAT;
input  wire    INPUT;
input  wire    SCLK;

output  reg [0:15]  row;
output  reg [95:0]  col;
output  reg [0:0]   SDRAM_CS;
output  reg [0:0]   SRAM_CS;

    reg [24:0]  clk_slow;
    reg [4:0]  row_cnt;
    reg [1535:0]  col_buffer;
    
    reg  [3:0]  read_addr;
    reg  [3:0]  write_addr;
    reg  [95:0]  disp_mem_data;
    reg      disp_mem_wren;
    wire  [95:0]  disp_mem_q;
    
    reg [3:0]  sel;

initial 
  begin
    row <= 16'b0000000000000001;
    col <= 16'b0000000000000000;
    SDRAM_CS <= 1'b0;
    SRAM_CS <= 1'b0;
    clk_slow <= 16'b0000000000000000;
    row_cnt <= 5'b00000;
    write_addr <= 4'b0000;
    read_addr <= 4'b0000;
    disp_mem_wren <= 0;
    sel <= 4'b0000;
  end
  
always @(posedge clk) 
begin
  clk_slow <= clk_slow + 1'b1;
  
  if(!LAT & row_cnt == 5'b01111)
  begin
  case(sel)
    4'b0000:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[1535:1440];
      sel <= 4'b0001;
    end
    4'b0001:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[1439:1344];
      sel <= 4'b0010;
    end    
    4'b0010:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[1343:1248];
      sel <= 4'b0011;
    end    
    4'b0011:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[1247:1152];
      sel <= 4'b0100;
    end    
    4'b0100:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[1151:1056];
      sel <= 4'b0101;
    end    
    4'b0101:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[1055:960];
      sel <= 4'b0110;
    end
    4'b0110:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[959:864];
      sel <= 4'b0111;
    end
    4'b0111:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[863:768];
      sel <= 4'b1000;
    end
    4'b1000:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[767:672];
      sel <= 4'b1001;
    end
    4'b1001:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[671:576];
      sel <= 4'b1010;
    end    
    4'b1010:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[575:480];
      sel <= 4'b1011;
    end
    4'b1011:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[479:384];
      sel <= 4'b1100;
    end
    4'b1100:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[383:288];
      sel <= 4'b1101;
    end
    4'b1101:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[287:192];
      sel <= 4'b1110;
    end
    4'b1110:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[191:96];
      sel <= 4'b1111;
    end
    4'b1111:
    begin
      write_addr <= sel;
      disp_mem_wren <= 1'b1;
      disp_mem_data <= col_buffer[95:0];
      sel <= 4'b0000;
    end
    default: 
    begin
      sel <= 4'b0000;
      write_addr <= 4'b0000;
      disp_mem_wren <= 1'b0;
    end
  endcase
  end
  else
  begin
    disp_mem_wren <= 1'b0;
    sel <= 4'b0000;
  end
end

always @(posedge clk_slow[10]) 
begin
  if(row_cnt > 5'b01111)
  begin
    row_cnt <= 5'b00000;
  end
  col <= disp_mem_q;  
  row_cnt <= row_cnt + 1'b1;
  row <= {row[15],row[0:14]};
  read_addr <= read_addr + 1'b1;
  
end
  
always @(posedge SCLK)
begin
  if(LAT)
  begin
    col_buffer <= {col_buffer[1534:0],INPUT};
  end
end

display_ram disp_mem_inst (
        .rdaddress (read_addr),
        .wraddress (write_addr),
        .clock  (clk),
        .data   (disp_mem_data),
        .wren   (disp_mem_wren),
        .q      (disp_mem_q)
);

endmodule

Updated Code for the 128×32 DMD

The old program took to make Logic elements to fit. Now it uses a 2-port RAM element to replace the huge registers. The protocol for shifting in is a bit different now. Instead of shifting in all the data at once and then Latching the user shifts in the data 64 bits at a time then latches.


module LED_Matrix_32x128
(
  clk,e_cnt,row_cnt,col,SDRAM_CS,SRAM_CS,LAT,INPUT,SCLK
);

input  wire  clk;
input  wire  LAT;
input  wire  [7:0]  INPUT;
input  wire  SCLK;

output  reg [63:0]  col;
output  reg [0:0]   SDRAM_CS;
output  reg [0:0]   SRAM_CS;
output  reg [5:0]  row_cnt;
output  reg [4:0]  e_cnt;

    reg [24:0]  clk_slow;
    reg [63:0]  col_buffer;
    
    reg  [5:0]  read_addr;
    reg  [5:0]  write_addr;
    reg  [63:0]  disp_mem_data;
    reg      disp_mem_wren;
    wire  [63:0]  disp_mem_q;
initial 
  begin
    SDRAM_CS <= 1'b0;
    SRAM_CS <= 1'b0;
    clk_slow <= 16'b0000000000000000;
    row_cnt <= 5'b00000;
    e_cnt <= 4'b1000;
    write_addr <= 5'b00000;
    disp_mem_wren <= 0;
  end

always @(posedge clk) 
begin
  clk_slow <= clk_slow + 1'b1;
end

always @(posedge clk_slow[10]) 
begin
  if(row_cnt > 5'b01111)
  begin
    row_cnt <= 5'b00000;
    e_cnt <= {e_cnt[2:0],e_cnt[3]};
  end

  col <= disp_mem_q;  
  row_cnt <= row_cnt + 1'b1;
  read_addr <= read_addr + 1'b1;
  
end
  
always @(posedge SCLK)
begin
  if(LAT)
  begin
    col_buffer <= {col_buffer[62:0],INPUT[0]};
  end
  else
  begin
    disp_mem_data <= col_buffer;
    disp_mem_wren <= 1;
    disp_mem_wren <= 0;
    write_addr <= write_addr + 1'b1;
  end
end

display_memory disp_mem_inst (
        .rdaddress (read_addr),
        .wraddress (write_addr),
        .clock  (clk),
        .data   (disp_mem_data),
        .wren   (disp_mem_wren),
        .q      (disp_mem_q)
);

endmodule

128×32 DMD Verilog Code

Here is some Verilog code for a 32×128 DMD I am designing.


module LED_Matrix_32x128
(
  clk,e_cnt,row_cnt,col,SDRAM_CS,SRAM_CS,LAT,DATA,SCLK
);

input  wire    clk;
input  wire    LAT;
input  wire    DATA;
input  wire    SCLK;

output  reg [63:0]  col;
output  reg [0:0]   SDRAM_CS;
output  reg [0:0]   SRAM_CS;
output  reg [5:0]  row_cnt;
output  reg [4:0]  e_cnt;

    reg [24:0]  clk_slow;
    reg [4096:0]  col_buffer;
    reg [63:0]  col_temp [63:0];

initial 
  begin
    col <= 16'b0000000000000000;
    SDRAM_CS <= 1'b0;
    SRAM_CS <= 1'b0;
    clk_slow <= 16'b0000000000000000;
    row_cnt <= 5'b00000;
    e_cnt <= 4'b1000;
  end
  
always @(posedge clk) clk_slow=clk_slow + 1'b1;  

always @(posedge clk_slow[10]) 
begin
  if(row_cnt > 5'b01111)
  begin
    row_cnt <= 5'b00000;
    e_cnt <= {row[2:0],row[3]};
  end
  col <= col_temp[row_cnt];  
  row_cnt <= row_cnt + 1'b1;
end
  
always @(posedge SCLK)
begin
  if(LAT)
  begin
    col_buffer <= {col_buffer[4094:0],DATA[0]};
  end
  else
  begin
    col_temp [0] <= col_buffer [4095:4032];
    col_temp [1] <= col_buffer [4031:3968];
    col_temp [2] <= col_buffer [3967:3904];
    col_temp [3] <= col_buffer [3903:3840];
    col_temp [4] <= col_buffer [3839:3776];
    col_temp [5] <= col_buffer [3775:3712];
    col_temp [6] <= col_buffer [3711:3648];
    col_temp [7] <= col_buffer [3647:3584];
    col_temp [8] <= col_buffer [3583:3520];
    col_temp [9] <= col_buffer [3519:3456];
    col_temp [10] <= col_buffer [3455:3392];
    col_temp [11] <= col_buffer [3391:3328];
    col_temp [12] <= col_buffer [3327:3264];
    col_temp [13] <= col_buffer [3263:3200];
    col_temp [14] <= col_buffer [3199:3136];
    col_temp [15] <= col_buffer [3135:3072];
    col_temp [16] <= col_buffer [3071:3008];
    col_temp [17] <= col_buffer [3007:2944];
    col_temp [18] <= col_buffer [2943:2880];
    col_temp [19] <= col_buffer [2879:2816];
    col_temp [20] <= col_buffer [2815:2752];
    col_temp [21] <= col_buffer [2751:2688];
    col_temp [22] <= col_buffer [2687:2624];
    col_temp [23] <= col_buffer [2623:2560];
    col_temp [24] <= col_buffer [2559:2496];
    col_temp [25] <= col_buffer [2495:2432];
    col_temp [26] <= col_buffer [2431:2368];
    col_temp [27] <= col_buffer [2367:2304];
    col_temp [28] <= col_buffer [2303:2240];
    col_temp [29] <= col_buffer [2239:2176];
    col_temp [30] <= col_buffer [2175:2112];
    col_temp [31] <= col_buffer [2111:2048];
    col_temp [32] <= col_buffer [2047:1984];
    col_temp [33] <= col_buffer [1983:1920];
    col_temp [34] <= col_buffer [1919:1856];
    col_temp [35] <= col_buffer [1855:1792];
    col_temp [36] <= col_buffer [1791:1728];
    col_temp [37] <= col_buffer [1727:1664];
    col_temp [38] <= col_buffer [1663:1600];
    col_temp [39] <= col_buffer [1599:1536];
    col_temp [40] <= col_buffer [1535:1472];
    col_temp [41] <= col_buffer [1471:1408];
    col_temp [42] <= col_buffer [1407:1344];
    col_temp [43] <= col_buffer [1343:1280];
    col_temp [44] <= col_buffer [1279:1216];
    col_temp [45] <= col_buffer [1215:1152];
    col_temp [46] <= col_buffer [1151:1088];
    col_temp [47] <= col_buffer [1087:1024];
    col_temp [48] <= col_buffer [1023:960];
    col_temp [49] <= col_buffer [959:896];
    col_temp [50] <= col_buffer [895:832];
    col_temp [51] <= col_buffer [831:768];
    col_temp [52] <= col_buffer [767:704];
    col_temp [53] <= col_buffer [703:640];
    col_temp [54] <= col_buffer [639:576];
    col_temp [55] <= col_buffer [575:512];
    col_temp [56] <= col_buffer [511:448];
    col_temp [57] <= col_buffer [447:384];
    col_temp [58] <= col_buffer [383:320];
    col_temp [59] <= col_buffer [319:256];
    col_temp [60] <= col_buffer [255:192];
    col_temp [61] <= col_buffer [191:128];
    col_temp [62] <= col_buffer [127:64];
    col_temp [63] <= col_buffer [63:0];
  end
end
endmodule