Scaling brightness using PWM - Page 1
Forum section: electronics / general

author: DrG
author_rank: Super Contributor
timestamp: March 27, 2021, 04:23:02 am
content: A while ago, there was a thread concerning choosing PWM-controlled brightness values for displayshttps://www.eevblog.com/forum/beginners/dividing-0-to-100-exponetially-for-dimming-light/50/. 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://----------------------------------------------------------------------------------------------------// 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 themunsigned int Varray; // 2^18int main(int argc, char* argv[]){int bitsize, y, counter = 0;float dthreshold; // JNDunsigned int MAXDC;float L = 0, lastL = 0, PWMp = 0, Idiff;//std::cout << std::fixed << std::showpoint; // setprecision is probably betterstd::cout << std::setprecision(3);// validate the command line argument numberif (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";exit(1);}// validate bitsize argumentbitsize = std::stoi(argv);if (bitsize < 4 || bitsize > 18) {std::cout << "Usage error: bit size must be > 3 and < 19\n";exit(1);}// validate difference_threshold argumentdthreshold = std::stof(argv);if (dthreshold < 0 || dthreshold >= 1.0) {std::cout << "Usage error: difference_threshold must be > 0 and < 1.0\n";exit(1);}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 thresholdIdiff = (L - lastL) / L;if (abs(Idiff) < dthreshold) {// nothing here unless you want to print a marker}else {Varray = y;lastL = L; // exceeds the difference threshold (JND)counter++;}}// print out the good valuesstd::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 dthresholdfor (y = MAXDC; y >= 0; y--) {if (Varray != 0) {std::cout << "," << std::to_string(Varray);}}std::cout << "};\n\n"; //exit(0);}// finishedHere is some sample output:Code:>weberstevens01 10 .25Working using: bit size=10 and difference threshold=0.25Finished with 38 values...A={0,1,2,3,4,5,7,9,12,15,19,25,32,40,51,64,80,99,120,142,166,192,220,250,282,317,355,396,440,487,538,593,652,716,784,858,937,1023};Code:>weberstevens01 8 .10Working using: bit size=8 and difference threshold=0.1Finished with 60 values...A={0,1,2,3,4,5,6,7,8,9,10,12,14,16,18,20,22,25,28,31,34,37,40,43,46,49,52,56,60,64,68,72,76,80,84,89,94,99,104,109,114,119,125,131,137,143,149,156,163,170,177,185,193,201,209,218,227,236,245,255};Code:>weberstevens01 8 .25Working using: bit size=8 and difference threshold=0.25Finished with 30 values...A={0,1,2,3,4,6,8,11,15,20,25,31,37,44,51,59,67,76,86,96,107,119,132,146,161,177,194,213,233,255};Here is a graph of that last output.That's it

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 27, 2021, 08:36:18 am
content: Nice!Quote from: DrG on March 27, 2021, 04:23:02 amNo 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:identifiercomment near the beginning of the file.

author: DiTBho
author_rank: Super Contributor
timestamp: March 27, 2021, 04:34:53 pm
content: Thanks, this will be super useful for a similar project(it's super secret, I am hacking a couple of ikea lamps)

author: jfiresto
author_rank: Frequent Contributor
timestamp: March 30, 2021, 12:21:23 pm
content: 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.

author: jfiresto
author_rank: Frequent Contributor
timestamp: April 03, 2021, 07:38:23 pm
content: I would like to thankDrGfor 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.