一,主要的实现功能
1.通过自主注册设备驱动,并未3个LED灯注册对应的设备文件,每一个设备文件控制对用的LED灯。2.对控制LED灯的寄存器进行设置时,采用GPIO子系统,而不是采用寄存器地址的映射,消除了当硬件环境改变时,驱动代码无法正常执行。3.用户空间代码手动控制灯的熄灭逻辑。4.使用内核定时器使LED灯自主一闪一灭。!!!当想要手动控制灯的熄灭时,必须先将内核定时器代码先注销,因为共用一个驱动。
二,应用层代码
主要给用户提供一个操作界面,通过终端命令获取控制灯的亮闪,与内核定时器无关。
#include "user.h"
int main(int argc, char const *argv[])
{
while (1)
{
printf("请选择你要进行的操作1.<点灯> 0.<熄灯>:");
scanf("%d", &my_select);
if (my_select == 1) // 点灯
{
printf("选择你要操作的灯 1<LED1>,2<LED2>,3<LED3>:");
scanf("%d", &key);
if (key == 1) // 操作LED1
{
fd_led1 = open("/dev/myled0", O_RDWR);
if (fd_led1 < 0)
{
printf("myled%d设备文件打开失败\n", key - 1);
return 0;
}
ioctl(fd_led1, LED_ON);
}
else if (key == 2) // 操作LED2
{
printf("LED2\n");
fd_led2 = open("/dev/myled1", O_RDWR);
if (fd_led2 < 0)
{
printf("myled%d设备文件打开失败\n", key - 1);
return 0;
}
ioctl(fd_led2, LED_ON);
}
else if (key == 3) // 操作LED3
{
printf("LED3\n");
fd_led3 = open("/dev/myled2", O_RDWR);
if (fd_led3 < 0)
{
printf("myled%d设备文件打开失败\n", key - 1);
return 0;
}
ioctl(fd_led3, LED_ON);
}
}
else if (my_select == 0) // 熄灯
{
printf("选择你要操作的灯 1<LED1>,2<LED2>,3<LED3>:");
scanf("%d", &key);
if (key == 1) // 操作LED1
{
fd_led1 = open("/dev/myled0", O_RDWR);
if (fd_led1 < 0)
{
printf("myled%d设备文件打开失败\n", key - 1);
return 0;
}
ioctl(fd_led1, LED_OFF);
}
else if (key == 2) // 操作LED2
{
fd_led2 = open("/dev/myled1", O_RDWR);
if (fd_led2 < 0)
{
printf("myled%d设备文件打开失败\n", key - 1);
return 0;
}
ioctl(fd_led2, LED_OFF);
}
else if (key == 3) // 操作LED3
{
fd_led3 = open("/dev/myled2", O_RDWR);
if (fd_led3 < 0)
{
printf("myled%d设备文件打开失败\n", key - 1);
return 0;
}
ioctl(fd_led3, LED_OFF);
}
}
}
close(fd_led1);
close(fd_led2);
close(fd_led3);
return 0;
}
三,驱动代码
1.入口代码,包括自主注册驱动,初始化内核定时器,GPIO子系统节点信息的获取和资源申请
static int __init mycdev_init(void)
{
int i = -1;
/**定时器初始化**/
//设置定时阈值
mytimer.expires=jiffies+HZ;//设置定时1s # define HZ CONFIG_HZ
//完成定时器的初始化 参1:操作的定时器对象 参2:定时器到期时执行的回调函数 参3:标志0即可
timer_setup(&mytimer,mytimer_handler, 0);
//注册定时器到内核并启用
add_timer(&mytimer);
/*****LED灯的驱动安装流程*****/
// 1.安装驱动获取mojor设备号
printk("LED灯驱动安装\n");
major = register_chrdev(0, "myled", &fops); // 系统动态申请主设备号
if (major < 0)
{
printk("主设备号申请失败\n");
return major;
}
printk("主设备号申请成功:major=%d\n", major);
// 3.向上提交目录
cls = class_create(THIS_MODULE, "myled");
if (IS_ERR(cls))
{
printk("向上提交目录失败\n");
return PTR_ERR(cls);
}
printk("向上提交目录成功\n");
/*
cls=class_create(THIS_MODULE,"mychrdev1");
cls=class_create(THIS_MODULE,"mychrdev2");
*/
// 4.向上提交设备节点信息,分别为每一个灯创建设备文件
for (i = 0; i < 3; i++)
{
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
if (IS_ERR(dev))
{
printk("向上提交设备信息myled%d失败\n", i);
return PTR_ERR(dev);
}
printk("向上提交设备信息myled%d成功", i);
}
all_led_init();
return 0;
}
int all_led_init(void)
{
// 1.解析设备树节点信息通过名字
dnode = of_find_node_by_name(NULL, "myleds");
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");
// 2.2申请LED1对应资源
gpiono_led2 = gpiod_get_from_of_node(dnode, "led2", 0, GPIOD_OUT_LOW, NULL);
if (IS_ERR(gpiono_led2))
{
printk("LED2资源申请失败\n");
return -PTR_ERR(gpiono_led2);
}
printk("LED2资源申请成功\n");
// 2.3申请LED1对应资源
gpiono_led3 = gpiod_get_from_of_node(dnode, "led3", 0, GPIOD_OUT_LOW, NULL);
if (IS_ERR(gpiono_led3))
{
printk("LED3资源申请失败\n");
return -PTR_ERR(gpiono_led1);
}
printk("LED3资源申请成功\n");
return 0;
}
2.出口代码,资源的释放,回收等)
注意回收和注销的顺序:1.先关闭stm32上运行的硬件灯,2.将申请的资源释放 3.销毁自主创建的设备文件,目录。4.最后销毁驱动。
static void __exit mycdev_exit(void)
{
// 关灯
gpiod_set_value(gpiono_led1, 0);
gpiod_set_value(gpiono_led2, 0);
gpiod_set_value(gpiono_led3, 0);
//注销定时器
del_timer(&mytimer);
// 释放申请的LED资源
gpiod_put(gpiono_led1);
gpiod_put(gpiono_led2);
gpiod_put(gpiono_led3);
// 销毁设备信息
device_destroy(cls, MKDEV(major, 0));
device_destroy(cls, MKDEV(major, 1));
device_destroy(cls, MKDEV(major, 2));
// 销毁目录
class_destroy(cls);
// 注销字符设备驱动
unregister_chrdev(major, "myled");
}
3.手动控制LED灯逻辑代码函数ioctl
int mycdev_open(struct inode *inode, struct file *file)
{
// 1.通过struct inode找到设备号
// 2.根据设备号使用MINOR函数找到次设备号
// 3.将次设备号放入到struct file文件中private_date中
led_minor = MINOR(inode->i_rdev);
file->private_data = (void *)led_minor;
printk("data=%x\n",(unsigned int)file->private_data);
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
// 向用户空间读取拷贝
if (size > sizeof(kbuf)) // 用户空间期待读取的大小内核满足不了,那就给内核支持的最大大小
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) // 拷贝失败
{
printk("copy_to_user filed\n");
return ret;
}
return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
// 获取struct file结构体中的private_data中存储的次设备号
unsigned int led_x = 0;
led_x = (unsigned int )file->private_data;
printk("led_x=%x",led_x);
switch (led_x)
{
case 0: // 打开的是LED1设备文件
printk("文件myled0打開\n");
switch (cmd) // 判断是开灯还是熄灯
{
case LED_ON: // 开灯
printk("LED1开\n");
gpiod_set_value(gpiono_led1,1);
break;
case LED_OFF: // 熄灯
gpiod_set_value(gpiono_led1,0);
break;
}
break;
case 1: // 打开的是LED2设备文件
switch (cmd) // 判断是开灯还是熄灯
{
case LED_ON: // 开灯
printk("LED2开\n");
gpiod_set_value(gpiono_led2,1);
break;
case LED_OFF: // 熄灯
gpiod_set_value(gpiono_led2,0);
break;
}
break;
case 2: // 打开的是LED3设备文件
switch (cmd) // 判断是开灯还是熄灯
{
case LED_ON: // 开灯
printk("LED3开\n");
gpiod_set_value(gpiono_led3,1);
break;
case LED_OFF: // 熄灯
gpiod_set_value(gpiono_led3,0);
break;
}
break;
}
return 0;
}
4.内核定时器自动控制灯的亮灭代码
主要在于执行函数内部调用定时器,达到一个无限循环的效果
void mytimer_handler(struct timer_list *timer)
{
//控制灯亮灭
gpiod_set_value(gpiono_led1,!gpiod_get_value(gpiono_led1));
gpiod_set_value(gpiono_led2,!gpiod_get_value(gpiono_led2));
gpiod_set_value(gpiono_led3,!gpiod_get_value(gpiono_led3));
//再次启动定时器
mod_timer(timer,jiffies+HZ);
}