Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 23 of 23

Thread: TCD1304 with teensy 3.2

  1. #1
    Junior Member
    Join Date
    Apr 2018
    Posts
    7

    TCD1304 with teensy 3.2

    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.
    Thats hard, because buffer[I] = 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 [I]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. Its 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
    Attached Files Attached Files

  2. #2
    Junior Member
    Join Date
    Aug 2018
    Posts
    8
    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

  3. #3
    Junior Member
    Join Date
    Apr 2018
    Posts
    7
    Quote Originally Posted by madaeon View Post
    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

  4. #4
    Junior Member
    Join Date
    Aug 2018
    Posts
    8
    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!!
    Click image for larger version. 

Name:	20180808_123733.jpg 
Views:	100 
Size:	73.1 KB 
ID:	14377Click image for larger version. 

Name:	20180808_123539.jpg 
Views:	226 
Size:	84.6 KB 
ID:	14378

  5. #5
    Junior Member
    Join Date
    Apr 2018
    Posts
    7
    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:Click image for larger version. 

Name:	AnalogoutMuchlight.jpg 
Views:	74 
Size:	76.0 KB 
ID:	14379
    2. with low light:Click image for larger version. 

Name:	AnalogoutLowlight.jpg 
Views:	64 
Size:	75.5 KB 
ID:	14380
    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

  6. #6
    Junior Member
    Join Date
    Aug 2018
    Posts
    8
    Click image for larger version. 

Name:	001.jpg 
Views:	103 
Size:	148.3 KB 
ID:	14387Click image for larger version. 

Name:	002.jpg 
Views:	89 
Size:	79.1 KB 
ID:	14388

    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!

  7. #7
    Junior Member
    Join Date
    Apr 2018
    Posts
    7
    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

  8. #8
    Junior Member
    Join Date
    Aug 2015
    Posts
    7
    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

  9. #9
    Junior Member
    Join Date
    Aug 2015
    Posts
    7
    Clock Signals (fM,SH,ICG) Click image for larger version. 

Name:	Bildschirmfoto 2019-12-26 um 19.08.20.png 
Views:	16 
Size:	99.3 KB 
ID:	18547
    Output (SH,OS) Click image for larger version. 

Name:	Bildschirmfoto 2019-12-26 um 17.13.52.jpg 
Views:	26 
Size:	50.0 KB 
ID:	18546

    Any Idea why i cannot read any values?
    Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	Bildschirmfoto 2019-12-26 um 17.05.08.png 
Views:	12 
Size:	53.4 KB 
ID:	18545  
    Last edited by SamMoo; 12-26-2019 at 05:09 PM.

  10. #10
    Junior Member
    Join Date
    Aug 2015
    Posts
    7
    Is anyone willing to help me here?

  11. #11
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,199
    Quote Originally Posted by SamMoo View Post
    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.

  12. #12
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,199
    For instance - until I clicked link above I might have assumed not getting OS signal related to some 'Operation System' when it seems to be the Ouput Signal :: tcd1304.wordpress.com/tcd1304-specifications/

    First date I see on the BLOG is Jan then March 2017 with regard to a single resolved Bug :: tcd1304.wordpress.com/bugs/

  13. #13
    Junior Member
    Join Date
    Dec 2017
    Posts
    2
    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 = nSHperiod, 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

  14. #14
    Senior Member
    Join Date
    Apr 2018
    Location
    Eastern Time, USA
    Posts
    106
    Hi Reim

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

    Thank you
    M Nelson

  15. #15
    Junior Member
    Join Date
    Apr 2018
    Posts
    7
    Hi M Nelson
    there is a list of connections in the file TCD1304neg.ino.

    Reiner

  16. #16
    Senior Member
    Join Date
    Apr 2018
    Location
    Eastern Time, USA
    Posts
    106
    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 by DrM; 01-31-2020 at 03:16 PM.

  17. #17
    Senior Member
    Join Date
    Apr 2018
    Location
    Eastern Time, USA
    Posts
    106
    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?

  18. #18
    Junior Member
    Join Date
    Apr 2018
    Posts
    7
    Quote Originally Posted by DrM View Post
    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.

  19. #19
    Senior Member
    Join Date
    Apr 2018
    Location
    Eastern Time, USA
    Posts
    106
    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

  20. #20
    Senior Member
    Join Date
    Apr 2018
    Location
    Eastern Time, USA
    Posts
    106
    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.

  21. #21
    Junior Member
    Join Date
    Apr 2020
    Posts
    1
    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..

  22. #22
    Senior Member
    Join Date
    Apr 2018
    Location
    Eastern Time, USA
    Posts
    106
    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 by DrM; 08-09-2020 at 11:27 PM.

  23. #23
    Junior Member
    Join Date
    Sep 2019
    Posts
    3
    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:
    Click image for larger version. 

Name:	pinhole.png 
Views:	1 
Size:	9.6 KB 
ID:	21743

    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 by jremington; 09-16-2020 at 02:06 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •