반응형

기존 파일에서 이어서 간다.

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

 

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

Verilog modules required to get the OV7670 camera working - westonb/OV7670-Verilog

github.com

 

SCCB 인터페이스 동작에 대해 전에 봤고, 이번에는 카메라를 볼 차례이다.

 

camera_config_tb.v 를 보도록 한다.

 

마찬가지로 modelsim 으로 파형을 보는데, continue 를 눌러 끝까지 실행시킨다.

 

 

내부 플래그 이름을 보면 버스와 클럭에 관계되는

전체를 관리하는 부분 같다.

 

어,,

조금 이따 보고 read 먼저 본다.

 

camera_read_tb.v 파일은 다음과 같은 구조다.

 

module camera_read_tb;

// inputs 
reg p_clock;
reg vsync;
reg href;
reg [7:0] p_data;

//outputs 
wire [15:0] pixel_data;
wire pixel_valid;
wire frame_done;

 camera_read camera_read_1 (
        .p_clock(p_clock), 
        .vsync(vsync), 
        .href(href),
        .p_data(p_data), 
        .pixel_data(pixel_data), 
        .pixel_valid(pixel_valid),
        .frame_done(frame_done)
    );
    
    always #5 p_clock = ~p_clock;
    
    initial begin 
    
    p_clock = 0;
    vsync = 0;
    href = 0;
    p_data = 0;
    #5;
    #100;
    
    vsync = 1;
    #10;
    vsync = 0;
    #10;
    href = 1; 
    p_data = 8'hFF;
    #10; 
    p_data = 8'h0;
    #10;
    href = 0;
    #10;
    vsync = 0; 
    
    
    end
    
    
endmodule

 

일단 이것만 봐서는 좀 이해가 안 된다.

 

파형을 한 번 살펴본다.

 

 

105ns 에서 Vsync가 1로 뜨고, 115ns 에서 vsync 가 0으로 내려간다.

125ns에서 href가 1로 뜨고, p_data가 0xFF 값이 들어간다.

135ns 에서 p_data 가 0x00 값이 들어간다.

145ns 에서 href 가 다시 0으로 내려간다.

155ns 에서 vsync 가 다시 0으로 내려가는(아까 전에 0이였음) 흐름으로 테스트벤치가 구성되어 있다.

 

camera_read.v 본문 내용을 한 번 살펴본다.

 

module camera_read(
	input wire p_clock,
	input wire vsync,
	input wire href,
	input wire [7:0] p_data,
	output reg [15:0] pixel_data =0,
	output reg pixel_valid = 0,
	output reg frame_done = 0
    );
	 
	
	reg [1:0] FSM_state = 0;
    reg pixel_half = 0;
	
	localparam WAIT_FRAME_START = 0;
	localparam ROW_CAPTURE = 1;
	
	
	always@(posedge p_clock)
	begin 
	
	case(FSM_state)
	
	WAIT_FRAME_START: begin //wait for VSYNC
	   FSM_state <= (!vsync) ? ROW_CAPTURE : WAIT_FRAME_START;
	   frame_done <= 0;
	   pixel_half <= 0;
	end
	
	ROW_CAPTURE: begin 
	   FSM_state <= vsync ? WAIT_FRAME_START : ROW_CAPTURE;
	   frame_done <= vsync ? 1 : 0;
	   pixel_valid <= (href && pixel_half) ? 1 : 0; 
	   if (href) begin
	       pixel_half <= ~ pixel_half;
	       if (pixel_half) pixel_data[7:0] <= p_data;
	       else pixel_data[15:8] <= p_data;
	   end
	end
	
	
	endcase
	end
	
endmodule

 

굉장히 짧게 구성된 코드이다.

다른것 볼 필요 없이 state machine 만 보도록 한다.

 

WAIT_FRAME_START: begin //wait for VSYNC
   FSM_state <= (!vsync) ? ROW_CAPTURE : WAIT_FRAME_START;
   frame_done <= 0;
   pixel_half <= 0;
end

 

VSYNC 가 1일 때 frame_start(현재 state) 를 유지하고, 0이면 ROW_CAPTURE state 로 넘어간다.

frame_done, pixel_half 는 아직 나온것이 없기 때문에 초기화 해 주는 내용이라고 생각하고 넘어간다.

 

ROW_CAPTURE: begin 
   FSM_state <= vsync ? WAIT_FRAME_START : ROW_CAPTURE;
   frame_done <= vsync ? 1 : 0;
   pixel_valid <= (href && pixel_half) ? 1 : 0; 
   if (href) begin
       pixel_half <= ~ pixel_half;
       if (pixel_half) pixel_data[7:0] <= p_data;
       else pixel_data[15:8] <= p_data;
   end
end

 

ROW_CAPTURE state 에서

VSYNC가 1이면 WAIT_FRAME_START, 0이면 ROW_CAPTURE(현재 유지)가 된다. 아까의 state 와 반대이다.

그리고, frame_done 도 VSYNC 에 따라 바뀐다.

VSYNC 가 1이 되면 state 를 프레임 시작을 기다리고, 현재 프레임을 끝났다 라고 생각하게 만든다. 라고 볼 수 있다.

 

href 가 활성화 된 경우

pixel_half 값 반전,

pixel_half 가 1이면 하위 8비트에 픽셀 데이터를 넣음.

pixel_half 가 0이면 상위 8비트에 픽셀 데이터를 넣음.

 

역시나 이것만으로는 동작에 대한 설명이 좀 부족하다.

