使用linux内核hrtimer高精度定时器实现GPIO口模拟PWM,【原创】
关键词:Android linux hrtimer 蜂鸣器 等待队列 信号量 字符设备
平台信息:
内核:linux3.4.39
系统:android/android5.1
平台:S5P4418
作者:庄泽彬(欢迎转载,请注明作者)
邮箱:2760715357@qq.com
程序描述:本文控制的设备是无源蜂鸣器,由于无源蜂鸣器是需要产生一定的频率的PWM才能够控制蜂鸣器,不像有源蜂鸣器,只需要提供高低电平就可以控制蜂鸣器。linux内核普通的定时器,由于具有一定的局限性,不能达到纳秒级别的定时,使用普通的定时器模拟GPIO口产生PWM会导致蜂鸣器出现杂音,因此要使用hrtimer高精度定时器模拟GPIO口产生PWM可以极大的改善性能。使用信号量sem只是为了避免多个应用程序打开设备,使用等待队列是为了让程序可以按照指定的方式去运行,如果不加等待队列,在启动hrtimer定时器之后就会不会等待定时器完成之后在关闭设备,因此需要加入等待队列,等待定时器定时的工作完成之后再唤醒等待队列。虽然程序可以使用GPIO模拟PWM产生一定频率的信号去控制无源蜂鸣器,但是还是存在一定的局限性,因为定时器也是加入内核进行调度,所以有可能杂定时的时候会导致输出的PWM被打断,导致杂音的出现。因此严格来说不能完整的输出一段不被打断模拟PWM信号。如果有什么好方法可以实现的话,可以共同探讨。好吧不说了上代码吧。
无源蜂鸣器的驱动程序(代码写的一般般如有不对,欢迎指点)
buzzer_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/semaphore.h> #include <asm/uaccess.h>
#include <asm/io.h> #include <mach/platform.h> #define BUZZER_DEVICE_NAME "mybuzzer"
#define BUZZER_CLASS_NAME "mybuzzer" //sys/class/mybuzzer
#define BUZZER_DEVICE_NUM 1 //设备节点的编号最终生成节点的名字为/dev/buzzer-1 #define BUZZER_GPIO_SWITCH 1 //是否设置buzzer的gpio的初始化,另一个驱动已经初始化GPIO
#define BUZZER_DELAY_TIME_HIGHT (190000) //2.7KHZ
#define BUZZER_DELAY_TIME_LOW (190000) //2.7KHZ
#define DE_BUG 1 #if DE_BUG
#define prdebug(fmt,arg...) printk("zbzhuang"KERN_ERR fmt"\n",##arg)
#else
#define prdebug(fmt,arg...) do{}while(0);
#endif typedef enum {
BUZZER_DISABLE = ,
BUZZER_ENABLE,
}BUZZER_STATUS_t; //buzzer的设备对象
struct buzzer_chip{
dev_t devno;
struct cdev *cdev;
struct class *cls;
struct device *dev;
unsigned long count; //从应用空间读取的数据
struct semaphore sem;
struct hrtimer mytimer;
ktime_t kt; //设置定时时间
wait_queue_head_t wait_queue;
BUZZER_STATUS_t status;
}; static int count = ;
struct buzzer_chip *buzzer_dev; static void buzzer_test(void);
static void buzzer_gpio_start(void);
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer); int buzzer_drv_open (struct inode * inode, struct file *filp)
{
int minor;
int major; prdebug("--------------%s----------------",__func__); minor = iminor(inode);
major = imajor(inode); prdebug("\r\nmajor = %d minor = %d\rn",major,minor);
filp->private_data = (void *)minor; if(down_interruptible(&buzzer_dev->sem))
return -ERESTARTSYS; return ;
}
ssize_t buzzer_drv_read (struct file *filp, char __user *userbuf, size_t count, loff_t *fpos)
{
int ret; prdebug("--------------%s----------------",__func__); if(filp->f_flags & O_NONBLOCK){
return -EAGAIN;
} ret = copy_to_user(userbuf,&buzzer_dev->count,count);
if(ret > ){
prdebug("error copy_to_user");
return -EFAULT;
}
prdebug("%s :read count = %ld",__func__,buzzer_dev->count); return count;
}
ssize_t buzzer_drv_write (struct file *filp, const char __user *userbuf, size_t count, loff_t *fpos)
{
int ret; prdebug("--------------%s----------------",__func__);
prdebug("task pid[%d] context[%s]",current->pid,current->comm); ret = copy_from_user(&buzzer_dev->count,userbuf,count);
if(ret > ){
prdebug("error copy_from_user");
return -EFAULT;
} prdebug("%s :write count = %ld",__func__,buzzer_dev->count); if(buzzer_dev->count){
if(buzzer_dev->status == BUZZER_DISABLE){
//启动定时器
prdebug("-----------start hrtimer timer-------------");
buzzer_dev->status = BUZZER_ENABLE;
buzzer_gpio_start();
wait_event(buzzer_dev->wait_queue, buzzer_dev->status == BUZZER_DISABLE);
prdebug("------------wake up queue-------------------------------");
}else{
prdebug("buzzer_aready work");
} }else{ } return count; }
int buzzer_drv_close (struct inode *inode, struct file *filp)
{ prdebug("--------------%s----------------",__func__);
up(&buzzer_dev->sem); return ; } const struct file_operations buzzer_fops = {
.open = buzzer_drv_open,
.write = buzzer_drv_write,
.read = buzzer_drv_read,
.release = buzzer_drv_close, }; static int buzzer_gpio_init(void)
{
int ret = -; if(gpio_request(BUZZER_IO, "BUZZER_GPIO")){
prdebug("error buzzer_gpio_init");
return ret; }else{
gpio_direction_output(BUZZER_IO, );
gpio_set_value(BUZZER_IO, );
buzzer_dev->status = BUZZER_DISABLE;
} return ; } static void buzzer_gpio_exit(void)
{
gpio_set_value(BUZZER_IO, );
gpio_free(BUZZER_IO);
} static void buzzer_gpio_start(void)
{
prdebug("-----------buzzer_gpio_start------------"); //高精度定时器
hrtimer_init(&buzzer_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
buzzer_dev->mytimer.function = hrtimer_handler;
buzzer_dev->kt = ktime_set(, BUZZER_DELAY_TIME_LOW);
hrtimer_start(&buzzer_dev->mytimer,buzzer_dev->kt,HRTIMER_MODE_REL); } static void buzzer_test(void)
{
unsigned long i; prdebug("-----------start test buzzer------------");
for(i = ;i < ;i ++){
gpio_set_value(BUZZER_IO, );
udelay();
gpio_set_value(BUZZER_IO, );
udelay();
}
prdebug("-----------end test buzzer------------");
} static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{ //prdebug("--------------%s----------------",__func__); if(buzzer_dev->count != ){ if (gpio_get_value(BUZZER_IO) == ) {
gpio_set_value(BUZZER_IO, ); buzzer_dev->kt = ktime_set(, BUZZER_DELAY_TIME_LOW);
hrtimer_forward_now(&buzzer_dev->mytimer, buzzer_dev->kt); } else {
gpio_set_value(BUZZER_IO, ); buzzer_dev->kt = ktime_set(, BUZZER_DELAY_TIME_HIGHT);
hrtimer_forward_now(&buzzer_dev->mytimer, buzzer_dev->kt);
}
buzzer_dev->count --;
return HRTIMER_RESTART;
}else{
buzzer_dev->count --;
buzzer_dev->status = BUZZER_DISABLE;
prdebug("buzzer_dev->count = %d",buzzer_dev->count);
prdebug("-----------finsh hrtimer timer-------------");
wake_up(&buzzer_dev->wait_queue);
return HRTIMER_NORESTART;
} } static int __init buzzer_drv_init(void)
{
int ret; prdebug("--------------%s----------------",__func__); buzzer_dev = kzalloc(sizeof(struct buzzer_chip),GFP_KERNEL);
if(buzzer_dev == NULL){
prdebug("kzalloc error");
return -ENOMEM;
} //动态的申请设备号
ret = alloc_chrdev_region(&buzzer_dev->devno,,,BUZZER_DEVICE_NAME);
if(ret != ){
prdebug("error alloc_chrdev_region");
goto err_free;
} //分配cdev对象
buzzer_dev->cdev = cdev_alloc();
cdev_init(buzzer_dev->cdev,&buzzer_fops);
cdev_add(buzzer_dev->cdev,buzzer_dev->devno,); //自动创建设备节点
buzzer_dev->cls = class_create(THIS_MODULE,BUZZER_CLASS_NAME);
if(IS_ERR(buzzer_dev->cls)){
prdebug("error class_create");
ret = PTR_ERR(buzzer_dev->cls);
goto err_unregister;
} buzzer_dev->dev = device_create(buzzer_dev->cls,NULL,buzzer_dev->devno,NULL,"buzzer-%d",BUZZER_DEVICE_NUM);
if(IS_ERR(buzzer_dev->dev)){
prdebug("error device_create");
ret = PTR_ERR(buzzer_dev);
goto err_class_error;
} //信号量
sema_init(&buzzer_dev->sem,); init_waitqueue_head(&buzzer_dev->wait_queue); #if BUZZER_GPIO_SWITCH
//初始化Buzzer的GPIO
ret = buzzer_gpio_init();
if(ret !=){
prdebug("error buzzer_gpio_init");
goto err_device_create;
} #endif return ; #if BUZZER_GPIO_SWITCH
err_device_create:
device_destroy(buzzer_dev->cls,buzzer_dev->devno);
#endif err_class_error:
class_destroy(buzzer_dev->cls); err_unregister:
cdev_del(buzzer_dev->cdev);
unregister_chrdev_region(buzzer_dev->devno,); err_free:
kfree(buzzer_dev);
return ret; } static void __exit buzzer_drv_exit(void)
{
prdebug("--------------%s----------------",__func__);
#if BUZZER_GPIO_SWITCH
buzzer_gpio_exit();
#endif
device_destroy(buzzer_dev->cls,buzzer_dev->devno);
class_destroy(buzzer_dev->cls);
cdev_del(buzzer_dev->cdev);
unregister_chrdev_region(buzzer_dev->devno,);
kfree(buzzer_dev); } module_init(buzzer_drv_init);
module_exit(buzzer_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhuang zebin@qq.com");
驱动程序对应的Makefile,自己修改交叉工具链还有内核的位置
CROSS_COMPILE = /home/zsf/u4209/s5p4418-5.1-android/trunk/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
#CROSS_COMPILE = /home/zsf/book/toolchain-4.5.-farsight/bin/arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
#APP_NAME = led_app
MODULE_NAME = buzzer_driver #内核源码路径
#KERNEL_DIR = /home/zsf/rk3188_5./android/kernel
KERNEL_DIR = /home/zsf/u4209/s5p4418-5.1-android/trunk/kernel CUR_DIR = $(shell pwd) all :
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
#$(CC) $(APP_NAME).c -o $(APP_NAME) clean :
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
#rm -rf $(APP_NAME) install:
cp -raf *.ko $(APP_NAME) /opt/rootfs/drv_module/ #指定编译哪个源文件
obj-m = $(MODULE_NAME).o ~
写一个简单的APP去控制设备代码如下buzzer_write.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h> int main(int argc,char **argv)
{
int fd = -;
unsigned long on = -; fd = open("/dev/buzzer-1",O_RDWR);
if(fd < ){
perror("open");
exit();
} on = atol(argv[]);
write(fd,&on,); close(fd); return ;
}
对应的Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_MODULE := buzzer_write LOCAL_SRC_FILES := buzzer_write.cpp include $(BUILD_EXECUTABLE)
编译驱动:错误和警告可以忽略,之后使用adb工具将buzzer_driver.ko推送进板子。
编译应用程序
实验操作如下:
查看相应的LOG
这里无法展示你们看,蜂鸣器的叫声,经过测试改程序勉强还是可以使用的啊。
使用linux内核hrtimer高精度定时器实现GPIO口模拟PWM,【原创】的更多相关文章
- Linux下的hrtimer高精度定时器【转】
转自:http://blog.csdn.net/waverider2012/article/details/38305785 hrtimer高精度定时器的interval由ktime_set(cons ...
- linux下jiffies定时器和hrtimer高精度定时器【转】
本文转载自:http://blog.csdn.net/dosculler/article/details/7932315 一.jiffies定时器,HZ=100,精度只能达到10ms. 注:采用jif ...
- 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)
浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...
- 浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】
转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_time ...
- 51单片机GPIO口模拟串口通信
51单片机GPIO口模拟串口通信 标签: bytetimer终端存储 2011-08-03 11:06 6387人阅读 评论(2) 收藏 举报 本文章已收录于: 分类: 深入C语言(20) 作者同 ...
- hrtimer高精度定时器的简单使用【学习笔记】
#include <linux/module.h> #include <linux/kernel.h> #include <linux/hrtimer.h> #in ...
- Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现
转自:http://blog.csdn.net/droidphone/article/details/8074892 上一篇文章,我介绍了传统的低分辨率定时器的实现原理.而随着内核的不断演进,大牛们已 ...
- Linux内核定时器struct timer_list
1.前言 Linux内核中的定时器是一个很常用的功能,某些需要周期性处理的工作都需要用到定时器.在Linux内核中,使用定时器功能比较简单,需要提供定时器的超时时间和超时后需要执行的处理函数. 2.常 ...
- Linux 内核协议栈 学习资料
终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...
随机推荐
- PS学习笔记(01)
[1]PS,文件-脚本-删除所有的空图层. [2]设计师与美工的区别? 设计在于有思路了再去找素材, 美工在于有素材后再去设计 (思路是在大量的设计上,才累计出来的.) [3]如何知道一张图片 ...
- selenium启动chrome出错处理:Message: 'chromedriver' executable needs to be in PATH
- Leetcode 236.二叉树的最近公共祖先
二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个结点 x,满足 x ...
- php 正则匹配包含字母、数字以及下划线,且至少包含2种
新系统注册功能需对用户名和密码做以下要求:包含字母.数字以及下划线,且至少包含2种: 在网上没有搜到符合要求的代码,于是自己对他人代码做了一点修改,经测试满足要求.代码如下: if (!preg_ma ...
- SGU515:Recover path 【最短路】
警告:这题卡SPFA,警告:这题卡SPFA 这不是演习 题目大意:给出一个无向图,以及一些点的序列,要找出一条最短的路径使得通过所有点,题目保证存在一条头尾都在点的序列中的最短路满足题意 思路:没有最 ...
- bzoj2190 [SDOI2008]仪仗队 - 筛法 - 欧拉函数
作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图). ...
- winrar5.0破解
RAR registration data Federal Agency for Education 1000000 PC usage license UID=b621cca9a84bc5deffbf ...
- mysql 修改管理员密码
mysql 修改管理员密码 本次学习环境: windows 7系统.mysql 5.7.14. 一.如果是忘记了用户密码: (1).关闭正在运行的MySQL服务. 方法一:可以直接操作wamp软件,左 ...
- HDU 6395 分段矩阵快速幂 HDU 6386 建虚点+dij
http://acm.hdu.edu.cn/showproblem.php?pid=6395 Sequence Time Limit: 4000/2000 MS (Java/Others) Me ...
- 浏览器websocket
使用浏览器,PHP 来构建的应用,发现都是每次浏览器发送一次http 请求,PHP 回一个响应. 这样,后端的PHP 在处理多次http请求是,每次都是不同的进程在处理. 这就加大了开销, 而且,PH ...