FPGA通过SPI驱动0.96寸OLED小屏幕,按键切换OLED显示内容,分别是全白和条纹
SSD1306驱动芯片:
SSD1306 是0.96OLED的驱动芯片,数据/命令的发送有三种接口可选择:6800/8000 串口,I2C 接口或 SPI 接口。这里采用的是七针OLED屏幕,通过SPI发送数据/命令的方式。
虽然SPI是全双工的,但是oled模块不会向主机写数据,因此在SPI的数据传输中,只需要两根线就可以了,及MOSI和CLK。
OLED模块接口定义
GND | VCC | D0 | D1 | RES | D/C | CS |
电源地 | 电源(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