Simple GPIO access in C


24 posts
by evinrude » Fri Jun 15, 2012 2:18 pm
Hi,
I am looking for some direction on how to directly access registers in my beloved RasberryPi. I have looked at the code at this url http://elinux.org/RPi_Low-level_peripherals. However, I am not looking for the kitchen sink of GPIO's. I just want to look at the Broadcom data sheet and cherry pick a register address and manipulate it through an mmap.

I am trying to use GPIO17 as a reference. Here is what the datasheet has provided me.

Code: Select all
/**
 Broadcom datasheet excerpt

 Peripherals (at physical address 0x20000000 on) are mapped into the kernel virtual address
 space  starting  at  address  0xF2000000.  Thus  a  peripheral  advertised
 here  at  bus  address 0x7Ennnnnn is available in the ARM kenel at virtual address 0xF2nnnnnn.

 Select register GPIO17 are bits (23 -21)
 GPFSEL1 = 0xF2200004
 output bits are '001'
 GPIO17 = 00000000 00100000 00000000 00000000

 GPSET0 = 0xF220001C
 Used to set the pin value if it is configured as an output
 GPIO17 = 00000000 00000010 00000000 00000000

 GPCLR0 = 0xF2200028
 Used to clear the output of a pin
 GPIO17 = 00000000 00000010 00000000 00000000]

 GPLEV0 = 0xF2200034
 Returns the level of the pins 0  - 31
**/


Now I am under the impression that this will be as simple as mapping the address at offset 0xF2nnnnnn at a size of 4 bytes from there, but this is proving to be a challenge.

Can anyone provide some quick sample code in C that modifies one of the registers above in using mmap. Say bitwise &'ing GPFSEL1 to set GPIO17 as an output.

Thanks!
User avatar
Posts: 8
Joined: Fri Jun 15, 2012 2:02 pm
Location: Texas
by gordon@drogon.net » Fri Jun 15, 2012 8:57 pm
What I'd suggest you do is get hold of my wiringPi librarys and look at the sources. It will have all you need - as well as an easy way to access the GPIO pins from a C program.

Start here:

https://projects.drogon.net/raspberry-pi/wiringpi/

Or fast forward to the code here:

https://projects.drogon.net/raspberry-pi/wiringpi/download-and-install/

and welcome to the dark side of low-level shenanigans (and just remember you need to do all this as root!)

-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1537
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by evinrude » Fri Jun 15, 2012 9:58 pm
Thanks for your response Gordon.

I wanted to know, where did you find these values at in the datasheet?
Code: Select all
#define BCM2708_PERI_BASE                 0x20000000
#define GPIO_PADS                         (BCM2708_PERI_BASE + 0x100000)
#define CLOCK_BASE                        (BCM2708_PERI_BASE + 0x101000)
#define GPIO_BASE                         (BCM2708_PERI_BASE + 0x200000)
#define GPIO_PWM                          (BCM2708_PERI_BASE + 0x20C000)


I was able to find the BCM2708_PERI_BASE, but this is not the virtual address you are defining. I was always under the impression that I needed to access the kernel virtual address space which starts at 0xF2? I am probably missing something since your code works perfectly. However, I do not know where you are getting the other values from.

The Broadcom datasheet says:
"Peripherals (at physical address 0x20000000 on) are mapped into the kernel virtual address
space starting at address 0xF2000000. Thus a peripheral advertised here at bus address
0x7Ennnnnn is available in the ARM kenel at virtual address 0xF2nnnnnn."
User avatar
Posts: 8
Joined: Fri Jun 15, 2012 2:02 pm
Location: Texas
by evinrude » Sat Jun 16, 2012 4:32 am
Okay, I answered my own question :D and possibly can shed some light as to why Gert used the following values in his code.

Code: Select all
#define BCM2708_PERI_BASE       0x20000000
#define GPIO_PADS               (BCM2708_PERI_BASE + 0x100000)
#define CLOCK_BASE              (BCM2708_PERI_BASE + 0x101000)
#define GPIO_BASE               (BCM2708_PERI_BASE + 0x200000)
#define GPIO_PWM                (BCM2708_PERI_BASE + 0x20C000)


The Broadcom data sheet lists that peripherals are at physical address range 0x20000000 to 0x20FFFFFF. So BCM2708_PERI_BASE sets the beginning of that range. The rest of the data sheet assumes that a virtual memory map will be used to access these peripherals, but this is not the case for us and we need to replace/ignore the beginning of the address space (i.e. 0xnn00 0000 0000).

This is because we are accessing the physical memory through /dev/mem. Access to virtual memory can be achieved through /dev/kmem, but it is strongly advised not to enable this feature in the kernel for obvious security reasons. Thankfully, the Debian linux I am using on my RPi has it disabled. This also means that we are forced to use physical memory.

So Gert basically went through the data sheet and converted the virtual memory register addresses to physical memory addresses.

Example:
Virtual address for GPIO_BASE = 0x7E20 0000 <---- this is actually GPFSEL0
Physical address = 0x2020 0000
Which amounts to (BCM2708_PERI_BASE + 0x200000)
User avatar
Posts: 8
Joined: Fri Jun 15, 2012 2:02 pm
Location: Texas
by gordon@drogon.net » Sat Jun 16, 2012 6:45 am
evinrude wrote:Thanks for your response Gordon.

I wanted to know, where did you find these values at in the datasheet?
Code: Select all
#define BCM2708_PERI_BASE                 0x20000000
#define GPIO_PADS                         (BCM2708_PERI_BASE + 0x100000)
#define CLOCK_BASE                        (BCM2708_PERI_BASE + 0x101000)
#define GPIO_BASE                         (BCM2708_PERI_BASE + 0x200000)
#define GPIO_PWM                          (BCM2708_PERI_BASE + 0x20C000)


I was able to find the BCM2708_PERI_BASE, but this is not the virtual address you are defining. I was always under the impression that I needed to access the kernel virtual address space which starts at 0xF2? I am probably missing something since your code works perfectly. However, I do not know where you are getting the other values from.

The Broadcom datasheet says:
"Peripherals (at physical address 0x20000000 on) are mapped into the kernel virtual address
space starting at address 0xF2000000. Thus a peripheral advertised here at bus address
0x7Ennnnnn is available in the ARM kenel at virtual address 0xF2nnnnnn."


Gert published some code a while back and I used that to learm more about the hardware, then got hold of the ARM Peripherals manual to find out more. Read page 6 of the to find out why the 2 addresses are there. Essentially we're mapping physical memory space and there is a re-mapping inside the ARM (AIUI)

There is one bank of registers missing from the manual to do with setting up the clocks for PWM, so my code just more or less copies Gerts there... Seems to work!

And remember that the offsets I'm using are 32-bit word offsets too!

-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1537
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by evinrude » Sun Jun 17, 2012 1:00 am
Here is my code that toggles gpio17 every second. I tried to make it as minimal as possible while making it clear what is being done. Thanks for the help Gordon!

Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdint.h>

static volatile uint32_t *gpio;

int main(int argc, char **argv)
{
        int fd ;

        //Obtain handle to physical memory
        if ((fd = open ("/dev/mem", O_RDWR | O_SYNC) ) < 0) {
                printf("Unable to open /dev/mem: %s\n", strerror(errno));
                return -1;
        }


        //map a page of memory to gpio at offset 0x20200000 which is where GPIO goodnessstarts
        gpio = (uint32_t *)mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x20200000);

        if ((int32_t)gpio < 0){
                printf("Mmap failed: %s\n", strerror(errno));
                return -1;
        }

        //set gpio17 as an output
        //increment the pointer to 0x20200004
        //set the value through a little bit twiddling where we only modify the bits 21-23 in the register
        *(gpio + 1) = (*(gpio + 1) & ~(7 << 21)) | (1 << 21);

        //toggle gpio17 every second
        while(1){
                //set the pin high
                //increment the pointer to 0x2020001C
                *(gpio + 7) = 1 << 17;

                //sleep
                sleep(1);

                //set the pin to low
                //increment the pointer to 0x20200028
                *(gpio + 10) = 1 << 17;

                sleep(1);
        }
}
User avatar
Posts: 8
Joined: Fri Jun 15, 2012 2:02 pm
Location: Texas
by SN » Sun Jun 17, 2012 1:14 am
interesting... I suggest some MACRO'ising of that bit shifting would probably be good to hide the nastyness a bit ...
Steve N – binatone mk4->intellivision->zx81->spectrum->cbm64->cpc6128->520stfm->pc->raspi ?
User avatar
Posts: 1008
Joined: Mon Feb 13, 2012 8:06 pm
Location: Romiley, UK
by evinrude » Sun Jun 17, 2012 12:42 pm
I would definitely abstract it out. I just wanted to share with the forum how to do a quick and dirty GPIO in C without using the sysfs. I did notice that when I use mmap, I can pass 1 as the length and I would still have access to the entire physical memory from 0x20200000 -> 0xFFFFFFFF.

By passing 1, I thought this meant 1 byte from offset (if specified).

0 = Delegate mmap to allocate memory (no malloc necessary)
1 = length in bytes from offset
PROT_READ | PROT_WRITE = what we want to do to it
MAP_SHARED = allow other processes to access the mapped memory too
fd = handle to /dev/mem
0x20200000 = physical address to the start of gpio's
Code: Select all
mmap(0, 1, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x20200000);
User avatar
Posts: 8
Joined: Fri Jun 15, 2012 2:02 pm
Location: Texas
by rurwin » Sun Jun 17, 2012 1:14 pm
It probably means at least one byte. The minimum size will be one memory management page, however big that is.
User avatar
Forum Moderator
Forum Moderator
Posts: 2932
Joined: Mon Jan 09, 2012 3:16 pm
by tavdov » Sun Mar 02, 2014 5:38 am
Hello, I tried to run this code in my Raspberry but, it tells me that has many errors like this "error: stray '\240' in program, somebody knows why

evinrude wrote:Here is my code that toggles gpio17 every second. I tried to make it as minimal as possible while making it clear what is being done. Thanks for the help Gordon!

Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdint.h>

static volatile uint32_t *gpio;

int main(int argc, char **argv)
{
        int fd ;

        //Obtain handle to physical memory
        if ((fd = open ("/dev/mem", O_RDWR | O_SYNC) ) < 0) {
                printf("Unable to open /dev/mem: %s\n", strerror(errno));
                return -1;
        }


        //map a page of memory to gpio at offset 0x20200000 which is where GPIO goodnessstarts
        gpio = (uint32_t *)mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x20200000);

        if ((int32_t)gpio < 0){
                printf("Mmap failed: %s\n", strerror(errno));
                return -1;
        }

        //set gpio17 as an output
        //increment the pointer to 0x20200004
        //set the value through a little bit twiddling where we only modify the bits 21-23 in the register
        *(gpio + 1) = (*(gpio + 1) & ~(7 << 21)) | (1 << 21);

        //toggle gpio17 every second
        while(1){
                //set the pin high
                //increment the pointer to 0x2020001C
                *(gpio + 7) = 1 << 17;

                //sleep
                sleep(1);

                //set the pin to low
                //increment the pointer to 0x20200028
                *(gpio + 10) = 1 << 17;

                sleep(1);
        }
}
Posts: 1
Joined: Sun Mar 02, 2014 5:31 am
by joan » Sun Mar 02, 2014 9:03 am
Some browsers/forum software interact badly and add these spurious characters.

From the command line use

tr -d "\240\302" <bad-file >good-file

Where bad_file is the input and good_file is the output.
User avatar
Posts: 5991
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by rPi_User_111 » Wed Apr 30, 2014 8:40 pm
I'm hoping somebody can help with an issue I'm having. I am trying to use this technique to address the physical memory for GPIO 17 for high speed embedded applications. Basically I am trying to read from the GPIO at a sampling rate of approximately 100-200ns.

Using the volatile pointer is, for some reason, putting gaps in my signal every 15us or so. When I removed the volatile part, the gaps go away, but I read all zeros.

From my understanding, a volatile pointer is designed to identify a virtual address that could be changing. Since we are talking about a constant physical address, why do we need a volatile pointer? Would I have more luck writing this kind of thing as a module and pushing it into kernel space?

Thanks in advance
Posts: 15
Joined: Mon Jan 20, 2014 8:34 pm
by joan » Wed Apr 30, 2014 8:49 pm
So you are spinning in a busy loop reading the gpios and recording the state of gpio17.

Are you just storing status changes? How do you know when the reading was made?
User avatar
Posts: 5991
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by gordon@drogon.net » Wed Apr 30, 2014 9:26 pm
rPi_User_111 wrote:I'm hoping somebody can help with an issue I'm having. I am trying to use this technique to address the physical memory for GPIO 17 for high speed embedded applications. Basically I am trying to read from the GPIO at a sampling rate of approximately 100-200ns.

Using the volatile pointer is, for some reason, putting gaps in my signal every 15us or so. When I removed the volatile part, the gaps go away, but I read all zeros.

From my understanding, a volatile pointer is designed to identify a virtual address that could be changing. Since we are talking about a constant physical address, why do we need a volatile pointer? Would I have more luck writing this kind of thing as a module and pushing it into kernel space?

Thanks in advance


A volatile variable tells the C compiler that the contents of that variable can change without the compiler knowing about it. e.g. via another thread changing a global variable, or reading a variable mapped (via a pointer) to hardware that can change between subsequent reads.

When you're not declaring it volatile, the compiler thinks: Ah, this will never change, so I'll not bother re-reading it and optimises the read out of your code.

So it needs to be volatile.

However what you're trying to do is not going to be reliable for a variety of reasons - and what I suggest you do is search for the panalyzer thread here and see what they did to overcome some other timing issues, but from memory, the best they got was a reliable 1µS sample rate. I think this is it: viewtopic.php?f=37&t=7696

-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1537
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by rPi_User_111 » Thu May 01, 2014 1:31 pm
gordon@drogon.net wrote:
However what you're trying to do is not going to be reliable for a variety of reasons - and what I suggest you do is search for the panalyzer thread here and see what they did to overcome some other timing issues, but from memory, the best they got was a reliable 1µS sample rate. I think this is it: viewtopic.php?f=37&t=7696


Thanks for the really useful link. We have actually added a module that enables user access to information about elapsed clock cycles using the technique found here: http://blog.regehr.org/archives/794 By overclocking to 1000MHz, and reading the elapsed clock cycles, we have a nanosecond resolution timer (granted it takes 20ns to make the call and 50ns to read the signal)

joan wrote:
So you are spinning in a busy loop reading the gpios and recording the state of gpio17.

Are you just storing status changes? How do you know when the reading was made?


Since the signal is always either a 1 or 0, it is essentially a one-bit piece of information for every recording. So what we are doing is reading only the time, as fast as possible, and forcing the final bit high or low based on the signal. This gives us a 15 bit number storing the number of elapsed nanoseconds since the last recording and a 1 bit number for the signal. This is then stored in a single short int array.

here is the code

Code: Select all
for(i=0; i<n; i++){
   t1 = (*gpio & BIT_17) ? ccnt_read() | 1 : ccnt_read() & 4294967294 ;
   t[i] = t1 -t2;
   t2 = t1;
   }


To read the gpio pin, we are currently using a similar technique to the one described here : http://www.pieter-jan.com/node/15 Very similar to the source code for Gordon's wiringPi (fantastic library by the way). The core problem we are having, is that every 15us (or about 200 recordings) there is a 500ns "jitter" or gap. This jitter is highly periodic and goes away if I remove the read command. Does anyone have insight as to what phenomena could have this pattern? memory paging issue?
Posts: 15
Joined: Mon Jan 20, 2014 8:34 pm
by joan » Thu May 01, 2014 1:40 pm
Is this bare metal or under Linux?
User avatar
Posts: 5991
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by rPi_User_111 » Thu May 01, 2014 1:51 pm
Linux. We are using Raspbian with an RT patch.
Posts: 15
Joined: Mon Jan 20, 2014 8:34 pm
by joan » Thu May 01, 2014 1:52 pm
You probably need to raise it with the bare metal guys. How large is t[i], 1, 2, 4, 8 bytes? Perhaps it hits cache every 200 entries.
User avatar
Posts: 5991
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by rPi_User_111 » Thu May 01, 2014 1:55 pm
t[i] is a short int, so 2 bytes. 400-450 bytes sound right for a cache size? I would think it would be larger.
Posts: 15
Joined: Mon Jan 20, 2014 8:34 pm
by joan » Thu May 01, 2014 1:59 pm
rPi_User_111 wrote:t[i] is a short int, so 2 bytes. 400-450 bytes sound right for a cache size? I would think it would be larger.

I don't know. I do not understand the ARM architecture. If anyone does it's likely to be one of the bare metal guys.
User avatar
Posts: 5991
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by gordon@drogon.net » Thu May 01, 2014 4:42 pm
rPi_User_111 wrote:To read the gpio pin, we are currently using a similar technique to the one described here : http://www.pieter-jan.com/node/15 Very similar to the source code for Gordon's wiringPi (fantastic library by the way). The core problem we are having, is that every 15us (or about 200 recordings) there is a 500ns "jitter" or gap. This jitter is highly periodic and goes away if I remove the read command. Does anyone have insight as to what phenomena could have this pattern? memory paging issue?


It's probably the GPU doing dram memory refresh and/or video ram update.

AIUI there's nothing you can do about that, even in "bare metal" mode.

-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1537
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by rPi_User_111 » Fri May 02, 2014 1:35 pm
That's what I was afraid of. One more question. By using mmap and the physical address of the gpio pin, are we bypassing the linux virtual memory manager?
Posts: 15
Joined: Mon Jan 20, 2014 8:34 pm
by gordon@drogon.net » Fri May 02, 2014 2:34 pm
rPi_User_111 wrote:That's what I was afraid of. One more question. By using mmap and the physical address of the gpio pin, are we bypassing the linux virtual memory manager?


Yes.

-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1537
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by joan » Fri May 02, 2014 5:40 pm
rPi_User_111 wrote:That's what I was afraid of. One more question. By using mmap and the physical address of the gpio pin, are we bypassing the linux virtual memory manager?

Depends on what you mean. The pointer you use to access the gpio will be a Linux virtual address.

Could you show the code with and without the jitter?

I don't understand why removing the "read" makes a difference.

sample.png
sample.png (33.01 KiB) Viewed 1972 times


I've done some sampling at 5MHz (all gpios recorded) and don't quite see the same result. The plot shows time in microseconds across and number of samples taken in that microsecond up.

Perhaps I need to sample at a higher rate.
User avatar
Posts: 5991
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK