8/21 I2C

정유석·2024년 8월 21일

교육 - 베릴로그

목록 보기
24/28

i2c 통신

i2c 통신은 데이터를 주고 받는 통신 규약이다.
dht11인 경우에는 datasheet에서 통신의 규약을 정의한 케이스이고, 이것은 dht11 모듈만 갖는 종속적인 통신 규약이다.
반면에 i2c 통신은 표준적인 통신 규약이다. i2c 통신이 적용된 모듈은 i2c 통신 규약을 따르다.
이번에는 i2c 통신 규약을 따르는 Text LCD 모듈에 대해서 공부하면서 I2C에 대해서 공부해보자.

i2c 통신 정리 블로그 (교수님 공유)
https://blog.naver.com/specialist0/220645221966

i2c 통신에 대해서

1 대 다 통신이 가능한 통신 규약
i2c 통신 Clock 기준으로 동작하며, Clock 기준으로 데이터를 읽게 된다.
Clock Signal이 High Level일 때마다 딱 한 번 데이터가 읽는다.
=> 신호가 High Level일 때마다 딱 한 번만 데이터를 읽는다.
=> 신호가 High Level일 때마다 딱 한 번만 데이터를 읽기 때문에
=> 신호가 Low Lvel일 때 데이터를 변환하여야 한다.

dht11인 경우에는 Low Level 신호를 기준으로 갖고, PWM의 펄스폭을 기준으로 데이터의 값을 정한다.
번외) UART 통신은 시간으로 기준으로 데이터를 받는다.
= 1초마다 데이터를 한 번만 읽는다.
= 4초면 4BIT 데이터를 읽게 되는 것이다.

Master와 Slave 관계

하나의 Master에 아래의 그림과 같이 Slave 모듈을 연결할 수 있다.
I2C 버스는 SDA(Serial Data)과 SCL(Serial Clock) 신호선으로 구성되어 있다.

SDA(Serial Data) 신호선

=> 역활: 양방향 데이터 전송을 담당
=> 의미: Master와 Slave간에 데이터를 양방향으로 송수신한다.
=> 데이터 비트, 주소 비트, ACK/NACK 비트 등을 전송합니다.

SCL(Serial Clock) 신호선

=> 역활 : 통신의 동기화를 위한 Clock 신호를 제공
=> 의미 : 통신의 동기화를 위한 클럭 신호를 제공함으로서 데이터 송수신 타이밍을 제공
=> Mater 쪽에서 Clock 신호를 제공하는 동시에 제어를 하게 된다.
=> SDA 선의 데이터 샘플링 및 변경 타이밍을 결정합니다.
=> SDA 신호선은 양방향으로 가능하지만, 일반적으로 SCL 신호선은 Master에서 Slave으로 Clock Signal을 전달한다.

Start Signal, End Signal

SCL 신호선과 SDA 신호선은 아래와 같은 Pulse wave를 갖고 송수신하게 된다.

SCL 신호선에서 High-Level일때, SDA 신호선의 데이터 값을 읽기 때문에 SCL 신호선이 High Level 일때, SDA 신호선의 신호 값을 0 or 1로 유지해야 한다.

만약, 0 or 1이 아닌 값이 변하는 상태라면 Start or End Signal을 의미한다.

SCL 신호선이 High level일 때, SDA 신호선이 Negative edge이라면 Master 측에서 Slave에게 Start Signal을 전송하는 것을 의미한다.

반대로 SCL 신호선이 High level일 때, SDA 신호선이 Positive edge이라면 Master 측에서 Slave에게 End Signal을 전송하는 것을 의미한다.

ACK : Slayer측에서 Master쪽으로 보내는 Responce signal
=> Slayer측에서 Master에게 "어, 신호 잘 받았어."를 알려주는 신호

SDA 신호선은 아래 단계에 걸쳐 데이터 전송된다.


1단계) Start Signal 전송
2단계) Slave의 Address (7bit) 을 전송하여 어떤 Slave에게 Read/Write을 할지를 해당 Slave에게 전송한다.

3단계) 해당 Slave에 Read or Write할지를 전송 (Read : 1, Write : 0)

4단계) 해당 Slave측에서 Master에게 ACK or NACK 신호를 전송

=> ACK은 Slave측에서 신호를 잘 받았다고 반응하는 신호
=> NACK은 Slave측에서 신호를 못 받았다고 반응하는 신호

5단계) Master에서 Slave측으로 데이터를 전송한다.

6단계) 해당 Slave측에서 Master에게 ACK or NACK 신호를 전송

7단계) sda신호선의 상승엣지 발생시 종료

I2C 컨트롤 모듈

시스템 클락과 always문의 실행

시스템 클락마다 always문 안에는 순식간에 많은 일을 한번에 처리할 수 있고 또 처리하고 있다 하지만 next_state의 always문은 negedge에서 동작하기 때문에 상태머신의 always문의 case문은 next_state를 받는 실행문에서 다음 negedge까지 기다려야한다

https://blog.naver.com/specialist0/220645221966

