DAY9-PWM,内核配置和输入子系统

285 阅读6分钟

十四.PWM驱动蜂鸣器

1.概念

PWM指的是脉冲宽度调制,实际上就是一种占空比可调节的方波

image-20220802000256476

PWM广泛应用于电路控制,比如呼吸灯,直流电机,步进电机,风力发电等等。

6818开发板的蜂鸣器连接的是一个具有PWM功能的引脚,所以可以通过PWM来控制蜂鸣器的频率和音量。

PWM = 定时器+IO输出

PWM输出由定时器控制,PWM定时器在标准定时器的基础上加上一个比较寄存器,用于控制翻转电平的时机。此外还提供极性(电平顺序)和有效电平(高/低)的配置。

2.原理图

image-20220802000304727

image-20220802000312417

GPIOC14的复用功能2是PWM功能

3.CPU芯片手册

(1)逻辑框架图

image-20220802000317029

(2)控制操作

定时器操作:

image-20220802000324309

定时器的初始化:

image-20220802000329717

注意:启动定时器之前要手动更新比较值和计数值,方法时开关手动更新位。

极性开关:

image-20220802000337017

PWM定时器的配置步骤:

1.写入初始计数值和比较值到对应的寄存器
2.使能手动更新位,再关闭手动更新位
3.设置极性
4.启动定时器(打开自动重装载)

(3)PWM寄存器

image-20220802000343399

image-20220802000347753

1)TCFG0 ------------- 配置第一次预分频的值

image-20220802000352001

2)TCFG1 ---------------- 配置第二次分配的值

image-20220802000413307

image-20220802000527250

3)TCON -------------- 控制寄存器

image-20220802000438251

image-20220802000420527

4)TCNTB2 --------------- 定时器2的初始计数值

image-20220802000852022

5)TCMPB2 -------------- 定时器2的比较计数值

image-20220802000821783

(4)PWM配置总结

配置GPIO的复用功能
原始频率 --------------- 0x4200000
(1)配置定时器
    配置分频值,得到参考频率
    配置初始计数值,确定定时器的周期
    配置自动重装载功能,让定时器自动循环工作
    配置控制寄存器控制定时器的动作
    
(2)配置PWM相关内容
    配置极性,确定默认电平和高低电平顺序
    配置比较计数,确定PWM的占空比    

练习:

控制蜂鸣器播放其他两首歌

十六.Linux内核的配置和裁剪

使用内核配置界面来配置新加入的代码是否编译进内核

内核配置界面(make menuconfig)是根据每个目录下的Kconfig文件生成的

1.Kconfig语法

(1)config条目 ---------------- 对应一个变量,会自动载变量名前加CONFIG_

image-20220802000546738

//生成congfig条目界面,一般用来控制一个文件/目录的编译动作
​
prompt:提示信息
变量类型:
    bool - 二值型变量(y/n)
    tristate - 三值型变量(y/m/n)
    string - 字符串变量
    hex - 十六进制变量
    int - 整型变量
default:config条目的默认值
depends on:依赖项,表示当前条目必须在依赖的选项被选中后才出现
select:反向依赖,当前项被选中,反向依赖的项自动被选中
help:帮助信息    

(2)menu条目 -------------------- 表示一个菜单

以menu开头,以endmenu结束,中间的内容就是菜单的内容

image-20220802000555117

(3)source条目 ----------------- 调用其他位置的Kconfig生成界面

(4)choice条目 ----------------- 表示多选一的选项

以choice开头,以endchoice结束,中间的内容就是多选一选项的内容

image-20220802000600201

(5)menuconfig条目 ---------------- 表示待选项的菜单

通常和if/endif联用,表示选中之后某些条目才会出现

image-20220802000605586

(6)comment条目 ------------------ 文字信息

注:make menuconfig配置完成之后,配置结果(变量的值)保存在内核源代码顶层目录的.config文件中。Makefile编译内核时会去读取.config文件中的变量来控制编译行为。

image-20220802000610195

obj-y ------------------ 编译进内核
obj-m ------------------ 变异成模块
 obj-n ------------------ 不编译

2.修改内核配置界面实现对新加入内核源代码的控制

(1)将源代码加入到内核对应的目录下

image-20220802000616158

(2)为配置界面添加控制新加入源代码的内容

在新添加的源代码目录(drivers/gec6818)创建一个Kconfig文件,添加以下内容

menu "GEC6818 Driver"
​
config GEC6818_LED
    tristate "led platform driver"
    default y
    help
        this is a gpio led platform driver for GEC6818.
​

修改上一级目录(drivers)的Kconfig,添加如下内容

source "drivers/gec6818/Kconfig"

(3)修改Makefile

添加的源代码目录(drivers/gec6818)下新建Makefile,内容如下:

# Makefile for gec6818 drivers
​
obj-$(CONFIG_GEC6818_LED)  +=       led_dev.o led_drv.o

修改上一级目录(drivers)的Makefile,添加一行

obj-y  +=              gec6818/

3.配置内核

Device Drivers --->
    GEC6818 Driver  --->
        <*> led platform driver

4.重新编译内核

cp .config arch/arm/configs/GEC6818_defconfig
     
cd ..
./mk -k

编译完成后烧写内核

总结:

通过Kconfig文件生成配置界面,配置完成之后的结果保存在.config文件中,内核编译时Makefile会读取.config中的变量的值来控制编译行为。

image-20220802000639561

十七.输入(input)子系统

1.概念

输入子系统用于实现Linux输入设备驱动的一种框架。Linux内核将其中固定的部分放入内核中,驱动开发只需要实现其中不固定的部分。

输入子系统对应的设备文件是固定名称 /dev/input/event0...1...2...

/dev/event0...1...2...

输入子系统对应的设备文件(驱动)的主设备号是13

2.输入子系统用来实现驱动的设备

键盘 鼠标 按键 触摸屏 游戏手柄 游戏摇杆.......

3.输入子系统在内核中的框架

image-20220802000659134

4.输入子系统中设备驱动层的编程实现

需要包含的头文件:

#include <linux/input.h>

(1)分配input_dev结构

struct input_dev *input_allocate_device(void);
//释放
void input_free_device(struct input_dev *dev);

(2)初始化input_dev

1)指定会触发哪些事件

.evbit = 
    #define EV_SYN			0x00//同步事件
    #define EV_KEY			0x01//按键事件
    #define EV_REL			0x02//相对坐标事件
    #define EV_ABS			0x03//绝对坐标事件
    ......

2)指定事件中的数据类型

.keybit = 
    上报的按键事件包含哪些键值
.absbit = 
    上报的绝对坐标事件中包含哪些坐标类型
......        

注:可以使用位原子操作set_bit函数对以上某些成员的某些位置1

     ```c
     void set_bit(int nr, unsigned long *addr);
     //将addr地址上的nr位置1
     ```

(3)注册input_dev到内核

int input_register_device(struct input_dev *dev);
//注销
void input_unregister_device(struct input_dev *dev);

(4)当输入设备需要上报数据时,调用输入子系统提供的接口通知接口层发生了事件,并且传递相关的数据 -------------- input_event函数

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value);
参数:
    dev - input_dev结构
    type - 上报的事件类型
    code - 键值/坐标类型/...
    value - 按键状态/坐标值/...   

(5)在用户空间获取的数据时struct input_event类型

struct input_event {
	struct timeval time;//时间戳
	__u16 type;//事件类型
	__u16 code;//键值/坐标类型/...
	__s32 value;//按键状态/坐标值/... 
};

注:input_dev注册成功后,会在/dev/input目录下新增一个event*的设备文件。

image-20220802000708774

image-20220802000712908

练习:

编写应用程序访问input按键驱动

十八.输入子系统源代码分析

1.input核心层(input_handle) ---------------------- drivers/input/input.c

(1)subsys_initcall(input_init);//系统初始化时调用

//注册设备类
err = class_register(&input_class);
//申请主设备号13和注册cdev
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

(2)操作函数集合

static const struct file_operations input_fops = {
	.owner = THIS_MODULE,
	.open = input_open_file,//仅仅实现了open函数
	.llseek = noop_llseek,
};

(3)其他的操作接口在哪里? -------------- input_open_file

  ```c
  input_open_file函数内容:
      struct input_handler *handler;//声明一个接口层的指针
      const struct file_operations *old_fops, *new_fops = NULL;
      
      //根据次设备号从input_table数组中取出一项input_handler
      //将其中的操作函数集合赋值给new_fops
      handler = input_table[iminor(inode) >> 5];
  	if (handler)
  		new_fops = fops_get(handler->fops);
     //将new_fops赋值给file结构中的f_op,以后在访问设备文件就是访问new_fops中的接口 
     old_fops = file->f_op;
     file->f_op = new_fops;
     //调用new_fpos中的open函数
     err = new_fops->open(inode, file);
  ```

疑问?input_table数组中的元素在哪里填充的 ------------------- input_handler层放进去的