Posts FPGA를 이용한 방법용 감시장치
Post
Cancel

FPGA를 이용한 방법용 감시장치

27기 박제윤, 28기 이형주, 28기 김재욱

1. 프로젝트 설명

초기 계획안 제출 및 중간발표에 비교하여 달라진 부분이 일부 있으므로, 프로젝트 개요에 대하여 다시 설명하겠다.

-프로젝트 목표

카메라 모듈을 통해 들어오는 데이터를 받아 프레임별로 프레임 버퍼에 저장하고, 프레임 간의 비교를 통하여 화면상에서 움직임이 있었는지를 체크해 LED로 표시. FPGA 보드와 Verilog HDL을 통해 구현한다.

-활용 장비 및 참고자료

활용 장비

Terasic DE10_Nano board: Cycle V SoC FPGA 5CSEBA6U23I7

SZH-EK094: OV7670 VGA 카메라

참고서적

Intel FPGA를 이용한 Processor/Logic 설계입문! 오리지널 마이크로컴퓨터 만들기


책의 예제의 코드를 활용한 부분이 일부 존재하며, 저작권 관련 우려가 있으므로 해당 부분은 부득이하게 업로드하지 않겠다. (프로젝트 팀에서 만든 코드만 업로드할 것임)

(프로젝트 이해를 위한 배경설명)

  1. 먼저, OV 7670에서 데이터를 출력하는 방식을 설명하겠다.

-PCLK : 카메라에서 들어오는 클럭. 25MHz

-수평 동기화 신호(CamHsync): 주기는 1568 cycle이며(PCLK 기준), 2cycle에 1개의 pixel이 입력된다.(앞의 cycle 에 Rg, 뒤의 사이클에 gB로 나뉘어 입력) 2cycle을 t_p라고 할 때, 수평 동기화의 한 주기는 45/640/19/80 (단위 t_p)로 쪼갤 수 있으며, 중간의 640 t_p 가 유효 화소 기간이다.

-수직 동기화 신호(CamVsync): 주기는 510 CamHsync. 수직 동기화의 한 주기는 1 수평동기화를 단위로 3/17/480/10으로 쪼갤 수 있으며, 유효 라인 기간은 중간의 480 라인이다.

또한, CamHsync는 유효 라인 기간이 아닌 부분은 토글하지 않는다.

-SCCB 통신을 통하여 OV7670의 레지스터를 설정 가능하며, 이는 NIOS 프로세서(FPGA위에 올릴 수 있는 가상 프로세서)를 통해 수행 가능하다. 레지스터를 설정하여 카메라 모듈의 작동과 관련된 수치를 입력하여 실제에 가까운 이미지를 얻을 수 있다. -> 이 부분의 경우 참고서적의 예제를 그대로 가져다 쓰면 되지만 현재 프로젝트 팀에서 쓰는 Quartus와 연동된 Eclipse의 쪽에서 발생한 문제로(make를 못 찾는다.) 인해 프로그래밍이 불가능해서 현재 저 부분은 처리되지 않았다.

자세한 내용이 궁금하다면 datasheet를 참고하라. (구글에 OV 7670 datasheet 검색하면 바로 나온다.)

  1. 다음으로, 프로젝트 파일의 구조에 대하여 간단히 설명하겠다.

Top-level entity는 VideoProc.v이며, 이것이 포함하는 모듈들은 2개이다. nios2e:u0(NIOS II)와 VideoProcCore.v이다. VideoProcCore.v는 다시 CAM_CTRL과 FRAME_BUFFER 모듈을 포함한다. FRAME_BUFFER 모듈은 8개의 FRAME 모듈 인스턴스를 포함한다.

각 모듈의 역할은 다음과 같다.

-VideoProc.v: Top-level entity로서 회로 전체의 입출력을 정의하고, 하위 모듈을 호출

-VideoProcCore.v: CAM_CTRL과 FRAME_BUFFER 호출(카메라 모듈의 레지스터 설정 이외에 모든 영상처리 종합)

