linux 内核动态定时器是依赖于 linux 内核时钟的,动态定时器的周期只能是内核时钟周期的整数倍。
动态定时器不是硬件定时器的驱动,而是利用内核动态定时器产生的时钟周期。
在项目当中常见于表示硬件工作的繁忙状态,不需要应用层控制参与。例如硬盘在频繁读写数据的时候,会发
现硬盘灯快速闪烁,空闲的时候慢速闪烁,这种实现方法就可以使用内核的动态定时器实现。
资料链接:链接:https://pan.baidu.com/s/1U30LSw4CU_yfSfpaoY6J9A
提取码:qeph
adc_drv.c
/*
在项目当中常见于表示硬件工作的繁忙状态,不需要应用层控制参与。例如硬盘在频繁读写数据的时候,会发
现硬盘灯快速闪烁,空闲的时候慢速闪烁,这种实现方法就可以使用内核的动态定时器实现。动态定时器不是硬件定时器的驱动
本测试主要是实现led 闪烁频率受ADC 的电压高低影响,电压越高闪烁越快,否则相反,因为我们希望不需要应用层来控制闪烁频率,
应用层主要是启动ADC工作即可,后面自动进入底层驱动工作,利用动态定时器实现循环自主控制led 闪烁。
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <cfg_type.h>
#include <linux/miscdevice.h>
#include <linux/ioctl.h>
#include <linux/timer.h>
#include <linux/gpio.h>
#include <cfg_type.h>
static struct timer_list gec6818_timer;
static unsigned int timer_period=HZ;
#define GEC6818_ADC_IN0 _IOR('A', 1, unsigned long)
#define GEC6818_ADC_IN1 _IOR('A', 2, unsigned long)
static void __iomem *adc_base_va; //adc的虚拟地址基址
static void __iomem *adccon_va;
static void __iomem *adcdat_va;
static void __iomem *prescalercon_va;
void gec6818_timer_handler(unsigned long data)
{
static int b=0;
//printk("gec6818_timer_handler,data=%lu\n",data);
//重新修改超时时间,进行周期性的超时
mod_timer(&gec6818_timer,jiffies+timer_period);
//添加D7灯的操作,反转电平
b=!b;
gpio_set_value(PAD_GPIO_E+13,b);
}
static int gec6818_adc_open (struct inode * inode, struct file *file)
{
printk("gec6818_adc_open \n");
return 0;
}
static int gec6818_adc_release (struct inode * inode, struct file *file)
{
printk("gec6818_adc_release \n");
return 0;
}
static long gec6818_adc_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
{
unsigned long adc_val=0,adc_vol=0;
int rt=0;
//adc通道选择
switch(cmd)
{
case GEC6818_ADC_IN0:
iowrite32(ioread32(adccon_va)&(~(7<<3)),adccon_va);
break;
case GEC6818_ADC_IN1:
{
iowrite32(ioread32(adccon_va)&(~(7<<3)),adccon_va);
iowrite32(ioread32(adccon_va)|(1<<3),adccon_va);
}break;
default:
printk("IOCTLCMD failed\n");
return -ENOIOCTLCMD;
}
//将电源开启
iowrite32(ioread32(adccon_va)&(~(1<<2)),adccon_va);
//预分频值=199+1,ADC的工作频率=200MHz/(199+1)=1MHz
iowrite32(ioread32(prescalercon_va)&(~(0x3FF<<0)),prescalercon_va);
iowrite32(ioread32(prescalercon_va)|(199<<0),prescalercon_va);
//预分频值使能
iowrite32(ioread32(prescalercon_va)|(1<<15),prescalercon_va);
//ADC使能
//iowrite32(ioread32(adccon_va)&(~(1<<0)),adccon_va);
iowrite32(ioread32(adccon_va)|(1<<0),adccon_va);
//等待AD转换结束
while(ioread32(adccon_va)&(1<<0));
//读取12bit数据
adc_val = ioread32(adcdat_va)&0xFFF;
//关闭CLKIN时钟输入
iowrite32(ioread32(prescalercon_va)&(~(1<<15)),prescalercon_va);
//关闭ADC电源
iowrite32(ioread32(adccon_va)|(1<<2),adccon_va);
//将AD转换的结果值 换算为 电压值
adc_vol = adc_val*1800/4095;
//设置动态定时器的超时时间
if(adc_vol <=450)
timer_period=50;
else if(adc_vol >450 && adc_vol<=900)
timer_period=100;
else if(adc_vol >900 && adc_vol<=1350)
timer_period=250;
else
timer_period=HZ;
rt = copy_to_user((void *)args,&adc_vol,4);
if(rt != 0)
return -EFAULT;
return 0;
}
static ssize_t gec6818_adc_read (struct file *file, char __user *buf, size_t len, loff_t * offs)
{
return 0;
}
static const struct file_operations gec6818_adc_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = gec6818_adc_ioctl,
.open = gec6818_adc_open,
.release = gec6818_adc_release,
.read = gec6818_adc_read,
};
static struct miscdevice gec6818_adc_miscdev = {
.minor = MISC_DYNAMIC_MINOR, //MISC_DYNAMIC_MINOR,动态分配次设备号
.name = "adc_drv", //设备名称,/dev/adc_drv
.fops = &gec6818_adc_fops, //文件操作集
};
//入口函数
static int __init gec6818_adc_init(void)
{
int rt=0;
//混杂设备的注册
rt = misc_register(&gec6818_adc_miscdev);
if (rt)
{
printk("misc_register fail\n");
return rt;
}
//IO内存动态映射,得到物理地址相应的虚拟地址
adc_base_va = ioremap(0xC0053000,0x14);
if(adc_base_va == NULL)
{
printk("ioremap 0xC0053000,0x14 fail\n");
goto fail_ioremap_adc;
}
//得到每个寄存器的虚拟地址
adccon_va = adc_base_va+0x00;
adcdat_va = adc_base_va+0x04;
prescalercon_va = adc_base_va+0x10;
//先释放内核占用的gpio引脚
gpio_free(PAD_GPIO_E+13);
//申请gpio引脚
rt = gpio_request(PAD_GPIO_E+13,"gpioe_13");
if(rt < 0)
{
printk("gpio_request fail\n");
goto gpio_request_fail;
}
//配置GPIOE13为输出模式,GPIOE13引脚为高电平
gpio_direction_output(PAD_GPIO_E + 13,1);
//初始化动态定时器
init_timer(&gec6818_timer);
//配置动态定时器
gec6818_timer.function = gec6818_timer_handler; //处理函数
gec6818_timer.expires = jiffies + HZ; //当前时间开始,HZ个jiffies后,会产生1秒超时
gec6818_timer.data = 100; //传递的参数
//动态定时器加入到内核
add_timer(&gec6818_timer);
printk("gec6818 adc init\n");
return 0;
gpio_request_fail:
iounmap(adc_base_va);
fail_ioremap_adc:
misc_deregister(&gec6818_adc_miscdev);
return rt;
}
//出口函数
static void __exit gec6818_adc_exit(void)
{
gpio_free(PAD_GPIO_E+13);
//动态定时器从内核删除
del_timer(&gec6818_timer);
iounmap(adc_base_va);
misc_deregister(&gec6818_adc_miscdev);
printk("gec6818 adc exit\n");
}
//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用gec6818_adc_init。
module_init(gec6818_adc_init);
//驱动程序的出口:rmmod led_drv调用module_exit,module_exit又会去调用gec6818_adc_exit。
module_exit(gec6818_adc_exit)
//模块描述
MODULE_AUTHOR("stephenwen88@163.com"); //作者信息
MODULE_DESCRIPTION("gec6818 adc driver"); //模块功能说明
MODULE_LICENSE("GPL"); //许可证:驱动遵循GPL协议
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define GEC6818_ADC_IN0 _IOR('A', 1, unsigned long)
#define GEC6818_ADC_IN1 _IOR('A', 2, unsigned long)
int main(int argc, char **argv)
{
int fd=-1;
int rt;
int i=0;
unsigned long adc_vol = 0;
//打开adc设备
fd = open("/dev/adc_drv",O_RDWR);
if(fd < 0)
{
perror("open /dev/adc_drv:");
return fd;
}
while(1)
{
//读取ADC通道0的电压值
rt=ioctl(fd,GEC6818_ADC_IN0,&adc_vol);
if(rt != 0)
{
printf("adc in0 read filed\r\n");
usleep(50*1000);
continue;
}
printf("adc in0 vol: %lu=mv\r\n",adc_vol);
sleep(1);
}
close(fd);
return 0;
}