TCD1304 with teensy 3.2

Hi Qar,
I assume you are talking about the TCD1304neg code for the 3.2 and not the ntyrell code for the 4.0. I have worked with the ntyrell code and have found that it works well (although not perfectly for my application). I dont have a Teensy 3.2, so I can't test this stuff out properly. If you are willing to switch to the Teensy 4.0 (or 4.1) I might be of more use to you.
I thought I might be able to modify the 3.2 code a little, put it on my teensy 4.0 and see what the scope says is happening. Unfortunately, I have not been able to get the code loaded.
Anyway, in my experience the TCD works fine on 3V3 power and no transistor. Seems to me that I have seen schematics were people put a logic inverter between the microcontroller and the tcd1304. I am not sure whether the code you are using is set up for that or not. I think one of the posts above had at least one of the pins going the wrong direction. If the logic is backwards (high when it is supposed to be low) then you are not going to get anything meaningful out.
The other thing I would point out is that someone above said that the sensor is really sensitive to light. I think that is the most likely source of your problem. If you have this thing working in a normal room level light, I think you need to run with something like 5 ms integration time or else all of the pixels will be saturated (relatively low voltage output. I think it is somewhere between 0.6 and 1.2 V). If you have the TCD1304 in total darkness, you should get a higher voltage reading on the output pin (like about 2.8 V). The integration time is the period of the SH pin. Looking at the TCD1304neg code, it looks like t_int is the integration time and it is set at 20000 us as a default. While you can cut the input value down, it looks like the user can't go below the ICG minimum, which is ~14 ms / fm where fm is the clock speed in MHz. Not sure what the clock speed is here, but if it is 2MHz then yoiur integration time is at least 7 ms. That is not a limitation of the TCD1304, it is just what the code says.
Good luck on this.
I know it's an old topic, but I think it fits here for everyone who is searching and has not much experience with electronics like me.

I wrote a code using interrupts to fit the timings with a teensy 4 and couldn't get get the sensor to work at 1Mhz. I found this thread and tried the code of jremington, but also only constant output on all 3694 pixels.

I am using a breadboard connecting everything directly to a teensy 4.0 (and a teensy 3.6 at 96Mhz for the code here). I have no oscilloscope, so i can't check the graphs. I read low Mhz range is working fine on breadboards (1Mhz I2C was working fine for me). I checked the datasheet and the 3.3V power supply (to prevent damaging the ADC) is fine for 1Mhz clock. So can the sensor run without a transistor at the output, capacitor on the input (the power supply line has a capacitor on the teensy board) on a breadboard? Is aliexpress 10€ sensor the reason for the problems?

Sorry for the late reply. The code posted in reply #23 assumes that there are inverting buffers on the TCD1304 SH and ICG inputs. To make it work with a direct connection, the HIGH and LOW states must all be swapped.

This is essentially that same code, but with those modifications, has been tested and works perfectly.

#include "ADC.h"
// last updated 12/3/2023 by SJR, new ADC library calls
// Works for TCD1304DG with DIRECT connections to control inputs
// Notebook 9/13/20 p9
// Teensy 4.0 stuff at
// TCD1304 timing notes from
    The data-rate is 1/4 of fM  (4 us/sample for fM = 1MHz)
    Pixels are only moved to the shift registers when ICG and SH coincide. If SH runs with a shorter period than ICG, the CCD runs in electronic shutter mode, and SH serves to control the integration time.
    The shortest integration time is 10 µs
unsigned long fM  = 1000;       // Mclk in !!!! kHz !!!!!!!!!!
unsigned int t_int = 20000;      // us (min 10 us)
unsigned int n = 1;             // nr SH pulses for whole T_ICG
unsigned int nrSHs = 0;         // nr of executed SH pulses
unsigned long T_ICGmin = 3694 * 4 * 1000 / fM ;  // us  >= n * t_int;

unsigned long T_ICG;

int LED_control = 2; //low to activate
int fMPin   = 3;      // Mclk out 3  FTM1
int SHPin   = 14;     //
int ADCtrc  = 6;      // ADCtriggerClock out 6 FTM0, only internal use
int ICGPin  = 15;     //
int analogPin = A3;   // Pin 17
ADC *adc = new ADC(); // adc object
uint16_t buffer[4000];
elapsedMicros elMi;
char cmdBuffer[16];
int cmdIndex;
char LED_state=1;  //off

