本文用C 语言编写了一个利用树莓派的GPIO点亮一个LED 的驱动程序和应用程序,在我的树莓派4B上亲测可用。
目前网上有很多关于树莓派GPIO驱动的文章,但有些是针对老版本的树莓派写的,树莓派的IO的物理地址(或者是BUS地址)不适用于树莓派4B。利用树莓派官网上提供的库编程,可以获得树莓派4B的IO的物理地址(或者是BUS地址)是FE200000。
驱动程序源码和应用程序源码大部分是借鉴网上这篇文章
网址:https://www.pianshen.com/article/9594392405/
在此感谢原作者,如果构成侵权,请留言,我看到留言后立即删除。
驱动程序源码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/moduleparam.h>
#define BCM2711_GPIO_BASE 0xfe200000
#define LED_ON 1
#define LED_OFF 0
#define GPFSEL0 (0x0/4)
#define GPFSEL1 (0x4/4)
#define GPFSEL2 (0x8/4)
#define GPFSEL3 (0xc/4)
#define GPFSEL4 (0x10/4)
#define GPFSEL5 (0x14/4)
#define GPSET0 (0x1c/4)
#define GPSET1 (0x20/4)
#define GPCLR0 (0x28/4)
#define GPCLR1 (0x2c/4)
static dev_t devno; //设备号
static int major; //主设备号
static int minor; //次设备号
static struct cdev led_dev;
static struct class *cls;
static struct device *test_device;
unsigned int *global_gpio = NULL;
static int led_open (struct inode *inode, struct file *filep)
{//open
int sel0_value = 0;
//实现对pin 4引脚 设为输出操作
sel0_value = *(global_gpio+GPFSEL0);
sel0_value |= (1<<12);
sel0_value &= ~(1<<13);
sel0_value &= ~(1<<14);
*(global_gpio+GPFSEL0) = sel0_value;
printk(KERN_INFO"led_open sel0_value = 0x%x\n", sel0_value);
return 0;
}
static ssize_t led_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
printk(KERN_INFO"led_read \n");
return 0;
}
static ssize_t led_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
{ //实现对pin 4引脚 高电平 低电平操作
int set0_value = 0;
char led_value = 0;
unsigned long count = 1;
if(copy_from_user((void *)&led_value, buf, count))
{
return -1;
}
printk(KERN_ERR"led_write led_num =%d \n",led_value);
if (led_value > 0)
{
set0_value = *(global_gpio+GPSET0);
set0_value |= (1<<4);
*(global_gpio+GPSET0) = set0_value;
printk(KERN_ERR"0 write led_value =0x%x \n",set0_value);
}
else
{
set0_value = *(global_gpio+GPCLR0);
set0_value |= (1<<4);
*(global_gpio+GPCLR0) = set0_value;
printk(KERN_ERR"1 write led_value =0x%x \n",set0_value);
}
printk(KERN_INFO"led_write end \n");
return 0;
}
static struct file_operations led_ops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
};
static int __init led_init(void)
{
int ret;
printk (KERN_INFO"global_gpio = 0x%lx\n", (unsigned long)global_gpio);
global_gpio = ioremap(BCM2711_GPIO_BASE, 0x80);
printk (KERN_INFO"global_gpio = 0x%lx\n", (unsigned long)global_gpio);
ret = alloc_chrdev_region(&devno,0,1,"ledzfj");
printk(KERN_INFO"ret= %d\n", ret);
printk(KERN_INFO"devno= 0x%x\n", devno);
major=MAJOR(devno);
minor=MINOR(devno);
printk(KERN_INFO"major = %d \n",major);
printk(KERN_INFO"minor = %d \n",minor);
cdev_init(&led_dev, &led_ops);
led_dev.owner=THIS_MODULE;
ret = cdev_add(&led_dev,devno,1); // 把这个驱动加入到内核的链表中
cls = class_create(THIS_MODULE, "ledclass");// 注册class 让代码在dev自动生成设备
if(IS_ERR(cls))
{
cdev_del(&led_dev);
return -1;
}
test_device = device_create(cls,NULL,devno,NULL,"ledzfj");创建设备文件/dev/ledzfj
if(IS_ERR(test_device))
{
class_destroy(cls);
cdev_del(&led_dev);
return -1;
}
printk(KERN_INFO"led init ok!\n");
return 0;
}
static void __exit led_exit(void)
{
iounmap(global_gpio);
device_destroy(cls,devno);
class_destroy(cls);
cdev_del(&led_dev);
unregister_chrdev_region(devno, 1);
printk(KERN_INFO"led_exit \n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
说明一下:驱动源代码几乎全部来源于网上文章,我只是改了一下 #define BCM2711_GPIO_BASE 的值,另外用新版接口注册设备。
应用程序源码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd,pinvalue;
fd = open("/dev/ledzfj",O_RDWR);
if(fd<0)
{
printf("open fail \n");
return -1;
}
printf("open success fd=%d \n", fd);
if( argc > 1 )
{
pinvalue= atoi(argv[1]);
write(fd,&pinvalue,sizeof(int));
printf("str =%s \n", argv[1]);
}
close(fd);
}
上述程序中包含的头文件可能有些是不需要的,但我对linux编程不熟悉,不知道该删除哪个,又懒得删掉一个,编译一次的看看是否通过的试验。网友们如果知道那个头文件是不需要的,可以留言告诉我。
取树莓派4B的IO的物理地址的程序源码如下:
#include <stdio.h>
#include <bcm_host.h>
int main()
{
unsigned long*IO_PHY_ADDR=NULL;
unsigned long*IO_PHY_SIZE=NULL;
unsigned long*RAM_ADDR=NULL;
IO_PHY_ADDR=(unsigned long *)bcm_host_get_peripheral_address();
IO_PHY_SIZE=(unsigned long *)bcm_host_get_peripheral_size();
RAM_ADDR=(unsigned long *)bcm_host_get_sdram_address();
printf("peripheral physical address are mapped to : 0x%lx \n", (unsigned long)IO_PHY_ADDR);
printf("peripheral physical address size: 0x%lx \n", (unsigned long)IO_PHY_SIZE);
printf("bus address of RAM: 0x%lx \n", (unsigned long)RAM_ADDR);
}
程序中的 bcm_host_get_peripheral_address();
bcm_host_get_peripheral_size();
bcm_host_get_sdram_address();
是树莓派官方提供的库函数,源码和库已经集成在/opt/vc/中
编译参数如下:
cc 123.c -I /opt/vc/include -L /opt/vc/lib -l bcm_host -o 123
也可以按照这篇文章介绍的方法:https://www.pianshen.com/article/9594392405/获取IO的物理地址。
因为是CSDN 的新手,文章排版不美观。欢迎网友们能把这个程序改的更健壮,更好用。