TCD1304 with teensy 3.2

Reim

Member
Hi,
the CCD linear image sensor TCD1304 can be driven with a teensy 3.2.
There is a very detailed description on the TCD1304 at EsbenRossels page http://tcd1304.wordpress.com. His description is perfect, I have learned a lot there, thank you very much, Esben.

Here some remarks on creating teensy code driving the CCD.

1. Timing

The TCD1304 is driven by three pulses:
fM – the master clock, must run at 0.8-4 MHz
SH – the shift gate
ICG – the integration clear gate
The sensor has 3694 pixels, related to the light received by a pixel, it is charged up and the resulting voltage can be read at the output OS. It takes 4 fM cycles to readout that voltage of one pixel. So the readout frequency is 0.2 ... 1 MHz and the ADC must read the voltage in 1 ... 5 µs.
That’s hard, because buffer = analogRead(anlogPin); takes about 10 µs.


2. Speed up the ADC

In teensyduino there is ADC.h to be included. (https://github.com/pedvide/ADC).
Create an object with
Code:
ADC *adc = new ADC(); // adc object
and with the folllwing lines
Code:
adc->setReference(ADC_REFERENCE::REF_3V3, ADC_0);
adc->setAveraging(1); // set number of averages
adc->setResolution(12); // set bits of resolution
adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
the voltage can be read in half the time:
Code:
buffer[i] = adc->adc0->analogRead(analogPin); // takes 5,4 µs
buffer[i] = adc->analogRead(analogPin, ADC_0); // takes 4,9 µs .
That's faster, but not fast enough. Fortunately the settings for the adc object in the 5 lines above have also an influence on the time for analogRead(), so with those settings it takes only 3,5 µs.
Code:
buffer[i] = analogRead(analogPin); // takes 3,5 µs
It is faster than buffer = adc->analogRead(analogPin, ADC_0); because here it is decided which ADC to take (ADC_0 or ADC_1) only within the function, every time it is called. It’s better to choose the ADC once and call then a function specialized for the desired ADC to read all pixels.
This can be made in setup() with
Code:
adc->adc0->singleMode();
I have copied a function from ADC.cpp and ADC_Module.cpp and specialized it for ADC_0.
Code:
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;
}
Now it is fast enough:
Code:
buffer[i] = analogReadFastADC0(analogPin); // takes 2,2 us

3. Timing pulses

At the beginning we need two pulses: fM and another one triggering the ADC.
The pulses for fM 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 can drive pins 3, 4
FTM2 can drive pins 25, 32
All pins of one timer have the same frequency, so three different frequencies are possible.
It must be 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. (with f_CPU = 96 MHz)
To get a square wave the dutycycle must be half of 2^resolution (16 for 5 bits, 8 for 4 bits).
For details: https://www.pjrc.com/teensy/td_pulse.html
 

Attachments

  • TCD1304neg.ino
    4.9 KB · Views: 578
Hi,
thanks for your guide. I am trying to replicate it, but I have issues at the moment: can you please help me a little bit?
I have TCD1304DG, not AP. Is this critical?
I have tested the TCD1304neg.ino on a Teensy 3.6 and a Teensy 3.2. With or without the suggested components (resistor 150ohm, 2200ohm, transistor).
I get basically all the pixels value around 3600. If i cover a portion of the sensor, all the pixels still stay around 3600. If I cover the whole TCD1304, the pixels are all around 6-700, but this does not always happen.
Any hints?
Regards,
Davide
 
Hi,
thanks for your guide. I am trying to replicate it, but I have issues at the moment: can you please help me a little bit?
I have TCD1304DG, not AP. Is this critical?
I have tested the TCD1304neg.ino on a Teensy 3.6 and a Teensy 3.2. With or without the suggested components (resistor 150ohm, 2200ohm, transistor).
I get basically all the pixels value around 3600. If i cover a portion of the sensor, all the pixels still stay around 3600. If I cover the whole TCD1304, the pixels are all around 6-700, but this does not always happen.
Any hints?
Regards,
Davide