void setup()
  pinMode(analogPin, INPUT);
  pinMode(SHPin,     OUTPUT);
  pinMode(ICGPin,    OUTPUT);
  pinMode(fMPin,     OUTPUT);
  pinMode(ADCtrc,    OUTPUT);
  pinMode(LED_control, OUTPUT);

  digitalWriteFast(SHPin, HIGH);
  digitalWriteFast(ICGPin, LOW);
  digitalWriteFast(LED_control, LED_state);

  /*  The pulses for Mclk and ADCtrigger are made with PWM outputs.
      On teensy 3.2 there are 3 timers for PWM:
      FTM0  can drive pins 5, 6, 9, 10, 20, 21, 22, 23
      FTM1  3, 4
      FTM2  25, 32
      all pins of one timer have the same frequency,
      so three different frequencies are possible.
      We can set the
        resolution with analogWriteResolution(bits),
        frequency with analogWriteFrequency(pin, frequency) and
        dutycycle with analogWrite(pin, value)
      Depending on the resolution we can choose different ranges of
      frequency, the lower the resolution the higher the possible frequency.
      e.g. 5 bits up to 1,5 MHz,  4 bits up to 3 MHz. (f_CPU = 96 MHz)
      The dutycycle must be half of 2^resolution (16 for 5 bits, 8 for 4 bits)
      to get a square wave.
      for details:
  analogWriteResolution(4);             // f <= 3 MHz
  analogWriteFrequency(fMPin, fM * 1000);
  analogWrite(fMPin, 8);             // dutycycle 50% von 2^4
  analogWriteFrequency(ADCtrc, fM * 1000 / 4);
  analogWrite(ADCtrc, 8);              // dutycycle 50% von 2^4

     Settings for analog Read
     for details:
  adc->adc0->setAveraging(1);                 // set number of averages
  adc->adc0->setResolution(12);               // set bits of resolution
  adc->adc0->singleMode();              // is needed for analogReadFastADC0()

  //  Serial.println(T_ICGmin);

/* the function uint16_t analogReadFastADC0(uint8_t pin)
   is from ADC.cpp and ADC_Module.cpp, here specialised for ADC0
   for more speed; this one takes 2,2 us with f_CPU = 96 MHz
uint16_t analogReadFastADC0(uint8_t pin) {
  uint16_t result;

  adc->adc0->startReadFast(pin);      // start single read (with adc->adc0->singleMode(); in setup() )
  while (adc->adc0->isConverting()) {} // wait for the ADC to finish
  //  __disable_irq();
  result = (uint16_t)ADC0_RA;
  //   __enable_irq();
  return result;
// expect decreasing output for increasing illumination.

void readTCD() {
  int i;

  n = 1;                                // calculate n and T_ICG from t_int
  while (n * t_int < T_ICGmin) {
    n++;  // t_int is given by user input
  T_ICG = n * t_int;
  while (!digitalReadFast(ADCtrc)) {}
  digitalWriteFast(ICGPin, LOW);
  digitalWriteFast(SHPin, HIGH);
  elMi = 0;
  digitalWriteFast(SHPin, LOW);
  nrSHs = 1;
  digitalWriteFast(ICGPin, HIGH);
  for (i = 0; i < 3694; i++) {
    while (!digitalReadFast(ADCtrc)) {}
    buffer[i] = analogReadFastADC0(analogPin);  // takes 2,2 us
    if (elMi >= nrSHs * t_int) {                // new SH-puls
      digitalWriteFast(SHPin, HIGH);
      digitalWriteFast(SHPin, LOW);
  while (elMi < T_ICG) {}
} //exits with SH LOW

void printBuffer() {
  int i, j, jmax, Imax = 0, jmin, Imin=10000;
  //0 to 3693, 3694 elements
  //skip first 16 and last 14
  for (i = 16; i < 3680; i++) {
    j = i - 16; //index for 3648 active elements + 16 light shield
    Serial.print (j);
    Serial.print (',');
    if (buffer[i] > Imax) {
      Imax = buffer[i];
      jmax = j;
     if (buffer[i] < Imin) {
      Imin = buffer[i];
      jmin = j;
  Serial.print(Imin); Serial.print("@"); Serial.print(jmin); Serial.print(" "); Serial.print(Imax);
  Serial.print("@"); Serial.println(jmax);
  Serial.print("T_ICG "); Serial.print(T_ICG); Serial.print(" min: "); Serial.print(T_ICGmin);
  Serial.print(" T_int "); Serial.print (t_int); Serial.print(" nSH "); Serial.println(n);

void loop() {
  if (Serial.available())  {
    cmdBuffer[cmdIndex++] =;
  if (cmdBuffer[0] == 'r')  {
  else if (cmdBuffer[0] == 't')  {
    LED_state = !LED_state;
    digitalWriteFast(LED_control, LED_state);

  cmdBuffer[0] = '\0';
  cmdIndex = 0;
Last edited:
I am preparing a post for github with circuits and firmware for the TCD1304 with T4, and a python class library and control program. It illustrates an optimal electrical design for signal and digitization performance, and a full set of features including clocked, triggered and gated frames and kinetic series. Hopefully it will be up in the next week or two. I have used this in my research for a few years.

Stay tuned.