The top trace is the gate drive at 200 mV/div, the bottom trace is the LED current at 50 mA/div. Expanding the timebase gives a closer look at the fuzz:
Yup, that’s what deriving an analog voltage from a PWM output looks like. Verily, you’re seeing a 32 kHz PWM passed through a 1 ms = 160 Hz low-pass RC filter; the PWM frequency is 2 decades + 1 octave above the filter, so the 5 Vpp digital signal should be down 46 dB. Squinting at the ripple, it’s maybe 40 mV = -42 dB, which is certainly close enough, all things considered.
The MOSFET controlling the LED current operates in its linear region (the whole point of this exercise!) and acts as a Class A amplifier. The datasheet says the forward transconductance is 21 S at VDS = 5 V and ID = 8 A, which certainly isn’t what we have here (about 1 V and 150 mA); you’d expect a 40 mV ripple to produce 840 mA of sawtooth. Under these conditions, the transconductance seems to be 2.5 S = 100 mA/40 mV.
Anyhow, because the gate drive comes from an Arduino PWM output, it has 0.4% resolution and the voltage steps by a bit under 20 mV per PWM increment. Here’s what increasing the PWM output by one count looks like:
Expanding the timebase:
The gate drive is 20 mV higher and the current is 50 mA higher, so the transconductance again works out to 2.5 S.
Note bene: The smallest gate voltage increment produces 50 mA more LED current. It works the same way in the other direction, too, putting a lower limit on the allowable LED current: when the ripple becomes larger than the nominal current, what’s the point?
So, not surprisingly, precise LED current control isn’t possible with an Arduino’s PWM output, at least under these conditions. Using 16 bit PWM would increase the resolution (by a factor of 256), but the PWM ripple means the LED current varies by nearly 2/3 of the setpoint: 100 mApp for a 160 mA nominal LED current.
You could apply a more drastic low-pass filter, but remember that the whole point is to blink the LEDs, not gradually turn them on and off. Eyeballometrically, the LED current risetime = 7 ms, which is very roughly what you’d expect from the 1 ms filter time constant: 5 τ = 99.3%. Doubling the filter time constant wouldn’t be a step in the right direction…
To do this right, you need a real DAC with maybe 10 or 12 bit output (and careful attention to analog layout), which would be absurd in a circuit with an Arduino Pro Mini jammed on top.
Given that it’s just blinking LEDs, none of this really matters: the LEDs are shatteringly bright and blink most satisfactorily. It’s a keeper, even with all that ripple…