驱动程序--AD转换器、GPIO
一、AD转换器
Inline内联函数,修饰的函数在编译时处理,不是在预处理时编译
动态加载内核模块失败
原因:内核已存在ADC驱动
关闭内核的ADC驱动
内核把ADC的时钟关闭了,需要开启时钟
二、ioctl
底层
应用层
内核所用过的数字(宏定义)
底层定义一个ioctl,要使用内核没用过的
例如:x设备号 00-02 所使用到的命令编号 adc use it 描述
三、GPIO
在内核空间控制gpio有两种方法,第一种是通过调用gpiolib的接口来控制gpio;第二种是通过ioremap来控制gpio
中间层是 Gpiolib,用于管理系统中的 GPIO。Gpiolib 汇总了 GPIO 的通用操作,根据 GPIO 的特性,Gpiolib 对上(其他 Drivers)提供的一套统一通用的操作 GPIO 的软件接口,屏蔽了不同芯片的具体实现。对下,Gpiolib 提供了针对不同芯片操作的一套 framework,针对不同芯片,只需要实现 Specific Chip Driver ,然后使用 Gpiolib 提供的注册函数,将其挂接到 Gpiolib 上,这样就完成了这一套东西。
1.gpiolib控制gpio
int gpio_request(unsigned gpio, const char *label);
/*向内核申请 gpio,要使用 GPIO 首先应该向内核进行申请,返回 0,代表申请成功,
*可以进行后续操作*/
void gpio_free(unsigned gpio);
/*对应 gpio_request,是使用完gpio以后把gpio释放掉*/
int gpio_direction_input(unsigned gpio);
/*设置 GPIO 为输入*/
int gpio_direction_output(unsigned gpio, int value);
/*设置 GPIO 为输出*/
int gpio_get_value(unsigned gpio);
/*读取 GPIO 的值*/
int gpio_set_value(unsigned gpio, int value);
/*设置 GPIO 的值*/
使用后要记得释放
2.ioremap控制gpio
这种方法会降低程序的可读性,不建议使用。
linux内核空间访问的地址为虚拟地址(3~4GB),故在内核空间操作某个寄存器时,需先通过ioremap函数将物理地址映射成虚拟地址。
函数原型:
void *ioremap(phys_addr_t phys_addr, size_t size);
参数
phys_addr
:需要映射的物理地址。size
:需要映射的区域大小(以字节为单位)。
返回值
- 成功时,返回一个指向映射区域起始位置的虚拟地址指针。
- 失败时,返回
NULL
。
使用
用ioremap() 获取寄存器的地址:
#define ADCCON 0x58000000
#define ADCDAT0 0x5800000C
#define CLKCON 0x4C00000C
static volatile unsigned long * adccon;
static volatile unsigned long * adcdat0;
static volatile unsigned long * clkcon;
adccon = ioremap(ADCCON, sizeof(*adccon));
adcdat0 = ioremap(ADCDAT0, sizeof(*adcdat0));
clkcon = ioremap(CLKCON, sizeof(*clkcon));
通过 readl() 或者 writel() 函数直接操作映射后的地址:
取消映射:
iounmap(clkcon);
iounmap(adcdat0);
iounmap(adccon);
四、中断底半部
中断上半部通常执行快速、紧急的任务,如读取硬件寄存器中的中断状态、清除中断标志等,并暂时关闭中断请求。而下半部则用于延迟处理上半部未完成的工作,这些工作通常比较耗时,或者可以容忍一定的延迟。因此,下半部不会被立即执行,而是由系统自行安排在适当的时机以异步的方式执行。
1.中断上半部
按键上半部中断程序
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/string.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#define DEV_NAME "key"
static irqreturn_t irq_handler(int irq_num, void * dev)
{
printk("irq_handler irq_num = %d dev = %d\n", irq_num, *(int *)dev);
return IRQ_HANDLED;
}
static int open(struct inode * node, 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();
return 0;
}
static int close(struct inode * node, struct file * file)
{
printk("key close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int arg = 100;
static int __init key_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,"eint8", &arg);
if(ret < 0)
goto err_request_irq;
printk("key_init! #############################\n");
return 0;
err_misc_register:
misc_deregister(&misc);
printk("key_init faikey\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8, &arg);
misc_deregister(&misc);
printk("err_request_irq ...\n");
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(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
注意:中断底半部的软中断(linux内部实现的机制)和汇编里的软中断不一样。
workqueue是进程
2.tasklet
tasklet结构体
struct tasklet_struct {
struct tasklet_struct *next; /* 链表中的下一个tasklet */
unsigned long state; /* tasklet的状态 */
atomic_t count; /* 引用计数器 */
void (*func)(unsigned long); /* tasklet处理函数 */
unsigned long data; /* 给tasklet处理函数的参数 */
};
tasklet_struct.func成员是tasklet的处理程序,data是其唯一参数。state成员只能在{0, TASKLET_STAT_SCHED, TASKLET_STATE_RUN}之间取值。
TASKLET_STAT_SCHED:表明tasklet已被调度,正准备投入;
TASKLET_STATE_RUN:表面该tasklet正在运行。只有在多处理器的系统上,才会作为一种优化来使用,单处理器系统任何时候都清楚单个tasklet是不是在运行。
tasklet不能做耗时操作,在tasklet加入sleep(2)延时会导致栈崩溃
sleep()不准确,delay()占用系统资源,所以短时间用delay,长时间用sleep
3.workqueue
4.等待队列
主要操作:初始化等待队列头
添加等待队列项
唤醒等待队列
删除等待队列项
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/string.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.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; //这行代码声明了一个静态的等待队列头名为wq
static int condition = 0;
static inline void init_adc(void)
{
*adccon = (1 << 14) | (19 << 6);
}
static inline void adc_start(void)
{
*adccon |= (0x1 << 0);
}
static inline unsigned short adc_read(void)
{
unsigned short value = *adcdat0 & 0x3ff;
return value;
}
static inline void set_channel(unsigned char num)
{
*adccon &= ~(0x7 << 3);
*adccon |= (num << 3);
}
static irqreturn_t irq_handler(int irq_num, void * dev)
{
condition = 1;
wake_up_interruptible(&wq);
printk("irq_handler irq_num = %d\n", irq_num);
return IRQ_HANDLED;
}
static int open(struct inode * node, struct file * file)
{
printk("adc open ...\n");
init_adc();
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
unsigned short data = 0;
size_t len_cp = (sizeof(data) < len) ? sizeof(data) : len;
condition = 0;
adc_start();
wait_event_interruptible(wq, condition);
data = adc_read();
copy_to_user(buf, &data, len_cp);
return len_cp;
}
static int close(struct inode * node, struct file * file)
{
printk("adc close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
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_DISABLED, "irq_adc", NULL);
if(ret < 0)
goto err_request_irq;
init_waitqueue_head(&wq);
adccon = ioremap(ADCCON, sizeof(*adccon));
adcdat0 = ioremap(ADCDAT0, sizeof(*adcdat0));
clkcon = ioremap(CLKCON, sizeof(*clkcon));
*clkcon |= (1 << 15);
printk("adc_init! *clkcon = %lx #############################\n", *clkcon);
return 0;
err_misc_register:
misc_deregister(&misc);
printk("adc_init faiadc\n");
return ret;
err_request_irq:
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, NULL);
misc_deregister(&misc);
printk("err_request_irq ...\n");
return ret;
}
static void __exit adc_exit(void)
{
iounmap(clkcon);
iounmap(adcdat0);
iounmap(adccon);
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, NULL);
misc_deregister(&misc);
printk("adc_exit #############################\n");
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");
wait_event_interruptible()
是一个宏,当执行到 wait_event_interruptible(wq, condition);
时,如果 condition
为假(即ADC转换未完成),则当前进程会被添加到 wq
等待队列中,并进入睡眠状态。进程会一直睡眠,直到中断服务例程(ISR)或其他线程改变了 condition
的值,使其变为真。这通常意味着ADC转换已经完成,数据已经准备好被读取
wake_up_interruptible()
函数用于唤醒在等待队列上睡眠的、并且可以被信号中断的进程
原文地址:https://blog.csdn.net/T66656/article/details/142833211
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!