Built-in project · ESP32-C3-DevKitM-1

ESP32-C3 servo

Sweeps an SG90 end to end with a 50 Hz LEDC channel on GPIO 4: the pulse width is the angle, and the hardware keeps the frame while the core sleeps. The whole build, an ESP32-C3 and 1 more part, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Runs in your browser. Free, and no account needed.

ESP32-C3 servo · ESP32-C3-DevKitM-1live0.000 s 0.00x
Click to open it in the editor
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32-C3-DevKitM-1
// Servo: an SG90 swept from end to end by the LEDC hardware.
//
// A servo does not want PWM in the dimming sense. It wants a pulse *width*:
// about 1 ms for one end of the travel, 2 ms for the other, repeated fifty
// times a second, and between pulses it holds whatever it was last told.
//
// That is still a PWM waveform, so LEDC can make it, and making it in hardware
// is better than making it by hand: the pulse is exact, the core is asleep
// between frames, and nothing an interrupt does can stretch it.
//
//   ledcAttach(pin, 50, 14)   fifty frames a second, 16384 counts each
//   ledcWrite(pin, duty)      duty counts of high out of 16384
//
// A frame is 20000 us, so one count is 20000 / 16384 = 1.22 us and a pulse of
// `us` microseconds is `us * 16384 / 20000` counts. The multiplication is done
// in 32 bits with the division last, so 2000 us is 1638 counts and not 1637.
//
// Wire the servo's brown wire to GND, red to 5V and orange to GPIO 4.

const uint8_t SERVO_PIN = 4;
const uint32_t FRAME_HZ = 50;
const uint8_t FRAME_BITS = 14;
const uint32_t FRAME_US = 1000000UL / FRAME_HZ;   // 20000
const uint32_t FRAME_COUNTS = 1UL << FRAME_BITS;  // 16384

// An SG90's window, and how far it turns across it.
const uint32_t MIN_US = 1000;
const uint32_t MAX_US = 2000;
const int SWEEP_DEG = 180;

uint32_t countsForMicros(uint32_t us) {
  return (us * FRAME_COUNTS) / FRAME_US;
}

void writeAngle(int degrees) {
  if (degrees < 0) {
    degrees = 0;
  }
  if (degrees > SWEEP_DEG) {
    degrees = SWEEP_DEG;
  }
  uint32_t us = MIN_US + ((MAX_US - MIN_US) * (uint32_t)degrees) / (uint32_t)SWEEP_DEG;
  ledcWrite(SERVO_PIN, countsForMicros(us));
}

void setup() {
  Serial.begin(115200);
  if (!ledcAttach(SERVO_PIN, FRAME_HZ, FRAME_BITS)) {
    Serial.println("no LEDC channel free for the servo");
    return;
  }
  writeAngle(90);
  delay(500);
}

void loop() {
  for (int angle = 0; angle <= SWEEP_DEG; angle += 5) {
    writeAngle(angle);
    delay(40);
  }
  Serial.println("at 180");
  for (int angle = SWEEP_DEG; angle >= 0; angle -= 5) {
    writeAngle(angle);
    delay(40);
  }
  Serial.println("back at 0");
}

Parts list

3 parts, plus the jumper wires. Every one is in the editor's parts bin.

How it is wired

3 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • ESP32-C3-DevKitM-1 pin 4; Hobby servo pin PWM
  • ESP32-C3-DevKitM-1 pin 5V; Hobby servo pin VCC
  • Ground: ESP32-C3-DevKitM-1 pin GND; Hobby servo pin GND

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.