The software needs to do a few things:
I'm concerned that handling network traffic might interfere with smooth operation of the stepper motors, so I think I might unsubscribe from time-consuming MQTT topics while a pattern is playing, and then subscribe again once a pattern is finished to get new patterns from persisted topic publications.
I initially picked the Wemos D1 Mini for the microcontroller, because it's cheap and compact. Cheaper knockoffs are common too. I wrote the code assuming I had unlimited processing power available - my code would run as fast as I would need it to run to achieve my goals. This turned out to be incorrect by about two orders of magnitude. I was able to scrimp and save some overhead here and there by quashing float maths, but even after all the optimisation I could muster I was still about one order of magnitude shy of where I wanted to be.
At the time I was running with 16x microstepping, where 16 rising edges on the step pin would result in 1.8 degrees of turning on my steppers. This, plus a 2.2:1 gear reduction, meant that if I wanted to turn at 60 rpm, an arbitrary target, I would need to transition the step pin every 71 microseconds. In reality I was only getting around to transitioning the pin every 740 microseconds on average. I needed more speed. I tested the same code running on an ESP32, the ESP8266's beefier sibling, and got around 500 microsecond transitions. Still not good enough.
Note these numbers are with some minimal console debugging enabled, so perhaps increase performance by 30% and you'll be close to accurate for performance with debugging disabled.
There are a few reasons for wanting more speed. The geometry of the system mean that while drawing circles near the circumference is super fast, close to the centre is much slower. I also wanted to use 32x microstepping in the final version, as higher microstepping means smoother movement means less noise. To move the steppers faster I'd need dedicated hardware.
Many microcontrollers have built in timer hardware that can be used to generate high frequency output waveforms. Memories of TCCR1A in the ATMega8. As far as I could tell the ESP8266 had none available to me. There was a Timer library I could use, but it was pure software and could only manage 1kHz. I needed a 7kHz square wave. The ESP32 has lots of dedicated timing hardware, so I set about testing it. Results were great! I was able to drive a pin too fast for the steppers to handle! Great success! However…
My initial naive approach on the ESP8266 had a hidden benefit: I tracked every transition of the step pin. I could keep a perfectly accurate index register of how far the stepper had turned. Once I offloaded responsibility for twiddling the step pin to hardware, not software, I lost the ability to exactly track what step I was up to. My first pass at the PWM implementation was to convert my step interval to a frequency, set the duty cycle to 50% and Just Go. It was a disaster! My slow loop logic was still managing timing the movements, running at its reduced speed, while the PWM hardware was merrily thrashing the stepper 'round and 'round. I needed more smarts.
My plan is to have two modes of operation: 1) Accurate slow, 2) Less accurate, fast. You'll be able to switch between modes in gcode. The accurate mode will have the waveform generation purely in software, and will track each pulse. The less accurate mode will use the PWM interface and measure the time it takes to move. If I use timer interrupts I should be able to say “move at 270 degrees per second and stop the movement after 1 second” and get reasonably good results. It won't be as good as tracking each step, but it will allow for fast-enough movement even at 32x microstepping.
I could start motion with a PWM setting, then set a hardware interrupt timer for the duration of the move. During movement my main loop could update the index based on the time since the last update and the rate of movement.