linear CCD TCD1201d readout and timing issue

Hello,
I try to read out a TCD1201d linear CCD. It has 2048 pixel and two clock pulses (one is the invert of the other). I have been able to read the CCD but it looks like the pixel data is always the data of the first pixel, as the output of all pixels varies with the light that gets to that first pixel.
According to the data sheet, the timing of the two clock is critical and the rising and falling pulses have to cross. I coded this by writing to the PORTD directly setting the respective bits. The allowed time is 60 ns which is in line with the 16 MHz. However, the readout is not advancing ... (also the value of the dummy pixels and dark pixels is the value of the first pixel that sees light). The noise compared to the comparator output is less than 0.2%, so the chip seems to be ok.

Has anyone been able to read the TCD1201d?

Other linear CCD like the ILX511 or TSL CCDs use only one clock and no boost and reset. And yes, I have read all articles I was able to find on this chip using the common search engines and all linear CCD articles in this forum without success (I have now spent multiple days on this search)
Any help is welcome ...
[Update]
using a logic analyzer, I was able to verify the right sequence of the signals. This includes the timing of the boost and reset versus integration time. I would love to see if someone has read the TCD1201 and could give me the right timing ...
[/Update]

Ciao, Mathias

Salut,
attached the timing taken from the pins. Ch1=Phase-1, Ch2=Phase-2, Ch3=Reset, Ch4=boost
The overlap between p1 and p2 when switching is 40 ns
The code for reading the chip, using an arduino UNO R2:

/*
  Linear CCD readout
  lCCD: Toshiba TCD 1201D, 2048 pixel
  Pinmap: 22 DIL
  Chip                               Arduino Pin
    1  :  OS (Output Signal)           A0
    2  :  DOS (Compensation Output)    A1
    3  :  OD (Power +5V)               +5V
    4  :  RS (Reset Gate)              6
    5  :  BT (Boost Pulse)             7
    6  :  P2 (clock Phase 2)           5
    19 :  P1 (clock Phase 1)           4
    21 :  SH (Shift Gate)              3
    all other pins connected to GND    GND
*/
#define OS_PIN A0
#define DOS_PIN A1
#define SH_PIN 3
#define P1_PIN 4
#define P2_PIN 5
#define RS_PIN 6
#define BT_PIN 7

int bt_time = 50; // length of boost
int rs_time = 50; // length of reset
int p1_time = 1; // length of P1
int p2_time = 1; // length of P2
int sf_time = 1; // shift between BT and RS

int sadc[256];  // signal output values read
int sdummy1[32];
int sdummy2[14];


void setup() {
  Serial.begin(9600);
  Serial.println("Setup start");
  for (int i=3;i<8;i++) pinMode(i,OUTPUT);
  pinMode(OS_PIN,INPUT);
  pinMode(DOS_PIN,INPUT);
  // take all to LOW
  PORTD = B00000000;
  Serial.println("Setup Complete");
}

void loop() {
  // do nothing - all done via SerialEvent
  Serial.println("start loop");
  readCCD();

  delay(5000);
}

