自学内容网 自学内容网

驱动程序--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)!