FPGA驱动七针OLED屏幕demo

FPGA通过SPI驱动0.96寸OLED小屏幕,按键切换OLED显示内容,分别是全白和条纹

SSD1306驱动芯片:

SSD1306 是0.96OLED的驱动芯片,数据/命令的发送有三种接口可选择:6800/8000 串口,I2C 接口或 SPI 接口。这里采用的是七针OLED屏幕,通过SPI发送数据/命令的方式。

虽然SPI是全双工的,但是oled模块不会向主机写数据,因此在SPI的数据传输中,只需要两根线就可以了,及MOSI和CLK。

OLED模块接口定义

GNDVCCD0D1RESD/CCS
电源地电源(3~5V)在SPI驱动方式中为时钟线在SPI驱动方式中为数据线复位引脚数据/命令控制引脚,0为命令,1为数据片选信号

FPGA实现SPI主站发送:

因为OLED不会发送数据给控制器,因此只需要写SPI的发送端即可

`timescale 1ns / 1ps


// 这个模块实现了以下功能:

// 在IDLE状态下等待start信号。
// 当start信号有效时,进入START状态,准备开始发送数据。
// 在TRANSFER状态下,通过sclk和mosi信号逐位发送数据。
// 发送完8位数据后,进入STOP状态,拉高cs_n信号,并设置done信号为高,表示发送完成。

module SPI_send_driver(
    input wire clk,           // 系统时钟
    input wire rst_n,         // 复位信号,低电平有效
    input wire [7:0] data_in, // 要发送的数据
    input wire start,         // 开始发送信号
    output reg sclk,          // SPI时钟
    output reg mosi,          // 主输出从输入
    output reg cs_n,          // 片选信号,低电平有效
    output reg done          // 低电平SPI发送中,高电平为空闲
    );

reg [2:0] state;         // 状态机状态
reg [2:0] bit_cnt;       // 位计数器
reg [7:0] shift_reg;     // 移位寄存器

localparam IDLE = 3'b000;
localparam START = 3'b001;
localparam TRANSFER = 3'b010;
localparam STOP = 3'b011;


always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        state <= IDLE;
        sclk <= 1'b1;
        mosi <= 1'b0;
        cs_n <= 1'b1;
        done <= 1'b1;
        bit_cnt <= 'd0;
        shift_reg <= 8'b00000000;
    end
    else begin
        case(state) 
            IDLE: begin
                if(start) begin
                    done <= 1'b0;
                    shift_reg <= data_in;
                    cs_n <= 1'b0;
                    state <= START;
                end
            end
            START: begin
                sclk <= 1'b0;
                mosi <= shift_reg[7];
                shift_reg <= {shift_reg[6:0], 1'b0};
                bit_cnt <= 3'b000;
                state <= TRANSFER;
            end
            TRANSFER: begin
                if(bit_cnt < 'd7) begin
                    sclk <= ~sclk;
                    if(sclk) begin
                        bit_cnt <= bit_cnt + 1;
                        shift_reg <= {shift_reg[6:0], 1'b0};
                        mosi <= shift_reg[7];
                    end
                end
                else begin
                    state <= STOP;
                    sclk <= 1'b1;
                end
            end
            STOP: begin
                cs_n <= 1'b1;
                done <= 1'b1;
                state <= IDLE;
            end
            default: begin
                state <= IDLE;
            end
        endcase
    end
end

endmodule

接下来就是使用这个模块完成OLED的指令和数据的发送,补充完成CS和DC信号的输出

`timescale 1ns / 1ps


module test_OLED(
    input            sys_clk            ,
    input            sys_rst_n          ,
    input            key0               ,

    output  reg      led0               ,
    //OLED_spi接口
    output  wire     oled_clk           ,               //spi时钟
    output  reg      oled_dc            ,               //表明发的是命令还是数据,0命令/1数据
    output  wire     oled_MOSI          ,               //spi_MOSI
    output  reg      oled_rst           ,               //spi_MOSI
    output  wire     oled_cs                            //SPI_CS片选,低电平有效
    );

/***********************reg变量声明*********************************/   
//按键相关
reg     [19:0]          key0_cnt         ;
reg                     key0_reg         ;

//指令数组
reg     [7:0]           data_write [51:0];    
//显示输出缓冲数组
reg     [7:0]          diaplay_buffer[7:0][127:0];   

//当前输入计数
reg     [11:0]          data_cmd_cnt     ;

reg                     work_flag        ;
reg     [15:0]          cmd_cnt          ;
reg     [11:0]          data_cnt         ;
reg     [2:0]           data_row_cnt     ;
reg                     start            ;
reg                     start_reg        ;


/***********************wire变量声明*********************************/

wire done;
wire [7:0] data_in;

/*******************assign语句*********************/
assign data_in = !oled_dc ? data_write[cmd_cnt] : diaplay_buffer[data_row_cnt][data_cnt];
assign start_posedge = !start_reg &&start;


