虽然用GPIO可以控制按健来,但要是实现按一次关灯,按一次开灯,可以使用中断。
在Linux内核设备驱动中,中断(Interrupt)是一种非常重要的机制,它允许硬件设备在需要时异步地向CPU发送信号,以通知CPU发生了某个事件。CPU响应中断后,会暂停当前正在执行的程序,转而执行中断服务例程(Interrupt Service Routine, ISR),处理完中断后再返回原程序继续执行。这种方式可以大大提高系统对硬件事件的响应速度和效率。
一、中断的基本概念和流程
-
中断源:能够产生中断信号的硬件设备或软件条件。在嵌入式系统中,常见的中断源包括按键、定时器、串行通信接口等。
-
中断号:每个中断源都有一个唯一的中断号(IRQ号),用于标识不同的中断源。
-
中断向量表:中断向量表是一个包含中断服务例程入口地址的表。当CPU接收到中断信号时,会根据中断号在中断向量表中查找对应的中断服务例程地址,并跳转到该地址执行。
-
中断服务例程(ISR):中断服务例程是处理中断事件的函数。它执行必要的操作来处理中断源发出的请求,并在处理完成后返回。
-
中断响应:当CPU接收到中断信号并确认中断有效后,会保存当前执行环境的上下文(如寄存器值、程序计数器等),跳转到中断服务例程执行。
-
中断返回:中断服务例程执行完毕后,会恢复之前保存的上下文,并返回到被中断的程序继续执行。
二、相关函数
1、irq_handler
函数
irqreturn_t irq_handler(int num, void * arg)
这是中断服务例程(Interrupt Service Routine, ISR),它是当指定中断发生时由内核调用的函数。该函数有两个参数:
int num
:表示触发中断的中断号。void *arg
:通常是一个指向设备私有数据的指针,可以用来区分不同的中断源或设备。在这个例子中,它被用作指向arg
全局变量的指针,但在中断处理中实际上并没有使用到这个值。
函数的主要任务包括:
- 使用
printk
打印中断号和传递的参数(尽管参数arg
并未被使用)。 - 设置
condition
标志为1,以通知等待的进程中断已经发生。 - 调用
wake_up_interruptible(&wq)
唤醒所有在等待队列wq
上睡眠且可以被信号中断的进程。 - 返回
IRQ_HANDLED
,表示中断已被处理。
2、request_irq
函数
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev_id);
这个函数用于注册一个中断处理函数。它的参数包括:
irq
:要注册的中断号。handler
:指向中断处理函数的指针。flags
:指定中断触发的类型(如上升沿、下降沿等)和中断的其他行为(如禁用中断自动重新启用)。name
:中断处理函数的名称,用于调试。dev
:指向设备私有数据的指针,该指针会被传递给中断处理函数。
如果注册成功,request_irq
返回0;如果失败,则返回错误码。
3、disable_irq
和 free_irq
函数
-
disable_irq(IRQ_EINT8);
:禁用指定的中断号(这里是IRQ_EINT8
)。这通常在卸载模块或不再需要中断服务时调用,以防止中断处理函数在设备已关闭或不可用时被意外调用。 -
free_irq(IRQ_EINT8, &arg);
:释放之前通过request_irq
请求的中断。它确保中断号不再与任何中断处理函数关联,并允许其他模块或驱动程序重新请求该中断号。第二个参数&arg
是与request_irq
调用中相同的dev
参数,用于标识中断。
三、实现代码
1、驱动中断按健
下列代码如何通过中断处理机制来控制一个按键设备,尽。模块通过注册一个字符设备(使用miscdevice
结构)和请求一个中断(IRQ_EINT8
)来实现其功能。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/string.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <asm-generic/errno-base.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define DEV_NAME "key"
static wait_queue_head_t wq;
static int condition = 0;
irqreturn_t irq_handler(int num, void * arg)
{
printk("num = %d arg = %d\n", num, *(int *)arg);
condition = 1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
static int open (struct inode * inode, struct file * file)
{
printk("key open ...\n");
return 0;
}
static ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user(buf, &value, sizeof(value));
condition = 0;
wait_event_interruptible(wq, condition);
printk("key read ...\n");
return 4;
}
static ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
return 0;
}
static int close (struct inode * inode, struct file * file)
{
printk("key close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int arg = 100;
static int __init key1_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_EINT8, irq_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, "key_irq", &arg);
if(ret < 0)
goto err_request_irq;
init_waitqueue_head(&wq);
printk("key_init ...\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("key misc_register faikey\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8, &arg);
return ret;
}
static void __exit key_exit(void)
{
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8, &arg);
misc_deregister(&misc);
printk("key_exit ###############################\n");
}
module_init(key1_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
全局变量
wait_queue_head_t wq;
:一个等待队列头,用于在中断处理和read
操作之间同步。int condition = 0;
:一个条件变量,用于指示中断是否发生。static int arg = 100;
:传递给中断处理函数的参数(虽然在这个例子中并未在中断处理函数内部使用它)。
中断处理函数
irqreturn_t irq_handler(int num, void * arg)
:这是中断处理函数,当指定的中断发生时会被调用。它打印中断号和传递的参数(虽然参数未被使用),设置condition
变量为1,并唤醒等待队列wq
上的所有进程。最后,它返回IRQ_HANDLED
表示中断已被处理。
文件操作函数
open
、read
、write
、close
:这些是字符设备的标准文件操作函数。在这个例子中,read
函数通过等待wq
上的条件变量condition
变为1来阻塞调用者,模拟按键按下事件。一旦中断处理函数唤醒等待队列,read
函数就会返回,但这里实际上没有返回任何按键值(只是返回一个固定的4字节长度)。write
函数目前为空操作。
模块初始化和退出
key1_init
:模块的初始化函数。它首先注册一个字符设备,然后请求中断IRQ_EINT8
,并设置中断处理函数为irq_handler
。如果任一操作失败,它将执行相应的清理工作。key_exit
:模块的退出函数。它首先禁用并释放中断,然后注销字符设备。
2、驱动ADC
ADC操作的流程
- 采样(Sampling)
- 定义:采样是将时间上连续变化的模拟信号转换为时间上离散变化的信号。简单来说,就是把连续变化的模拟量转换为一系列等间隔的脉冲,脉冲的幅度取决于输入模拟量。
- 遵循原则:采样过程遵循奈奎斯特采样定理,即采样频率必须大于信号中最高频率成分的两倍,这样才能保证采样值能够不失真地反映原来的模拟信号。
- 保持(Holding)
- 定义:在采样步骤之后,得到的一系列样值脉冲需要在下一个采样脉冲到来之前暂时保持,以便进行下一步的转换。因此,在采样电路之后通常会加入保持电路。
- 作用:保持电路确保在采样脉冲宽度(通常是短暂的)内,所取得的样值脉冲幅度保持不变,以便进行后续的量化处理。
- 量化(Quantization)
- 定义:量化是将采样后的模拟电平归化为离散的数字电平。换句话说,它是把采样后的N个点数值按照一定的标准和步骤转化为数字式的0和1。
- 量化误差:由于量化输出的数字信号位数有限,所以输出的数字信号和采样得到的模拟信号之间会存在误差,这个误差被称为量化误差。量化误差的大小与ADC的分辨率(即位数)有关。
- 编码(Encoding)
- 定义:编码是将量化后的结果按照一定的数制形式(如二进制、十进制等)表示出来。这一步是ADC的输出,可以直接供数字电路或系统进行处理和应用
驱动代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/string.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <asm-generic/errno-base.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#define DEV_NAME "adc"
#define ADCCON 0x58000000
#define ADCDAT0 0x5800000C
#define CLKCON 0x4C00000C
static volatile unsigned long * adccon;
static volatile unsigned long * adcdat0;
static volatile unsigned long * clkcon;
static wait_queue_head_t wq;
static int condition = 0;
#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM, ADC_SET_CHANNEL, unsigned char)
irqreturn_t irq_handler(int num, void * arg)
{
printk("num = %d arg = %d\n", num, *(int *)arg);
condition = 1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
static void init_adc(void)
{
*adccon = (1 << 14) | (49 << 6);
}
static void start_adc(void)
{
*adccon |= (1 << 0);
}
static unsigned short read_adc(void)
{
unsigned short data = *adcdat0 & 0x3ff;
return data;
}
static int set_channel(unsigned char channel)
{
if(channel < 0 || channel > 7)
return -EINVAL;
*adccon &= ~(0x7 << 3);
*adccon |= (channel <<3);
return 0;
}
static int open (struct inode * inode, struct file * file)
{
init_adc();
printk("adc open ...\n");
return 0;
}
static ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user(buf, &value, sizeof(value));
unsigned short value = 0;
printk("adc read start ...\n");
condition = 0;
start_adc();
wait_event_interruptible(wq, condition);
value = read_adc();
copy_to_user(buf, &value, sizeof(value));
printk("adc read ...\n");
return sizeof(value);
}
static ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
return 0;
}
static long ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
unsigned char args = 0;
switch(cmd)
{
case CMD_ADC_SET_CHANNEL:
copy_from_user(&args, (unsigned char *)arg, _IOC_SIZE(CMD_ADC_SET_CHANNEL));
ret = set_channel(args);
break;
default :
ret = -EINVAL;
}
return ret;
}
static int close (struct inode * inode, struct file * file)
{
printk("adc close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int arg = 100;
static int __init adc_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_ADC, irq_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, "adc_irq", &arg);
if(ret < 0)
goto err_request_irq;
adccon = ioremap(ADCCON, sizeof(*adccon));
adcdat0 = ioremap(ADCDAT0, sizeof(*adcdat0));
clkcon = ioremap(CLKCON, sizeof(*clkcon));
*clkcon |= (1 << 15);
printk("clkcon = 0x%lx\n", *clkcon);
init_waitqueue_head(&wq);
printk("adc_init ...\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("adc misc_register faiadc\n");
return ret;
err_request_irq:
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, &arg);
return ret;
}
static void __exit adc_exit(void)
{
iounmap(clkcon);
iounmap(adcdat0);
iounmap(adccon);
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, &arg);
misc_deregister(&misc);
printk("adc_exit ###############################\n");
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");
这段代码是一个Linux内核模块,旨在模拟或控制一个基于中断的模数转换器(ADC)设备。它通过使用Linux内核的杂项设备接口(miscdevice
)来注册一个设备,并通过中断来通知ADC转换完成。下面是对代码主要部分的详细介绍:
定义和全局变量
#define
指令用于定义ADC相关的寄存器地址和设备名称。- 全局变量包括指向ADC控制寄存器、ADC数据寄存器和时钟控制寄存器的指针,以及一个等待队列头和一个条件变量。
中断处理函数
irq_handler
是中断处理函数,但在这个示例中,它并没有直接关联到ADC转换完成的中断。通常,ADC转换完成会触发一个硬件中断,但这里只是模拟了中断处理过程,通过打印中断号和参数,并唤醒等待队列上的进程。然而,由于IRQ_ADC
可能不是一个实际存在的中断号,这部分需要根据具体的硬件平台进行调整。
ADC操作函数
init_adc
初始化ADC控制寄存器。start_adc
启动ADC转换。read_adc
读取ADC转换结果。
文件操作函数
open
在打开设备时初始化ADC。read
启动ADC转换,等待中断(尽管这里的中断处理并不直接关联到ADC转换完成),然后读取转换结果并将其复制到用户空间。write
目前没有实现任何功能。close
在关闭设备时执行清理工作(尽管在这个示例中并没有特别的清理工作)。
模块初始化和退出
adc_init
是模块的初始化函数,它注册杂项设备,请求中断,映射寄存器地址到内核空间,并初始化等待队列。adc_exit
是模块的退出函数,它释放资源,包括注销中断、取消寄存器映射和注销杂项设备。
应用代码
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM, ADC_SET_CHANNEL, unsigned char)
int main(int argc, const char *argv[])
{
int fd = open("/dev/adc", O_RDWR);
if(fd < 0)
{
perror("open adc") ;
return -1;
}
unsigned short value = 0;
unsigned char arg = 0;
while(1)
{
arg = 0;
int ret = ioctl(fd, CMD_ADC_SET_CHANNEL, &arg);
printf("ioctl ret = %d \n", ret);
ret = read(fd, &value, sizeof(value));
float v = 3.3 * value / 1024.0;
printf("ret = %d value = %d v = %.3f\n", ret, value, v);
sleep(2);
arg = 1;
ret = ioctl(fd, CMD_ADC_SET_CHANNEL, &arg);
printf("ioctl ret = %d \n", ret);
ret = read(fd, &value, sizeof(value));
v = 3.3 * value / 1024.0;
printf("ret = %d value = %d v = %.3f\n", ret, value, v);
sleep(2);
}
close(fd);
return 0;
}