vsync와 href 의 동작을 알아야 카메라에서 어떤 값을 어떤 방식으로 받아올 수 있는지 알 수 있을 것이다.

 

 

 

 

 


 

다음으로 ov7670_config.v 를 보도록 한다.(camera_configure.v 가 아니다)

 

module OV7670_config
#(
    parameter CLK_FREQ = 25000000
)
(
    input wire clk,
    input wire SCCB_interface_ready,
    input wire [15:0] rom_data,
    input wire start,
    output reg [7:0] rom_addr,
    output reg done,
    output reg [7:0] SCCB_interface_addr,
    output reg [7:0] SCCB_interface_data,
    output reg SCCB_interface_start
    );
    
    initial begin
        rom_addr = 0;
        done = 0;
        SCCB_interface_addr = 0;
        SCCB_interface_data = 0;
        SCCB_interface_start = 0;
    end
    
    localparam FSM_IDLE = 0;
    localparam FSM_SEND_CMD = 1;
    localparam FSM_DONE = 2;
    localparam FSM_TIMER = 3;
    
    reg [2:0] FSM_state = FSM_IDLE;
    reg [2:0] FSM_return_state;
    reg [31:0] timer = 0; 
    
    always@(posedge clk) begin
    
        case(FSM_state)
            
            FSM_IDLE: begin 
                FSM_state <= start ? FSM_SEND_CMD : FSM_IDLE;
                rom_addr <= 0;
                done <= start ? 0 : done;
            end
            
            FSM_SEND_CMD: begin 
                case(rom_data)
                    16'hFFFF: begin //end of ROM
                        FSM_state <= FSM_DONE;
                    end
                    
                    16'hFFF0: begin //delay state 
                        timer <= (CLK_FREQ/100); //10 ms delay
                        FSM_state <= FSM_TIMER;
                        FSM_return_state <= FSM_SEND_CMD;
                        rom_addr <= rom_addr + 1;
                    end
                    
                    default: begin //normal rom commands
                        if (SCCB_interface_ready) begin
                            FSM_state <= FSM_TIMER;
                            FSM_return_state <= FSM_SEND_CMD;
                            timer <= 0; //one cycle delay gives ready chance to deassert
                            rom_addr <= rom_addr + 1;
                            SCCB_interface_addr <= rom_data[15:8];
                            SCCB_interface_data <= rom_data[7:0];
                            SCCB_interface_start <= 1;
                        end
                    end
                endcase
            end
                        
            FSM_DONE: begin //signal done 
                FSM_state <= FSM_IDLE;
                done <= 1;
            end
                           
                
            FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
                SCCB_interface_start <= 0;
            end
        endcase
    end
endmodule

 

처음 부분에서 초기화를 시키고 있다.

 

initial begin
    rom_addr = 0;
    done = 0;
    SCCB_interface_addr = 0;
    SCCB_interface_data = 0;
    SCCB_interface_start = 0;
end

 

State 로 넘어간다.

 

FSM_IDLE: begin 
    FSM_state <= start ? FSM_SEND_CMD : FSM_IDLE;
    rom_addr <= 0;
    done <= start ? 0 : done;
end

 

start 입력이 들어오면 다음 state 인 FSM_SEND_CMD 로 넘어가고, 아니면 유지한다.

특이하게 done 값을 유지하고 있다. start 가 시작되면 다시 done 은 0으로 초기화된다.

 

FSM_SEND_CMD: begin 
    case(rom_data)
        16'hFFFF: begin //end of ROM
            FSM_state <= FSM_DONE;
        end
        
        16'hFFF0: begin //delay state 
            timer <= (CLK_FREQ/100); //10 ms delay
            FSM_state <= FSM_TIMER;
            FSM_return_state <= FSM_SEND_CMD;
            rom_addr <= rom_addr + 1;
        end
        
        default: begin //normal rom commands
            if (SCCB_interface_ready) begin
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_SEND_CMD;
                timer <= 0; //one cycle delay gives ready chance to deassert
                rom_addr <= rom_addr + 1;
                SCCB_interface_addr <= rom_data[15:8];
                SCCB_interface_data <= rom_data[7:0];
                SCCB_interface_start <= 1;
            end
        end
    endcase
end

 

rom_data 를 보고 FFFF signal(롬 종료 시그널) 이 오면 init command 보내는 것을 종료한다.

이 부분은 OV7670 내부 레지스터 초기 세팅을 하는 부분이라고 봐도 좋다.

 

여기도 따로 delay state 를 생성해 유지한다. FFF0 signal 이 오면 delay 10ms 만큼 delay 시킨다.

 

기본적으로는 SCCB interface ready flag가 뜨면 command 를 계속 보낸다.

rom 파일을 보게 되면 이상하게도 16비트가 나오는데, 앞은 reg address, 뒤는 value 로 나뉘는 것을 여기서 볼 수 있다.

 

결론은 rom 파일 내부 init command 를 모두 버스로 보내기 위해 사용하는 모듈이라고 보면 되겠다.

 

Done 상태는 뭐 없으니 넘어가도록 한다.

 


 

다시 camera_configure 로 온다.

tb 먼저 보도록 한다.

 

module camera_configure_tb;

    // inputs

    reg clk;
    reg start;
    
    //outputs 
    
    wire done;
    wire sioc;
    wire siod;
    
    camera_configure dut1
        (
        .clk(clk),
        .start(start),
        .sioc(sioc),
        .siod(siod),
        .done(done)
        );
        
        always #5 clk = ~ clk;
        
        initial begin
        clk = 0;
        start = 0;
        #100;
        start = 1;
        #10;
        start = 0;
        @(posedge done) $finish;
        end
endmodule

 

그냥 start 만 넣어주는 코드라고 봐도 무방하겠다.

FPGA 에 구현했다면, 마치 버튼을 눌러 동작 시작을 시켰다와 같다.

 

camera_configure.v 로 가 본다.

 

module camera_configure
    #(
    parameter CLK_FREQ=25000000
    )
    (
    input wire clk,
    input wire start,
    output wire sioc,
    output wire siod,
    output wire done
    );
    
    wire [7:0] rom_addr;
    wire [15:0] rom_dout;
    wire [7:0] SCCB_addr;
    wire [7:0] SCCB_data;
    wire SCCB_start;
    wire SCCB_ready;
    wire SCCB_SIOC_oe;
    wire SCCB_SIOD_oe;
    
    assign sioc = SCCB_SIOC_oe ? 1'b0 : 1'bZ;
    assign siod = SCCB_SIOD_oe ? 1'b0 : 1'bZ;
    
    OV7670_config_rom rom1(
        .clk(clk),
        .addr(rom_addr),
        .dout(rom_dout)
        );
        
    OV7670_config #(.CLK_FREQ(CLK_FREQ)) config_1(
        .clk(clk),
        .SCCB_interface_ready(SCCB_ready),
        .rom_data(rom_dout),
        .start(start),
        .rom_addr(rom_addr),
        .done(done),
        .SCCB_interface_addr(SCCB_addr),
        .SCCB_interface_data(SCCB_data),
        .SCCB_interface_start(SCCB_start)
        );
    
    SCCB_interface #( .CLK_FREQ(CLK_FREQ)) SCCB1(
        .clk(clk),
        .start(SCCB_start),
        .address(SCCB_addr),
        .data(SCCB_data),
        .ready(SCCB_ready),
        .SIOC_oe(SCCB_SIOC_oe),
        .SIOD_oe(SCCB_SIOD_oe)
        );
    
endmodule

 

기본적으로 지금까지 봤던 모듈들을 재사용하는데, 한 가지 read 가 없다.

여기서는 SCCB interface 를 통해 동작시키는 것 까지가 메인이 되겠다.

 

전체 파형은 다음과 같다.

 

 

read 는 이 이후, SCCB init register setting 이 끝난 이후의 vsync, href 값을 판단해서 들고 와서 hdmi, vga 등 화면을 띄울 수 있는 인터페이스에 픽셀 데이터를 넘겨주면 될 것 같다.

반응형

'Verilog' 카테고리의 다른 글

[Verilog] SCCB & OV7670 - more1  (0) 2024.05.28
[Verilog] SCCB Interface & OV7670  (0) 2024.04.16
반응형

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

 

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

Verilog modules required to get the OV7670 camera working - westonb/OV7670-Verilog

github.com

를 참고했다.

 

SCCB가 어떻게 움직이는지 보기 위해 modelsim 으로 SCCB_interface_tb.v 를 시뮬레이션 돌려 보았다.

 

봐야 할 코드 부분은 다음과 같다.

 

SCCB_interface 
    #(.CLK_FREQ(800), .SCCB_FREQ(20)) 
    dut1
    (
    .clk(clk),
    .start(start),
    .address(address),
    .data(data),
    .ready(ready),
    .SIOC_oe(SIOC_oe),
    .SIOD_oe(SIOD_oe)
    );
    
    always #5 clk=~clk;
    
    initial begin
        clk = 0;
        start = 0;
        address = 0;
        data = 0;
        
        #100;
        address = 8'hAA;
        data = 8'h77;
        start = 1;
        
        #10;
        start = 0;
        address = 0;
        data = 0;
    end

 

시뮬레이션용으로 Clock freq를 800, bus freq를 20으로 설정해 뒀다.

클럭은 10ns 마다 pos edge 를 가진다.

 

처음 clk, start, address, data 가 0이고

 

100 ns 가 지나면 address에 AA, data에 77, 시작 값을 가진다.

 

그리고 그 후 10ns 가 지나면 start, address, data를 0으로 돌린다.

 

 이런 경우 아마 내부에 ready 플래그를 둬서 start 입력에도 내부 동작이 끝나지 않았다면 동작하지 않게 만들 가능성이 크다.

 

SCCB interface 코드를 한 번 보도록 한다.

 

