// femtorv32, a minimalistic RISC-V RV32I core // Bruno Levy, 2020-2021 // // This file: driver for SSD1351 and SSD1331 OLED display // Reference: https://www.crystalfontz.com/controllers/SolomonSystech/SSD1351/ // // TODO: we could use wmask to write directly 16 bits or 32 bits of data // (we could even have a 'fast clear' option that writes a number // of zeroes). `ifdef NRV_IO_SSD1331 `define NRV_IO_SSD1351_1331 `endif `ifdef NRV_IO_SSD1351 `define NRV_IO_SSD1351_1331 `endif module SSD1351_clk #( parameter width=1 )( input wire clk, // input system clock output wire CLK, // SSD1351 clock output wire CLK_falling_edge // pulses at each falling edge of CLK ); reg [width-1:0] slow_cnt; always @(posedge clk) begin slow_cnt <= slow_cnt + 1; end assign CLK = slow_cnt[width-1]; assign CLK_falling_edge = (slow_cnt == (1 << width)-1); endmodule module SSD1351( input wire clk, // system clock input wire wstrb, // write strobe (use one of sel_xxx to select dest) input wire sel_cntl, // wdata[0]: !CS; wdata[1]: RST input wire sel_cmd, // send 8-bits command to display input wire sel_dat, // send 8-bits data to display input wire sel_dat16, // send 16-bits data to display input wire [31:0] wdata, // data to be written output wire wbusy, // asserted if the driver is busy sending data // SSD1351 pins output DIN, // data in output CLK, // clock output reg CS, // chip select (active low) output reg DC, // data (high) / command (low) output reg RST // reset (active low) ); initial begin DC = 1'b0; RST = 1'b0; CS = 1'b1; end /********* The clock ****************************************************/ // Note: SSD1351 expects the raising edges of the clock in the middle of // the data bits. // TODO: try to have a 'waveform' instead, that is shifted (simpler and // more elegant). // Page 52 of the doc: 4-wire SPI timing: // Unclear what 'Clock Cycle Time' (220 ns) means, // Clock Low Time (20ns) + Clock High Time (20ns) = 40ns // max freq = 1/(40ns) = 25 MHz // experimentally, seems to work up to 30 Mhz (but not more) wire CLK_falling_edge; generate if(`NRV_FREQ <= 60) begin // Divide by 2-> 30 MHz SSD1351_clk #( .width(1) )slow_clk( .clk(clk), .CLK(CLK), .CLK_falling_edge(CLK_falling_edge) ); end else if(`NRV_FREQ <= 120) begin // Divide by 4 SSD1351_clk #( .width(2) )slow_clk( .clk(clk), .CLK(CLK), .CLK_falling_edge(CLK_falling_edge) ); end else begin // Divide by 8 SSD1351_clk #( .width(3) )slow_clk( .clk(clk), .CLK(CLK), .CLK_falling_edge(CLK_falling_edge) ); end endgenerate // Currently sent bit, 1-based index // (0000 config. corresponds to idle) reg[4:0] bitcount = 5'b0000; reg[15:0] shifter = 0; wire sending = (bitcount != 0); assign DIN = shifter[15]; assign wbusy = sending; /*************************************************************************/ always @(posedge clk) begin if(wstrb) begin if(sel_cntl) begin CS <= !wdata[0]; RST <= wdata[1]; end if(sel_cmd) begin RST <= 1'b1; DC <= 1'b0; shifter <= {wdata[7:0],8'b0}; bitcount <= 8; CS <= 1'b1; end if(sel_dat) begin RST <= 1'b1; DC <= 1'b1; shifter <= {wdata[7:0],8'b0}; bitcount <= 8; CS <= 1'b1; end if(sel_dat16) begin RST <= 1'b1; DC <= 1'b1; shifter <= wdata[15:0]; bitcount <= 16; CS <= 1'b1; end end else begin // detect falling edge of slow_clk if(CLK_falling_edge) begin if(sending) begin if(CS) begin // first tick activates CS (low) CS <= 1'b0; end else begin // shift on falling edge bitcount <= bitcount - 5'd1; shifter <= {shifter[14:0], 1'b0}; end end else begin // last tick deactivates CS (high) CS <= 1'b1; end end end end endmodule