2 MHz PWM of an LED

Status
Not open for further replies.

ibuddy

Member
Hi,

I started with the Teensy 4.0 (a few days ago) to build a quick and dirty camera chip tester. Therefore I am running a LED at ~2 MHz with PWM and check for intensity changes in the image output. Unfortunately, I do see some strange behaviour with my setup while I believe the Teensy is working absolutely fine. I hope, someone can answer some questions and has some hints for me.
Here it comes:

(1) I am using pin 15 at the T4 for PWM. This is my (slightly thinned) code:

Code:
#define DEBUG

const int led =  15;
const int internalLed =  13;

// intensity control via serial
char serialBuffer[16];
uint8_t serialBufferIdx = 0;
bool serialComplete = false;
char delimiter[] = "\r\n";
uint32_t timer = 0;

void CheckSerial(void);

void setup()   
{                
  pinMode(led, OUTPUT);
  pinMode(internalLed, OUTPUT);
  
  analogWriteFrequency(led, 2343750);
  analogWriteResolution(6);


  Serial.begin(9600);
}

void loop()                     
{

  CheckSerial(); // checks the serial and evaluates the "cmds"

}

// Serial input: number + '\r\n', i.e. "20\r\n"
void CheckSerial()
{

  while (Serial.available())
  {
    timer = millis(); // set for timeout
    serialBuffer[serialBufferIdx] = Serial.read();
    serialBufferIdx++;

    if (serialBufferIdx > 2) // check for \r\n
    {
      serialComplete = serialBuffer[serialBufferIdx-2] == '\r' && serialBuffer[serialBufferIdx-1] == '\n';
      if (serialComplete) break;
    }
  }
  
  if (serialComplete)
  {
    char* ptr = strtok(serialBuffer, delimiter);
    int number = atoi(ptr);

    if (number == 0)
    {
      pinMode(led, INPUT);
      #ifdef DEBUG
      Serial.println("off");
      #endif
    }
    else if (number == 63)
    {
      pinMode(led, OUTPUT);
      digitalWrite(led, HIGH);
      
      #ifdef DEBUG
      Serial.println("on");
      #endif
    }
    else if (number > 0 && number < 63)
    {
      pinMode(led, OUTPUT);
      analogWrite(led, number);
      #ifdef DEBUG
      Serial.print("new Intensity: ");
      Serial.print(number);
      Serial.println();
      #endif
    }
    else
    {
      #ifdef DEBUG
      Serial.println("error");
      #endif
    }

    // reset buffer
    while (serialBufferIdx > 0)
      serialBuffer[serialBufferIdx--] = ' ';
    serialBufferIdx = 0;
    serialComplete = false;
    
  }

  // [... time out timer routine...]
}

Does this perform a PWM with 64 steps @ ~2 MHz at pin 15? Unfortunetly I do not have an oscilloscope, but least my logic analyzer claims that I have the correct frequency at the pin.

(2) Connecting resistor + led from pin 15 to GND ( PIN15 -> RESISTOR -> LED -> GND) all seems to be fine. The LED collects ca. 15 mA and I see changes from off-state to on-state as expected. Since this is too much light for my application, I reduce the current by using a larger resistor. Now I see, that the LED goes off much earlier, i.e. at value 20/63 [analogWrite(led,20)]. Can anyone explain me why this could be the case? Less current should mean less light, but no light at all?

(3) Due to the issue (2) I am using a HEX buffer (74LS07) as a LED driver. It can sink up to 40 mA. Here is the schematics from the datasheet (link to the datasheet).

74LS07_Schematics.png

Connecting Pin15 with its input and connecting the led: VCC -> RESISTOR -> LED -> OUTPUT let the LED operate. But now I observe a very strange behaviour.
(a) The non-inverting 74LS07 seems to invert the PWM, 0 := max light and 63 := no light
(b) Increasing the resistor value leads to a loss of contrast. I mean, the max intensity is less (as expected), but it does not turn off anymore.
Do I need a pull-up resistor at pin 15 of the Teensy for this?

(4) What is the correct way to handle the timer's border values (0 and 63)? I did not look too long into the datasheet, but I believe this is not pure off and on, is it? As you can see in my code, I turn it on or define the pin as an input, respectively. Is it ok or is there any better/more elegant solution?


I know, the problem is not a pure programmer's issue, but hopefully I can get certain about the Teensy stuff.
Let me know, if you have any further questions or if I shall provide any further information.

Best wishes from Germany,
Marius
 
Hallo Marius,

A quick reply here on
(a) The non-inverting 74LS07 seems to invert the PWM, 0 := max light and 63 := no light

