2011년 7월 8일 금요일

Experimental USB Camera

 To test single CMOS Image sensor module, I combined CPLD, SRAM, and USB FIFO module. In the below pictures, these blocks are connected by jumping wire. To operate circuit, it is required three pieces of software; for CPLD, verilog HDL code to interfacing with SRAM, USB FIFO, and CMOS sensor. And for USB FIFO, 8051 based firmware code to operate as Slave FIFO. At final, PC based application and USB device driver.






CMOS Image Sensor Module

It is composed of Omnivision's OV9121, powering components and PCB. OV9121 is setting as Master mode; provided MCLK, it distributes PCLK, VSYNC, HREF and digital data signals to CPLD module. The provided MCLK is 10Mhz for stable operation despite of regular MCLK 24Mhz.

SRAM

It has asynchronous operation, 16bit data bus width and 10ns maximum operation time for both reading and writing. The purpose of SRAM is buffering between CMOS Module and USB FIFO.

CPLD Module

It is Dr.Kim CPLD Module(MCA128-10) from http://www.devicemart.co.kr/mart7/mall.php?cat=004010000&query=view&no=18796.
Because of poor performance CPLD, CMOS Sensor has to be operated under 10Mhz MCLK. Its functionalities are CMOS Image Sensor Data Interfacing (based on PCLK, VSYNC, HREF and Pixel Data Bus), Pixel Data Gathering (two 8bits pixel data merge to 16bits data to be able to save on SRAM), USB FIFO Interfacing (based on nFLAGB, nSLWR, 16bits FD). Here is verilog HDL code.


`timescale 1ns / 1ps


`define USE_FULL_FLAG


module cam2usb(RESET, TRIG, 
VSYNC, HREF, /* MCLK, */ PCLK, D,
nWE, nOE, A, IO,
IFCLK, nFLAGB, nSLWR, FD/*, LED*/);


// 정의 되지 않은 내부 reg값을 정해진 초기값으로 설정할때 사용 하는 Reset Pin
input wire RESET;
// 이미지 센서로 부터의 영상획득 시점을 알려주는 영상회득 개시용 pin
input wire TRIG;
// 이미지 센서로 부터 전달되는 한 화면 혹은 한 라인의 동기화 신호 
input wire VSYNC;
input wire HREF;
// 이미지 센서의 동작 클럭
//output wire MCLK;
// 이미지 센서의 한 화소의 전송 속도를 나타내는 동기화 클럭
input wire PCLK;
// 이미지 센서의 한 화소의 디지털화된 데이터 
input wire [7:0] D;
// USB 인터 페이스 칩와 FPGA 사이의 데이터 전송시 병목 현상을 차단하기 위한 메모리 인터페이스 
output reg nWE;
output reg nOE;
parameter ADDR_SIZE = 18;
output wire [ADDR_SIZE-1:0] A;
inout wire [15:0] IO;
// USB 인터페이스 칩의 동기화된 전송을 위한 인터페이스 클럭
output wire IFCLK;
// USB 인터페이스 내부 메모리의 FULL상태를 지시하는 플래그 
input wire nFLAGB;
// USB 인터페이스로의 데이터 전송을 지시하는 인터페이스 Pin
output reg nSLWR;
// USB 인터페이스로 전송할 데이터 버스
output reg [15:0] FD;
// output wire [3:0] LED;

/* 외부 메모리를 FIFO로 사용할 수 있게 해주는 인터페이스 
실제 외부로 노출되는 어트레스는 상위 한비트를 제외한 나머지 부분이다.
상위 한바이트는 FIFO의 FULL과 EMPTY상태를 구분하기 위해서 사용된다. */
reg [ADDR_SIZE:0] read_addr;
/*
always @(posedge PCLK) begin
if (RESET) read_addr <= 0;
else if (~nOE) read_addr <= read_addr+1;
end
*/
always @(posedge nOE) begin
if (RESET) read_addr <= 0;
else read_addr <= read_addr+1;
end


reg [ADDR_SIZE:0] write_addr;
/*
always @(posedge PCLK) begin
if (RESET) write_addr <= 0;
else if (~nWE) write_addr <= write_addr+1;
end
*/
always @(posedge nWE) begin
if (RESET) write_addr <= 0;
else write_addr <= write_addr+1;
end


