Servo example for MRMS ESP32: Arduino, IMU, eFuse, BT, WiFi, CAN Bus (mrm-esp32)


If not already done so already, install Arduino software and make basic connections, as described in page for MRMS ESP32: Arduino, IMU, eFuse, BT, WiFi, CAN Bus (mrm-esp32).


Using a servo we will demonstrate how to produce PWM signal (Pulse Width Modulation). Almost any microcontroller has many ADCs (analog to digital convertors) but just a very few, if any, DAC (digital to analog), as there are technical challenges implementing them. Because of dearth of real analog outputs, PWM outputs are widely used, some kind of a fake analog. Not the real thing, but they serve the purpose well. A PWM signal is only zero-voltage or full voltage, but the part of total time of each state varies. If the signal is 90% of the time 0 V and 10% of the time 3.3 V, the average value is comparable to analog 0.33 V.

Servo motors use PWM signal to control their shafts' positions. A typical servo expects PWM signal consisting of 1 - 2 ms wide pulses (high voltage) each 20 ms, with 1.5 ms needed to hold its central position. PWM control is also a typical for electromotor driving.


Starting from the basic hardware connection, as depicted in home page for MRMS ESP32: Arduino, IMU, eFuse, BT, WiFi, CAN Bus (mrm-esp32), we will connect a servo, Servo, 0.0165 Nm, 9g, analog (mrm-ps-1109hb). The image here shows just this board and the servo, not the other parts of the basic connection (power supply and battery), which are also necessary.

This is a small servo with a radio control toys connector JR type. This connector is not well suited for a generic microcontroller board so we will either have to disassemble it or connect it to MRMS Distribution Pins 7x Passive (mrm-distrib-d). Here, we will take the disassembly route. Remove the existing triple Dupont housing by bending small plastic locks with a knife or a small screwdriver, at the same time pulling the appropriate wire. Repeat the process for all of the 3 wires. Use a double Dupont housing and a single one or 3 single ones and insert the contacts into them. Connect the Dupont connectors to 5 V pins of the ESP32 board, if You connected the other 5 V connector to power supply. Otherwise connect the wires to 5 V supply directly. If You use a separate supply to produce 5 V DC, connect 0 V (GND) pins of the both 3.3 and 5 V power supply. If You use a MRMS power supply, like MRMS Power Supply 3x B (mrm-ps3xb), the 0 V levels are already connected.


The same applies for PWM as to ADC (analog inputs): it is complicated, if You do not use Arduino, but MCU's native code. The good news, like for ADC, is that Arduino offers a beautifully simple function for a rather complicated task: analogWrite(). Now, the bad news: ESP32 does not implement it. That means 2 thing:

  • The code You will make for ESP32 will not be portable; it will not run in the original Arduino board. The same applies vice-versa: an original Arduino progam might not run in ESP32 board.
  • You will have to cope with difficulties of a proprietary implementation.
The problem is common for all the Arduino nearly-compatible boards. Even when a board is faster than the original, it can be said that it is not 100% compatible, as an original program might break when it executes faster than expected.

On the brighter side, ESP32 does implement PWM output and it offers some quite useful extensions. The rest of ESP32 Arduino implementation is not bad at all, but analogWrite() is quite annoying. Let's examine the program.

#define GPIO 25
#define FREQUENCY_HZ 50
#define CHANNEL 0 // 0 - 15
#define RESOLUTION_BITS 10 // 1 - 16

void setup() {
  ledcAttachPin(GPIO, CHANNEL);

void loop() {
  for (uint8_t degrees = 0; degrees <= 180; degrees++){
    uint16_t maximumDutyCycle = 1023;
    ledcWrite(CHANNEL, map(degrees, 0, 180, maximumDutyCycle * 0.05, maximumDutyCycle * 0.1));

GPIO used here is 25, but it can be any. Almost all the pins are PWM capable. See limitations section at the end of this page. So, 25 is the pin which we used to connect the servo data wire. FREQUENCY_HZ is 50, so 50 Hz, and that is 20 ms, the period needed for servo specification. CHANNEL is selected as 0. You can use any other in 0 - 15 range, but for each new servo motor select a new channel. RESOLUTION_BITS is 10 and can be 1 - 16. 10 means that the total period time (20 ms in this case) will be divided in 1023 parts (1023 = 210 - 1). When a higher resolution is used, we can control the motor more precisely. However, this is a simple motor, and 1024 will be enough.

First command in setup() function sets PWM's configuration, according to our selected constants. The second one uses ledcAttachPin() function to attach PWM signal to our selected pin, GPIO 25.

There is a for loop in loop(), which swipes servo. ledcWrite(channel, dutyCycle) accepts the mentioned 2 parameters. channel is our CHANNEL and dutyCycle can be selected between 0 and 2resolution - 1, in our case 0 - 1023. We set maximumDutyCycle variable to 1023. When we multiply it by 0.05, we get 5% of the maximum duty. As maximum duty cycle is 20 ms, 5% is 1 ms, exactly the lower limit for servo. In the similar manner we get 10% of 20 ms, 2 ms, the top limit. Arduino map() function maps our degree variable to fit into the 1 - 2 ms range. The picture shows duty cycle value for 2 ms.

if we expect RESOLUTION_BITs to change, 1023 in setting maximumDutyCycle can be changed into 1 << RESOLUTION_BITS.


You can use some other server motor for this example. Take care about duty cycle limit, as the lower limit can be less than 1 ms and upper more than 2 ms. Also be warned that servo motors can drain a lot of current. A few servos can easily top 10 or 20 A. As the required voltage is usually 5 V, somewhat more for "high voltage" servos, a DC-DC regulator will be needed, and it could struggle with such high current values.


Some pins are not available for PWM. Check this list.