This is to be expected because when you connect "resistor + led from pin 15 to GND ( PIN15 -> RESISTOR -> LED -> GND)", you are actually sourcing current, while "connecting the led: VCC -> RESISTOR -> LED -> OUTPUT" you are sinking current.
To source from an output you have to set the output high, while sinking current requires the output to be low. See also page 7 of the datasheet.

Capture.PNG

If I have time tonight, I will hookup a scope to a T4 and check.

Regards,
Paul
 
Hallo Marius,
Took out the scope and ran your code.
Because I was too lazy to search for an LED, I used the onboard LED and modified these 3 lines:
Code:
const int led =  13;
//const int internalLed =  13;

//  pinMode(internalLed, OUTPUT);

PWM seems to function fine, here are the scope screendumps.
You can read the duty cycle bottom-right in the onscreen table ["+Duty"]

intensity 2
i02.png
intensity 7
i07.png
intensity 15
i15.png
intensity 23
i23.png
intensity 31
i31.png
intensity 39
i39.png
intensity 47
i47.png
intensity 55
i55.png
intensity 62
i62.png

Intensity 63 is full on.
At intensity 2 the onboard LED is visibly off.

Regards,
Paul
 
Hi Paul,

thanks for your help. Your comment about sinking and sourcing opened my eyes, you are absolutely right. When input of 74LS07 is low, it sinks ... and LED is bright ... All right!

Is it already night on your site? Thanks for checking the pwm with your scope.
A question to the on-board LED: Is 470 Ohm @ 3.3V the correct resistance to get the LED at the typical (max.) set point? (Low-current LED? 3 mA @ 2 V?
I "see" the same behaviour with my LED at the specified set point, i.e. 15 mA @ 2 V. But when reducing current by increasing the resistor value (i.e. 100 Ohm -> 1.5 kOhm), I observe the described issues.

I made some videos in order to "show" my observations. Please find the videos in my DropBox here.
Especially with 74LS07 and reduced current (File: "LED_74LS07_LowCurrent.mp4"), the LED does not turn off at "63" ... Also, Teensy with low current turns the LED off quite early. Of course, this could be related to human (or my) eye.

Any idea?

Here is my code used for the videos:

Code:
#include <Arduino.h>

const int led =  15;

// ramping
int intensity = 0;
bool upwards = true;


void setup()   
{                
  pinMode(led, OUTPUT);
  
  analogWriteFrequency(led, 2343750);
  analogWriteResolution(6);

  analogWrite(led, intensity);

  Serial.begin(9600);
}


void loop()                     
{
  
  if (upwards)
  {
    intensity++;
    if (intensity == 63)
      upwards = false;
  }
  else
  {
    intensity--;
    if (intensity == 0)
      upwards = true;
  }
  Serial.println(intensity);
  analogWrite(led, intensity);

  // // LED connected to Teensy
  // if (intensity < 5)
  //   delay(500);
  // else if (intensity < 20)
  //   delay(250);
  // else 
  //   delay(50);

  // LED connected to 74LS07
  if (intensity > 58)
    delay(500);
  else if (intensity > 43)
    delay(250);
  else 
    delay(50);
  
}
 
Of course, this could be related to human (or my) eye.

Yes, human vision perceives light in a logarithmic fashion. Our eyes are very sensitive to low light. Even a very weak light looks visible. Small changes at low intensity are easy to discern. But at higher intensity, those same small change look very similar, and a light which is much brighter appears to be only slightly more than one which is actually far less.

I believe the effect you're seeing is due to the PWM never going fully 100% high, so when driven "active low" the LED is still getting short pulses of current for 1/64th of the cycle. If the LED current is substantial, even those very short pulses are going to look like the LED is on fairly bright. That's just how our non-linear human vision works.

74LS07 is probably the wrong chip for this job. The combination of non-inverting buffer and open collector output and Teensy's PWM pin not going to 100% duty cycle at this setting are just not going to work. You need either an inverting chip, so you can sink current when Teensy's output is high, or you need a buffer that can source current. 74LS07 can't do either of those things. It's simply the wrong chip for this job.

A chip like 74AC04 or 74AC14 could work well. Those invert the signal, and they're rated to source up to 24 mA. Or perhaps you could use a NPN or N-channel mosfet transistor. 2N3904 or 2N2222 are common NPN parts which should work well.
 
Hi Paul,

thanks for your reply. I believe that you're right, the 74LS07 is not a good choice for my application. I will have a look at another chip from the 74-series.
The transistors are slightly too slow for this task. The On/Off times of the 74-ICs are ~10x faster.

Thank you for your help!

Cheers,
Marius
 
Indeed, the rise and fall times of the LS07 are to slow to propagate the short pulses when intensity is set to 1 or 2.
On the rather high PWM frequency: I assume the 2.343750 MHz has been chosen to fit your camera chip test application? [just curious].

Yes, human vision perceives light in a logarithmic fashion
Here is a piece of code that logarithmically increases the current through the LED but is perceived by us humans as a linear increase in brightness.
Code:
void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  //2000 steps log curve, 2 secs, 1.00346^2000 = 1000
  for (float i = 1; i < 1000; i = i*1.00346)
  //1000 steps log curve, 1 sec, 1.00693^1000 = 998
  //for (float i = 1; i < 1000; i = i*1.00693)
  { 
    digitalWriteFast(LED_BUILTIN, HIGH);
    delayMicroseconds(i);
    digitalWriteFast(LED_BUILTIN, LOW);
    delayMicroseconds(1000-i);
  }
}