-CAM_CTRL: 카메라 모듈로부터 들어온 데이터를 받아 Rg, gB로 나뉘어 있는 데이터를 하나의 픽셀 데이터로 합치고, 수직동기화/수평동기화 신호를 이용하여 현재 읽어들이는 데이터의 line과 line에서의 위치를 알려준다. 해당 코드는 책의 예제를 그대로 활용하였다. 입력은 리셋(RST_N), FPGA 보드 내부 클럭(CLK, 50MHz 발진에 연결), 카메라 클럭(PCLK), 카메라로부터 들어오는 데이터(CamData), 수직동기화 신호(CamVsync), 수평동기화 신호(CamHsync). 출력으로 픽셀 데이터와 픽셀을 쓸 주소, 현재 라인을 출력.

-FRAME_BUFFER: 데이터를 받아 과거 8개의 프레임을 8개의 FRAME 모듈 인스턴스에 저장한다. 그리고 현재 쓰고 있는 프레임보다 한 번호 전(가장 최근 프레임)과 한 번호 후(가장 이전 프레임)의 차를 이용하여 움직임이 있었는지를 계산한다.

-FRAME: FRAME 하나 저장을 위한 크기의 SRAM.

2. 핵심 코드 설명

직접 작성/수정한 코드인 FRAME.v, FRAME_BUFFER.v의 중요한 부분들을 설명하겠다. 코드와 함께 모든 부분을 보고 싶다면 같이 업로한 소스코드를 참고하기 바란다.

FRAME.v

일반적인 SRAM의 설계와 동일하다. 동시에 읽고 쓰는 것은 불가능하게 설계하였다. WR_N 신호에 따라 읽기인지, 쓰기인지가 결정된다.

FRAME_BUFFER.v

8개의 Frame에서 번갈아가며 하나의 Frame씩 쓰기를 수행. 수직동기화 EDGE 신호에서 frame의 count를 업데이트한다. 해당되는 FRAME은 쓰기, 나머지는 읽기가 된다. 또한, 해당 프레임의 +1, -1 프레임에서 읽어들인 데이터는 R, G, B로 분리한다. 이때, 각 색상 요소별로 비트 수가 다름에 유의하라. 색상 별로 차이를 구하고(diffX), 이의 절대값을 구한다. (absX) 이 차이의 절대값을 합치는 방법은 책에 나와 있는 부분을 참고하였다. (따라서 임계값이나 픽셀에서 차이 절대값을 구하는 공식 등이 어떻게 나왔는지는 잘 모른다.) 책에서 한 픽셀의 차이의 크기를 구하는 공식은 다음과 같다.

y=8r+16g+3b

다시, 해당 데이터를 유효 화소 구간에서 누적해 한 라인의 차이 절대값 누적을 구한다.

이를 1/256하고 유효 라인 구간에서 누적한다.

마지막으로, 누적된 한 프레임의 차이가 256을 넘으면 유의미한 움직임이 있는 것으로 판단한다. (MDET_EDGE_L)

움직임이 판단되었을 때, 신호를 일정 시간 유지시켜주기 위해서 대략 255 프레임 정도의 기간동안 signal이 유지되도록 한다.

3. 기타

카메라 모듈은 GPIO를 통해 연결 가능하며 Top-level entity와 GPIO 핀의 연결은 Quartus의 Pin-planner를 통해 수행하였다.

위에서 언급하였듯이, NIOS II이용한 레지스터 설정은 실패하였다. 그러나 해당 부분은 단순히 NIOS II위의 소프트웨어에서 실행 시 처음 1회 레지스터를 써주기만 하면 된다.

부품 주문 실수로 인해 실제로 테스트에 실패하였다.

FRAME의 크기를 그대로 가져가면 팀이 보유한 가장 좋은 컴퓨터에서도 컴파일에 필요한 메모리가 부족하여 컴파일에 실패하였다. (FRAME의 크기를 임의로 작게 잡으면 성공한다.)

FRAME이 보유한 보드에서 돌릴 수 있는 크기인지는 FRAME 크기를 그대로 집어넣은 컴파일이 돌아가지 않아 어느 정도의 Resource를 사용하는지에 대한 Summary가 나오지 않아 모르겠다. (아마 8 frame을 넣기에는 부족할 듯 하다. 2frame해서 체크하는 것은 가능하지 않을까 싶다)