Hi Davide,
thanks for your interest.
I don't know if it is critical with DG or AP, and don't believe.
The reaction of your TCD seems to be the wrong way. Have a look at https://tcd1304.wordpress.com/tcd1304-pcb/ .
The three pulses controlling the CCD are negated on the pcb and my software is made that way. Did you build in a 7404?
best regards
Reiner
 
Hi,
thanks for your reply. I used the "2nd SMD-version" of the PCB from https://hackaday.io/project/9829-linear-ccd-module, it has an HC04 as inverter; the only difference is that I used a 2N5401 as transistor.
I wrote a small windows program that show a graph of the values printed on the serial.
I tested both 3v and the usb 5v as +V on the circuit. No differences.
For pin connections I used:
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
Must I connect something to pin 6 ?
Now I read more or less 1040 on all the pixels, no matter if there is light/dark/ a black strip in the middle. What am I missing?
Thank you very much!!
20180808_123733.jpg20180808_123539.jpg
 
Hi Davide,
I don't rally know if the transistor fits but I think it's not critical.
The transistors emitter should be connected to 3,3V (with 2,2k) not 5V, because the ADC can only work upto 3,3V.
Your connections are correct. Pin 6 must not be connected.
I've added to diagrams from an oscilloscope, the blue line is pin 17 the analog pin, red line ICG.
1. with much light on all pixels:AnalogoutMuchlight.jpg
2. with low light:AnalogoutLowlight.jpg
As you can see in the second picture there are some pixels at the beginning and at theend of the ICG frame, which are at 3,3 V, that are the dummy pixels 0..31 and 32..45 at the end.
In the first picture there is so much light on the CCD, that also those pixels are at the low voltage - too much light. All pixels read about 1400.
I hope it can help, feel free to ask again, if not.

Reiner
 
001.jpg002.jpg

It now works! Even if some things make me think: The TCD is REALLY sensible to he ambient light. I wanted to check the precision of detection cut-out slits in black cardboard. But at int_t integration time of 20000 the sensor was probably saturated, all pixels around 1700. I put the cut-out slits over the TCD, put 2 couples of (polarizer film sheet + another polarizer film at 90° blocking all the light), and a white paper sheet to diffuse the ambient light, and it works (see pictures) if the room is quite dark. Instead of the slits, each cut-out is a "round" valley, if I increment int_t time to 50000 it is better, a little bit. Is this normal? My goal would be to detect the position of a red laser (about 3mw) pointed at the TCD. I was able to measure its position, but ambient light sensisitvity is scaring me a little, if the sensor will operate in a dark room ok, but what if there is some more light? or it is operated outdoor? Is up to some degree solvable via firmware or electronically?
Thanks!
 
Hi Davide,
fine it works.
Yes, it's very sensible.
I don't believe there is a lot to do by firmware or electronics, if there is too much light the CCD is saturated,
then you can only shorten t_int, probably down to 10 microseconds ( by sending 't' to teensy)
May be a filter can help?
Reiner
 
Hi everyone,

i'm also trying to replicate it on a breadboard but i cannot manage to get any signal on OS pin. I've checked the clock signals with a logic analyzer and all seem to be good. All values i'm reading are at 3600.
Is it possible i got a faulty sensor?
@Madaeon: what did you change to get it running?

BRs,
Sem
 
Clock Signals (fM,SH,ICG) Bildschirmfoto 2019-12-26 um 19.08.20.png
Output (SH,OS) Bildschirmfoto 2019-12-26 um 17.13.52.jpg

Any Idea why i cannot read any values?
 

Attachments

  • Bildschirmfoto 2019-12-26 um 17.05.08.png
    Bildschirmfoto 2019-12-26 um 17.05.08.png
    53.4 KB · Views: 158
Last edited:
Is anyone willing to help me here?

Most assuredly @SamMoo … problem is it would have someone with the hardware at hand like the prior two posters on this year+ old thread.

> Or enough details / context /specifics to inspire and entice someone to understand the problem enough to get toward a solution.

Perhaps in reviewing the prior post details and presenting your own the missing piece will become apparent.
 
You are showing four signals, but the CCD takes only three.

