Controlling Servo Motors on Raspberry Pi 4 via Direct Register Access

In this section, we will explore how to control servo motors on the Raspberry Pi 4 using direct register access, bypassing high-level libraries such as pigpio or wiringPi.
This approach allows for precise timing control and a deeper understanding of how the PWM hardware operates at the register level.
We have the following devices:
Raspberry Pi 4 Model B running Raspberry Pi OS (64-bit)
servo motor WK-P0025
First, let's examine the Raspberry Pi pinout — connector J8:

Connect the servo motor according to the following wiring diagram:
• Servo motor GND (black) → J8.34 (GND)
• Servo motor VCC (red) → J8.2 (5V)
• Servo motor signal (white) → J8.32 (GPIO12)
To control the servo motor, we need a PWM signal with a 20 ms period.
Duty cycle 1ms: 0 degree
Duty cycle 1.5ms: 90 degree
Duty cycle 2ms: 180 degree

Next, we proceed to write a simple C program to control the servo motor.
1) We will obtain access to the physical memory of the device
if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
perror("can't open /dev/mem");
return -1;
}
2) We will map the control register regions into the application's virtual memory space.
For this, we need the following:
#define GPIO_BASE 0xFE200000
#define PWM_BASE 0xFE20C000
gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE);
pwm_map = mmap(NULL, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, PWM_BASE);
gpio = (volatile uint32_t *)gpio_map;
pwm = (volatile uint32_t *)pwm_map;
3) Configure GPIO12 to use Alternate Function 0 (PWM)
#define GPFSEL1 (0x04 / 4)
gpio[GPFSEL1] &= ~(7 << 6); // clear bits
gpio[GPFSEL1] |= (4 << 6); // ALT0 = 100 (PWM0)
4) Enable PWM on GPIO12
#define PWM_RNG1 (0x10 / 4)
#define PWM_DAT1 (0x14 / 4)
// pwm settings
pwm[PWM_RNG1] = 1000000; // T=20ms
pwm[PWM_DAT1] = 50000; // 1ms
pwm[PWM_CTL] = 0x81; // PWEN1 | MSEN1
5) We will loop to move the servo horn from 0° to 180°
while (1){
pwm[PWM_DAT1] = 50000; // 1ms
sleep(2);
pwm[PWM_DAT1] = 100000; // 2ms
sleep(2);
}
Note: In newer versions of Raspberry Pi OS, to enable PWM, you need to open /boot/firmware/config.txt and add the following at the end:
dtoverlay=pwm,pin=12,func=4
The complete source code can be obtained here
