11.重寫——led驅動

我們之前的驅動程序已經把框架寫好,只是打印一些語句,沒有做其他的事,現在我們讓他點亮led燈

驅動的硬件操作分爲3步:

  • 看原理圖

  • 看芯片手冊(這裏是S3C2440)

  • 寫硬件相關代碼

linux驅動的硬件相關代碼和單片機的有什麼不同呢

單片機的是直接操作物理地址

linux的是操作虛擬地址(由ioremap函數映射,物理地址——>虛擬地址)

1.查看原理圖

(1)通過查看原理圖,我們知道要led的引腳爲GPF4,5,6

2.查看芯片手冊

搜索GPFCON,知道要操作GPFCON和GPFDAT寄存器,他們的物理地址如下:

點亮led需要配置GPFCON寄存器,輸出模式

                   設置GPFDATE寄存器,來輸出高低電平

我們把配置GPFCON放在open函數,把設置GPFDATE放在write函數

(1)配置GPF4,5,6爲輸出,操作GPFCON寄存器

(2)設置GPFDATE寄存器,來輸出高低電平

 

3.設置GPFCON寄存器

(1)由芯片手冊知道GPFCON寄存器的物理地址爲0x56000050,linux操作是使用虛擬地址,所以我們要使用ioremap把物理地址映射爲虛擬地址,在入口函數裏面配置,每次打開就自動配置好

搜索ioremap的使用方法,參數爲起始地址和長度(映射空間爲從起始地址+長度這麼一段空間,這裏由於有4個寄存器,用16字節),由於32位的指針都是4字節,所以gpfdat= gpfcon + 1,完善:

在出口地址設置取消映射:

以後操作寄存器的時候就用指針gpfcon和gpfdat操作引腳

 

(2)操作

A &= ~B爲位清零

A |= B 爲置1

先爲清零在置1設置輸出模式,由於是2位,用0x03(兩個1)清零,01爲輸出,用0x01置1.

4.設置GPFDAT寄存器

在write函數裏操作

我們要點亮關閉,需要在應用層輸入參數on/off,在應用層的參數怎麼傳到內核層的驅動函數,用copy_from_user()函數

然後設置val是1或者0開燈關燈

5.編寫測試程序

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

/*

*myled_test on

*myled_test off

*/

int main(int argc, char **argv)

{

    int fd;

    int val = 1;

    fd = open("/dev/myled", O_RDWR);

    if (fd < 0)

    {

        printf("can't open!\n");

    }

        if (argc != 2)

    {

        printf("Usage :\n");

        printf("%s <on|off>\n", argv[0]);

        return 0;

    }



    if (strcmp(argv[1], "on") == 0)

    {

        val  = 1;

    }

    else

    {

        val = 0;

    }

    write(fd, &val, 4);

    return 0;

}

驅動代碼爲:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>


volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

static struct class *firstdrv_led_class;
static struct class_device *firstdrv_led_class_dev;


static int firstdrv_led_open(struct inode *inode, struct file *file)
{
    /*配置GPF 4 5 6 爲輸出*/
	*gpfcon &= ~((0x03)<<(4*2)|(0x03)<<(5*2)|(0x03)<<(6*2));
	*gpfcon |=  ((0x01)<<(4*2)|(0x01)<<(5*2)|(0x01)<<(6*2));
    return 0;
}
static ssize_t firstdrv_led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	copy_from_user(&val,buf,count); //由應用層的write(fd, &val, 4)函數的val參數傳到內核層,讓我們可以使用應用層的val值
							  //把buf=&val(write(fd, &val, 4)函數中的&val)傳給copy_from_user(&val,buf,count)中的val
						          //對應的內核層到應用層是用copy_to_user()
	if(val == 1)
	{
		//點燈,輸出低電平
		*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
	}
	else
	{
	    //滅燈,高電平
	    *gpfdat |=(1<<4)|(1<<5)|(1<<6);
	    
	}
    return 0;
}

/* 這個結構是字符設備驅動程序的核心
* 當應用程序操作設備文件時所調用的open、read、write等函數,
* 最終會調用這個結構中指定的對應函數
*/
static struct file_operations firstdrv_led_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   firstdrv_led_open,           
    .write    =    firstdrv_led_write,       
};

int major;
int firstdrv_led_init(void)
{
    major =register_chrdev(0,"myled",&firstdrv_led_fops);//註冊firstdrv,告訴內核,註冊設備時給設備號寫0,則內核會自己主動分配一個主設備號返回。
    firstdrv_led_class = class_create(THIS_MODULE,"firstdrv_led");
    firstdrv_led_class_dev = class_device_create(firstdrv_led_class,NULL,MKDEV(major,0),NULL,"myled");

	gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
    gpfdat= gpfcon + 1;
	return 0;
}

void firstdrv_led_exit(void)
{
    unregister_chrdev(major,"firstdrv_led");//卸載
    class_device_unregister(firstdrv_led_class_dev);
    class_destroy(firstdrv_led_class);
	iounmap(gpfcon);
}

module_init(firstdrv_led_init);
module_exit(firstdrv_led_exit);
MODULE_LICENSE("GPL");

6.測試

結果燈成功亮滅

7.總結

(1)寫驅動框架(open,write等,file_operations結構體,註冊,自動創建設備節點)

(2)硬件操作(看原理圖,手冊,ioremap映射)

 

8.拓展(次設備號的應用)

我們的實驗結果是傳進1,就點亮三盞燈,0就滅燈,那有沒有方法單獨點亮某盞燈,可以使用不同的val,但是太麻煩,這裏我們使用次設備號對應不同的燈來操作,之前我們生成的是:

 

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