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

news2025/2/4 10:42:12

目录

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1328520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSC访问学者/博士后/联培博士如何规划申请时间

申请国家留学基金委&#xff08;CSC&#xff09;公派访问学者/博士后/联合培养博士等出国项目&#xff0c;邀请函是必要条件&#xff0c;需提前准备。那么&#xff0c;何时提出申请比较合适&#xff1f;获得邀请函需要多长时间&#xff1f;根据知识人网多年的申请经验&#xff…

电影《名侦探柯南:黑铁的鱼影》观后感

上周看了电影《名侦探柯南&#xff1a;黑铁的鱼影》,整体故事的话,就是柯南他们团队一起去岛屿去上参观&#xff0c;“正好”碰上了“海上信标案件”&#xff0c;在柯南的电影里&#xff0c;用“正好”多少有些反讽的意味&#xff0c;因为柯南好像走到哪&#xff0c;都正好碰到…

Python实验报告十一、自定义类模拟三维向量及其运算

一、实验目的&#xff1a; 1、了解如何定义一个类。 2、了解如何定义类的私有数据成员和成员方法。 3、了解如何使用自定义类实例化对象。 二、实验内容&#xff1a; 定义一个三维向量类&#xff0c;并定义相应的特殊方法实现两个该类对象之间的加、减运算&#xff08;要…

Vue中为什么data属性是一个函数而不是一个对象?(看完就会了)

文章目录 一、实例和组件定义data的区别二、组件data定义函数与对象的区别三、原理分析四、结论 一、实例和组件定义data的区别 vue实例的时候定义data属性既可以是一个对象&#xff0c;也可以是一个函数 const app new Vue({el:"#app",// 对象格式data:{foo:&quo…

优化企业员工管理的利器——ADManager Plus

在当今数字化的商业环境中&#xff0c;企业员工管理是组织成功运营的关键组成部分。为了提高效率、确保安全性和满足法规合规性要求&#xff0c;企业需要一种强大的工具来简化和集中管理其活跃目录&#xff08;Active Directory&#xff09;环境。ADManager Plus作为一款功能丰…

利用ffmpeg cv2取h265码流视频(转换图片灰屏问题解决)

利用海康威视相机拍出来的视频是H265格式的&#xff0c;相比于常规的H264编码&#xff0c;压缩率更高&#xff0c;但因此如果直接用正常取流方法读取&#xff0c;会出现无法读取的情况 1. 如图h265码流取出图片为灰屏 2 、解决灰屏问题 import subprocess import cv2# 将h265流…

天津web前端就业培训班,Web机构选择重点

Web前端培训是目前非常热门的培训领域之一。很多领域都会涉及到web前端开发&#xff0c;比如传统互联网、房地产、金融、游戏、影视传媒等行业都需要web前端技术的支持。越来越多的企业和个人也需要建立自己的网站和移动应用程序&#xff0c;因此市场对web前端工程师的需求是非…

RabbitMQ笔记(基础篇)

RabbitMQ笔记_基础篇 MQ基本概念1. MQ概述2. MQ的优势和劣势2.1 优势☆2.2 劣势2.3 使用 MQ 需要满足什么条件呢&#xff1f; 3. 常见的MQ产品 RabbitMQ基本介绍1. RabbitMQ 基础架构2. RabbitMQ 中的相关概念3. RabbitMQ的6 种工作模式☆4. AMQP 和 JMS4.1 AMQP4.2 JMS4.3 AMQ…

文件夹数据同步工具 Sync Folders Pro mac支持选项

Sync Folders Pro for Mac 是一款功能强大的文件夹同步工具&#xff0c;旨在帮助用户在 Mac 计算机和移动设备之间创建双向同步。这款软件支持各种文件系统和设备&#xff0c;如 iPhone&#xff0c;iPad&#xff0c;iPod&#xff0c;Android 等。通过这款软件&#xff0c;用户可…

[c]超半的数

题目意思很简单&#xff0c;就是输入一组数据&#xff0c;输出出现次数过半的数 根据这个题我们也可以写出另一个题&#xff0c;&#xff08;题2&#xff09;&#xff08;统计一组数据中各个数出现的次数&#xff09; 下面附上两个题代码 题1&#xff1a; #include<stdio.…