`ifdef USE_FULL_FLAG
reg empty_flag;
reg full_flag;
always @(write_addr or read_addr) begin
if (~|(write_addr[ADDR_SIZE-1:0]^read_addr[ADDR_SIZE-1:0])) begin
if (~|(write_addr[ADDR_SIZE]^read_addr[ADDR_SIZE])) begin
empty_flag <= 1'b1;
full_flag <= 1'b0;
end else begin
empty_flag <= 1'b0;
full_flag <= 1'b1;
end
end else begin
empty_flag <= 1'b0;
full_flag <= 1'b0;
end
end
`else
reg empty_flag;
always @(write_addr or read_addr) begin
if (~|(write_addr[ADDR_SIZE:0]^read_addr[ADDR_SIZE:0])) empty_flag <= 1'b1;
else empty_flag <= 1'b0;
end
`endif


// 이미지 센서의 신호선의 상태를 제어하는 State Machine
// 픽셀 데이터를 2 byte단위로 포집해서 FIFO에 주기적으로 저장한다.
parameter CAMERA_HDLR_STATE_SIZE = 6;
parameter CAMERA_HDLR_IDLE       = 6'b000001;
parameter CAMERA_HDLR_WAIT_VSYNC = 6'b000010;
parameter CAMERA_HDLR_VSYNC      = 6'b000100;
parameter CAMERA_HDLR_WAIT_HREF  = 6'b001000;
parameter CAMERA_HDLR_HREF0      = 6'b010000;
parameter CAMERA_HDLR_HREF1      = 6'b100000;


reg [7:0] lower_byte;
reg [7:0] higher_byte;
reg [CAMERA_HDLR_STATE_SIZE-1:0] camera_hdlr_state;
always @(posedge PCLK) begin
if (RESET) begin
camera_hdlr_state <= CAMERA_HDLR_IDLE;
nWE <= 1;
end else begin
case (camera_hdlr_state)
CAMERA_HDLR_IDLE: begin
if (TRIG) camera_hdlr_state <= CAMERA_HDLR_WAIT_VSYNC;
nWE <= 1;
end
CAMERA_HDLR_WAIT_VSYNC: begin
if (VSYNC) camera_hdlr_state <= CAMERA_HDLR_VSYNC;
nWE <= 1;
end
CAMERA_HDLR_VSYNC: begin
if (~VSYNC) camera_hdlr_state <= CAMERA_HDLR_WAIT_HREF;
nWE <= 1;
end
CAMERA_HDLR_WAIT_HREF: begin
if (HREF) begin
camera_hdlr_state <= CAMERA_HDLR_HREF0;
lower_byte <= D;
end else if (VSYNC) begin
camera_hdlr_state <= CAMERA_HDLR_IDLE;
end
nWE <= 1;
end
CAMERA_HDLR_HREF0: begin
camera_hdlr_state <= CAMERA_HDLR_HREF1;
higher_byte <= D;
nWE <= 0;
end
CAMERA_HDLR_HREF1: begin
if (HREF) begin
camera_hdlr_state <= CAMERA_HDLR_HREF0;
lower_byte <= D;
end else begin
camera_hdlr_state <= CAMERA_HDLR_WAIT_HREF;
end
nWE <= 1;
end
default: begin
camera_hdlr_state <= CAMERA_HDLR_IDLE;
nWE <= 1;
end
endcase
end
end


// FIFO의 상태와 USB 인터페이스의 상태, 이미지 센서 State Machine의
// 상태에 따라서 저장되있던 픽셀 데이터를 읽어 온다.
always @(posedge PCLK) begin
if (nOE &                                     // 최소 두 클럭당 한번의 읽기가 가능하다.
(|(camera_hdlr_state^CAMERA_HDLR_HREF0)) & // 메모리 Write단계일 때는 read 금지
(~empty_flag) &                            // 메모리가 비워져 있을때는 읽기 금지
nFLAGB) begin                              // USB 인터페이스가 꽉 찼을때는 읽기 금지
nOE <= 1'b0;
end else begin
nOE <= 1'b1;
end
end


assign A = (nOE ? write_addr[ADDR_SIZE-1:0] : read_addr[ADDR_SIZE-1:0]);
assign IO = (nOE ? {higher_byte, lower_byte} : 16'hz);


// FIFO에서 읽어온 데이터를 USB 인터페이스에 전송한다.
always @(posedge PCLK) begin
if (nOE) nSLWR <= 1;
else nSLWR <= 0;
end
/*
always @(posedge PCLK) begin
if (~nOE) FD <= IO;
end
*/
always @(posedge nOE) begin
FD <= IO;
end


assign IFCLK = PCLK;
// assign LED[0] = ~|(camera_hdlr_state^CAMERA_HDLR_IDLE);
// assign LED[1] = empty_flag;
// assign LED[2] = full_flag;
//assign LED[3] = 


endmodule

USB FIFO Module

Cypress FX2LP USB FIFO has several operation modes. In here, Slave FIFO mode is proper to interface with CPLD. It's scheme is informing internal status of 2k bytes fifo ram on FX2LP, allowing data transferring via CPLD under not-FULL internal status (nFLAGB's high state) and transferring via USB.


#pragma NOIV               // Do not generate interrupt vectors
#include
#include
#include            // SYNCDELAY macro
#include "counter0.h"
#include "binary.h"


typedef enum {
IDLE_STATE     = 0x00,
ON_STATE       = 0x01,
OFF_STATE      = 0x02,
AUTO_INV_STATE = 0x04
} state_t;


//#define BLINK_COUNT 2000 // 500us
#define BLINK_COUNT 4000 // 1ms


state_t state_PA0 = IDLE_STATE;
unsigned short count_PA0;
state_t state_PA1 = IDLE_STATE;
unsigned short count_PA1;
state_t state_PA3 = IDLE_STATE;
unsigned short count_PA3;
state_t state_PA7 = IDLE_STATE;
unsigned short count_PA7;


extern BOOL   GotSUD;         // Received setup data flag
extern BOOL   Sleep;
extern BOOL   Rwuen;
extern BOOL   Selfpwr;


enum {
    High_Alt0_BulkIN = 0,
    High_Alt1_IsocTripleIN,
    High_Alt2_IsocIN
};


enum {
    Full_Alt0_BulkIN = 0,
    Full_Alt1_IsocIN
};


BYTE    Configuration;      // Current configuration
BYTE    AlternateSetting = High_Alt0_BulkIN;   // Alternate settings


void Slave_Fifo_Init(void)
{
PA0 = 0;
PA1 = 0; // TRIG (active high)
PA3 = 0; // PWDN (active high)
PA7 = 0; // FSIN (active high)


/* 1. Configure bits IFCONFIG[7:4] to define the behavior of the interface clock. */
// IFCONFIG
// bit7 : IFCLKSRC(0 -> exernal clock, 1 -> internal clock)
// bit6 : 3048MHZ(0 -> 30Mhz, 1 -> 48Mhz)
// bit5 : IFCLKOE(0 -> disable, 1-> enable internal clock output)
// bit4 : IFCLKPOL(invert external or internal clock)
// bit3 : ASYNC(0 -> synchronous, 1 -> asynchronous)
// bit2 : GSTATE(0 -> disable GSTATE, 1-> enable GSTATE)
// bit1 : IFCFG[1:0] (0b00: ports, 0b01: reserved, 0b10: GPIF, 0b11: Slave fifo)
// bit0 : 
IFCONFIG = b01010000; // ext, 48Mhz, disable OE, invert, synchronous, disable GSTATE, ports
//IFCONFIG = b11110000; // int, 48Mhz, enable OE, invert, synchronous, disable GSTATE, ports
// SYNCDELAY;


/* 2. Set bits IFCFG1:0=11. */
IFCONFIG |= b00000011;
SYNCDELAY;


/* 3. Set REVCTL.0 and REVCTL.1 to 1. */
REVCTL = 0x03;
SYNCDELAY;


/* 4. Configure EPxCFG. */
// Default interface uses endpoint 2, zero the valid bit on all others
// Just using endpoint 2, zero the valid bit on all others
EP1OUTCFG = 0xA0; //bmVALID | bmBULK;
SYNCDELAY;
EP1INCFG = 0xA0; //bmVALID | bmBULK;
SYNCDELAY;
// EPxCFG
EP2CFG = 0xE0;  //bmVALID | bmIN | bmBULK; //EP2 is DIR=IN, TYPE=BULK, SIZE=512, BUF=4x
SYNCDELAY;
EP4CFG = (EP4CFG & 0x7F); //non-valid
SYNCDELAY;
EP6CFG = (EP6CFG & 0x7F); //non-valid
SYNCDELAY;
EP8CFG = (EP8CFG & 0x7F); //non-valid
SYNCDELAY;


// EPxFIFOCFG
// bit7 : reserved
// bit6 : INFM1(IN Full Minus One)
// bit5 : OEP2(OUT Empty Plus One)
// bit4 : AUTOOUT
// bit3 : AUTOIN
// bit2 : ZEROLENIN(Enable Zero-length IN Packets)
// bit1 : reserved
// bit0 : WORDWIDE(Select Byte/Word FIFOs on PORTB/D Pins)
EP2FIFOCFG = 0x01;
//EP2FIFOCFG = 0x00; // 8bit FD
SYNCDELAY;
EP4FIFOCFG = 0x00;
SYNCDELAY;
EP6FIFOCFG = 0x00;
SYNCDELAY;
EP8FIFOCFG = 0x00;
SYNCDELAY;


PINFLAGSAB = 0x00;   // defines FLAGA as prog-level flag, pointed to by FIFOADR[1:0]
SYNCDELAY;           // FLAGB as full flag, as pointed to by FIFOADR[1:0]
PINFLAGSCD = 0x00;   // FLAGC as empty flag, as pointed to by FIFOADR[1:0]
SYNCDELAY;           // won't generally need FLAGD


/* 5. Reset the FIFOs. */
FIFORESET = 0x80;             // activate NAK-ALL to avoid race conditions
SYNCDELAY;                    // 
FIFORESET = 0x02;             // reset, FIFO 2
SYNCDELAY;                    // 
FIFORESET = 0x00;             // deactivate NAK-ALL
SYNCDELAY;                    // 


/* 6. Set bit EPxFIFOCFG.3=1. */
EP2FIFOCFG |= 0x08; // AUTOIN


/* 7. Set the size via the EPxAUTOINLENH:L registers. */
// this is the length for high speed
EP2AUTOINLENH = MSB(512);
SYNCDELAY;
EP2AUTOINLENL = LSB(512);
SYNCDELAY;


// FLAGA asserts when current fifo is at or less than 3pkts+508
EP2FIFOPFH = 0x19; // DECIS=0, PKTSTAT=0, PKTS[2]=0, PKTS[1]=1, PKTS[0]=1, 0, 0, PFC[8]=1
SYNCDELAY;
EP2FIFOPFL = 0xfc; // PFC[7:0] = 0b11111100
SYNCDELAY;


// Reset data toggle to 0
TOGCTL = 0x12;  // EP2 IN
TOGCTL = 0x32;  // EP2 IN Reset

// We want to get SOF interrupts
USBIE |= bmSOF;
}


//-----------------------------------------------------------------------------
// Task Dispatcher hooks
//   The following hooks are called by the task dispatcher.
//-----------------------------------------------------------------------------
void TD_Init(void)             // Called once at startup
{
WORD i;
// when IFCFG[1:0] = 0b11,
// FIFOADR[1:0], PKTEND, and SLOE are automatically configured
// for PORTA(IO PORTA Alternate Configuration)
// PA.7(PORTACFG.7=0, PORTACFG.6=0) as FSIN pin
// PA.1(PORTACFG.1=0) as TRIG pin
// PA.0(PORTACFG.0=0) as RESET pin 
PORTACFG = 0;


// PA.3(WU2EN=0) as PWDN pin
WAKEUPCS &= ~0x02; // WU2EN=0
OEA = 0x08b; // Input FIFOADR[0:1], PKTEND, SLOE, OUTPUT(PA0, PA1, PA3)
//OEA = 0x03; // Input FIFOADR[0:1], PKTEND, PA3, OUTPUT(PA0, PA1)


IOA = 0x00; // pull up off
PA0 = 1; // RESET (active high)
for (i = 0; i < 200; i++);
PA0 = 0;


PA1 = 0; // TRIG (active high)
PA3 = 0; // PWDN (active high)
PA7 = 0; // FSIN (active high)

// When IFCFG.1 = 1
// PB[0:7] is configured as FD[0:7]
OEB = 0; // Input as FD[0:7]
IOB = 0;

// When IFCFG.1 = 1 and any WORDWIDE bit = 1
// PD[0:7] is configured as FD[8:15]
OED = 0; // Input as FD[8:15]
IOD = 0;


PORTECFG = 0x03; // needs to be set GSTATE to zero
OEE = 0x03; // IFCLK, CLKOUT
IOE = 0;


// set the CPU clock to 48MHz
// CPUCS
// bit7 :
// bit6 :
// bit5 : PORTCSTB (0 -> disable, 1-> enable RD#/WR# strobes of PORTC)
// bit4 : CLKSPD1
// bit3 : CLKSPD0 (00 -> 12Mhz, 01 -> 24Mhz, 10 -> 48Mhz, 11 -> x)
// bit2 : CLKINV (0 -> disable, 1 -> enable inverting CLKOUT)
// bit1 : CLKOE (0 -> disable, 1 -> enable CLKOUT signal)
// bit0 : 8051RES (1 -> reset 8051)
//CPUCS = ((CPUCS & ~bmCLKSPD) | bmCLKSPD1 | bmCLKOE);
CPUCS = 0x12;
SYNCDELAY;


Slave_Fifo_Init();
// EZUSB_InitI2C(); // Initialize EZ-USB I2C controller
// counter0_init();
}


/*
*/
void TD_Poll(void)             // Called repeatedly while the device is idle
{


switch (state_PA0) {
case IDLE_STATE: break;
case ON_STATE:
PA0 = 1;
count_PA0 = counter0_get();
state_PA0 = AUTO_INV_STATE;
break;
case OFF_STATE:
PA0 = 0;
count_PA0 = counter0_get();
state_PA0 = AUTO_INV_STATE;
break;
case AUTO_INV_STATE:
if ((((DWORD)counter0_get() - (DWORD)count_PA0 + 0x10000) % 0x10000) >= BLINK_COUNT) {
PA0 ^= 1;
state_PA0 = IDLE_STATE;
Slave_Fifo_Init();
}
break;
}


switch (state_PA1) {
case IDLE_STATE: break;
case ON_STATE:
PA1 = 1;
count_PA1 = counter0_get();
state_PA1 = AUTO_INV_STATE;
break;
case OFF_STATE:
PA1 = 0;
count_PA1 = counter0_get();
state_PA1 = AUTO_INV_STATE;
break;
case AUTO_INV_STATE:
if ((((DWORD)counter0_get() - (DWORD)count_PA1 + 0x10000) % 0x10000) >= BLINK_COUNT) {
PA1 ^= 1;
state_PA1 = IDLE_STATE;
}
break;
}


switch (state_PA3) {
case IDLE_STATE: break;
case ON_STATE:
PA3 = 1;
count_PA3 = counter0_get();
state_PA3 = AUTO_INV_STATE;
break;
case OFF_STATE:
PA3 = 0;
count_PA3 = counter0_get();
state_PA3 = AUTO_INV_STATE;
break;
case AUTO_INV_STATE:
if ((((DWORD)counter0_get() - (DWORD)count_PA3 + 0x10000) % 0x10000) >= BLINK_COUNT) {
PA3 ^= 1;
state_PA3 = IDLE_STATE;
}
break;
}


switch (state_PA7) {
case IDLE_STATE: break;
case ON_STATE:
PA7 = 1;
count_PA7 = counter0_get();
state_PA7 = AUTO_INV_STATE;
break;
case OFF_STATE:
PA7 = 0;
count_PA7 = counter0_get();
state_PA7 = AUTO_INV_STATE;
break;
case AUTO_INV_STATE:
if ((((DWORD)counter0_get() - (DWORD)count_PA7 + 0x10000) % 0x10000) >= BLINK_COUNT) {
PA7 ^= 1;
state_PA7 = IDLE_STATE;
}
break;
}
}


BOOL TD_Suspend(void)          // Called before the device goes into suspend mode
{
return(TRUE);
}


BOOL TD_Resume(void)          // Called after the device resumes
{
return(TRUE);
}


//-----------------------------------------------------------------------------
// Device Request hooks
//   The following hooks are called by the end point 0 device request parser.
//-----------------------------------------------------------------------------


BOOL DR_GetDescriptor(void)
{
return(TRUE);
}


BOOL DR_SetConfiguration(void)   // Called when a Set Configuration command is received
{
Configuration = SETUPDAT[2];
return(TRUE);            // Handled by user code
}


BOOL DR_GetConfiguration(void)   // Called when a Get Configuration command is received
{
EP0BUF[0] = Configuration;
EP0BCH = 0;
EP0BCL = 1;
return(TRUE);            // Handled by user code
}


BOOL DR_SetInterface(void)       // Called when a Set Interface command is received
{
BYTE updateDisplay = TRUE;
AlternateSetting = SETUPDAT[2];


// ...FX2 in high speed mode
if (EZUSB_HIGHSPEED()) {
// Change configuration based upon the Alt. Interface selected
switch (AlternateSetting) {
case High_Alt0_BulkIN:
EP2CFG = 0xE0; //bmVALID | bmIN | bmBULK; // EP2 is DIR=IN, TYPE=BULK, SIZE=512, BUF=4x
SYNCDELAY;
// Clear out any committed packets
FIFORESET = 0x80;
SYNCDELAY;
FIFORESET = 0x02;
SYNCDELAY;
FIFORESET = 0x00;
SYNCDELAY;
// this is the length for high speed
EP2AUTOINLENH = MSB(512);
SYNCDELAY;
EP2AUTOINLENL = LSB(512);
SYNCDELAY;
// Reset data toggle to 0
TOGCTL = 0x12;  // EP2 IN
TOGCTL = 0x32;  // EP2 IN Reset
       break;
case High_Alt1_IsocTripleIN:
// Only using endpoint 2, zero the valid bit on all others
EP2CFG = 0xD8; //bmVALID | bmIN | bmISOC | bm1024 | bmQUAD // EP2 is DIR=IN, TYPE=ISOC, SIZE=1024, BUF=4x
SYNCDELAY;
// Clear out any committed packets
FIFORESET = 0x80;
SYNCDELAY;
FIFORESET = 0x02;
SYNCDELAY;
FIFORESET = 0x00;
SYNCDELAY;
// this is the length for high speed
EP2AUTOINLENH = MSB(1024);
SYNCDELAY;
EP2AUTOINLENL = LSB(1024);
SYNCDELAY;
// This register sets the number of Isoc packets to send per
// uFrame.  This register is only valid in high speed.
EP2ISOINPKTS = 0x03;
       break;
        case High_Alt2_IsocIN:
EP2CFG = 0xD8; //bmVALID | bmIN | bmISOC | bm1024 | bmQUAD // EP2 is DIR=IN, TYPE=ISOC, SIZE=1024, BUF=4x
SYNCDELAY;
// Clear out any committed packets
FIFORESET = 0x80;
SYNCDELAY;
FIFORESET = 0x02;
SYNCDELAY;
FIFORESET = 0x00;
SYNCDELAY;
// this is the length for high speed
EP2AUTOINLENH = MSB(1024);
SYNCDELAY;
EP2AUTOINLENL = LSB(1024);
SYNCDELAY;
// This register sets the number of Isoc packets to send per
// uFrame.  This register is only valid in high speed.
EP2ISOINPKTS = 0x01;
       break;
}
} else {
// Change configuration based upon the Alt. Interface selected
switch (AlternateSetting) {
case Full_Alt0_BulkIN:
EP2CFG = 0xE0; //bmVALID | bmIN | bmBULK; // EP2 is DIR=IN, TYPE=BULK, SIZE=512, BUF=4x
            SYNCDELAY;
// Clear out any committed packets
            FIFORESET = 0x80;
            SYNCDELAY;
            FIFORESET = 0x02;
            SYNCDELAY;
            FIFORESET = 0x00;
            SYNCDELAY;
// this is the length for high speed
EP2AUTOINLENH = MSB(512);
SYNCDELAY;
EP2AUTOINLENL = LSB(512);
SYNCDELAY;
            // Reset data toggle to 0
            TOGCTL = 0x12;  // EP2 IN
            TOGCTL = 0x32;  // EP2 IN Reset
break;
case Full_Alt1_IsocIN:
EP2CFG = 0xD8;  // EP2 is DIR=IN, TYPE=ISOC, SIZE=1024, BUF=4x
            SYNCDELAY;
// Clear out any committed packets
            FIFORESET = 0x80;
            SYNCDELAY;
            FIFORESET = 0x02;
            SYNCDELAY;
            FIFORESET = 0x00;
            SYNCDELAY;
// this is the length for high speed
EP2AUTOINLENH = MSB(1023);
SYNCDELAY;
EP2AUTOINLENL = LSB(1023);
SYNCDELAY;
            // This register sets the number of Isoc packets to send per
            // uFrame.  This register is only valid in high speed.
            EP2ISOINPKTS = 0x01;
break;
}
}
return(TRUE);            // Handled by user code
}


BOOL DR_GetInterface(void)       // Called when a Set Interface command is received
{
EP0BUF[0] = AlternateSetting;
EP0BCH = 0;
EP0BCL = 1;
return(TRUE);            // Handled by user code
}


BOOL DR_GetStatus(void)
{
return(TRUE);
}


BOOL DR_ClearFeature(void)
{
return(TRUE);
}


BOOL DR_SetFeature(void)
{
return(TRUE);
}


#include "../commands.h"


#define bRequestType    SETUPDAT[0] 
#define bRequest        SETUPDAT[1] 
#define wValueL         SETUPDAT[2] 
#define wValueH         SETUPDAT[3] 
#define wIndexL         SETUPDAT[4] 
#define wIndexH         SETUPDAT[5] 
#define wLengthL        SETUPDAT[6] 
#define wLengthH        SETUPDAT[7] 


BOOL DR_VendorCmnd(void)
{
BYTE xdata i2c_id;
BYTE xdata tmp[2];


switch (bRequest) {
case VRQ_GET_STATUS:
EP0BUF[0] = IFCONFIG;
SYNCDELAY;
EP0BCH = 0;
EP0BCL = 1;
EP0CS |= bmHSNAK;
break;
case VRQ_PA0_CTRL:
if (state_PA0 != IDLE_STATE)
break;
#if 0
// Clear out any committed packets
            FIFORESET = 0x80;
            SYNCDELAY;
            FIFORESET = 0x02;
            SYNCDELAY;
            FIFORESET = 0x00;
            SYNCDELAY;


// Reset data toggle to 0
TOGCTL = 0x12;  // EP2 IN
TOGCTL = 0x32;  // EP2 IN Reset
#endif


if (wIndexL) {
if (wValueL) PA0 = 1;
else PA0 = 0;
} else {
if (wValueL) state_PA0 = ON_STATE;
else state_PA0 = OFF_STATE;
}
break;
case VRQ_PA1_CTRL:
if (state_PA1 != IDLE_STATE)
break;
if (wIndexL) {
if (wValueL) PA1 = 1;
else PA1 = 0;
} else {
if (wValueL) state_PA1 = ON_STATE;
else state_PA1 = OFF_STATE;
}
break;
case VRQ_PA3_CTRL:
if (state_PA3 != IDLE_STATE)
break;
if (wIndexL) {
if (wValueL) PA3 = 1;
else PA3 = 0;
} else {
if (wValueL) state_PA3 = ON_STATE;
else state_PA3 = OFF_STATE;
}
break;
case VRQ_PA7_CTRL:
if (state_PA7 != IDLE_STATE)
break;
if (wIndexL) {
if (wValueL) PA7 = 1;
else PA7 = 0;
} else {
if (wValueL) state_PA7 = ON_STATE;
else state_PA7 = OFF_STATE;
}
break;
case VRQ_I2C_WRITE:
i2c_id = wIndexL;
tmp[0] = wValueL;
if (EZUSB_WriteI2C(i2c_id, 1, &(tmp[0])) != I2C_OK) return TRUE;
//EZUSB_WriteI2C(i2c_id, 2, &(tmp[0]));
EZUSB_WaitForEEPROMWrite(i2c_id);
break;
case VRQ_I2C_READ:
//IOA ^= 0x04;
i2c_id = wIndexL;


if (EZUSB_ReadI2C(i2c_id, 1, &(tmp[0])) != I2C_OK) return TRUE;
EZUSB_WaitForEEPROMWrite(i2c_id);


EP0BUF[0] = (BYTE)(wIndexL);
EP0BUF[1] = (BYTE)(tmp[0]);
EP0BCL = 0x02;
break;
case VRQ_I2C_WRITE_REG:
i2c_id = wIndexL;
tmp[0] = wValueH;
tmp[1] = wValueL;
if (EZUSB_WriteI2C(i2c_id, 2, &(tmp[0])) != I2C_OK) return TRUE;
//EZUSB_WriteI2C(i2c_id, 2, &(tmp[0]));
EZUSB_WaitForEEPROMWrite(i2c_id);
break;
case VRQ_I2C_READ_REG:
//IOA ^= 0x04;
i2c_id = wIndexL;
tmp[0] = wValueH;
if (EZUSB_WriteI2C(i2c_id, 1, &(tmp[0])) != I2C_OK) return TRUE;
EZUSB_WaitForEEPROMWrite(i2c_id);
if (EZUSB_ReadI2C(i2c_id, 1, &(tmp[0])) != I2C_OK) return TRUE;
EZUSB_WaitForEEPROMWrite(i2c_id);


EP0BUF[0] = (BYTE)(wIndexL);
EP0BUF[1] = (BYTE)(wValueH);
EP0BUF[2] = (BYTE)(tmp[0]);
EP0BUF[3] = (BYTE)(0xff);


EP0BCL = 0x04;
//EP0CS |= bmHSNAK;
// EP0BUF[0] = tmp[0];
// SYNCDELAY;
// EP0BCH = 0;
// EP0BCL = 1;
// EP0CS |= bmHSNAK;
//EP1INBUF[0] = tmp[0]; SYNCDELAY;
// EP1INBC = 1; SYNCDELAY; // arm EP6IN
break;
default: return TRUE;
}


return FALSE;
}


//-----------------------------------------------------------------------------
// USB Interrupt Handlers
//   The following functions are called by the USB interrupt jump table.
//-----------------------------------------------------------------------------


// Setup Data Available Interrupt Handler
void ISR_Sudav(void) interrupt 0
{
GotSUD = TRUE;            // Set flag
EZUSB_IRQ_CLEAR();
USBIRQ = bmSUDAV;         // Clear SUDAV IRQ
}


// Setup Token Interrupt Handler
void ISR_Sutok(void) interrupt 0
{
EZUSB_IRQ_CLEAR();
USBIRQ = bmSUTOK;         // Clear SUTOK IRQ
}


void ISR_Sof(void) interrupt 0
{
EZUSB_IRQ_CLEAR();
USBIRQ = bmSOF;            // Clear SOF IRQ
}


void ISR_Ures(void) interrupt 0
{
// Whenever we get a USB Reset, we should revert to full speed mode
pConfigDscr = pFullSpeedConfigDscr;
((CONFIGDSCR xdata *) pConfigDscr)->type = CONFIG_DSCR;
pOtherConfigDscr = pHighSpeedConfigDscr;
((CONFIGDSCR xdata *) pOtherConfigDscr)->type = OTHERSPEED_DSCR;

EZUSB_IRQ_CLEAR();
USBIRQ = bmURES;         // Clear URES IRQ
#if 0
if (EZUSB_HIGHSPEED()) {
pConfigDscr = pHighSpeedConfigDscr;
pOtherConfigDscr = pFullSpeedConfigDscr;
} else {
pConfigDscr = pFullSpeedConfigDscr;
pOtherConfigDscr = pHighSpeedConfigDscr;
}

EZUSB_IRQ_CLEAR();
USBIRQ = bmURES;         // Clear URES IRQ
#endif
}


void ISR_Susp(void) interrupt 0
{
Sleep = TRUE;
EZUSB_IRQ_CLEAR();
USBIRQ = bmSUSP;
}


void ISR_Highspeed(void) interrupt 0
{
if (EZUSB_HIGHSPEED()) {
pConfigDscr = pHighSpeedConfigDscr;
((CONFIGDSCR xdata *) pConfigDscr)->type = CONFIG_DSCR;
pOtherConfigDscr = pFullSpeedConfigDscr;
((CONFIGDSCR xdata *) pOtherConfigDscr)->type = OTHERSPEED_DSCR;
// This register sets the number of Isoc packets to send per
// uFrame.  This register is only valid in high speed.
EP2ISOINPKTS = 0x03;
} else {
pConfigDscr = pFullSpeedConfigDscr;
pOtherConfigDscr = pHighSpeedConfigDscr;
}


EZUSB_IRQ_CLEAR();
USBIRQ = bmHSGRANT;
#if 0
if (EZUSB_HIGHSPEED()) {
pConfigDscr = pHighSpeedConfigDscr;
pOtherConfigDscr = pFullSpeedConfigDscr;
// This register sets the number of Isoc packets to send per
// uFrame.  This register is only valid in high speed.
EP2ISOINPKTS = 0x03;
} else {
pConfigDscr = pFullSpeedConfigDscr;
pOtherConfigDscr = pHighSpeedConfigDscr;
}

EZUSB_IRQ_CLEAR();
USBIRQ = bmHSGRANT;
#endif
}


void ISR_Ep0ack(void) interrupt 0
{
}
void ISR_Stub(void) interrupt 0
{
}
void ISR_Ep0in(void) interrupt 0
{
}
void ISR_Ep0out(void) interrupt 0
{
}
void ISR_Ep1in(void) interrupt 0
{
}
void ISR_Ep1out(void) interrupt 0
{
}


// ISR_Ep2inout is called on every OUT packet receieved.
// We don't do anything with the data.  We just indicate we are done with the buffer.
void ISR_Ep2inout(void) interrupt 0
{
    // Perform USB activity based upon the Alt. Interface selected 
/*
     switch (AlternateSetting)
    {
        case Alt1_BulkOUT:
        case Alt4_IsocOUT:
           // check EP2 EMPTY(busy) bit in EP2468STAT (SFR), core set's this bit when FIFO is empty
            if(!(EP2468STAT & bmEP2EMPTY))
            { 
                EP2BCL = 0x80;          // re(arm) EP2OUT
            }
        break;


        case Alt2_BulkINOUT:
        case Alt6_IsocINOUT:
            // check EP6 EMPTY(busy) bit in EP2468STAT (SFR), core set's this bit when FIFO is empty
            if(!(EP2468STAT & bmEP6EMPTY))
            { 
                EP6BCL = 0x80;          // re(arm) EP6OUT
            }
        break;
   }
*/
}


void ISR_Ep4inout(void) interrupt 0
{
}
void ISR_Ep6inout(void) interrupt 0
{
}
void ISR_Ep8inout(void) interrupt 0
{
}
void ISR_Ibn(void) interrupt 0
{
}
void ISR_Ep0pingnak(void) interrupt 0
{
}
void ISR_Ep1pingnak(void) interrupt 0
{
}
void ISR_Ep2pingnak(void) interrupt 0
{
}
void ISR_Ep4pingnak(void) interrupt 0
{
}
void ISR_Ep6pingnak(void) interrupt 0
{
}
void ISR_Ep8pingnak(void) interrupt 0
{
}
void ISR_Errorlimit(void) interrupt 0
{
}
void ISR_Ep2piderror(void) interrupt 0
{
}
void ISR_Ep4piderror(void) interrupt 0
{
}
void ISR_Ep6piderror(void) interrupt 0
{
}
void ISR_Ep8piderror(void) interrupt 0
{
}
void ISR_Ep2pflag(void) interrupt 0
{
}
void ISR_Ep4pflag(void) interrupt 0
{
}
void ISR_Ep6pflag(void) interrupt 0
{
}
void ISR_Ep8pflag(void) interrupt 0
{
}
void ISR_Ep2eflag(void) interrupt 0
{
}
void ISR_Ep4eflag(void) interrupt 0
{
}
void ISR_Ep6eflag(void) interrupt 0
{
}
void ISR_Ep8eflag(void) interrupt 0
{
}
void ISR_Ep2fflag(void) interrupt 0
{
}
void ISR_Ep4fflag(void) interrupt 0
{
}
void ISR_Ep6fflag(void) interrupt 0
{
}
void ISR_Ep8fflag(void) interrupt 0
{
}
void ISR_GpifComplete(void) interrupt 0
{
}
void ISR_GpifWaveform(void) interrupt 0
{
}


PC-based Software

USB driver is from open source libusb-win32, and application is written in Visual C++ 6.0. Its source code is so big to list. To access them, please leave a message.

Result

This is grepped image in PC-monitor.









댓글 13개:

  1. 안녕하세요?
    혹시 PKTEND 신호는 센서와 어떻게 연결을 하셨는지요?

    답글삭제
    답글
    1. PKTEND는 신경 쓰지 않아도 될 겁니다. 물론 Pull up을 하여도 되고, 영상 사이즈가 USB 전송단위와 맞아 떨어 지지 않는다면 이미지 센서와 usb fx2lp 사이에 fpga이나 cpld같은 하드웨어를 통해 하나의 영상이 종료 되는 시점에 한번씩 Low 상태로 만들어 주면 됩니다. USB 의 Bulk전송의 기본 단위가 256으로 기억하는데 대부분의 영상은 256 바이트의 배수로 될 것이니 그냥 pull up 해 주면 무관할 것입니다. 답변이 되었는지 모르겠네요.

      삭제
  2. 작성자가 댓글을 삭제했습니다.

    답글삭제
  3. 작성자가 댓글을 삭제했습니다.

    답글삭제
    답글
    1. 하드웨어 구성을 어떻게 하셨는지 모르기 때문에 답변에 제한이 있네요. 우선 영상 사이즈는 큰 문제가 되지 않습니다. 오히려 문제가 되는 것은 pixel clock이 어느 정도이냐가 결정적인 문제입니다. slave fifo 모드를 동기와 비동기로 동작 시킬 수 있는데 비동기 모드는 동기 모드 보다 속도가 느린 것으로 기억하기 때문에 동기 모드인지 비동기 모드인지 확인해 보시기 바라고, 동기 모드 일때 slave fifo의 동작 클럭과 pixel clock을 동일하게 맞추어 주어야 합니다. 그리고 소프트웨어 적인 문제에서 while() 루프 안에서 frame 단위로 USB 데이터를 읽게 됩니다. 기억하기로 512 byte인가로 기억합니다. 아마도 한번에 읽어내는 기본 단위를 512로 해 주셨을 것으로 사료 됩니다. 이것을 한 화면 크기로 키우셔야 중간에 잃어 버리는 데이터가 없게 됩니다. 이건 제 경험상으로 그랬습니다. cypress사에서 제공하는 sdk를 사용하시게 되시면 while문을 사용하지 않으셔도 읽어내는 함수 자체가 내부에서 데이터가 도착할때까지 polling 합니다. 되도록 이면 libusb-win32를 쓰시기 바랍니다. 시스템에 좀더 안정적입니다. 제 게시물 중에 usb_binder를 찾아 보시기 바랍니다. 위와 같은 것을 다 해보셔도 hsync에 맞게 들어 오지 안으신다면 하드웨어의 문제라고 생각합니다. cpld나 혹은 fpga의 문제 일 겁니다. 메일 주소를 알려 드리고 싶은데 스펨걸러 내기 너무 힘들어서요. 정말 메일로 주고 받고 싶으시면 lpslab님의 메일 주소를 먼저 알려주세요.

      삭제
    2. 작성자가 댓글을 삭제했습니다.

      삭제
  4. 안녕하세요? 이 글을 쓰신지도 1년이 넘었네요..ㅠ
    죄송하지만 혹시 위에 말씀하신 PC-based Software 소스 코드를 보내주실 수 있으신가요..
    부탁드립니다..
    geniusksk@naver.com

    답글삭제
    답글
    1. 아쉽게도 찾을 수가 없네요.

      삭제
    2. 아. 그렇군요ㅠ 알겠습니다. 저도 지금 FX2 SlaveFIFO를 사용해서 카메라로부터 영상을 획득하려고 하거든요..Streamer 예제를 수정하면서 해보고있는데 아직 소프트웨어쪽으로는 감이 없어서그런지 잘 안되네요..

      삭제
    3. 음. 아직 시작하시는 단계이시면 cypress사의 드라이버나 예제를 사용하기 보단 libusb-win32라는 오픈 소스 패키지를 사용하시기 바랍니다. VID와 PID만 알면 자동으로 드라이버를 생성하고 동일한 방식으로 다양한 USB 기기를 제어할 수 있습니다. 그리고 무엇보다 cypress사의 드라이버보다 안정적(그 당시의 기억으로 빈번하게 재부팅하는 경우가 많았습니다.)이었고 예제가 광범위하게 널려 있습니다. 아마도 원하시는 기능의 예제를 검색을 통해 거의 다 구할 수 있을 겁니다.

      삭제
    4. 네~ 자세한 답변 감사합니다..

      삭제
    5. 안녕하세요? 지난번에 바로 위 질문드렸던 사람입니다.
      zzZz님께서 2013년 5월23일에 작성하신 댓글에서 "동기 모드 일때 slave fifo의 동작 클럭과 pixel clock을 동일하게 맞추어 주어야 합니다."라고 하셨는데요. 혹시 여기서 slave fifo의 동작 클럭이라는 것이 IFCLK를 의미하는 것인지요.

      삭제
    6. 예 맞아요. IFCLK과 PCLK을 같이 묶어 주면 됩니다.

      삭제