module I2C_master (
    input clk, reset_p,
    input [6:0] addr, // Slave의 주소는 7bit이다.
    input rd_wr,       // 데이터를 read할것인지 write할 것인지
    input [7:0] data,  // Slave쪽으로 보낼 데이터의 크기는 8bit
    input comm_go,		// 1을 주면 통신 시작
    output reg scl, sda,
    output reg [15:0] led );

    // 7개의 state로 정의
    // 블로그 게시글 참고
    parameter IDLE = 7'b000_0001;
    parameter COMM_START = 7'b000_0010;
    parameter SEND_ADDR = 7'b000_0100;
    parameter RD_ACK = 7'b000_1000;
    parameter SEND_DATA = 7'b001_0000;
    parameter SCL_STOP = 7'b010_0000;
    parameter COMM_STOP = 7'b100_0000;


    //  Slave 주소 + Master 쪽에서 Read할것인지 Write할것인지를 담고 있는 8bit wire
    wire [7:0] addr_rw;
    assign addr_rw = {addr, rd_wr};

    // I2C 통신에서 사용하는 Clock signal은 "일반적으로" 100kHz를 사용 == 10us period 인 Clock Pulse 생성
    // Master에서 Clock Speed을 정해서 사용할 수 있음.
    // I2C 통신의 Clock 
    wire clk_usec;
    clock_div_100 usec_clk (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));


    // 
    reg [2:0] counter_usec5; // 10us 주기인 Clock Pulse를 만들기 위한 Register
    reg scl_e;  // scl 신호선의 enable

    // 10usec 주기인 Clock Pulse를 만들기
    always @(posedge clk or posedge reset_p) begin
        if(reset_p)  begin
             counter_usec5 = 0;
             scl = 1;   // IDLE 상태에서는 High-level이다.
        end
        else if(scl_e) begin
                if(clk_usec) begin
                    if(counter_usec5 >= 4) begin
                        counter_usec5 = 0;
                        scl = ~scl;              // SCL 신호선의 값을 Toggle시킨다.
                     end
                    else  counter_usec5 = counter_usec5 + 1;
                end 
        end
        else if(!scl_e) begin
                scl = 1;   // SCL 신호선이 Disable이면 1로 초기화
                counter_usec5 = 0;
        end
    end
// Get edge of 10usec인 Clock Pulse 
    wire scl_nedge, scl_pedge;
    edge_detector_n scl_edge (.clk(clk), .reset_p(reset_p), .cp(scl), .p_edge(scl_pedge), .n_edge(scl_nedge));

    //  Get positive edge of Comm-go
    wire comm_go_pedge;
    edge_detector_n _edge (.clk(clk), .reset_p(reset_p), .cp(comm_go), .p_edge(comm_go_pedge));
    
    reg [6:0] state, next_state;
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)state = IDLE;
        else state = next_state;
    end
    
    reg [2:0] cnt_bit;
    reg stop_flag;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)begin
            next_state = IDLE;
            scl_e = 0;
            sda = 1;
            cnt_bit = 7;
            stop_flag = 0;
        end
        else begin
            case(state)
                IDLE:begin
                    scl_e = 0;
                    sda = 1;
                    if(comm_go_pedge)next_state = COMM_START;
                end
                COMM_START:begin
                    sda = 0;
                    scl_e = 1;
                    next_state = SEND_ADDR;
                end
                SEND_ADDR:begin
                    if(scl_nedge)sda = addr_rw[cnt_bit]; //하강엣지일때 데이터를 sda에 주면
                    if(scl_pedge)begin 					//상승엣지일때 슬레이브의 칩이 알아서 읽음
                        if(cnt_bit == 0)begin
                            cnt_bit = 7;
                            next_state = RD_ACK;
                        end
                        else cnt_bit = cnt_bit - 1;
                    end
                end
                RD_ACK:begin
                    if(scl_nedge)sda = 'bz;
                    else if(scl_pedge)begin
                        if(stop_flag)begin
                            stop_flag = 0;
                            next_state = SCL_STOP;
                        end
                        else begin
                            stop_flag = 1;
                            next_state = SEND_DATA;
                        end
                    end                
                end
                SEND_DATA:begin
                    if(scl_nedge)sda = data[cnt_bit]; //하강엣지일때 데이터를 sda에 주면
                    if(scl_pedge)begin				//상승엣지일때 슬레이브의 칩이 알아서 읽음
                        if(cnt_bit == 0)begin
                            cnt_bit = 7;
                            next_state = RD_ACK;
                        end
                        else cnt_bit = cnt_bit - 1;
                    end
                end
                SCL_STOP:begin
                    if(scl_nedge)sda = 0;
                    else if(scl_pedge)next_state = COMM_STOP;
                end
                COMM_STOP:begin
                    if(counter_usec5 >= 3)begin
                        scl_e = 0;
                        sda = 1;
                        next_state = IDLE;
                    end
                end
            endcase
        end
        
    end
    
 endmodule  

시뮬레이션

시작신호

주소 보냄 010_0111

임피던스 후 데이터 보냄 0100_0000

profile
개인 기록공간

0개의 댓글