module SCCB_interface
#(
    parameter CLK_FREQ = 25000000,
    parameter SCCB_FREQ = 100000
)
(
    input wire clk,
    input wire start,
    input wire [7:0] address,
    input wire [7:0] data,
    output reg ready,
    output reg SIOC_oe,
    output reg SIOD_oe
    );
    
    localparam CAMERA_ADDR = 8'h42;
    localparam FSM_IDLE = 0;
    localparam FSM_START_SIGNAL = 1;
    localparam FSM_LOAD_BYTE = 2;
    localparam FSM_TX_BYTE_1 = 3;
    localparam FSM_TX_BYTE_2 = 4;
    localparam FSM_TX_BYTE_3 = 5;
    localparam FSM_TX_BYTE_4 = 6;
    localparam FSM_END_SIGNAL_1 = 7;
    localparam FSM_END_SIGNAL_2 = 8;
    localparam FSM_END_SIGNAL_3 = 9;
    localparam FSM_END_SIGNAL_4 = 10;
    localparam FSM_DONE = 11;
    localparam FSM_TIMER = 12;
    
    
    initial begin 
        SIOC_oe = 0;
        SIOD_oe = 0;
        ready = 1;
    end
    
    reg [3:0] FSM_state = 0;
    reg [3:0] FSM_return_state = 0;
    reg [31:0] timer = 0;
    reg [7:0] latched_address;
    reg [7:0] latched_data; 
    reg [1:0] byte_counter = 0;
    reg [7:0] tx_byte = 0;
    reg [3:0] byte_index = 0;
    
    
    always@(posedge clk) begin
        
        case(FSM_state) 
        
            FSM_IDLE: begin
                byte_index <= 0;
                byte_counter <= 0;
                if (start) begin 
                    FSM_state <= FSM_START_SIGNAL;
                    latched_address <= address;
                    latched_data <= data;
                    ready <= 0;
                end
                else begin
                    ready <= 1;
                end
            end
            
            FSM_START_SIGNAL: begin //comunication interface start signal, bring SIOD low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_LOAD_BYTE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
                SIOD_oe <= 1;
            end
            
            FSM_LOAD_BYTE: begin //load next byte to be transmitted
                FSM_state <= (byte_counter == 3) ? FSM_END_SIGNAL_1 : FSM_TX_BYTE_1;
                byte_counter <= byte_counter + 1;
                byte_index <= 0; //clear byte index
                case(byte_counter)
                    0: tx_byte <= CAMERA_ADDR;
                    1: tx_byte <= latched_address;
                    2: tx_byte <= latched_data;
                    default: tx_byte <= latched_data;
                endcase
            end
            
            FSM_TX_BYTE_1: begin //bring SIOC low and and delay for next state 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_2;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 1; 
            end
            
            FSM_TX_BYTE_2: begin //assign output data, 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_3;
                timer <= (CLK_FREQ/(4*SCCB_FREQ)); //delay for SIOD to stabilize
                SIOD_oe <= (byte_index == 8) ? 0 : ~tx_byte[7]; //allow for 9 cycle ack, output enable signal is inverting
            end
            
            FSM_TX_BYTE_3: begin // bring SIOC high 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_4;
                timer <= (CLK_FREQ/(2*SCCB_FREQ));
                SIOC_oe <= 0; //output enable is an inverting pulldown
            end
            
            FSM_TX_BYTE_4: begin //check for end of byte, incriment counter
                FSM_state <= (byte_index == 8) ? FSM_LOAD_BYTE : FSM_TX_BYTE_1;
                tx_byte <= tx_byte<<1; //shift in next data bit
                byte_index <= byte_index + 1;
            end
            
            FSM_END_SIGNAL_1: begin //state is entered with SIOC high, SIOD high. Start by bringing SIOC low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_2;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 1;
            end
            
            FSM_END_SIGNAL_2: begin // while SIOC is low, bring SIOD low 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_3;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOD_oe <= 1;
            end
            
            FSM_END_SIGNAL_3: begin // bring SIOC high
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_4;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
            end
            
            FSM_END_SIGNAL_4: begin // bring SIOD high when SIOC is high
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_DONE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOD_oe <= 0;
            end
            
            FSM_DONE: begin //add delay between transactions
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_IDLE;
                timer <= (2*CLK_FREQ/(SCCB_FREQ));
                byte_counter <= 0;
            end
            
            FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
            end
        endcase
    end
    
    
endmodule

 

위의 테스트벤치에서 CLK_FREQ를 800, SCCB_FREQ를 20으로 제한했다.

 

initial begin 
        SIOC_oe = 0;
        SIOD_oe = 0;
        ready = 1;
    end

내부 파라미터는 넘어가고, SIOC_oe, SIOD_oe, ready 를 init 에서 할당해주고 있다. 순차적으로 안 해도 될 것 같은 부분이다.

 

State machine 을 한 번 본다.

FSM_IDLE: begin
                byte_index <= 0;
                byte_counter <= 0;
                if (start) begin 
                    FSM_state <= FSM_START_SIGNAL;
                    latched_address <= address;
                    latched_data <= data;
                    ready <= 0;
                end
                else begin
                    ready <= 1;
                end
            end

 

IDLE 상태에서 입력(start)이 들어오면 다음 상태로 변화시키는 코드다.

기본 값들은 초기화 혹은 리셋 시켜주면서, 입력이 들어오면 현재 address, data 를 레지스터에 저장한다.

뒤에서 이 값들(latched_address, latched_data)을 변화시키는 부분이 없었으므로 계속 값을 가져갈 것으로 보인다.

 

FSM_START_SIGNAL: begin //comunication interface start signal, bring SIOD low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_LOAD_BYTE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
                SIOD_oe <= 1;
            end

 

START_SIGNAL 부분은 IDLE에서 start 입력이 들어오면 다음 클럭에서 state가 변화되는 부분이다.

특이하게 return state 를 지정하고 있는데, 이는 다음 state 인 TIMER 로 인해 필요한 부분이다.

또, timer 는 FPGA의 클럭과 버스를 동기화하기 위해 사용하고, 

SIOC_oe(SCL) 은 0을 유지하고, SIOD_oe(SDA) 는 1로 올라간다. 이는 SCCB의 start signal이다.

I2C 와 다른 인터페이스인 이유이기도 하다.

 FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
            end

 

자주 등장하는 TIMER state 를 본다.

다른 건 없고, 시간 때우기 용으로 넣어 놓은 state 라고 보면 좋다.