위에서도 언급했지만, 프로젝트의 일부가 외부에서 코드를 가져다 쓴 부분이 있으므로 CAM_CTRL의 소스코드는 공개 불가능하다. 실제로 작성한 부분인 FRAME, FRAME_BUFFER를 봐주기 바란다.

소스코드

+VideoProc.v

module VideoProc ( CLK, RST_N, XCLK, SCL, SDA, CamHsync, CamVsync, PCLK, CamData, MDET_L );

input CLK, RST_N; output XCLK; output SCL, SDA; input CamHsync; input CamVsync; input PCLK; input [7:0]CamData; output MDET_L;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
VideoProcCore VideoProcCore_inst(
	.CLK(CLK),
	.RST_N(RST_N),
.XCLK(XCLK),
.CamHsync(CamHsync),
.CamVsync(CamVsync),
.PCLK(PCLK),   .CamData(CamData),   .MDET_L(MDET_L)
);

nios2e u0(
	.clk_clk(CLK),
	.reset_reset_n(RST_N),
	.sda_external_connection_export(SDA),
	.scl_external_connection_export(SCL),
);	 endmodule

+VideoProcCore.v

module VideoProcCore ( CLK, RST_N, XCLK, CamHsync, CamVsync, PCLK, CamData, MDET_L );

input CLK, RST_N; output XCLK; input CamHsync; input CamVsync; input PCLK; input [7:0]CamData; output MDET_L;

wire CamHsync_EDGE, CamVsync_EDGE; wire [9:0] LB_WR_ADDR; wire [15:0] LB_WR_DATA; wire LB_WR_N; wire [15:0] buf_RGB; wire [8:0] CamLineCount; wire [15:0] CamPixCount4x;

assign XCLK = CamPixCount4x[0];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CAM_CTRL CAM_CTRL_inst(
.CLK(CLK),
.RST_N(RST_N),
.PCLK(PCLK),
.CamHsync(CamHsync),
.CamVsync(CamVsync),
.CamData(CamData),
.LB_WR_ADDR(LB_WR_ADDR),
.LB_WR_DATA(LB_WR_DATA),
.LB_WR_N(LB_WR_N),
.CamHsync_EDGE(CamHsync_EDGE),
.CamVsync_EDGE(CamVsync_EDGE),
.CamLineCount(CamLineCount),
.CamPixCount4x(CamPixCount4x) );

FRAME_BUFFER FRAME_BUFFER_inst(
.CLK(CLK),
.RST_N(RST_N),
.WR_N(WR_N),
.CamLineCount(CamLineCount),
.CamVsync_EDGE(CamVsync_EDGE),
.WR_ADDR(LB_WR_ADDR),
.WR_DATA(LB_WR_DATA),	
.MDET_L(MDET_L) );

endmodule


+FRAME.v

module FRAME ( CLK, CS_N, WR_N, WRADDR_ROW, RDADDR_ROW, WRADDR_COL, RDADDR_COL, WRDATA, RDDATA ); //big 2-D sram. control signals are from FRAME_BUFFER

input CLK; input CS_N, WR_N; input [8:0] WRADDR_ROW; input [8:0] RDADDR_ROW; input [9:0] WRADDR_COL; input [9:0] RDADDR_COL; input [15:0] WRDATA; output [15:0] RDDATA;

reg [15:0] RAMDATA [512:0][1024:0]; //2-D array: save 1 frame reg [15:0] RDDATA_sig;

/////////////////////////////////////////////////////// // sram write always @(posedge CLK) begin if (CLK == 1’b1) begin if(CS_N == 0 && WR_N == 0) begin RAMDATA[WRADDR_ROW][WRADDR_COL] <= WRDATA; end end end

/////////////////////////////////////////////////////// // sram read always @(posedge CLK) begin if (CLK == 1’b1) begin if(CS_N == 0 && WR_N == 1) begin RDDATA_sig <= RAMDATA[RDADDR_ROW][RDADDR_COL]; end else begin RDDATA_sig <= 0; end end end

assign RDDATA = RDDATA_sig;

endmodule


