이 웹 페이지는 Smart Zynq board의 기능을 탐색하는 소규모 프로젝트 그룹 에 속합니다.
이 프로젝트는 중국 독자들에게 추천하는 HelloFPGA로도 출간 되었습니다.
소개
이 튜토리얼에서는 OV7670 camera module을 Smart Zynq 에 연결하고 보드 자체 HDMI output에서 라이브 비디오 신호를 보는 방법을 설명합니다. 또한 간단한 Linux 명령을 사용하여 raw video stream을 파일에 쉽게 저장하는 방법도 보여 드리겠습니다.
이 튜토리얼의 정보는 다른 유형의 data acquisition 에도 적용됩니다. 아래에 표시된 기술은 image data 의 다른 소스 및 기타 디지털 데이터 소스와 함께 사용할 수 있습니다.
camera module은 Zynq chip의 PL (FPGA) 부분에 연결됩니다. 따라서 image data를 ARM processor로 보내기 전에 image processing을 구현하는 logic을 추가하는 것이 쉽습니다. 이 튜토리얼은 Xillinux를 기반으로 하므로 시스템의 processor 부분은 완전한 Linux distribution로 구성됩니다.
이 시연을 위해 OV7670 module을 선택한 이유는 이 하드웨어가 저렴하고 대중적이며 구입하기 쉽기 때문입니다. 또한 이 구성 요소가 생성하는 디지털 신호는 이해하기 쉽습니다.
그러나 OV7670 에는 불행한 결함이 있습니다. 기본적으로 비디오 스트림의 색상은 올바르지 않습니다. 이는 이 카메라 센서의 알려진 문제입니다. 이 카메라의 소수의 registers를 변경하면 이 결함을 수정할 수 있습니다. 따라서 이 튜토리얼은 두 부분으로 나누어집니다:
- camera sensor를 Smart Zynq 보드에 연결하는 방법과 간단한 Linux 도구를 사용하여 비디오 스트림을 읽는 방법입니다.
- 올바른 색상의 비디오 스트림을 얻는 방법. 이 부분에서는 I2C의 도움으로 Xillybus를 사용하여 camera sensor의 registers를 변경하는 방법을 보여줍니다.
이 튜토리얼의 대부분은 구현 작동 방식을 설명합니다. 카메라를 사용하기 위해 이러한 설명을 이해할 필요는 없습니다.
OV7670 module
이 튜토리얼은 아래 그림에 표시된 카메라 모듈을 기반으로 합니다.
이 모듈에서는 pin header 의 pins 대부분이 OV7670 구성 요소에 직접 연결됩니다. voltage regulators에는 3.3V 와 GND 만 연결됩니다. 따라서 FPGA 와 모듈 사이의 모든 연결은 FPGA 와 OV7670 구성 요소 사이의 직접 연결입니다.
시장에는 동일한 기능을 가진 다른 모듈이 있습니다. 아마도 이러한 다른 모듈도 사용해도 괜찮을 것입니다. 예를 들어 PCB에 "2017/3/15"라고 쓰여진 다른 모듈이 있습니다. 이 모듈도 제대로 작동합니다. 반면에 제대로 작동하지 않는 모듈이 있습니다. 작동하지 않는 모듈은 PCB에 "QYF-OV7670 V3.0"라고 적혀있습니다.
또 한 가지 알아야 할 점은 OV7670 구성 요소에 다양한 개정판이 있다는 것입니다. 모듈에 올바른 개정판이 사용되었는지 확인할 수 있습니다. 이를 수행하는 방법은 이 튜토리얼의 두 번째 부분 에서 설명됩니다.
OV7670에 대한 정보를 얻을 수 있는 주요 소스는 두 가지입니다. 이 두 문서는 인터넷에서 찾을 수 있습니다.
- datasheet: OV7670/OV7171 CMOS VGA (640x480) CAMERA CHIP Sensor with OmniPixel Technology, Version 1.4, August 21, 2006 . 이 문서의 버전 1.4를 구하는 것이 중요합니다.
- 구현 가이드: OV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide . 분명히 인터넷에서는 Version 1.0, September 2, 2005 만 찾을 수 있습니다. 이 버전은 안타깝게도 구식이며 camera chip의 이전 버전을 나타냅니다.
Vivado 프로젝트 준비
demo bundle의 zip 파일( boot partition kit)에서 새 Vivado 프로젝트를 만듭니다. 텍스트 편집기에서 verilog/src/xillydemo.v를 엽니다. "PART 2"라고 표시된 코드 부분을 삭제하십시오. 해당 부분 대신 다음 코드 조각을 삽입하세요.
/*
* PART 2
* ======
*
* This code demonstrates a frame grabber (data acquisition) from
* an OV7670 camera module.
*
*/
reg [1:0] clkdiv;
always @(posedge bus_clk)
clkdiv <= clkdiv + 1;
assign J6[10] = clkdiv[1]; // MCLK / XCLK
assign J6[0] = 0; // PWDN, the camera is always on
assign J6[1] = !user_w_write_32_open; // RESET#, active low
wire [7:0] D_in;
wire pclk_in, hsync_in, vsync_in;
assign D_in = J6[9:2];
assign pclk_in = J6[11];
assign hsync_in = J6[12];
assign vsync_in = J6[13];
(* IOB = "TRUE" *) reg [7:0] D_guard;
(* IOB = "TRUE" *) reg pclk_guard, hsync_guard, vsync_guard;
reg [7:0] D;
reg pclk, hsync, vsync;
always @(posedge bus_clk)
begin
// Metastability guards on asynchronous inputs
D_guard <= D_in;
pclk_guard <= pclk_in;
hsync_guard <= hsync_in;
vsync_guard <= vsync_in;
D <= D_guard;
pclk <= pclk_guard;
hsync <= hsync_guard;
vsync <= vsync_guard;
end
wire sample_valid;
reg previous_pclk;
always @(posedge bus_clk)
previous_pclk <= pclk;
assign sample_valid = pclk && !previous_pclk;
// wait_for_frame's purpose is to start getting data from the camera
// at the beginning of a frame.
reg wait_for_frame;
always @(posedge bus_clk)
if (!user_r_read_32_open)
wait_for_frame <= 1;
else if (sample_valid && vsync)
wait_for_frame <= 0;
// fifo_has_been_full changes to '1' when the FIFO becomes full, so
// that the data acquisition stops and an EOF is sent to the host.
// This ensures that the data that arrives to the host is contiguous.
reg fifo_has_been_nonfull, fifo_has_been_full;
wire fifo_full;
always @(posedge bus_clk)
begin
if (!fifo_full)
fifo_has_been_nonfull <= 1;
else if (!user_r_read_32_open)
fifo_has_been_nonfull <= 0;
if (fifo_full && fifo_has_been_nonfull)
fifo_has_been_full <= 1;
else if (!user_r_read_32_open)
fifo_has_been_full <= 0;
end
assign user_r_read_32_eof = fifo_has_been_full && user_r_read_32_empty;
// This part writes pixels from the camera to the FIFO
reg fifo_wr_en;
reg [1:0] byte_position;
reg [31:0] dataword;
always @(posedge bus_clk)
if (wait_for_frame)
begin
byte_position <= 0;
fifo_wr_en <= 0;
end
else if (sample_valid && hsync)
begin
case (byte_position)
0: dataword[7:0] <= D;
1: dataword[15:8] <= D;
2: dataword[23:16] <= D;
3: dataword[31:24] <= D;
endcase
if (byte_position == 3)
fifo_wr_en <= !fifo_has_been_full;
else
fifo_wr_en <= 0;
byte_position <= byte_position + 1;
end
else
fifo_wr_en <= 0;
fifo_32x512 fifo_32
(
.clk(bus_clk),
.srst(!user_r_read_32_open),
.din(dataword),
.wr_en(fifo_wr_en),
.full(fifo_full),
.rd_en(user_r_read_32_rden),
.dout(user_r_read_32_data),
.empty(user_r_read_32_empty)
);
또는 여기 에서 변경 후 xillydemo.v를 다운로드할 수 있습니다.
이 변경을 수행한 후 평소와 같이 bitstream 파일을 만듭니다. 이 Verilog 코드의 작동 방식은 이 페이지 아래에 자세히 설명되어 있습니다.
카메라 모듈 연결
짧은 Dupont 점퍼선을 사용하여 카메라 모듈과 Smart Zynq 보드를 연결할 수 있습니다. 전선의 길이는 10 cm 이하이어야 합니다. 최적의 길이는 5 cm입니다. 전선이 더 길면 crosstalk로 인해 디지털 신호의 품질이 저하될 수 있습니다. horizontal sync 에 과도한 노이즈가 있으면 녹색과 보라색 줄무늬가 있는 점프 비디오 이미지가 발생합니다.
전선 길이가 10 cm인 경우 OV7670의 I/O driver current를 줄이기 위해 register를 변경해야 할 수도 있습니다. 이 튜토리얼의 두 번째 부분에서는 이러한 변경을 수행하는 방법을 보여줍니다.
이것은 Smart Zynq SP 보드에 연결된 OV7670 모듈의 사진입니다.
아래는 반대방향에서 찍은 사진입니다. 왼쪽 상단의 작은 이미지는 pin header 의 마지막 pin이 아무 것에도 연결되어 있지 않음을 강조합니다.
위의 이미지는 전선을 연결하는 방법을 보여줍니다. 먼저 Smart Zynq 보드 뒷면에서 "Bank 33 VCCIO Vadj"라고 쓰여진 곳을 찾아보세요. 이 표시에 가까운 pins 행이 우리가 작업할 pin header 입니다. HDMI 커넥터에 가까운 pin header 입니다.
카메라 센서와 Smart Zynq 보드 사이에는 16개의 전선이 병렬로 연결되어 있습니다. pin header에서는 3.3V 와 GND 만 다른 위치에 연결되어 있습니다. 이 두 전선은 짧을 필요는 없습니다.
pin header의 마지막 pin은 5V가 므로 이 pin에 전선을 연결하지 마십시오.
카메라 모듈과 Smart Zynq의 pin header간의 배선 사양입니다. 이 정보는 위 이미지에서도 추론할 수 있습니다.
Pin header | 1 | 3 | 5 | 7 | 9 | 11 | 13 | 15 | 35 |
Module pin | PWDN | D0 | D2 | D4 | D6 | MCLK | HS | SDA | GND |
Module pin | RST | D1 | D3 | D5 | D7 | PCLK | VS | SCL | 3.3V |
Pin header | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 37 |
다시 한번 3.3V 와 GND의 연결에 각별히 주의하시기 바랍니다. 이 두 전선을 잘못 연결하면 카메라 모듈이 파손될 수 있습니다.
Frame grabbing
업데이트된 xillydemo.v를 기반으로 하는 bitstream 파일로 Xillinux를 시작합니다(위에 표시된 대로).
이 명령( shell prompt에서)은 카메라 출력에서 짧은 비디오 클립을 생성합니다.
# cat /dev/xillybus_read_32 > clip.raw
이 명령은 몇 초 동안 실행된 다음 중지됩니다. 이는 비디오 스트림의 data rate가 SD card의 데이터 쓰기 속도보다 높기 때문입니다. 이로 인해 데이터 흐름이 중지되는 overflow가 발생합니다. 이 동작의 메커니즘은 아래에 설명되어 있습니다.
다음 명령을 사용하여 이 비디오 클립을 재생할 수 있습니다.
# mplayer -demuxer rawvideo -rawvideo w=640:h=480:format=uyvy:size=614400:fps=31.25 clip.raw
Xillinux의 그래픽 데스크탑 내부에 있는 terminal window 에서 이 명령을 사용하면 비디오는 Xillinux의 그래픽 인터페이스에서 재생됩니다.
별도 페이지 에 설명된 기술을 사용하면 다른 컴퓨터 화면에서 동영상을 재생할 수도 있습니다. 예를 들어, 다른 컴퓨터의 IP address가 192.168.1.11인 경우 다음으로 시작되도록 명령을 변경합니다.
# DISPLAY=192.168.1.11:0 mplayer -demuxer rawvideo ...
이미 언급한 바와 같이 비디오 클립의 색상이 올바르지 않습니다. 다음 페이지에서는 이 문제를 해결하는 방법을 설명합니다.
이 명령은 하나의 video frame을 frame.raw라는 파일로 읽습니다.
# dd if=/dev/xillybus_read_32 of=frame.raw bs=614400 iflag=fullblock count=1
raw frame 의 형식은 UYVY 4:2:2입니다. 즉, 각 픽셀은 16 bits로 구성됩니다. 첫 번째 바이트는 첫 번째 픽셀(또는 Cb)의 U 구성 요소입니다. 다음 바이트는 동일한 픽셀의 Y 구성 요소입니다. 세 번째와 네 번째 바이트는 두 번째 픽셀(Cr 및 Y)의 V 및 Y 구성 요소입니다.
이 파일은 다음을 사용하여 PNG 파일로 변환할 수 있습니다.
# convert -size 640x480 pal:frame.raw frame.png
이미지를 보기 위한 간단한 도구가 있습니다:
# display frame.png &
Live view
카메라의 live view를 얻으려면 다음을 포함하는 liveview.sh 라는 파일을 생성하십시오.
#!/bin/bash
while [ 1 ] ; do
dd if=/dev/xillybus_read_32 bs=614400 iflag=fullblock count=1 2>/dev/null
done | mplayer -demuxer rawvideo -rawvideo w=640:h=480:format=uyvy:size=614400 -
이 script를 다음과 같이 실행하십시오.
# bash liveview.sh
이 script가 왜 필요한가요? mplayer는 너무 느리기 때문에 device file 에서 직접 데이터를 읽는 것은 불가능합니다. 즉, Zynq의 ARM processor는 올바른 프레임 속도(31.25 fps)로 비디오를 재생할 만큼 강력하지 않습니다. 그렇게 하면 영상이 잠깐만 재생됩니다. FPGA내부에 overflow가 있으면 데이터 흐름이 중지됩니다.
이 script는 매번 /dev/xillybus_read_32 에서 raw frame을 읽는 무한 루프를 기반으로 합니다. 이는 이전에 frame.raw로 한 프레임을 읽어들이기 위해 사용했던 것과 동일한 명령입니다. 그러나 이번에는 dd에 대한 출력 파일이 정의되어 있지 않습니다. 따라서 dd는 대신 standard output 에 데이터를 씁니다.
이 무한 루프의 결과는 pipe 덕분에 mplayer의 standard input 로 리디렉션됩니다(루프 끝에 있는 "|"에 주의하세요). mplayer는 standard input에서 수신된 비디오 데이터를 재생합니다.
dd는 항상 완전한 비디오 프레임을 읽기 때문에 script는 overflow 의 문제를 해결합니다. mplayer가 더 많은 데이터를 받아들일 준비가 되지 않으면 pipe 의 데이터 흐름이 일시적으로 중단됩니다. 결과적으로 dd는 카메라 센서에서 video frames를 건너뜁니다. 따라서 화면에 표시되는 프레임 속도는 카메라의 프레임 속도보다 낮습니다. 보다 정확하게는 표시되는 프레임 속도는 mplayer가 표시할 수 있는 최대 프레임 속도입니다.
mplayer자체의 buffering로 인해 화면에 나타나는 영상이 약간 지연되는 현상이 있습니다. 낮은 latency를 얻으려면 buffer를 추가하지 않고 화면에 이미지를 표시하는 간단한 프로그램을 작성해야 합니다.
mplayer는 강력한 미디어 플레이어입니다. 예를 들어 잘못된 색상이 거슬리는 경우 비디오 클립을 흑백 비디오로 재생할 수 있습니다. saturation을 0으로 줄이려면 명령에 다음 부분을 추가하십시오.
# mplayer -saturation -100 -demuxer rawvideo ...
이 페이지의 실제 부분 끝
이 페이지의 나머지 부분에서는 data acquisition을 수행하는 logic 의 구현에 대해 설명합니다. 실용적인 주제에만 관심이 있다면 이 튜토리얼의 다음 부분을 계속 읽어보세요.
FPGA 와 host간의 통신
이 예의 logic은 Xillybus IP core를 기반으로 합니다. 이 IP core는 host와의 통신을 담당합니다.
위에서 Verilog 코드에서 대체된 부분은 다음과 같이 끝난다는 점을 기억하세요.
fifo_32x512 fifo_32
(
.clk(bus_clk),
.srst(!user_r_read_32_open),
.din(dataword),
.wr_en(fifo_wr_en),
.full(fifo_full),
.rd_en(user_r_read_32_rden),
.dout(user_r_read_32_data),
.empty(user_r_read_32_empty)
);
이것은 표준 FIFO의 instantiation 입니다. 이 FIFO 에는 FIFO에서 데이터를 읽는 데 사용되는 3개의 ports가 있습니다. rd_en, dout 및 empty. 이 ports는 Xillybus IP core에 연결됩니다. 이를 통해 IP core는 FIFO 에서 데이터를 읽고 이 데이터를 host로 보낼 수 있습니다. 결과적으로 FIFO 에 기록된 모든 내용은 /dev/xillybus_read_32라는 이름의 device file 에 도달합니다. 즉, host 의 일반 컴퓨터 프로그램은 /dev/xillybus_read_32를 일반 파일로 열 수 있습니다. 프로그램이 이 파일을 읽을 때 FPGA내부의 application logic이 FIFO 에 쓴 데이터를 수신합니다.
FIFO 에는 데이터 쓰기용으로 설계된 3개의 ports가 있습니다. wr_en, din 및 full. 이 ports는 카메라 센서에서 픽셀 데이터를 수집하는 logic 에 연결됩니다. 이 페이지의 다음 섹션에서는 이 logic이 어떻게 작동하는지 설명합니다. 지금은 logic이 @dataword 및 @fifo_wr_en의 도움으로 FIFO 에 픽셀 데이터를 쓴다는 점만 지적하겠습니다. 이 시점부터 이 데이터를 host에서 실행되는 컴퓨터 프로그램으로 가져오는 것이 Xillybus IP core의 역할입니다. 이것이 바로 위에서 이미 언급한 이 명령이 이 데이터를 파일에 쓰는 이유입니다.
# cat /dev/xillybus_read_32 > clip.raw
FIFO 작동 방식에 대한 일반적인 설명은 이 페이지를 참조하십시오.
데이터 흐름은 다음 다이어그램으로 요약할 수 있습니다.
이 웹사이트에는 Xillybus에 관한 섹션이 있고, 이 섹션에는 data acquisition을 논의하는 페이지가 있습니다. 해당 페이지를 읽어보는 것이 도움이 될 수 있습니다.
user_r_read_32_open은 FIFO의 srst port에 연결되어 있습니다. /dev/xillybus_read_32가 host에서 열리면 이 signal이 High로 변경됩니다. signal은 NOT와 연결되어 있으므로 device file을 닫으면 FIFO는 reset 상태를 유지합니다. 이렇게 하면 파일을 닫을 때마다 FIFO 의 모든 데이터가 삭제됩니다.
카메라 센서와의 인터페이스
이제 위에서부터 Verilog 코드의 시작 부분을 살펴보겠습니다.
reg [1:0] clkdiv;
always @(posedge bus_clk)
clkdiv <= clkdiv + 1;
assign J6[10] = clkdiv[1]; // MCLK / XCLK
@bus_clk의 주파수는 100 MHz입니다. 이 clock은 @clkdiv의 도움으로 4로 나뉩니다. 따라서 카메라 모듈은 25 MHz reference clock을 수신합니다. 카메라의 datasheet에 따르면 이는 허용 주파수입니다. 그러나 카메라는 reference clock의 주파수가 24 MHz일 때 30 fps 비디오 스트림을 생성하도록 설계되었습니다. 따라서 실제 비디오 프레임 속도는 약간 더 높습니다. 31.25 fps.
이것은 일반적으로 clock을 생성하는 잘못된 방법이라는 점을 언급하고 싶습니다. 올바른 방법은 PLL 또는 유사한 리소스를 사용하는 것입니다. @clkdiv는 output signal을 생성하는 데만 사용되기 때문에 이 특정한 경우에는 이 방법에 문제가 없습니다. FPGA의 자체 logic은 이 신호를 사용하지 않습니다.
Verilog 코드의 다음 부분은 다음과 같습니다.
assign J6[0] = 0; // PWDN, the camera is always on
assign J6[1] = !user_w_write_32_open; // RESET#, active low
J6[0]은 카메라 모듈의 PWDN pin에 연결됩니다. 카메라의 전원이 꺼지지 않습니다.
J6[1]이 카메라의 RESET#에 연결됩니다. 이 pin이 낮으면 카메라가 재설정됩니다. host의 프로그램에서 /dev/xillybus_write_32를 열면 @user_w_write_32_open이 높아집니다. 따라서 일반적으로 @user_w_write_32_open이 낮고 결과적으로 J6[1]이 높기 때문에 카메라가 재설정되지 않습니다. 이 배열을 사용하면 다음 명령을 사용하여 카메라를 재설정할 수 있습니다.
# echo 1 > /dev/xillybus_write_32
이 명령은 짧은 시간 동안 device file을 열어 원하는 결과를 얻습니다.
지금까지 FPGA 에서 카메라 센서로 전달되는 신호가 어떻게 생성되는지 살펴보았습니다. 이제 카메라 센서에서 FPGA로 전달되는 신호입니다.
OV7670은 FPGA에서 reference clock 와 동일한 주파수를 갖는 pixel clock을 생성합니다. 즉, PCLK의 주파수는 25 MHz입니다. 이 신호는 Verilog 코드에서 @pclk_in 에 연결됩니다.
카메라 센서는 비디오 데이터가 포함된 세 가지 신호도 생성합니다. Verilog 코드에서 이러한 신호의 이름은 @D_in , @hsync_in 및 @vsync_in입니다. 카메라 센서는 @pclk_in이 높음에서 낮음(falling edge)으로 변경됨과 동시에 이러한 신호의 값을 변경합니다. 보다 정확하게는 @D_in , @hsync_in 및 @vsync_in 의 변경 사항이 @pclk_in의 falling edge 와 일치합니다. FPGA입장에서는 source synchronous input라고 부릅니다.
이제 Verilog 코드에서 관련 부분을 살펴보겠습니다.
wire [7:0] D_in;
wire pclk_in, hsync_in, vsync_in;
assign D_in = J6[9:2];
assign pclk_in = J6[11];
assign hsync_in = J6[12];
assign vsync_in = J6[13];
(* IOB = "TRUE" *) reg [7:0] D_guard;
(* IOB = "TRUE" *) reg pclk_guard, hsync_guard, vsync_guard;
reg [7:0] D;
reg pclk, hsync, vsync;
always @(posedge bus_clk)
begin
// Metastability guards on asynchronous inputs
D_guard <= D_in;
pclk_guard <= pclk_in;
hsync_guard <= hsync_in;
vsync_guard <= vsync_in;
D <= D_guard;
pclk <= pclk_guard;
hsync <= hsync_guard;
vsync <= vsync_guard;
end
카메라 센서의 모든 신호는 @bus_clk의 도움으로 샘플링됩니다. 카메라의 PCLK 도 다른 신호와 동일한 방식으로 샘플링됩니다. 즉, PCLK는 clock로 취급되지 않고 data signal로 취급됩니다. 이 기술에 대해서는 아래에서 간략하게 설명하겠습니다.
또한 @bus_clk 와 카메라 센서 신호 간의 타이밍 관계는 알려져 있지 않습니다. 따라서 이러한 신호를 수신하는 flip-flops 의 출력은 신뢰할 수 없습니다. 이러한 flip-flops의 timing requirements는 보장이 불가능하므로 짧은 시간 동안 불안정해질 수 있습니다. 이는 clock domain crossing와 관련하여 알려진 문제입니다.
이 문제에 대한 해결책은 별도의 페이지 에서 논의됩니다. Metastability guards. 이는 두 개의 flip-flops가 서로 직렬로 연결되어 있음을 의미합니다. 첫 번째 flip-flop (예:@pclk_guard)는 외부 신호에 연결됩니다. 두 번째 flip-flop은 첫 번째 flip-flop에 연결됩니다. 그래서 첫 번째 flip-flop이 잠시 불안정해지더라도 두 번째 flip-flop의 timing requirements는 보장됩니다. 따라서 두 번째 flip-flop의 출력은 안정적입니다.
결론적으로: @D, @pclk, @hsync 및 @vsync는 안정적인 registers 입니다( @bus_clk와 동기화됨).
@bus_clk의 주파수는 100 MHz라는 것을 기억하세요. 반면 PCLK의 주파수는 25 MHz입니다. 따라서 @D, @hsync , @vsync 의 가치는 clock cycles4개에 한 번씩만 소모되어야 합니다. 그런데 4개 중 어떤 clock cycle을 사용하나요?
대답은 Verilog 코드의 다음 행에 있습니다.
wire sample_valid;
reg previous_pclk;
always @(posedge bus_clk)
previous_pclk <= pclk;
assign sample_valid = pclk && !previous_pclk;
이 코드 조각은 간단히 다음을 의미합니다. @pclk가 현재 높고 이전 clock cycle에서는 낮았다면 @D, @hsync 및 @vsync의 값을 사용하십시오. 카메라의 PCLK가 높음에서 낮음으로 변경되면 카메라 센서의 신호 값이 변경된다는 점을 기억하세요. 따라서 PCLK가 로우에서 하이로 변경되면 다른 신호는 안정적입니다.
그러나 @pclk, @D, @hsync 및 @vsync는 registers입니다. 이 registers는 @bus_clk와 동기화되며 특정 시간의 카메라 센서 신호의 스냅샷을 나타냅니다. PCLK 자체에서 rising edge를 감지하는 대신 logic은 @pclk와 유사한 작업을 수행합니다. @pclk 의 값이 낮은 값에서 높은 값으로 바뀌는 시점이 다른 registers의 값을 사용하는 올바른 시점입니다.
이 기술을 01-signal sampling라고 합니다. 이 기술의 배경 아이디어는 01-signal sampling에 대한 별도 페이지 에 자세히 설명되어 있습니다. 해당 페이지에서는 이 방법이 FPGA의 timing requirements를 보장하는 방법도 설명합니다. 이를 통해 logic은 @D, @hsync 및 @vsync 의 값이 올바른지 확인합니다.
데이터 흐름 시작 및 중지
이제 host로의 데이터 전송을 방지하기 위한 두 개의 registers를 살펴보겠습니다.
- @wait_for_frame: 이 register 의 목적은 device file 의 데이터가 frame의 시작 부분부터 시작되도록 하는 것입니다.
- @fifo_has_been_full: 이 register는 FIFO가 가득 차면 데이터 흐름이 중단되도록 보장하는 메커니즘의 일부입니다.
이제 이 두 registers 각각에 대해 자세히 설명하겠습니다. 첫째, @wait_for_frame:
reg wait_for_frame;
always @(posedge bus_clk)
if (!user_r_read_32_open)
wait_for_frame <= 1;
else if (sample_valid && vsync)
wait_for_frame <= 0;
device file을 열지 않았을 때@wait_for_frame의 가치는 높습니다. 이 register 의 값은 카메라 센서의 vsync 신호에 반응하여 low로 변경됩니다. 이 신호는 frames사이의 기간 동안 High입니다. 즉, @vsync가 High이면 카메라에서 픽셀 데이터가 전송되지 않습니다.
결론적으로, 카메라의 픽셀 데이터를 무시해야 할 때 @wait_for_frame은 높습니다. device file이 닫혀 있거나 최근에 device file을 열었지만 카메라가 여전히 frame중앙에 있는 경우.
이제 @fifo_has_been_full에 대해 설명하겠습니다. host 에 도착하는 데이터가 카메라 센서가 생성하는 데이터와 동일한지 확인하는 것이 중요합니다. 그러나 컴퓨터 프로그램이 device file 의 데이터를 충분히 빠르게 읽지 못하는 경우 FIFO 에서 overflow가 발생할 수 있습니다. DMA buffers는 결국 가득 차게 되므로 FIFO 의 콘텐츠를 복사할 곳이 없게 됩니다. 결과적으로 Xillybus IP core는 FIFO에서 데이터를 읽을 수 없습니다. 그렇게 되면 FIFO가 가득 차서 새 데이터를 쓸 수 없게 됩니다.
logic은 이러한 상황을 방지하기 위해 아무것도 할 수 없습니다. 그러나 logic은 host 에 도착하는 데이터가 연속되도록 보장할 수 있습니다. FIFO가 가득 차면 logic은 FIFO에 데이터 쓰기를 중지합니다. 또한, FIFO가 꽉 찬 후 FIFO가 비게 되면 logic은 EOF를 host로 보내달라고 요청합니다. 결과적으로 컴퓨터 프로그램은 FIFO가 가득 차기 전에 FIFO 에 기록된 모든 데이터를 수신합니다. 해당 데이터 이후 컴퓨터는 EOF를 수신합니다. 이는 일반 파일의 끝에 도달했을 때 발생하는 것과 동일합니다.
이 메커니즘은 컴퓨터 프로그램이 도착하는 데이터가 정확하고 연속적이라는 것을 신뢰할 수 있도록 보장합니다. 연속성이 손실되면 EOF는 컴퓨터 프로그램을 강제로 device file을 닫습니다. 프로그램이 device file을 다시 열면 @wait_for_frame덕분에 데이터가 새 프레임에서 시작됩니다.
이것은 Verilog 코드의 관련 부분입니다.
reg fifo_has_been_nonfull, fifo_has_been_full;
wire fifo_full;
always @(posedge bus_clk)
begin
if (!fifo_full)
fifo_has_been_nonfull <= 1;
else if (!user_r_read_32_open)
fifo_has_been_nonfull <= 0;
if (fifo_full && fifo_has_been_nonfull)
fifo_has_been_full <= 1;
else if (!user_r_read_32_open)
fifo_has_been_full <= 0;
end
assign user_r_read_32_eof = fifo_has_been_full && user_r_read_32_empty;
FIFO가 가득 차면@fifo_has_been_full이 높아집니다. 이 register는 device file이 열리지 않으면 Low로 변경됩니다. @fifo_full 와 @fifo_has_been_nonfull이 모두 High일 때 @fifo_has_been_full은 High로 변경됩니다.
@fifo_full은 FIFO의 "full" port에 연결됩니다. 그런데 @fifo_has_been_nonfull이 왜 필요한가요? 그 이유는 FIFO가 reset 상태로 유지되는 한 FIFO가 "full"를 높게 유지하는 경우가 많기 때문입니다. 이 기능의 목적은 FIFO가 아직 데이터를 수신할 준비가 되지 않았음을 application logic 에 알리는 것입니다. @fifo_has_been_nonfull 의 목적은 이 시나리오에서 @fifo_has_been_full이 실수로 높아지는 것을 방지하는 것입니다.
@fifo_has_been_full 와 @user_r_read_32_empty가 모두 하이일 때@user_r_read_32_eof는 하이가 됩니다. 즉, 과거에 FIFO가 꽉 차 있었다가 지금은 비어 있을 때 EOF를 host 로 보내는 것입니다. 이 상황에서는 FIFO 에 새로운 데이터가 기록되지 않습니다.
데이터 연속성을 보장하기 위한 유사한 솔루션을 논의하는 별도의 페이지가 있습니다. 해당 페이지에 제시된 솔루션은 FIFO 의 양면이 다른 clock domains에 속하는 경우 필요합니다. 이 페이지에 제시된 코드에서 FIFO는 하나의 clock와만 동기화됩니다. 따라서 이 페이지에서는 @fifo_has_been_full 구현이 더 간단합니다.
FIFO에 데이터 쓰기
Verilog 코드의 다음 부분은 픽셀 데이터를 FIFO에 씁니다.
reg fifo_wr_en;
reg [1:0] byte_position;
reg [31:0] dataword;
always @(posedge bus_clk)
if (wait_for_frame)
begin
byte_position <= 0;
fifo_wr_en <= 0;
end
else if (sample_valid && hsync)
begin
case (byte_position)
0: dataword[7:0] <= D;
1: dataword[15:8] <= D;
2: dataword[23:16] <= D;
3: dataword[31:24] <= D;
endcase
if (byte_position == 3)
fifo_wr_en <= !fifo_has_been_full;
else
fifo_wr_en <= 0;
byte_position <= byte_position + 1;
end
else
fifo_wr_en <= 0;
카메라 센서의 픽셀 데이터는 8비트 너비의 데이터 요소로 도착합니다. logic 의 이 부분은 이러한 데이터 요소를 32비트로 재구성하여 데이터를 FIFO에 쓸 수 있도록 합니다. 8-bit Xillybus stream (/dev/xillybus_write_8)는 다음 두 가지 이유로 이 목적으로 사용되지 않습니다.
- data acquisition 애플리케이션의 경우 32-bit stream이 더 적합합니다. Xillybus IP core는 data word 의 너비가 8/16 bits에 불과할 때 데이터를 효율적으로 전송하지 않습니다.
- /dev/xillybus_write_8은 이 튜토리얼의 다음 부분 에서 설명하는 것처럼 카메라 센서와의 I2C 통신에 사용됩니다.
@wait_for_frame이 높으면 다음 두 가지 가능성 중 하나로 인해 FIFO 에 아무 것도 기록되지 않습니다. device file이 열리지 않거나, device file이 열리지만 새로운 frame 의 시작은 아직 이르지 못했습니다.
카메라 센서의 HSYNC가 높으면 data signals 에 유효한 픽셀이 포함되어 있음을 의미합니다. "sample_valid && hsync" 표현의 가치는 두 가지 기준을 결합합니다: @sample_valid가 높으면 @hsync 및 @D 에 유효한 값이 포함됩니다. 그래서 @hsync가 High이면 @D 의 값이 @dataword의 일부로 복사됩니다. 또한 @D를 @dataword 의 마지막 부분에 복사하면(예: @byte_position이 3와 같음) @fifo_wr_en이 High가 됩니다. 결과적으로 @dataword는 FIFO에 기록됩니다. 보다 정확하게 @fifo_wr_en 의 표현은 다음과 같습니다.
fifo_wr_en <= !fifo_has_been_full;
따라서 @fifo_has_been_full이 높으면 앞서 언급한 것처럼 FIFO에 아무 것도 기록되지 않습니다.
Verilog 코드와 실제 pins의 관계
위의 Verilog 코드는 J6라는 이름의 inout port를 사용하는데, 이 port 와의 연결은 어떻게 pin header에 도달할까요? 그 답은 xillydemo.xdc에서 찾을 수 있습니다. 이 파일은 bitstream을 생성하는 Vivado 프로젝트("vivado-essentials" 디렉터리에 있음)의 일부입니다.
xillydemo.xdc 에는 FPGA가 전자부품으로 제대로 작동하기 위해 필요한 다양한 정보가 담겨 있습니다. 이 파일에는 다음과 같은 행이 포함되어 있습니다.
[ ... ]
## J6 on board (BANK33 VADJ)
set_property PACKAGE_PIN U22 [get_ports {J6[0]}]; #J6/1 = IO_B33_LN2
set_property PACKAGE_PIN T22 [get_ports {J6[1]}]; #J6/2 = IO_B33_LP2
set_property PACKAGE_PIN W22 [get_ports {J6[2]}]; #J6/3 = IO_B33_LN3
set_property PACKAGE_PIN V22 [get_ports {J6[3]}]; #J6/4 = IO_B33_LP3
set_property PACKAGE_PIN Y21 [get_ports {J6[4]}]; #J6/5 = IO_B33_LN9
set_property PACKAGE_PIN Y20 [get_ports {J6[5]}]; #J6/6 = IO_B33_LP9
set_property PACKAGE_PIN AB22 [get_ports {J6[6]}]; #J6/7 = IO_B33_LN7
set_property PACKAGE_PIN AA22 [get_ports {J6[7]}]; #J6/8 = IO_B33_LP7
[ ... ]
첫 번째 행에는 J6[0] 신호가 U22에 연결되어야 한다고 나와 있습니다. FPGA의 물리적 패키지에 대한 위치입니다. Smart Zynq의 schematics에 따르면 이 FPGA pin은 pin header의 첫 번째 pin 에 연결됩니다. 다른 ports 의 위치도 같은 방식으로 정의됩니다.
결론
이 페이지에서는 OV7670 에서 픽셀 데이터를 얻고 Xillybus IP core를 사용하여 이 데이터를 host 로 보내는 방법을 보여주었습니다.
이 튜토리얼의 다음 부분에서는 SCCB (예: I2C)의 도움으로 카메라 센서의 registers를 변경하기 위해 Xillybus IP core를 사용하는 방법을 설명합니다. 이는 카메라의 매개변수를 변경하는 데 유용합니다. 특히 이는 정확한 색상의 이미지를 얻기 위해 필요합니다.