“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=128
return 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 0
del_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/TimerDriver
filename = 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.o
CC := arm-none-linux-gnueabihf-gcc
drv:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
app:
$(CC) $(MyAPP).c -o $(MyAPP)
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm $(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”是否存在