在做正点原子教程野火开发板时内核定时器的实验总结

根据正点原子的教程移植到野火开发板上实现LED灯的定时闪烁

定时器代码总结:

1、icotl函数问题

//命令值
#define CLOSE_CMD (_IO(0XEF, 0x1)) //关闭定时器
#define OPEN_CMD (_IO(0XEF, 0x2)) //打开定时器
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) //设置定时器周期

为什么这么定义命令呢?因为ioctl函数在Linux中就是这么定义的,所以我们需要按照它的写法来,然后在内核里面又有:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0),这里的四个参数值其实是对应了一个图的
在这里插入图片描述
所以我们这里定义的_IOC中的参数就只有type和nr,也就是图中的后两部分:
其中type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,这里是参照Linux内核中的设置为0XEF;
nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增,这里我们设置了三个命令:分别是0X1等,这三个相当于switch中的选项;
static long timer_unlocked_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)

内核版本变更问题

1、因为使用的是正点原子的教程,教程上面用的内核版本是小于4.14的,但是在野火的开发板上使用的内核是4.20版本的;
在4.15以上的内核里,init_timer被移除了。需要换用新的timer_setup接口。所以正点原子版本上的代码不能用了

2、查阅资料https://blog.csdn.net/qq_26558047/article/details/115249587得出如何更改
这个资料的更改如下:
在这里插入图片描述
而我的更改是:
在这里插入图片描述
回调函数的更改为:
在这里插入图片描述
更改的时候要注意函数参数的对应和传入的是结构体指针还是地址还是指针还是结构体首地址;
struct imx6uirq_dev *dev = from_timer(&imx6uirq,t,timer);注意这里的书写 imx6uirq_dev ,每个程序不同的
以上是此实验的难点代码讲解

2、定时器流程代码讲解

1、注册驱动的时候会执行init函数,先需要初始化自旋锁 spin_lock_init(&timerdev.lock);
lock是定义在结构体timerdev中的变量 spinlock_t lock ; /定义一个自旋锁/
然后就是执行设备驱动框架的定义
然后需要初始化自己定义的定时器: timer_setup(&timerdev.timer,timer_function,0); timer是定义在结构体timerdev中的变量
struct timer_list timer; /定义一个定时器/
timer_function是定时器回调函数,0不知道怎么设置的

2、应用程序会使用open函数打开驱动设备,此时会执行驱动中的open函数,在这里要将其设置为私有数据
filp->private_data = &timerdev; /* 设置私有数据 */
然后设置定时器的值:timerdev.timerperiod = 1000 ; //设置为1s timerperiod是定义在结构体timerdev中的变量,是整型;
然后在这里初始化LED灯,在这里获取设备树当中的gpio硬件信息,此时获得对应的io口以后需要使用请求gpio函数:
gpio_request(timerdev.timer_gpio, “led”); //请求io,参数为得到的led编号,第二个参数为起别名,但是不知道为啥需要这条

3、接下来就是去执行应用程序
在定义主函数的时候参数的意义:int main(int argc, char *argv[])
argc表示输入参数的个数,argv保存着输入的参数的个数,个数随便自己定,自己在函数内对输入的参数判断即可,argv[0]表示第一个参数,依次类推;
使用scanf函数来读取键盘上输入的命令:ret = scanf(“%d”,&cmd);返回值是输入的参数个数,比如这里就是返回参数1;然后想单片机编程一样进行判断输入的值进行处理,在scanf后面需要跟一个函数吸收掉scanf后面的回车符,不然会影响下一次输入gets(str); str是一个数组 unsigned char str[100];大小无所谓
如果输入的命令为3,则再需要进行一次scanf,输入想重新定时的时间
然后执行应用程序当中的ioctl函数,ioctl(fd,cmd,arg);cmd表示命令,arg表示输入的定时周期,然后调用驱动中的ioctl函数;

4、内核中的ioctl函数
先设置为私有数据:struct timer_dev *dev = (struct timer_dev *)filp->private_data;
然后使用switch结构判断输入的命令
删除定时器函数的命令:del_timer_sync(&dev->timer); //取dev结构体中的timer成员的地址作为这个函数的参数
struct timer_dev *dev = (struct timer_dev *)filp->private_data;
打开定时器的命令,这里在打开定时器的时候设置的定时时间是在驱动中的open函数当中设置的,而且设置过程中需要对该设置加自旋锁
spin_lock_irqsave(&dev->lock,flags); //保存中断状态,禁止本地中断,并获取自旋锁。
timerperiod = dev->timerperiod; //设置定时器周期,这个定时器周期从哪里来,是不是从open函数中的赋值的来的
spin_unlock_irqrestore(&dev->lock,flags); //释放锁

//这个函数用于修改定时器值,第一个参数为要修改的定时器,第二个参数为修改后的超时时间;将毫秒、微秒、纳秒转换为 jiffies 类型。
//如果定时器没有被激活,则使用此函数顺便进行激活
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
设置定时器的命令:这里定义的SETPERIOD_CMD也是_IO类型的,是因为应用程序当中设置了两次scanf;同样需要加自旋锁:
spin_lock_irqsave(&dev->lock,flags);
dev->timerperiod = arg;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(arg));

4、定时结束后会进入定时器回调函数
void timer_function(struct timer_list *t)
{
struct timer_dev *dev = from_timer(&timerdev,t,timer);

static int sta = 1;
int timerperiod;
unsigned long flags;

sta = !sta ;//在这里实现timer灯的反转
gpio_set_value(dev->timer_gpio,sta);

//重启定时器
spin_lock_irqsave(&dev->lock,flags);
timerperiod = dev->timerperiod;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timerperiod));

}
在这里需要对定时器进行重启,因为定时器定时到了会自动关闭,需要重启;

在这里实现灯的状态的翻转,也就是设置不同的io口输出电平

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值