玩转树莓派(七)使用C语言 通过修改寄存器控制GPIO
目录
- 玩转树莓派(七)使用C语言 通过修改寄存器控制GPIO
- 一、创建环境
- 二、编写代码
- 三、源码解析
- 3.1 init()
- 四、编译运行
- 五、关键函数
- 5.1 mmap
- 5.2 munmap
- 5.3 fopen和open
- 六、查看效果
- 七、漂亮的Ending
一、创建环境
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--+---+------+---------+-----+-----+
七、漂亮的Ending