Linux驱动15 --- buildroot杂项驱动开发方法
目录
一、中断
1.1 介绍
1.2 Linux 下的中断
须知
1、SGI 软件中断
2、PPI 私有外设中断
3、SPI 共享外设中断
1.3 Linux 下的中断开发方式
1.设备树下的中断 --- 重点
获取中断号
使能中断 --- 目前不用
注册中断服务函数
注销中断服务函数
失能中断 --- 目前不用
2.一般中断开发方式 ---了解
获取中断号
注册中断服务函数
释放终端服务函数
二、等待队列
2.1 介绍
等待队列的使用
等待队列的申请
等待队列的阻塞
等待队列的释放
三、定时器
3.1 介绍
内核定时器的使用
定时器的初始化
定时器的添加
定时器的删除
定时器的调用
一、中断
1.1 介绍
中断是一个信号,是一个异常,打断正在运行的过程,转而执行中断服务函数
中断会有分类:
在 MCU 中:外部中断(EXIT)、定时器中断(TIM)、串口中断(USART)、看门狗、ADC 中断、DMA 中断等等
其中应用最多的就是串口中断和定时器中断
中断的唯一标识:中断向量
中断向量表 --- 启动文件
中断服务函数 --- 启动文件
无参无返回值
中断控制器:NVIC
中断向量,使能,抢占(占先)优先级,响应(次级)优先级
数字越小,优先级越高
1.2 Linux 下的中断
须知
Linux 是一个整个操作系统,我们可以用中断,但是接触不到控制中断
和 FreeRTOS 类似 --- 当操作系统接手板子的管理,那么操作系统会帮忙管理中断
在 Linux 中断 --- 共享中断(SPI),私有中断(PPI),软件中断(SGI)
三类中断中我们接触最多的就是共享中断 --- 其中包含了 GPIO
不同的中断号可以使用相同的中断服务函数
但是需要保证中断的触发方式是相同的
在 Linux 的中断唯一标识 --- 中断号
中断号是需要获取的
在 Linux 的中断控制器 --- GIC
在目前的编程过程中接触不到
1、SGI 软件中断
16 个,中断号:0—15。通过向 ICDSGIR 寄存器写入 SGI 中断号、CPU ID,来产生一个软件中断;通过读 ICCIAR 寄存器或者向 ICDICPR 寄存器相应的比特位写 1,可以清除中断。所有的 SGI 为边沿触发。
2、PPI 私有外设中断
每个 CPU(CPU0、CPU1)连接 5 个私有中断,中断号:27—31。ICDICFR1 为 PPIs 的优先级及触发条件控制寄存器,是只读的,因而 PPIs 的触发条件也不可更改。需要注意到的是,PL 部分的快速响应中断 FIQ(fast interrupt)信号与普通中断 IRQ(interrupt)需要被送往中断控制器中,所以即便 ICDICFR1 规定 IRQ 与 FIQ 的响应等级为 low level,但是他们的在 PS 与 PL 接口的响应等级仍是 high。
3、SPI 共享外设中断
中断号:32-95。由 PS 和 PL 上的各种 I/O 控制器和存储器控制器产生,如 GPIO、 DMA、定时器等模块的中断信号,这些中断信号会被路由到 CPU。PS 的外设产生的SPI 中断也会路由到 PL 上。
1.3 Linux 下的中断开发方式
1.设备树下的中断 --- 重点
中断的关键字:irq、interrupt
注意:当前在设备树中的触发方式选择为 IRQ_TYPE_EDGE_FALLING 下降沿
IRQ_TYPE_EDGE_RISING 上升沿
在编程的时候也要选择为 IRQ_TYPE_EDGE_FALLING
单独编译内核,烧录内核
GPIO 的中断(EXIT)在 MCU 是做什么的 --- 按键检测
使用中断检测的优势 --- 更加准确的检测按键的状态(消抖)
获取中断号
头文件:#incluide <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
函数原型
int platform_get_irq(struct platform_device *dev, unsigned int num)
函数参数
dev:直接使用(*probe)的形参
num:下标 ,从 0 开始
函数返回值
成功返回中断号,失败返回负数
理论上中断需要使能
但是在这个板子下,使能中断会报警告
使能中断 --- 目前不用
函数原型
void enable_irq(unsigned int irq)
注册中断服务函数
函数原型
int __must_check devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
函数参数
dev:在(*probe)函数的参数 struct platform_device{struct device dev;}
irq:中断号
handler:中断服务函数的原型
irqreturn_t __irqfunc(自己名字) (*irq_handler_t)(int, void *)
函数参数
int:表示进入中断服务函数的中断号
换句话说,是产生中断的中断号
void *:是中断服务函数注册函数传入的参数 ,一般不用
irqflags:中断触发标志
IRQ_TYPE_EDGE_RISING 上升沿
IRQ_TYPE_EDGE_FALLING 下降沿
devname:不重复,无所谓,尽可能有意义
dev_id:传入中断服务函数的参数 ,一般不用,填 NULL
函数返回值
必须承接,否则报警告
注销中断服务函数
函数原型
void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
函数参数
dev:同上
irq:中断号
dev_id:传递给中断服务函数的参数
当前发现,按键已经比较灵敏了
但是,会发现本该阻塞的 read,现在没有阻塞
失能中断 --- 目前不用
函数原型
void disable_irq(unsigned int irq)
函数参数
irq:中断号
2.一般中断开发方式 ---了解
获取中断号
函数原型
int gpio_to_irq(unsigned int gpio)
函数参数
gpio:GPIO 号
函数返回值
成功返回中断号,失败返回负数
注册中断服务函数
函数原型
int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
函数参数
irq:中断号
handler:中断服务函数
flags:中断触发方式
name:无所谓,尽可能由意义
dev:传递给中断服务函数的参数
函数返回值
需要承接,否则报警告
释放终端服务函数
函数原型
void *free_irq(unsigned int irq, void *dev_id)
函数参数
irq:中断号
dev_id:传递给中断服务函数的参数
二、等待队列
2.1 介绍
不用往队列的方向去考虑,用起来很简单
等待队列是内核阻塞最常用的实现方式
我们使用等待队列主要是为了让 read 函数阻塞
等待队列,是指 linux 系统中进程所组成的队列,就是需要其他事件的发生才会自己本身被唤醒的进程,也就是说这些进程本身是在等待其他某些进程为他们提供进程发生的条件。他们是属于消费者的,但是他们要消耗的东西还没有产生,这些就是处于等待状态的进程,组成了等待队列。等待队列很容易使用, 尽管它的设计很是微妙, 但不需要知道它的内部细节。
等待队列的使用
关键字:wait_queue
头文件:#include <linux/wait.h>
#include <linux/sched.h>
等待队列的申请
原型
DECLARE_WAIT_QUEUE_HEAD(name)
函数参数
name:就是自己申请等待队列的名字
等待队列的阻塞
函数原型
wait_event_interruptible(wq_head,condition)
函数参数
wq_head:就是等待队列的名字
condition:条件 ,要求传入一个 0 值就是阻塞
等待队列的释放
函数原型
wake_up_interruptible(x)
函数参数
x:需要给等待队列取地址传入
并且需要将等待队列的条件赋值为真
三、定时器
3.1 介绍
定时器的功能就是计数
内核中的定时器相较于 MCU 没有那么多功能
内核定时器的时钟主频只有 --- 300Hz
这个主频可以自己改:100 ~ 1000
修改过后需要重新编译内核
300Hz 意味这 → 每秒钟记 300 次数
记一次数需要 1/300 s
对于内核中的定时器来说,它的计数是依靠时间基准实现的
这个时间基准指的是:当前系统运行时间
在内核中通过宏定义表示 --- jiffies
如果使用定时器计时,需要
当前系统运行时间+计时的时间 ,假如计时时间单位是 s
jiffies+计时的时间*300
这个写法在 MCU 中用过
MCU 在学习系统滴答定时器之后,用滴答定时器写 delay 函数
原理:
1、定义一个变量,放在滴答定时器的中断中一直做++
2、在延时函数中,定义一个变量,获取当前系统运行时间,加上需要延时的时间,作为一个比较值在某一时刻,系统运行时间是固定的
3、用系统运行时间和第二步得到的比较值做比较
比较值是定值,而系统运行时间是变值
在后续某一时刻,变值必然会大于定值
内核定时器的使用
关键字:timer
头文件:#include <linux/timer.h>
定时器的初始化
函数原型
__init_timer(_timer, _fn, _flags)
函数参数
_timer:定时器核心结构体
struct timer_list{
unsigned long expires; //计时时间
}
_fn:void (*function)(struct timer_list *)
定时器计时完成执行的函数 ,计时完成一次,执行一次
_flags:填 0 即可
定时器的添加
函数原型
void add_timer(struct timer_list *timer)
函数参数
定时器核心结构体
定时器的删除
函数原型
int del_timer(struct timer_list * timer)
函数参数
同上
函数返回值
删除活跃定时器返回 1,删除不活跃定时器返回 0
定时器的调用
函数原型
int mod_timer(struct timer_list *timer, unsigned long expires)
函数参数
timer:定时器核心结构体
expires:计时时间
jiffies+计时时间
这个 mod_timer 是调用定时器的关键
注意:为什么调用一次 add_timer 之后会进入定时器的回调函数
在 add_timer 中,默认调用了 mod_timer