As part of my Capstone robot project, I need to control the speed of the drive wheels. This is done using Pulse Width Modulation (PWM). To implement PWM on my robot, I needed to program and control the hardware timers.
There are libraries for PWM, but I decided not to use any library because they use wasteful busy loops and I needed tighter control of the Raspberry Pi's resources. I also wanted to learn and understand how to program direct device access.
The ultimate goal is to control motors, but I started with LEDs since they are easier to visualize while debugging.
The images below show the GPIO Pin header for a Rasberry Pi. You can see that the PWM outputs are on Pin-32 labeled BCM-12 PWM0 and Pin-33 labeled BCM-13 PWM1. I also use the Ground on Pin-6.
The Raspberry Pi uses memory-mapped I/O. This means that device registers can be read and written as if they were locations in memory. The hardware address of the GPIO registers depends on the version the Raspberry Pi board. I am using a RPi4, which has a Broadcom BCM2711 SoC with an ARM Cortex-A72.
The BCM2711 Peripherals Manual documents the starting address of the GPIO registers. For the RPi4, the address is 0xfe000000. But I also want my program to work with a RPi3, where the address is 0x3f00000. So I use this code to find the RPi model number:
static uint32_t
getModelInfo(void)
{
static const char fn[] = "/sys/firmware/devicetree/base/model";
char modelString[64];
readFileToBuffer(fn, modelString, sizeof(modelString));
if (isPrefix("Raspberry Pi 3 ", modelString)) {
return 3;
}
if (isPrefix("Raspberry Pi 4 ", modelString)) {
return 4;
}
fatal("Unsupported RPi model: %s", modelString);
}
I then use the mmap() system call to map the GPIO registers into virtual memory.
static const char devmem[] = "/dev/mem";
static uint32_t *
mapMem(int fd, UInt32 addr)
{
void *p = mmap(0, BLOCK_SIZE,
PROT_READ|PROT_WRITE,
MAP_SHARED,
fd, addr);
if (p == MAP_FAILED) {
fatal("mmap failed for %s, addr=0x%08x: %s",
devmem, addr, syserr());
}
return (UInt32 *) p;
}
static void
mapPeriphrialPtrs(void)
{
int fd = open(devmem, O_RDWR | O_SYNC);
if (fd < 0) {
fatal("Can't open %s: %s", devmem, syserr());
}
UInt32 model = getModelInfo();
UInt32 periBase;
switch (model) {
case 3: periBase = RPI3_PERI_BASE; break;
case 4: periBase = RPI4_PERI_BASE; break;
default: fatal("Unsupported RPi model: %d", model);
}
g.pwmRegPtr = mapMem(fd, periBase + PWM_BASE_OFFSET);
g.clkRegPtr = mapMem(fd, periBase + CLK_BASE_OFFSET);
g.gpioRegPtr = mapMem(fd, periBase + GPIO_BASE_OFFSET);
close(fd);
}
The rest of the code is a lot of low-level register banging.
You can see The Source code here, or download a zipfile by clicking here.
You can compile it using this makefile:
#
# Makefile
#
CC := gcc
CFLAGS := -O3 -Wall -Wextra -Werror
pwmLed: pwmLED.c
${CC} ${CFLAGS} -o $@ $<
clean:
rm -f pwmLed *~
Compile it by running make.
$ make gcc -O3 -Wall -Wextra -Werror -o pwmLed pwmLED.c $
You can use the -help flag to show the usage.
$ sudo su # whoami root # pwmLED -help Usage: pwmLed <red-duty-cycle-%> <red-frequency> <blue-duty-cycle-%> <blue-freq> duty-cycle: 0 to 100 frequency: Hertz, 1 to 18000000 # #
So, let's give it a try, with a two-hertz, 50% duty cycle to the red LED, and a one-hertz, 50% duty cycle to the blue LED:
# pwmLED 50 2 50 1 #
Looks's good!
After the program exits the LED continues to blink because no software is running. The blinking is controlled solely by the PWM hardware registers. The CPU is doing nothing.
Now, let's try a 5Hz, 20% duty cycle on blue, and nothing on red:
# pwmLED 0 1 20 5 #
Perfect!
Download a zipfile by clicking here.
The next step is to use this program to control motors: Motor Speed Control using Raspberry Pi PWM.
Another related project you can see is Controlling a Raspberry Pi with a Cell Phone.