玩转树莓派(七)使用C语言 通过修改寄存器控制GPIO

2,864 阅读2分钟

玩转树莓派(七)使用C语言 通过修改寄存器控制GPIO

@TOC

一、创建环境

pi@raspberrypi:~ $ mkdir CWorkSpace
pi@raspberrypi:~ $ cd CWorkSpace/
pi@raspberrypi:~/CWorkSpace $ vim my_gpio.c 

二、编写代码

  • 参考bcm2585库
  • 编辑my_gpio.c
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>

#define PIN 18

#define BCM2835_GPFSEL0                      0x0000 	/*!< GPIO Function Select 0 */
#define BCM2835_GPSET0                       0x001c 	/*!< GPIO Pin Output Set 0 */
#define BCM2835_GPCLR0                       0x0028 	/*!< GPIO Pin Output Clear 0 */

#define BCM2835_PERI_BASE               0x20000000		/*! Peripherals block base address on RPi 1 */
#define BCM2835_PERI_SIZE               0x01000000		/*! Size of the peripherals block on RPi 1 */

#define BMC2835_RPI2_DT_PERI_BASE_ADDRESS_OFFSET 4			/*! Offset into BMC2835_RPI2_DT_FILENAME for the peripherals base address */
#define BMC2835_RPI2_DT_PERI_SIZE_OFFSET 8					/*! Offset into BMC2835_RPI2_DT_FILENAME for the peripherals size address */

#define BCM2835_ST_BASE			0x3000
#define BCM2835_GPIO_PADS       0x100000		/*! Base Address of the Pads registers */
#define BCM2835_CLOCK_BASE      0x101000		/*! Base Address of the Clock/timer registers */
#define BCM2835_GPIO_BASE       0x200000		/*! Base Address of the GPIO registers */
#define BCM2835_SPI0_BASE       0x204000		/*! Base Address of the SPI0 registers */
#define BCM2835_BSC0_BASE 		0x205000		/*! Base Address of the BSC0 registers */
#define BCM2835_GPIO_PWM        0x20C000		/*! Base Address of the PWM registers */
#define BCM2835_BSC1_BASE		0x804000		/*! Base Address of the BSC1 registers */

/*!   \brief bcm2835PortFunction
  Port function select modes for bcm2835_gpio_fsel()
*/
typedef enum
{
    BCM2835_GPIO_FSEL_INPT  = 0x00,   /*!< Input 0b000 */
    BCM2835_GPIO_FSEL_OUTP  = 0x01,   /*!< Output 0b001 */
    BCM2835_GPIO_FSEL_ALT0  = 0x04,   /*!< Alternate function 0 0b100 */
    BCM2835_GPIO_FSEL_ALT1  = 0x05,   /*!< Alternate function 1 0b101 */
    BCM2835_GPIO_FSEL_ALT2  = 0x06,   /*!< Alternate function 2 0b110, */
    BCM2835_GPIO_FSEL_ALT3  = 0x07,   /*!< Alternate function 3 0b111 */
    BCM2835_GPIO_FSEL_ALT4  = 0x03,   /*!< Alternate function 4 0b011 */
    BCM2835_GPIO_FSEL_ALT5  = 0x02,   /*!< Alternate function 5 0b010 */
    BCM2835_GPIO_FSEL_MASK  = 0x07    /*!< Function select bits mask 0b111 */
} bcm2835FunctionSelect;

/* Physical address and size of the peripherals block
// May be overridden on RPi2
*/
uint32_t *bcm2835_peripherals_base = (uint32_t *)BCM2835_PERI_BASE;
uint32_t bcm2835_peripherals_size = BCM2835_PERI_SIZE;

/* Virtual memory address of the mapped peripherals block 
 */
uint32_t *bcm2835_peripherals = (uint32_t *)MAP_FAILED;

/* And the register bases within the peripherals block
 */
volatile uint32_t *bcm2835_gpio        = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_pwm         = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_clk         = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_pads        = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_spi0        = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_bsc0        = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_bsc1        = (uint32_t *)MAP_FAILED;
volatile uint32_t *bcm2835_st	       = (uint32_t *)MAP_FAILED;

/* Map 'size' bytes starting at 'off' in file 'fd' to memory.
// Return mapped address on success, MAP_FAILED otherwise.
// On error print message.
*/
static void *mapmem(const char *msg, size_t size, int fd, off_t off)
{
    void *map = mmap(NULL, size, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, off);
    if (map == MAP_FAILED)
		fprintf(stderr, "bcm2835_init: %s mmap failed: %s\n", msg, strerror(errno));
    return map;
}

static void unmapmem(void **pmem, size_t size)
{
    if (*pmem == MAP_FAILED) return;
    munmap(*pmem, size);
    *pmem = MAP_FAILED;
}

