FPGA驱动OLED Verilog代码 (二)------ OLED初始化

一、概述:

        逐字节发送初始化命令给OLED,设置OLED的一些寄存器。当然,我自己肯定也没有去研究过应该发哪些命令(毕竟用32的使用都是直接copy的代码),然后现在也是参照之前32驱动OLED的初始化命令来实现OLED的初始化。

        先贴一份32的代码(大伙也可以自己去改编为Verilog来练练手)

void OLED_Init(void)
{
	OLED_SPI_Init();
	OLED_CLK = 1;
	OLED_RST = 0;
	OLED_DLY_ms(100);
	OLED_RST = 1;

	//从上电到下面开始初始化要有足够的时间,即等待RC复位完毕
	WriteCmd(0xAE);	 		// Display Off (0x00)
	WriteCmd(0xD5);
	WriteCmd(0x80);		    // Set Clock as 100 Frames/Sec
	WriteCmd(0xA8);
	WriteCmd(0x3F);	        // 1/64 Duty (0x0F~0x3F)
	WriteCmd(0xD3);
	WriteCmd(0x00);		    // Shift Mapping RAM Counter (0x00~0x3F)
	WriteCmd(0x40 | 0x00);  // Set Mapping RAM Display Start Line (0x00~0x3F)
	WriteCmd(0x8D);
	WriteCmd(0x10 | 0x04);	// Enable Embedded DC/DC Converter (0x00/0x04)
	WriteCmd(0x20);
	WriteCmd(0x02);		    // Set Page Addressing Mode (0x00/0x01/0x02)
	WriteCmd(0xA0 | 0x01);  // Set SEG/Column Mapping    
	WriteCmd(0xC0);  // Set COM/x Scan Direction   
	WriteCmd(0xDA);
	WriteCmd(0x02 | 0x10);  // Set Sequential Configuration (0x00/0x10)
	WriteCmd(0x81);
	WriteCmd(0xCF);	        // Set SEG Output Current
	WriteCmd(0xD9);
	WriteCmd(0xF1);         // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	WriteCmd(0xDB);
	WriteCmd(0x40);	        // Set VCOM Deselect Level
	WriteCmd(0xA4 | 0x00);	// Disable Entire Display On (0x00/0x01)
	WriteCmd(0xA6 | 0x00);	// Disable Inverse Display On (0x00/0x01)
	WriteCmd(0xAE | 0x01);  // Display On (0x01)

	OLED_Clear();  //初始清屏	
}

/*************************************************************************/
/*函数功能: 将OLED从休眠中唤醒                                            */
/*************************************************************************/
void OLED_ON(void)
{
	WriteCmd(0X8D);  //设置电荷泵
	WriteCmd(0X14);  //开启电荷泵
	WriteCmd(0XAF);  //OLED唤醒
}

/*************************************************************************/
/*函数功能: 更新显存到OLED                                                 */
/*************************************************************************/
void OLED_Refresh_Gram(void)
{
	u8 i,n;		    
	for(i=0;i<8;i++)  
	{  
		WriteCmd(0xb0+i);   //设置页地址(0~7)
		WriteCmd(0x00);      //设置显示位置—列低地址
		WriteCmd(0x10);      //设置显示位置—列高地址   
		for(n=0;n<128;n++)WriteData(OLED_GRAM[n][i]); 
	}   
}

二、Verilog代码编写        

        将用到的命令先用reg型变量寄存起来

//初始化命令
//这个初始化的点更密集
initial begin	
	init_cmd[0] = 8'hAE;				init_cmd[1] = 8'hD5;				init_cmd[2] = 8'h80;				init_cmd[3] = 8'hA8;
	init_cmd[4] = 8'h3F;				init_cmd[5] = 8'hD3;				init_cmd[6] = 8'h00;				init_cmd[7] = 8'h40;
	init_cmd[8] = 8'h8D;				init_cmd[9] = 8'h10|8'h04;		init_cmd[10] = 8'h20;			init_cmd[11] = 8'h02;
	init_cmd[12] = 8'hA0|8'h01;	init_cmd[13] = 8'hC0;			init_cmd[14] = 8'hDA;			init_cmd[15] = 8'h02|8'h10;
	init_cmd[16] = 8'h81;			init_cmd[17] = 8'hCF;			init_cmd[18] = 8'hD9;			init_cmd[19] = 8'hF1;
	init_cmd[20] = 8'hDB;			init_cmd[21] = 8'h40;			init_cmd[22] = 8'hA4|8'h00;	init_cmd[23] = 8'hA6|8'h00;
	init_cmd[24] = 8'hAE|8'h01;
end

//oled开命令
initial begin
	oled_on_cmd[0] = 8'h8D;oled_on_cmd[1] = 8'h14;oled_on_cmd[2] = 8'hAF;
