Change Clock Speed

Oleum

Well-known member
Hello
This is my first post. I am rather a beginner in the microcontroller world, also have little experience with C++, having only used VisualBasic up to VS2019, as well as assembler (many years ago).

I am currently trying to control a TCD1304DG. Using the delay commands worked quite well, because you can specify the timing quite well. However, the timing will hardly be sufficient to drive an ADC as well.
But on my oscilloscope I could observe the reactions of the chip.

Now I am trying to use this clock:
#define CPU_RESET_CYCLECOUNTER do { ARM_DEMCR |= ARM_DEMCR_TRCENA; \
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; \
ARM_DWT_CYCCNT = 0; } while(0)

In principle this works very well for the slower signals. Only for the clock the tempo is again not enough.
I used the Teensy 4.1 because I had read that it can work with much higher clock rates. To generate the clock for my chip I used
a modulo function. But maybe it's possible to lead a chip-internal clock to the outside, which I can synchronize with the other signals?
I don't want to use a ready-made library for my chip, because I can't learn anything with it.

What would you suggest?

Code:
#define CPU_RESET_CYCLECOUNTER    do { ARM_DEMCR |= ARM_DEMCR_TRCENA;          \
                                       ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; \
                                       ARM_DWT_CYCCNT = 0; } while(0)


volatile int cycles;


int fMPin   = 3; 
int SH      = 5;
int ICG     = 7;

int duty = 50;
int freq = 40;
int fak = freq +1;
float dutys = (freq/(100/duty));


void setup()
{

  pinMode(fMPin, OUTPUT);   
  pinMode(SH, OUTPUT);   
  pinMode(ICG, OUTPUT);   


  digitalWriteFast(ICG, HIGH);
  digitalWriteFast(SH, LOW);


  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;


  noInterrupts();

}




void yield () {}


FASTRUN 

void loop(){
 
// ICG
if((ARM_DWT_CYCCNT >= 100) && (ARM_DWT_CYCCNT <= 2400) ){
  digitalWriteFast(ICG,LOW);
}
else {
  digitalWriteFast(ICG,HIGH);
}


// SH
if((ARM_DWT_CYCCNT >= 200) && (ARM_DWT_CYCCNT <= 1400) ){
  digitalWriteFast(SH,HIGH);
}
else {
  digitalWriteFast(SH,LOW);
}


// fMPin
if( float((ARM_DWT_CYCCNT * fak) % freq) < dutys  ){
  digitalWriteFast(fMPin,HIGH);
}
else {
  digitalWriteFast(fMPin,LOW);
}


if (ARM_DWT_CYCCNT>=1200000){
  CPU_RESET_CYCLECOUNTER;
  digitalWriteFast(fMPin,LOW);

}

}



On the screenshot you can see that the pulse width of the yellow signal (chip-clock) varies a lot. If I had a higher clock rate of the processor it should be possible to stabilize it, and besides to operate an ADC.
 

Attachments

  • ScreenImg.png
    ScreenImg.png
    89.4 KB · Views: 68
I am rather a beginner in the microcontroller world. I don't want to use a ready-made library for my chip, because I can't learn anything with it. What would you suggest?

