이 웹 페이지는 Smart Zynq board의 기능을 탐색하는 소규모 프로젝트 그룹 에 속합니다.
이 프로젝트는 중국 독자들에게 권장되는 HelloFPGA로도 출판 되었습니다.
소개
이 튜토리얼에서는 Smart Zynq 보드를 사용하여 OV7670 camera sensor의 registers 에 액세스하는 방법을 설명합니다. 이는 카메라 센서로부터 비디오 데이터를 수신하는 방법을 설명한 이전 페이지 의 후속 조치입니다.
OV7670 에는 카메라 센서의 매개변수를 구성하기 위한 Serial Camera Control Bus 인터페이스(SCCB)가 있습니다. SCCB protocol은 "OmniVision Serial Camera Control Bus (SCCB) Functional Specification"라는 Omnivision문서에 정의되어 있습니다. 이 protocol은 잘 알려진 I2C protocol와 호환됩니다.
카메라 센서의 registers 에 액세스하는 주요 동기는 카메라가 올바른 색상의 이미지를 생성하도록 하는 것입니다. 그러나 registers를 통해 카메라를 제어하면 다음과 같은 다른 이점도 있습니다. 디지털 신호의 전류 제어, 카메라의 밝기 및 색상 자동 조정 제어 및 중지, test pattern 요청 등.
Zynq의 processor 에는 각각 I2C bus master를 구현하는 두 개의 내장 장치가 있습니다. 카메라 센서와 통신하기 위해 이러한 장치 중 하나를 사용할 수 있습니다. 불행하게도 이러한 내장형 I2C 장치를 사용하려는 시도에서 OV7670와 잘 작동하지 않는 것으로 나타났습니다. 그 이유는 아마도 전선에 많은 양의 소음이 있기 때문일 것입니다. 전용 pull-up resistors가 부족한 것도 또 다른 이유일 수 있습니다( FPGA의 내부 pull-ups가 이 목적으로 사용되었습니다).
내장된 I2C 장치를 사용할 수 없으므로 대신 Verilog module이 프로젝트에 추가됩니다. 이 logic은 시끄러운 신호에 더 잘 대처하도록 설계되었습니다.
Vivado 프로젝트 변경 사항
아래 지침은 이전 페이지 에 따라 이미 생성된 Vivado 프로젝트를 기반으로 합니다.
이 링크 에서 I2C bus master 의 Verilog 구현을 다운로드하세요. 이 파일을 verilog/src/ 디렉터리에 복사합니다. 그런 다음 Vivado 프로젝트에 파일을 추가합니다. File > Add Sources…를 클릭하고 "Add or create design sources"를 선택합니다. 그런 다음 Next를 클릭합니다. "Add Files" 버튼을 클릭하고 verilog/src/ 디렉토리에서 "i2c_if.v"라는 파일을 선택하십시오. 그런 다음 "Finish" 버튼을 클릭하세요.
processor는 두 개의 Xillybus streams의 도움으로 이 module을 제어합니다. 텍스트 편집기에서 verilog/src/xillydemo.v를 엽니다. "PART 3"라고 표시된 코드 부분을 삭제하십시오. 해당 부분 대신 다음 코드 조각을 삽입하세요.
/*
* PART 3
* ======
*
* The instantiation of i2c_if demonstrates how to use two Xillybus
* streams to implement an I2C interface with the camera sensor module.
*
*/
i2c_if i2c_if_ins
(
.bus_clk(bus_clk),
.quiesce(quiesce),
.i2c_clk(J6[15]),
.i2c_data(J6[14]),
.user_w_write_8_open(user_w_write_8_open),
.user_w_write_8_wren(user_w_write_8_wren),
.user_w_write_8_data(user_w_write_8_data),
.user_w_write_8_full(user_w_write_8_full),
.user_r_read_8_open(user_r_read_8_open),
.user_r_read_8_rden(user_r_read_8_rden),
.user_r_read_8_data(user_r_read_8_data),
.user_r_read_8_empty(user_r_read_8_empty),
.user_r_read_8_eof(user_r_read_8_eof)
);
또는 여기 에서 변경 후 xillydemo.v를 다운로드할 수 있습니다.
이 변경을 수행한 후 평소와 같이 bitstream 파일을 만듭니다.
i2c_if module은 J6[15] 및 J6[14]에 연결됩니다. 이 ports는 pin header를 통해 카메라 모듈의 SCL 및 SDA에 연결됩니다.
카메라 센서의 registers변경하기
I2C 명령을 카메라에 보내는 컴퓨터 프로그램은 이 링크 에서 다운로드할 수 있습니다. 이 프로그램을 Xillinux의 file system 에 복사합니다(예: scp를 사용하거나 파일을 TF card에 직접 복사).
파일이 있는 디렉터리로 변경합니다. compilation을 수행하려면 shell prompt 에 다음 명령을 입력하십시오.
# gcc -Wall -O3 -o i2c i2c.c
이 명령은 자동으로 완료되어야 합니다.
프로그램을 실행하는 방법입니다. 프로그램이 일반적으로 생성하는 출력도 표시됩니다.
# ./i2c
Camera sensor's product ID is 0x7673
Reg 0x3d = 0x88 (to be altered)
Reg 0xb0 = 0x00 (to be altered)
Reg 0x6f = 0x9a (to be altered)
Wrote 0x3d = 0x81
Wrote 0xb0 = 0x84
Wrote 0x6f = 0x9f
먼저, 프로그램은 카메라 센서의 product ID가 포함된 registers를 읽습니다. OV7670 카메라 센서에는 다양한 버전이 있습니다. 이 튜토리얼은 0x7673 ( product ID)라는 숫자로 자신을 식별하는 카메라 센서를 기반으로 합니다. 다른 product ID를 사용하는 OV7670 카메라 센서를 만날 가능성은 없습니다. product ID가 다른 경우 카메라 모듈에 다른 모델의 카메라 센서가 설치되어 있을 가능성이 있습니다.
프로그램은 카메라 센서 이미지의 색상이 정확하도록 필요한 최소한의 변경을 수행합니다. 이를 위해 3개의 registers가 변경되었습니다.
변경하기 전에 프로그램은 registers의 기존 값을 읽습니다. 이는 프로그램 출력의 다음 세 행입니다. 그런 다음 프로그램은 이러한 registers에 올바른 값을 씁니다.
이 세 가지 registers의 의미
안타깝게도 카메라 센서의 registers 의 의미는 부분적으로만 문서화되어 있습니다. OV7670의 registers 중 다수는 "reserved"로 정의됩니다. 따라서 registers 의 일부 변경이 왜 필요한지에 대한 설명이 없습니다. OV7670의 registers에 대한 정보 출처는 다양합니다. registers 에 대한 힌트를 찾을 수 있는 가장 좋은 곳은 카메라 센서의 Linux driver입니다. ov7670.c. 특히 ov7670_default_regs[] ( driver에 정의된 variable )에는 귀중한 힌트가 많이 포함되어 있습니다.
i2c.c 프로그램이 registers3개를 변경하는 이유에 대한 설명은 다음과 같습니다. 불행하게도 이러한 변경이 필요하다는 것이 분명함에도 불구하고 이러한 변경 중 일부에 대한 이유는 알려져 있지 않습니다.
- COM13 (0x3d): 먼저 이 register 의 bit 0이 '1'로 변경됩니다. 결과적으로 카메라 센서의 출력에서 U 와 V 의 위치가 바뀌게 됩니다. 이는 출력 형식이 UYVY가 되도록 필요합니다. 이는 mplayer 및 기타 소프트웨어에서 기대하는 형식입니다. 게다가 이번 register의 bit 3이 '0'로 변경되었습니다. 이 bit 의 의미는 카메라 센서 문서에 적혀 있지 않습니다.
- Reserved register (0xb0): 이 register에 대한 문서가 없습니다.
- AWBCTR0 (0x6f): 이 register는 카메라 센서의 white balance와 관련이 있습니다. OV7670의 Implementation Guide에 따르면 이 register 에 0x9f를 쓰면 두 가지 변경이 발생합니다. Advanced AWB mode가 활성화되고 Maximum color gain이 2x 에서 4x로 변경됩니다. 이 register를 바꾸지 않으면 white balance가 제대로 작동하지 않습니다.
다른 registers에 쓰기
이는 i2c.c 프로그램 시작 부분에서 찾을 수 있는 @writelist의 정의입니다.
static const struct {
int addr;
int value;
} writelist [] = {
{ 0x3d, 0x81 }, // COM13, swap UV, turn off reserved bit 3
{ 0xb0, 0x84 },
{ 0x6f, 0x9f }, // AWBCTR0, crucial for white balance
{ -1, -1 }, // Terminate
};
@writelist array 의 elements는 두 개의 숫자로 구성됩니다. 첫 번째 숫자는 register의 주소입니다. 두 번째 숫자는 이 register에 써야 할 값입니다.
예를 들어 첫 번째 element는 { 0x3d, 0x81 }입니다. 이는 0x81 값이 COM13에 기록된다는 의미입니다. 이 register의 address는 0x3d입니다.
배열의 마지막 요소는 { -1, -1 }여야 합니다.
카메라 센서의 drive current줄이기
카메라 센서와 Smart Zynq 보드 사이의 배선이 너무 길면 비디오 이미지가 불안정할 수 있습니다. 비디오 프레임이 점프하고 이미지 전체에 녹색과 보라색 줄무늬가 나타납니다. 이런 현상은 전선 사이에 발생하는 crosstalk 때문에 자주 발생합니다.
카메라 센서가 전선에 인가하는 전류를 줄임으로써 이 문제를 해결하는 것이 가능할 수도 있습니다. 이렇게 하려면 0x00 값을 COM2에 씁니다. 이 register의 address는 0x09입니다.
즉, @writelist의 정의를 다음과 같이 변경합니다.
static const struct {
int addr;
int value;
} writelist [] = {
{ 0x09, 0x00 }, // Drive current to 1x level
{ -1, -1 }, // Terminate
};
그런 다음 이 프로그램의 compilation을 수행하고 이전과 같이 프로그램을 실행하십시오.
다른 가능성
카메라 센서 문서(특히 OV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide )는 다른 여러 registers에 대한 정보를 제공합니다. 위에서 언급했듯이 Linux kernel의driver 도 중요한 힌트를 제공할 수 있습니다.
이 튜토리얼의 첫 번째 부분 에서 다음 명령을 사용하여 카메라 센서를 재설정할 수 있다는 점을 기억하세요.
# echo 1 > /dev/xillybus_write_32
이 명령의 결과로 모든 registers는 기본값으로 돌아갑니다.
모든 registers의 값을 출력합니다.
이는 i2c.c 프로그램의 main() 기능의 일부입니다.
if (0) { // Change this in order to print out registers instead
for (i=0; i<=0xc9; i++) {
i2c_read(i, &value);
printf("Reg 0x%02x = 0x%02x\n", i, value);
}
return 0;
}
이 부분의 목적은 모든 registers의 가치를 보여주는 것입니다. 이 부분은 "if (0)" 조건으로 인해 일반적으로 도달되지 않습니다. 모든 registers값을 인쇄하려면 이를 "if (1)"로 변경하십시오.
모든 registers를 인쇄하는 데 1초도 걸리지 않습니다. 프로그램 실행이 일시적으로 멈추거나 프로그램이 멈춘다면 I2C bus의 통신 오류 때문입니다. 이런 일이 발생하면 프로그램의 출력이 부정확하거나 불완전할 수 있습니다. 빠르고 원활하게 실행될 때까지 프로그램을 다시 실행하십시오.
모든 registers 의 인쇄물은 이 링크 에서 다운로드할 수 있습니다. 이 인쇄물은 카메라 센서가 올바른 색상의 이미지를 생성할 때 registers를 반영합니다. 기본값 인쇄(카메라 센서가 재설정된 직후)는 여기에서 다운로드할 수 있습니다. 카메라는 자동 밝기 조절, 화이트 밸런스 등의 결과로 일부 registers를 지속적으로 변경합니다.
I2C 쓰기 작업이 수행되는 방법
이 섹션에서는 I2C protocol의 기본 사항에 익숙해야 합니다.
i2c.c 프로그램은 두 개의 Xillybus streams를 통해 FPGA 내부의 i2c_if.v module 와 통신합니다. /dev/xillybus_write_8 및 /dev/xillybus_read_8.
I2C write operation은 다음과 같이 발생합니다.
- host가 /dev/xillybus_write_8을 열면 FPGA는 I2C start condition을 생성합니다.
- 이 device file 에 기록된 바이트는 수정 없이 I2C 와이어에 나타납니다.
- host가 /dev/xillybus_write_8을 닫으면 FPGA는 I2C stop condition을 생성합니다.
다음 단계는 i2c_write() 기능으로 구현됩니다.
static void i2c_write(int addr, unsigned char data) {
unsigned char sendbuf[3] = { i2c_addr << 1, addr, data };
allwrite(sendbuf, sizeof(sendbuf));
}
이 함수는 3바이트로 구성된 buffer를 준비합니다.
- I2C address. 쓰기 작업을 위한 0x42 입니다.
- register address.
- register에 기록되는 값입니다.
FPGA는 I2C bus를 통해 이 3바이트를 카메라 센서로 보냅니다. i2c_write() 기능은 /dev/xillybus_write_8을 열고 buffer의 데이터를 쓴 다음 파일을 닫습니다.
I2C protocol에 따르면 수신자는 bus에서 전송된 각 바이트를 확인해야 합니다. 각 바이트(8비트로 구성)에는 이 목적을 위한 9번째 비트가 있습니다. 이 9번째 비트에는 전송 중에 특별한 시간 슬롯이 있습니다. 바이트를 수신한 측은 바이트가 수신되었는지 확인하기 위해 이 시간 슬롯 동안 SDA 와이어를 '0' 로 당겨야 합니다.
카메라 센서가 이런 식으로 반응하지 않으면 FPGA 내부의 i2c_if module은 device file을 통해 더 많은 바이트를 받아들이는 것을 거부합니다. 이로 인해 오류가 발생하지는 않지만 close() 에 대한 함수 호출은 1000 ms지연 후에만 반환됩니다. 그 이유는 Xillybus의 driver는 파일을 닫기 전에 남은 모든 데이터가 FPGA 에 도달할 때까지 기다리기 때문입니다. 그러나 I2C slave가 바이트를 승인하지 않은 경우 FPGA는 다음 바이트 승인을 거부합니다. 이 상황에서 driver는 1000 ms를 기다린 다음 파일을 닫고 kernel log에 다음 메시지를 추가합니다.
Timed out while flushing. Output data may be lost.
kernel log 의 메시지는 "dmesg" 명령으로 볼 수 있습니다.
결론적으로 i2c_write() 에 대한 함수 호출을 완료하는 데 1초가 걸린다면 카메라 센서가 I2C bus 작업에 올바르게 응답하지 않았기 때문일 수 있습니다. 카메라 센서가 FPGA에 잘못 연결되었거나 전혀 연결되지 않았을 수 있습니다.
I2C 읽기 작업이 수행되는 방법
읽기 작업은 두 가지 개별 작업으로 구성되므로 더 복잡합니다.
- 그러나 데이터 바이트가 없는 쓰기 작업입니다. 이 작업의 목적은 register address를 I2C slave에 제출하는 것입니다.
- 읽기 작업. register의 값은 slave 에서 master로 전송됩니다.
i2c_read() 기능은 아래와 같습니다:
static void i2c_read(int addr, unsigned char *data) {
int fdr;
unsigned char cmdbuf[2] = { i2c_addr << 1, addr };
unsigned char dummybuf[2] = { (i2c_addr << 1) | 1, 0 };
allwrite(cmdbuf, sizeof(cmdbuf));
// We open xillybus_read_8 only now. Had it been open during the first
// operation, there would have been a restart condition rather than a
// stop condition after the first command.
fdr = open("/dev/xillybus_read_8", O_RDONLY);
if (fdr < 0) {
perror("Failed to open /dev/xillybus_read_8 read-only");
exit(1);
}
allwrite(dummybuf, sizeof(dummybuf));
allread(fdr, data, sizeof(*data));
close(fdr);
}
이 함수는 slave에 2바이트(@cmdbuf)를 보내는 것으로 시작됩니다.
- I2C address. 0x42이며 쓰기 작업과 동일합니다.
- 우리가 읽고 싶은 register 의 address 입니다.
i2c_read()는 /dev/xillybus_read_8을 엽니다. allwrite()와 달리 allread()에서는 이 작업이 수행되지 않습니다.
다음으로 i2c_read()는 allwrite()를 통해 bus 에 2바이트(@dummybuf)를 씁니다.
- I2C address. 이것은 0x43이 며, 이는 bus에 대한 읽기 작업임을 slave 에 알려줍니다.
- 0을 포함하는 바이트입니다. i2c_if module은 이 바이트의 내용을 무시합니다.
FPGA 의 i2c_if module은 수신한 첫 번째 바이트의 bit 0을 검사합니다. 이를 기반으로 FPGA는 bus에서 쓰기 작업을 수행해야 하는지, 읽기 작업을 수행해야 하는지 추론합니다. 읽기 작업이 필요한 경우 다른 모든 바이트의 내용은 무시됩니다. 이 바이트는 FPGA 에 수신해야 하는 바이트 수를 알려주는 용도로만 사용됩니다.
i2c_if module은 bus 에서 요청된 바이트 수만큼 읽어 /dev/xillybus_read_8을 통해 host 로 전송합니다. @dummybuf를 작성하여 bus 에 대한 읽기 작업을 시작하기 전에 이 device file을 열어야 합니다. 그 후 allread()는 register의 값을 읽습니다. allread()는 파일을 일찍 열어야 하기 때문에 파일을 열고 닫지 않습니다.
Bus restart
이 섹션은 OV7670 카메라 센서와 관련이 없습니다. 그러나 이 정보는 i2c_if가 다른 slave와 함께 사용되는 경우 유용할 수 있습니다.
i2c_read()는 allwrite() 에 대한 함수 호출을 두 번 수행합니다. 매번 /dev/xillybus_write_8이 열리고 닫힙니다. 그러다 보니 데이터를 보내기 전의 I2C start condition이 있고, 보낸 후의 I2C stop condition이 있습니다.
즉, register의 address를 slave로 보낸 후에 stop condition이 있는 것입니다. 그런 다음 master가 읽기 작업을 시작하기 전에 start condition이 있습니다.
카메라 센서는 이러한 일련의 이벤트를 예상합니다. 그러나 다음과 같이 읽기 작업을 시도하면 I2C slaves 로 작동하는 다른 전자 부품이 제대로 작동하지 않습니다. 이러한 구성 요소는 stop condition에 대한 응답으로 register의 address를 잊어버립니다. 따라서 bus의 첫 번째 작업과 두 번째 작업 사이에 restart condition을 생성해야 합니다.
i2c_if module은 다음과 같은 가능성을 지원합니다. /dev/xillybus_write_8이 닫혔다가 /dev/xillybus_read_8이 계속 열려 있는 동안 다시 열면 대신 bus 에 restart condition이 있게 됩니다. 즉, slave 에 restart condition이 필요한 경우 allwrite() 에 대한 함수 호출을 이동해야 합니다. 그러면 i2c_read()는 다음과 같습니다:
fdr = open("/dev/xillybus_read_8", O_RDONLY);
if (fdr < 0) {
[ ... ]
}
allwrite(cmdbuf, sizeof(cmdbuf));
allwrite(dummybuf, sizeof(dummybuf));
allread(fdr, data, sizeof(*data));
close(fdr);
다시 한번 말씀드리지만, 이 코드는 OV7670에는 적합하지 않습니다.
결론
카메라 센서의 registers에 액세스하기 위해 Xillybus IP core를 사용할 수 있습니다. I2C bus와 인터페이스하려면 추가 모듈이 필요합니다. i2c_if. 이 모듈은 다른 I2C slaves와 통신하는데도 유용합니다.
안타깝게도 OV7670 카메라 모듈의 레지스터에 대해 사용할 수 있는 정보가 부족합니다. 따라서 인터넷에서 해결책을 검색하거나 카메라 센서의 Linux driver의 도움을 받아야 할 수도 있습니다.