timer 가 0이 될 때 까지 현재 상태를 유지하는데, 이 때 SCL, SDA 값도 유지된다.

위의 waveform 에서 FSM_state 가 1100(decimal 값으로 12)에서 길게 유지하는 것을 볼 수 있다.

 실제로 파형을 보면 timer 이외에는 FSM_state 가 1100(binary) 인 때는 움직이지 않고 유지하는 것을 볼 수 있음.

 

timer가 0이 되어 return_state 로 돌아가도록 한다.

현재 return_state 는 FSM_LOAD_BYTE 로 지정되어 있다.

FSM_LOAD_BYTE: begin //load next byte to be transmitted
    FSM_state <= (byte_counter == 3) ? FSM_END_SIGNAL_1 : FSM_TX_BYTE_1;
    byte_counter <= byte_counter + 1;
    byte_index <= 0; //clear byte index
    case(byte_counter)
        0: tx_byte <= CAMERA_ADDR;
        1: tx_byte <= latched_address;
        2: tx_byte <= latched_data;
        default: tx_byte <= latched_data;
    endcase
end

 

FSM_state 가 byte_counter 가 3이면 분기하는 조건이 있는걸 봐선 여기서 바이트 카운터를 올리는 것이 있던지, 다른 state 에서 바이트 카운터를 올리는 옵션이 있을 것이다.

실제로 다음 코드에서 바이트코드를 증가시키는 코드가 있다.

그리고 뭔지 모르겠지만 바이트 인덱스를 0으로 만든다. 다른 곳에서 바이트 인덱스를 사용하고 여기 다시 돌아와서 뭔가 한다는 의미일 확률이 높겠다.

그리고 바이트카운터에 따라서 tx_byte 의 값을 조정한다.

 

CAMERA_ADDR 은 localparam 에서 42로 지정했다.

 

현재 LOAD_BYTE 에서의 값은 다음과 같다.

 

현재 막 LOAD_BYTE에 도착했다.

다음 클럭에서 바뀌는 값들을 보도록 한다.

FSM_state 는 byte_counter 가 3이 아니므로(0임) FSM_TX_BYTE_1

byte_counter 은 다음 클럭에 1로

byte_index 는 다음 클럭에 0으로

tx_byte 는 다음 클럭에 CAMERA_ADDR 의 값을 가짐.

non-blocking(<=) 이기 때문에 순차적 처리가 아닌 병렬 처리를 함.

 

 

실제 다음 클럭의 값임.

 

이번에 온 TX_BYTE_1 을 살펴본다.

 

FSM_TX_BYTE_1: begin //bring SIOC low and and delay for next state 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_2;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 1; 
end

 

다음 클럭에 SCL 을 변화시킨다. 실제로 통신을 시작하려는 부분임.

이 부분에서 SCL = 1, SDA = 1 이 되므로 통신 시작, 이제 8비트의 데이터를 보내고 1비트의 ACK 를 수신해야 함.

하지만 다음 state 가 TIMER 로 다시 간다.

이로 인해 SCCB 인터페이스와 FPGA 사이의 클럭 차이가 굉장히 많이 나기 때문에 클럭 손실이 크다.

따라서 I2C 를 느려터진 인터페이스라고 하는 이유다.

 

현재 코드 상에서는 시뮬레이션이 아닌, 기기에서의 클럭을 25Mhz, SCCB freq 를 100Khz 로 정의해 놓았다.

timer 에서 SCCB 주파수에 4를 곱하는 이유는 아마 I2C 처럼 고속 모드(400Khz) 지원을 위함이지 싶다.

아무튼 저 값을 나누면 62.5 가 나오는데 소수 버림으로 62 가 나온다.

아무것도 안하고 벌써 62 클럭을 2번이나 낭비했다.

 

아무튼 return state 를 TX_BYTE_2 로 정의하고 다시 timer 에 갔다 온다.

 

FSM_TX_BYTE_2: begin //assign output data, 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_3;
    timer <= (CLK_FREQ/(4*SCCB_FREQ)); //delay for SIOD to stabilize
    SIOD_oe <= (byte_index == 8) ? 0 : ~tx_byte[7]; //allow for 9 cycle ack, output enable signal is inverting
end

 

timer 에 갔다 다시 돌아온 state 다.

근데 다음 state 가 다시 timer 이다.

여기서는 잘 보니, 한 바이트에 대해 처리하고 있는 것을 볼 수 있다.

SDA 에 byte index 가 8(9번째 비트, ACK) 가 아니면 tx_byte[7]을 반전시켜 넣고 있다.

 

return state 를 TX_BYTE_3으로 지정하고 다시 timer 로 간다. (62*3)

 

FSM_TX_BYTE_3: begin // bring SIOC high 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_4;
    timer <= (CLK_FREQ/(2*SCCB_FREQ));
    SIOC_oe <= 0; //output enable is an inverting pulldown
end

 

이번에도 다음 state 는 TIMER 이다.

여기서는 SCL 을 0으로 바꾼다. 

다른 건 없다.

return state 를 TX_BYTE_4 로 지정하고 다음 timer 로 간다. (62*4)

 

 

 

FSM_TX_BYTE_4: begin //check for end of byte, incriment counter
    FSM_state <= (byte_index == 8) ? FSM_LOAD_BYTE : FSM_TX_BYTE_1;
    tx_byte <= tx_byte<<1; //shift in next data bit
    byte_index <= byte_index + 1;
end

 

