linux 树莓派3B led驱动 问题总结

io物理地址

    首先树莓派3B的物理地址已经变了。能找到的手册只有2835/2708型号的手册,其实其他的几乎都没什么改变。怎么知道他的io地址改变了呢?通过3B更新的boot文件补丁里面得到的。

    我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。

   特别注意,BCM2708 和BCM2709 IO起始地址不同,BCM2708是0x20000000,BCM2709是0x3f000000,这是造成大部分人驱动出现“段错误”的原因。树莓派3B的CPU为BCM2709。
    根据手册原来的io地址是0x20200000,而3B的io地址变成了0x3f200000.其他的寄存器地址根据这个基地址算就可以了。

树莓派gpio操作

设置步骤为

1.读取该寄存器的值

根据数据手册记载,每个gpio口占用输出寄存器的1个位,也就是说每个寄存器管理32/1一共32个gpio口,所以确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)

2.修改寄存器方向的值(输入/输出)

根据数据手册记载,每个gpio口占用方向寄存器的3个位,也就是说每个寄存器管理32/3一共10个gpio口,所以确定是哪个寄存器(每个寄存器可以配置10个gpio 每个gpio需要3位)

3.写入寄存器                         

根据数据手册记载,每个gpio口占用输出寄存器的1个位,也就是说每个寄存器管理32/1一共32个gpio口,所以确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)

led驱动代码

新建一个文本,名字为pi_leds.c,实际可根据你的爱好来

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include  

#define PIN 18
#define BCM2835_GPIO_BASE           0x3f200000  
//32 32   4Bit function register
#define BCM2835_GPIOReg_GPFSEL1     0x00000004
//gpio set 1 output register 
#define BCM2835_GPIOReg_GPSET0      0x0000001c  
//gpio set 0 output register
#define BCM2835_GPIOReg_GPCLR0      0x00000028 



#define DEVICE_NAME     "led"  
#define LED_MAJOR       231   


#define IOCTL_LED_ON    0
#define IOCTL_LED_OFF   1


static volatile unsigned long *gpcon;
static volatile unsigned long *gpset;
static volatile unsigned long *gpclr;


static int pi_leds_open(struct inode *inode, struct file *file)
{
	//config your led pin function mode
	gpcon = (volatile unsigned long *)ioremap(0x3f200004,4);
	gpset = (volatile unsigned long *)ioremap(0x3f20001C,4);
	gpclr = (volatile unsigned long *)ioremap(0x3f200028,4);        

    return 0;
}



static int pi_leds_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    if (arg > 3) {
        return -EINVAL;
    }
    
    switch(cmd) {
    case IOCTL_LED_ON:
        // set your led 0
        *gpcon = *gpcon | (1<<24); //set pin18 to output function mode
        *gpset = *gpset | (1<<18); //set pin18 output high 
        return 0;

    case IOCTL_LED_OFF:
        // set your led 1
        *gpcon = *gpcon | (1<<24); //set pin18 to output function mode
        *gpclr = *gpclr | (1<<18); //set pin18 output low
        return 0;

    default:
        return -EINVAL;
    }
}




static struct file_operations pi_leds_fops = {
    .owner  =   THIS_MODULE,    
    .open   =   pi_leds_open,     
    .unlocked_ioctl     =   pi_leds_ioctl,
};




static int __init pi_leds_init(void)
{
    int ret;

   
    ret = register_chrdev(LED_MAJOR, "led", &pi_leds_fops);


    if (ret < 0) {
      printk("led  can't register major number\n");
      return ret;
    }
    
    printk("led  is  initialized  \n");
    printk("led device number is : %d  \n",ret);
    return 0;
}



static void __exit pi_leds_exit(void)
{
    
    unregister_chrdev(LED_MAJOR, DEVICE_NAME);
    printk("led  is  goodbye\n");
}


module_init(pi_leds_init);
module_exit(pi_leds_exit);


MODULE_LICENSE("GPL");                             

Makefile文件

修改KDIR为你的linux源码根目录,修改NAME为你的驱动文件名字

