After a bit of research, I found that this is referred to as "gamma correction", and even found an application note that discusses using gamma correction with PWM:
http://www.maxim-ic....dex.mvp/id/3667
So, here's an update to PwmSample that uses a pre-computed LUT (lookup table) for the PWM output values. These are the gamma-corrected values (for gamma = 2.5) that produce a brightness ramp that looks linear (more or less) to our eye.
Here's the code:
using System; using System.Threading; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware; using SecretLabs.NETMF.Hardware.Netduino; namespace PwmGamma { public class Program { const int pwmPeriod = 50; // Lookup table for the LED brightness. The table contains the // gamma-corrected values (for gamma = 2.5). This approximates // a linear increase in the perceived brightness. static uint[] gammaTable = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 9, 10, 12, 14, 16, 18, 20, 22, 25, 28, 31, 34, 37, 41, 45, 49, 53, 57, 62, 67, 72, 77, 82, 88, 94, 100, }; static bool buttonState; static int pwmIndex; static int pwmIncrement; static InterruptPort button; static PWM led; public static void Main() { pwmIndex = 0; pwmIncrement = 1; buttonState = false; // NOTE: You will need to connect an LED to Digital Pin 5 on the Netduino board. // Use a 100-ohm (brown-black-brown) resistor between the LED anode (+) and Digital Pin 5. // Connect the LED cathode (-) to one of the GND pins on the Power header. // // You can use any other PWM-enabled pin (5, 6, 9 or 10), but also remember to change // the Pin parameter in the PWM constructor below. led = new PWM(Pins.GPIO_PIN_D5); button = new InterruptPort( Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth); // Bind the interrupt handler to the pin's interrupt event. button.OnInterrupt += new NativeEventHandler(SwitchInterruptHandler); // Create a System.Threading.Timer instance and pass it the timer callback method. Timer pwmTimer = new Timer( new TimerCallback(PwmTimerCallback), null, pwmPeriod, pwmPeriod); Thread.Sleep(Timeout.Infinite); } public static void PwmTimerCallback(Object obj) { // Only change the LED brightness when the button is pushed (true). if (true == buttonState) { // Set the pin's new duty cycle. led.SetDutyCycle(gammaTable[pwmIndex]); pwmIndex += pwmIncrement; Debug.Print(gammaTable[pwmIndex].ToString()); if (((gammaTable.Length - 1) == pwmIndex) || (0 == pwmIndex)) { // The duty cycle has hit the min or max value. // Start ramping in the other direction. pwmIncrement = -pwmIncrement; } } } public static void SwitchInterruptHandler(UInt32 data1, UInt32 data2, DateTime time) { button.DisableInterrupt(); buttonState = (0 == data2); button.EnableInterrupt(); } } }