【C语言】自定义类型:结构体深入解析(二)结构体内存对齐宏offsetof计算偏移量结构体传参

文章目录 &#x1f4dd;前言&#x1f320; 结构体内存对齐&#x1f309;内存对齐包含结构体的计算&#x1f320;宏offsetof计算偏移量&#x1f309;为什么存在内存对⻬?&#x1f320; 结构体传参&#x1f6a9;总结 &#x1f4dd;前言 本小节&#xff0c;我们学习结构的内存对…

什么是网络工程师? 就业前景好吗?

互联网发展日渐成熟&#xff0c;所有企业都依赖于网络管理&#xff0c;有企业的地方就需要网络工程师。 在一般人的概念里&#xff0c;网络工程师不过就是通过拨号上网&#xff0c;发个Email&#xff0c;聊聊天&#xff0c;计算机组装与维护&#xff0c;组建局域网就以为是网络…

项目进度管理:常用项目管理工具推荐

工欲善其事必先利其器&#xff0c;借助项目管理工具可以帮助项目经理更好的管理项目&#xff0c;起到事半功倍的效果。 使用项目管理工具来管理项目&#xff0c;有助于事情的快速落地&#xff0c;提升做事效率&#xff0c;也能让事情做的更周到全面 选择项目管理工具时可以参…

uniapp、微信小程序类似mui中的chat(聊天窗口)

在mui中有chat界面的例子&#xff0c;升级到uni-app后&#xff0c;没有类似的模板&#xff0c;因此模仿写了一个。遇到了一些坑&#xff0c;在此一一记录下来。当然&#xff0c;由于是新手&#xff0c;可能有些坑可以避开。 预览效果 scroll-view高度的设置 输入内容后&#…

室内导航技术在智慧医疗的革新应用

随着科技的飞速发展&#xff0c;智慧医疗已经成为现代医疗服务的重要组成部分。在这个背景下&#xff0c;室内导航技术逐渐崭露头角&#xff0c;为智慧医疗建设带来了革命性的改变。本文将深入探讨室内导航技术在智慧医疗中的应用&#xff0c;并分析其为医疗服务带来的诸多便利…

3分钟部署自己独享的Gemini

3分钟部署自己独享的Gemini 在前面的几篇文章中&#xff0c;分别介绍了Gemini Pro的发布和Gemini Pro API的详细申请步骤&#xff0c;那么今天给大家分享的是如何快速搭建一个属于自己的Gemini 。 1️⃣ 准备工作 科学网络环境Github账号和Vercel账号Gemini Pro API Key&…

一个抖店内做几个商品链接比较合适?解答下新手问题,建议收藏

我是王路飞。 一个抖店内的商品链接数量&#xff0c;是多一些比较好还是少一些比较好呢&#xff1f; 可能在大多数人看来&#xff0c;当然是多一些比较好了&#xff0c;商品数量更多&#xff0c;基数增加&#xff0c;也能承载更多的进店流量&#xff0c;增加下单几率。 但真…

数智金融技术峰会|数新网络受邀分享《金融信创湖仓一体数据平台架构实践》,敬请期待

12月23日&#xff0c;数新网络参加DataFunSummit 2023&#xff1a;数智金融技术峰会。会上&#xff0c;数新CTO原攀峰将为大家带来《金融信创湖仓一体数据平台架构实践》 主题分享。 本次峰会由DataFun联合火山引擎、蓝驰等知名企业举办&#xff0c;将共同为大家带来一场数智金…

Docker 编译OpenHarmony 4.0 release

一、背景介绍 1.1、环境配置 编译环境&#xff1a;Ubuntu 20.04OpenHarmony版本&#xff1a;4.0 release平台设备&#xff1a;RK3568 OpenHarmony 3.2更新至OpenHarmony 4.0后&#xff0c;公司服务器无法编译通过&#xff0c;总是在最后几十个文件时报错,错误码4000&#xf…

【分享】4个方法打开PDF文件

PDF是很多人工作中经常使用的电子文档格式&#xff0c;但是可能有些刚接触的小伙伴不知道用什么工具来打开PDF文件&#xff0c;今天小编就来分享一下4种常用的工具。 1. 使用浏览器 只要有电脑基本都会安装一到两款浏览器&#xff0c;其实浏览器也可以用来打开PDF文件。 只需…