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