STM32mp157驱动开发实验—内核定时器

所用的板子是正点原子stm32mp157。

参考的教程是正点原子驱动开发教程。


Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序

中断周期性产生的频率就是系统频率,也就是节拍率,比如100HZ,1000HZ。

高节拍率和低节拍率的优缺点:

  • 高节拍率会提高系统时间精度。
  • 高节拍率会导致中断产生的更加频繁,加剧系统的负担。
  1. 软件定时器不像硬件定时器一样,直接给周期值,而是设置期满以后的时间节点。
  2. 内核定时器不是周期性的,一次定时时间到了以后会关闭,除非重新打开。
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动会将jiffies初始化为0。
因此,我们在加上周期的时候,需要加上已经产生的节拍数。
jiffies + msecs_to_jiffies(2000)//超时的节点,周期为2s

相关API:

timer_setup 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一
定要先用 timer_setup 初始化一下,其他API如下:
//初始化定时器
void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)
//注册定时器
void add_timer(struct timer_list *timer)
//删除定时器
int del_timer(struct timer_list * timer)
//上一个的同步版,会等待处理器使用完再删除,但是不能用在中断上下文中
int del_timer_sync(struct timer_list *timer)
//修改定时值,并激活
int mod_timer(struct timer_list *timer, unsigned long expires)

timer_list在内核5.4之后就进行了更新,如下:

struct timer_list {
 /*
 * All fields that change during normal runtime grouped to the
 * same cacheline
 */
 struct hlist_node entry;
 unsigned long expires; /* 定时器超时时间,单位是节拍数 */
 void (*function)(struct timer_list *);/* 定时处理函数*/
 u32 flags; /* 标志位 */

 #ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
 #endif
 };

实验

实验内容:利用内核定时器写一个按照一定频率闪烁的LED灯

代码如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atmioc.h>
#include <linux/timer.h>
#include <linux/jiffies.h>


#define LED_ON 1
#define LED_OFF 0

#define DTSLED_CNT 1
#define DTSLED_NAME "dtsled"

#define CLOSE_CMD        _IO(0xEF,1)           //关闭定时器
#define OPEN_CMD         _IO(0xEF,2)           //打开定时器
#define SETPERTOD_CMD    _IO(0xEF,3)           //设置周期



//定义timer设备结构体
struct time_dev{
    dev_t devid;//设备号
    int major;  //主设备号
    int minor;  //次设备号
    struct cdev cdev;//注册函数
    struct class *class;//节点
    struct device *device;//设备
    struct device_node *nd;//设备节点
    struct timer_list timer;//定义一个定时器
    int timerperiod;   //定义默认周期
    int gpiodev;
    int dev_stats;     //设备使用状态,0,表示设备未使用;1则表示被是使用了
    spinlock_t lock;   //自旋锁
};

static struct time_dev timerdev; //led设备