/* Write with memory barriers to peripheral
 */
void peri_write(volatile uint32_t* paddr, uint32_t value)
{
	__sync_synchronize();
	*paddr = value;
	__sync_synchronize();
}

/* Read with memory barriers from peripheral
 *
 */
uint32_t peri_read(volatile uint32_t* paddr)
{
	uint32_t ret;
	__sync_synchronize();
	ret = *paddr;
	__sync_synchronize();
	return ret;
}

/* Set output pin */
void gpio_set(uint8_t pin)
{
    volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPSET0/4 + pin/32;
    uint8_t shift = pin % 32;
    peri_write(paddr, 1 << shift);
}

/* Clear output pin */
void gpio_clr(uint8_t pin)
{
    volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPCLR0/4 + pin/32;
    uint8_t shift = pin % 32;
    peri_write(paddr, 1 << shift);
}

/* Set/clear only the bits in value covered by the mask
 * This is not atomic - can be interrupted.
 */
void peri_set_bits(volatile uint32_t* paddr, uint32_t value, uint32_t mask)
{
    uint32_t v = peri_read(paddr);
    v = (v & ~mask) | (value & mask);
    peri_write(paddr, v);
}

int uninit(void)
{
    unmapmem((void**) &bcm2835_peripherals, bcm2835_peripherals_size);
    bcm2835_peripherals = MAP_FAILED;
    bcm2835_gpio = MAP_FAILED;
    bcm2835_pwm  = MAP_FAILED;
    bcm2835_clk  = MAP_FAILED;
    bcm2835_pads = MAP_FAILED;
    bcm2835_spi0 = MAP_FAILED;
    bcm2835_bsc0 = MAP_FAILED;
    bcm2835_bsc1 = MAP_FAILED;
    bcm2835_st   = MAP_FAILED;
    return 1; /* Success */
}    

int init(void)
{
	int  memfd;
    int  ok;
    FILE *fp;


    /* Figure out the base and size of the peripheral address block
    // using the device-tree. Required for RPi2, optional for RPi 1
    */
    if ((fp = fopen("/proc/device-tree/soc/ranges" , "rb")))
    {
        unsigned char buf[4];
		fseek(fp, BMC2835_RPI2_DT_PERI_BASE_ADDRESS_OFFSET, SEEK_SET);
		if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf))
			bcm2835_peripherals_base = (uint32_t *)(buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0);
		fseek(fp, BMC2835_RPI2_DT_PERI_SIZE_OFFSET, SEEK_SET);
		if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf))
			bcm2835_peripherals_size = (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0);
		fclose(fp);
    }
    /* else we are prob on RPi 1 with BCM2835, and use the hardwired defaults */

    /* Now get ready to map the peripherals block 
     * If we are not root, try for the new /dev/gpiomem interface and accept
     * the fact that we can only access GPIO
     * else try for the /dev/mem interface and get access to everything
     */
    memfd = -1;
    ok = 0;
    if (geteuid() == 0)
    {
		printf("root user\r\n");
		/* Open the master /dev/mem device */
		if ((memfd = open("/dev/mem", O_RDWR | O_SYNC) ) < 0) 
		{
			fprintf(stderr, "bcm2835_init: Unable to open /dev/mem: %s\n", strerror(errno));
			goto exit;
		}
      
		/* Base of the peripherals block is mapped to VM */
		bcm2835_peripherals = mapmem("gpio", bcm2835_peripherals_size, memfd, (uint32_t)bcm2835_peripherals_base);
		if (bcm2835_peripherals == MAP_FAILED) goto exit;

		/* Now compute the base addresses of various peripherals, 
		// which are at fixed offsets within the mapped peripherals block
		// Caution: bcm2835_peripherals is uint32_t*, so divide offsets by 4
		*/
		bcm2835_gpio = bcm2835_peripherals + BCM2835_GPIO_BASE/4;
		bcm2835_pwm  = bcm2835_peripherals + BCM2835_GPIO_PWM/4;
		bcm2835_clk  = bcm2835_peripherals + BCM2835_CLOCK_BASE/4;
		bcm2835_pads = bcm2835_peripherals + BCM2835_GPIO_PADS/4;
		bcm2835_spi0 = bcm2835_peripherals + BCM2835_SPI0_BASE/4;
		bcm2835_bsc0 = bcm2835_peripherals + BCM2835_BSC0_BASE/4; /* I2C */
		bcm2835_bsc1 = bcm2835_peripherals + BCM2835_BSC1_BASE/4; /* I2C */
		bcm2835_st   = bcm2835_peripherals + BCM2835_ST_BASE/4;

		ok = 1;
    }
    else
    {
		printf("not root user\r\n");
		/* Not root, try /dev/gpiomem */
		/* Open the master /dev/mem device */
		if ((memfd = open("/dev/gpiomem", O_RDWR | O_SYNC) ) < 0) 
		{
			fprintf(stderr, "bcm2835_init: Unable to open /dev/gpiomem: %s\n", strerror(errno)) ;
			goto exit;
		}
      
		/* Base of the peripherals block is mapped to VM */
		bcm2835_peripherals_base = 0;
		bcm2835_peripherals = mapmem("gpio", bcm2835_peripherals_size, memfd, (uint32_t)bcm2835_peripherals_base);
		if (bcm2835_peripherals == MAP_FAILED) 
			goto exit;
		bcm2835_gpio = bcm2835_peripherals;
		ok = 1;
    }

exit:
    if (memfd >= 0)
        close(memfd);

    if (!ok)
		uninit();

    return ok;
}

void gpio_fsel(uint8_t pin, uint8_t mode)
{
    /* Function selects are 10 pins per 32 bit word, 3 bits per pin */
    volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPFSEL0/4 + (pin/10);
    uint8_t   shift = (pin % 10) * 3;
    uint32_t  mask = BCM2835_GPIO_FSEL_MASK << shift;
    uint32_t  value = mode << shift;
    peri_set_bits(paddr, value, mask);
}

/* Set the state of an output */
void gpio_write(uint8_t pin, uint8_t on)
{
    if (on)
		gpio_set(pin);
    else
		gpio_clr(pin);
}

/* Some convenient arduino-like functions
// milliseconds
*/
void delay(unsigned int millis)
{
    struct timespec sleeper;
    
    sleeper.tv_sec  = (time_t)(millis / 1000);
    sleeper.tv_nsec = (long)(millis % 1000) * 1000000;
    nanosleep(&sleeper, NULL);
}

int main(void)
{
	int i=0;
	
	if (!init())
		return 1;
	
	gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);
	for(i=0;i<10;i++)
	{
		printf("gpio out: %d\r\n", i);
		gpio_write(PIN, i%2);
		delay(3000);
	}
	printf("gpio out end\r\n");
	return 0;
}

三、源码解析

讲真,这次源码有点长,大部分都是参考bcm2835.c。程序的关键在于初始化部分init(),在初始化期间找到树莓派寄存器地址,并映射到内存。而后的设置GPIO输出,设置GPIO高低电平,则是修改寄存器的值。

3.1 init()

  • 通过读取/proc/device-tree/soc/ranges,得到外设寄存器的基地址和长度
  • 如果是root,则映射/dev/mem到内存
  • 如果不是root,则映射/dev/gpiomem到内存

四、编译运行

  • 编译
  • 运行
    • root用户运行
    • 非root用户运行

不同权限的用户,走的程序也不一样

pi@raspberrypi:~/CWorkSpace $ gcc -o my_gpio my_gpio.c 
pi@raspberrypi:~/CWorkSpace $ ./my_gpio 
not root user
gpio out: 0
gpio out: 1
gpio out: 2
gpio out: 3
gpio out: 4
gpio out: 5
gpio out: 6
gpio out: 7
gpio out: 8
gpio out: 9
gpio out end
pi@raspberrypi:~/CWorkSpace $ sudo ./my_gpio 
root user
gpio out: 0
gpio out: 1
gpio out: 2
gpio out: 3
gpio out: 4
gpio out: 5
gpio out: 6
gpio out: 7
gpio out: 8
gpio out: 9
gpio out end

五、关键函数

5.1 mmap

mmap将一个文件或者其它对象映射进内存。mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。
函数void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
输入start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
——length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理。
——prot:期望的内存保护标志,不能与文件的打开模式冲突。PROT_EXEC //页内容可以被执行;PROT_READ //页内容可以被读取;PROT_WRITE //页可以被写入;PROT_NONE //页不可访问
——flags:指定映射对象的类型,映射选项和映射页是否可以共享。
——fd:有效的文件描述词。一般是由open()函数返回。
——offset:被映射对象内容的起点。
返回成功:被映射区的指针。失败:MAP_FAILED[其值为(void *)-1]。

5.2 munmap

munmap()用来取消参数start所指的映射内存起始地址

函数int munmap(void *start,size_t length);
输入start:映射区的开始地址
——length:欲取消的内存大小
返回成功:0;失败:-1。

5.3 fopen和open

fopen打开普通文件,用open打开设备文件

六、查看效果

用万用表量下BCM编号18的GPIO.1的引脚,可以发现其间隔3s的高低电平变换了10次。

pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | 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 | 1 | OUT  | 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 |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | 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 3B--+---+------+---------+-----+-----+