数字IC实践项目(9)— Tang Nano 20K: I2C OLED Driver

Tang Nano 20K: I2C OLED Driver

写在前面的话

  • 之前在逛淘宝的时候偶然发现了Tang Nano 20K,十分感慨国产FPGA替代方案的进步之快;
  • 被Tang Nano 20K小巧精致的外形和丰富的内在资源震惊到了,买来想要体验一下国产FPGA的软件生态。

硬件模块

  • 项目主要设备是高云半导体的Tang Nano 20K开发板和0.96寸四针I2C模块的OLED模块;
  • OLED模块采用SSD1306驱动芯片;

Tang Nano 20K

0.96寸 I2C 接口OLED

RTL电路和相关资源报告

  • 采用GOWIN FPGA Designer平台查看RTL电路图;
  • 采用GOWIN FPGA Designer平台查看资源报告;

SSD1306 OLED 驱动芯片

  • SSD1306是一个单片CMOS OLED/PLED驱动芯片可以驱动有机/聚合发光二极管点阵图形显示系统。由128 segments和64Commons组成。该芯片专为共阴极OLED面板设计。
  • SSD1306中嵌入了对比度控制器、显示RAM和晶振,并因此减少了外部器件和功耗。有256级亮度控制。数据/命令的发送有三种接口可选择:6800/8000串口,I2C接口或SPI接口。适用于多数简介的应用,移动电话的屏显,MP3播放器和计算器等。

SSD1306 芯片框图

SSD1306中内置128 * 64的GDDRAM,是一个为映射静态RAM保存位模式来显示,RAM分为8页,从PAFE0到PAGE7,用于单色128 * 64点阵显示,如下图所示:
GDDRAM:

当一个数据字节写到GDDRAM中,所有当前列的同一页的行图像数据都会被被填充(比如,被列地址指针指向的整列(8位)都会被填充)。数据位D0写到顶行,而数据位D7写到底行,如下图所示。

SSD1306 I2C协议接口

I2C通讯接口由从机地址为SA0,I2C总线数据信号(SDAout/D2输出和SDAin/D1输入)和I2C总线时钟信号SCL(D0)组成。数据和时钟信号线都必须接上上拉电阻。RES#用来初始化设备。
I2C数据格式:

a. 从机地址位(SA0)