这里第一次用的话,要去到你的源码目录里面的drivers/char/Makefile里面随便找一个新的行,输入###。这样我们自己写的Makefile才好定位插入文本。

	
ifneq ($(KERNELRELEASE),)
	obj-m := pi_leds.o

else
	KDIR := /home/ubuntu1604/Desktop/work/linux-rpi-4.9.y
	SPWD := $(shell pwd)
	NAME := pi_leds

default:
	cp $(SPWD)/*.c  $(KDIR)/drivers/char
	sed -i "s/###/###\nobj-m += "$(NAME)".o/g" $(KDIR)/drivers/char/Makefile
	make -C $(KDIR) modules
	cp $(KDIR)/drivers/char/$(NAME).ko $(SPWD)
	rm -f $(KDIR)/drivers/char/$(NAME).*
	sed -i "N;s/\nobj-m += $(NAME).o//g" $(KDIR)/drivers/char/Makefile
	sed -i "N;s/obj-m += $(NAME).o\n//g" $(KDIR)/drivers/char/Makefile


#zai  di  er  ju shi
#zal di yi ju shi
clean:	
	sed -i "N;s/\nobj-m += $(NAME).o//g" $(KDIR)/drivers/char/Makefile
	sed -i "N;s/obj-m += $(NAME).o\n//g" $(KDIR)/drivers/char/Makefile

endif

    

然后make就可以了,编译后会直接把.ko复制到你的目录下面。

应用程序代码

新建文本,名字为app.c

#include  
#include  
#include  
#include  
#include  
  

  
int main(int argc, char **argv)  
{  
    int dev_fd;  
	int on;

if (argc != 2 || sscanf(argv[1],"%d", &on) != 1 ||on < 0 || on > 1 ) {  
        fprintf(stderr, "Usage:%s 0|1\n",argv[0]);  
        exit(1);  
    }  


    dev_fd = open("/dev/led",O_RDWR);  
    if( dev_fd == -1){  
        printf("Cann't open file \n");  
	return 1;
    }  
     /*通过ioctl来控制灯的亮、灭*/  
    if(on){  
        printf("turn on leds!\n");  
        ioctl(dev_fd, 1);  
    }  
    else {  
        printf("turn off leds!\n");  
        ioctl(dev_fd, 0);  
    }  

    close(dev_fd);  
      
    return 0;  
}  

编译命令

可以去树莓派上编译

gcc app.c -o app.o

在主机上编译就要用交叉编译器,实际编译器名字根据你的环境而定

arm-linux-gcc app.c -o app.o

树莓派上面操作

插入模块 ,创建节点,更改权限

sudo insmod pi_leds.ko
sudo mknod /dev/led c 231 0
sudo chmod 777 /dev/led 

测试应用程序

分别控制亮灭

./app.o 1
./app.o 0

另一款app(在应用程序上实现flash灯)

#include  
#include  
#include  
#include  
#include  
  

  
int main()  
{  
    int dev_fd;
    int on=0;
    dev_fd = open("/dev/led",O_RDWR | O_NONBLOCK);  
    if( dev_fd == -1){  
        printf("Cann't open file /dev/led\n");  
	return 1;
    }  
    printf("Start led\n");  

while(1){ 
     
    sleep(1);
    fflush(stdout);   
    if(on==1)on=0;
    else on=1;
    if(on==0)
	{
		ioctl(dev_fd,1);
		printf("on");
	}
    else
	{
		ioctl(dev_fd,0);
		printf("off");
	}  
}

    printf("Stop led\n"); 
    close(dev_fd);  
      
    return 0;  
}  

这里要说一点,如果用循环和sleep去延时的话,会出现程序卡死。io控制函数不起作用。可能是lock机制的问题或者print缓冲的问题。用强制刷新缓冲区   fflush(stdout);  就可以解决了,如果还是卡的话,可以在每个延时和循环后面加fflush(stdout);。

 

你可能感兴趣的:(计算机,树莓派,驱动)