end

//oled清零命令
//也就是设置页地址,设置显示的低地址和设置显示的高地址
initial begin
	clear_cmd[0] = 8'hB0;clear_cmd[1] = 8'h00;clear_cmd[2] = 8'h10;//第0页
	clear_cmd[3] = 8'hB1;clear_cmd[4] = 8'h00;clear_cmd[5] = 8'h10;//第1页
	clear_cmd[6] = 8'hB2;clear_cmd[7] = 8'h00;clear_cmd[8] = 8'h10;//第2页
	clear_cmd[9] = 8'hB3;clear_cmd[10] = 8'h00;clear_cmd[11] = 8'h10;//第3页
	clear_cmd[12] = 8'hB4;clear_cmd[13] = 8'h00;clear_cmd[14] = 8'h10;//第4页
	clear_cmd[15] = 8'hB5;clear_cmd[16] = 8'h00;clear_cmd[17] = 8'h10;//第5页
	clear_cmd[18] = 8'hB6;clear_cmd[19] = 8'h00;clear_cmd[20] = 8'h10;//第6页
	clear_cmd[21] = 8'hB7;clear_cmd[22] = 8'h00;clear_cmd[23] = 8'h10;//第7页
end

        然后接下来就是写状态机进行状态转换了,大概的逻辑就是,有一个状态来为spi写的数据data赋值,还有一个状态用来检测当前状态的命令是否写完了,写完则跳转到下一个状态,否则就继续写下一个数据,当然得等待spi把上一个数据写完,产生done信号后再写下一个数据


OLED初始化的全部代码

module oled_init(
	input clk,					//时钟信号 1m的时钟
	input rst_n,				//复位信号
	input write_done,			//spi写完成信号 获得该信号后开启下一次写
	output reg oled_rst,		//oled的复位引脚信号
	output reg oled_dc,		//oled的dc写数据 写命令控制信号
	output reg [7:0] data,	//输出数据用于spi中写入数据
	output reg ena_write,	//spi写使能信号
	output init_done			//初始化完成信号
);

reg [20:0] us_cnt;			//us计数器 上电延时等待
reg us_cnt_clr;				//计数器清零信号
parameter RST_NUM = 10;		//1000_000 //等待1s
//状态说明
//复位状态 初始化写命令状态 oled开写命令状态 oled显示清零写命令状态 oled显示清零写数据状态
//等待初始化写命令完成 等待oled开写命令完成 等待清零写命令完成 等待清零写数据完成
parameter Rst=0,Init=1,OledOn=2,ClearCmd=3,ClearData=4,WaitInit=5,WaitOn=6,WaitClearCmd=7,WaitClearData=8,Done=9;

reg[3:0] state,next_state;//状态机的当前状态和下一个状态


	
reg [7:0] init_cmd[27:0];	//初始化命令存储
reg [4:0] init_cmd_cnt;		//初始化命令计数

reg [7:0] oled_on_cmd[2:0];//oled开命令存储
reg [1:0] oled_on_cmd_cnt;	//oled开命令计数

reg [7:0] clear_cmd[24:0];	//清零命令存储
reg [4:0] clear_cmd_cnt;	//清零命令计数

reg [10:0] clear_data_cnt;	//清零写数据计数


//初始化命令
//这个初始化的点更密集
initial begin	
	init_cmd[0] = 8'hAE;				init_cmd[1] = 8'hD5;				init_cmd[2] = 8'h80;				init_cmd[3] = 8'hA8;
	init_cmd[4] = 8'h3F;				init_cmd[5] = 8'hD3;				init_cmd[6] = 8'h00;				init_cmd[7] = 8'h40;
	init_cmd[8] = 8'h8D;				init_cmd[9] = 8'h10|8'h04;		init_cmd[10] = 8'h20;			init_cmd[11] = 8'h02;
	init_cmd[12] = 8'hA0|8'h01;	init_cmd[13] = 8'hC0;			init_cmd[14] = 8'hDA;			init_cmd[15] = 8'h02|8'h10;
	init_cmd[16] = 8'h81;			init_cmd[17] = 8'hCF;			init_cmd[18] = 8'hD9;			init_cmd[19] = 8'hF1;
	init_cmd[20] = 8'hDB;			init_cmd[21] = 8'h40;			init_cmd[22] = 8'hA4|8'h00;	init_cmd[23] = 8'hA6|8'h00;
	init_cmd[24] = 8'hAE|8'h01;
end

