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 <linux/errno.h>  
#include <linux/kernel.h>  
#include <linux/module.h>  
#include <linux/slab.h>  
#include <linux/input.h>  
#include <linux/init.h>  
#include <linux/serio.h>  
#include <linux/delay.h>  
#include <linux/clk.h>  
#include <linux/miscdevice.h>  
#include <linux/gpio.h>  
#include <asm/io.h>  
#include <asm/irq.h>  
#include <asm/uaccess.h>  
#include <linux/cdev.h> 

#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<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<fcntl.h>  
#include<linux/ioctl.h>  
  

  
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<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<fcntl.h>  
#include<linux/ioctl.h>  
  

  
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);。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章