What is the distribution of forward voltages within in a LED bin (binning by forward voltage)?

In our previous post What is the forward voltage deviation of typical power LEDs? we discussed that the typical forward voltage tolerance of LEDs. In this post we will discuss considerations regarding. Note that this is not based on actual distribution data since I could not find any, however it is based on actual datasheets and actual LEDs.

Since LEDs are binned, the following assumptions can be considered true if we assume what is promised in the datasheet is actually true:

  • Since each LED’s forward voltage is actually measured (this is the way the LEDs), no LED is outside the binning range
  • The LEDs are binned from an unknown base distribution of LEDs. Note that it is totally allowable to produce more LEDs in one bin than another, throw away some fraction of the LEDs or

First-level model: Truncated uniform distribution

As a first-level model, we can assume the LEDs to be uniformly distributed within the forward voltage range of the bin. In other words, for a bin of 2.90V to 3.0V (see the previous post), the likelihood of having a LED with Vf=2.91V is exactly the same as Vf=2.97V, but the likelihood of having a LED with Vf=2.88V is zero.

Second-level model: Unskewed non-uniform base distribution

Typical manufacturing distributions are not uniformly distributed but have some shape imposed by the process – such as a normal distribution. For simplicity, we can assume that the forward voltage distribution is an unskewed normal distribution centered around the average of the lowest and the highest possible forward voltage (lowest voltage of the lowest bin plus highest voltage of the highest bin, divided by 2).

In this second-level model, the bins lower than the center of the distribution will have a higher likelihood of having one of the higher voltages. For example, the 2.60V to 2.70V bin will have a slighly higher likelihood of drawing a Vf=2.69V LED than drawing a Vf=2.61V LED. For bins with voltages higher than the center of the distribution, the likelihood of drawing a lower voltage LED will be slightly higher. How large the difference in likelihoods is not only depends on the unknown base distribution, so we can’t assume all too much about it. But consider that the manufacturer will strictly avoid to throw away LEDs unneccessarily. So at least one of the following considerations must be true:

  • The LEDs thrown away due to forward voltage consideration are unmarketable
    • possibly because the number of LEDs produced outside the actual bins are too low to consider introducing another bin
    • possibly because these LEDs are considered defective or untestable (such as due to their light output not being within the specs)
  • Overall, the manufacturer will attempt to optimize the process in order to minimize the number of LEDs to throw away since they consume resources (manufacturing resources & time to test) but are thrown away
    • A sensible base assumption is that the number of LEDs sorted out is in the low single percent digits. Assume 2% with high variability between manufacturers if you lack better data.
  • The LEDs which are sorted out are marketed under a secondary (“non-premium”) brand with higher tolerances on at least some of the parameters

Third-level model: Considering tolerances and rounding modes

What does it mean to have a LED with a forward voltage of 2.70V – does an actual forward voltage 2.701V  put the LED into Bin A from 2.60V to 2.70V or Bin B from 2.70V to 2.80V? If we consider the measurement to be 100% accurate, obviously it will be put into Bin B.

However, even the OSRAM datasheet from our previous post lists

The Forward voltage is measured during a current pulse duration of typically 1 ms with a tolerance of ± 0.05V .

Note that ±50mV is quite a significant tolerance. Since we don’t know what the actual deviation of the measurement equipment is, we need to consider a tolerance of ±50mVfor the bin voltages. For example, Bin B could have LEDs with a Vf from 2.65V to 2.85V. Instead of the deviation of ±1.8% (from 2.70V to 2.80V, referred to their center Vf),  this process results in a deviation of ±2.65% – approximately double the tolerance we have seen in our previous model.

Typically, the errors in such measurement are driven mostly by offsets than by noise (we’re talking about measurements in the low tens of millivolts here, you don’t need metrology-grade equipment to do that). Calibrating equipment all the time costs lots of money, hence defining even slighly higher tolerances saves a lot of money. If you were to guarantee a higher tolerance, you would need to account for a stricter temperature control during the binning process due to the temperature coefficient of Vf, with single-degree, and you would have to calibrate and adjust your equipment much more often.

If we were to assume that only a single testing device tested our equipment, the unknown offset would slightly move the truncated voltage distribution, but not more than the 50mV specified devation (depends on the manufacturer of course and is not always found in datasheets) in either direction. In other words, Bin B with a nominal 2.70V to 2.80V could be 2.66V to 2.76V with no LED outside that range. That range, however, is unknown

Further considerations

However we still need to consider that most LEDs (except some very expensive ones of course) are ultra-high-quantity, low-cost products produced in vast quantities. You can only speed up0 a production machine up to a certain point economically – beyond that point, it’s much better to just place multiple machines performing the same manufacturing step next to one another. While this certainly can’t be guaranteed, it can not be assumed that all LEDs from a single part number are produced by the same machine. Hence, we can’t assume that the offset is constant within a single bin. One binning machine might have an offset of +0.03V while the next one might have an offset of -0.04V.

However, there is some basis for an argument that whatever is sold on a single reel could be from a single binning machine – in other words, buy a continous reel or tape of LEDs from someone you trust to give you a virgin reel and not a mix of separate reels connected together, and you have a higher likelihood of the actual voltages being more like ±1.8% than ±2.65% since it has been tested. But still, if it is not guaranteed explicitly, you have no guarantee that LED bins from different machines are not thrown together into a single, physical, bin. Since most quality manufacturers try to implement some form of traceability system to track back errors appearing in the field to whatever process or machine caused them, it is more likely than not that continous tapes have slighly less Vf deviation that just LEDs of the same part number.

Even within a single reel, you could see adjacent LEDs have slightly less deviation than if you compare LEDs from one end of the reel to the other end – say, because the temperature during the binning process varied very very slightly, affecting the measured Vf by means of the the Vf temperature coefficient.

One aspect we did not consider so far is outliers. Many quality manufacturing processes are designed with five-sigma tolerance in mind (i.e. at most one in every 3.5 million LEDs will not adhere to specifications). This means that even though the manufacturing & binning process effectively does 100% testing of the LEDs, at least every once in a while you will produce an outlier – be it every 1 million, every 10 million or every trillion parts. In practice, reaching five sigma is hard and causes for rare errors are literally everywhere. So even if the probability of a Bin having an LED with a voltage outside the specified Vf range is extremely low, say, 0.00002%, it is not zero. Note that even an LED only a millivolt outside the specification would technically considered an outlier, and most outliers are only just outliers – even within outliers, only a small fraction is way outside the tolerance. Except for one group: Actually defective LEDs.

Consider this scenario: What if the wirebond has an intermittent contact for some reason, and during testing it works. This will produce an outlier that may even work for a couple of years after being assembled on a PCB. But when the LED is temperature-cycled over and over again by normal operation, it might fail at some point – or it might not. The point to take home here is that not only is life influenced by statistics, life itself is statistics. So you should consider guarantees as someone has reason to believe some event is extremely unlikely, but you will never reach 0% or 100%.

Posted by Uli Köhler in Electronics

What is the forward voltage deviation of typical power LEDs?

A typical starting point is to assume a Vf deviation of ±10% to ±15%, but you typically buy LEDs binned by their measured forward voltage.

As an example, we’ll use the OSRAM 3W OSCONIQ® P 3737 as an example: The datasheet lists the forward voltage range as 2.60V to 3.10V with a nominal of 2.75V – in other words, -5.5%, +12.7%. Since you buy LEDs binned by forward voltage, the actual deviation is much lower within a single part number. For example, the voltage range in the L2 bin is ±1.7%

Note that this does not take into account the forward voltage change due to the Vf temperature coefficient (the temperature coefficient will be very slightly different for each individual LED, but this doesn’t matter too much in practice – so you can mostly assume that the forward voltage temperature coefficient is constant).

Posted by Uli Köhler in Electronics

How to install LXI-Tools with lxi-gui on Ubuntu

The easiest way of getting lxi-tools and lxi-gui running under Ubuntu is to install it using snap:

sudo snap install lxi-tools

After that, just run it using

lxi-gui

or the command line tool

lxi

 

Posted by Uli Köhler in Electronics

How to fix Ubuntu ./configure error: lua not found

Problem:

When you run ./configure while trying to build some software, you see an error message like

checking for lua >= 5.1... no
configure: error: lua not found

Solution:

Install a recent Lua version including the development files. First try:

sudo apt -y install liblua5.4-dev

if that package can’t be found, try

sudo apt -y install liblua5.3-dev

and go on trying liblua5.2-dev, liblua5.1-dev or liblua5.0-dev. Which version will work depends on your Ubuntu version.

Posted by Uli Köhler in Linux

Diagnosing I2C issues using an oscilloscope: Slow rising edge, fast falling edge

If your I2C clock and/or data signal look like this:

in other words, if the rising edge is very slow and not sharp compared to the you are facing an issue with a pull-up resistor which is too large. A good point to start is to use a pull-up resistor 1/4 the value of the pull-up currently installed on your board. If you don’t have any pull-up on your board, start with a 2.2 kOhm pull-up resistor.

As a hotfix, you can operate I2C at slower speed like 100 kHz or even slower (like 10 kHz). This will temporarily fix the issue and depending on your application there might not be any need to go faster than that.

Posted by Uli Köhler in Compliance, Electronics, Embedded

How should the SPI SCLK look on the oscilloscope?

SPI is typically used at 1-20MHz clock frequency. Start with setting the oscilloscope to 2 microseconds per division and 2V per division. Hence, set your scope. Set the trigger to edge mode to trigger at half the supply voltage (e.g. 1.65V for 3.3V supply voltage).The following example is SPI running at 1 MHz on a supply of 3.3V:

Always start by measuring SCLK, to verify both a valid signal clock and your measurement setup. The signal should always look like this:

The first aspect to verify here is that the SCLK should be mostly symmetric (50% duty cycle) and running continously throughout each SPI data transfer. The frequency should typically not change during a single SPI transfer.

SPI tolerates a significant amount of overshoot. In case you have signal integrity issues, you can typically just reduce the clock speed. In order to have a closer look on the signal integrity aspects, zoom in so you see just one clock cycle.

 

The amount of overshoot you see in our example is totally fine. What you should look for here is that both the rising and falling edge should be reasonably sharp and the 0 and 1 bits should be clearly visible.

Regarding overshoot/undershoot, a good rule of thumb is that during the 1 bit, the voltage should never be less than 0.8 times the steady-state voltage during the one bit (see below for red and blue markers):

Similarly, during the 0 bit, the voltage should never be less than 0.2 times the steady-state voltage during the one bit

Note that not only does your PCB affect the signal integrity – your measurement setup (oscilloscope & probe) affects them to some extent as well.

Posted by Uli Köhler in Compliance, Electronics, Embedded

How to add FreeRTOS task (“thread”) to any PlatformIO project

Most PlatformIO default configurations already have FreeRTOS enabled – they just don’t use it.

In order to start a new FreeRTOS “thread” (called task in FreeRTOS-speak), first add these includes:

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

Now add the task function and handle:

TaskHandle_t myTaskHandle;
void MyTask( void * parameter )
{
    for(;;)
    {
       // TODO Task code goes here
    }
    // if you ever exit the loop, this is here to clean up the resources
    vTaskDelete( NULL );
}

then start the task using this code once, for example in your main function:

// Start MyTask thread
xTaskCreate(
    MyTask, // Task function
    "MyTask", // Name
    10000, // Stack size
    NULL, // Parameter
    1, // Priority
    &myTaskHandle);

 

Posted by Uli Köhler in C/C++, Electronics, Embedded, FreeRTOS, PlatformIO

How to add library dependency from local filesystem in PlatformIO

If you want to add a library dependency to lib_deps where the library is from the local file system, just add the full path to lib_deps

lib_deps =
    /home/user/MyCustomLib

This is really handy when developing libs since it allows you to just save changes in the source code and rebuild your main project without any need to publish and update the package first.

Note that you need to Clean and then Build or Upload in order to update the files from the local directory. PlatformIO will cache them unless you Clean and then rebuild!

Posted by Uli Köhler in PlatformIO

What is the value of portTICK_PERIOD_MS and configTICK_RATE_HZ on the ESP32 using PlatformIO?

When you use PlatformIO with the Arduino framework in its default configuration on the ESP32, configTICK_RATE_HZ is set to 1000. In other words, FreeRTOS has a default tick frequency of 1kHz. This is defined in sdkconfig.h:

#define CONFIG_FREERTOS_HZ 1000

Hence portTICK_PERIOD_MS is 1. In my opinion, a tick rate of 1kHz is a sane configuration for most usecases.

I found the value by using the following code on an ESP32:

Serial.println("Timing: ");
Serial.println(portTICK_PERIOD_MS);
Serial.println(configTICK_RATE_HZ);

 

Posted by Uli Köhler in Arduino, Electronics, Embedded, ESP8266/ESP32, FreeRTOS, PlatformIO

How to read 8-bit I2C register using Arduino Wire library: A minimal example

The following code demonstrates how to read a register that is 1 byte (8 bits) long over I2C. It will work with almost all I2C devices like EEPROMs, ADCs and others, provided you have the correct. Note that some devices like the LAN9303 have a slightly different addressing scheme or other peculiarities. In my opinion, it’s most efficient to just try out the standard way of reading a register and start from there.

Note that this code does not implement error handling for the sake of simplicity. Additionally, we wait for data using delay() instead of Wire.available(). This is a minimal example so it creates minimal confusion for the reader. We will provide a full example with error handling in a followup post.

const uint8_t SLAVE_I2C_ADDRESS = 0b1010;
const uint16_t SLAVE_I2C_REGISTER_ADDRESS = 0x50;

Wire.beginTransmission(SLAVE_I2C_ADDRESS);
Wire.write(SLAVE_I2C_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SLAVE_I2C_ADDRESS, 1); // This register is 8 bits = 1 byte long
delay(2); // Wait for data to be available
// Read directly into an uint8_t
uint8_t buf = (uint8_t)Wire.read();
// Print register value
Serial.printf("Register value: %02x\r\n", buf);

Also see:

Posted by Uli Köhler in Arduino, C/C++, Electronics, Embedded, PlatformIO

How to read 16-bit I2C register using Arduino Wire library: A minimal example

The following code demonstrates how to read a register that is 2 bytes (16 bits) long over I2C. It will work with almost all I2C devices like EEPROMs, ADCs and others, provided you have the correct. Note that some devices like the LAN9303 have a slightly different addressing scheme or other peculiarities. In my opinion, it’s most efficient to just try out the standard way of reading a register and start from there.

Note that this code does not implement error handling for the sake of simplicity. Additionally, we wait for data using delay() instead of Wire.available(). This is a minimal example so it creates minimal confusion for the reader. We will provide a full example with error handling in a followup post.

Option 1: Reading the register into an uint16_t (recommended)

const uint8_t SLAVE_I2C_ADDRESS = 0b1010;
const uint16_t SLAVE_I2C_REGISTER_ADDRESS = 0x50;

Wire.beginTransmission(SLAVE_I2C_ADDRESS);
Wire.write(SLAVE_I2C_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SLAVE_I2C_ADDRESS, 2); // This register is 16 bits = 2 bytes long
delay(5); // Wait for data to be available
// Read directly into an uint32_t
uint16_t buf;
Wire.readBytes((uint8_t*)&buf, 2);
// Print register value
Serial.printf("Register value: %04x\r\n", __builtin_bswap16(buf));

For an explanation on why we need __builtin_bswap16(), see How to print 16-bit uint16_t as four hex digits in Arduino

Option 2: Reading the register into an uint8_t array

const uint8_t SLAVE_I2C_ADDRESS = 0b1010;
const uint16_t SLAVE_I2C_REGISTER_ADDRESS = 0x50;

Wire.beginTransmission(SLAVE_I2C_ADDRESS);
Wire.write(SLAVE_I2C_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SLAVE_I2C_ADDRESS, 2); // This register is 16 bits = 2 bytes long
delay(5); // Wait for data to be available
// Read into a 2-byte buffer
uint8_t buf[2];
Wire.readBytes(buf, 2);
// Print register value
Serial.printf("Register value: %02x%02x\r\n", buf[0], buf[1]);

Also see:

Posted by Uli Köhler in Arduino, C/C++, Electronics, Embedded, PlatformIO

