GARP Motor Controller: Alpha

The GARP

GARP Motor Controller: Alpha

This article discusses the GARP’s Motor Controller Alpha implementation to include the integration of a COTS Raspberry Pi Pico 2 microcontroller board, CAN PiCowbell from Adafruit, and a custom SMD PCB. The software architecture is described and its primary components’ key functionalities are outlined. The PID controller software and hardware are discussed, including the motor wire connections. The custom PCB built to host voltage converters and supplementary power connection is discussed at the schematic and layout levels. The CAN controller is discussed and its CANopen configuration and Object Dictionary are introduced.

GARP Article Series

The Ground Autonomy Research Platform (GARP) is a home-grown UGV designed and built to support independent learning of robotics and autonomy through a full stack from hardware to behavioral autonomy and HMI. To document the implementation of GARP, I’m capturing the process in a series of articles that I’ll link here as they’re completed:

GARP Article Map

Introduction

The GARP Motor Controller (MC) is implemented as a collection of configured COTS hardware and software running on an RP2350 microcontroller. The MC accepts target rotational velocities from the CAN bus and provides control signals to the 42PG-4260BL brushless DC (BLDC) motor, reporting actual velocities as measured via the motor’s encoder back to the CAN bus. To do this, it uses the Adafruit PiCowbell CAN Bus for Pico, a Raspberry Pi Pico 2, and an E-S Motors 42PG-4260 24 V motor:

The CAN interface is composed of CAN High and Low wires in addition to the cable shielding connected to the PiCowbell’s GND terminal. The PiCowbell uses the Pico 2’s first SPI channel and an interrupt pin. The motor interfaces with the MC via an encoder input wire and direction and speed control output wires. The software running on the microcontroller is split into the CAN Controller and PID Controller, with a minimal entrypoint providing bootstrapping. Note that power is provided to the motor separately, via leads direct to the GARP Power Subsystem backend’s 24 V terminals.

The software is hosted on GitLab.

Main Entrypoint and CMakeLists.txt

The primary entrypoint defined in the garp-main.cc file is responsible providing the bootstrapping necessary to kick off the PID and CAN controller loops. Within the file, multicore queues are declared to support exchanging data between the PID and CAN controllers, the controllers are configured, instantiated, and their loops are started.

The Pico SDK’s multicore queues abstract the RP2350’s multicore FIFO to allow passing 32-bit values between cores in an IRQ-safe fashion. This is used in the GARP MC to trade target velocities coming from the CAN bus and actual velocities of the motor between the CAN and PID controllers executing in separate threads running on separate cores. The queues are configured to store floats in a pair of queue_init() calls which also configure the queue depth. To help prevent the queues from filling, the queues are dimensioned deeper than a single value, and the controller loops are configured to attempt to read more frequently than expected to be necessary. When the controllers read from its incoming queue, it reads all available values and retains only the latest. Furthermore, the PID controller checks its input (target velocity) queue at the start of every loop. During characterization in the Beta development stage, it may be necessary to extract these attempts to a separate timer callback, or to change the way that the PID loop is throttled. The CAN controller similarly checks in each execution loop. The PID controller writes the actual velocity calculated from encoder feedback (and used to calculate error in the PID calculations) to its output queue at the end of every loop, and the CAN controller writes values to the queue from the CANopen Object Dictionary at a regular interval specified in the CAN controller’s configuration. During the Beta or later development stages, this strategy will likely be refactored to decouple configuration across the GARP (such as configured queue check rates here in the MC and control interface writes from the differential drive controller in ROS2 Control).

To configure the different wheels’ MCs, the CMakeLists.txt includes a series of compiler definitions specific to targets for each wheel. The build system is configured to produce a separate ELF (and UF2 to support different methods for flashing binaries) for each wheel. The compiler definitions provided this way include details such as the CAN bus node identifier (CAN_ID) and the direction of wheel rotation that leads to forward platform motion (FORWARD_DIRECTION). There is also an entry in the CMakeLists.txt file to tell the compiler that the PID controller core does not access flash (as the CAN controller core does), so do deconfliction needs to be done.

PID Controller

The GARPPIDController class encapsulates the functionality needed to run a simple PID controller of a 42PG-4260BL brushless DC (BLDC) motor. The PID controller is a simplistic placeholder for future controllers and is likely to be replaced with a more advanced strategy once it becomes the limiting factor in the GARP Mobility Subsystem’s performance. The GARPPIDController is configured at instantiation with a PIDConfig structure, initialized, and then put into a perpetual loop.

