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

2023-12-22 16:30

本文主要是介绍养老院自助饮水机(字符设备驱动),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

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_readclose(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.chinasem.cn/article/524649

相关文章

剑指offer(C++)--第一个只出现一次的字符

题目 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写). class Solution {public:int FirstNotRepeatingChar(string str) {map<char, int> mp;for(int i = 0; i < str.size(); ++i)m

WDF驱动开发-WDF总线枚举(一)

支持在总线驱动程序中进行 PnP 和电源管理 某些设备永久插入系统,而其他设备可以在系统运行时插入和拔出电源。 总线驱动 必须识别并报告连接到其总线的设备,并且他们必须发现并报告系统中设备的到达和离开情况。 总线驱动程序标识和报告的设备称为总线的 子设备。 标识和报告子设备的过程称为 总线枚举。 在总线枚举期间,总线驱动程序会为其子 设备创建设备对象 。  总线驱动程序本质上是同时处理总线枚

linux匹配Nginx日志,某个字符开头和结尾的字符串

匹配 os=1 开头, &ip结尾的字符串 cat 2018-06-07.log | egrep -o ‘os=1.*.&ip’ 存入日志。然后使用submit 前面和后面的值去掉,剩下就是需要的字符串。 cat 2018-06-07.log | egrep -o ‘os=1.*.&ip’ >log.log

WDF驱动开发-特定于KMDF的技术(一)

这部分的技术是一些零散的记录知识点,它们主要是在WDF框架中特定于KMDF的部分。 将内核模式驱动程序框架和非 PnP 驱动程序配合使用 如果要为不支持 即插即用 (PnP) 的设备编写驱动程序,则驱动程序必须: 在 WDF_DRIVER_CONFIG 结构的 DriverInitFlags 成员中设置 WdfDriverInitNonPnpDriver 标志;提供 EvtDriverUnl

ADD属性驱动架构设计(一)

目录 一、架构设计过程 1.1、架构设计过程 1.1.1、设计目的 1.1.2、质量属性(非功能需求) 1.1.3、核心功能(功能需求) 1.1.4、架构关注 1.1.5、约束条件 1.2、基于设计过程 二、什么是ADD? 三、为什么选择ADD? 四、作用 五、ADD实现步骤 5.1、架构设计目标 5.1.1、系统类型确定  5.1.2、系统阶段确定 5.2、建

博通5720 windows server 2003 32位网卡驱动和系统

driver for DELL R320 Broadcom 5720 Windows 2003 32bit 本人安装windows server 2003 网卡驱动成功!! 提供方便网盘下载地址博通5720网卡驱动 : http://pan.baidu.com/s/1GQWpw  windows server 2003 ghost 系统: http://pan

C语言中的字符输入/输出和验证输入

在C语言中,字符输入/输出功能允许程序与用户进行交互,读取用户的输入信息并展示输出结果。同时,验证输入的作用在于确保用户输入的数据符合预期,以提高程序的稳定性和可靠性,防止无效输入引发的错误或异常行为,从而提供更好的用户体验。 基础概念 输入(Input):指的是向程序填充数据的过程,通常来源于用户输入、文件读取或其他外部数据源。 输出(Output):指的是将数据显示在屏幕上、打印机上或

无霍尔BLDC驱动

目前主要的无霍尔控制方案是基于反电势检测信 息判断换相点,本文研究反电势在 PWM - OFF 点的检 测方案确定换相点。 1. 反电动势检测方案 BLDC 的模型做等效,将线圈阻抗看成是一个 线性电阻和一个储能电感的等效,其等效电路图如图 1所示。 电机三相绕组输出端电压的电压方程组为 式中,LM = L - M; Ua0、Ub0和 Uc0为三相绕组输出端对直 流电源地的电压。e

嵌入式linux系统中LCD屏驱动实现思路分析

在 Linux 下 LCD 的使用更加广泛,在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。接下来就来学习一下如何在 Linux 下驱动 LCD 屏幕。                           第一:Framebuffer设备简介       先来回顾一下裸机的时候 LCD 驱动是怎么编写的,裸机 LCD 驱动编写流程如下: ①、初始化 I.MX6U

ESP32通过I2C驱动PCA9557IO扩展芯片

前言 ESP32自带的IO管脚比较有限,这个时候我们就需要使用一些IO扩展芯片扩展我们的IO,今天就介绍一款使用I2C接口扩展8个IO的芯片 PCA9557 PCA 9557芯片介绍 PCA9557是一款硅CMOS电路,为SMBus和I²C总线应用提供并行输入/输出扩展。PCA9557由8位输入端口寄存器、8位输出端口寄存器和I²C总线/SMBus接口组成。具有低电流消耗和高阻抗开漏输出引脚