[原创] 【国产Tang Primer 25K测评】自写IIC驱动0.96寸OLED屏

学学学学学学学   2024-1-7 23:54 楼主
  1. 靠网上的各路例程和研究时序成功写了一个OLED屏幕驱动,但是目前除了IIC那个驱动是我自己参考着写出来的,驱动oled的其他文件还是引用了其他大佬的成果。先贴一下我参考的那些例程,想真正搞明白要好好研究时序:

    FPGA实现IIC协议(二)----IIC总线的FPGA实现(单次读写驱动) - 古月居 (guyuehome.com)

    IIC总线的原理与Verilog实现 - 接口/总线/驱动 - 电子发烧友网 (elecfans.com)(这是我目前写的驱动主要借鉴的帖子,它的时序也讲得很明白,想要搞明白时序和思路可以仔细看看)

    一文解读IIC总线的FPGA实现原理及过程 - 知乎 (zhihu.com)
    IIC驱动FPGA-----oled_verilog i2c oled-CSDN博客目前使用的代码主要源于这个博主和公众号公开的代码)

  2. 写驱动要研究好时序,IIC的虽然只有两根线SDA SCL,但是IIC有一个最麻烦的地方,每次发送数据要先发送器件地址 和 寄存器地址,然后才能发送数据。想要看明白时序,建议去找个EEPROM芯片或者其他相关的IIC驱动的芯片的规格书。

  3. IIC驱动的操作流程:对应11个状态

    1. 使能SCL时钟,同时输出

    2. 输出iic启动时序

    3. 向器件写入器件地址

    4. 写入器件地址后通过SDA线判断目前IIC驱动时写操作还是读操作,默认写操作

    5. 判断一次ACK

    6. 写入寄存器地址

    7. 再判断一次ACK

    8. 写入寄存器数据

    9. 最后判断一次ACK

    10. 输出iic停止时序

    11. 关闭时钟输出,IIC驱动进入空闲状态,同时输出IIC操作完成信号

  4. 以下是IIC驱动模块代码
     

    `timescale 1ns / 1ps
    //
    // Company: 
    // Engineer: 
    // 
    // Create Date:    22:40:45 11/20/2017 
    // Design Name: 
    // Module Name:    I2C_Master 
    // Project Name: 
    // Target Devices: 
    // Tool versions: 
    // Description: 
                   /*
    	       I2C总线通信协议通用模块:SCL SDA
    	       开始信号:SCL高时,SDA拉低
    	       结束信号:SCL高时,SDA拉高
    	       SDA数据在SCL低电平时置位
    	       模块中实际默认开始信号与结束信号在SCL高电平中间产生
    	       SDA数据位改变在SCL低电平的中间产生
    	       SCL时钟频率为200kHz
    	       从机地址可调,模块既支持读也支持写,通过输入管脚控制
    		*/
    // 
    // Dependencies: 
    //
    // Revision: 
    // Revision 0.01 - File Created
    // Additional Comments: 
    //
    //
    
    module I2C_Master(
        //I/O
        input		I_Clk_in,
        input		I_Rst_n,
        output		O_SCL,
        inout		IO_SDA,
        
        input		I_Start,
        output		O_Done,
        input  [6:0] 	I_Slave_Addr,
        input		I_R_W_SET,
        input  [15:0]	I_R_W_Data,
        output [7:0] 	O_Data,
        output      	O_Error
     );
     
    
    		// I_Clk_in,
    		// I_Rst_n,
    		// O_SCL,
    		// IO_SDA,
    		// //control_sig
    		// I_Start,   //一次读/写操作开始信号
    		// O_Done,    //一次读/写操作结束信号
    
    		// I_R_W_SET, //读写控制信号,写为1,读为0
    		// I_Slave_Addr,//从机地址
    		// I_R_W_Data,//读写控制字16位I_R_W_Data[15:8]->reg_addr,I_R_W_Data[7:0]->W_data,读状态则可默认为7'b0
            //     	O_Data,    //读到的数据,当O_Done拉高时数据有效
    		// O_Error	  //检测传输错误信号,当出现从机未响应,从机不能接收数据等情况时,拉高电平		
    parameter   CLK_FREQ   = 32'd50_000_000         ; //模块输入的时钟频率
    parameter   I2C_FREQ   = 24'd100_000            ;  //IIC_SCL的时钟频率
    parameter clk_divide  = CLK_FREQ/I2C_FREQ       ;//模块驱动时钟的分频系数500,50M/100k=500,分频250为scl
    
    parameter clk_divide1 = (clk_divide >> 1'b1)    ;//模块驱动时钟的分频系数500/2=250,产生scl时钟线 249
    
    parameter clk_divide2 = (clk_divide1 >> 1'b1)-1 ;//模块驱动时钟的分频系数250/2=125-1,scl低电平中间位 124
    
    parameter clk_divide3 = (clk_divide2 >> 1'b1)   ;//模块驱动时钟的分频系数124/2=62,scl的4倍频时钟 4
    
    parameter clk_divide4 = (clk_divide1+clk_divide2)+1;//用来产生IIC总线SCL高电平最中间的标志位 375
    
    reg    [8:0]  SCL_cnt   ; //分频时钟计数
    wire    scl_h_mid;
    wire    scl_l_mid;
    wire    slc_h2l;//产生时钟下降沿
    wire    scl__o;
    // reg    [8:0]	SCL_cnt;
    reg         	SCL_En;
    
    assign scl_h_mid = (SCL_cnt==clk_divide2)? 1'b1:1'b0;//如果等于125,高电平中间
    assign scl_l_mid = (SCL_cnt==clk_divide4)? 1'b1:1'b0;//如果等于375,低电平中间
    assign scl__o    = (SCL_cnt<=clk_divide1)? 1'b1:1'b0;//如果cnt2 小于249,那么高电平,大于250,低电平
    assign scl_h2l   = (SCL_cnt==clk_divide1)?1'b1:1'b0;//如果等于250,高向低
    
    always @(posedge I_Clk_in or negedge I_Rst_n) begin
        if(!I_Rst_n) begin
            SCL_cnt <= 9'd0;
        end
        else if(SCL_En)begin
            if(SCL_cnt ==(clk_divide)-1) begin//cnt2==39,实际已经数到40
            SCL_cnt <= 9'd0;
        end
            else
            SCL_cnt <= SCL_cnt + 1'b1;
        end
        else    SCL_cnt<=9'd0;
    end
    
    assign          O_SCL=scl__o;
     
     /******SDA读写控制模块******/
     reg [5:0]    C_State;
     reg          SDA_IO_DIR;//SDA双向选择I/O口 1为输出,0为输入
     reg          SDA_reg;      //SDA的输出端口
     reg          O_Done;       //结束信号
     reg [7:0]    O_Data;       //读到的数据
     reg          O_Error;		//传输错误指示信号
    // 
    ///****状态定义*****/
     parameter    Start=6'd0;  //开始
     parameter    iic_address=6'd1;//写入oled地址 这里只有7位
     parameter    w_or_r=6'd2;//写入读写位
     parameter    Ack_1=6'd3;  //第一次ack
     parameter    reg_addr=6'd4;//写入寄存器地址值
     parameter    Ack_2=6'd5;    //Ack_2
     parameter    data_addr=6'd6; //写入寄存器数据
     parameter    Ack_3=6'd7;    //Ack_3
     parameter    Stop=6'd8;    //停止发送数据,此刻时钟不停
     parameter    Idle=6'd9;    //进入空闲状态,停止时钟
     parameter    ReStart=6'd10;//准备重新开始
     reg[5:0]    sda_cnt;
    
    always @ (posedge I_Clk_in or negedge I_Rst_n)begin
         if(~I_Rst_n)begin
            sda_cnt<=6'd0;
            SDA_IO_DIR<=1'b1;//默认设置为输出管脚
     		SDA_reg<=1'b1;      //SDA输出默认拉高
     		O_Error<=1'b0;
            SCL_En<=1'b0;
            O_Done<=1'b0;
            C_State<=Start;
         end
         else  if(I_Start)
             begin
                 case (C_State)
                     Start: begin
                         O_Error<=1'b0; 
                         SCL_En<=1'b1;
                         if(scl_h_mid)
                             begin
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd1;
                                 C_State<=iic_address;
                             end
                         else
                             begin
                                 SDA_reg<=1'b1;
                                 C_State<=C_State;
                             end
                     end
                     iic_address : begin 
                         if(scl_l_mid)
                             begin
                                if(sda_cnt==6'd8)begin
                                 sda_cnt<=6'd0;
                                 C_State<=w_or_r;
                                    end
                                else if(scl_l_mid)
                                begin
                                 SDA_reg=I_Slave_Addr[6'd7-sda_cnt];
                                 sda_cnt<=sda_cnt+1'd1;
                                end
                             end
                         else
                             begin
                                 C_State<=C_State;
                             end
                     end
                     w_or_r : begin 
                         if(scl_l_mid)
                             begin
                                 SDA_reg<=1'b0;
                                 C_State<=Ack_1;
                             end
                         else
                             begin
                                 C_State<=C_State;
                             end
                     end
                     Ack_1 : begin 
                             SDA_IO_DIR<=1'b0;
                         if(scl_h_mid && sda_cnt==6'd0 )
                             begin
                                 O_Error<=IO_SDA;
                                 sda_cnt<=6'd1;
                             end
                         else if(O_Error==1'd0 && scl_h2l)
                             begin
                                 SDA_IO_DIR<=1'b1;
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd0;
    
                                 C_State<= reg_addr;
                             end 
                         else if(O_Error==1'd1 && scl_h2l)
                             begin
                                 SDA_IO_DIR<=1'b1;
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd0;
    
                                 C_State<= Stop;
                             end 
                         else         
                             begin
                                 C_State<=C_State;
                             end             
                     end
                     reg_addr : begin 
                         if(scl_l_mid)
                             begin
                                if(sda_cnt==6'd8)begin
                                 sda_cnt<=6'd0;
                                 C_State<=Ack_2;
                                end
                                 else if(scl_l_mid)
                                begin
                                 SDA_reg=I_R_W_Data[6'd15-sda_cnt];
                                 sda_cnt<=sda_cnt+1'd1;
                                end
                             end
    
                         else
                             begin
                                 C_State<=C_State;
                             end
                     end
                     Ack_2 : begin 
                             SDA_IO_DIR<=1'b0;
                         if(scl_h_mid && sda_cnt==6'd0 )
                             begin
                                 O_Error<=IO_SDA;
                                 sda_cnt<=6'd1;
                             end
                         else if(O_Error==1'd0 && scl_h2l)
                             begin
                                 SDA_IO_DIR<=1'b1;
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd0;
    
                                 C_State<= data_addr;
                             end 
                         else if(O_Error==1'd1 && scl_h2l)
                             begin
                                 SDA_IO_DIR<=1'b1;
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd0;
    
                                 C_State<= Stop;
                             end 
                         else         
                             begin
                                 C_State<=C_State;
                             end      
                     end
                     data_addr : begin 
                         if(scl_l_mid)
                             begin
                                if(sda_cnt==6'd8)begin
                                 sda_cnt<=6'd0;
                                 C_State<=Ack_3;
                                end
                                 else if(scl_l_mid)
                                begin
                                 SDA_reg=I_R_W_Data[6'd7-sda_cnt];
                                 sda_cnt<=sda_cnt+1'd1;
                                end
                             end
                         else
                             begin
                                 C_State<=C_State;
                             end
                     end
                     Ack_3 : begin 
                             SDA_IO_DIR<=1'b0;
                         if(scl_h_mid && sda_cnt==6'd0 )
                             begin
                                 O_Error<=IO_SDA;
                                 sda_cnt<=6'd1;
                             end
                         else if(O_Error==1'd0 && scl_h2l)
                             begin
                                 SDA_IO_DIR<=1'b1;
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd0;
    
                                 C_State<= Stop;
                             end 
                         else if(O_Error==1'd1 && scl_h2l)
                             begin
                                 SDA_IO_DIR<=1'b1;
                                 SDA_reg<=1'b0;
                                 sda_cnt<=6'd0;
    
                                 C_State<= Stop;
                             end 
                         else         
                             begin
                                 C_State<=C_State;
                             end       
                     end
                     Stop : begin 
                         if(scl_h_mid)
                             begin
                                 SDA_reg<=1'b1;
                                 C_State<=Idle;
                             end
                         else         
                             begin
                                 C_State<=C_State;
                             end   
                     end
                     Idle : begin 
                                SCL_En<=1'b0;
                                O_Done<=1'b1;//拉高Done信号
                                C_State<=ReStart;
    
                     end
                     ReStart : begin 
                            O_Done<=1'b0;
                            SDA_IO_DIR<=1'b1;
                            sda_cnt<=6'd0;
                            SDA_reg<=1'b1;      //SDA输出默认拉高
                            O_Error<=1'b0;
                            SCL_En<=1'b0;
                            C_State<=Start;
                     end
                     default: begin
                            O_Done<=1'b0;
                            SDA_IO_DIR<=1'b1;
                            sda_cnt<=6'd0;
                            SDA_reg<=1'b1;      //SDA输出默认拉高
                            O_Error<=1'b0;
                            SCL_En<=1'b0;
                            C_State<=Start;
    
                     end
                  endcase
             end
         else  begin   
                            O_Done<=1'b0;
                            SDA_IO_DIR<=1'b1;
                            sda_cnt<=6'd0;
                            SDA_reg<=1'b1;      //SDA输出默认拉高
                            O_Error<=1'b0;
                            SCL_En<=1'b0;
                            C_State<=Start;
         end
     end
    
     assign  IO_SDA=(SDA_IO_DIR)?SDA_reg:1'bz;
    
    
    endmodule
    


     

回复评论 (7)

I2C官方IDE软件中有直接能用的IP么?
点赞  2024-1-8 08:52

我也希望我今年能有机会学习一下FPGA知识

点赞  2024-1-8 09:25
引用: fxyc87 发表于 2024-1-8 08:52 I2C官方IDE软件中有直接能用的IP么?

有的,但我没用

点赞  2024-1-8 15:52

FPGA自写IIC如何保证速率?FPGA的速度应该很快

在爱好的道路上不断前进,在生活的迷雾中播撒光引
点赞  2024-1-8 17:26
引用: 秦天qintian0303 发表于 2024-1-8 17:26 FPGA自写IIC如何保证速率?FPGA的速度应该很快

SCL是IIC的时钟,速率由SCL决定,SCL由系统时钟分频产生,一般100K,普通最大400KHz,SPI的话可以达到1M

点赞  2024-1-9 10:21

感觉像只是做了i2c驱动,能点屏效果可以展示下。FPGA做像i2c驱动这些底层的都得自己写,还是要些verilog方面扎实的基础

点赞  2024-1-16 22:42
引用: dirty 发表于 2024-1-16 22:42 感觉像只是做了i2c驱动,能点屏效果可以展示下。FPGA做像i2c驱动这些底层的都得自己写,还是要些verilog方 ...

是的,只写了驱动,整套我用了网上开源的一个例程,所以没贴图片

 

点赞  2024-1-21 21:47
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复