The PIDConfig structure holds the configuration of the PID controller including the P, I, and D gain factors and parameters for throttling the loop via pid_period_ms. In practice the PID loop will free-run at roughly 7 kHz, such that less than one encoder count is reported when the motor spins at its maximum velocity. To compensate for the relatively low resolution of the encoder, throttling and filtering mechanisms are implemented.

The 42PG-4260BL includes an encoder issuing 300 counts per output shaft rotation. When combined with its maximum speed of 5 rotations per second (RPS), the maximum rate of encoder counts is 1.5 kHz. Consider the following plot of minimum resolvable speed and number of resolvable speeds as a function of PID loop rate:

As the PID loop rate increases, we can see that as the minimum resolvable speed reaches the maximum speed of the motor, control is reduced to two states. We can also see that as the rate increases the number of commandable speeds is reduced as the resolution with which the controller can extract speed from encoder counts is worsened. When reducing the PID loop rate, ultimately we run into a limit where updates from navigation systems will outpace the ability of the controller to respond to changing commanded values. For example, when free-running the PID controller operates at roughly 7 kHz, beyond the 1.5 kHz binary cutoff. In particular, when spinning at their maximum speed of 5 RPS, a 7 kHz PID loop will see an average of 0.6 encoder counts per loop iteration which without any filtering will cause it to jump between calculated actual velocities of 0 and ~23 RPS, depending on whether that iteration included an encoder tick and the actual duration of that PID loop iteration. To provide an additional control dimension, a moving average filter is introduced in the PID controller and configured via the PIDConfig structure, albeit at the expense of latency. During the Beta development stage the trade space of PID gains, loop rate, and filter parameters will be explored, and whether the PID strategy should be replaced with another such as an EKF will be evaluated.

Custom PCB

The connection of the motor inputs and output to the microcontroller for PID control is accomplished via a custom PCB. The PCB is primarily responsible for exposing a JST PH connector for the motor (control) leads, and hosting the pull-up and voltage divider resistors necessary to connect the 5V motor encoder signal to the RP2350’s 3V3 logic. The custom PCB is also used to host a P-channel MOSFET to supply switchable power to the MC via the GARP’s 5V terminals (vice requiring a USB connection.)

From the schematic above left (from Fritzing) we can see the pull up and voltage divider structures on the left of the Pico component the Power and Motor JST connectors at top and bottom, respectively. The P-channel MOSFET in the upper right provides the switching necessary to avoid back-powering the USB hub when connecting the Pico to 5V power and the USB at the same time and is pulled from the Raspberry Pi RP2040 User Guide. This also provides a redundant power supply option should the (ahem) JST connectors fail due to being too rough with them during development. The resistor chain on the left provides the 10 kΩ pull-up to 5V on the encoder output as specified by the OEM, and also provides the voltage division necessary to feed the GPIO 6 pin at 3V3 logic levels.The PWM speed control and direction control fed to the motor both accept 3V3 logic directly, so no level shifting is necessary.

From the PCB layout above right (from Fritzing) we can see the layout is fairly simple with a single via and only two traces on the back side of the board. Also note that the smaller JST PH connectors were used to fit between the holes used to fix the board to its enclosure. These were the only through-hole components as the resistors and MOSFET used were SMD. In hindsight, the SMDs were difficult to affix cleanly with 0.8 mm solder, and in future revisions of the board, I’ll likely use solder paste and a heat gun. Also the JST connectors were a soft plastic that tended to deform significantly during connect-disconnect cycles. Future versions of the board (e.g. during Beta) will likely use a more rigid connector and/or pigtails to connectors mounted to the enclosure for strain relief. The “WayWayWay” annotation is space reserved for PCBWay to add their board number.

CAN Controller

The GARPCANController is the interface between the Motor Controller (MC) and the CAN bus, and is responsible for starting and managing the canopen-stack node, trading target and actual velocities with the PID controller, and defining the CANopen Object Dictionary (OD). It also uses the CAN, Timer, and NVM drivers to interface the canopen-stack with the RP2350 microcontroller and MCP2515 CAN controller hardware. A summary of the interactions between components is depicted below:

As data is received on the CAN bus by the MCP2515 on the PiCowbell, a configured interrupt within the GARPCANController triggers a callback telling the CANopen Node to process the message and update the OD. In parallel, a regular timer interrupt triggers the cyclic timer callback the “ticks” the software timers managed by the CANopen Node. As one of these timers, the Target Velocity Timer triggers a callback to pull the latest target velocity from the OD and push it to the PID controller via a multicore queue. At the Alpha stage, the MC application retains no memory between resets beyond that captured in the MC source code, and so the Nonvolatile Memory (NVM) driver is unused. In future versions of the MC, additional features (e.g. resilience features) may be implemented that will use the NVM driver to store values between power cycles or through resets. An implementation of the NVM Driver is stubbed in, but is largely untested in the current baseline.

CANopen and canopen-stack

The canopen-stack baseline implements the CAN in Automation (CiA) 301 CANopen standard via C99-compliant code that is targeted for deployment on embedded platforms. The CiA 402 standard leveraged by the GARP’s Mobility Subsystem is supported primarily via the definition of additional 6000h-series objects in the OD. See the Object Dictionary subsection below for more information. To use the canopen-stack, CAN, Timer, and NVM drivers must be implemented to interface the stack’s functionality to the RP2350 hardware. This is outlined in the canopen-stack documentation, but in brief:

  • The CAN driver (drv_can_mcp2515 in the source) provides the functions necessary to start, stop, and reset the CAN controller (MCP2515 in this case), as well as to send and receive messages on the CAN bus
  • The Timer driver (drv_timer_alarm in the source) provides the canopen-stack a mechanism for accessing the RP2350’s hardware timers; In this case, a cyclic mode timer is used.
  • The NVM driver (drv_nvm_flash in the source) provides access to the underlying non-volatile/flash memory of the Pico.

The GARPCANController is configured at instantiation with a CANConfig structure, initialized, and then put into a perpetual loop. The CANConfig structure includes parameters such as the CAN bus bitrate to use (as the MCP2515 does not have an “auto” bitrate mode) and the intervals at which target velocities should pushed from the OD to the PID controller. More details on the parameters are available in the (doxygen) API documentation.

CANopen Object Dictionary

In CANopen, the Object Dictionary (OD) defines the data interface used to communicate with the Motor Controller (MC). This includes storage of configuration values such as the period with which heartbeat messages are posted, and current commands such as the latest target velocity. Entries in the OD, or “objects”, are identified by a two-byte index and one-byte sub-index, commonly expressed in hexidecimal format like “index 0x1017, sub-index 0x00” indicating the heartbeat production period. On the GARP’s MC side (vice the C&DH), the OD is defined in the GARPCANController‘s header file, and serves to set initial configuration values and allocate memory on the Pico 2.

The table below includes some sample CiA 301 and 402 objects used by the MC:

CiA 301/402IndexSub-IndexDescription
CiA 3011017h00hHeartbeat period in ms
CiA 3011F80h00hStartup behavior (e.g. auto-start)
CiA 4026502h00hModes supported by MC (e.g. position control, velocity control)
CiA 4026042h00hTarget velocity
CiA 402604Ch01hScaling factor for target velocity (numerator)
CiA 402604Ch02hScaling factor for target velocity (denominator)

CAN PiCowbell

For the CAN bus configuration on the GARP, the only hardware modification required was cutting the terminating resistor trace (Silkscreened “Term” in the image below) as the star configuration used by GARP does not employ terminators at the peripheral devices:

During the Beta development stage, the CANopen configuration and PID and CAN controller configurations will need to be characterized and bottlenecks identified. Also, the feasibility of using CAN-FD will need to be evaluated, including a hardware upgrade such as the MCP2518FD.

The MC stack when combined measures roughly 35 x 22 x 60 mm using tall female stacking headers. The height can be reduced by using shorter headers as there is roughly another JST connector worth of space above the custom PCB and Pico 2 boards.

Summary

This article discussed the GARP’s Motor Controller Alpha implementation to include the COTS Raspberry Pi Pico 2 microcontroller board, CAN PiCowbell from Adafruit, and the custom PCB. The software architecture was described and its primary components’ key functionalities were outlined. The PID controller software and hardware were discussed, including the motor wire connections. The custom PCB built to host voltage converters and supplementary power connection was discussed at the schematic and layout levels. The CAN controller was discussed and its CANopen configuration and Object Dictionary were presented.

Back To Top