void readCCD() {
  int dos = 300;
  int dwt = 1;
  int j;
  int val;
  for (int i=0; i<256;i++) sadc[i]=0;
  // start read cycle
  Serial.println("Start read cycle");
  // take all to LOW
  PORTD = B00000000;
  delay(1);
  // set inital state
  //       BRPPSxxx
  //       TS21H
  PORTD = B10011000;  
  delay(1);
  // take BT low
  //       TS21H
  PORTD = B00011000;
  delayMicroseconds(bt_time);
  // take RS high
  //       TS21H
  PORTD = B01011000;
  delayMicroseconds(rs_time);
  // take BT high
  //       TS21H
  PORTD = B11011000;
  delayMicroseconds(bt_time);
  // take RS low
  //       TS21H
  PORTD = B10011000;
  delayMicroseconds(rs_time);
  //
  Serial.println("read dummy output");
  // take gate to low
  //       TS21H
  PORTD = B10010000;
  for (int i=0; i<16;i++){
    // read cylce part 1 (p1=high,p2=low)
    // wait integration time
    delayMicroseconds(dos);
    sdummy1[i*2] = analogRead(DOS_PIN) - analogRead(OS_PIN);
    // p1 high - BT/RS cycle (4 steps)
    PORTD = B00010000;
    delayMicroseconds(bt_time);
    PORTD = B01010000;
    delayMicroseconds(rs_time);
    PORTD = B11010000;
    delayMicroseconds(bt_time);
    PORTD = B10010000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10100000;
//    PORTD = B10110000; // take p2 high
//    delayMicroseconds(p2_time);
//    PORTD = B10100000; // take p1 low
    // read cylce part 2 (p1=low,p2=high)
    // wait integration time
    delayMicroseconds(dos);
    sdummy1[i*2+1] = analogRead(DOS_PIN) - analogRead(OS_PIN);
    // p1 low - BT/RS cycle (4 steps)
    PORTD = B00100000;
    delayMicroseconds(bt_time);
    PORTD = B01100000;
    delayMicroseconds(rs_time);
    PORTD = B11100000;
    delayMicroseconds(bt_time);
    PORTD = B10100000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10010000;
//    PORTD = B10000000; // take p2 low
//    delayMicroseconds(p2_time);
//    PORTD = B10010000; // take p1 high
  }
  for (int i=0; i<32;i++){
    Serial.print(sdummy1[i]);
    Serial.print(" ");
  }
  Serial.println();
  Serial.println("read sensor output");
  for (int i=0; i<1024;i++){
    j = i / 4;
    // read cylce part 1 (p1=high,p2=low)
    // wait integration time
    delayMicroseconds(dos);
    sadc[j] += analogRead(DOS_PIN) - analogRead(OS_PIN);
    // p1 high - BT/RS cycle (4 steps)
    PORTD = B00010000;
    delayMicroseconds(bt_time);
    PORTD = B01010000;
    delayMicroseconds(rs_time);
    PORTD = B11010000;
    delayMicroseconds(bt_time);
    PORTD = B10010000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10100000;
//    PORTD = B10110000; // take p2 high
//    delayMicroseconds(p2_time);
//    PORTD = B10100000; // take p1 low
    // read cylce part 2 (p1=low,p2=high)
    // wait integration time
    delayMicroseconds(dos);
    sadc[j] += analogRead(DOS_PIN) - analogRead(OS_PIN);
    // p1 low - BT/RS cycle (4 steps)
    PORTD = B00100000;
    delayMicroseconds(bt_time);
    PORTD = B01100000;
    delayMicroseconds(rs_time);
    PORTD = B11100000;
    delayMicroseconds(bt_time);
    PORTD = B10100000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10010000;
//    PORTD = B10000000; // take p2 low
//    delayMicroseconds(p2_time);
//    PORTD = B10010000; // take p1 high
  }
  for (int i=0; i<256;i++){
    sadc[i] /= 8;
    Serial.print(sadc[i]);
    Serial.print(" ");
    if ((i%16)==0) Serial.println();
  }
  Serial.println();
  Serial.println("read second dummy output");
  for (int i=0; i<7;i++){
    // read cylce part 1 (p1=high,p2=low)
    // wait integration time
    delayMicroseconds(dos);
    sdummy2[i*2] = analogRead(DOS_PIN) - analogRead(OS_PIN);
    // p1 high - BT/RS cycle (4 steps)
    PORTD = B00010000;
    delayMicroseconds(bt_time);
    PORTD = B01010000;
    delayMicroseconds(rs_time);
    PORTD = B11010000;
    delayMicroseconds(bt_time);
    PORTD = B10010000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10100000;
//    PORTD = B10110000; // take p2 high
//    delayMicroseconds(p2_time);
//    PORTD = B10100000; // take p1 low
    // read cylce part 2 (p1=low,p2=high)
    // wait integration time
    delayMicroseconds(dos);
    sdummy2[i*2+1] = analogRead(DOS_PIN) - analogRead(OS_PIN);
    // p1 low - BT/RS cycle (4 steps)
    PORTD = B00100000;
    delayMicroseconds(bt_time);
    PORTD = B01100000;
    delayMicroseconds(rs_time);
    PORTD = B11100000;
    delayMicroseconds(bt_time);
    PORTD = B10100000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10010000;
//    PORTD = B10000000; // take p2 low
//    delayMicroseconds(p2_time);
//    PORTD = B10010000; // take p1 high
  }
  for (int i=0; i<14;i++){
    Serial.print(sdummy1[i]);
    Serial.print(" ");
  }
  Serial.println();
  Serial.println("read cycle complete");
}

