工作需要常用的平台总线与设备树的驱动框架

可以一个驱动连多个设备
led-char.c

#include "led.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>

#define LED_DEVICE_MAJOR 230
#define LED_DEVICE_MINOR 0
#define LED_ON_CMD  0x11
#define LED_OFF_CMD 0x22

#define LED_CON_OFFSET  0
#define LED_DAT_OFFSET  4

MODULE_LICENSE("GPL v2");

//前面都是在应用层打开的时候会运行

struct led_device *pled;

void led_init(struct led_device *pled)
{
	//gpio ouput mode
	int pin = pled->des.pin;
	int bits = pled->des.bits;
	int mode = pled->des.mode;
	int mask = (1 << bits) - 1;//计算小方法
	
	int regval = readl(pled->regaddr + LED_CON_OFFSET);
	//readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )。 
	regval = regval & ~(mask << (pin * bits));//修改IO空间里面的值
	regval = regval |  (mode << (pin * bits));
	writel(regval,pled->regaddr + LED_CON_OFFSET);
	//往内存映射的 I/O 空间上写数据,wirtel()  I/O 上写入 32 位数据 (4字节)。 
	return;
}

void led_on(struct led_device *pled)
{
	//都是在虚拟地址上,在驱动当中映射过,因此需要用虚拟地址
	int level = pled->des.level;
	int pin = pled->des.pin;
	int regval = readl(pled->regaddr + LED_DAT_OFFSET);
	regval = regval & ~(0x1 << pin);
	regval = regval | (level << pin);
	writel(regval,pled->regaddr + LED_DAT_OFFSET);
	return;
}

void led_off(struct led_device *pled)
{
	int level = !pled->des.level;
	int pin = pled->des.pin;
	int regval = readl(pled->regaddr + LED_DAT_OFFSET);
	regval = regval & ~(0x1 << pin);
	regval = regval | (level << pin);
	writel(regval,pled->regaddr + LED_DAT_OFFSET);

	return;
}

//当应用层open的时候,通过设备地址找到设备的inode(只要是文件都会有inode),同时也会给文件
//创建file对象,通过inode里面的信息得到是字符设备后,寻找对应的字符设备,然后将cdev里面的
//地址拷贝到inode当中,也会将驱动当中的file_operation拷贝到文件虚拟系统层文件对象file当中,
//内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。因此上层应用必须传fd、
//这个参数,只有open的时候是不需要的
//文件最多打开1024次,每次打开都会重新找设备
static int led_device_open(struct inode *inode, struct file *file)
{
	//inode里面记录了cdev的首地址,通过此函数可以推算出led_device的首地址
	struct led_device *pled = container_of(inode->cdev,struct led_device,led_cdev);
	file->private_data = pled;
	printk("led device open\n");
	
	led_init(pled);
	
	return 0;
}

static int led_device_close( struct inode *inode, struct file *file )
{
	printk("led device close\n");

	iounmap(pled->regaddr);//关闭映射
	
	return 0;
}
//上次应用的ioctl函数的第二个参数会传入cmd
static long led_device_ioctl(struct file *file, unsigned int cmd, 
							 unsigned long arg)
{
	struct led_device *pled = file->private_data;
	switch(cmd){
	case LED_ON_CMD:
		printk("led cmd on\n");
		led_on(pled);
		break;

	case LED_OFF_CMD:
		printk("led cmd off\n");
		led_off(pled);
		break;
	}

	return 0;
}
//从这个结构体之下都是在加载驱动或者卸载驱动的时候有关系
//设备操作的函数接口对象
static const struct file_operations led_device_ops = {
	.owner		= THIS_MODULE,
	.open		= led_device_open,
	.release	= led_device_close,
	.unlocked_ioctl	= led_device_ioctl,
};

//手动注册字符设备
struct led_device *register_led_chardev(void)
{	
	int err;
	static int devnum = 0;
	struct led_device *pled;
	
	printk("led driver init\n");

	//创建cdev对象
	pled = kmalloc(sizeof(*pled),GFP_KERNEL);
	if(!pled){
		printk("Fail to kmalloc\n");
		err =  -ENOMEM;
		goto err_kmalloc;
	}

