Author Topic: Scaling brightness using PWM  (Read 648 times)

0 Members and 1 Guest are viewing this topic.

Offline DrG

  • Super Contributor
  • ***
  • Posts: 1021
  • Country: us
Scaling brightness using PWM
« on: March 27, 2021, 04:23:02 am »
A while ago, there was a thread concerning choosing PWM-controlled brightness values for displays . There were a number of approaches including, of course, those based on the CIE 1931 “standard”. The basic idea is that you usually do not want to increase or decrease brightness in a linear fashion (at least once you are beyond the very dim levels), but rather, exponentially.

In contrast to starting with an array size and then calculating the values, I had played around with a difference threshold (representing luminosity changes) between values. Array values can then be determined from minimum (0) to maximum (depending on bit size) PWM duty cycle value. The difference threshold is pre-determined rather than the size of the array.

I have been meaning to “finish” that mini-project (at least getting it out of my head) and the result is this post. I’m not saying it is a big deal, I am saying that this is how I finished the project.

The resulting program (listing below) is written in C++ using Visual Studio 2019 and is for the Windows command line. You enter bit size and difference threshold as arguments and it prints out the array values, which can then be cut and pasted into a program. I rarely use C++ and I am sure that I missed some optimization and the like, but it seems to work fine for me.

Pending discovery of some catastrophic blunder, it is finished and out of my head.

(edit: I did fix a >= and replaced a printf)

Code: [Select]
// WeberStevens01.cpp
// DrG March, 2021 / Visual Studio C++ command line program
// Use at your own risk.
// Prints out an array of values based on the command line arguments 'bit size' and 'difference threshold'.
// The array values follow the linear+exponential function associated with CIE 1931 perceived lightness formula
// Rather than specifying the number of values in the array, a value for the difference threshold (in luminosity)
// is specified. Smaller difference threshold values produce larger arrays. The maximum value (defined by bitsize)
// and the minimum value of '0' are always used.
// The values are designed to aid in creating PWM-controlled brightness functions.

#include <iomanip>
#include <iostream>
#include <string>
using std::cout;

// note VS C++ array elements are initialized to '0' - you could explicitedly initialize them
unsigned int Varray[262144]; // 2^18

int main(int argc, char* argv[])
int bitsize, y, counter = 0;
float dthreshold; // JND
unsigned int MAXDC;
float L = 0, lastL = 0, PWMp = 0, Idiff;

//std::cout << std::fixed << std::showpoint; // setprecision is probably better
std::cout << std::setprecision(3);

// validate the command line argument number
if (argc < 3) {
std::cout << "Usage: WeberStevens <bit size> <difference_threshold>\n";
std::cout << "where bit size > 3 and < 19\n";
std::cout << "and difference_threshold > 0 and < 1.0\n\n";
// validate bitsize argument
bitsize = std::stoi(argv[1]);
if (bitsize < 4 || bitsize > 18) {
std::cout << "Usage error: bit size must be > 3 and < 19\n";
// validate difference_threshold argument
dthreshold = std::stof(argv[2]);
if (dthreshold < 0 || dthreshold >= 1.0) {
std::cout << "Usage error: difference_threshold must be > 0 and < 1.0\n";
std::cout << "Working using: bit size=" << bitsize;
std::cout << " and difference threshold=" << dthreshold << "\n";
MAXDC = (unsigned int)(pow(2.0, bitsize) - 1.0f);

for (y = MAXDC; y > 0; y--) {
PWMp = (((float)y / (float)MAXDC) * 100.0f);
if (PWMp > 8.0f) {
L = (float)pow(((PWMp + 16.0f) / 116.0f), 3);
else {
L = (PWMp / 903.3f);
// check if the difference exceeds threshold
Idiff = (L - lastL) / L;
if (abs(Idiff) < dthreshold) {
// nothing here unless you want to print a marker
else {
Varray[counter] = y;
lastL = L; // exceeds the difference threshold (JND)
// print out the good values
std::cout << "Finished with " << (counter + 1) << " values.\n\n";
std::cout << "A[" << counter + 1 << "]={";
std::cout << "0"; // 0 will always be valid as the last value since last-1 is non-zero which will always exceed dthreshold
for (y = MAXDC; y >= 0; y--) { 
if (Varray[y] != 0) {
std::cout << "," << std::to_string(Varray[y]);
std::cout << "};\n\n"; //
// finished

Here is some sample output:
Code: [Select]
>weberstevens01 10 .25
Working using: bit size=10 and difference threshold=0.25

 Finished with 38 values...


Code: [Select]
>weberstevens01 8 .10
Working using: bit size=8 and difference threshold=0.1

 Finished with 60 values...


Code: [Select]
>weberstevens01 8 .25
Working using: bit size=8 and difference threshold=0.25

 Finished with 30 values...


Here is a graph of that last output.

That's it :)

« Last Edit: March 27, 2021, 02:49:04 pm by DrG »
- Invest in science - it pays big dividends. -
The following users thanked this post: newbrain, I wanted a rude username, DiTBho

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2823
  • Country: fi
    • My home page and email address
Re: Scaling brightness using PWM
« Reply #1 on: March 27, 2021, 08:36:18 am »

I am sure that I missed some optimization and the like, but it seems to work fine for me.
No reason for optimizing it, it's fast enough as is.  Actually, you could (should?) use doubles (instead of floats), as that would increase accuracy without any measurable impact on the run time.

I wrote a variant in C (compilable as a C++11 program without any errors or warnings), that uses a binary search in the difference threshold, to find the difference threshold that yields the desired number of states/steps.  It also takes the minimum and maximum values (corresponding to 0% and 100% luminosity) as parameters.  (The idea being, you can use non-power of two PWM period lengths on many microcontrollers this way, and get the coefficients for the desired number of states/steps.)  Let me know if you want me to post that source too in this thread; it's a direct derivative of yours, but you didn't specify a license...  I'd recommend adding a // SPDX-License-Identifier: identifier comment near the beginning of the file.
The following users thanked this post: newbrain

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 890
  • Country: gb
Re: Scaling brightness using PWM
« Reply #2 on: March 27, 2021, 04:34:53 pm »
Thanks, this will be super useful for a similar project
(it's super secret, I am hacking a couple of ikea lamps  :D )

Offline jfiresto

  • Frequent Contributor
  • **
  • Posts: 572
  • Country: de
Re: Scaling brightness using PWM
« Reply #3 on: March 30, 2021, 12:21:23 pm »
Funny you should mention that. Last weekend I was sorting out how to mount a couple IKEA Jansjö lights to a microscope boom stand, and how to exponentially dim some automotive LED lighting. The former now awaits a drill bit and a knurled screw; the latter, another spin of a circuit board.
The following users thanked this post: DiTBho

Offline jfiresto

  • Frequent Contributor
  • **
  • Posts: 572
  • Country: de
Re: Scaling brightness using PWM
« Reply #4 on: April 03, 2021, 07:38:23 pm »
I would like to thank DrG for this thread. It prompted me to revisit and push forward a project with some slow (over 4 s) PWM LED dimming. I think I have figured out how to do that on a meager and a little "quirky" AVR ATtiny13A. I have used enough stupid tricks, however, that I want to make sure that things are going to work before I show anything. I do not want to suggest something that is too clever by half.

In the meantime, I have attached what the 11–12 bit resolution, Weber law dimming curve should look like.
« Last Edit: April 23, 2021, 01:23:59 pm by jfiresto »

Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo