linux驱动学习笔记---点亮一个led灯(三)

led硬件地址映射和操作

1.点亮s5pv210的led灯首先需要知道其所用引脚,以及引脚所对应的状态寄存器与数据寄存器

根据外围电路图可知两个灯所用的引脚分别为gpc0_3与gpc0_4,找到其引脚所对应的寄存器 

 状态寄存器

 5位的数据寄存器

在驱动模块加载中做地址映射,映射的地址为8个字节64位(包括状态寄存器与数据寄存器)

gpc0con + 1(一个寄存器为4个字节32位,表示指向下一个寄存器0xE0200064)

// 3,实现驱动模块加载/卸载入口函数
static int led_drv_init(void)
{
	
	printk("--------^_*  %s-------\n", __FUNCTION__);
	int ret;

	// 申请主设备号, 默认次设备号为0
	// 参数1---指定的主设备号--就是一个整数,选255以上
	//参数2--设备的描述--自定义的字符串
	//参数3--设备驱动的文件操作对象
	//返回值: 错误为负数,正确为0
	ret = register_chrdev(led_major, led_name,  &led_fops);
	if(ret < 0)
	{
		printk("register_chrdev error\n");
		return ret;
	}

	// 自动创建设备节点
	//创建设备文件所属类别
	//参数1--拥有者--当前模块
	//参数2--类别的名字--自定义
	//返回值---返回一个指针
	led_cls = class_create(THIS_MODULE, "led_cls");

	//创建设备文件
	//参数1--所属类别
	//参数2--当前创建的设备文件的父类是谁--一般NULL
	//参数3--关联的设备号
	//参数4--当前设备文件的私有数据--一般NULL
	//参数5/6--设定设备文件的名字
	device_create(led_cls, NULL, MKDEV(led_major, 0), NULL, "led0");  // led0



	
	//参数1--硬件的物理地址
	//参数2--映射的地址长度
	//返回值---映射之后的虚拟地址
	gpc0con = ioremap(0xE0200060,  8);
	gpc0dat = gpc0con + 1;


	

	return 0;
}

在卸载的函数中 去映射

static void led_drv_exit(void)
{

	printk("--------^_*  %s-------\n", __FUNCTION__);
	//去映射
	//参数1---映射之后的虚拟地址
	iounmap(gpc0con);
	
	//参数1--所属类别
	//参数2--关联的设备号
	device_destroy(led_cls, MKDEV(led_major, 0));

	//参数1--所属类别
	class_destroy(led_cls);
	
	// 参数1---指定的主设备号--就是一个整数,选255以上
	//参数2--设备的描述--自定义的字符串
	unregister_chrdev(led_major,  led_name);

}

 在open函数中使能,配置状态寄存器为输出功能

int led_drv_open (struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	//直接对硬件进行操作
	//配置成输出功能
	*gpc0con &= ~(0xff<<12);
	*gpc0con |= (0x11<<12);



	return 0;
}

 在write函数中给数据寄存器写入数据,控制灯的亮灭

//  write(fd, buf, size); //虽然buf就是应用空间的数据存储的地址,但是驱动不要直接通过指针访问,copy_from_user会判断传进来的地址是否为空,做出错误处理.
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int ret = -1;
	int value;
	
	printk("--------^_*  %s-------\n", __FUNCTION__);
	
	//设计led的控制功能--灭亮---用户只要传递1(亮)/0(灭)
	

	//参数1--内核驱动中的某个空间--用于存放用户空间拷贝过来的数据
	//参数2--用户传递数据对应的地址
	//参数3--传递数据的个数
	//返回值: 返回没有拷贝成功的数据个数: 大于0表示出错(返回一个负数),0表示正确
    
	ret = copy_from_user(&value,  buf, count);
	if(ret > 0)
	{
		printk("copy_from_user error\n");
		return -EFAULT;
	}

	switch(value){
		case 0:
			// 全灭
			*gpc0dat &= ~(0x3<<3);
			break;
		case 1:	//全亮
			*gpc0dat |= (0x3<<3);
			break;

		case 2: // 2--led1_on 
			*gpc0dat &= ~(0x3<<3);
			*gpc0dat |= (0x1<<3);
			break;
		case 3: // 3_led2_on
			*gpc0dat &= ~(0x3<<3);
			*gpc0dat |= (0x2<<3);
			break;

		case 4:	// 4_led1_off
			*gpc0dat &= ~(0x1<<3);
			break;
			
		case 5: // 5_led2_off
			*gpc0dat &= ~(0x1<<4);
			break;
	}

	

	//返回拷贝功能的数据个数
	return count;
}

 在close中最好做一下灭灯操作

int led_drv_close(struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	//灭灯
	*gpc0dat &= ~(0x3<<3);
	
	return 0;
}

 写一个应用层app控制灯的亮灭


#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

#define LED1_ON  2
#define LED2_ON  3
#define LED1_OFF  4
#define LED2_OFF  5


