所用的板子是正点原子stm32mp157。
参考的教程是正点原子驱动开发教程。
Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序。
中断周期性产生的频率就是系统频率,也就是节拍率,比如100HZ,1000HZ。
高节拍率和低节拍率的优缺点:
- 高节拍率会提高系统时间精度。
- 高节拍率会导致中断产生的更加频繁,加剧系统的负担。
- 软件定时器不像硬件定时器一样,直接给周期值,而是设置期满以后的时间节点。
- 内核定时器不是周期性的,一次定时时间到了以后会关闭,除非重新打开。
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);
}
最后上开发板运行。
实验效果因为是视频,传不上来,就不放了, 反正就是闪烁嘛。
成功!
有问题欢迎讨论!