养老院自助饮水机(字符设备驱动)

发布时间:2023年12月22日

目录

1、项目背景

2、驱动程序

2.1 三层架构

2.2 驱动三要素

2.3 字符设备驱动

?2.3.1 驱动模块

2.3.2 应用层

3、设计实现

3.1 项目设计

3.2 项目实现

3.2.1 驱动模块代码

3.2.2 用户层代码?

4、功能特性

5、技术分析?

6. 总结与未来展望


1、项目背景

? ? ? ? 养老院的老人在生活中难免有所不方便,为了便捷老年人的生活,使用字符设备驱动编写了一个自助饮水机项目。该饮水机与普通饮水机的区别在于拥有更复杂的功能;饮水机拥有可以自行输入金额,然后程序开始运行。运行期间常亮绿灯,可以点击按钮暂停,灯颜色改变;一直到余额不足,然后蜂鸣器提示用户。

? ? ? ? 提示:该项目的基础是在”系统移植“之上,对于系统移植步骤及说明:

????????http://t.csdnimg.cn/O1uMi

?

2、驱动程序

2.1 三层架构

图2-1 结构框图

?????????对于三层的说明,想必大家都不陌生。内核层既需要去通过转换地址映射到内核,使得内核可以通过虚拟地址去操作底层的硬件设备;也需要使用虚拟文件系统向上层提供一个用户能够操作的文件设备。内核层作为中间枢纽,能提供如此的功能,归功于驱动程序,见图2-2。

?

2.2 驱动三要素

//驱动模块三要素 入口、出口、许可证
#include <linux/init.h>
#include <linux/module.h>
//入口
static int hello_init(void)
{
 return 0;
}
//出口
static void hello_exit(void)
{
}
module_init(hello_init);
module_exit(hello_exit);
//许可证
MODULE_LICENSE("GPL");


//一个最简单、最基本的驱动程序

?

2.3 字符设备驱动

图2-2 字符设备驱动框图

?

?2.3.1 驱动模块

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
unsigned int major=0;
#define CNAME "hello"
ssize_t mycdev_read (struct file *file, char __user *ch, size_t len, loff_t *lodd)
{
  printk("this is read\n");
  return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *buf, size_t len, loff_t *lodd)
{
   printk("this is write\n");
  return 0;
}
int mycdev_open (struct inode *ino, struct file *file)
{
   printk("this is open\n");
  return 0;
}
int mycdev_release (struct inode *ino, struct file *file)
{
   printk("this is close\n");
  return 0;
}
const struct file_operations fops=
{
  .read=mycdev_read,
  .write=mycdev_write,
  .open=mycdev_open,
  .release=mycdev_release,
};//该结构体主要用于像上层的用户层,提供调用的接口函数
static int __init hello_init(void)
{
  major=register_chrdev(major,CNAME,&fops);//注册一个字符设备驱动
  if(major<0)
  {
   printk("register chrdev error\n");
   return major;
  }
  return 0;
}
static void __exit hello_exit(void)
{
  unregister_chrdev(major,CNAME);
  printk("bai bai\n");
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
MODULE_LICENSE("GPL");//返回值

?

2.3.2 应用层

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
char buf[128]={0};
int main(int argc,const char *argv[])
{
  int fd;
  fd=open("./hello",O_RDWR);
  if(fd==-1)
  {
    perror("open error");
    return -1;
  }
  write(fd,buf,sizeof(buf));//对应着设备驱动模块的mycdev_write 
  read(fd,buf,sizeof(buf));//对应着设备驱动模块的mycdev_read
  close(fd);
  return 0;
}

? ? ? ? ?所以,具体应用层所能干的,就是调用接口,而接口函数里面做的事情,则由我们驱动开发人员去编写,当然,此驱动模块还没有去操作实际的硬件设备,对于想要操作底层的硬件设备,则需要去看板子的原理图,查看外设的地址映射等。(以上驱动模块并未自动创建设备文件,执行完还需自行mknod,命令格式如下:sudo mknod hello c/b(c 代表字符设备 b代表块设备)主设备号 次设备号)

3、设计实现

3.1 项目设计

? ? ? ? 对于该项目组成,同样是上述组成,不过更加复杂,具体的我就不再引出了。如果有任何问题,可以联系博主。

图3-1 驱动模块的作用

3.2 项目实现

3.2.1 驱动模块代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>


#define CNAME "NEW_C"
unsigned int major=0;
struct class *cls;
struct device *dvs;
char kbuf[64]={0};
int money=0;
int devlen=0;

#define RED_BASE 0xC001A000
#define GRE_BASE 0xC001E000
#define BLU_BASE 0xC001B000
#define BEEP_BASE 0xC001C000
unsigned int *red_base=NULL;
unsigned int *gre_base=NULL;
unsigned int *blu_base=NULL;
unsigned int *beep_base=NULL;


#define GPIONO(m,n) m*32+n  //计算gpio号
#define GPIO_B8  (GPIONO(1,8))//计算按键gpio号
#define GPIO_B16  (GPIONO(1,16)) //计算gpio号
struct timer_list mytimer;//声明结构体
struct timer_list mytimer1;
struct timer_list mytimer2;
int gpiono[] = {GPIO_B8,GPIO_B16};//数组内存入两个按键的软中断号
char *irqname[] = {"interrupt-b8","interrupt-b16"};//中断的名字
void key_irq_timer_handle(unsigned long data)//定时器中断处理函数
{
	int status_b8  = gpio_get_value(GPIO_B8);//读取gpiob8数值
	int status_b16 = gpio_get_value(GPIO_B16);//读取gpiob16数值
	if(status_b8 == 0){//如果等于0表示按下,执行打印函数
		*blu_base |= 1<<12;
		mod_timer(&mytimer1,jiffies+1000);
		printk("left  button down............\n");
	}
	if(status_b16 == 0){//如果等于0表示按下,执行打印函数
		*blu_base &= ~(1<<12);
		del_timer(&mytimer1);
		printk("right button down#############\n");
	}
}

void key_irq_timer_handle1(unsigned long data)//定时器中断处理函数
{
	kbuf[11]=kbuf[11]-1;
	if(kbuf[11]=='/')
	{
		kbuf[10]=kbuf[10]-1;
		kbuf[11]=kbuf[11]+10;
	}
	mod_timer(&mytimer1, jiffies + 1000);
}

void key_irq_timer_handle2(unsigned long data)//定时器中断处理函数
{
	*beep_base &= ~(1<<14);
}
irqreturn_t farsight_irq_handle(int num, void *dev)//按键产生的中断处理函数
{
	mod_timer(&mytimer,jiffies+10);//开启定时器。只要触发就重新赋值,用来消抖
	return IRQ_HANDLED; 
}

ssize_t my_dev_read(struct file *file, char __user *ubuf, size_t len, loff_t *loff)
{
	if(len >sizeof(kbuf))
	{
		len =sizeof(kbuf);
	}
	printk("%c %c\n",kbuf[10],kbuf[11]);
	devlen = copy_to_user(ubuf,kbuf,len);
	if(devlen)
	{
		printk("copy to user is err\n");
		return devlen;
	}
	return 0;
}

ssize_t my_dev_write(struct file *file, const char __user *ubuf, size_t len, loff_t *loff)
{
	if(len > sizeof (kbuf))
	{
		len=sizeof(kbuf);
	}
	devlen = copy_from_user(kbuf,ubuf,len);
	if(devlen)
	{
		printk("copy from user is err\n");
		return devlen;
	}
	money=(kbuf[10]-48)*10+(kbuf[11]-48);
	printk("This is my char_dev_write %d\n",money);
	return 0;
}

int my_dev_open(struct inode *inode, struct file *file)
{
	return 0;
}

int my_dev_release(struct inode *inode, struct file *file)
{
	*beep_base |= 1<<14;
	mod_timer(&mytimer2, jiffies + 1000);
	return 0;
}

const struct file_operations fops=
{
	.read=my_dev_read,
	.write=my_dev_write,
	.open=my_dev_open,
	.release=my_dev_release,
	// .unlocked_ioctl=my_unlocked_ioctl,
};

static int __init hello_init(void)
{
	major=register_chrdev(major,CNAME,&fops);
	if(major<0)
	{
		printk("register_chrdev is error\n");
		return major;
	}
	red_base=ioremap(RED_BASE,36);
	gre_base=ioremap(GRE_BASE,36);
	blu_base=ioremap(BLU_BASE,36);
	beep_base=ioremap(BEEP_BASE,36);
	if(red_base==NULL || gre_base==NULL)
	{
		printk("ioremap is err\n");
		return -ENOMEM;
	}
	*red_base &=~(1<<28);
	*(red_base+1) |=1<<28;
	*(red_base+9) &=~(3<<24);

	*gre_base &= ~(1<<13);
	*(gre_base+1) |= (1<<13);
	*(gre_base+8) &=~(3<<26);

	*(blu_base+1) |= 1<<12;
	*(blu_base+8) |=(2<<24);

	*beep_base &= ~(1<<14);
	*(beep_base+1) |= 1<<14;
	*(beep_base+8) &= ~(1<<29);
	*(beep_base+8) |= 1<<28;

	cls = class_create(THIS_MODULE, CNAME);
	if(IS_ERR(cls)){
		printk("class create is err\n");
		return PTR_ERR(cls);
	}
	dvs=device_create(cls, NULL, MKDEV(major,0), NULL, CNAME);
	if(IS_ERR(dvs))
	{
		printk("device create is err\n");
		return PTR_ERR(dvs);
	}
	int ret,i;
	mytimer.expires = jiffies + 10;//时间
	mytimer.function = key_irq_timer_handle;//定时器中断处理函数
	mytimer.data = 0;//参数
	init_timer(&mytimer);//将定时器信息写入进行初始化
	add_timer(&mytimer);//开启一次定时器
	mytimer1.expires = jiffies + 1000;//时间
	mytimer1.function = key_irq_timer_handle1;//定时器中断处理函数
	mytimer1.data = 0;//参数
	init_timer(&mytimer1);//将定时器信息写入进行初始化
	add_timer(&mytimer1);//开启一次定时器
	mytimer2.expires = jiffies + 1000;//时间
	mytimer2.function = key_irq_timer_handle2;//定时器中断处理函数
	mytimer2.data = 0;//参数
	init_timer(&mytimer2);//将定时器信息写入进行初始化
	add_timer(&mytimer2);//开启一次定时器
	for(i=0;i<ARRAY_SIZE(gpiono); i++)
	{//这里用for主要目的之申请两个中断
		ret = request_irq(gpio_to_irq(gpiono[i]),farsight_irq_handle,IRQF_TRIGGER_FALLING,irqname[i],NULL);//中断申请 参数:软中断号  中断执行函数  下降沿触发 中断的名字
		if(ret){
			printk("request irq%d error\n",gpio_to_irq(gpiono[i]));//申请失败提示
			return ret;
		}
	}
	return 0;
}

static void __exit hello_exit(void)
{
	int i;
	for(i=0;i<ARRAY_SIZE(gpiono); i++){//注销掉中断
		free_irq(gpio_to_irq(gpiono[i]),NULL);
	}
	del_timer(&mytimer);//注销掉定时器
	del_timer(&mytimer1);//注销掉定时器
	del_timer(&mytimer2);//注销掉定时器
	class_destroy(cls);
	device_destroy(cls,MKDEV(major,0));
	iounmap(red_base);
	iounmap(gre_base);
	iounmap(blu_base);
	iounmap(beep_base);
	unregister_chrdev(major,CNAME);
	
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

3.2.2 用户层代码?

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

#include "head.h"

char buf[64] ={0};
char buff[128]={0};
struct tm *tp;
time_t t;
int main(int argc, char *argv[])
{
	int fd = open("/dev/NEW_C", O_RDWR);
	if (fd < 0)
	{
		perror("open NEW_C err\n");
		return -1;
	}
	int fds=open("./history.txt",O_APPEND|O_CREAT|O_WRONLY,0666);
	if(fds<0)
	{
		perror("open history err\n");
		return -1;
	}
	sprintf(buf,"0X55%s%s0XFF",argv[1],argv[2]);
	write(fd,buf,sizeof(buf));
	int readlen=0;
	while(1)
	{
		time(&t);
 		tp = localtime(&t);
		if((buf[10]=='0') && buf[11]=='0')
		{
			goto loop;
		}
		readlen = read(fd,buf,sizeof(buf));
		sprintf(buff,"%4d-%02d-%02d %02d:%02d:%02d : 账户:%c%c\t剩余金额:%c%c\n",tp->tm_year+1900,tp->tm_mon+1,
													tp->tm_mday,tp->tm_hour,tp->tm_min,tp->tm_sec,buf[6],buf[7],buf[10],buf[11]);
		write(fds,buff,strlen(buff));
		printf("账户:%c%c\t剩余金额:%c%c\n",buf[6],buf[7],buf[10],buf[11]);
		sleep(1);
	}
loop:
	close(fd);
	return 0;
}

?

4、功能特性

上述项目的功能大体如下:

? ? ? ? 用户可自行输入金额。

? ? ? ? 金额定时减少,出水时亮绿灯

? ? ? ? 用户可点击按钮实现暂停接水,并且余额不会减少。

? ? ? ? 余额归零,代表出水完毕,蜂鸣器响提示用户。

? ? ? ? 本地日志会记录用户的购买记录及详细信息。

?

5、技术分析?

? ? ? ? 字符设备驱动编写:向上提供接口,向下控制硬件。

? ? ? ? 定时器使用:按键消抖,水量控制,蜂鸣器控制。

? ? ? ? 中断使用:按键触发中断。

? ? ? ? 文件io:保存用户消费日志。

?

6. 总结与未来展望

? ? ? ??字符设备驱动是操作系统中的一种设备驱动程序,用于管理和控制字符设备。在Linux系统中,字符设备驱动通常使用字符设备接口进行开发。驱动程序需要定义设备结构体、注册设备、实现文件操作函数等,以提供稳定高效的设备访问接口。除了基本的功能,驱动程序还可以实现多个进程访问同一个设备、内存映射、虚拟文件系统、设备驱动模块化、调试信息输出等特性。

????????字符设备驱动技术在计算机领域有着重要的意义和影响。首先,它为应用程序提供了访问字符设备的标准接口,使得应用程序能够方便地与设备进行数据交互,从而促进了各种应用软件的开发和推广。其次,字符设备驱动技术也支持多种设备类型和多种操作系统平台,使得设备之间的互通性得到了提升,为设备互联和智能化提供了先决条件。

????????未来,随着物联网技术的不断发展和普及,字符设备驱动技术将会得到更广泛的应用和推广。特别是在智能家居、工业自动化、医疗健康等领域,字符设备驱动技术将发挥更大的作用和贡献。同时,随着技术的不断进步和创新,字符设备驱动技术也将会不断完善和优化,以满足日益增长的设备互联需求和应用场景。

? ? ? ? 感谢大家的阅读,欢迎留言指教。

文章来源:https://blog.csdn.net/qq_52305085/article/details/135144971
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。