Perhaps you could try using IntervalTimer (https://www.pjrc.com/teensy/td_timing_IntervalTimer.html), which uses the processor's PIT (programmable interrupt timer) to generate interrupts at a fixed rate. You can probably go as fast as 1 MHz (1 us interval). That would let you control the timing of the edges better than the method you are using. You can learn a lot by experimenting with interrupts and finding where you hit the limits.
 
Thank you @joepasquariello for responding.
I will look at your suggestion with the interval timers I have now also experimented with different clock speeds and it is indeed the case that the faster the processor runs, the more stable values I get. I think I need a cooler.
Now to get an analog reading I need to be able to read the ADC values within 2000ns. Is that even possible to read out almost 4000 readings in a row?
Otherwise I would always have to skip a few cycles, and then read other photodiodes
 
Now to get an analog reading I need to be able to read the ADC values within 2000ns. Is that even possible to read out almost 4000 readings in a row? Otherwise I would always have to skip a few cycles, and then read other photodiodes

It would be helpful if you told us more about what you are doing, what is the ADC you are using, etc. 2000 ns is 2 us. You might be able to read a 12-bit bit value from an ADC in 2 us, but I don't think you could do it via the bit-banging method you are using now. There is nothing wrong with experimenting to see what you can do controlling digital outputs this way, but why don't you want to learn about and use the peripherals built into the microcontroller for this purpose? If you are using an SPI ADC that can handle over 10 MHz clock frequency, you might be able to read a sample every 2 us.
 
As I mentioned above, I want to read out a TCD1304DG.
This is a linear image sensor. I want to build a spectrometer from it. The optical parts are available to me.

I haven't made a decision yet on which ADC I would use. For example, I don't know yet up to which sampling rate the Teensy's internal ADCs can work. If they would be too slow, I would of course use an extrenal ADC. I also lack the expertise, for example, whether it is possible to address the internal registers of the processor faster. I suppose C++ is not the fastest method here.
 
As I mentioned above, I want to read out a TCD1304DG.I haven't made a decision yet on which ADC I would use. For example, I don't know yet up to which sampling rate the Teensy's internal ADCs can work. If they would be too slow, I would of course use an extrenal ADC. I also lack the expertise, for example, whether it is possible to address the internal registers of the processor faster. I suppose C++ is not the fastest method here.

Okay. I looked at the TCD1304DG data sheet and I see the variables in your code match the signal names. You may want to read the iMXRT manual's chapter on QuadTimer. Each timer has 4 channels, and I think you could use it to generate the signals with much less overhead than the method you are using. There is also FlexIO, which I haven't used, but you can find threads on by searching the forum. I have the impression it has a steep learning curve. Regarding C++, compilers today are very good, so there is little to be gained by using assembly language.
 
I have tried the TeensyTimerTool.h now. With it I get just 500KHz. Can I still turn something at the timer configuration, so that I come to higher frequencies?
The signal looks clean, but what would you expect at 500KHz?

Code:
#include "TeensyTimerTool.h"

using namespace TeensyTimerTool;

 int fMPin   = 3; 
// int SH      = 5;
// int ICG     = 7;


PeriodicTimer fMPin_Timer;


void setup()
{

  pinMode(fMPin, OUTPUT);   
//  pinMode(SH, OUTPUT);   
//  pinMode(ICG, OUTPUT);   

  //noInterrupts();


  fMPin_Timer.begin(callback1, 1);

}

void callback1() {

digitalToggleFast(fMPin);

}




// void yield () {}


FASTRUN 

void loop(){
 
}
 
I have tried the TeensyTimerTool.h now. With it I get just 500KHz. Can I still turn something at the timer configuration, so that I come to higher frequencies?
The signal looks clean, but what would you expect at 500KHz?

Yes, in PeriodicTimer::begin(), you specified a 1 us period, so you get one cycle of your waveform every 2 us (500 kHz). The period argument is a float, so you can specify 0.5 instead of 1, and that should give you 1 MHz, and you can experiment to see how fast you can go with this method.
 
You're right, I hadn't realized it was float. 2MHz seems to be the maximum.
How can I achieve synchronization. Wouldn't I have to start the timers at the same time?
Is that possible if I create them as an array?

As soon as I use two timers, with their own interupts, the signal becomes unstable.

Code:
#include "TeensyTimerTool.h"

using namespace TeensyTimerTool;

 int fMPin   = 3; 
 int SH      = 5;
// int ICG     = 7;


PeriodicTimer fMPin_Timer;
PeriodicTimer SH_Timer;

void setup()
{

  pinMode(fMPin, OUTPUT);   
  pinMode(SH, OUTPUT);   
//  pinMode(ICG, OUTPUT);   

  //noInterrupts();


  fMPin_Timer.begin(callback1, 0.25);
  SH_Timer.begin(callback2, 1);

}

void callback1() {

digitalToggleFast(fMPin);

}

void callback2() {

digitalToggleFast(SH);

}



// void yield () {}


FASTRUN 

void loop(){
 
}
 

Attachments

  • 2Timers.png
    2Timers.png
    74.8 KB · Views: 53
Last edited:
You're right, I hadn't realized it was float. 2MHz seems to be the maximum.
How can I achieve synchronization. Wouldn't I have to start the timers at the same time?
Is that possible if I create them as an array?

As soon as I use two timers, with their own interupts, the signal becomes unstable.

That's not too surprising. As far as I know, there is no synchronization capability with TeensyTimerTool or IntervalTimer, and you quickly run out of CPU when you try to generate the waveforms the way you are doing. This is why you need to get into the details of other peripherals, such as QuadTimer/FlexPWM/FlexIO, where you can set up the peripheral to generate the waveform more automatically, and you can synchronize (or offset) the edges of multiple waveforms.
 
There's a lot of capacitance on the integration gate and shift gate inputs according to the datasheet, it likely needs a high current buffer to drive these at full speed. The application circuit of the datasheet hints at this too with its IC1.
 
@joepasquariello
Perhaps I had misunderstood you. All I found useful under the term QuadTimer was this timer library. That's why I thought that you could program the internal timers of the processor to run synchronously at 2MHz. But now I understand you to mean that I should get external timer devices, and connect them as peripherals to the Teensy. I thought you meant by peripherals the output capabilities that are built into the processor.

The only possibility I see now would be if you could route internal timers directly to the outputs, without having to keep jumping into an interupt routine to operate the output pins there. However, this method would also probably not provide a way to synchronize these timers. Maybe by always restarting them.


@MarkT

There's a lot of capacitance on the integration gate and shift gate inputs according to the datasheet, it likely needs a high current buffer to drive these at full speed. The application circuit of the datasheet hints at this too with its IC1.




Did you mean I should put the timer outputs back to inputs using wires that would then control the interupts?

If I connect an oscilloscope with 1MegOhm to the pins, I will hopefully not need an output driver?

So far, I only have my oscilloscope on the outputs and wanted to use only the internal interupts without using wires and drivers to connect the outputs to any inputs. Or did you mean something else?
 
Doing this signal generation interrupt based will generate a lot of overhead. So I'd at least not use 2 timers but only one which runs at 2MHz. In its callback you could then decide if you need to set the SH pin or not. Here a simple example:
Code:
#include "TeensyTimerTool.h"

using namespace TeensyTimerTool;

constexpr int fmPin = 3;
constexpr int SH    = 5;

PeriodicTimer t;

void callback()
{
    static int cnt = 0;

    digitalToggleFast(fmPin);
    if (cnt++ == 3)
    {
        digitalToggleFast(SH);
        cnt = 0;
    }
}

void setup()
{
    pinMode(fmPin, OUTPUT);
    pinMode(SH,OUTPUT);
    t.begin(callback, 250ns);
}

void loop()
{ 
}

which generates this pulse train:
Screenshot 2023-09-05 104207.jpg

Hope that helps getting you going.

BTW: if you google for "tcd1304dg github" you will find a couple of libraries controling this CCD from Arduino. Checking how they did it might also help.
 
@joepasquariello
Perhaps I had misunderstood you. All I found useful under the term QuadTimer was this timer library. That's why I thought that you could program the internal timers of the processor to run synchronously at 2MHz. But now I understand you to mean that I should get external timer devices, and connect them as peripherals to the Teensy. I thought you meant by peripherals the output capabilities that are built into the processor.

I think @luni has provided some very useful info, but just to answer your question, when I said peripherals, I meant the internal peripherals (timers, SPI, I2C, etc.), not external peripherals. QuadTimer is one of the internal peripherals. It's called QuadTimer because it's a timer with 4 "channels". Each channel can be associated with a pin, either input or output, so you can do Input Capture (detect an input change) or Output Compare (force an output change). There are no libraries that I know of that let you synchronize multiple QuadTimer channels. To use them, you have to get into lower-level programming of the timer, i.e. read the manual and figure out how to do what you want.
 
Are you the @luni who wrote the TeensyTimerTool.h?
Thank you, for your contribution. You are right, I should try to create the second pulse in the interupt routine, then it is synchronized right away.
The signal gets harmonics, but they still remain acceptable.
Do you want to tell me what your simulation program is called?

@joepasquariello
Good, I understand. That's something else I'd like to learn. How to access the hardware registers? For example, I have experimented with the portenta. But unfortunately there were problems with the clock there. If I had known how to read and write the registers, I would have gotten further. For example, are all registers accessible by name, or how do you do something like that?
 

Attachments

  • harmonics.png
    harmonics.png
    81.1 KB · Views: 44
@joepasquariello
Good, I understand. That's something else I'd like to learn. How to access the hardware registers? For example, I have experimented with the portenta. But unfortunately there were problems with the clock there. If I had known how to read and write the registers, I would have gotten further. For example, are all registers accessible by name, or how do you do something like that?

Registers are defined in file "imxrt.h" in TeensyDuino folder "cores\Teensy4". The code below is from that file, and it shows the data structure for a single QuadTimer channel, and for a complete QuadTimer. You could look at the source code for TeensyTimerTool to see an example of how they are used. For QuadTimer, one of the relevant files is "libraries\TeensyTimerTool\src\TimerModules\TMR\TMR.h".

Code:
typedef struct
{
   volatile uint16_t COMP1;
   volatile uint16_t COMP2;
   volatile uint16_t CAPT;
   volatile uint16_t LOAD;
   volatile uint16_t HOLD;
   volatile uint16_t CNTR;
   volatile uint16_t CTRL;
   volatile uint16_t SCTRL;
   volatile uint16_t CMPLD1;
   volatile uint16_t CMPLD2;
   volatile uint16_t CSCTRL;
   volatile uint16_t FILT;
   volatile uint16_t DMA;
   volatile uint16_t unused1[3];
} IMXRT_TMR_CH_t;
typedef struct
{
   union {
      IMXRT_TMR_CH_t CH[4];
      struct
      {
         volatile uint16_t unused2[15];
         volatile uint16_t ENBL;
      };
   };
} IMXRT_TMR_t;
#define IMXRT_TMR1		(*(IMXRT_TMR_t *)IMXRT_TMR1_ADDRESS)
 
Back
Top