I'm looking at the 3rd figure in your post, and assuming your four signals are (from the top)
MCLK (looks ok, I can't read the frequency)
SH (polarity is reversed and the delay from the falling edge of ICG is not long enough)
ICG (looks ok)
4th unknown, not sent to the CCD

Also I cannot see the frequency (or period) of ICG vs SH. The periods must obey this relation ICGperiod = n·SHperiod, and ICGperiod > 7,4 ms (assuming MCLK = 2,0 MHz) and SH > 10 µs

As reim suggest, try with very low values for the SHperiod. The chip is quite sensitive. In a normally lit room I see saturation with an SHperiod of app. 100-200 µs
 
Hi Reim

Could you include a listing, or a diagram, to show how you connected the TCD1304 to the Teensy?

Thank you
M Nelson
 
Thank you, I found that. Is there any reason you chose TX3, RX3 rather than, Tx2,RX2 or TX1,RX1?

The pairs on the side of the Teensy are a little more convenient for routing a pcb.
 
Last edited:
BTW, the bandwidth of the Teensy 3.2 ADC input, seems a bit tight for this. Do you see any issues with that? What do you think of doing it with the 4.0?
 
BTW, the bandwidth of the Teensy 3.2 ADC input, seems a bit tight for this. Do you see any issues with that? What do you think of doing it with the 4.0?

I don't know 4.0 good enough. In my description above i told a lot an the speed of the ADC.
 
One more question, I think. Are you using the logic inverter with your code? Would there be any issue in wiring the digital pins from the Teensy directly to the TCD 1304?

Thank you
 
I saw a note above somewhere asking if the transistor is important. The answer is yes. Alternatively an opamp with a sufficient slew rate should be good. Given the sensor performance, it can be a simple non-inverting unit gain configuration.
 
hi all,

i have been working on a TCD1304 project with some artistic inclinations, using a teensy 4.0

i have had some success in reading out the signal and live streaming the output over ethernet (using a wiz850io board) to a raspberry pi for further processing (i was able to achieve a 4Mhz clock rate, which equates to about 270 frames-- i.e. lines of pixels-- per second). i have also implemented the electronic shutter function

i realized that the waveform (SH, ICG, MCLK) generation code required to do the above was lacking-- too many interrupts and i was using delayMicros() to achieve waveform timing, which seemed like bad form. this manifested as timing slip between adc sampling and tcd1304 output

i have just finished rewriting the waveform generation to occur in hardware with the flexpwm modules available on the teensy 4. the adc sampling can now be driven by associated interrupts, which means that i have precise timing control over everything! this required some deep dives through these forums, the teensy core libraries, and the IMXRT1060 reference manual. I have not coupled this portion with the fast adc readout and realtime stream yet, but i anticipate this working well as the two halves of the code are pretty modular. i have checked the TCD1304 output with a scope and it looks good!

the next step, after getting the stream working with the updated waveform generation code, might be to use DMA to store the adc samples. right now, i am using pedvide's fast ADC library and it seems i can sample really fast (I tried to measure adc read speed at 12bits with no averaging and it seemed to be around 500ns; I have also verified this adc sampling speed by sampling known sine waves and looking at the output)

here is where all this code lives: https://github.com/ntyrell/TCD1304

here is just the pwm waveform generation code since i think it is the most interesting and widely useful part
Code:
#define PIXELS 3648
#define MCLK 4 // target TCD1304 clock rate in MHz; we will get as close as possible

// calculate counter values such that an integer number of both 128x divided fbus cycles and MCLK cycles happen during each frame
int CLKPMCLK = ceil((float)F_BUS_ACTUAL/(MCLK*1000000)); // f bus clock cycles per ccd clock cycles
int MCLKPF = ceil((float)(32+PIXELS+14)*4/128); // number of ccd clock cycles per frame
int CNTPF = MCLKPF * CLKPMCLK; // number of pwm counts per frame
int CLKPF = 128 * CNTPF; // bus clock ticks per frame
int USPF = CLKPF / (F_BUS_ACTUAL / 1000000); // microseconds per frame
// make sure that 6 + 1 + 0.5 > 128*116 - (32+PIXELS+14)*4 i.e. there's enough extra time between pixels to set ICG low then high
int CNT_ICG = (6.0 * (F_BUS_ACTUAL / 1000000))/128; 
int ES = 128; // how many times to divide framerate for electronic shutter [1 2 4 8 16 32 64 128]
int CNT_SH = (1.0 * (F_BUS_ACTUAL / 1000000))/ (128 / ES); // divide by whatever prescaler to set shorter integration time 
int off = (0.5 * (F_BUS_ACTUAL / 1000000))/(128 / ES); // delay time between ICG low and SH high
int adc_off = (0.0 * (F_BUS_ACTUAL / 1000000)); // delay time between ICG high and ADC sample start 
// for now the MCLK starts totally synchronized with ICG high edge but we could add a delay there too (datasheet says 0 delay is ok). I am not sure if I should change the polarity of the clock (do we want a rising or falling edge first?)

void setup_ICG() {
  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_08 = 1; // set pin 5 to be PWM2 A SM 01
  
  FLEXPWM2_SM1CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_WAITEN | FLEXPWM_SMCTRL2_DBGEN;
  FLEXPWM2_SM1CTRL2 |= 128; // set force enable

  FLEXPWM2_SM1CTRL = 112; // set prescaler to 128
  FLEXPWM2_SM1CTRL |= 2048; // reload on half count
  
  FLEXPWM2_SM1OCTRL = 0; // output control register, bits 10 and 9 are A, B polarity
  //FLEXPWM2_SM2OCTRL |= 1024; // set A polarity to 1, should swap high and low
  FLEXPWM2_SM1DTCNT0 = 0;
  FLEXPWM2_SM1INIT = 0; // set counter initialization to 0

  // we think of the waveform 0 as beginning when ICG goes high. We want all clocks to be synchronized relative to this moment
  FLEXPWM2_SM1VAL0 = CNTPF - 2; // set VAL0 to trigger an interrupt that will rest pixel count 128 clock cycles before ICG goes high; this should occur between ADC reads (which at max happen once ever 38*4 = 172 bus clock cycles), be careful if you have a large adc offset but i think that will stay near 0
  FLEXPWM2_SM1VAL1 = CNTPF - 1; 
  FLEXPWM2_SM1VAL2 = 0; 
  FLEXPWM2_SM1VAL3 = CNTPF - CNT_ICG;
  FLEXPWM2_SM1VAL4 = 0;
  FLEXPWM2_SM1VAL5 = 0;

  FLEXPWM2_OUTEN |= 512; // set output enable for A, module 1 

  // set up interrupt
  FLEXPWM2_SM1STS |= 0; // set val0 compare flag to 0
  FLEXPWM2_SM1INTEN = 1; // enable interrupts for val0 compare

  attachInterruptVector(IRQ_FLEXPWM2_1, flexpwm2_1_isr);
  NVIC_ENABLE_IRQ(IRQ_FLEXPWM2_1);

}

int count = 0;

void flexpwm2_1_isr() {
  // resets the pixel count before ICG goes high to signal new frame
  FLEXPWM2_SM1STS = 1;  // clear interrupt flag for sm 0
  count = 0;
  while (FLEXPWM2_SM1STS == 1); // wait for clear
}


void setup_SH() {
  //IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_01 = 6; // set pin 7 to be PWM1 B SM 03
  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_06 = 1; // set pin 4 to be PWM A SM 0

  FLEXPWM2_SM0CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_WAITEN | FLEXPWM_SMCTRL2_DBGEN;
  FLEXPWM2_SM0CTRL2 |= 128; // set force enable

  if (ES == 1) FLEXPWM2_SM0CTRL = 112; // set prescaler to 128
  if (ES == 2) FLEXPWM2_SM0CTRL = 96; // set prescaler to 64
  if (ES == 4) FLEXPWM2_SM0CTRL = 80; // set prescaler to 32
  if (ES == 8) FLEXPWM2_SM0CTRL = 64; // set prescaler to 16
  if (ES == 16) FLEXPWM2_SM0CTRL = 48; // set prescaler to 8
  if (ES == 32) FLEXPWM2_SM0CTRL = 32; // set prescaler to 4
  if (ES == 64) FLEXPWM2_SM0CTRL = 16; // set prescaler to 2

  // SH occurs 128 / SH_PRSC times during one frameout cycle, i.e. 1, 2, 4, 8, 16, 32, 128 times
  // could happen more times but would have to change val1
  
  FLEXPWM2_SM0CTRL |= 2048; // reload on half count (actually reloads immediately since val0 is set to 0)
  
  FLEXPWM2_SM0OCTRL = 0; // output control register, bits 10 and 9 are A, B polarity
  //FLEXPWM2_SM0OCTRL |= 1024; // set A polarity to 1, should swap high and low
  FLEXPWM2_SM0DTCNT0 = 0;
  FLEXPWM2_SM0INIT = 0; // set counter initialization to 0

  FLEXPWM2_SM0VAL0 = 0; 
  FLEXPWM2_SM0VAL1 = CNTPF - 1; 
  FLEXPWM2_SM0VAL2 = CNTPF - ES*CNT_ICG + off; 
  FLEXPWM2_SM0VAL3 = CNTPF - ES*CNT_ICG + off + CNT_SH; // pulse SH high for CNT_SH counts
  FLEXPWM2_SM0VAL4 = 0;
  FLEXPWM2_SM0VAL5 = 0;

  // 0000 0100 0000 0000
  FLEXPWM2_OUTEN |= 256; // set output enable for A, module 0

}

void setup_MCLK() {
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_10 = 2; // set pin 6 to be PWM2 A SM 02
  
  FLEXPWM2_SM2CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_WAITEN | FLEXPWM_SMCTRL2_DBGEN;
  FLEXPWM2_SM2CTRL2 |= 128; // set force enable

  // I'll set prescaler to 64 such that the min frequency is 150e6/2^16/16 = 35.8Hz
  // CTRL = 0000 0100 0110 0000
  FLEXPWM2_SM2CTRL |= 2048; // reload on half count
  
  FLEXPWM2_SM2OCTRL = 0; // output control register, bits 10 and 9 are A, B polarity
  FLEXPWM2_SM2DTCNT0 = 0;
  FLEXPWM2_SM2INIT = 0; // set counter initialization to 0

  FLEXPWM2_SM2VAL0 = 0; // set VAL0 to be halfway through counter??
  FLEXPWM2_SM2VAL1 = CLKPMCLK - 1; // each clock tick is 2/3us with 16x prescaler
  FLEXPWM2_SM2VAL2 = 0; // for now, no phase shift
  FLEXPWM2_SM2VAL3 = CLKPMCLK/2; // pulse ICG low for CNT_ICG counts
  FLEXPWM2_SM2VAL4 = 0;
  FLEXPWM2_SM2VAL5 = 0;

  // 0000 0100 0000 0000
  FLEXPWM2_OUTEN |= 1024; // set output enable for A, module 2 

}

void setup_ADCCLK() {
  // don't need to set up output pin; just using this for interrupt
  // use flexpwm2 A sm 3
  
  FLEXPWM2_SM3CTRL2 = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_WAITEN | FLEXPWM_SMCTRL2_DBGEN;
  FLEXPWM2_SM3CTRL2 |= 128; // set force enable

  // I'll set prescaler to 64 such that the min frequency is 150e6/2^16/16 = 35.8Hz
  // CTRL = 0000 0100 0110 0000
  FLEXPWM2_SM3CTRL |= 2048; // reload on half count
  
  FLEXPWM2_SM3OCTRL = 0; // output control register, bits 10 and 9 are A, B polarity
  FLEXPWM2_SM3DTCNT0 = 0;
  FLEXPWM2_SM3INIT = 0; // set counter initialization to 0

  FLEXPWM2_SM3VAL0 = 0; // set VAL0 to be halfway through counter??
  FLEXPWM2_SM3VAL1 = 8*CLKPMCLK - 1; // each clock tick is 2/3us with 16x prescaler
  FLEXPWM2_SM3VAL2 = adc_off; 
  FLEXPWM2_SM3VAL3 = adc_off + 4*CLKPMCLK; // set up an edge ever 4 MCLK cycles
  FLEXPWM2_SM3VAL4 = 0; // for now, no phase shift
  FLEXPWM2_SM3VAL5 = 0; // remember B uses 4 and 5

  // don't need to set output; just using this for interrupt

  // set up interrupt
  FLEXPWM2_SM3STS |= 0<<3; // set val3 compare flag to 0
  FLEXPWM2_SM3STS |= 0<<2; // set val2 compare flag to 0
  FLEXPWM2_SM3INTEN = 1<<3; // enable interrupts for val3 compare
  FLEXPWM2_SM3INTEN |= 1<<2; // enable interrupts for val2 compare

  attachInterruptVector(IRQ_FLEXPWM2_3, flexpwm2_3_isr);
  NVIC_ENABLE_IRQ(IRQ_FLEXPWM2_3);

}

int s = 1;

void flexpwm2_3_isr() {
  FLEXPWM2_SM3STS = 12;  // clear all set bits
  // do a fast read of adc here!
  digitalWriteFast(7,s); // toggle pin for scoping
  s = !s;
  count += 1;
  while (FLEXPWM2_SM3STS == 12); // wait for clear
}

void setup() {
  pinMode(7, OUTPUT);
  
  Serial.begin(115200);

  Serial.println(USPF);
  Serial.println(CLKPF);
  Serial.println(CNTPF);
  Serial.println(CNT_ICG);
  Serial.println(CNT_SH);
  Serial.println(off);

  // set up clocks  
  CCM_CCGR4 |= CCM_CCGR4_PWM1(CCM_CCGR_ON) | CCM_CCGR4_PWM2(CCM_CCGR_ON) | CCM_CCGR4_PWM3(CCM_CCGR_ON) | CCM_CCGR4_PWM4(CCM_CCGR_ON);
  
  //FLEXPWM2_MCTRL = 0; // make sure RUN flags are off
  FLEXPWM2_FCTRL0 = FLEXPWM_FCTRL0_FLVL(15); // logic high = fault
  FLEXPWM2_FSTS0 = 0x000F; // clear fault status
  FLEXPWM2_FFILT0 = 0;
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_CLDOK(15);

  setup_ICG();
  setup_SH();
  setup_MCLK();
  setup_ADCCLK();
  
  //Serial.print("FLEXPWM2_MCTRL: ");
  //Serial.println(FLEXPWM2_MCTRL);
  
  //FLEXPWM2_SM2CTRL2 |= 64; // force initialization ICG
  //FLEXPWM2_SM0CTRL2 |= 64; // force initialization SH
  // not actually initializing as expected..

  
  FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_LDOK(15); // do counters initialize upon loading?
  //FLEXPWM2_MCTRL |= FLEXPWM_MCTRL_RUN(15); // why is this not necessary?
  
  

}

int count_prev;

void loop() {
  // put your main code here, to run repeatedly:
  if (count != count_prev) Serial.println(count);
  count_prev = count;

}

and in reference to some of the above comments: i have not been buffering the TCD1304 output going to the teensy analog read pin and it seems fine.. output impedance from the datasheet is 1k ish..
 
Hi Reim,

I have your configuration and code running. But I am not quite sure how it is supposed to work.

For example, the commands 't' and 'T' seem to decrement or increment t_int and then call readTCD(). But then at the end of the if-then block, it calls readTCD() again.

So, it seems like any character other than 'r', 't' or 'T' causes a single read, while the characters 't' and 'T' cause two reads..

Is that intentional?

Second question, inside readTCD(), inside the loop, it seems like SHpin might be toggled once or none per ADC read. Can you explain a little more about how that is supposed to work? The description at hackaday says something about sequence and timing, but its not clear what each pin actually does.

And finally, on an oscilloscope it looks like their is a definite timing relationship between SH and ICG, but they seem not synchronized to fM, or at least as far as it appears in one sweep of the scope.

Is that intentional? Does it effect performance of the sensor, for example in terms of linearity or constancy of integration time?


Thank you
 
Last edited:
Hi, All:

I recovered a TCD1304DG on separate small interface board (standard 74HC04 circuit but with MCP6002 buffer/inverter on the analog output) from a discarded Milwaukee Seawater Refractometer, and used a Teensy 3.2 with @Riem's code linked above to grab the data. The ADC library has changed, so it required a few mods.

Interestingly the refractometer uses a 48 MHz PIC 18F2550 with only 2K of memory to capture the data, and it appears that they used a nonstandard protocol to read out the sensor. I did not fully investigate.

It works great! I put a strip of black electrical tape over the sensor, with a tiny pinhole, and captured this plot using a 20 ms exposure:
pinhole.png

Here is the source:
Code:
// updated 9/14/2020 by SJR, new ADC library calls
// Works with TCD1304DG
unsigned long fM  = 1000;       // Mclk in !!!! kHz !!!!!!!!!!
unsigned int t_int = 20000;     // 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 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;

void setup()
{
  pinMode(analogPin, INPUT);
  pinMode(SHPin,     OUTPUT);
  pinMode(ICGPin,    OUTPUT);
  pinMode(fMPin,     OUTPUT);   
  pinMode(ADCtrc,    OUTPUT);
  
  Serial.begin(115200);
  
  digitalWriteFast(SHPin, HIGH);
  digitalWriteFast(ICGPin, LOW);
  
  /*  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 timmer 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: https://www.pjrc.com/teensy/td_pulse.html
   */
  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: https://forum.pjrc.com/threads/25532-ADC-library-update-now-with-support-for-Teensy-3-1
   */
  adc->adc0->setReference(ADC_REFERENCE::REF_3V3); 
  adc->adc0->setAveraging(1);                 // set number of averages
  adc->adc0->setResolution(12);               // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); 
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); 
  adc->adc0->singleMode();              // is needed for analogReadFastADC0()

  noInterrupts();
//  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;
}

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, HIGH);
  digitalWriteFast(SHPin, LOW);
  elMi = 0;
  delayMicroseconds(2);
  digitalWriteFast(SHPin, HIGH);
  nrSHs = 1;
  delayMicroseconds(2);
  digitalWriteFast(ICGPin, LOW);       
  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, LOW);
      delayMicroseconds(2);
      digitalWriteFast(SHPin, HIGH);
      nrSHs++;
    }
  }
  while (elMi < T_ICG){}
}

