接上篇:(六)嵌入式 Linux驱动程序设计之物理地址到虚拟地址映射
总结:
杂项设备框架
1注册杂项设备:
misc _register(&misc)
2构础杂项设备结构体
static struct miscdevice misc={
.minor=12,
.name="filename"//dev/下名称
.fops=&filename_fops,
};
3.构建file-operation
open
write copy_from_user
read copy_to_user
close
4.卸载杂项设备
misc_deregister(&misc)
字符设备框架
1.驱动初始化
1.1分配设备号:
静态分配设备号(register_chrdev_region)
动态分配设备号(alloc_chrdev_region)
1.1.1操作设备号dev_t:
宏MAJOR用于从dev_t中获取主设备号
宏MINOR用于从dev_t中获取次设备号
宏MKDEV用于将给定的主设备号和次设备号的值组合成dev_t类型的设备号
1.2初始化cdev cdev_init
1.3注册cdev cdev_add
1.4初始化硬件
2.构建file-operation
open
write copy_from_user
read copy_to_user
close
3.生成设备节点
3.1自动生成设备节点
创建一个class class_create
创建一个设备 device_create
3.2手动生成设备节点
mknod命令
4.驱动卸载
4.1释放设备号:unregister_chrdev_region
卸载cdev cdev_del
卸载设备 device_destroy
卸载class class_destroy
应用层打开设备节点(通过file_operation操作硬件设备)
fd=open("/dev/xxx",O_RDWR) xxx_open()
read(fd,date,1); xxx_read()
write(fd,date,1); xxx_write()
杂项设备驱动流程图:
目标
使用杂项设备完成一个蜂鸣器的驱动 完成一个上层测试应用 应用要求:在上层应用中传入参数1为打开蜂鸣器,传入参数0为关闭蜂鸣器
任务分析
想要操作蜂鸣器,就要完成read函数 open函数,等等,我们做驱动,大部分情况下也都是使用这几个函数。
要完成上层应用的测试,就需要应用层和内核层传输数据,copy_to_user和copy_from_user
注意:确定蜂鸣器地址:先从原理图找到对应GPIO管脚,然后在手册中搜索管教名称找到具体的GPIO管教(GPIO5)然后根据此管教搜索此管教的DR寄存器地址即可,如GPIO5_DR
因为内核已经有蜂鸣器驱动,所以复用关系和电气属性寄存器都不要设置,只需要设置数据寄存器即可。
源码如下:
beep.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define GPIO5_DR 0x020AC000
unsigned int *vir_gpio5_dr;
int misc_open (struct inode *inode, struct file *file){
printk("hello misc_open!!!\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file){
printk("bye bye misc_release!!!\n");
return 0;
}
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy to user!!!\n";
if( copy_to_user(ubuf, kbuf, size) != 0 ){
printk("copy_to_user error!!!\n");
return -1;
}
printk("hello misc_read!!!\n");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
char kbuf[64] = "copy from user!!!\n";
printk("hello misc_write!!!\n");
if( copy_from_user(kbuf, ubuf, size) != 0 ){
printk("copy_from_user error!!!\n");
return -1;
}
printk("buf is:%s\n", kbuf);
if(kbuf[0] == 1){ //对蜂鸣器的控制,如果是1,控制gpio口
*vir_gpio5_dr |= (1 << 1); //因为是gpio5的01,所以左移一位就可以,给一个高电平,使蜂鸣器工作
}else if(kbuf[0] == 0){
*vir_gpio5_dr &= ~(1 << 1);
}
return 0;
}
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops
};
static int misc_init(void)
{
int ret;
ret = misc_register(&misc_dev);
if(ret < 0){
printk("misc_register failed!!!\n");
return -1;
}
printk("misc_register succeed!!!\n"); // 在内核中无法使用c语言库,所以不用printf
vir_gpio5_dr = ioremap(GPIO5_DR, 4); // 物理地址到虚拟地址的映射
if(vir_gpio5_dr == NULL){ //如果映射失败
printk("GPIO5_DR ioremap error!!!\n");
return -EBUSY; //EBUSY是Linux的预留参数,前面加个负号
}
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
iounmap(vir_gpio5_dr); //取消虚拟地址的映射
printk("misc exit!!!\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buff[64] = {0};
fd = open("/dev/hello_misc", O_RDWR);
if(fd < 0){
perror("open error\n"); // perror在应用中打印
return fd;
}
buff[0] = atoi(argv[1]); //字符串转化成整形
// read(fd, buff, sizeof(buff));
write(fd, buff,sizeof(buff)); //在write中就传数据到底层,这样可以调用驱动的mis_write的操作了,直接操作 ./app 1或者./app 0
// printf("buf is:%s\n", buff);
close(fd);
return 0;
}