static int led_init(void){

    int ret = 0;
    const char *str;
    
   
    //获取设备树属性内容
    //获取设备节点
    timerdev.nd = of_find_node_by_path("/gpioled");
    if(timerdev.nd == NULL){ 
        return -EINVAL;
    }

    //获取compatible属性
    ret = of_property_read_string(timerdev.nd, "compatible", &str);
    if (ret<0)
    {
        printk("Compatible  find failed\r\n");
        return -EINVAL;
    }
    else if (strcmp(str,"dada,led"))
    {
        printk("gpioled: Compatible match failed!\n");
    }

    //获取status属性
    ret = of_property_read_string(timerdev.nd, "status", &str);
    if(ret < 0){
       printk("status read failed!\r\n");
       return -EINVAL;
    }else if(strcmp(str,"okay")){
       return -EINVAL;
    }

    //获取设备树中的gpio属性,得到led的编号
    timerdev.gpiodev = of_get_named_gpio(timerdev.nd,"led-gpio",0);
    if(timerdev.gpiodev<0){
        printk("Can't get led-gpio!\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n",timerdev.gpiodev);

    //向gpio子系统申请使用gpio,使能gpio
    ret = gpio_request(timerdev.gpiodev,"LED-GPIO");
    if(ret){
        printk(KERN_ERR "gpioled: Failed to request led-gpio\n");//疑问点
        return ret;
    }

    //设置pi0为输出,并且默认上拉
    ret = gpio_direction_output(timerdev.gpiodev,1);
    if(ret < 0)
    {
        printk("Can't set gpio!\r\n");
        return ret;

    }

    return 0;
}



static int led_open(struct inode *inode, struct file *filp)
{
    int ret = 0;
    filp->private_data = &timerdev; /* 设置私有数据 */
    timerdev.timerperiod = 1000;//1000ms
    ret = led_init();
    if(ret < 0){
        printk("led init is fail!\n");
        return ret;
    }
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{  
    struct time_dev *dev = filp->private_data;//传入私有数据
    del_timer_sync(&dev->timer);//删除定时器
    gpio_free(dev->gpiodev);//释放GPIO

    
    return 0;
    
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,
                         loff_t *offt)
{
    int ret = 0;
    return ret;
}

/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
                          size_t cnt, loff_t *offt)
{
    /*
    int ret = 0;
    unsigned char databuf[1];//数据缓冲区
    unsigned char ledstat;//灯的状态
    struct dtsled_dev *dev = filp->private_data;//疑问点
    ret = copy_from_user(databuf,buf,cnt);  
     if (ret < 0)
    {
        printk("kernel receviedata failed!\r\n");
        ret = -EFAULT;
    }
    ledstat = databuf[0];//传进来灯的开关状态
    

    if(ledstat == LED_ON) //开灯
    {
        gpio_set_value(dev->gpiodev,0);

    }else if(ledstat == LED_OFF)//关灯
    {
        gpio_set_value(dev->gpiodev,1);
    }
    */
    return 0;

}

/*
 * @description : ioctl 函数,
 * @param - filp : 要打开的设备文件(文件描述符)
 * @param - cmd : 应用程序发送过来的命令
 * @param - arg : 参数
 * @return : 0 成功;其他 失败
 */

static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd,
			unsigned long arg)
{
   
    int timerperiod;
    unsigned long flags;
    struct time_dev *dev = filp->private_data;

    switch (cmd)
    {

    case CLOSE_CMD:
        del_timer_sync(&dev->timer);
        break;
    case OPEN_CMD:
        spin_lock_irqsave(&dev->lock, flags);
        timerperiod = dev->timerperiod;
        spin_unlock_irqrestore(&dev->lock, flags);
        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));

        break;

    case SETPERTOD_CMD:
        spin_lock_irqsave(&dev->lock, flags);
        dev->timerperiod = arg;
        spin_unlock_irqrestore(&dev->lock, flags);
        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));

        break;
    }

    return 0;
}




const struct file_operations led_fops = {

    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .write = led_write,
    .read = led_read,
    .unlocked_ioctl = timer_unlocked_ioctl,

};

void time_fuction(struct timer_list *arg){

    struct time_dev *dev = from_timer(dev,arg,timer);//将结构体传递过来
    static int sta = 1;
    unsigned long flags;
    int timerperiod;

    sta = !sta;
    gpio_set_value(dev->gpiodev,sta);

    //重启定时器
    spin_lock_irqsave(&dev->lock,flags);//加自旋锁
    timerperiod = dev->timerperiod;
    spin_unlock_irqrestore(&dev->lock,flags);//解锁
    mod_timer(&dev->timer,jiffies+msecs_to_jiffies(timerperiod));//重新设置定时值,并激活定时器

}



//入口函数
static int __init timerled_init(void)
{

   
    int ret = 0;
    spin_lock_init(&timerdev.lock);
    //注册字符
    //构造设备号
    if(timerdev.major)//定义了主设备号
    {

        timerdev.devid = MKDEV(timerdev.major,0);
        ret = register_chrdev_region(timerdev.devid,DTSLED_CNT,DTSLED_NAME);
        if(ret<0){
            printk("dtsled driver register failed!\r\n");
            goto free_gpio;
        }
    }else {
        ret = alloc_chrdev_region(&timerdev.devid,0,DTSLED_CNT,DTSLED_NAME);//没有定义就从内核申请
        if(ret<0){
            printk("dtsled driver register failed!\r\n");
            goto free_gpio;
        }

    }

    //添加字符设备
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev,&led_fops);//初始化
    ret = cdev_add(&timerdev.cdev,timerdev.devid,DTSLED_CNT);
    if(ret<0){
            goto del_unregister;
    }

    //自动创建设备节点
    timerdev.class = class_create(THIS_MODULE, DTSLED_NAME);
    if (IS_ERR(timerdev.class)) {
		pr_err("QAT: class_create failed for adf_ctl\n");
		goto err_cdev_del;
	}

    //创建设备
    timerdev.device = device_create(timerdev.class, NULL,
				   timerdev.devid,
				   NULL,  DTSLED_NAME);
	if (IS_ERR( timerdev.device)) {
		pr_err("QAT: failed to create device\n");
		goto destroy_class;
	}

    timer_setup(&timerdev.timer,time_fuction,0);//初始化定时器
    printk("led_init\r\n");
    return 0;

 destroy_class:
   class_destroy(timerdev.class);
 err_cdev_del:
   cdev_del(&timerdev.cdev);
 del_unregister:
   unregister_chrdev_region(timerdev.devid, DTSLED_CNT);
 free_gpio:
   gpio_free(timerdev.gpiodev);
   return -EIO;

}

//出口函数
static void __exit timerled_fini(void)
{
   
    device_destroy(timerdev.class,timerdev.devid);//删掉设备
    cdev_del(&timerdev.cdev);//注销字符设备
    class_destroy(timerdev.class);//删除设备节点
    unregister_chrdev_region(timerdev.devid, DTSLED_CNT);//注销设备号
    gpio_free(timerdev.gpiodev);//释放gpio
    del_timer_sync(&timerdev.timer);//删除定时器
    printk("led_exit\r\n");

}

//注册驱动和卸载驱动
module_init(timerled_init);
module_exit(timerled_fini);

// 添加作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dada");
MODULE_INFO(intree, "Y");

这是在之前的实验上改的,编译了一下,会发现很多奇怪的错误,没什么大问题,按照修改,最终用以上代码编译成功。

下面开始写测试程序。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <sys/ioctl.h>

#define LED_ON 1
#define LED_OFF 0

#define CLOSE_CMD        _IO(0xEF,1)           //关闭定时器
#define OPEN_CMD         _IO(0xEF,2)           //打开定时器
#define SETPERTOD_CMD    _IO(0xEF,3)      //设置周期

/*
APP运行命令:./timerAPP filename  
*/
int main(int argc, char *argv[])
{
    int fd, retvalue;                // 定义返回值
    int ret = 0;  
    unsigned int cmd;  
    unsigned int arg;
    unsigned char str[10];           
    char *filename;                  // 要打开的文件

    if (argc != 2)                   // 判断输入的参数是否是两个
    {                               
        printf("error usage!\r\n");
        return -1;
    }
    filename = argv[1];              // 第二个参数,既要打开的文件
    /*
    打开文件
    */
    fd = open(filename, O_RDWR);     // 读写的方式打开文件

    if (fd < 0)
    {

        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    /*
    循环读取命令
    */
   while (1)
   {
    printf("input CMD:");
    ret = scanf("%d",&cmd);
    if(ret != 1){
       fgets(str,sizeof(str),stdin);//防止卡死
    }
    if(4 == cmd)
    goto out;

    if(cmd == 1){

        cmd = CLOSE_CMD;
    }else if(cmd == 2){

        cmd = OPEN_CMD;
    }else if(cmd == 3){

        cmd = SETPERTOD_CMD;
        printf("input timerperiod:");
        ret = scanf("%d",&arg);
        if(ret != 1){
            fgets(str,sizeof(str),stdin);
        }

    }
    ioctl(fd,cmd,arg);
   }
   

    /*
    关闭文件
    */
    retvalue = close(fd);
    if (retvalue < 0)
    {
        printf("Can't close file%s\r\n", filename);
        return -1;
    }
    return 0;
 out:
     close(fd);

}

最后上开发板运行。

 实验效果因为是视频,传不上来,就不放了, 反正就是闪烁嘛。

成功!


有问题欢迎讨论!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值