byte index 가 8이 아니면(ack) 다시 TX_BYTE_1 로 돌아간다. 아니면 LOAD_BYTE 로 간다.

tx_byte 는 하나 shift 하는데, 이는 이미 우리가 반전시킨 tx_byte[7] 을 SDA 로 보냈기 때문이다.

그리고 byte_index 를 1 증가시킨다.

 

대략적으로 다음과 같은 구조가 되었다.

 

결국 TX_BYTE_1 ~ TX_BYTE_4 를 돌면서 1 Bit 를 보낸 것과 같다.

근데 TX_BYTE 에 사용한 클럭은 

LOAD, TX1, TX2, TX3, TX4, TIMER*3 = 5 + 186 = 191 clock 이다.

25Mhz cpu 를 사용하면 7,640 ns = 7.64us 가 소요된 것이다.

이걸 이제 byte_index 가 8이 될 때 까지, 총 9번 실행해 준다.

그럼 이제 CAMERA_ADDRESS 를 SCCB interface 로 보내는 것이 완료 된 것이다.

 

그리고 다시 LOAD_BYTE 로 오게 된다.

byte_counter 에 따라, 3이 될 때 까지 반복한다. (기기 address, 내부 레지스터 address, 쓸 값)

 

3이 되었으면, END_SIGNAL_1 로 간다.

 

FSM_END_SIGNAL_1: begin //state is entered with SIOC high, SIOD high. Start by bringing SIOC low
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_2;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 1;
end

 

여기서는 통신을 종료하기 위해 STOP 시그널을 만드려는 쪽 같다.

SCL 을 1로 만든다.

return state 를 END_SIGNAL_2 로 지정하고, timer 로 간다.

파형으로 보면 저 위치이다.

 

자세하게 보면 이렇다.

 

FSM_END_SIGNAL_2: begin // while SIOC is low, bring SIOD low 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_3;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOD_oe <= 1;
end

 

이번엔 SDA를 1로 만든다.

return state 를 END_SIGNAL_3 으로 지정하고 timer 로 간다.

파형은 다음과 같다.

 

FSM_END_SIGNAL_3: begin // bring SIOC high
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_4;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 0;
end

 

이번에 SCL 을 다시 0으로 만든다.

return state 를 END_SIGNAL_4 로 지정하고 timer 로 간다.

파형은 다음과 같다.

 

 

FSM_END_SIGNAL_4: begin // bring SIOD high when SIOC is high
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_DONE;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOD_oe <= 0;
end

 

마지막으로 SDA 를 0으로 내린다.

주석 적어놓은 거 보면 I2C 를 완전히 반대로 구현해 놓은 것과 동일한 것 같다.

timer 를 지나고, 마지막 done state 로 간다.

 

I2C를 보게 되면 마지막이 모두 HIGH로 끝나고, SCCB 는 정 반대이다.

 

FSM_DONE: begin //add delay between transactions
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_IDLE;
    timer <= (2*CLK_FREQ/(SCCB_FREQ));
    byte_counter <= 0;
end

 

Done state 는 다음 트랜잭션이 오기 전의 딜레이 역할을 한다.

딱히 byte_counter 를 초기화 안 해도 동작에는 지장이 없을 것 같다.(IDLE에서 초기화를 하기 때문)

시간이 저렇게 표기되어 있는데 작동에 준수해야 되는 최소 시간인지는 잘 모르겠다.

 

 

반응형

'Verilog' 카테고리의 다른 글

[Verilog] OV7670 - more2  (0) 2024.05.28
[Verilog] SCCB Interface & OV7670  (0) 2024.04.16
반응형

OV7670 카메라 지원을 위해 SCCB Interface 에 대해 먼저 보려고 함.

 

다음 레퍼런스를 참고하였다.

 

https://www.waveshare.com/w/upload/1/14/OmniVision_Technologies_Seril_Camera_Control_Bus%28SCCB%29_Specification.pdf

 

DeepL 번역본을 사용해도 좋다.

 

OV7670은 2-Wire SCCB Interface 를 사용한다.

2-Wire SCCB Interface 는 단 하나의 Slave Device 와 통신하므로 다음과 같은 블록도를 가진다.

 

 

2-wire 구현을 위해서 다음 두 가지 방법 중 하나는 되어야 한다.

1. master 가 tri-state 모드에서 버스의 데이터 라인을 지원하고 유지할 수 있어야 함

2. 마스터가 데이터 라인의 tri-state 상태를 유지할 수 없는 경우 데이터 라인을 하이 또는 로우로 구동하고, 슬레이브 카메라 칩과의 통신을 하기 위해 전환되는 것을 기록하는 것

 

마스터

Signal name Signal type Description
SIO_C Output 버스가 IDLE 상태일 때 마스터가 logical 1 로 SIO_C를 구동시킴. 시스템이 일시 중단 모드일 때 logical 0으로 구동
SIO_D inout 버스가 IDLE일 때 움직이지 않고, 일시 중단 모드일 때 logical 0로 구동
PWDN Output Power down

 

슬레이브

Signal name Signal type Description
SIO_C Input 시스템이 일시 중단 모드일 때 입력 패드 종료 가능
SIO_D inout 시스템이 일시 중단 모드일 때 입력 패드 종료 가능 
PWDN Input 입력 전원 차단

 

 

SIO_C

마스터에서 구동해야 하는 단방향, Active HIGH, 컨트롤 신호

버스가 IDLE 상태일 때 logical 1 로 SIO_C를 구동시킴

