Without much tuning at all the Peltier module holds the MOSFET-under-test block within the ±1 °C deadband for heating to 30 °C:
And it’s pretty close for cooling to 10 °C:
The PWM and Integral traces refer to the right-hand Y axis scale, the rest to the left. The total elapsed time is a bit under 3 minutes, but it’s measured in samples because I’m not going to bother with a formal timebase for this thing.
The Basement Laboratory is around 14 °C right now, so cooling isn’t all that much of a problem.
The code doesn’t really run a PI loop: it switches from P control outside the deadband to I control inside, preloading the integral accumulator to maintain the PWM value at the inbound switchover. That sidesteps the whole integral windup problem, which seems like a Good Idea, but a quick test at 50 °C says the P control may not have enough moxie to reach the deadband and the I control seems overenthusiastic.
More fiddling is definitely in order.
So far, the machinery looks like this:
The aluminum block toward the rear holds the MOSFET and the thermistor atop the Peltier module, all stuck on the black CPU cooler with the fan on the bottom. The various heatsinks are obviously scrounged from the heap and are much too large; some fine tuning is in order now that the temperature’s nailed down.