last updated: 2/1/2025
TLDR: Mini project to raise and lower my blinds on a timer. Uses a stepper motor and a ESP32 which fetches the time via wifi and also the open and close times from a Firebase DB.
I like waking up to natural light, and I was bored because the semester hadn't started yet so I decided to see how long it would take me to do this little project. Yes, it is easy to raise them manually.
I used an ESP32 that comes in the same form factor as an arduino (link) and a really old motor driver that uses an L298P (don't use this board for your projects, I just had one in an Arduino shield form factor) The whole thing is powered from a 19V laptop charger that can do 4 amps (overkill). The motor driver sits on top of the ESP32 and so there is no wiring excluding the screw terminals to motor and power supply.
The most interesting part of this project was driving the stepper motor. At first I just drove each phase in order but the motor was really loud. This was because the rotor would accelerate and decelerate for each step.
I understand the general working principle of FOC motor driving - and with two perpendicular phases it is so simple! Just a sin and cos needed to be applied. However the motor when driven with sin waves was not moving linearly. After a few days of pondering it hit me it could be that the relationship between duty cycle and current might not be proportional.
Sure enough, after writing a bit of code to find the required duty for a certain current, and then controlling for current, the motor drove almost silently! The gear noise is now what prevents faster operation.
Here's the relevant code below. Pretty simple! I used a 25kHz PWM which was not audible.
while (true) { // Calculate normalized positions on sine wave [0..1], offset coil B by 90° const double angle = fmod(degs, PI * 2); // NOLINT(*-narrowing-conversions) const double a = cos(angle); const double b = sin(angle) * dir; if (degs > 27000) { break; } // Set coil A ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, limit(a)); ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); digitalWrite(dir_a, a > 0); // Set coil B ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, limit(b)); ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1); digitalWrite(dir_b, b > 0); degs += PI / 100; delayMicroseconds(dir == 1 ? 200 : 100); }