Paul
 
Hi Paul,

thanks for your response. Well, I selected the frequency by checking the Teensy tutorial about PWM. I copied the "ideal"frequency from table 3 (section "PWM Resolution (Teensy LC, 3.0 - 3.6, 4.0, 4.1)") for a 6-bit resolution. Thanks for your code, this might be helpful in future.
I have just mentioned the "camera" to get an idea for everybody why I want to run a LED at 2 MHz since for "just dimming a LED" it is a bit crazy. Here a longer explanation:

We are using spectrographs in our products. The build-in CCD/CMOS sensors output the intensity distribution over wavelength. The system shall finally be able to measure the wavelength dependant transmission/reflection of optical surfaces by putting the sample spectrum in relation to a reference spectrum. For trustful results, we have to know the intensity linearity of the sensor(s) quite well. In order to measure this, we have to dimm a light source from 0 -> max. sensor (or ADC, i.e. 16-bit) counts.
In general, when dimming light sources in current (or voltage, respectively), ALL light sources additionally change their emission in wavelength and angle of radiation. Not very good, since we do not know the "exact" emmitted intensity (over wavelength). However, a LED can be pulse-width moduled and - being a semiconductor - follows its modulation instead of averaging or washing out the current flow. On HIGH it lights always the same, on LOW it is off: two states that are known or can be measured.
Due to short acquisition times of the sensor (~ millisections or less), a high PWM frequency is required in order to minimize the error, that the illumination and acquisition are not synchronized. 2 MHz corresponds to a period of 0.5 us, so with a 1 ms acquisition the sensor accumulates (and averages) 2000 periods. The effect, that the first and last period is not perfectly within the acquisition, is neglectable (2/2000).
Since we want to acquire even faster (~microseconds), 2 Mhz and 63 steps (6-bit resolution) seems to be a good compromise. We then just accumulate and average multiple spectra.
Normally, a FPGA or something else is used to get even higher PWM frequencies, but the Teensy 4.0 seems to be powerful enough to get a good trade off. But, what is important? We need to have sharp edges (rise and fall) in order to minimize these side effects. That is why we want to use a chip from the 7400 series. These claim to operate at > 20 MHz. I will try next with the 74HTC04. I believe it will give me the performance we need.

It's a pity, that I do not have a oscilloscope... Well, least we can somewhat check with our detectors. Thank you again for checking the output with the oscilloscope. That was very helpful for me!

Best wishes,
Marius
 
Hallo Marius,

Thanks for your detailed explanation. You may even consider using a 74AHCT04, this part has a rise & fall time ≤3ns.
Just out of curiosity, I checked the waveform on pin 15 using this code:
Code:
void setup() {
  pinMode(15, OUTPUT);
  analogWriteFrequency(15, 2343750);
  analogWriteResolution(6);
  analogWrite(15, 2);
}

void loop() {
}
IMG_20200626_123506.jpg

SDS00002.png

Pretty fast rise & fall time, ~2ns [at least, that is what my scope shows, not sure how accurate that is...]

Interestingly though is that this wafevorm is shown with analogWrite(15, 2), with analogWrite(15, 1) there is no output at all. Not sure why this is happening.
It looks like the waform belongs to analogWrite(15, 1) since 1/64th of 2.343750 MHz is ~6.67ns...

Paul
 
Hi Paul,

thank you for checking the PWM output again! I am sorry coming back to this late, unfortunately I had to check my broken washing maschine, yesterday.
Your observation is really interesting. Looks like some internal calculation fail or become incorrect. I (tried to) check the implementation of "analogWrite(pin, value)". I believe, it finally uses "quadtimerWrite(...)". I looked into this portion of code.

Code:
void quadtimerWrite(IMXRT_TMR_t *p, unsigned int submodule, uint16_t val)
{
	uint32_t modulo = 65537 - p->CH[submodule].LOAD + p->CH[submodule].CMPLD1;
	uint32_t high = ((uint32_t)val * (modulo - 1)) >> analog_write_res;
	if (high >= modulo - 1) high = modulo - 2;

	//printf(" modulo=%lu\n", modulo);
	//printf(" high=%lu\n", high);
	uint32_t low = modulo - high; // low must 2 or higher
	//printf(" low=%lu\n", low);

	p->CH[submodule].LOAD = 65537 - low;
	p->CH[submodule].CMPLD1 = high;
}

