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,但是太麻烦,这里我们使用次设备号对应不同的灯来操作,之前我们生成的是:

发布了114 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_40535588/article/details/90580082
今日推荐