Still hoping, someone has an interest in this problem
Ciao, Mathias

Hi,

    PORTD = B10010000;
    delayMicroseconds(rs_time);
    // switch P1/P2 state
    PORTD = B10110000;
    PORTD = B10100000;

I think it's a mistake to switch the clock phases in two operations by writing B10110000 and then B10100000, rather than just writing B10100000. You are introducing a 60ns or more likely 120ns skew between the clock phases, and I can't see anything in the datasheet that allows that. Try writing B10100000 directly, and similarly when switching the other way.

The Arduino high and low output resistances are not very different, so the clocks should cross over at about 2.5V with no extra effort. However, if getting the clock phases to cross above 1.5V is a problem, then try a hardware solution. Between each Arduino clock output and the corresponding sensor clock input connect a small signal Schottky diode, anode to Arduino, cathode to clock input. Connect a resistor of about 22 ohms in parallel with the diode. This will slow down the fall of the clock edge by about 10ns.

Salut,
I had originally switched p1/p2 in one statement but then thought the error would be that the crossing point is below 2.5V - So to be on the safe side, I moved the P2 forward. Unfortunately, this is not resolving the problem.

Ciao, Mathias

You know, sometimes we don't consider that we don't have to do everything with the Arduino. You could use a flip-flop or something to produce your two phases and they would be exactly overlapping. If you use a flip-flop, you would clock it at twice the frequency you needed. At least that would answer the question about whether or not the clocks were the issue.

PapaG:
You could use a flip-flop or something to produce your two phases and they would be exactly overlapping .... At least that would answer the question about whether or not the clocks were the issue.

Not necessarily. Many type of logic chips have outputs that sink current more strongly than they source it. So even with a flip flop, you might still have to resort to the sort of hardware I suggested to make sure the clocks cross at 2.5V or higher.

BTW the clock and ground connections between the Arduino and the sensor need to be really short, otherwise you are likely to get ringing on the clock lines. The clock pins have input capacitance of 400pF typical, which is rather high for an Arduino (and most logic chips generally) to drive.

Salut,
thanks for the reply!

I have no flip-flop at hand and due to the Christmas break, it will take some time to get one. However, if I read the documentation right, the cross-over voltage may not fall below 2.5 Volt, so raising P2 prior to lowering P1 will move the crossing voltage to 5 V well above 2.5 V.[Edit] I guess a NE 555 chip will do as flop flop - will try[/Edit]

In my understanding of the CCD, p1 creates in the first step a trap for the charge created by the captured light. Then P2 is lowered so that the charge flows into the P2-trap and P1 is raised to avoid a flow back. Now I can read the charge into my Arduino ADC. Then, via Reset (RT), the charge is removed. P1 is now lowered to let the charge from the left P2-trap flow into the P1-trap and P2 is raised to close the trap. (Just to confirm)

Still looks like the charge transport is not working and I read out pixel 1 ...

Ciao, Mathias

I guess a NE 555 chip will do as flop flop - will try[/Edit]

My suggestion to use a flip flop was because of its complimentary outputs, Q and /Q. The NE555 doesn't have a /Q output.

I can't any reason why a flip flop would perform better than direct port access on the Arduino.

Salut,

looks like there is no way to read this CCD with an arduino. I have tried all permutations including a flip-flop and a comparator for the signal. I played with all timing and had the same timing on the logic analyzer as it is in the data sheet. Interesting fact is that the DOS signal varies as well with the light intensity.
However, when searching the web I find that all successful linear CCD readouts use CCDs that have the phase1/phase2 signal generation inside the chip (i.e. ILX551 or TCD1304).
So I will give up the attempt to use the TCD1201 (too bad for the four chips I have) and trash them in favor of the Sony ILX511.

I´ll keep you updated once the chips have arrived.
Ciao, Mathias

I'm sure it's possible to read fro that chip using an Arduino. However, unless you have an oscilloscope, you won't know whether you have the signal timings right.

Hi guys. I've been searching for the answer to this problem for months now. I have 3 TCD1201d chips that I need working. I am curious if there is any progress on this topic? If the Arduino doesn't work what will? A comment earlier says that it won't be able to be fixed without a oscilloscope. If I have a oscilloscope, what would I have to do to fix it?

I apologize if my questions seem dumb. I've never had to drive a sensor like this before. The code is a tad bit over my head. Any help would be awesome. Thanks!

Here are my suggestions:

  1. Keep the ground and clock wires between the Arduino and the CCD as short as possible. Run these 3 wires right next to each other to minimise the inductance. Even a small amount of inductance will cause ringing, because of the high capacitance of the CCD clock inputs. If you can't keep the length of the wires to a few cm, then use a 74HC04 chip to buffer the signals, as shown on the datasheet (and keep the wiring between the 74HC04 and the CCD as short as possible).

  2. Make sure you have a 0.1uF decoupling capacitor connected across the CCD power and ground pins, as close to the CCD as possible.

  3. Switch the clock lines simultaneously using direct port access. To help make sure that they cross above 2.5V, try either:

(a) a 150 ohm pullup resistor on each clock line, connected as close to the CCD as possible; or

(b) the Schottky diode/parallel resistor arrangement I suggested earlier.

  1. Double check that you are sequencing all the CCD inputs correctly.

What do you have the output of the CCD connected to?

I'm using a Due to create the signals for a UPD3799CY (5300 pixels/line)
it can create two phase clocks and syncronous RB signals using 2 channels of the PWM,
which can also trigger the ADC
AND also use PDC to fill the buffer(s) ...

downside is the need for level converters of the Due output (clocks,RB & TG1-3 )
& voltage followers on the Due input (ADC) to adjust the level and of course the 12 vdc for the sensor ...

NOTE: this is not 100% tested / waiting for the scope to be delivered before doing the final hookup
BUT raw tests look like i can get 37 lines a sec (4096 pixel lines) and near 88 lines (2048 pixels)

the reason i chose the UPD3799CY is that is ripped out of desktop scanner & came with its own circuit board / decoupling , input & output buffers . ..

Salut ralphnev,

it would be nice to have a view of the code you are using.
I am awaiting the hardware to follow the suggestions dc42 has made to narrow down the issues. I also have to get an oscilloscope with sufficient bandwidth (might even use it as a reason to buy one).
I hope to have all in place starting February 2nd

Ciao, Mathias

Yo Mathias;
code is highly messy , filled full of testing :wink: & very uncommented ...
but PM me your email .. & i'll fwd it ..

Salut,

I have spent all weekend on reading the lCCD without success. I finally decided to order a oscilloscope as I wanted to buy one anyhow and I use this as excuse to finally get one. I need to see the signals and their timing as well as the real output of the CCD ...
It will take 2-3 weeks until I receive it, so there will be no news until then

Ciao, Mathias

Hi Matthias,

you'll need that scope. The problem with these devices is that they're insanely sensitive. If you use them in normal light (in front of a monitor, near lights on the ceiling, ...) they'll go into saturation.

Use your existing software and cover the setup with a box with a narrow rectangular slit on the top (1/4 by 4 inch). Measure on the output pin with the scope so you get a view of an entire cycle (trigger with SH on a second channel). You can use a piece of paper to cover the slit and get an idea of what the useful light level is. Spoiler: not much.

I'll attach some pics of the scope later this evening if I have time.

FYI: I programmed my TCD1201D in C with avrstudio and get around 350 full CCD measurements per second with naive programming. With some tuning this can be increased to 500. Fully optimised I believe 1000/s is possible on an ATMega by positioning the ADC capture and hold (after 1.5 ADC clock) correctly. In my setup I only need 2 or 3 bits from the ADC, so I have the freedom to scale the ADC clock up.

regards,
Fred

Salut Fred,
the scope is ordered but due to the Chinese New Year, the shipping is delayed and I expect to have it early March.
I will tackle the issue exactly as you wrote: first I will generate a symmetric clock pulse and check with the scope that the output is as expected. I found in the meantime quite a lot of documentation on these linear CCD and will first try to reproduce the measurements I found there. I then will add the adc readout and change the timing to make sure, the duty cycle of 50% on the clock pulse is still valid. I found code segments that can speed up the adc readout by almost a factor of 10.
In addition, I have a chipkit max32 as well as an arduino due, both running at 80(84) MHz, which may give better results.

As soon as the scope arrives, I'll post an update

Ciao, Mathias