+FRAME_BUFFER.v

module FRAME_BUFFER( CLK, WR_N, RST_N, CamLineCount, CamVsync_EDGE, WR_ADDR, WR_DATA, MDET_L );

input CLK; input WR_N; input RST_N; input[8:0] CamLineCount; input[9:0] WR_ADDR; input[15:0] WR_DATA; input CamVsync_EDGE; output MDET_L; //active when movement is dectected

reg [2:0] FrameCount; //Show the current frame(write) reg [15:0] sram_data; wire[2:0] Frame2Write; wire[8:0] line2write; wire[9:0] RD_ADDR; //address to read wire[15:0] RD_DATA [7:0]; //read data from each frame wire[8:0] line2read; wire CamVsync_EDGE;

wire WR_N_1; //write enable signal of each FRAME wire WR_N_2; wire WR_N_3; wire WR_N_4; wire WR_N_5; wire WR_N_6; wire WR_N_7; wire WR_N_8;

wire [5:0] diffR; //difference of each red data wire [6:0] diffG; //difference of each green data wire [5:0] diffB; //difference of each blue data

reg [15:0] buf_d; reg [15:0] buf_p;

wire [11:0] abs_y; wire [11:0] abs_r; wire [11:0] abs_g; wire [11:0] abs_b; reg [11:0] abs_y_sft; reg [11:0] acc_y_L; reg [11:0] acc_y_L_latch; wire [11:0] add_y_L; wire [11:0] v_abs_y_L_sft; reg [11:0] v_acc_y_L; wire [11:0] v_add_y_L; reg [11:0] v_acc_y_latch; reg [7:0] CountV_L; wire MDET_EDGE_L; wire MDET_L; //output wire CS_N; //——————————————–frame update always@(posedge CLK or negedge RST_N) begin if ((RST_N == 1’b0) ) begin FrameCount <= {3{1’b0}}; end else begin if ((CamVsync_EDGE == 1’b1) ) begin //camvsync_edge shows the start/end of frame FrameCount <= FrameCount + 1; end end end

assign Frame2Write=FrameCount; assign line2write = CamLineCount[8:0] ;

//——————————————–write in frame assign WR_N_1=(Frame2Write == 3’b000)?WR_N:1; assign WR_N_2=(Frame2Write == 3’b001)?WR_N:1; assign WR_N_3=(Frame2Write == 3’b010)?WR_N:1; assign WR_N_4=(Frame2Write == 3’b011)?WR_N:1; assign WR_N_5=(Frame2Write == 3’b100)?WR_N:1; assign WR_N_6=(Frame2Write == 3’b101)?WR_N:1; assign WR_N_7=(Frame2Write == 3’b110)?WR_N:1; assign WR_N_8=(Frame2Write == 3’b111)?WR_N:1;

//——————————————–8 frame instance FRAME FRAME_1 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[0]) );

FRAME FRAME_2 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[1]) );

FRAME FRAME_3 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[2]) );

FRAME FRAME_4 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[3]) );

FRAME FRAME_5 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[4]) );

FRAME FRAME_6 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[5]) );

FRAME FRAME_7 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[6]) );

FRAME FRAME_8 ( .CLK(CLK), .CS_N(CS_N), .WR_N(WR_N_1), .WRADDR_ROW(line2write), .WRADDR_COL(WR_ADDR), .RDADDR_ROW(line2read), .RDADDR_COL(RD_ADDR), .WRDATA(WR_DATA), .RDDATA(RD_DATA[7]) );

//——————————————frame read

assign RD_ADDR = (WR_ADDR==783)?783:WR_ADDR+1; //SRAM read delay ->I’m not sure that we really need this. assign line2read = line2write; //There are blank at signals, so we don’t need to adjust the line to read

always@(posedge CLK or negedge RST_N) begin //Get the data of previous/descending frame if ((RST_N == 1’b0) ) begin buf_d <= {15{1’b0}}; buf_p <= {15{1’b0}}; end else begin buf_d <= RD_DATA[FrameCount+1]; buf_p <= RD_DATA[FrameCount-1]; end end

