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

目录

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

社交网络分析(汇总)

这里写自定义目录标题 写在最前面社交网络分析系列文章汇总目录 提纲问题一、社交网络相关定义和概念提纲问题1. 社交网络、社交网络分析&#xff1b;2. 六度分隔理论、贝肯数、顿巴数&#xff1b;3. 网络中的数学方法&#xff1a;马尔科夫过程和马尔科夫链、平均场理论、自组织…

仿悬赏猫任务平台源码 悬赏任务系统源码 带支付接口

源码介绍 最新仿悬赏猫任务平台源码 悬赏任务系统源码 带支付接口&#xff0c; 全新开发悬赏任务系统&#xff0c;功能齐全&#xff0c;包含接任务&#xff0c;发布任务&#xff0c; 店铺关注&#xff0c;置顶推荐&#xff0c;排行榜&#xff0c;红包大厅&#xff0c;红包抽奖…

Android Studio如何实现 成语接龙游戏(简单易上手)

该项目是一个基于Android Studio和Java语言编写的成语接龙游戏App。成语接龙是一种经典的中文文字游戏&#xff0c;旨在测试玩家的词汇量和思维敏捷性。该成语接龙游戏App旨在提供一种有趣、挑战性和教育性的游戏体验。玩家可以通过游戏提高自己的中文词汇量和思维敏捷性&#…

Text Intelligence - TextIn.com AI时代下的智能文档识别、处理、转换

本指南将介绍Text Intelligence&#xff0c;AI时代下的智能文档技术平台 Textin.com 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认…

KubeSphere应用【六】中间件部署

一、Mysql部署 1.1创建配置字典 [client] default-character-setutf8mb4 [mysql] default-character-setutf8mb4[mysqld] sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION init_connectSET…

【分治算法】之汉诺塔问题

汉诺塔问题 三根柱子 把A柱子上的盘子全部挪到C上&#xff0c;且每次挪动的时候 小的必须在大的上面 分治算法的思想; 分&#xff1a;把一个大问题拆成若干个小的子问题&#xff0c;每个子问题相互独立&#xff1b; 治&#xff1a;求解每个子问题的&#xff08;递归&#xf…

前端FLV视频直播解决方案

项目背景&#xff1a; 1. 后台给出一个地址&#xff0c;持续不断的推送flv视频流。 2.前端需要接收视频流&#xff0c;并寻找合适的播放插件。 一开始&#xff1a; 其实用的是xgplayer&#xff08;西瓜视频&#xff09;。 官网地址&#xff1a;西瓜播放器 使用的是直播&a…

开放式耳机怎么选?2023高人气品牌推荐:新手避坑必看!

自从开放式耳机风靡市场以来&#xff0c;大家对于开放式耳机的选购也越发摸不着头脑。价格从百元到千元不等&#xff0c;就连大品牌的产品口碑也褒贬不一。 不少人私信向我询问&#xff1a; 1、难道只有千元价位的开放式耳机才好吗&#xff1f;2、是否有价格更实惠且性价比更…

如何使用 Helm 在 K8s 上集成 Prometheus 和 Grafana|Part 1

本系列将分成三个部分&#xff0c;您将学习如何使用 Helm 在 Kubernetes 上集成 Prometheus 和 Grafana&#xff0c;以及如何在 Grafana 上创建一个简单的控制面板。Prometheus 和 Grafana 是 Kubernetes 最受欢迎的两种开源监控工具。学习如何使用 Helm 集成这两个工具&#x…

C#电源串口调试

目的 记录串口调试的遇到的一些问题以及相应的解决方法 1.串口定义:串口是计算机与其他硬件传输数据的通道&#xff0c;在计算机与外设通信时起到重要作用 2.串口通信的基础知识 C#中的串口通信类 C#使用串口通信类是SerialPort(),该类使用方法是 new 一个 SerialPort对象 为S…

Prometheus-JVM

一. JVM监控 通过 jmx_exporter 启动端口来实现JVM的监控 Github Kubernetes Deployment Java 服务&#xff0c;修改 wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.19.0/jmx_prometheus_javaagent-0.19.0.jar# 编写配置文件&#xff0…

JAVA判断两个时间之间的差

1.首先引入jar包 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.7</version> </dependency>2.计算差值 public static DateFormat getDateTimeFormat(){DateFormat dtf new Sim…

即将来临的2024年,汽车战场再起波澜?

我们来简要概况一下11月主流车企的销量表现&#xff1a; 根据数据显示&#xff0c;11月吉利集团总销量29.32万辆&#xff0c;同比增长28%。这在当月国内主流车企中综合实力凌厉&#xff0c;可谓表现得体。而与吉利直接竞争的比亚迪&#xff0c;尽管数据未公布&#xff0c;但我们…

华为二层交换机与防火墙配置实例

二层交换机与防火墙对接上网配置示例 组网图形 图1 二层交换机与防火墙对接上网组网图 二层交换机简介配置注意事项组网需求配置思路操作步骤配置文件相关信息 二层交换机简介 二层交换机指的是仅能够进行二层转发&#xff0c;不能进行三层转发的交换机。也就是说仅支持二层…

Flink系列之:Savepoints

Flink系列之&#xff1a;Savepoints 一、Savepoints二、分配算子ID三、Savepoint 状态四、算子五、触发Savepoint六、Savepoint 格式七、触发 Savepoint八、使用 YARN 触发 Savepoint九、使用 Savepoint 停止作业十、从 Savepoint 恢复十一、跳过无法映射的状态恢复十二、Resto…

22 3GPP在SHF频段基于中继的5G高速列车场景中的标准化

文章目录 信道模型实验μ参考信号初始接入方法波形比较 RRH&#xff1a;remote radio head 远程无线头 HTS&#xff1a;high speed train 高速移动列车 信道模型 考虑搭配RRH和车载中继站之间的LOS路径以及各种环境&#xff08;开放或峡谷&#xff09;&#xff0c;在本次实验场…

Postgresql源码(118)elog/ereport报错跳转功能分析

1 日志接口 elog.c完成PG中日志的生产、记录工作&#xff0c;对外常用接口如下&#xff1a; 1.1 最常用的ereport和elog ereport(ERROR,(errcode(ERRCODE_UNDEFINED_TABLE),errmsg("relation \"%s\" does not exist",relation->relname)));elog(ERRO…

如何粗暴地下载huggingface_hub指定数据文件

参考这里&#xff1a; https://huggingface.co/docs/huggingface_hub/guides/download 可见下载单个文件&#xff0c;下载整个仓库文件都是可行的。 这是使用snapshot_download下载的一个例子&#xff1a; https://qq742971636.blog.csdn.net/article/details/135150482 sn…

轻松管理TXT文本,高效批量内容调整,打造高效工作流程!

在数字时代&#xff0c;文本文件已经成为我们生活和工作中不可或缺的一部分。无论是简单的笔记、待办事项&#xff0c;还是复杂的项目报告、小说草稿&#xff0c;TXT文本都能为我们提供灵活的存储和编辑方式。但是&#xff0c;随着文本文件的增多&#xff0c;如何轻松管理、高效…

Java 并发编程中的线程池

7 并发编程中的线程池 自定义线程池 package com.rainsun.d7_thread_pool;import lombok.extern.slf4j.Slf4j;import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Co…