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”是否存在

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/766980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

路由器的端口映射能实现什么?

路由器的端口映射是一项重要的网络配置功能&#xff0c;它可以帮助实现局域网内外的设备之间的通信。通过端口映射&#xff0c;我们可以在公网上访问局域网内的设备&#xff0c;方便的进行远程访问、共享文件和资源等操作。 什么是端口映射&#xff1f; 在介绍端口映射之前&am…

【springcloud开发教程】spring cloud config——分布式配置

什么是SpringCloud config分布式配置中心&#xff1f; spring cloud config 为微服务架构中的微服务提供集中化的外部支持&#xff0c;配置服务器为各个不同微服务应用的所有环节提供了一个中心化的外部配置。 spring cloud config 分为服务端和客户端两部分。 服务端也称为 …

MySQL 查询性能优化

优质博文&#xff1a;IT-BLOG-CN​ 如果把查询看作是一个任务&#xff0c;那么它由一些列子任务组成&#xff0c;每个子任务都会消耗一定的时间。如果要优化查询&#xff0c;实际上要优化其子任务&#xff0c;要么消除其中一些子任务&#xff0c;要么减少子任务的执行次数。通常…

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

DevEco Profiler性能调优工具简介

一、概述 应用或服务运行期间可能出现响应速度慢、动画播放不流畅、列表拖动卡顿、应用崩溃或耗电量过高、发烫、交互延迟等现象,这些现象表明应用或服务可能存在性能问题。造成性能问题的原因可能是业务逻辑、应用代码对系统API的误用、对ArkTS对象的不合理持有导致内存泄露…

记录开发STM32遇到的卡死问题-串口

背景&#xff1a;以STM32作为主控&#xff0c;广州大彩显示屏显示&#xff0c;主控实时采集数据&#xff0c;串口波特率115200.设置收发频率为50Hz&#xff0c;即单片机每秒发送50帧数据&#xff0c;每秒接收50帧数据&#xff0c;每帧数据大概14字节。 问题&#xff1a;系统长…

部署prometheus 监控k8s集群

目录 1、主机清单 2、拉取镜像 3、服务安装 4、安装prometheus-operator 5、查看custom metrics api 6、获取prometheus端口 7、将 alertmanager-main 、grafana、prometheus-k8s的端口暴露出来 8、再次查看prometheus端口 9、浏览器访问IP&#xff1a;31940 部署k8集群…

隐私计算实训营学习三:隐私计算框架的架构和技术要点

文章目录 一、隐语架构二、产品层三、算法层3.1 PSI与PIR3.2 Data Analysis-SCQL3.3 Federated Learning 四、计算层4.1 混合调度编译-RayFed4.2 密态引擎4.3 密码原语YACL 五、资源管理层六、互联互通七、跨域管控 一、隐语架构 1、完备性&#xff1a;支持多种技术&#xff0…

基于Springboot的牙科就诊管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的牙科就诊管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍: 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c…

RocketMq 顺序消费、分区消息、延迟发送消息、Topic、tag分类 实战 (消费者) (三)

消费端配置 如下所示&#xff1a;是消费者的配置类&#xff0c;有以下几点需要注意的地方 1、是TargetMessageListener这个监听类&#xff08;下文会把这个监听类的具体代码贴出来&#xff09;&#xff0c;需要把这个监听类订阅。 2、rocketMqDcProperties.getTargetProperties…

【机器学习】k近邻(k-nearest neighbor )算法

文章目录 0. 前言1. 算法原理1.1 距离度量1.2 参数k的选择 2. 优缺点及适用场景3. 改进和扩展4. 案例5. 总结 0. 前言 k近邻&#xff08;k-nearest neighbors&#xff0c;KNN&#xff09;算法是一种基本的监督学习算法&#xff0c;用于分类和回归问题。k值的选择、距离度量及分…

Linux中Oracle数据库启动顺序

首先使用oracle用户登录Linux&#xff0c;用lsnrctl status查看监听状态 1、&#xff1a;进入sqlplus $ sqlplus /nolog SQL> 2&#xff1a;使用sysdab角色登录sqlplus SQL> conn /as sysdba 3&#xff1a;启动数据库 SQL> startup 4&#xff1a;打开Oracle监听 …

微信小程序 - picker-viewer实现省市选择器

简介 本文会基于微信小程序picker viewer组件实现省市选择器的功能。 实现效果 实现代码 布局 <picker-view value"{{value}}" bindchange"bindChange" indicator-style"height: 50px;" style"width: 100%; height: 300px;" &…

OCR研究背景及相关论文分享

光学字符识别&#xff08;Optical Character Recognition&#xff0c;OCR&#xff09;是指使用光学方法将图像中的文字转换为机器可编辑的文本的技术。OCR技术的研究和应用已有数十年的历史&#xff0c;其背景和发展受到多方面因素的影响。 技术需求背景 1.自动化文档处理&am…

【数据分享】2012-2023年全球范围逐年NPP/VIIRS夜间灯光数据

夜间灯光数据是我们在各项研究中经常使用的数据&#xff01;本次我们给大家分享的是2012-2023年全球范围的逐年的NPP/VIIRS夜间灯光数据&#xff0c;数据格式为栅格格式(.tif)。该数据来自于NCEI国家环境信息中心&#xff0c;近期该网站更新了2023年的夜间灯光数据&#xff0c;…

电脑如何关闭自启动应用?cmd一招解决问题

很多小伙伴说电脑刚开机就卡的和定格动画似的&#xff0c;cmd一招解决问题&#xff1a; CtrlR打开cmd,输入&#xff1a;msconfig 进入到这个界面&#xff1a; 点击启动&#xff1a; 打开任务管理器&#xff0c;禁用不要的自启动应用就ok了

Linux——进程间通信管道

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、进程间通信1、进程间通信的目的2、进程间通信发展3、进程间通信分类 二、管道1、什么是管…

tcp 协议详解

什么是 TCP 协议 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制。TCP 是一个传输层的协议。 如下图&#xff1a; 我们接下来在讲解 TCP/IP 协议栈的下三层时都会先解决这两个问题&#xff1a; 报头与有效载荷如何…

【Linux】线程控制{fork() / vfork / clone/pthread_join()/pthread_cancel()}

文章目录 1.fork() / vfork / clone2.线程等待2.1pthread_join()2.2pthread_tryjoin_np() 3.pthread_exit()4.pthread_cancel()5.一些线程相关的问题6.pthread_detach()7.pthread_self()8.认识线程标识符&#xff1a;pthread_self()获取线程标识符9.POSIX线程库 1.fork() / vfo…

【CVPR2024】CricaVPR

【CVPR2024】CricaVPR: Cross-image Correlation-aware Representation Learning for Visual Place Recognition 这个论文提出了一种具有跨图像相关性的鲁棒全局表示方法用于视觉位置识别&#xff08;VPR&#xff0c;Visual Place Recognition &#xff09;任务&#xff0c;命…