Not really sure, what's happening here... Perhaps we can ask Paul from PJRC to doublecheck this. May I kindly ask you to measure some more ON-times? Perhaps: 3,4,32,62,63,(64)? (Images would not be necessary)
That would be helpful in order to understand what's hapening here and would help me with my application. I will get the 74hct04 within the next days...

Offtopic:
By the way, are you happy with your siglent scope? We are looking for a simple oscilloscope, because we see how useful it is ... Any advice? We are no pro-developers, but checking things from time to time seems to be important...

Thanks and best regards from Germany,
Marius

edit: added link to source code files on github (Paul Stoffregen)
 
Hi Paul,

at least it looks consistent, thanks a lot! Value 64 should be out of range, since the counters (I know so far) count from 0 to 2^n-1 to access 2^n steps. On the other hand, if PJRC wants to start counting at 1, 64 should be (almost) full on. I will send Paul Stoffregen a message to find out what is going on.
Hopefully he leaves a message here.

I will have a look into the siglent scopes, thanks.

Cheers,
Marius
 
Unfortunately, PaulStoffregen does not want to recieve PNs, so I post my overview here:

Dismatch of set values and output times. For me it looks like an issue with the implementation in the duino-core. Here are the numbers:

PWM-f: 2343750 Hz
Resolution: 64 (6 bit resolution)
step width: ~6,7 ns

ON-times:

PWM value | theoretical / ns | observed / ns
---------------------------------------------------------------
0 | 0 | 0
1 | 6,7 | 0
2 | 13,3 | 7
3 | 20,0 | 13
4 | 26,7 | 20
5 | 33,3 |
32 | 213,3 | 206
61 | 406,7 |
62 | 413,3 | 406
63 | 420,0 | 416
64 | - | 416

What do you think?

-- If Paul does not reply within a few days, I am going to open a new thread with this issue to seperate it from my initial problem.
 
Yeah, I think something is off in the calculation. Value 64 should be full ON.

Maybe we have a bug in the code? Or maybe the documentation isn't quite right (about the ideal frequency perhaps)? Or maybe this is just some limitation that's not apparent.

I will put this on my list of issues to investigate. But I can promise you one thing: I will not dig into this problem for at least 2 weeks, probably longer. We're about to release 1.53, and then my top priority is releasing a Teensy 4 bootloader chip which many people are patiently waiting for.

But I can say that not all the PWM pins are capable of 100% duty cycle. It's a hardware limitation. In Teensy 4.x, there are basically 3 different types of PWM hardware. The QuadTimers and the FlexPWM "X" outputs are less capable than the FlexPWM "A" & "B" outputs. Here is a list of all the PWM pins with comments about which type of hardware each uses:

https://github.com/PaulStoffregen/c...83a6fd5888e8660b0fae078297f/teensy4/pwm.c#L19

For this fairly demanding PWM application, I'd recommend choosing one of the pins which is FlexPWM "A" or "B".


Unfortunately, PaulStoffregen does not want to recieve PNs

That's correct. As a general rule I answer questions only on public forum threads, for two reasons. 1: Answers become part of a searchable knowledge base, which over the long term helps everyone far more than private communication which nobody else can see. 2: Public discussion allows everyone here to participate. My opinions are not always correct, and often the many very smart people here (like PaulS) have valuable insight. You can't get that other input if communication is kept private.

So please do not contact me by PM, email, social networking or telephone for these technical questions. Ask here on this forum. And please, do not unnecessarily start new threads. A new thread is good when you have a genuinely different question for a different project. But new threads come with a serious cost disadvantage. First, they tend to annoy the many very smart people who read this forum daily and might help you (should be common sense that making a good 1st impression is key to getting help from strangers on the internet). Second, duplicate threads dilute attention away, especially if people who might comment are already following the original thread.
 
Hey Paul,

thanks for your response and acknowledgement. I am absolutely fine putting this onto your list. For me it is just important to know, what is going on. Therefore, many thanks for the further information. For me it is a bit difficult to find all information, since it looks as if information is spreaded over github, this forum and the PJRC webpage tutorials.

I was not aware, that you look into every new post popping up, that is why I just wanted to inform you via PN. I fully understand, that you do not want to receive PNs, you seem to be extremely busy in this forum and with the development. I just saw this as a simple option to send a (bug) information to PJRC as a company.

Thanks again for all help and best wishes,
Marius
 
Status
Not open for further replies.
Back
Top