void printBuffer(){
  int i,j,jmax,max=0;
  //0 to 3693, 3694 elements
  //skip first 32 and last 14
  for (i=32; i<3680; i++){
    j=i-32;  //3648 active elements
    Serial.print (j);
    Serial.print (',');
    Serial.println(buffer[i]);
    if(buffer[i]>max) { max=buffer[i]; jmax=j; }
    }
    Serial.print(max);Serial.print(" @ ");Serial.println(jmax);
}

void loop(){
  if (Serial.available())  {
    cmdBuffer[cmdIndex++] = Serial.read();
  }
  if (cmdBuffer[0] == 'r')  {
     printBuffer();
  }
  else if (cmdBuffer[0] == 't')  {
    if (t_int > 100){
      t_int = t_int-100;
    }
    if (t_int < 200){
      t_int = t_int - 10;
    }
    readTCD();
    Serial.print("T_ICG = ");
    Serial.print(T_ICG);
    Serial.print("   n = ");
    Serial.print(n);
    Serial.print("   t_int = ");
    Serial.println(t_int);
    if (t_int < 0) t_int = 0;
  }
  else if (cmdBuffer[0] == 'T')  {
    t_int = t_int + 100;
    readTCD();
    Serial.print("T_ICG = ");
    Serial.print(T_ICG);
    Serial.print("   n = ");
    Serial.print(n);
    Serial.print("   t_int = ");
    Serial.println(t_int);
  }
  cmdBuffer[0] = '\0';
  cmdIndex = 0;
  readTCD();
}
 
Last edited:
This is all very useful. Thank you to EsbenRossel, Reim and ntyrell for your good work.
I have been working to apply this to spectroscopy instrumentation for use in science education and have just posted a bunch of stuff to my GitHub page, https://github.com/SmokyMountainScientific. At that page, you will find repositories for software that will (1) help you design a spectrometer, (2) turn the design into STL files for 3D printing, (3) sets of STL files for (3a) a visible spectrometer and (3b) a uv-vis spectrometer, (4) firmware based on ntyrell's flex_pwm sketch, and software to compile a user interface for calibration. Electronic and optical hardware is described at SmokyMtSci.com, where you will also find links to videos demonstrating the assembly and use of the instrument.
Please be aware that the GitHub page also has repositories for hardware based on Texas Instruments microcontrollers, so if you dont see Teensy somewhere in the title or description, that is not what you want.
 
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?
 
Back
Top