How to read 32-bit I2C register using Arduino Wire library: A minimal example

The following code demonstrates how to read a register that is 4 bytes (32 bits) long over I2C. It will work with almost all I2C devices like EEPROMs, ADCs and others, provided you have the correct. Note that some devices like the LAN9303 have a slightly different addressing scheme or other peculiarities. In my opinion, it’s most efficient to just try out the standard way of reading a register and start from there.

Note that this code does not implement error handling for the sake of simplicity. Additionally, we wait for data using delay() instead of Wire.available(). This is a minimal example so it creates minimal confusion for the reader. We will provide a full example with error handling in a followup post.

Option 1: Reading the register into an uint32_t (recommended)

const uint8_t SLAVE_I2C_ADDRESS = 0b1010;
const uint16_t SLAVE_I2C_REGISTER_ADDRESS = 0x50;

Wire.beginTransmission(SLAVE_I2C_ADDRESS);
Wire.write(SLAVE_I2C_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SLAVE_I2C_ADDRESS, 4); // This register is 32 bits = 4 bytes long
delay(5); // Wait for data to be available
// Read directly into an uint32_t
uint32_t buf;
size_t actually_read = Wire.readBytes((uint8_t*)&buf, 4);
// Print register value
Serial.printf("Register value: %08lx\r\n", __builtin_bswap32(buf));

For an explanation on why we need __builtin_bswap32(), see How to print 32-bit uint32_t as eight hex digits in Arduino

Option 2: Reading the register into an uint8_t array

const uint8_t SLAVE_I2C_ADDRESS = 0b1010;
const uint16_t SLAVE_I2C_REGISTER_ADDRESS = 0x50;

Wire.beginTransmission(SLAVE_I2C_ADDRESS);
Wire.write(SLAVE_I2C_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SLAVE_I2C_ADDRESS, 4); // This register is 32 bits = 4 bytes long
delay(5); // Wait for data to be available
// Read into a 4-byte buffer
uint8_t buf[4];
size_t actually_read = Wire.readBytes(buf, 4);
// Print register value
Serial.printf("Register value: %02x%02x%02x%02x\r\n", buf[0], buf[1], buf[2], buf[3]);

Also see:

Posted by Uli Köhler in Arduino, C/C++, Electronics, Embedded, PlatformIO

Which strap resistor value to use for LAN9303?

The LAN9303 datasheet currently does explictly specify a strapping resistor value. However, the evalboard schematic effectively uses a 10kΩ resistor when strapping to GND or a 20kΩ resistor when strapping to VDD. I have experimentally verified that 10kOhm strapping resistors work when strapping to GND. On my boards, I do not explicitly strap to VDD because most strappable pins have internal pull-ups and I don’t need to use the LEDs.

Hence, my recommendation is to strap the LAN9303 using 10kΩ resistors.

Posted by Uli Köhler in Electronics, Embedded, Networking

How to get the LAN9303 into I2C managed mode

In order to set the LAN9303 into I2C managed mode where you can configure the chip using I2C,  strap MNGT1 to 1 and strap MNGT0 to 0. Since the MNGT[1:0] strap pins have an internal pull-up, you only need to strap MNGT0 (pin 26 on the QFN package) to GND using a 10kΩ resistor from this pin to GND.

Posted by Uli Köhler in Electronics, Embedded, Networking

How to access LAN9303 registers over I2C using Arduino / PlatformIO

The LAN9303 has some peculiarities when accessing its registers. This post will not cover indirect register access but only access to the registers which are directly accessible over I2C. Note that the prerequisite for this is to configure the LAN9303 into a mode where management over the I2C slave interface is enabled. See How to get the LAN9303 into I2C managed mode for more info on how to do that.

The main point to take away is that you do not write the register offset from the datasheet (such as 0x50 for the Chip ID and revision register) in the I2C address byte but the address divided by 4 (0x50 >> 2 == 0x14). This is evidenced by figure 8-8 from the datasheet, copyright Microchip, listing the address byte as A[9:2] as opposed to the standard A[7:0]:

 

Example on how to access the register at offset 0x50 (Chip ID and revision register) in Arduino using the Wire library:

const uint8_t LAN9303_I2C_ADDRESS = 0b1010;
const uint16_t LAN9303_CHIPID_REV_Register = 0x50;

Wire.beginTransmission(LAN9303_I2C_ADDRESS);
Wire.write(LAN9303_CHIPID_REV_Register >> 2);
Wire.endTransmission();
Wire.requestFrom(LAN9303_I2C_ADDRESS, 4); // This register is 32 bits = 4 bytes long
delay(5); // Wait for data to be available

// Read directly into an uint8_t
uint32_t buf;
size_t actually_read = Wire.readBytes((uint8_t*)&buf, 4);
// Check if we have received all 4 bytes
if(actually_read != 4) {
    Serial.println("Did not read enough bytes");
}

// Print register value
Serial.printf("LAN9303 Chip ID and revision: %08lx\r\n", __builtin_bswap32(buf));

This will print

LAN9303 Chip ID and revision: 93030001

in other words: Chip ID = 0x9303, revision = 0x0001

 

Posted by Uli Köhler in Arduino, C/C++, Electronics, Embedded, Networking, PlatformIO

How to print 64-bit uint64_t as sixteen hex digits in Arduino

When using Arduino, you often want to print the hex value of a 64-bit value such as a uint64_t, consisting of sixteen hex digits. For example, if you have uint64_t val = 169557370125;, you intend to print 000000277a68250d.

In Arduino you can do that using Serial.printf() with %08lx08lx as format specifier, splitting the uint64_t in two uint32_t instances and printing those one after another:

Serial.printf("%08lx%08lx\r\n",
    ((uint32_t)((val >> 32) & 0xFFFFFFFF)),
    ((uint32_t)(val & 0xFFFFFFFF)));

 

See How to print 32-bit uint32_t as eight hex digits in Arduino for more information on what %x means and why we need to use the 08 in %08x as a printf format specifier.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

How to print 32-bit uint32_t as eight hex digits in Arduino

When using Arduino, you often want to print the hex value of a 32-bit value such as a uint32_t, consisting of eight hex digits. For example, if you have uint32_t val = 9177025;, you intend to print 008C07C1.

In Arduino you can do that using Serial.printf() with %08lx as format specifier. Furthermore, you typically want to invert the byte order of the uint32_t using __builtin_bswap32()since it’s more inuitive to write the hex value MSB-first (big-endian) while most hardware platforms represent the uint32_t as LSB-first (little-endian):

Serial.printf("val = %08lx\r\n", __builtin_bswap32(val));

When using printf, %x means to format the value as hex whereas l tells printf() that the argument is a long (32 bit) as opposed to an int (16 bit). 08 means to pad the value with 0s up to a length of 8 digits. If you would format 9177025 using just %x, it would print 008C07C1  instead of 008C07C1. This is why you need to use %08lx instead.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

How to print 16-bit uint16_t as four hex digits in Arduino

When using Arduino, you often want to print the hex value of a 16-bit value such as a uint16_t, consisting of four hex digits. For example, if you have uint16_t val = 2022;, you intend to print 07E6.

In Arduino you can do that using Serial.printf() with %04x as format specifier.

Furthermore, you typically want to invert the byte order of the uint16_t using __builtin_bswap16()since it’s more inuitive to write the hex value MSB-first (big-endian) while most hardware platforms represent the uint16_t as LSB-first (little-endian):

Serial.printf("val = %04x\r\n", __builtin_bswap16(val));

When using printf, %x means to format the value as hex. 04 means to pad the value with 0s up to a length of 4 digits. If you would format 2022 using just %x, it would print 7E6  instead of 07E5. This is why you need to use %04x instead.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

How to print byte as two hex digits in Arduino

In embedded programming, you often want to print the hex value of a byte, consisting of two hex digits. For example, if you have uint8_t val = 14;, you intend to print 0x0E.

In Arduino you can do that using Serial.printf() with %02x as format specifier:

Serial.printf("val = %02x\r\n", val);

When using printf, %x means to format the value as hex. 02 means to pad the value with 0s up to a length of 2 digits. If you would format 14 using just %x, it would print E  instead of 0E. This is why you need to use %02x.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO