Simple GPIO access in C


11 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: 1421
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: 1421
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
Moderator
Posts: 2887
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: 4110
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK