Linux第83步_采用“Linux内核定时器”点灯以及相关API函数

2024-03-24 07:52

本文主要是介绍Linux第83步_采用“Linux内核定时器”点灯以及相关API函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

“Linux内核定时器”是采用“系统时钟”来实现的。它不是周期性运行的,一旦发生超时就会自动关闭。如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

Limux内核使用全局变量jiffies来记录“系统从启动以来的系统节拍数”,系统启动的时候会将jiffies初始化为0。

需要包含#include <linux/jiffies.h>头文件。

extern u64 __cacheline_aligned_in_smp jiffies_64;

extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies

jiffies为jiffies_64的低32位,用来记录系统从启动以来的“系统节拍数”。见下图:

1、设置系统节拍率

1)、输入“cd /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/回车”,切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/”目录下

2)、输入“ls回车”,

查看“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/”目录下的所有文件和文件夹

记住:“stm32mp1_atk_defconfig”位于“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/configs”

记住:“make menuconfig”用于打开linux内核图形化配置界面

输入“make menuconfig回车”,打开linux内核图形化配置界面

移动向下光标键至“Kernel Features”,见下图:

按“回车键”,得到下图:

移动向下光标键至“Timer frequency”,见下图:

按“回车键”,得到下图:

移动向下光标键至合适的“系统节拍率”。然后按下“回车键”就可以选中了。默认选择100Hz,不要选择“高节拍率”,因为那样做会引起频繁中断,加剧系统负担。因此,不需要修改,按“ESC键”,直至关闭“linux内核图形化配置界面”。

2、处理Linux内核定时器的回绕函数

time_after(unkown, known)

如果unkown>known,超过预设的系统节拍数,time_after()返回真

time_before(unkown, known)

如果unkown<known,没有超过预设的系统节拍数,time_before()返回真

time_after_eq(unkown, known)

如果unkown>=known,超过预设的系统节拍数,time_after_eq()返回真

time_before_eq(unkown, known)

如果unkown<=known,没有超过预设的系统节拍数,time_before_eq()返回真

3、Linux内核定时器的“计数器jiffies”和ms、us、ns之间的转换函数

unsigned int jiffies_to_msecs(const unsigned long j);

//将jiffies类型的参数 j转换为对应的毫秒;

unsigned int jiffies_to_usecs(const unsigned long j);

//将jiffies类型的参数 j转换为对应的微秒;

u64 jiffies_to_nsecs(const unsigned long j);

//将jiffies类型的参数 j转换为对应的纳秒;

unsigned long msecs_to_jiffies(const unsigned int m);

//将毫秒转换为jiffies类型的数据;

unsigned long usecs_to_jiffies(const unsigned int u);

//将微秒转换为jiffies类型的数据;

unsigned long nsecs_to_jiffies(u64 n);

//将纳秒转换为jiffies类型的数据;

4、Linux内核定时器timer_list结构体

Linux内核使用timer_list结构体表示内核定时器,需要包含#include <linux/timer.h>头文件

struct timer_list {

struct hlist_node entry;

unsigned long expires; /*定时器超时时间,单位是节拍数*/

void (*function)(struct timer_list *); /* 定时处理函数*/

u32 flags; /* 标志位 */

#ifdef CONFIG_LOCKDEP

struct lockdep_map lockdep_map;

#endif

};

5、Linux内核定时器初始化函数

void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)

timer指向要初始化的定时器

func为定时器的回调函数

flags为标志位

负责初始化“timer_list结构变量”

void add_timer(struct timer_list *timer)

timer指向要注册的定时器

向Linux内核注册定时器

int del_timer(struct timer_list * timer)

timer指向要被删除的定时器

用于删除一个定时器,返回0表示定时器没有被激活;返回1表示定时器被激活。

int del_timer_sync(struct timer_list *timer)

timer指向要被删除的定时器

等待其他处理器使用完定时器后再删除该定时器,del_timer_sync()不能用在中断服务程序中;

int mod_timer(struct timer_list *timer, unsigned long expires)

timer:要修改超时时间(定时值)的定时器;

expires:修改后的超时时间;

用于修改定时值,如果定时器还没有激活的话,mod_timer()函数会激活定时器;

返回值为0表示调用mod_timer()函数前,该定时器未被激活;

返回值为1表示调用mod_timer()函数前,该定时器已被激活;

举例:

struct timer_list timer; /* 定义定时器 */

/* 定时器回调函数 */

void timer_function(struct timer_list *arg)

{

  /*

  * 定时器处理代码

  */

  /* 如果需要定时器周期性运行的话就使用mod_timer()

  * 函数重新设置超时值并且启动定时器。

  */

  mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));

}

/* 初始化函数 */

void init(void)

{

  timer_setup(&timerdev.timer, timer_function, 0); /* 初始化定时器 */

  timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间2秒 */

  add_timer(&timer); /*向Linux内核注册定时器,启动定时器 */

}

/* 退出函数 */

void exit(void)

{

  del_timer(&timer); /* 删除定时器 */

  /* 或者使用del_timer_sync(&timer) */

}

6、Linux内核短延时函数,需要包含头文件“#include <delay.h>

void ndelay(unsigned long nsecs)延时nsecs个纳秒;

void udelay(unsigned long usecs) 延时usecs个微秒;

void mdelay(unsigned long mseces) 延时mseces个毫秒;

1)、添加“gpio_led”节点

打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,点击“确定”,点击“stm32mp157d-atk.dts”。

stm32mp157d-atk.dts文件如下:

/dts-v1/;

#include "stm32mp157.dtsi"

#include "stm32mp15xd.dtsi"

#include "stm32mp15-pinctrl.dtsi"

#include "stm32mp15xxaa-pinctrl.dtsi"

#include "stm32mp157-m4-srm.dtsi"

#include "stm32mp157-m4-srm-pinctrl.dtsi"

#include "stm32mp157d-atk.dtsi"

/ {

model = "STMicroelectronics STM32MP157D eval daughter";

/*model属性用于描述开发板的名字或设备模块的信息*/

compatible = "st,stm32mp157d-ed1", "st,stm32mp157";

/*compatible属性用于将设备和驱动绑定起来*/

chosen {  /*chosen子节点*/

stdout-path = "serial0:115200n8";

};

aliases {    /*aliases子节点*/

serial0 = &uart4;

    /*给&uart4起个别名叫“serial0”*/

};

reserved-memory {

gpu_reserved: gpu@f6000000 {  /*gpu节点标签为gpu_reserved*/

reg = <0xf6000000 0x8000000>;

no-map;

};

optee_memory: optee@fe000000 {

reg = <0xfe000000 0x02000000>;

no-map;

};

};

stm32mp1_led {

compatible = "atkstm32mp1-led";

/*compatible属性用于将设备stm32mp1_led和驱动“.ko”绑定起来*/

status = "okay";

reg = <0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */

0X5000A000 0X04 /* GPIOI_MODER */

0X5000A004 0X04 /* GPIOI_OTYPER */

0X5000A008 0X04 /* GPIOI_OSPEEDR */

0X5000A00C 0X04 /* GPIOI_PUPDR */

0X5000A018 0X04 >; /* GPIOI_BSRR */

};

};

&cpu1{

cpu-supply = <&vddcore>;

};

&gpu {

contiguous-area = <&gpu_reserved>;

status = "okay";

};

&optee {

status = "okay";

};

修改后的stm32mp157d-atk.dts文件如下:

/dts-v1/;

#include "stm32mp157.dtsi"

#include "stm32mp15xd.dtsi"

#include "stm32mp15-pinctrl.dtsi"

#include "stm32mp15xxaa-pinctrl.dtsi"

#include "stm32mp157-m4-srm.dtsi"

#include "stm32mp157-m4-srm-pinctrl.dtsi"

#include "stm32mp157d-atk.dtsi"

/ {

model = "STMicroelectronics STM32MP157D eval daughter";

/*model属性用于描述开发板的名字或设备模块的信息*/

compatible = "st,stm32mp157d-ed1", "st,stm32mp157";

/*compatible属性用于将设备和驱动绑定起来*/

chosen {  /*chosen子节点*/

stdout-path = "serial0:115200n8";

};

aliases {    /*aliases子节点*/

serial0 = &uart4;

    /*给&uart4起个别名叫“serial0”*/

};

reserved-memory {

gpu_reserved: gpu@f6000000 {  /*gpu节点标签为gpu_reserved*/

reg = <0xf6000000 0x8000000>;

no-map;

};

optee_memory: optee@fe000000 {

reg = <0xfe000000 0x02000000>;

no-map;

};

};

stm32mp1_led {

compatible = "atkstm32mp1-led";

/*compatible属性用于将设备stm32mp1_led和驱动“.ko”绑定起来*/

status = "okay";

reg = <0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */

0X5000A000 0X04 /* GPIOI_MODER */

0X5000A004 0X04 /* GPIOI_OTYPER */

0X5000A008 0X04 /* GPIOI_OSPEEDR */

0X5000A00C 0X04 /* GPIOI_PUPDR */

0X5000A018 0X04 >; /* GPIOI_BSRR */

};

gpio_led {

compatible = "zgq,led";

status = "okay";

led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;

};

};

&cpu1{

cpu-supply = <&vddcore>;

};

&gpu {

contiguous-area = <&gpu_reserved>;

status = "okay";

};

&optee {

status = "okay";

};

2)、编译设备树

、在VSCode终端,输入“make dtbs回车”,执行编译设备树

②、输入“ls arch/arm/boot/uImage -l

查看是否生成了新的“uImage”文件

③、输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l

查看是否生成了新的“stm32mp157d-atk.dtb”文件

拷贝输出的文件:

④、输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;

⑤、输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC

⑥、输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;

⑦、输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;

⑧、输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹

⑨、输入“ls -l /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹

输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车

给“stm32mp157d-atk.dtb”文件赋予可执行权限

输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车 ,给“uImage”文件赋予可执行权限

输入“ls /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹

3)、查看“gpio_led节点

启动开发板,从网络下载程序

输入“root

输入“cd /proc/device-tree/回车

切换到“/sys/firmware/devicetree/base”目录

输入“ls”查看“gpio_led”是否存在

输入“cd gpio_led/回车

输入“ls”查看“/sys/firmware/devicetree/base/gpio_led”目录下的文件和文件夹

输入“cat compatible回车

输入“cat led-gpio回车

输入“cat name回车

输入“cat status回车

4)、创建MyTimer目录

输入“cd /home/zgq/linux/Linux_Drivers/回车

切换到“/home/zgq/linux/Linux_Drivers/”目录

输入“mkdir MyTimer回车”,创建“MyTimer”目录

输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹

5)、添加“LED.c”

#include "LED.h"
#include <linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include <linux/of_gpio.h>
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio()struct MyLED_dev  strMyLED;int Get_gpio_led_num(void);
int led_GPIO_request(void);
void MyLED_free(void);
void led_OnOff(u8 sta);int Get_gpio_led_num(void)
{int ret = 0;const char *str;/* 设置LED所使用的GPIO *//* 1、获取设备节点:strMyLED */strMyLED.nd = of_find_node_by_path("/gpio_led");//path="/gpio_led,使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“gpio_led”//返回值:返回找到的节点,如果为NULL,表示查找失败。if(strMyLED.nd == NULL) {printk("gpio_led node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(strMyLED.nd, "status", &str);//在gpio_led节点中,status = "okay";//指定的设备节点strMyLED.nd//proname="status",给定要读取的属性名字//out_string=str:返回读取到的属性值//返回值:0,读取成功,负值,读取失败。if(ret < 0) return -EINVAL;if (strcmp(str, "okay")) return -EINVAL;//strcmp(s1,s2),当s1<s2时,返回值为负数//strcmp(s1,s2),当s1>2时,返回值为正数//strcmp(s1,s2),当s1=s2时,返回值为0/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(strMyLED.nd, "compatible", &str);//在gpio_led节点中,compatible = "zgq,led";//指定的设备节点strMyLED.nd//proname="compatible",给定要读取的属性名字//out_string=str:返回读取到的属性值//返回值:0,读取成功,负值,读取失败。if(ret < 0) {printk("gpio_led node: Failed to get compatible property\n"); return -EINVAL;}if (strcmp(str, "zgq,led")) {printk("gpio_led node: Compatible match failed\n");return -EINVAL;}/* 4、 根据设备树中的"led-gpio"属性,得到LED所使用的LED编号 */strMyLED.led_gpio_number = of_get_named_gpio(strMyLED.nd, "led-gpio", 0);//在gpio_led节点中,led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>//np=strMyLED.nd,指定的“设备节点”//propname="led-gpio",给定要读取的属性名字//Index=0,给定的GPIO索引为0//返回值:正值,获取到的GPIO编号;负值,失败。if(strMyLED.led_gpio_number < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", strMyLED.led_gpio_number);//打印结果为:“led-gpio num = 128“//因为GPIO编号是从0开始的,GPIOI端口的序号是8,每个端口有16个IO口,因此GPIOI0的编号为8*16=128return 0;
}int led_GPIO_request(void)
{int ret = 0;/* 5.向gpio子系统申请使用“gpio编号” */ret = gpio_request(strMyLED.led_gpio_number, "LED-GPIO");//gpio=strMyLED.led_gpio_number,指定要申请的“gpio编号”//Iabel="LED-GPIO",给这个gpio引脚设置个名字为"LED-GPIO"//返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;if (ret) {printk(KERN_ERR "strMyLED: Failed to request led-gpio\n");return ret;}/* 6、设置PI0为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(strMyLED.led_gpio_number, 1);//gpio=strMyLED.led_gpio_number,指定的“gpio编号”,这里是128,对应的是GI0引脚//value=1,设置引脚输出高电平//返回值:0,设置“引脚输出为vakued的值”成功;负值,设置“引脚输出为vakued的值”失败。if(ret < 0) {printk("can't set gpio!\r\n");}return 0;
}//函数功能:释放MyLED的gpio
void MyLED_free(void)
{gpio_free(strMyLED.led_gpio_number);
}void led_OnOff(u8 sta)
{gpio_set_value(strMyLED.led_gpio_number, sta);//sta=0,写入0,打开LED灯//sta=1,写入1,关闭LED灯	
}

6)、添加“LED.h”

#ifndef __LED_H
#define __LED_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/of.h>   //使能device_node结构struct MyLED_dev{struct device_node *nd; /*设备节点*/int led_gpio_number;   /*led所使用的GPIO编号*/
};
extern struct MyLED_dev strMyLED;extern int Get_gpio_led_num(void);
extern int led_GPIO_request(void);
extern void MyLED_free(void);
extern void led_OnOff(u8 sta);#endif

7)、添加“LinuxTimer.c”

#include "LinuxTimer.h"
#include "LED.h"
#include <linux/timer.h>
//使能timer_list结构
//使能timer_setup(),del_timer(),del_timer_sync()
//使能add_timer(),mod_timer()struct Timer_dev strTimer;void Timeperiod_Init(void);
void Timer_Config(unsigned int cmd, unsigned long period);
void Delete_Timer(void);
void Timer_function(struct timer_list *arg);
void Timer_lock_Init(void);
void Set_Timer(void);void Timeperiod_Init(void)
{strTimer.timeperiod=1000; /* 默认周期为1s */
}void Timer_Config(unsigned int cmd, unsigned long period)
{int timerperiod;unsigned long flags;switch (cmd){case CLOSE_TIMER_CMD: /* 关闭定时器 */del_timer_sync(&strTimer.timer);break;case OPEN_TIMER_CMD: /* 打开定时器 */spin_lock_irqsave(&strTimer.lock, flags);//保存中断状态,禁止本地中断,并获取自旋锁;timerperiod = strTimer.timeperiod;spin_unlock_irqrestore(&strTimer.lock, flags);//将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。mod_timer(&strTimer.timer, jiffies + msecs_to_jiffies(timerperiod));//timer=&strTimer.timer:要修改超时时间(定时值)的定时器;//expires=jiffies + msecs_to_jiffies(timerperiod):修改后的超时时间;//用于修改定时值,如果定时器还没有激活的话,mod_timer()函数会激活定时器;//返回值为0表示调用mod_timer()函数前,该定时器未被激活;//返回值为1表示调用mod_timer()函数前,该定时器已被激活;break;case SET_TIMER_PERIOD_CMD: /* 设置定时器周期 */spin_lock_irqsave(&strTimer.lock, flags);//保存中断状态,禁止本地中断,并获取自旋锁;timerperiod=period;strTimer.timeperiod = timerperiod;//更新定时器周期spin_unlock_irqrestore(&strTimer.lock, flags);//将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。mod_timer(&strTimer.timer, jiffies + msecs_to_jiffies(timerperiod));//timer=&strTimer.timer:要修改超时时间(定时值)的定时器;//expires=jiffies + msecs_to_jiffies(timerperiod):修改后的超时时间;//用于修改定时值,如果定时器还没有激活的话,mod_timer()函数会激活定时器;//返回值为0表示调用mod_timer()函数前,该定时器未被激活;//返回值为1表示调用mod_timer()函数前,该定时器已被激活;break;default: break;}
}void Delete_Timer(void)
{del_timer_sync(&strTimer.timer); /* 删除timer */#if 0del_timer(&strTimer.tiemr);
#endif
}/* 定时器回调函数 */
void Timer_function(struct timer_list *arg)
{
/* from_timer是个宏,可以根据结构体的成员地址,获取到这个结构体的首地址。第一个参数表示结构体,第二个参数表示第一个参数里的一个成员,第三个参数表示第二个参数的类型,得到第一个参数的首地址。
*/struct Timer_dev *dev = from_timer(dev, arg, timer);static int sta = 1;int timerperiod;unsigned long flags;sta = !sta; /* 每次都取反,实现LED灯反转 */led_OnOff(sta);/* 重启定时器 */spin_lock_irqsave(&dev->lock, flags);//保存中断状态,禁止本地中断,并获取自旋锁;timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);//将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));//timer=&dev->timer:要修改超时时间(定时值)的定时器;//expires=jiffies + msecs_to_jiffies(timerperiod):修改后的超时时间;//用于修改定时值,如果定时器还没有激活的话,mod_timer()函数会激活定时器;//返回值为0表示调用mod_timer()函数前,该定时器未被激活;//返回值为1表示调用mod_timer()函数前,该定时器已被激活;
}void Timer_lock_Init(void)
{spin_lock_init(&strTimer.lock);
}void Set_Timer(void)
{/* 初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */timer_setup(&strTimer.timer, Timer_function, 0);
}

8)、添加“LinuxTimer.h”

#ifndef __LinuxTimer_H
#define __LinuxTimer_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/spinlock_types.h> //使能spinlock_t结构
#include <linux/timer.h> //使能timer_list结构
#include <asm-generic/ioctl.h> //使能“_IO宏”#define CLOSE_TIMER_CMD      (_IO(0XEF, 0x1))  /* 关闭定时器 */
#define OPEN_TIMER_CMD       (_IO(0XEF, 0x2))  /* 打开定时器 */
#define SET_TIMER_PERIOD_CMD (_IO(0XEF, 0x3))  /* 设置定时器周期命令*/struct Timer_dev{struct timer_list timer; /* 定义一个定时器 */int timeperiod; /* 定时周期,单位为ms */spinlock_t lock; /* 定义自旋锁 */
};
extern struct Timer_dev strTimer;extern void Timeperiod_Init(void);
extern void Timer_Config(unsigned int cmd, unsigned long period);
extern void Delete_Timer(void);
extern void Timer_lock_Init(void);
extern void Set_Timer(void);#endif

9)、添加“TimerDriver.c”

#include "TimerDriver.h"
#include "LinuxTimer.h"
#include "LED.h"
#include <linux/types.h>
//数据类型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include <linux/ide.h>
//使能copy_from_user(),copy_to_user()
#include <linux/module.h>
//使能TimerDriver_init(),TimerDriver_exit()#define TimerDriver_CNT    1   //定义设备数量为1
#define TimerDriver_NAME  "TimerDriver"  //定义设备的名字struct TimerDriver_dev strTimerDriver;/* 打开设备 */
static int TimerDriver_open(struct inode *inode, struct file *filp)
{int ret;filp->private_data = &strTimerDriver; /*设置私有数据*/Timeperiod_Init();/* 默认周期为1s */ret=Get_gpio_led_num();//读引脚编号if(ret < 0) return ret; /* 1、申请“gpio编号”*/ret=led_GPIO_request();//申请“gpio编号” if(ret < 0) return ret;//向gpio子系统申请使用“gpio编号” 失败printk("TimerDriver_open!\r\n");return 0;
}/*
filp: 要打开的设备文件(文件描述符)
cmd: 应用程序发送过来的命令
arg: 参数
返回值: 0 成功;其他 失败
*/
static long TimerDriver_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{Timer_Config(cmd,arg);return 0; 
}/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t TimerDriver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t TimerDriver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/* 关闭/释放设备 */
static int TimerDriver_release(struct inode *inode, struct file *filp)
{Delete_Timer(); /* 删除timer */led_OnOff(1);   //关闭LED灯MyLED_free();/*关闭驱动文件的时候释放原子变量,便于其它线程使用*/printk("TimerDriver_release!\r\n");return 0;
}/*声明file_operations结构变量MyCharDevice_fops*/
/*它是指向设备的操作函数集合变量*/
const struct file_operations TimerDriver_fops = {.owner = THIS_MODULE,.open = TimerDriver_open,.unlocked_ioctl = TimerDriver_unlocked_ioctl,.read = TimerDriver_read,.write = TimerDriver_write,.release = TimerDriver_release,
};/*驱动入口函数 */
static int  __init TimerDriver_init(void)
{int ret;Timer_lock_Init();/* 初始化自旋锁 *///  ret=Get_gpio_led_num();//读引脚编号
//  if(ret < 0) return ret; /* 1、申请“gpio编号”*/
//  ret=led_GPIO_request();//申请“gpio编号” 
//  if(ret < 0) return ret;//向gpio子系统申请使用“gpio编号” 失败/*2、申请设备号*/strTimerDriver.major=0;if(strTimerDriver.major)/*如果指定了主设备号*/{strTimerDriver.devid = MKDEV(strTimerDriver.major, 0);//输入参数strTimerDriver.major为“主设备号”//输入参数0为“次设备号”,大部分驱动次设备号都选择0//将strTimerDriver.major左移20位,再与0相或,就得到“Linux设备号”ret=register_chrdev_region( strTimerDriver.devid,\TimerDriver_CNT, \TimerDriver_NAME );//strTimerDriver.devid表示起始设备号//TimerDriver_CNT表示次设备号的数量//TimerDriver_NAME表示设备名if(ret < 0)goto free_gpio;}else{ /* 没有定义设备号 */ret=alloc_chrdev_region( &strTimerDriver.devid,\0, \TimerDriver_CNT,\TimerDriver_NAME);/* 申请设备号 *///strTimerDriver.devid:保存申请到的设备号//0:次设备号的起始地址//TimerDriver_CNT:要申请的次设备号数量;//TimerDriver_NAME:表示“设备名字”if(ret < 0)goto free_gpio;strTimerDriver.major = MAJOR(strTimerDriver.devid);/* 获取分配号的主设备号 *///输入参数strTimerDriver.devid为“Linux设备号”//将strTimerDriver.devid右移20位得到“主设备号”strTimerDriver.minor = MINOR(strTimerDriver.devid);/* 获取分配号的次设备号 *///输入参数strTimerDriver.devid为“Linux设备号”//将strTimerDriver.devid与0xFFFFF相与后得到“次设备号”}/*3、注册字符设备*/strTimerDriver.cdev.owner = THIS_MODULE;//使用THIS_MODULE将owner指针指向当前这个模块cdev_init(&strTimerDriver.cdev,&TimerDriver_fops);//注册字符设备,初始化“字符设备结构变量strTimerDriver.cdev”//strTimerDriver.cdev是等待初始化的结构体变量//TimerDriver_fops就是字符设备文件操作函数集合/*4、添加字符设备*/ret=cdev_add(&strTimerDriver.cdev,strTimerDriver.devid,TimerDriver_CNT);//添加字符设备/*&strTimerDriver.cdev表示指向要添加的字符设备,即字符设备结构strTimerDriver.cdev变量*///strTimerDriver.devid表示设备号//TimerDriver_CNT表示需要添加的设备数量if(ret < 0 ) //添加字符设备失败goto del_register;printk("dev id major = %d,minor = %d\r\n", strTimerDriver.major, strTimerDriver.minor);printk("TimerDriver_init is ok!!!\r\n");/*5、自动创建设备节点 */strTimerDriver.class =class_create(THIS_MODULE, TimerDriver_NAME);if (IS_ERR(strTimerDriver.class)){goto del_cdev;}/*6、创建设备 */strTimerDriver.device = device_create(strTimerDriver.class, NULL, strTimerDriver.devid, NULL, TimerDriver_NAME);//创建设备//设备要创建在strTimerDriver.class类下面//NULL表示没有父设备//strTimerDriver.devid是设备号;//参数drvdata=NULL,设备没有使用数据//TimerDriver_NAME是设备名字//如果设置fmt=TimerDriver_NAME 的话,就会生成/dev/TimerDriver_NAME设备文件。//返回值就是创建好的设备。if (IS_ERR(strTimerDriver.device)){goto destroy_class;}/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */Set_Timer();return 0;destroy_class:class_destroy(strTimerDriver.class);//删除类//strTimerDriver.class就是要删除的类del_cdev:cdev_del(&strTimerDriver.cdev);//删除字符设备//&strTimerDriver.cdev表示指向需要删除的字符设备,即字符设备结构strTimerDriver.cdev变量del_register:unregister_chrdev_region(strTimerDriver.devid, TimerDriver_CNT);/* 释放设备号 *///strTimerDriver.devid:需要释放的起始设备号//TimerDriver_CNT:需要释放的次设备号数量;free_gpio://申请设备号失败/*释放gpio编号*///MyLED_free();return -EIO;
}/*驱动出口函数 */
static void __exit TimerDriver_exit(void)
{Delete_Timer(); /* 删除timer *//*1、删除字符设备*/cdev_del(&strTimerDriver.cdev);/*删除字符设备*//*&strTimerDriver.cdev表示指向需要删除的字符设备,即字符设备结构&strTimerDriver.cdev变量*//*2、 释放设备号 */unregister_chrdev_region(strTimerDriver.devid, TimerDriver_CNT);/*释放设备号 *///strTimerDriver.devid:需要释放的起始设备号//TimerDriver_CNT:需要释放的次设备号数;/*3、 删除设备 */device_destroy(strTimerDriver.class, strTimerDriver.devid);//删除创建的设备//strTimerDriver.class是要删除的设备所处的类//strTimerDriver.devid是要删除的设备号/*4、删除类*/class_destroy(strTimerDriver.class);//删除类//strTimerDriver.class就是要删除的类/*5、释放gpio编号*///MyLED_free();
}module_init(TimerDriver_init);
//指定TimerDriver_init()为驱动入口函数
module_exit(TimerDriver_exit); 
//指定TimerDriver_exit()为驱动出口函数MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”
MODULE_INFO(intree,"Y");
//去除显示“loading out-of-tree module taints kernel.”

10)、添加“TimerDriver.h”

#ifndef __TimerDriver_H
#define __TimerDriver_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/cdev.h> //使能cdev结构
#include <linux/cdev.h> //使能class结构和device结构
#include <linux/spinlock_types.h> //使能spinlock_t结构//#define CLOSE_TIMER_CMD      (_IO(0XEF, 0x1))  /* 关闭定时器 */
//#define OPEN_TIMER_CMD       (_IO(0XEF, 0x2))  /* 打开定时器 */
//#define SET_TIMER_PERIOD_CMD (_IO(0XEF, 0x3))  /* 设置定时器周期命令*/struct TimerDriver_dev{dev_t devid; /*声明32位变量devid用来给保存设备号*/int major;   /*主设备号*/int minor;   /*次设备号*/struct cdev  cdev; /*字符设备结构变量cdev */struct class *class;     /*类*/struct device *device;  /*设备*/
};
extern struct TimerDriver_dev strTimerDriver;#endif

11)、添加“Timer_APP.c”

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <sys/ioctl.h>//APP运行命令: ./Timer_APP /dev/TimerDriver
#define CLOSE_TIMER_CMD      (_IO(0XEF, 0x1))  /* 关闭定时器 */
#define OPEN_TIMER_CMD       (_IO(0XEF, 0x2))  /* 打开定时器 */
#define SET_TIMER_PERIOD_CMD (_IO(0XEF, 0x3))  /* 设置定时器周期命令*//*
参数argc: argv[]数组元素个数
参数argv[]:是一个指针数组
返回值: 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{int fd, ret;char *filename;unsigned int cmd;unsigned int arg;unsigned char str[100];if(argc != 2){printf("Error Usage!\r\n");return -1;}//argv[]是指向输入参数./Timer_APP /dev/TimerDriverfilename = argv[1];//argv[1]指向字符串“/dev/LED”fd = open(filename, O_RDWR);//如果打开“/dev/LED”文件成功,则fd为“文件描述符”//fd=0表示标准输入流; fd=1表示标准输出流;fd=2表示错误输出流;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) /* 退出APP */goto out;if(cmd == 1) /* 关闭LED灯 */cmd = CLOSE_TIMER_CMD;else if(cmd == 2) /* 打开LED灯 */cmd = OPEN_TIMER_CMD;else if(cmd == 3){cmd = SET_TIMER_PERIOD_CMD; /* 设置周期值 */printf("Input Timer Period:");ret = scanf("%d", &arg);if (ret != 1){ /* 参数输入错误 */fgets(str, sizeof(str), stdin); /* 防止卡死 */}}ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */}out:ret = close(fd);/* 关闭设备 *///fd表示要关闭的“文件描述符”//返回值等于0表示关闭成功//返回值小于0表示关闭失败if(ret < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}

12)、添加“Makefile”

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
MyAPP := Timer_APP
MyTimer-objs = TimerDriver.o LinuxTimer.o LED.o
obj-m := MyTimer.oCC := arm-none-linux-gnueabihf-gccdrv:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesapp:$(CC)  $(MyAPP).c  -o $(MyAPP)clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) cleanrm $(MyAPP)install:sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f

