目录
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-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.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.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系统中,字符设备驱动通常使用字符设备接口进行开发。驱动程序需要定义设备结构体、注册设备、实现文件操作函数等,以提供稳定高效的设备访问接口。除了基本的功能,驱动程序还可以实现多个进程访问同一个设备、内存映射、虚拟文件系统、设备驱动模块化、调试信息输出等特性。
字符设备驱动技术在计算机领域有着重要的意义和影响。首先,它为应用程序提供了访问字符设备的标准接口,使得应用程序能够方便地与设备进行数据交互,从而促进了各种应用软件的开发和推广。其次,字符设备驱动技术也支持多种设备类型和多种操作系统平台,使得设备之间的互通性得到了提升,为设备互联和智能化提供了先决条件。
未来,随着物联网技术的不断发展和普及,字符设备驱动技术将会得到更广泛的应用和推广。特别是在智能家居、工业自动化、医疗健康等领域,字符设备驱动技术将发挥更大的作用和贡献。同时,随着技术的不断进步和创新,字符设备驱动技术也将会不断完善和优化,以满足日益增长的设备互联需求和应用场景。
感谢大家的阅读,欢迎留言指教。