一,功能实现要求
/*功能实现 在stm32开发板上实现功能
1.使用阻塞IO读取number变量的值,当number的值改变时打印number的值
2.注册KEY1按键的驱动和LED1的驱动以及对应的设备文件,
3.按键和指示灯设备信息放在同一个设备树的节点中
4.当KEY1按下时LED1灯的状态取反,number的值取反,number值为0或1
*/
二,示例图
三,驱动注册(无实际功能)
1.主要实现手动注册驱动,并自动提交目录以及myled1和mykey1两个设备文件
int request_dev(void)
{
int i;
// 1.实例化字符设备驱动对象
cdev = cdev_alloc();
if (cdev == NULL)
{
ret = -ENOMEM;
goto OUT1;
}
// 2.部分初始化字符设备驱动对象
cdev_init(cdev, &fops);
// 3.申请设备号
if (my_major == 0) // 动态申请设备号
{
ret = alloc_chrdev_region(&devno, my_minor, 3, "mycdev"); // 成功返回0,失败返回错误码
if (ret)
{
printk("动态申请设备号失败\n");
goto OUT2;
}
printk("动态申请设备号成功\n");
my_major = MAJOR(devno); // 获取主设备号
my_minor = MINOR(devno); // 获取次设备号
}
else // 静态制定设备号
{
// 通过自定义主设备号和次设备号获取设备号
devno = MKDEV(my_major, 0);
ret = register_chrdev_region(devno, 3, "mycdev"); // 成功申请设备号返回0,失败返回错误码
if (ret)
{
printk("静态申请设备号失败\n");
goto OUT2;
}
printk("静态申请设备号成功\n");
}
// 4.将字符设备驱动对象注册进内核
ret = cdev_add(cdev, devno, 3);
if (ret)
{
printk("字符设备驱动注册进内核失败\n");
goto OUT3;
}
printk("字符设备驱动注册进内核成功\n");
// 5.自动创建设备节点
// 向上提交目录
cls = class_create(THIS_MODULE, "mycdev");
if (IS_ERR(cls)) // 为真表示cls指向4K的预留空间
{
printk("向上提交目录失败\n");
ret = -PTR_ERR(cls);
goto OUT4;
}
printk("向上提交目录成功\n");
// 自动创建设备文件LED1
dev = device_create(cls, NULL, MKDEV(my_major, 0), NULL, "myled1");
if (IS_ERR(dev))
{
printk("自动创建设备文件myled1失败\n");
ret = PTR_ERR(dev);
goto OUT5;
}
printk("自动创建设备文件myled1成功\n");
// 自动创建设备文件KEY1
dev = device_create(cls, NULL, MKDEV(my_major, 1), NULL, "mykey1");
if (IS_ERR(dev))
{
printk("自动创建设备文件mykey1失败\n");
ret = PTR_ERR(dev);
goto OUT5;
}
printk("自动创建设备文件mykey1成功\n");
return 0;
OUT5:
// 释放提交成功的设备节点信息
for (--i; i >= 0; i--)
{
device_destroy(cls, MKDEV(my_major, i));
}
// 销毁目录
class_destroy(cls);
OUT4:
cdev_del(cdev);
OUT3:
unregister_chrdev_region(devno, 3);
OUT2:
kfree(cdev);
OUT1:
return ret;
}
四,LED1灯的初始化
采用从自定义的设备树节点中获取LED1的GPIO对象信息,为LED1灯各个寄存器使能。只需手动调用电平改变函数gpiod-set-value即可实现灯的亮灭.
int request_LED(void)
{
// 1.解析设备树节点信息通过名字
dnode = of_find_node_by_name(NULL, "numbers");
if (dnode == NULL)
{
printk("设备树节点解析失败\n");
return -ENOMEM;
}
printk("设备树节点解析成功\n");
// 2.通过节点信息获取GPIO对象
// 2.1申请LED1对应资源
gpiono_led1 = gpiod_get_from_of_node(dnode, "led1", 0, GPIOD_OUT_LOW, NULL);
if (IS_ERR(gpiono_led1))
{
printk("LED1资源申请失败\n");
return -PTR_ERR(gpiono_led1);
}
printk("LED1资源申请成功\n");
return 0;
}
5.按键中断的初始化
也是根据自己定义的设备树节点中获取信息,申请对应的软中断号,然后根据获取到的软终断号初始化中断号,从而实现中断发生回调中断处理函数处理中断时间。
int request_interrupts(void)
{
// 准备:在设备树中添加gpiof组控制按键的节点信息
// 1.在设备数中寻找到对应设备树节点的信息 of_find_node_by_name
dnode = of_find_node_by_name(NULL, "numbers");
if (dnode == NULL)
{
printk("节点信息获取失败\n");
return -ENXIO;
}
printk("节点信息获取成功\n");
// 2.根据节点地址寻找key对应的软中断号 irq_of_parse_and_map
// 获取KEY按键对应的软中断号 参2的索引的值在于你的自定义节点GPIO控制所在位置,
// myirq{
// interrupts-extended=<&gpiof 9 0>,<&gpiof 7 0>,<&gpiof8 0>;
// };KEDY1对应<&gpiof 9 0>索引为0,KEY2对应<&gpiof 7 0>,索引为1,类似数组依次对应
myirq_key[0] = irq_of_parse_and_map(dnode, 0);
if (!myirq_key[0])
{
printk("key1软中断号获取失败\n");
return ENOMEM;
}
printk("key1软中断号获取成功\n");
// 3.注册中断号 包括对应的软中断号,中断执行的处理函数,中断的检测方式,中断名
// 注册KEY对应的软中断号
// 参1:中断号对应的软中断号 参2:中断的处理函数 参3:中断触发的方式 参4:为中断起一个名字 参5:给中断函数传递的值
// KEY1
ret = request_irq(myirq_key[0], myirq_handler, IRQF_TRIGGER_FALLING, irq_key1_name, 0);
if (ret < 0)
{
printk("key1中断注册失败\n");
return ret;
}
printk("key1中断注册成功\n");
return 0;
}
6.阻塞IO
当中断没有发生时,函数一直处于等待状态,当中断发生,标志位的值改变,从而使函数往下运行,改变number内核变量的值
int my_wait(void)
{
// 判断condition的值,为1则改变number的值
wait_event_interruptible(wq_head, condition);
if (number==1)
{
number = 0;
}
else if(number==0)
{
number = 1;
}
condition = 0;
return 0;
}
7.按键中断处理函数
根据LED1现在的状态改变,更改标志位并唤醒睡眠中的进程。实现功能的要求。
// 中断处理函数
irqreturn_t myirq_handler(int irqno, void *dev_id)
{
printk("软中断号%d的处理\n", irqno);
// 改变LED灯的状态
gpiod_set_value(gpiono_led1, !gpiod_get_value(gpiono_led1));
// 改变number的值
// 改变标志
condition = 1;
// 唤醒可中断进程
wake_up_interruptible(&wq_head);
return IRQ_HANDLED;
}