//OLED初始化
//OLED初始化指令
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        data_write[0] <= 8'hAE;//--turn off oled panel  
        data_write[1] <= 8'h02;//---set low column address
        data_write[2] <= 8'h10;//---set high column address
        data_write[3] <= 8'h40;//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
        data_write[4] <= 8'h81;//--set contrast control register
        data_write[5] <= 8'hCF;//--Set SEG Output Current Brightness
        data_write[6] <= 8'hA1;//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
        data_write[7] <= 8'hC8;//--Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
        data_write[8] <= 8'hA6;//--set normal display
        data_write[9] <= 8'hA8;//--set multiplex ratio(1 to 64)
        data_write[10] <= 8'h3F;//--1/64 duty
        data_write[11] <= 8'hD3;//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
        data_write[12] <= 8'h00;//-not offset
        data_write[13] <= 8'hD5;//--set display clock divide ratio/oscillator frequency
        data_write[14] <= 8'h80;//--set divide ratio, Set Clock as 100 Frames/Sec
        data_write[15] <= 8'hD9;//--set pre-charge period
        data_write[16] <= 8'hF1;//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
        data_write[17] <= 8'hDA;//--set com pins hardware configuration
        data_write[18] <= 8'h12;//
        data_write[19] <= 8'hDB;//--set vcomh
        data_write[20] <= 8'h40;//--Set VCOM Deselect Level
        data_write[21] <= 8'h20;//--Set Page Addressing Mode (0x00/0x01/0x02)
        data_write[22] <= 8'h02;//
        data_write[23] <= 8'h8D;//--set Charge Pump enable/disable
        data_write[24] <= 8'h14;//--set(0x10) disable
        data_write[25] <= 8'hA4;// Disable Entire Display On (0xa4/0xa5)
        data_write[26] <= 8'hA6;// Disable Inverse Display On (0xa6/a7) 
        data_write[27] <= 8'hAF;
        /*******  设置初始坐标点为0,0  ********/
        data_write[28] <= 8'hB0;//set y=0 行为0
        data_write[29] <= 8'h10;//set x=0
        data_write[30] <= 8'h00;//确认命令
        data_write[31] <= 8'hB1;//set y=1 行为1
        data_write[32] <= 8'h10;//set x=0
        data_write[33] <= 8'h00;//确认命令
        data_write[34] <= 8'hB2;
        data_write[35] <= 8'h10;
        data_write[36] <= 8'h00;
        data_write[37] <= 8'hB3;
        data_write[38] <= 8'h10;
        data_write[39] <= 8'h00;
        data_write[40] <= 8'hB4;
        data_write[41] <= 8'h10;
        data_write[42] <= 8'h00;
        data_write[43] <= 8'hB5;
        data_write[44] <= 8'h10;
        data_write[45] <= 8'h00;
        data_write[46] <= 8'hB6;
        data_write[47] <= 8'h10;
        data_write[48] <= 8'h00;
        data_write[49] <= 8'hB7;
        data_write[50] <= 8'h10;
        data_write[51] <= 8'h00;
    end
end