	//pled->led_cdev.ops = &led_device_ops;
	//这个作用是将实现的函数放到cdev成员当中,最终会将拷贝到文件虚拟
	//层当中的file_operation当中,实现向上层提供设备的操作函数接口
	cdev_init(&pled->led_cdev,&led_device_ops);
	//注册设备号
	pled->devid = MKDEV(LED_DEVICE_MAJOR,LED_DEVICE_MINOR + devnum);
	err = register_chrdev_region(pled->devid,1,"led-device");
	if(err){
		printk("Fail to register_chrdev_region\n");
		goto err_register_chrdev_region;
	}

	devnum ++;
	//根据设备号来添加字符设备
	err = cdev_add(&pled->led_cdev,pled->devid,1);
	if(err){
		printk("Fail to cdev_add\n");
		goto err_cdev_add;
	}

	//创建类:在/sys/class/目录  因为只能创建一次因此需要放到
	//模块初始化函数当中比较合适
#if 0
	if(devnum){
		pled->cls = class_create(THIS_MODULE, "fs4412-leds");
		if (IS_ERR(pled->cls)){
			printk("Fail to class_create\n");
			err = PTR_ERR(pled->cls);
			goto err_class_create;
		}
	}
#endif
	//创建设备:在/sys/class/目录/设备,第五个参数决定设备文件名,设备树上面那个只是为了匹配驱动
	//这里的名字一定要与用户空间保持一致
	pled->dev = device_create(led_cls,NULL,pled->devid,NULL,"led%d",devnum);
	if(IS_ERR(pled->dev)){
		printk("Fail to device_create\n");
		err = PTR_ERR(pled->dev);
		goto err_device_create;
	}

	return pled;

err_device_create:
	cdev_del(&pled->led_cdev);
err_cdev_add:
	unregister_chrdev_region(pled->devid,1);
err_register_chrdev_region:
	kfree(pled);
err_kmalloc:
	return ERR_PTR(err);
}

void unregister_led_chardev(struct led_device *pled)
{
	printk("led driver exit\n");
#if 0
	device_destroy(pled->cls,pled->devid);
#endif
	cdev_del(&pled->led_cdev);
	unregister_chrdev_region(pled->devid,1);
	kfree(pled);
	
	return;
}

led-driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "led.h"
#include <linux/io.h>

//平台总线上面挂驱动,
MODULE_LICENSE("GPL v2");

struct class *led_cls;

int of_parse_led_dt(struct device_node *np,struct led_des *info)
{
	int err;
	int value;
	//手动解析内核未规定的。这些都是自己在设备树上规定的
	err = of_property_read_u32(np, "pin", &value);
	if(err) {
		printk("fail to get property pin\n");
		return err;
	}

	printk("pin : %d\n",value);
	info->pin = value;

	err = of_property_read_u32(np, "bits", &value);
	if(err) {
		printk("fail to get property bits\n");
		return err;
	}
	info->bits = value;
	
	printk("bits : %d\n",value);

	err = of_property_read_u32(np, "mode", &value);
	if(err) {
		printk("fail to get property mode\n");
		return err;
	}
	info->mode = value;
	printk("mode : %d\n",value);

	err = of_property_read_u32(np, "level", &value);
	if(err) {
		printk("fail to get property level\n");
		return err;
	}
	info->level = value;
	printk("level : %d\n",value);

	return 0;
}

static int led_probe(struct platform_device *pdev)
{
	int err;
	void *regaddr;
	struct led_des info;
	struct resource *res;
	struct led_device *pled;//在另外一个文件可以定义指针(4字节)
	
	printk("led probe,device name:%s\n",pdev->name);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//

	if(!res){
		printk("Fail to platform_get_resource\n");
		return -ENODEV;
	}

	printk("resource reg addr : %#x\n",(unsigned int)res->start);


	//把物理地址映射成虚拟地址
	regaddr = devm_ioremap(&pdev->dev,res->start,resource_size(res));

	if(!regaddr){
		printk("fail to devm_ioremap\n");
		return -ENOMEM;
	}
	//解析设备树节点的属性
	err = of_parse_led_dt(pdev->dev.of_node,&info);

	if(err){
		printk("fail to of_parse_led_dt\n");
		return err;
	}
	//注册字符设备
	pled = register_led_chardev();
	if(IS_ERR(pled)){
		printk("fail to register_led_chardev\n");
		return PTR_ERR(pled);
	}
	//记录设备信息
	pled->regaddr = regaddr;
	pled->des = info;
	
	//把自定义设备数据放到平台设备结构体当中
	platform_set_drvdata(pdev, pled);
	return 0;
}

static int led_remove(struct platform_device *pdev)
{	//通过平台设备也能获得相应的自定义设备
	struct led_device *pled = platform_get_drvdata(pdev);
	unregister_led_chardev(pled);//注销设备
	return 0;
}

static const struct of_device_id led_of_match[] = {
		{.compatible = "led1"},
		{.compatible = "led2"},
		{.compatible = "led3"},
		{.compatible = "led4"},
		{/*Sentinel */} //这东西不能丢
};

MODULE_DEVICE_TABLE(of, led_of_match);
struct platform_driver led_driver = {
	.probe = led_probe, //加载的时候也就是匹配成功自动调用
	.remove = led_remove,//卸载的时候也调用
	.driver = {
		.name = "fs4412-led",
		.owner = THIS_MODULE,
		.of_match_table = led_of_match,
	},
};

int led_driver_init(void)
{	
	//创建设备的类,之前是放到添加设备之前。现在提前建好,是为了与
	//多设备进行匹配。防止多次建立类和多次销毁类
	led_cls = class_create(THIS_MODULE, "fs4412-leds");
	if (IS_ERR(led_cls)){
		printk("Fail to class_create\n");
		return PTR_ERR(led_cls);
	}
 	platform_driver_register(&led_driver);
	return 0;
}

void led_driver_exit(void)
{
	platform_driver_unregister(&led_driver);
	class_destroy(led_cls);
	return;
}

module_init(led_driver_init);
module_exit(led_driver_exit);

led-app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define LED_ON_CMD 0x11
#define LED_OFF_CMD 0x22
#define MAX_LEDS 4

int main(int argc, const char *argv[])
{
	int fds[MAX_LEDS];
	int i;
	char device_name[1024];
	
	//,这里的设备名字要与创建的设备名保持一致才行,并不是设备树上的名字
	for (i = 0;i < MAX_LEDS; ++i){
		sprintf(device_name,"/dev/led%d",i + 1);
		fds[i] = open(device_name,O_RDWR);
		if(fds[i] < 0){
			perror("fail to open");
			return -1;
		}

	}

	while(1){
		for(i = 0;i < MAX_LEDS;i++){
			ioctl(fds[i],LED_ON_CMD);
			sleep(1);
			ioctl(fds[i],LED_OFF_CMD);
			sleep(1);
	}
	
}
	for(i = 0;i < MAX_LEDS;i++){
		close(fds[i]);
	}
	return 0;
}

led.h

#ifndef __LED_HEAD_H__
#define __LED_HEAD_H__


#include <linux/types.h>
#include <linux/cdev.h>

struct led_des
{
	int pin;
	int bits;
	int mode;
	int level;

};

struct led_device{
	dev_t devid;
	void  *regaddr;
	struct led_des des;
	struct device *dev;
	struct cdev  led_cdev;
};

extern struct class *led_cls;//led-char.c和led-driver.c都需要用
extern struct led_device *register_led_chardev(void);
extern void unregister_led_chardev(struct led_device *pled);


#endif

Makefile文件

ifeq ($(KERNELRELEASE),)

X86_KERNEL_BUILD = /lib/modules/$(shell uname -r)/build
ARM_KERNEL_BUILD = /home/linux/fs4412/kernel/linux-3.14
MODULE_PATH = $(shell pwd)

x86_Module:
	make -C $(X86_KERNEL_BUILD) M=$(MODULE_PATH) modules
arm_module:
	make -C $(ARM_KERNEL_BUILD) M=$(MODULE_PATH) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- 
	cp led-char-driver.ko /home/linux/fs4412/rootfs
	arm-none-linux-gnueabi-gcc led-app.c -o /home/linux/fs4412/rootfs/led-app
clean:
	make -C $(X86_KERNEL_BUILD) M=$(MODULE_PATH) clean

else
	obj-m = led-char-driver.o
	led-char-driver-objs = led-char.o led-driver.o
endif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值