1110 views|7 replies

58

Posts

0

Resources
The OP
 

[Domestic Tang Primer 25K Review] Self-written IIC driver for 0.96-inch OLED screen [Copy link]

 
  1. I successfully wrote an OLED screen driver by relying on various routines on the Internet and studying the timing. However, except for the IIC driver, which I wrote by myself, the other files for driving OLED still quoted the results of other masters. I will first post the routines I referred to. If you want to really understand it, you need to study the timing carefully:

    FPGA implementation of IIC protocol (Part 2) ---- FPGA implementation of IIC bus (single read and write driver) - Guyue Home (guyuehome.com)

    The principle and Verilog implementation of IIC bus - Interface/Bus/Driver - Electronics Fans Network (elecfans.com) (This is the post that I mainly refer to for the driver I am currently writing. Its timing is also very clear. If you want to understand the timing and ideas, you can take a closer look)

    An article explains the FPGA implementation principle and process of the IIC bus - Zhihu (zhihu.com)
    IIC drives FPGA-----oled_verilog i2c oled-CSDN blog ( the code currently used is mainly derived from the code published by this blogger and the official account)

  2. When writing a driver, you need to study the timing. Although IIC only has two lines, SDA and SCL, IIC has a most troublesome feature. Each time you send data, you must first send the device address and register address before sending the data. If you want to understand the timing, it is recommended to find a specification sheet for an EEPROM chip or other related IIC driver chip.

  3. IIC driver operation flow: corresponding to 11 states

    1. Enable the SCL clock and output

    2. Output iic start timing

    3. Write the device address to the device

    4. After writing the device address, the SDA line is used to determine whether the current IIC driver is in a write operation or a read operation. The default is a write operation.

    5. Determine an ACK

    6. Write register address

    7. Determine ACK again

    8. Write register data

    9. The last ACK

    10. Output iic stop timing

    11. Turn off the clock output, the IIC driver enters the idle state, and outputs the IIC operation completion signal

  4. The following is the IIC driver module code

    `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
    


This post is from Domestic Chip Exchange

Latest reply

It feels like I just made an i2c driver, and the screen-clicking effect can be demonstrated. To do low-level things like i2c drivers on FPGA, you have to write them yourself, so you still need a solid foundation in Verilog.   Details Published on 2024-1-16 22:42
 
 

1127

Posts

17

Resources
2
 
Is there any IP that can be used directly in the official I2C IDE software?
This post is from Domestic Chip Exchange

Comments

Yes, but I don't use it.  Details Published on 2024-1-8 15:52
 
 
 

106

Posts

0

Resources
3
 

I also hope that I will have the opportunity to learn about FPGA this year.

This post is from Domestic Chip Exchange
 
 
 

58

Posts

0

Resources
4
 
fxyc87 posted on 2024-1-8 08:52 Is there an IP that can be used directly in the I2C official IDE software?

Yes, but I don't use it.

This post is from Domestic Chip Exchange
 
 
 

6027

Posts

6

Resources
5
 

How to ensure the speed of FPGA self-written IIC? FPGA should be very fast

This post is from Domestic Chip Exchange

Comments

SCL is the clock of IIC, the rate is determined by SCL, SCL is generated by the system clock frequency division, generally 100K, the maximum is 400KHz, SPI can reach 1M  Details Published on 2024-1-9 10:21
Personal signature

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 
 

58

Posts

0

Resources
6
 
Qintianqintian0303 posted on 2024-1-8 17:26 How to ensure the speed of FPGA self-written IIC? FPGA should be very fast

SCL is the clock of IIC, the rate is determined by SCL, SCL is generated by the system clock frequency division, generally 100K, the maximum is 400KHz, SPI can reach 1M

This post is from Domestic Chip Exchange
 
 
 

445

Posts

0

Resources
7
 

It feels like I just made an i2c driver, and the screen-clicking effect can be demonstrated. To do low-level things like i2c drivers on FPGA, you have to write them yourself, so you still need a solid foundation in Verilog.

This post is from Domestic Chip Exchange

Comments

Yes, I only wrote the driver. I used an open source example on the Internet for the whole set, so I didn't post any pictures.   Details Published on 2024-1-21 21:47
 
 
 

58

Posts

0

Resources
8
 
dirty posted on 2024-1-16 22:42 It feels like I just made an i2c driver, and I can show the screen effect. FPGA has to write the underlying i2c driver by itself, or it still needs some verilog method...

Yes, I only wrote the driver. I used an open source example on the Internet for the whole set, so I didn't post any pictures.

This post is from Domestic Chip Exchange
 
 
 

Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

Featured Posts
High-frequency power transformer design principles, requirements and procedures

Xu Zewei, editor of International Electronic Transformer    Abstract: Starting from the high-frequency power transfor ...

Assembly Language Programming [Download]

Chapter 1 Basic Knowledge 1.1 Assembly Language and Its Characteristics 1.2 Data Representation 1.3 8086 CPU and Registe ...

Infrared remote control receiving circuit (T9149A)

Infrared remote control receiving circuit (T9149A)

MSP430F6638 MCU FLL——Frequency Locked Loop

481229 Calculation formula: fDCOCLK ÷ = fFLLREFCLK ÷ n 【Note】: D: FLLD, default is 2 N: FLLN, default is 31 n: ...

Highly recommend a good introductory book on digital signal processing

I have been learning theoretical knowledge about digital signal processing recently. I used to read the textbooks I used ...

AC high voltage linear scheme to control two groups of lamps

Hello everyone! There is a case now, which requires a high-voltage linear solution, North America, AC120V lamps. There ...

IOT desk lamp software design

This post was last edited by skyworth74 on 2021-7-4 14:00 IOT desk lamp infrastructure To realize the IOT function, b ...

i.MX6ULL Embedded Linux Development 1-Preliminary Study on Uboot Transplantation

This post was last edited by DDZZ669 on 2021-7-27 23:05 This series of tutorials uses the ARM development board of the ...

Xintang M2354 Fault Injection Attack Protection

Abstract Shortly after Arm, a global mobile computing IP giant, launched its new isolation technology TrustZone, the hac ...

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list