十四.PWM驱动蜂鸣器
1.概念
PWM指的是脉冲宽度调制,实际上就是一种占空比可调节的方波
PWM广泛应用于电路控制,比如呼吸灯,直流电机,步进电机,风力发电等等。
6818开发板的蜂鸣器连接的是一个具有PWM功能的引脚,所以可以通过PWM来控制蜂鸣器的频率和音量。
PWM = 定时器+IO输出
PWM输出由定时器控制,PWM定时器在标准定时器的基础上加上一个比较寄存器,用于控制翻转电平的时机。此外还提供极性(电平顺序)和有效电平(高/低)的配置。
2.原理图
GPIOC14的复用功能2是PWM功能
3.CPU芯片手册
(1)逻辑框架图
(2)控制操作
定时器操作:
定时器的初始化:
注意:启动定时器之前要手动更新比较值和计数值,方法时开关手动更新位。
极性开关:
PWM定时器的配置步骤:
1.写入初始计数值和比较值到对应的寄存器
2.使能手动更新位,再关闭手动更新位
3.设置极性
4.启动定时器(打开自动重装载)
(3)PWM寄存器
1)TCFG0 ------------- 配置第一次预分频的值
2)TCFG1 ---------------- 配置第二次分配的值
3)TCON -------------- 控制寄存器
4)TCNTB2 --------------- 定时器2的初始计数值
5)TCMPB2 -------------- 定时器2的比较计数值
(4)PWM配置总结
配置GPIO的复用功能
原始频率 --------------- 0x4200000
(1)配置定时器
配置分频值,得到参考频率
配置初始计数值,确定定时器的周期
配置自动重装载功能,让定时器自动循环工作
配置控制寄存器控制定时器的动作
(2)配置PWM相关内容
配置极性,确定默认电平和高低电平顺序
配置比较计数,确定PWM的占空比
练习:
控制蜂鸣器播放其他两首歌
十六.Linux内核的配置和裁剪
使用内核配置界面来配置新加入的代码是否编译进内核
内核配置界面(make menuconfig)是根据每个目录下的Kconfig文件生成的
1.Kconfig语法
(1)config条目 ---------------- 对应一个变量,会自动载变量名前加CONFIG_
//生成congfig条目界面,一般用来控制一个文件/目录的编译动作
prompt:提示信息
变量类型:
bool - 二值型变量(y/n)
tristate - 三值型变量(y/m/n)
string - 字符串变量
hex - 十六进制变量
int - 整型变量
default:config条目的默认值
depends on:依赖项,表示当前条目必须在依赖的选项被选中后才出现
select:反向依赖,当前项被选中,反向依赖的项自动被选中
help:帮助信息
(2)menu条目 -------------------- 表示一个菜单
以menu开头,以endmenu结束,中间的内容就是菜单的内容
(3)source条目 ----------------- 调用其他位置的Kconfig生成界面
(4)choice条目 ----------------- 表示多选一的选项
以choice开头,以endchoice结束,中间的内容就是多选一选项的内容
(5)menuconfig条目 ---------------- 表示待选项的菜单
通常和if/endif联用,表示选中之后某些条目才会出现
(6)comment条目 ------------------ 文字信息
注:make menuconfig配置完成之后,配置结果(变量的值)保存在内核源代码顶层目录的.config文件中。Makefile编译内核时会去读取.config文件中的变量来控制编译行为。
obj-y ------------------ 编译进内核
obj-m ------------------ 变异成模块
obj-n ------------------ 不编译
2.修改内核配置界面实现对新加入内核源代码的控制
(1)将源代码加入到内核对应的目录下
(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中的变量的值来控制编译行为。
十七.输入(input)子系统
1.概念
输入子系统用于实现Linux输入设备驱动的一种框架。Linux内核将其中固定的部分放入内核中,驱动开发只需要实现其中不固定的部分。
输入子系统对应的设备文件是固定名称 /dev/input/event0...1...2...
/dev/event0...1...2...
输入子系统对应的设备文件(驱动)的主设备号是13
2.输入子系统用来实现驱动的设备
键盘 鼠标 按键 触摸屏 游戏手柄 游戏摇杆.......
3.输入子系统在内核中的框架
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*的设备文件。
练习:
编写应用程序访问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层放进去的