int main(int argc, char *argv[])
{

	int on;
	int ret;
	
	//直接将驱动模块当做文件来操作
	int fd = open("/dev/led0", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

	//先全灭
	on = 0;
	ret = write(fd, &on, 4);
	if(ret < 0)
	{
		perror("write off ");
		exit(1);
	}

	while(1)
	{

		//led1 on 
		on = LED1_ON;
		ret = write(fd, &on, 4);
		if(ret < 0)
		{
			perror("write on ");
			exit(1);
		}
		sleep(1);
		

		on = LED1_OFF;
		ret = write(fd, &on, 4);
		if(ret < 0)
		{
			perror("write on ");
			exit(1);
		}
		sleep(1);
	
		on = LED2_ON;
		ret = write(fd, &on, 4);
		if(ret < 0)
		{
			perror("write on ");
			exit(1);
		}
		sleep(1);

		on = LED2_OFF;
		ret = write(fd, &on, 4);
		if(ret < 0)
		{
			perror("write off ");
			exit(1);
		}
		sleep(1);


		on = 1;
		ret = write(fd, &on, 4);
		if(ret < 0)
		{
			perror("write off ");
			exit(1);
		}
		sleep(1);

		on = 0;
		ret = write(fd, &on, 4);
		if(ret < 0)
		{
			perror("write off ");
			exit(1);
		}
		sleep(1);
	}
	

	close(fd);


}

 完整led灯驱动代码

// 1, 添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>

#include <asm/io.h>
#include <asm/uaccess.h>

static unsigned int led_major = 266;
static char *led_name = "led_device";
static struct class *led_cls;

static volatile unsigned long *gpc0con = NULL;
static volatile unsigned long *gpc0dat = NULL;

int led_drv_open (struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	//直接对硬件进行操作
	//配置成输出功能
	*gpc0con &= ~(0xff<<12);
	*gpc0con |= (0x11<<12);



	return 0;
}

//  read(fd, buf, size);
ssize_t led_drv_read(struct file *filp, char __user *buf,  size_t count, loff_t *fpos)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);
	return 0;
}

//  write(fd, buf, size); //虽然buf就是应用空间的数据存储的地址,但是驱动不要直接通过指针访问,copy_from_user会判断传进来的地址是否为空,做出错误处理.
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int ret = -1;
	int value;
	
	printk("--------^_*  %s-------\n", __FUNCTION__);
	
	//设计led的控制功能--灭亮---用户只要传递1(亮)/0(灭)
	

	//参数1--内核驱动中的某个空间--用于存放用户空间拷贝过来的数据
	//参数2--用户传递数据对应的地址
	//参数3--传递数据的个数
	//返回值: 返回没有拷贝成功的数据个数: 大于0表示出错(返回一个负数),0表示正确
    
	ret = copy_from_user(&value,  buf, count);
	if(ret > 0)
	{
		printk("copy_from_user error\n");
		return -EFAULT;
	}

	switch(value){
		case 0:
			// 全灭
			*gpc0dat &= ~(0x3<<3);
			break;
		case 1:	//全亮
			*gpc0dat |= (0x3<<3);
			break;

		case 2: // 2--led1_on 
			*gpc0dat &= ~(0x3<<3);
			*gpc0dat |= (0x1<<3);
			break;
		case 3: // 3_led2_on
			*gpc0dat &= ~(0x3<<3);
			*gpc0dat |= (0x2<<3);
			break;

		case 4:	// 4_led1_off
			*gpc0dat &= ~(0x1<<3);
			break;
			
		case 5: // 5_led2_off
			*gpc0dat &= ~(0x1<<4);
			break;
	}

	

	//返回拷贝功能的数据个数
	return count;
}


int led_drv_close(struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	//灭灯
	*gpc0dat &= ~(0x3<<3);
	
	return 0;
}

//文件操作对象---为用户程序提供文件io接口的对象
const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_drv_open,
	.read = led_drv_read,
	.write = led_drv_write,
	.release =led_drv_close,
};

// 3,实现驱动模块加载/卸载入口函数
static int led_drv_init(void)
{
	
	printk("--------^_*  %s-------\n", __FUNCTION__);
	int ret;

	// 申请主设备号, 默认次设备号为0
	// 参数1---指定的主设备号--就是一个整数,选255以上
	//参数2--设备的描述--自定义的字符串
	//参数3--设备驱动的文件操作对象
	//返回值: 错误为负数,正确为0
	ret = register_chrdev(led_major, led_name,  &led_fops);
	if(ret < 0)
	{
		printk("register_chrdev error\n");
		return ret;
	}

	// 自动创建设备节点
	//创建设备文件所属类别
	//参数1--拥有者--当前模块
	//参数2--类别的名字--自定义
	//返回值---返回一个指针
	led_cls = class_create(THIS_MODULE, "led_cls");

	//创建设备文件
	//参数1--所属类别
	//参数2--当前创建的设备文件的父类是谁--一般NULL
	//参数3--关联的设备号
	//参数4--当前设备文件的私有数据--一般NULL
	//参数5/6--设定设备文件的名字
	device_create(led_cls, NULL, MKDEV(led_major, 0), NULL, "led0");  // led0



	
	//参数1--硬件的物理地址
	//参数2--映射的地址长度
	//返回值---映射之后的虚拟地址
	gpc0con = ioremap(0xE0200060,  8);
	gpc0dat = gpc0con + 1;


	

	return 0;
}

static void led_drv_exit(void)
{

	printk("--------^_*  %s-------\n", __FUNCTION__);
	//去映射
	//参数1---映射之后的虚拟地址
	iounmap(gpc0con);
	
	//参数1--所属类别
	//参数2--关联的设备号
	device_destroy(led_cls, MKDEV(led_major, 0));

	//参数1--所属类别
	class_destroy(led_cls);
	
	// 参数1---指定的主设备号--就是一个整数,选255以上
	//参数2--设备的描述--自定义的字符串
	unregister_chrdev(led_major,  led_name);

}

// 2,声明驱动模块加载/卸载入口函数
module_init(led_drv_init);
module_exit(led_drv_exit);

//在驱动装载的时候传递参数 insmod led_drv.ko  led_major=270 led_name="fs210_led"
module_param(led_major, int, 0644);
module_param(led_name, charp, 0644);


// 4, 添加gpl认证
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BaJie <[email protected]>");
MODULE_DESCRIPTION("Led driver for s5pv210");











猜你喜欢

转载自blog.csdn.net/weixin_42471952/article/details/81587059