数字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完成电路综合,并下板实现。

祝大家新年快乐呀

总结

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

相关推荐
仲南音17 小时前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner1 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20201 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy2 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia2 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20202 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子2 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安3 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun3 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun3 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议