/*
//初始化命令
//这个初始化出来的点比较稀疏
//应该是分辨率的设置不同把(猜测)
initial begin
	init_cmd[0] = 8'hAE;	init_cmd[1] = 8'h00;	init_cmd[2] = 8'h10;	init_cmd[3] = 8'h00;
	init_cmd[4] = 8'hB0;	init_cmd[5] = 8'h81;	init_cmd[6] = 8'hFF;	init_cmd[7] = 8'hA1;
	init_cmd[8] = 8'hA6;	init_cmd[9] = 8'hA8;	init_cmd[10] = 8'h1F;init_cmd[11] = 8'hC8;
	init_cmd[12] = 8'hD3;init_cmd[13] = 8'h00;init_cmd[14] = 8'hD5;init_cmd[15] = 8'h80;
	init_cmd[16] = 8'hD9;init_cmd[17] = 8'h1f;init_cmd[18] = 8'hD9;init_cmd[19] = 8'hF1;
	init_cmd[20] = 8'hDA;init_cmd[21] = 8'h00;init_cmd[22] = 8'hDB;init_cmd[23] = 8'h40;
end
*/

//oled开命令
initial begin
	oled_on_cmd[0] = 8'h8D;oled_on_cmd[1] = 8'h14;oled_on_cmd[2] = 8'hAF;
end

//oled清零命令
//也就是设置页地址,设置显示的低地址和设置显示的高地址
initial begin
	clear_cmd[0] = 8'hB0;clear_cmd[1] = 8'h00;clear_cmd[2] = 8'h10;//第0页
	clear_cmd[3] = 8'hB1;clear_cmd[4] = 8'h00;clear_cmd[5] = 8'h10;//第1页
	clear_cmd[6] = 8'hB2;clear_cmd[7] = 8'h00;clear_cmd[8] = 8'h10;//第2页
	clear_cmd[9] = 8'hB3;clear_cmd[10] = 8'h00;clear_cmd[11] = 8'h10;//第3页
	clear_cmd[12] = 8'hB4;clear_cmd[13] = 8'h00;clear_cmd[14] = 8'h10;//第4页
	clear_cmd[15] = 8'hB5;clear_cmd[16] = 8'h00;clear_cmd[17] = 8'h10;//第5页
	clear_cmd[18] = 8'hB6;clear_cmd[19] = 8'h00;clear_cmd[20] = 8'h10;//第6页
	clear_cmd[21] = 8'hB7;clear_cmd[22] = 8'h00;clear_cmd[23] = 8'h10;//第7页
end


//1微秒计数器
always @ (posedge clk or negedge rst_n) begin
    if (!rst_n)
        us_cnt <= 21'd0;
    else if (us_cnt_clr)
        us_cnt <= 21'd0;
    else 
        us_cnt <= us_cnt + 1'b1;
end 