13)、添加“c_cpp_properties.json

按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。

修改c_cpp_properties.json内容如下所示:

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31","/home/zgq/linux/Linux_Drivers/MyTimer", "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include","/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include","/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "gnu11","cppStandard": "gnu++14","intelliSenseMode": "gcc-x64"}],"version": 4
}

14)、编译

输入“make clean回车

输入“make drv回车

输入“make app回车

输入“make install回车

输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”查看是存在“Timer_APP和MyTimer.ko

15)、测试

启动开发板,从网络下载程序

输入“root

输入“cd /lib/modules/5.4.31/回车

切换到“/lib/modules/5.4.31/”目录

注意:“lib/modules/5.4.31/在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中

输入“ls -l”查看“MyTimer.ko和Timer_APP”是否存在

输入“depmod”,驱动在第一次执行时,需要运行“depmod”

输入“modprobe MyTimer.ko”,加载“MyTimer.ko”模块

输入“lsmod”查看有哪些驱动在工作

输入“ls /dev/TimerDriver -l回车”,发现节点文件“/dev/TimerDriver

输入“./Timer_APP /dev/TimerDriver回车”,APP运行

输入2回车,LED按照1秒亮1秒灭闪烁

输入3回车,再输入100,LED按照100毫秒亮100毫秒灭闪烁

输入1回车,关闭定时器

输入4回车,关闭APP

输入“rmmod MyTimer.ko”,卸载“MyTimer.ko”模块

注意:输入“rmmod MyTimer”也可以卸载“MyTimer.ko”模块

输入“lsmod”查看有哪些驱动在工作。

输入“ls /dev/Timer_APP -l回车”,查询节点文件“/dev/Timer_APP”是否存在

这篇关于Linux第83步_采用“Linux内核定时器”点灯以及相关API函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/840928

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n