BLDC Controller phase 6: productisation

In the last post, I’d just got a basic control loop going to control the speed of the motor by varying the throttle. In this post I’ll try to summarise what it took to get from there to something we could use in the robot.

Bench test with 4 motors

Modularisation

Up to now, I’d built the code for controlling the motor in the “main loop” of the program, but I had the goal to control multiple motors from one Pico. To achieve that I refactored the code so that the state variables for each motor were contained in a struct, and I moved the inline code into functions that take a pointer to that struct as first argument (this is a common way of doing “object oriented” programming in C). This gave an API that looked like this:

void motor_init(struct motor_cb *cb, uint pin_a, uint pin_b, uint pin_C, uint pin_pwm_in);
void motor_calibrate(struct motor_cb *cb);
uint16_t motor_get_calibration(struct motor_cb *cb);
...

With that in hand, I could now have four copies of the struct, each configured on different pins. The main loop then just needs to call each motor’s control functions in series to let them each update the state:

...
for (int m_upd_idx = 0; m_upd_idx<4; m_upd_idx++) {
    motor_update(&m[m_upd_idx]);
}
...

Of course, one the code was modularised there was a bunch more tuning to do and I adjusted the control algorithm to use a proper PID control.

Add I2C control

A motor driver isn’t much use if you can’t control it so I used the Pico’s I2C peripheral capability to make it configurable from the main Raspberry Pi. I started with the excellent example from the Pico examples repo and customised it to write 16-bit registers instead of random access.

At time of writing the Pico now has over a dozen registers:

  • An overall control register to enable/disable the motors, trigger calibration, etc.
  • A watchdog timeout register
  • Speed control x4
  • Calibration x4
  • Distance travelled x4
  • Temperature
  • Current
  • Voltage
  • Power

Watchdog

From past experience, I have found that the main Raspberry Pi will crash/reboot/get powered down unexpectedly. It is “really bad” if the motors keep going at full tilt when that happens. To deal with that, I added a watchdog timer; if no commands are received on the I2C bus for a configurable timeout, the Pico shuts down the motors. The main Pi has to “pet the watchdog” a few times a second to stop it from timing out.

DMA

To improve performance, I made use of the RP2040’s DMA controller to shuttle sensor readings from the PIO block to main memory without CPU interaction. I was also able to chain a second DMA to timestamp each sensor reading, which helped a lot with accuracy when runing multiple motors at once. It’s useful to know if the sensor reading is actually from half a millisecond ago. Interrupts were another option but DMA has even lower overhead since it happens without the CPU’s involvement.

INA219

To monitor battery and motor health, I used a second I2C block to connect to an INA219 voltage/current monitor. This is then exposed over I2C.

Temperature

The Pico has a convenient internal temperature probe, I made that available on the I2C bus for monitoring.

Wiring it up

This turned out to be a real challenge. There is a lot to fit into a tiny space:

  • 4 motor driver boards
  • The pico
  • Wiring for the 12 PWM signals
  • 12 motor outputs
  • Power (both motor and pico)
  • I2C
  • 4 sensors, each with 3 wires

I designed a 3D printed module to tame it but it took a lot of careful soldering to squeeze it all in.

A minor disaster

Of course, it didn’t work on the first try; my soldering iron was playing up and I overheated one of the motor driver boards, blowing out a trace. Thankfully I was able to track it down and apply a bodge wire:

Summary

It felt pretty good to hold the finished and working module in my hand after several months of work and it’s working great in the bot (with a bit of tuning still to do to account for the momentum of the bot).

Leave a Reply

Your email address will not be published. Required fields are marked *