SSD1306在发送或接受任何信息之前必须识别从机地址。设备将会响应从机地址,后面跟随着从机地址位(SA0位)和读写选择位(R/W#位)。

SA0位为从机地址提供了一个位的拓展。0111100或0111101都可以做为SSD1306的从机地址。D/C#引脚作为SA0用于从机地址选择。R/W#为用来决定I2C总线接口的操作模式。R/W# = 1,读模式。R/W# = 0 写模式
b. I2C总线数据信号SDA

SDA作为主机和从机之间的通讯通道。数据和应答都是通过SDA发送。
c. I2C总线时钟信号SCL

每个数据位的传输任务发生在SCL的单个的时钟周期中。

项目难度:⭐
项目推荐度:⭐⭐
项目推荐天数:1~天

FPGA开发环境:

前仿: Modelsim SE-64 2019.2

综合: Gowin_V1.9.9Beta-4_Education

项目学习目的:

(1)熟练掌握项目中各文件的工程管理;

(2)熟悉 Verilog HDL仿真、FPGA综合工具及流程;

(3)学习OLED 驱动和I2C的基础原理;

OLED 驱动模块RTL

csharp 复制代码
//oled_init  初始化模块
module OLED_Init(
	
	input					sys_clk			,
	input					rst_n			,
	
	input					init_req		,			//初始化请求
	input					write_done		,			//初始化数据完成信号
	
	output					init_finish		,			//初始化完成输出

	output[23:0]			Init_data					//初始化的数据
);

//WR CMD : 0X78+0X00+CMD
//WR Data: 0X78+0X40+Data
localparam			RST_T			=	1'b0;			//低电平复位有效

reg[23:0]		Init_data_reg;
reg[23:0]		Init_data_reg1;

reg[4:0]		Init_index;

reg init_finish_inside;


//OLED_WR
reg [10:0]  WR_index    ;   //TX data counter
reg [9:0]   WR_addr     ;   //TX ROM Data addr
wire [7:0]  WR_data     ;   //WR Data

//FSM
localparam IDLE =   2'b01   ;
localparam WR   =   2'b10   ;

reg [1:0]   CS ;
reg [1:0]   NS ;

always @(posedge sys_clk or negedge rst_n) begin 
    if(~rst_n) begin
         CS <= IDLE;
    end 
    else begin
         CS <= NS;
    end
end


always@(*) begin 
    case(CS)
        IDLE: begin
            if(Init_index >= 'd26 && write_done == 1'b1) begin
                NS = WR;
            end 
            else begin
                NS = IDLE;
            end 
        end 
        WR: begin
            if(rst_n==1'b0) begin
                NS = IDLE;
            end
            else begin
                NS = WR;
            end
        end 
        default: NS = IDLE;
    endcase 
end 

always@(*) begin 
    case(CS)
        IDLE: init_finish_inside = 1'b0;
        WR: init_finish_inside = 1'b1;
        default: NS = IDLE;
    endcase 
end





assign Init_data  = (init_finish_inside==1'b1)?Init_data_reg1:Init_data_reg;
assign init_finish = (write_done == 1'b1 && WR_index == 'd1047) ? 1'b1 : 1'b0;//完成信号

always@(posedge sys_clk or negedge rst_n)
begin
	if(rst_n == RST_T)
		Init_index <= 'd0;
	else if(Init_index == 'd26 && write_done == 1'b1 )
		Init_index <= 'd0;
	else if(write_done == 1'b1 && init_req == 1'b1)
		Init_index <= Init_index + 1'b1;
	else
		Init_index <= Init_index;
end

//初始化命令状态
always@(*)
begin
	case(Init_index)
		'd0:		Init_data_reg <= {8'h78,8'h00,8'hAE};
		'd1:		Init_data_reg <= {8'h78,8'h00,8'h00};
		'd2:		Init_data_reg <= {8'h78,8'h00,8'h10};
		'd3:		Init_data_reg <= {8'h78,8'h00,8'h40};
		'd4:		Init_data_reg <= {8'h78,8'h00,8'hB0};
		'd5:		Init_data_reg <= {8'h78,8'h00,8'h81};
		'd6:		Init_data_reg <= {8'h78,8'h00,8'hFF};
		'd7:		Init_data_reg <= {8'h78,8'h00,8'hA1};
		'd8:		Init_data_reg <= {8'h78,8'h00,8'hA6};
		'd9:		Init_data_reg <= {8'h78,8'h00,8'hA8};
		'd10:		Init_data_reg <= {8'h78,8'h00,8'h3F};
		'd11:		Init_data_reg <= {8'h78,8'h00,8'hC8};
		'd12:		Init_data_reg <= {8'h78,8'h00,8'hD3};
		'd13:		Init_data_reg <= {8'h78,8'h00,8'h00};
		'd14:		Init_data_reg <= {8'h78,8'h00,8'hD5};
		'd15:		Init_data_reg <= {8'h78,8'h00,8'h80};
		'd16:		Init_data_reg <= {8'h78,8'h00,8'hD8};
		'd17:		Init_data_reg <= {8'h78,8'h00,8'h05};
		'd18:		Init_data_reg <= {8'h78,8'h00,8'hD9};
		'd19:		Init_data_reg <= {8'h78,8'h00,8'hF1};
		'd20:		Init_data_reg <= {8'h78,8'h00,8'hDA};
		'd21:		Init_data_reg <= {8'h78,8'h00,8'h12};
		'd22:		Init_data_reg <= {8'h78,8'h00,8'hDB};
		'd23:		Init_data_reg <= {8'h78,8'h00,8'h30};
		'd24:		Init_data_reg <= {8'h78,8'h00,8'h8D};
		'd25:		Init_data_reg <= {8'h78,8'h00,8'h14};
		'd26:		Init_data_reg <= {8'h78,8'h00,8'hAF};
		default:
			Init_data_reg <= {8'h78,8'h00,8'hAE};
		endcase
end





always @(posedge sys_clk or negedge rst_n) 
begin
    if(~rst_n)begin
        WR_index <= 11'd0;
    end 
    else if(WR_index == 'd1048) begin
        WR_index <= 11'd0;
    end
    else if(init_finish_inside == 1'b1 && write_done == 1'b1 && init_req == 1'b1)
        WR_index <= WR_index + 1'b1;
    else
        WR_index <= WR_index;  
end



always @(posedge sys_clk or negedge rst_n) 
begin
    if(~rst_n)
    begin
        WR_addr <= 10'd0;
    end 
    else
    begin
        if ((WR_index >= 11'd2) && (WR_index <= 11'd129)) 
        begin
            WR_addr <= WR_index - 11'd2;
        end
        else if ((WR_index >= 11'd133) && (WR_index <= 11'd260)) 
        begin
            WR_addr <= WR_index - 11'd5;
        end
        else if ((WR_index >= 11'd264) && (WR_index <= 11'd391)) 
        begin
            WR_addr <= WR_index - 11'd8;
        end
        else if ((WR_index >= 11'd395) && (WR_index <= 11'd522)) 
        begin
            WR_addr <= WR_index - 11'd11;
        end
        else if ((WR_index >= 11'd526) && (WR_index <= 11'd653)) 
        begin
            WR_addr <= WR_index - 11'd14;
        end
        else if ((WR_index >= 11'd657) && (WR_index <= 11'd784)) 
        begin
            WR_addr <= WR_index - 11'd17;
        end
        else if ((WR_index >= 11'd788) && (WR_index <= 11'd915)) 
        begin
            WR_addr <= WR_index - 11'd20;
        end
        else if ((WR_index >= 11'd919) && (WR_index <= 11'd1046)) 
        begin
            WR_addr <= WR_index - 11'd23;
        end
    end
end


//初始化数据发送
always@(*) begin
            case (WR_index)
                11'd0: 	Init_data_reg1 <= {8'h78,8'h00,8'hB0};
                11'd1:	Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd2:	Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 0
                11'd131:Init_data_reg1 <= {8'h78,8'h00,8'hB1};
                11'd132:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd133:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 1
                11'd262:Init_data_reg1 <= {8'h78,8'h00,8'hB2};
                11'd263:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd264:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 2
                11'd393:Init_data_reg1 <= {8'h78,8'h00,8'hB3};
                11'd394:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd395:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 3
                11'd524:Init_data_reg1 <= {8'h78,8'h00,8'hB4};
                11'd525:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd526:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 4
                11'd655:Init_data_reg1 <= {8'h78,8'h00,8'hB5};
                11'd656:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd657:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 5
                11'd786:Init_data_reg1 <= {8'h78,8'h00,8'hB6};
                11'd787:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd789:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 6
                11'd917:Init_data_reg1 <= {8'h78,8'h00,8'hB7};
                11'd918:Init_data_reg1 <= {8'h78,8'h00,8'h00}; 
                11'd919:Init_data_reg1 <= {8'h78,8'h00,8'h10};
                // page 7
                default : Init_data_reg1 <= {8'h78,8'h40,WR_data}; //binary data
            endcase