//显存数组初始化
integer i;
integer j;
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[0][i] <= 'hff;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[1][i] <= 'h00;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[2][i] <= 'hff;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[3][i] <= 'h00;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[4][i] <= 'hff;   
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[5][i] <= 'h00;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[6][i] <= 'hff;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[7][i] <= 'hff;
        end
    end
    else if (led0 == 'd1) begin
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[0][i] <= 'hff;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[1][i] <= 'h00;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[2][i] <= 'hff;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[3][i] <= 'h00;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[4][i] <= 'hff;   
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[5][i] <= 'h00;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[6][i] <= 'hff;
        end
        for(i=0;i<128;i=i+1) begin
            diaplay_buffer[7][i] <= 'hff;
        end
    end
    else begin
        for(j=0;j<8;j=j+1) begin
            for(i=0;i<128;i=i+1) begin
                diaplay_buffer[j][i] <= 'hff;
            end
        end
    end
end

/******************按键相关信号*********************************/
//按键消抖计数
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        key0_cnt <= 'd0;
    end
    else if (!key0) begin
        if(key0_cnt > 'd1000000) begin
            key0_cnt <= key0_cnt;
        end
        else begin
          key0_cnt <= key0_cnt + 'd1;  
        end
    end
    else begin
        key0_cnt <= 'd0;
    end
end
//按键信号获取
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        key0_reg <= 'd0;
    end
    else if (key0_cnt == 'd10000) begin
        key0_reg <= 'd1;
    end
    else begin
        key0_reg <= 'd0;
    end
end


/******************LED相关信号**********/
//led信号输出
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        led0 <= 'd0;
    end
    else if (key0_reg) begin
        led0 <= ~led0;
    end
end

/******************OLED相关*******************************/
//开始工作标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        work_flag <= 'd0;
    end
    else if (key0_reg) begin
        work_flag <= 'd1;
    end
    else if (data_cmd_cnt == 'd1075) begin
        work_flag <= 'd0;
    end
end

//输入有效计数
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        data_cmd_cnt <= 'd0;
    end
    else if (work_flag) begin
        if (start_posedge) begin
            data_cmd_cnt <= data_cmd_cnt + 'd1;
        end
        else begin
            data_cmd_cnt <= data_cmd_cnt;
        end
    end
    else begin
        data_cmd_cnt <= 'd0;
    end
end

always @(posedge sys_clk) begin
    start_reg <= start;
end

//数据有效信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        start <= 'd0;
    end
    else if (work_flag && done) begin
        start <= 'd1;
    end
    else begin
        start <='d0;
    end
end

//命令计数
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cmd_cnt <= 'd0;
    end
    else if (work_flag) begin
        if (start_posedge && !oled_dc) begin
            if(cmd_cnt == 'd51) begin
                cmd_cnt <= cmd_cnt;
            end
            else begin
                cmd_cnt <= cmd_cnt + 'd1;
            end
        end
        else begin
            cmd_cnt <= cmd_cnt;
        end
    end
    else begin
        cmd_cnt <= 0;
    end
end

//显存数组列计数
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        data_cnt <= 'd0;
    end
    else if(work_flag) begin
        if (oled_dc && start_posedge) begin
            if(data_cnt == 'd127) begin
                data_cnt <= 'd0;
            end
            else begin
                data_cnt <= data_cnt + 'd1;
            end
        end
        else begin
            data_cnt <= data_cnt;
        end
    end
    else begin
        data_cnt <= 'd0;
    end
end

//显存数组行计数
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        data_row_cnt <= 'd0;
    end
    else if(work_flag) begin
        if (data_cnt == 'd127 && start_posedge) begin
            data_row_cnt <= data_row_cnt + 'd1; 
        end
        else begin
            data_row_cnt <= data_row_cnt;
        end
    end
    else begin
        data_row_cnt <= 'd0;
    end
end

//dc信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd31 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd159 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd162 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd290 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd293 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd421 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd424 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd552 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd555 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd683 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd686 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd814 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd817 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd945 && done) begin
        oled_dc <= 'd1;
    end
    else if (data_cmd_cnt < 'd948 && done) begin
        oled_dc <= 'd0;
    end
    else if (data_cmd_cnt < 'd1076 && done) begin
        oled_dc <= 'd1;
    end
    else begin
        oled_dc <= oled_dc;
    end
end

//OLED复位信号输出
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        oled_rst <= 'd1;
    end
    else if (key0_reg) begin
        oled_rst <= 'd0;
    end
    else begin
        oled_rst <= 'd1;
    end
end


SPI_send_driver inst(
    .clk      (sys_clk  )  ,         // 系统时钟
    .rst_n    (sys_rst_n)  ,         // 复位信号,低电平有效
    .data_in  (data_in  )  ,         // 要发送的数据
    .start    (start    )  ,         // 开始发送信号
    .sclk     (oled_clk )  ,         // SPI时钟
    .mosi     (oled_MOSI)  ,         // 主输出从输入
    .cs_n     (oled_cs  )  ,         // 片选信号,低电平有效
    .done     (done     )            // 低电平SPI发送中,高电平为空闲
);


endmodule

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的Verilog代码,用于在FPGA上实现SPI驱动OLED屏幕: ```verilog module spi_oled( input wire clk, //时钟信号 input wire rst, //复位信号 output wire cs, //片选信号 output wire dc, //数据/命令选择信号 output wire sda, //数据信号 output wire sck //时钟信号 ); //定义寄存器 reg [7:0] command_reg; reg [7:0] data_reg; //状态机 reg [2:0] state; parameter [2:0] IDLE = 3'b000; parameter [2:0] COMMAND = 3'b001; parameter [2:0] DATA = 3'b010; //时钟分频计数器 reg [7:0] cnt; parameter [7:0] CLK_DIV = 8'hFF; //时钟分频器 parameter [7:0] CMD_DELAY = 8'h0F; //命令延迟 parameter [7:0] DATA_DELAY = 8'h0F; //数据延迟 //初始化状态和计数器 initial begin state = IDLE; cnt = 0; end //状态机逻辑 always @(posedge clk) begin if (rst) begin state <= IDLE; cnt <= 0; end else begin case (state) IDLE: begin cs <= 1'b1; dc <= 1'b0; sda <= 1'b1; sck <= 1'b1; if (cnt == CLK_DIV) begin //进入命令状态 state <= COMMAND; cnt <= 0; end else begin cnt <= cnt + 1; end end COMMAND: begin cs <= 1'b0; dc <= 1'b0; sda <= command_reg[7]; command_reg <= {command_reg[6:0], 1'b0}; sck <= 1'b0; if (cnt == CMD_DELAY) begin //进入数据状态 state <= DATA; cnt <= 0; end else begin cnt <= cnt + 1; end end DATA: begin cs <= 1'b0; dc <= 1'b1; sda <= data_reg[7]; data_reg <= {data_reg[6:0], 1'b0}; sck <= 1'b0; if (cnt == DATA_DELAY) begin //返回空闲状态 state <= IDLE; cnt <= 0; end else begin cnt <= cnt + 1; end end endcase end end endmodule ``` 这个代码中的状态机逻辑控制SPI通信以向OLED屏幕发送命令和数据。您需要根据OLED屏幕的规格和数据手册定制代码,以确保正确配置SPI通信和正确发送命令和数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值