打开原理图,确定需要控制的IO端口为GPF4、GPF5、GPF6。
2、查看芯片手册,确定IO端口的寄存器地址,可以看到它的基地址为0x56000050
1)、编写出口、入口函数。
a、首先利用register_chrdev函数如果第一个参数为0的话那么会自动分配一个主设备号为Firstmajor ;第二个参数firstled_drv会是这个字符设备的名称可以利用命令cat /proc/devices看到;第三个参数是它的first_drv_fops结构体,这个结构体是字符设备中最主要的,后面再说明。
b、接着利用class_create函数创建一个firt_drv_class类。它的第一个参数指向这个模块,第二个参数为类的名称。再利用class_device_create创建四个设备节点,第一个参数为类、第三个参数为设备号,第五个参数为设备节点的名称,第六个参数为次设备号。这样的话会在加载驱动之后自动在/dev目录下创建四个设备文件。
c、ioremap函数重映射函数,将物理地址转换成虚拟地址
d、a-c为驱动入口函数,在驱动出口函数会将a-c创建的东西全部删除。
e、module_init与module_exit表示在insmod与rmmod的时候内核会调用first_ledsdrv_init与first_ledsdrv_exit
/*
* 执行insmod命令时就会调用这个函数
*/
static int __init first_ledsdrv_init(void)
{
int minor;//次设备号
Firstmajor = register_chrdev(0, 'firstled_drv', &first_drv_fops);//注册first_drv_fops结构体到字符设备驱动表,0表示自动分配主设备号
if(Firstmajor<0)
{
printk(' first_drv can't register major numbern');
return Firstmajor;
}
firt_drv_class = class_create(THIS_MODULE, 'leds');//创建类
firt_drv_class_dev[0] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, 0), NULL, 'leds');//创建设备节点
if (unlikely(IS_ERR(firt_drv_class_dev[0])))
return PTR_ERR(firt_drv_class_dev[0]);
for(minor=1;minor<4;minor++)
{
firt_drv_class_dev[minor] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, minor), NULL, 'led%d',minor);//创建设备节点
if (unlikely(IS_ERR(firt_drv_class_dev[minor])))
return PTR_ERR(firt_drv_class_dev[minor]);
}
gpfcon = ioremap(0x56000050 , 16);//重映射,将物理地址变换为虚拟地址
gpfdat = gpfcon + 1;
printk('firstdrv module insmodedn');
return 0;
}
/*
* 执行rmmod命令时就会调用这个函数
*/
static void __exit first_ledsdrv_exit(void)
{
int i;
for(i=0;i<4;i++)
class_device_unregister(firt_drv_class_dev[i]);//删除设备节点
class_destroy(firt_drv_class);//删除类
iounmap(gpfcon);//删除重映射分配的地址
unregister_chrdev(Firstmajor, 'firstled_drv');//将rst_drv_fops结构体从字符设备驱动表中删除
printk('firstdrv module rmmodn');
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(first_ledsdrv_init);
module_exit(first_ledsdrv_exit);
2)、添加file_operations 结构体,这个是字符设备驱动的核心结构,所有的应用层调用的函数最终都会调用这个结构下面定义的函数。
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = first_ledsdrv_open,
.write = first_ledsdrv_write,
};
其中THIS_MODULE在linux/module.h中定义,它执向__this_module的地址
84 extern struct module __this_module;
85 #define THIS_MODULE (&__this_module)
而__this_module这个变量是在编译的时候由modpost程序生成的,它的结构如下:
struct module __this_module
__attribute__((section('.gnu.linkonce.this_module'))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
};
3)、分别编写file_operations 结构体下的open、wrtie函数。当应用程序调用系统调用led设备的open与write时最终内核会定位到驱动层的open与write函数。
其中open函数的功能是根据打开的设备文件初始化相应的io口为输出口
static int first_ledsdrv_open(struct inode *inode, struct file *file)
{
int minor = MINOR(inode->i_rdev);//取得次设备号,根据次设备号来配置IO端口
switch(minor)
{
case 0:
*gpfcon &= ~((3 << 8) | (3 << 10) | (3 << 12));//先清0 :8,9,10,11,12,13
*gpfcon |= ((1 << 8) | (1 << 10) | (1 << 12));//再置1:8,10,12break;
printk('initialize ledsn');
break;
case 1:
*gpfcon &= ~((3 << 8) );//先清0 :8,9,10,11,12,13
*gpfcon |= ((1 << 8));//再置1:8,10,12break;
printk('initialize led1n');
break;
case 2:
*gpfcon &= ~( (3 << 10));//先清0 :8,9,10,11,12,13
*gpfcon |= ( (1 << 10) );//再置1:8,10,12break;
printk('initialize led2n');
break;
case 3:
*gpfcon &= ~((3 << 12));//先清0 :8,9,10,11,12,13
*gpfcon |= ((1 << 12));//再置1:8,10,12break;
printk('initialize led3n');
break;
default:break;
}
// printk('hello this is openn');
return 0;
}
write函数的功能是根据设备文件以及向设备写入的值来操作相应的IO口做相应的动作
static ssize_t first_ledsdrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
char val;
int ret;
int minor = MINOR(file->f_dentry->d_inode->i_rdev);//根据文件取出次设备号
ret = copy_from_user(&val, buf, count);//ret返回0表示拷贝成功
if(!ret)
{
switch(minor)
{
case 0:
if(val==1)
{
*gpfdat &= ~((1 << 4) | (1<<5) | (1<<6));//点灯
printk('leds onn');
}
else if(val == 0)
{
*gpfdat |= ((1 << 4) | (1<<5) | (1<<6));//灭灯
printk('leds offn');
}
break;
case 1:
if(val==1)
{
*gpfdat &= ~((1 << 4));//点灯
printk('led1 onn');
}
else if(val == 0)
{
*gpfdat |= ((1 << 4));//灭灯
printk('led1 offn');
}
break;
case 2:
if(val==1)
{
*gpfdat &= ~((1<<5));//点灯
printk('led2 onn');
}
else if(val == 0)
{
*gpfdat |= ((1<<5));//灭灯
printk('led2 offn');
}
break;
case 3:
if(val==1)
{
*gpfdat &= ~((1<<6));//点灯
printk('led3 onn');
}
else if(val == 0)
{
*gpfdat |= ((1<<6));//灭灯
printk('led3 offn');
}
break;
default:break;
}
}
else
printk('copy from user wrong!!!!%d %dn',ret,count);
// printk('hello this is writen');
return 0;
}
上一篇:Linux驱动之按键驱动编写(查询方式)
下一篇:Linux驱动之建立一个hello模块
推荐阅读最新更新时间:2024-11-10 10:27
设计资源 培训 开发板 精华推荐
- 基于STLA02的多达6个白色LED驱动器,用于显示背光
- ADA4062-4ACPZ-R7微功率仪表放大器典型应用电路
- 重磅发布-萝莉八通道接收机
- esp12F驱动7735小屏做天气时钟
- 信仰尺(LOVE CHINA)
- 基于ST25DV04K的动态NFC / RFID标签IC扩展板,用于STM32 Nucleo
- STEVAL-IHT008V1,具有浪涌电流限制和绝缘交流开关控制的低待机损耗前端评估板
- L4978 2A 降压型开关稳压器的典型应用
- DC256A-B,用于 LTC1629CG 6 相、极高电流、低压电源、5 至 12Vin、3.3Vout @ 90A 或 5Vout @ 60Amps 的演示板
- 多功能加热台控制器
- 手机观看TI视频教程,随时随地充电学习
- 温故知新:回顾 ST 2017 Roadshow,洞悉电子界技术新潮流!
- 万用表:越拆越开心,越评越精彩!
- 免费申请 | Nordic Semiconductor nPM1300-EK PMIC 评估套件
- 想要更直观的了解5G?观看罗德与施瓦茨主题演讲,填问卷赢好礼!
- EE大学堂新年游戏之\\
- 免费领取 | 射频年度盛会EDI CON VIP全场通票(北京,3.20~22)
- 吉时利DMM6500 6½ 位数字触摸屏万用表六大功能,满足工程师的切身需求,献给有梦想的你!
- 【已结束】 电感应用知识分享|MPS 有奖直播
- 安森美半导体重磅推出超低功耗蓝牙芯片 RSL10 — 观视频答题送样片 更有丰富礼品等你拿!