end

assign WR_data = 8'hAA;


//Change the instance name and port connections to the signal names
//--------Copy here to design--------

    Gowin_pROM your_instance_name(
        .dout 		(WR_data 	), 			//output [7:0] dout
        .clk 		(sys_clk 	), 			//input clk
        .oce 		( 		    ),  		//input oce
        .ce 		(1'b1 		), 			//input ce
        .reset	 	(~rst_n 	), 			//input reset
        .ad 		(WR_addr 	) 			//input [9:0] ad
    );

//--------Copy end-------------------

endmodule

综合实现

采用GOWIN完成电路综合,并下板实现。

祝大家新年快乐呀

总结

没啥总结的,在这里祝愿大家龙年大吉,万事如意!

相关推荐
乌恩大侠1 小时前
O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈
5g·平面·fpga开发·架构
DS小龙哥15 小时前
基于Zynq FPGA的雷龙SD NAND存储芯片性能测试
fpga开发·sd nand·雷龙·spi nand·spi nand flash·工业级tf卡·嵌入式tf卡
上理考研周导师1 天前
第二章 虚拟仪器及其构成原理
fpga开发
FPGA技术实战1 天前
《探索Zynq MPSoC》学习笔记(二)
fpga开发·mpsoc
bigbig猩猩2 天前
FPGA(现场可编程门阵列)的时序分析
fpga开发
Terasic友晶科技2 天前
第2篇 使用Intel FPGA Monitor Program创建基于ARM处理器的汇编或C语言工程<二>
fpga开发·汇编语言和c语言
码农阿豪2 天前
基于Zynq FPGA对雷龙SD NAND的测试
fpga开发·sd nand·spi nand·spi nand flash·工业级tf卡·嵌入式tf卡
江山如画,佳人北望2 天前
EDA技术简介
fpga开发
淘晶驰AK2 天前
电子设计竞赛准备经历分享
嵌入式硬件·fpga开发