assign diffR = ({1’b0,buf_d[15:11] }) - ({1’b0,buf_p[15:11] }); //difference of the two corresponding pixels(red) of the frames assign absR = (diffR[5] == 1’b1)? { ~diffR[4] , ~diffR[3] , ~diffR[2] , ~diffR[1] , ~diffR[0] } : diffR[4:0] ; //get abs val assign diffG = ({1’b0,buf_d[10:5] }) - ({1’b0,buf_p[10:5] }); //green assign absG = (diffG[6] == 1’b1)? { ~diffG[5] , ~diffG[4] , ~diffG[3] , ~diffG[2] , ~diffG[1] , ~diffG[0] } : diffG[5:0] ; assign diffB = ({1’b0,buf_d[4:0] }) - ({1’b0,buf_p[4:0] }); //blue assign absB = (diffB[5] == 1’b1)? { ~diffB[4] , ~diffB[3] , ~diffB[2] , ~diffB[1] , ~diffB[0] } : diffB[4:0] ; //we know that red is 5 bit, green is 6bit, blue is 5 bit assign abs_r = {6’b000000,absR,1’b0}; // 6bit + 5bit + 1bit assign abs_g = {6’b000000,absG}; // 6bit + 6bit assign abs_b = {6’b000000,absB,1’b0}; // 6bit + 5bit + 1bit assign abs_y = ({abs_r[8:0] ,3’b000}) + ({abs_g[7:0] ,4’b0000}) + abs_b + abs_b + abs_b; //y=8r+16g+3b

//——————————————————————- // Detects the motion of left side of the VGA //——————————————————————- assign add_y_L = abs_y_sft + acc_y_L; //————————————————– always@(posedge CLK or negedge RST_N) begin //accumulate the values for a line. if ((RST_N == 1’b0) ) begin acc_y_L <= {12{1’b0}}; end else begin if ((RD_ADDR < 46) ) begin acc_y_L <= {12{1’b0}}; end else if (acc_y_L[11] == 1’b0 ) begin acc_y_L <= add_y_L; end end end

//————————————————– always@(posedge CLK or negedge RST_N) begin if ((RST_N == 1’b0) ) begin acc_y_L_latch <= {12{1’b0}}; end else begin if ((line2read < 20) ) begin acc_y_L_latch <= {12{1’b0}}; end else if ((RD_ADDR == 685) ) begin acc_y_L_latch <= add_y_L; //latch the accumulated value at the edge of the display end end end

//————————————————————— assign v_abs_y_L_sft = {8’b00000000,acc_y_L_latch[11:8] }; assign v_add_y_L = v_abs_y_L_sft + v_acc_y_L; //————————————————– always@(posedge CLK or negedge RST_N) begin //simular as before but this time, it is the whole frame. if ((RST_N == 1’b0) ) begin v_acc_y_L <= {12{1’b0}}; end else begin if ((line2read < 20) ) begin v_acc_y_L <= {12{1’b0}}; end else if ((line2read < 500 && RD_ADDR == 783 && v_acc_y_L[11] == 1’b0) ) begin v_acc_y_L <= v_add_y_L; end end end

//————————————————————— always@(posedge CLK or negedge RST_N) begin if ((RST_N == 1’b0) ) begin v_acc_y_latch <= {12{1’b0}}; end else begin if ((line2read == 500 && RD_ADDR == 783) ) begin v_acc_y_latch <= v_acc_y_L; end end end

assign MDET_EDGE_L = (v_acc_y_latch >= 256 && CountV_L == 0 && line2read==500 && RD_ADDR == 783)? 1’b1 : 1’b0; assign MDET_L = (CountV_L != 0)? 1’b1 : 1’b0; //————————————————————— always@(posedge CLK or negedge RST_N) begin //maintain the “detected” state for about few seconds if ((RST_N == 1’b0) ) begin CountV_L <= 8’b00000000; end else begin if ((MDET_EDGE_L == 1’b1) ) begin CountV_L <= 8’b01111111; end else if ((line2read==500 && RD_ADDR == 783 && CountV_L != 0) ) begin CountV_L <= CountV_L - 1; end end end

assign CS_N = 0;

endmodule


This post is licensed under CC BY 4.0 by the author.