2

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:

Hardware PWM signal

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?

  • 1
    That looks like my code. It was intended to see if the Pi could generate extremely short pulses (in the nanosecond region). It was not intended to generate a ramping PWM signal. Could you clarify the characteristics of the signals you want to generate? E.g. pulse length, delay, ramp etc. It may then be possible to say if the Pi can generate such a signal. – joan Feb 18 at 18:21
  • @joan: Yes, it is your code. All I want is a square wave with fast rise time, about 2 ns, as mentioned in the other forum regarding this code. The 2014 hardware that this code ran was capable of it so I am assuming that RPi3 is also capable. – alokoko Feb 18 at 18:35
  • Have a look at abyz.me.uk/rpi/pigpio/examples.html#Misc_minimal_gpio which shows how to change the peripheral base address (which is different for the later Pi models). – joan Feb 18 at 18:48
  • @joan: That was it, thank you. The base address was 0x3f000000 instead of 0x20000000. If you write your comment as an answer, I will accept it. – alokoko Feb 18 at 21:09
1

The base memory address of the hardware peripherals changed between the earlier and later Pi models (to accommodate the extra memory).

The early models (ARMv6) use 0x20000000.

The later models (ARMv7/ARMv8) use 0x3f000000.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.