전송 시작 후 SIO_C가 logical 0으로 구동되면 데이터 전송 시작

데이터 전송 중 SIO_C의 논리 1은 전송된 단일 비트를 나타냄

 

따라서 SIO_C가 0으로 구동될 때만 SIO_D가 발생 가능

단일 전송 비트의 주기를 t_CYC 라고 하고

t_CYC의 최소값은 10us을 가짐[Fig. 3-8]

 

 

SIO_D

SIO_D는 마스터 / 슬레이브에서 구동할 수 있는 양방향 신호

Don't care bit 상태(나중에 다룸)를 가질 수 있음

 

 

Transmission Cycles(전송 주기)

하나의 Phase : 9-bit 로 구성

9번째 비트(X 표시)는 데이터 전송이 R/W 인지에 따라 Don't care bit 또는 N/A bit 임.

전송에는 최대 3개의 Phase 가 포함될 수 있음

MSB가 항상 먼저 전달됨

 

2-Wire Write Transmission Cycle

2-Phase 쓰기 전송 뒤에 2-Phase 읽기 전송이 이어짐.

쓰기를 사용하는 목적은 마스터가 데이터를 읽으려는 특정 슬레이브의 하위 주소 식별을 하기 위함

9번째 비트는 Don't care bit

 

2-Wire Read Transmission Cycle

쓰기가 진행된 후 읽기가 실행됨.

쓰기와 다르게 하위 주소를 식별할 수 있는 기능 없음

8비트의 읽기 데이터와 9번째 Don't care bit 또는 N/A 비트가 포함됨

마스터는 N/A 비트를 logical 1로 구동해야 함

 

 

 


 

ID 주소

마스터가 데이터를 읽거나 쓸 선택된 슬레이브 식별하기 위한 주소

각 슬레이브는 I2C 통신처럼 2^7개의 슬레이브 존재 가능(8번째 비트는 R/W 중 선택, 0: Write, 1:Read)

Phase 1의 9번째 비트는 Don't care bit 여야 함

SIO0_OE_M_(아마 SIO_D_OE_M_을 말하는 것 같음)

마스터의 내부 Active low, I/O 활성화 신호

 

SIO0_OE_S_(아마 SIO_D_OE_S_ 를 말하는 것 같음)

슬레이브의 내부 Active low, I/O 활성화 신호

 

OE_S_ 가 OE_M_ 보다 먼저 발생하고, 마스터가 ID address 를 가져옴 (9번째 비트는 가져오지 않음)

마스터는 알 수 없는 버스 상태 전파를 방지하기 위해 Don't care bit 기간 동안 SIO_D의 입력을 마스킹하고, 입력을 강제로 0으로 설정함.

그리고 마스터는 슬레이브의 Don't care bit에 대한 응답에 관계없이 다음 단계를 계속 가져옴

 

 

 

Sub-Address, Data Read

Phase 2 에서

Write : Sub-address

Read : Data

의 8비트를 받게 될 것임.

근데 2-Wire는 Slave가 하나밖에 없으므로 Sub-address 지정할 필요가 없음

 

따라서 Read Data 만 보도록 함

 

 

슬레이브가 Phase 2 전송을 할 때 9번째 비트는 N/A 비트로 정의

마스터는 N/A 비트 기간 동안 logical 1에서 SIO_D를 구동할 책임을 가짐

그리고 선택된 Slave가 Read data 중 SIO_D 구동을 담당함

 

 

 

Don't care bit

목적 : 전송이 완료되었음을 나타내는 것

 

 

쓰고 있으면서도 제대로 파악은 되지 않는다( I2C랑 비슷하다 라고는 알려져 있다.)

OV7670 을 한번 보자.

 

 


 

OV7670이다.

 

레퍼런스도 첨부한다.

OV7670.pdf (voti.nl)

 

특징은 다음과 같다.

 

스펙은 다음과 같다.

 

 

나는 Aliexpress 에서 OV7670 모듈을 구매했고 

다음 모양과 같다

Pin 구성은 위의 사진을 보게 되면,

3.3V GND
SCL SDA
VSYNC HREF
PCLK MCLK
D7 D6
D5 D4
D3 D2
D1 D0
RESET PWDN

으로 구성된 것을 볼 수 있다.

 

[그림2] 에서 기능 블록도를 제공하는 것을 볼 수 있다.

 

 

 

설명에 따르면, 

Image Sensor Array 656 x 488 개의 픽셀을 가지고, 실제 동작은 640 x 480 픽셀이 동작한다.
Timing Generator 배열 조작과 프레임 생성을 담당한다.
내부 타이밍 신호 생성과 분배를 담당한다.
프레임률 타이밍
자동 노출 조작(Automatic Exposure Control)
외부 타이밍 (VSYNC, HREF(HSYNC), PCLK) 출력
Analog Signal Processor Automatic Gain Control
Automatic White Balance
주로 카메라 동작과 관련됨
A/D Converter Analog Signal Processing block 이후, 
베이어 패턴이 10-bit analog-to-digital Converter 로 들어감(G, BR channel)
* 베이어 패턴 참고
https://darkpgmr.tistory.com/97

이 A/D 컨버터는 12Mhz 속도로 움직이고, Pixel rate 와 완전히 동기화됨.