//有一个故事告诉我们 always(*)别乱用(心酸)
//容易出问题。。。虽然也不知道为什么
//别什么东西都挤一起啊 赋值什么的还是和状态转换分开
//放进时序电路里面
//但是状态转换的下一个状态也不能放进时序电路里面
//会造成当前状态到下一个状态延迟一个时钟周期,时序可能就比较乱
always @(*) begin
	if(!rst_n) begin
		next_state = Rst;
	end
	else begin
		case(state)
			//复位等待状态
			//等待上电复位
			Rst: 
				next_state = us_cnt > RST_NUM ? Init : Rst;
			
			//初始化状态
			Init: 
				next_state = WaitInit;	//进入等待写命令完成的状态
			
			//等待初始化命令写完成状态
			//到达这个状态时cmd cnt才加到1,所以要大一个值判断
			//是否25个命令写完成 写完成进入下一个状态
			//否则是否spi写完成 spi写完成继续写下一个命令 否则就继续等待spi写完成
			//记得加&&write_done等待最后一次写完
			WaitInit: 
				next_state = (init_cmd_cnt == 5'd25&&write_done) ? OledOn : (write_done ? Init : WaitInit);	
				
							
			
			//oled开写命令状态
			OledOn: 
				next_state = WaitOn;
			
			//等待oled开写命令完成状态
			//判断命令是否写完 写完进入下一个状态
			//否则 再判断是否spi写完成 写完成继续写下一个数据
			WaitOn: 
				next_state = (oled_on_cmd_cnt == 2'd3&&write_done) ? ClearCmd : (write_done ? OledOn : WaitOn);	
			
			//清零写命令状态
			ClearCmd: 
				next_state = WaitClearCmd;

			
			//等待清零写命令状态			
			//每次写三个命令 所以对3取余数
			//这里0会造成进入这个状态就跳转了
			WaitClearCmd: 
				next_state = (clear_cmd_cnt % 2'd3 == 0 && write_done) ? ClearData : (write_done ? ClearCmd : WaitClearCmd);
				
			
			//清零写数据状态
			ClearData:
				next_state = WaitClearData;
			
			//等待清零写数据
			//1页需要写128个数据,写完7页就是1024个数据
			//写完1页,也就是每写完128个数据就要写一次命令,所以要对128取余,然后进入写命令的状态
			//其中0是不会对状态造成干扰的,因为进入这个状态的时候计数器已经加过1了
			WaitClearData: 
				next_state = (clear_data_cnt == 11'd1024&&write_done) ? Done : (clear_data_cnt % 11'd128 == 0&&write_done ? ClearCmd : (write_done ? ClearData : WaitClearData));
			
			//完成状态
			Done: 
				next_state = Done;

			default: 
				next_state = Rst;
				
		endcase
	end
end

//这个切忌不能写入上面的组合逻辑中
//会造成Latch
//至于原因,,我也不知道
always @(posedge clk,negedge rst_n) begin
	if(!rst_n) begin
		oled_rst <= 1'b0;
		us_cnt_clr <= 1'b1;
		oled_dc <= 1'b1;
		data <= 8'h10;
		ena_write <= 1'b0;
	end
	else begin
		case(state)
			//复位等待状态
			Rst:begin
					oled_rst <= 1'b0;
					us_cnt_clr <= 1'b0;
			end
			
			//初始化状态
			Init:begin
				oled_rst <= 1'b1;
				us_cnt_clr <= 1'b1;	//清零计数器
				ena_write <= 1'b1;			//写使能
				oled_dc <= 1'b0;			//写命令
				data <= init_cmd[init_cmd_cnt];//写数据赋值
			end
			
			//等待初始化命令写完成状态
			WaitInit: begin
				ena_write <= 1'b0;			//写失能		
			end
			
			//oled开写命令状态
			OledOn:begin
				ena_write <= 1'b1;			//写使能
				oled_dc <= 1'b0;			//写命令
				data <= oled_on_cmd[oled_on_cmd_cnt];	
			end
			
			//等待oled开写命令完成状态
			WaitOn:begin
				ena_write <= 1'b0;			//写失能
			end
			
			//清零写命令状态
			ClearCmd:begin
				ena_write <= 1'b1;
				oled_dc <= 1'b0;
				data <= clear_cmd[clear_cmd_cnt];
			end
			
			//等待清零写命令状态
			WaitClearCmd:begin
				ena_write <= 1'b0;
			end
			
			//清零写数据状态
			ClearData:begin
				ena_write <= 1'b1;
				oled_dc <= 1'b1;
				data <= 8'hff;
			end
			
			//等待清零写数据
			WaitClearData:begin
				ena_write <= 1'b0;
			end
		endcase
	end
end

//状态转换
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)	
		state <= Rst;
	else
		state <= next_state;
end

//计数器计数
always @(posedge clk,negedge rst_n) begin
	if(!rst_n) begin
		init_cmd_cnt <= 5'd0;
		oled_on_cmd_cnt <= 4'd0;
		clear_cmd_cnt <=3'd0;
		clear_data_cnt <= 11'd0;
	end
	else begin
		case(state)
			Init:			init_cmd_cnt <= init_cmd_cnt + 1'b1;
			OledOn:		oled_on_cmd_cnt <= oled_on_cmd_cnt + 1'b1;
			ClearCmd:	clear_cmd_cnt <= clear_cmd_cnt + 1'b1;
			ClearData:	clear_data_cnt <= clear_data_cnt + 1'b1;
			default:begin
				init_cmd_cnt <= init_cmd_cnt;
				oled_on_cmd_cnt <= oled_on_cmd_cnt;
				clear_cmd_cnt <= clear_cmd_cnt;
				clear_data_cnt <= clear_data_cnt;
			end
		endcase
	end
end

assign init_done = (state == Done);


endmodule

        因为数据初始值写入的是0xff,所以OLED点亮后应该是全屏被填满的样子

        (下一篇编写顶层模块,点亮OLED)


testbench模块测试代码

`timescale 1ns/1ns //仿真单位为1ns,精度为1ns

module oled_init_tb();

reg clk;
reg rst_n;
reg write_done;
wire oled_rst;
wire oled_dc;
wire [7:0] data;
wire ena_write;
wire init_done;

oled_init oled_init_inst(
	.clk(clk),
	.rst_n(rst_n),
	.write_done(write_done),
	.oled_rst(oled_rst),
	.oled_dc(oled_dc),
	.data(data),
	.ena_write(ena_write),
	.init_done(init_done)
);

initial begin
	#0	clk = 0;
		rst_n = 0;
		write_done = 1;
	#20 rst_n = 1;
end

always #5 clk = ~clk;

endmodule

        部分测试结果(调整上电复位等待时间为10个时钟周期)

你可能感兴趣的:(FPGA学习,verilog)