樹莓派linux驅動學習之LED控制

        前面我們編寫了hello world的程序,接下來繼續研究GPIO功能,通過GPIO來控制LED的亮滅,這在單片機中應該算是十分簡單的一個程序了,但是在Linux系統中控制GPIO沒有那麼簡單,難點就在於GPIO地址的獲取,也是我一直在糾結的問題。

一、GPIO地址

        我看了中嵌的嵌入式開發視頻,裏面使用三星2440控制LED的亮滅,但是驅動程序中沒有寫清楚具體的底層是如何實現的,這也是我查找的重點。我首先翻閱了樹莓派CPU(bcm2835)的芯片手冊,查到了GPIO的物理地址:

        但是在芯片資料的最開始,有提到芯片內部已經把上圖中的物理總線地址抽象到了面對操作系統的物理地址:


        所以,我們在編寫驅動程序的時候,IO空間的起始地址是0x20000000,加上GPIO的偏移量2000000,所以GPIO的物理地址應該是從0x20200000開始的,然後在這個基礎上進行Linux系統的MMU內存虛擬化管理,銀蛇到虛擬地址上。

二、硬件平臺

        我在樹莓派的擴展口的GPIO 17上接上了一個LED:


三、編寫驅動代碼

        一般的設備驅動我們需要設置主設備號和次設備號,在編寫應用程序的時候還要生成設備文件,比較麻煩。Linux針對像LED這樣的操作,有一種設備叫做混雜設備:是一種特殊的字符設備,雜設備早已經存在,是爲了給開發者一個較爲簡單的操作方式,因爲不用再重新申請一個設備號了(misc就是混雜設備的意思)
        驅動代碼:
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/ioport.h>

#include "bcm2835.h"

// Blinks on RPi Plug P1 pin 11 (which is GPIO pin 17)
#define PIN RPI_GPIO_P1_11

int open_state = 0;         //文件打開狀態

static int leds_open(struct inode *inode, struct file *filp)
{
    if(open_state == 0)  
    {  
        open_state =  1;  
        printk("Open file suc!\n");  
        return 0;  
    }  
    else  
    {  
        printk("The file has opened!\n");  
        return -1;  
    }  
}

static int leds_ioctl(struct file*filp, unsigned int cmd, unsigned long arg)
{
    switch(cmd)  
    {  
        case 0:  
            bcm2835_gpio_clr(PIN);
            printk("LED OFF!\n");
            break;  
        case 1:  
            bcm2835_gpio_set(PIN);
            printk("LED ON!\n");
            break;  

        default:  
            return-EINVAL;  
    }  

    return 0;
}

static int leds_release(struct inode *inode, struct file *filp)
{
    if(open_state == 1)  
    {  
        open_state =  0;  
        printk("close file suc!\n");  
        return 0;  
    }  
    else  
    {  
        printk("The file has closed!\n");  
        return -1;  
    }  
}

static const struct file_operations leds_fops = {
    .owner = THIS_MODULE,
    .open = leds_open,
    .unlocked_ioctl = leds_ioctl,
    .release = leds_release,
};

static struct miscdevice misc = {
    .minor =MISC_DYNAMIC_MINOR,
    .name ="my_leds",
    .fops =&leds_fops,
};


static int __init leds_init(void)
{
    int ret;

    //註冊混雜設備
    ret =misc_register(&misc);

    //配置功能選擇寄存器爲輸出
    bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);

    //設置輸出電平爲高電平,LED亮
    bcm2835_gpio_set(PIN);

    printk("ledsinit.\n");
    return ret;
}

static void leds_exit(void)
{
    //LED滅
    bcm2835_gpio_clr(PIN);

    misc_deregister(&misc);        

    printk("leds_exit\n");
}

module_init(leds_init);
module_exit(leds_exit);

MODULE_AUTHOR("Hu Chunxu");
MODULE_LICENSE("GPL");
    硬件相關操作:
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/ioport.h>

#include "bcm2835.h"

int bcm2835_gpio_fsel(uint8_t pin, uint8_t mode)
{
    //初始化GPIOB功能選擇寄存器的物理地址
    volatile uint32_t * bcm2835_gpio = (volatile uint32_t *)ioremap(BCM2835_GPIO_BASE, 16);
    volatile uint32_t * bcm2835_gpio_fsel = bcm2835_gpio + BCM2835_GPFSEL0/4 + (pin/10);
    uint8_t   shift = (pin % 10) * 3;
    uint32_t  value = mode << shift;
    *bcm2835_gpio_fsel = *bcm2835_gpio_fsel | value;

    printk("fsel address: 0x%lx : %x\n", bcm2835_gpio_fsel, *bcm2835_gpio_fsel);

    return 0;
}

int bcm2835_gpio_set(uint8_t pin)
{
    //GPIO輸出功能物理地址
    volatile uint32_t * bcm2835_gpio = (volatile uint32_t *)ioremap(BCM2835_GPIO_BASE, 16);
    volatile uint32_t * bcm2835_gpio_set = bcm2835_gpio + BCM2835_GPSET0/4 + pin/32;
    uint8_t   shift = pin % 32;
    uint32_t  value = 1 << shift;
    *bcm2835_gpio_set = *bcm2835_gpio_set | value;

    printk("set address:  0x%lx : %x\n", bcm2835_gpio_set, *bcm2835_gpio_set);

    return 0;
}

int bcm2835_gpio_clr(uint8_t pin)
{
   //GPIO清除功能物理地址
    volatile uint32_t * bcm2835_gpio = (volatile uint32_t *)ioremap(BCM2835_GPIO_BASE, 16);
    volatile uint32_t * bcm2835_gpio_clr = bcm2835_gpio + BCM2835_GPCLR0/4 + pin/32;
    uint8_t   shift = pin % 32;
    uint32_t  value = 1 << shift;
    *bcm2835_gpio_clr = *bcm2835_gpio_clr | value;
    
    printk("clr address:  0x%lx : %x\n", bcm2835_gpio_clr, *bcm2835_gpio_clr);

    return 0;
}

        應用測試程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
	int on;
	int fd;
	if (argc != 2 || sscanf(argv[1],"%d", &on) != 1 ||on < 0 || on > 1 ) {
		fprintf(stderr, "Usage:%s 0|1\n",argv[0]);
		exit(1);
	}
	fd = open("/dev/my_leds", 0);
	if (fd < 0) {
		perror("open device leds");
		exit(1);
	}
	/*通過ioctl來控制燈的亮、滅*/
	if(on){
		printf("turn on leds!\n");
		ioctl(fd, 1);
	}
	else {
		printf("turn off leds!\n");
		ioctl(fd, 0);
	}
	close(fd);
	return 0;
}

      分別編譯,插入模塊,然後運行測試程序,可以控制LED的亮滅了。

----------------------------------------------------------------

歡迎大家轉載我的文章。

轉載請註明:轉自古-月

http://blog.csdn.net/hcx25909

歡迎繼續關注我的博客


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