다른 기능들도 있는데 생략(카메라 기능에 대한 것들)
Test Pattern Generator 말 그대로 테스트용
Digital Signal Processor 이 블럭은 raw data 에서 RGB 로의 보간(interpolation) 과 일부 화질 제어를 담당함.
가장자리 강화 / RGB to YUV or YCbCr 로 변화 / 10비트 데이터를 8비트로 전환 등등
Image Scaler 이미지 외부 출력 전 모든 필요한 모든 출력 및 데이터 포맷 제어
이 블록은 VGA에서 CIF, CIF보다 작은 대부분의 크기로 YUV/RGB 출력을 변환한다.
Digital Video Port 레지스터 비트 COM2[1:0] 에 관련.. 일단 넘어감
SCCB interface 처음에 한 내용
LED and Strobe Flash
Control Output
외부 플래시, LED 관련

 

Pin 설정

 

 

하나의 Master, 하나의 Slave만 가지기 때문에 2-Wire communication 발생

가지고 있는 모듈보다 핀의 개수가 많은 것을 확인 가능하다.

OV7670만이 아닌 OV7171도 포함하는 datasheet 라서 그런 건가 생각한다.

 

 

전력이나 다른 것들은 따로 제어하기 힘드므로 타이밍을 본다.

 

그림 4~10을 참고하라고 하니 한 번 본다.

 

 

 

 

 

7~10 까지는 VGA가 아닌 다른 해상도일 때 HREF(HSYNC) 판단을 하는 것 같다.

 

그림 4로 돌아와 보자.

 

시간을 넣으면 다음과 같다.

 

Fig.6 의 경우, VGA(640*480) 으로 출력하기 위한 코드이다.

한 픽셀에 8비트 데이터가 계속 전달되는 식으로 동작한다.
아래 Note 를 보면, 

Raw Data : t_P = t_PCLK

YUV/RGB : t_P = 2 * t_PCLK 

라고 적혀있는 것을 볼 수 있다.

 

왜일까?

Fig. 11을 보자.

RGB565 output timing diagram 이다.

 

두 개의 바이트가 한 쌍을 이루는 것을 알 수 있다. 

그리고 다른 색상보다 Green 이 1비트 더 많이 표현한다.

사람의 눈은 초록색을 더 민감하게 인지하기 때문에 그렇다.. 라고 한다.

 

다음 그림인 Fig. 12 를 보자.

RGB 555 output timing diagram 이다.

 

R, G, B 모두 5비트로 맞춰주기 위해 처음 바이트의 MSB는 Don't care 상태이다.

 

다시 Fig. 6 으로 돌아가도록 하자.

 

 

단순하게 생각해 보자.

VGA(640 * 480) 는 다음의 타이밍도를 따른다.

 

VGA Signal 640 x 480 @ 60 Hz Industry standard timing (tinyvga.com)

 

VGA Signal 640 x 480 @ 60 Hz Industry standard timing

VGA Signal 640 x 480 @ 60 Hz Industry standard timing General timing Screen refresh rate60 HzVertical refresh31.46875 kHzPixel freq.25.175 MHz Horizontal timing (line) Polarity of horizontal sync pulse is negative. Scanline partPixelsTime [µs]Visible area

tinyvga.com

 

상세하게 보면, 좀 다르긴 하다.

그래도 Front porch, sync pulse, back porch 등이 존재해서 기존 보이는 화면보다 뭐가 더 추가되었구나 하는 것을 알면 될 것 같다.

 

OV7670에서 제시하는 VGA 는 다음과 같다.

Col 784 Lines

Row 510 Lines

Col_active 640 Lines

Row_active 480 Lines

나머지는 제어 관련이라고 생각하면 좋을 것 같다.

 

아까 손으로 직접 그린 그림에 t_P가 1 clock 이라고 적혀있는데,

이는 1 pixel clock이다. 

VGA ref 를 참고하면 동작에 25 Mhz 가 나온다.

그런데, 이는 60Hz 즉 60FPS 상에서 25Mhz가 필요하다.

 

카메라는 30FPS 촬영을 한다.

대충 생각해 봐도 손실이 있을 것 같은 느낌이 든다.(Raw 데이터의 경우)

 

일단 1초에 30frame 을 나타내려면 대략 33ms 당 1 frame 을 뽑아내야 한다.

그러기 위해서는 33ms 안에 784 * 510 = 399,840 pixels 에 대해 처리가 끝나야 된다는 소리이다.

 

t_P (pixel clock) 를 25Mhz 라고 가정해 보자. 그럼 1 clock 에 40ns 가 소요된다. (Raw 데이터의 경우)

1번의 row 가 끝나기 위해서는 784번의 clock이 필요하고, 510번 반복되어야 한다.

결국 399,840 픽셀에 걸리는 처리 시간과 동일해진다.

40ns * 399,840 = 15,993,600 ns = 약 16ms 

이 시간은 60fps 를 뽑아낼 수 있는 시간대이다.(Raw data의 경우)

 

YUV/RGB 데이터에 대해서는 어떨까

t_P = 2 * t_PCLK 이다.

가로 사이즈가 2배로 걸리고 세로는 그대로이다.

그러면 1번의 row가 끝나기 위해서는 1568번의 clock이 필요하고, 510번 반복되어야 한다.

= 799,680 * 40ns = 31,987,200 약 32ms

거의 30 프레임을 나타낼 수 있게 된다.

 

추후 작성 예정

 

 

 

 

 

 

 

 

반응형

'Verilog' 카테고리의 다른 글

[Verilog] OV7670 - more2  (0) 2024.05.28
[Verilog] SCCB & OV7670 - more1  (0) 2024.05.28

+ Recent posts