I want to use my Raspberry Pi 3 Model B (rev 1.2) as a source for fast rise PWM signal generation. The idea is to utilize RPi as part of a cheap TDR (time-domain reflectometry) setup. For anyone interested, the issue that I am trying to resolve is here.
I found a sample code at this forum post but it does not work for me: The oscilloscope doesn't show any activity. I tried this code both with a "jessie" installation from two years ago and after upgrading to "stretch" (also upgraded firmware).
Here's the sample code I found in the other forum:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#define CLK_BASE 0x20101000
#define GPIO_BASE 0x20200000
#define PWM_BASE 0x2020C000
#define CLK_LEN 0xA8
#define GPIO_LEN 0xB4
#define PWM_LEN 0x28
#define PWM_CTL 0
#define PWM_STA 1
#define PWM_RNG1 4
#define PWM_FIFO 6
#define PWM_CTL_CLRF1 (1<<6)
#define PWM_CTL_USEF1 (1<<5)
#define PWM_CTL_MODE1 (1<<1)
#define PWM_CTL_PWEN1 (1<<0)
#define PWM_STA_EMPT1 (1<<1)
#define CLK_PASSWD (0x5A<<24)
#define CLK_CTL_MASH(x)((x)<<9)
#define CLK_CTL_BUSY (1 <<7)
#define CLK_CTL_KILL (1 <<5)
#define CLK_CTL_ENAB (1 <<4)
#define CLK_CTL_SRC(x) ((x)<<0)
#define CLK_CTL_SRC_PLLD 6 /* 500.0 MHz */
#define CLK_DIV_DIVI(x) ((x)<<12)
#define CLK_DIV_DIVF(x) ((x)<< 0)
#define CLK_PWMCTL 40
#define CLK_PWMDIV 41
#define MAX_BITS 224
typedef struct
{
unsigned divider;
unsigned bits;
} pwm_clock_cfg_t;
unsigned base_nano[]={4, 8, 10, 20, 40, 80, 100, 200, 250, 500, 1000};
static volatile uint32_t *clkReg = MAP_FAILED;
static volatile uint32_t *gpioReg = MAP_FAILED;
static volatile uint32_t *pwmReg = MAP_FAILED;
static void mynanosleep(unsigned nanos)
{
struct timespec ts, tr;
ts.tv_sec = 0;
ts.tv_nsec = nanos;
while (nanosleep(&ts, &tr))
{
ts = tr;
}
}
int gpioSetMode(unsigned gpio, unsigned mode)
{
int reg, shift;
reg = gpio/10;
shift = (gpio%10) * 3;
gpioReg[reg] = (gpioReg[reg] & ~(7<<shift)) | (mode<<shift);
return 0;
}
int gpioGetMode(unsigned gpio)
{
int reg, shift;
reg = gpio/10;
shift = (gpio%10) * 3;
return (*(gpioReg + reg) >> shift) & 7;
}
static void initPWM(unsigned divider)
{
/* reset PWM clock */
clkReg[CLK_PWMCTL] = CLK_PASSWD | CLK_CTL_KILL;
mynanosleep(10000);
/* set PWM clock source as 500 MHz PLLD */
clkReg[CLK_PWMCTL] = CLK_PASSWD | CLK_CTL_SRC(CLK_CTL_SRC_PLLD);
mynanosleep(10000);
/* set PWM clock divider */
clkReg[CLK_PWMDIV] = CLK_PASSWD | CLK_DIV_DIVI(divider) | CLK_DIV_DIVF(0);
mynanosleep(10000);
/* enable PWM clock */
clkReg[CLK_PWMCTL] =
CLK_PASSWD | CLK_CTL_ENAB | CLK_CTL_SRC(CLK_CTL_SRC_PLLD);
mynanosleep(100000);
/* reset PWM */
pwmReg[PWM_CTL] = 0;
/* clear PWM status bits */
pwmReg[PWM_STA] = -1;
mynanosleep(10000);
}
static void sendPulse(unsigned bits)
{
int i;
uint32_t word;
if (bits == 0) bits = 1;
else if (bits > MAX_BITS) bits = MAX_BITS;
/* clear PWM fifo */
pwmReg[PWM_CTL] = PWM_CTL_CLRF1;
mynanosleep(10000);
while (bits >= 32)
{
pwmReg[PWM_FIFO] = -1;
bits -= 32;
}
if (bits)
{
word = 0;
for (i=0; i<bits; i++) word |= (1<<(31-i));
pwmReg[PWM_FIFO] = word;
}
pwmReg[PWM_FIFO] = 0;
/* enable PWM for serialised data from fifo */
pwmReg[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_MODE1 | PWM_CTL_PWEN1;
}
static uint32_t * mapMem(int fd, unsigned base, unsigned len)
{
return mmap
(
0,
len,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_SHARED|MAP_LOCKED,
fd,
base
);
}
pwm_clock_cfg_t getDivBits(unsigned nano)
{
pwm_clock_cfg_t cfg;
unsigned i, base, bits, err, bestErr, bestBase, bestBits;
bestErr = -1;
for (i=0; i<sizeof(base_nano)/sizeof(unsigned);i++)
{
bits = nano / base_nano[i];
if (bits > MAX_BITS) bits = MAX_BITS;
err = nano - (bits * base_nano[i]);
if (err < bestErr)
{
bestErr = err;
bestBase = base_nano[i];
bestBits = bits;
}
}
cfg.divider = bestBase / 2;
cfg.bits = bestBits;
return cfg;
}
int main(int argc, char *argv[])
{
int fd, i, gpio, mode;
pwm_clock_cfg_t cfg;
int nanos=1000, pulses=100, gap=5000;
fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd<0)
{
printf("need to run as root, e.g. sudo %s\n", argv[0]);
exit(1);
}
gpioReg = mapMem(fd, GPIO_BASE, GPIO_LEN);
pwmReg = mapMem(fd, PWM_BASE, PWM_LEN);
clkReg = mapMem(fd, CLK_BASE, CLK_LEN);
close(fd);
if (argc > 1) nanos = atoi(argv[1]);
if (argc > 2) pulses = atoi(argv[2]);
if (argc > 3) gap = atoi(argv[3]);
if (nanos < 4) nanos = 4;
else if (nanos > 224000) nanos = 224000;
if (pulses < 1) pulses = 1;
if (gap < 0) gap = 0;
cfg = getDivBits(nanos);
printf("%d pulses of %d nanos with gap of %d nanos (div=%d bits=%d)\n",
pulses, cfg.divider * 2 * cfg.bits, gap, cfg.divider, cfg.bits);
mode = gpioGetMode(18); /* save original mode */
gpioSetMode(18, 2); /* set to ALT5, PWM1 */
initPWM(cfg.divider);
for (i=0; i< pulses; i++)
{
sendPulse(cfg.bits);
mynanosleep(nanos + gap);
}
gpioSetMode(18, mode); /* restore original mode */
}
Here's gpio readall
output from the RPi 3:
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 0 | IN | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | IN | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | ALT0 | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | ALT0 | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | OUT | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
One observation I made is that the post on the other forum is from Jan 2014, which leads me to think it was designed to run on Pi models with one CPU only (maybe not "designed" but implicitly relies on). But I do not have the RPi-specific knowledge to make it work on the 4-core models. I am not even sure if this is the issue at hand.
I made the following modification (basically added a printf statement to observe the existing mode):
mode = gpioGetMode(18); /* save original mode */
printf("original mode is %d\n", mode);
gpioSetMode(18, 2); /* set to ALT5, PWM1 */
This code prints out different values each time (I saw 2, 5, 7 so far). I do not know if this has a significance but I wanted to point it out.
I also tested the hardware PWM functionality and saw that it works. However, the rise time is about 13 ns. I would really like to get this code running to get the 2-ns rise times as mentioned at the other forum posts. For completeness, here's what I did to test the hardware PWM functionality:
gpio mode 23 pwm
gpio pwm-ms
gpio pwmr 4
gpio pwmc 2
gpio pwm 23 2
This gives a 2.4 MHz, 50% duty cycle signal as shown below:
One final note: After running the code gpio mode 23 pwm
, the output of gpio readall
changes. Running gpio mode 18 pwm
, however, doesn't make any difference (GPIO18 still shows as 'IN').
To sum it up: What modification should be done to this sample code to make it work on RPi3?