使用linux内核hrtimer高精度定时器实现GPIO口模拟PWM,原创
Posted 请给我倒杯茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用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
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/fs.h> 4 #include <linux/device.h> 5 #include <linux/slab.h> 6 #include <linux/cdev.h> 7 #include <linux/interrupt.h> 8 #include <linux/gpio.h> 9 #include <linux/input.h> 10 #include <linux/sched.h> 11 #include <linux/wait.h> 12 #include <linux/delay.h> 13 #include <linux/semaphore.h> 14 15 #include <asm/uaccess.h> 16 #include <asm/io.h> 17 18 #include <mach/platform.h> 19 20 #define BUZZER_DEVICE_NAME "mybuzzer" 21 #define BUZZER_CLASS_NAME "mybuzzer" //sys/class/mybuzzer 22 #define BUZZER_DEVICE_NUM 1 //设备节点的编号最终生成节点的名字为/dev/buzzer-1 23 24 25 #define BUZZER_GPIO_SWITCH 1 //是否设置buzzer的gpio的初始化,另一个驱动已经初始化GPIO 26 #define BUZZER_DELAY_TIME_HIGHT (190000) //2.7KHZ 27 #define BUZZER_DELAY_TIME_LOW (190000) //2.7KHZ 28 #define DE_BUG 1 29 30 #if DE_BUG 31 #define prdebug(fmt,arg...) printk("zbzhuang"KERN_ERR fmt"\\n",##arg) 32 #else 33 #define prdebug(fmt,arg...) do{}while(0); 34 #endif 35 36 37 38 typedef enum { 39 BUZZER_DISABLE = 0, 40 BUZZER_ENABLE, 41 }BUZZER_STATUS_t; 42 43 //buzzer的设备对象 44 struct buzzer_chip{ 45 dev_t devno; 46 struct cdev *cdev; 47 struct class *cls; 48 struct device *dev; 49 unsigned long count; //从应用空间读取的数据 50 struct semaphore sem; 51 struct hrtimer mytimer; 52 ktime_t kt; //设置定时时间 53 wait_queue_head_t wait_queue; 54 BUZZER_STATUS_t status; 55 }; 56 57 58 59 60 static int count = 1000; 61 struct buzzer_chip *buzzer_dev; 62 63 static void buzzer_test(void); 64 static void buzzer_gpio_start(void); 65 static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer); 66 67 68 int buzzer_drv_open (struct inode * inode, struct file *filp) 69 { 70 int minor; 71 int major; 72 73 74 prdebug("--------------%s----------------",__func__); 75 76 minor = iminor(inode); 77 major = imajor(inode); 78 79 prdebug("\\r\\nmajor = %d minor = %d\\rn",major,minor); 80 filp->private_data = (void *)minor; 81 82 if(down_interruptible(&buzzer_dev->sem)) 83 return -ERESTARTSYS; 84 85 return 0; 86 } 87 ssize_t buzzer_drv_read (struct file *filp, char __user *userbuf, size_t count, loff_t *fpos) 88 { 89 int ret; 90 91 prdebug("--------------%s----------------",__func__); 92 93 if(filp->f_flags & O_NONBLOCK){ 94 return -EAGAIN; 95 } 96 97 ret = copy_to_user(userbuf,&buzzer_dev->count,count); 98 if(ret > 0){ 99 prdebug("error copy_to_user"); 100 return -EFAULT; 101 } 102 prdebug("%s :read count = %ld",__func__,buzzer_dev->count); 103 104 return count; 105 } 106 ssize_t buzzer_drv_write (struct file *filp, const char __user *userbuf, size_t count, loff_t *fpos) 107 { 108 int ret; 109 110 prdebug("--------------%s----------------",__func__); 111 prdebug("task pid[%d] context[%s]",current->pid,current->comm); 112 113 ret = copy_from_user(&buzzer_dev->count,userbuf,count); 114 if(ret > 0){ 115 prdebug("error copy_from_user"); 116 return -EFAULT; 117 } 118 119 prdebug("%s :write count = %ld",__func__,buzzer_dev->count); 120 121 122 123 124 if(buzzer_dev->count){ 125 if(buzzer_dev->status == BUZZER_DISABLE){ 126 //启动定时器 127 prdebug("-----------start hrtimer timer-------------"); 128 buzzer_dev->status = BUZZER_ENABLE; 129 buzzer_gpio_start(); 130 wait_event(buzzer_dev->wait_queue, buzzer_dev->status == BUZZER_DISABLE); 131 prdebug("------------wake up queue-------------------------------"); 132 }else{ 133 prdebug("buzzer_aready work"); 134 } 135 136 }else{ 137 138 } 139 140 141 return count; 142 143 } 144 int buzzer_drv_close (struct inode *inode, struct file *filp) 145 { 146 147 prdebug("--------------%s----------------",__func__); 148 up(&buzzer_dev->sem); 149 150 return 0; 151 152 } 153 154 const struct file_operations buzzer_fops = { 155 .open = buzzer_drv_open, 156 .write = buzzer_drv_write, 157 .read = buzzer_drv_read, 158 .release = buzzer_drv_close, 159 160 }; 161 162 static int buzzer_gpio_init(void) 163 { 164 int ret = -1; 165 166 if(gpio_request(BUZZER_IO, "BUZZER_GPIO")){ 167 prdebug("error buzzer_gpio_init"); 168 return ret; 169 170 }else{ 171 gpio_direction_output(BUZZER_IO, 1); 172 gpio_set_value(BUZZER_IO, 1); 173 buzzer_dev->status = BUZZER_DISABLE; 174 } 175 176 return 0; 177 178 } 179 180 static void buzzer_gpio_exit(void) 181 { 182 gpio_set_value(BUZZER_IO, 1); 183 gpio_free(BUZZER_IO); 184 } 185 186 static void buzzer_gpio_start(void) 187 { 188 prdebug("-----------buzzer_gpio_start------------"); 189 190 191 192 //高精度定时器 193 hrtimer_init(&buzzer_dev->mytimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL); 194 buzzer_dev->mytimer.function = hrtimer_handler; 195 buzzer_dev->kt = ktime_set(0, BUZZER_DELAY_TIME_LOW); 196 hrtimer_start(&buzzer_dev->mytimer,buzzer_dev->kt,HRTIMER_MODE_REL); 197 198 199 200 } 201 202 static void buzzer_test(void) 203 { 204 unsigned long i; 205 206 prdebug("-----------start test buzzer------------"); 207 for(i = 0;i < 10000;i ++){ 208 gpio_set_value(BUZZER_IO, 0); 209 udelay(150); 210 gpio_set_value(BUZZER_IO, 1); 211 udelay(150); 212 } 213 prdebug("-----------end test buzzer------------"); 214 } 215 216 static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer) 217 { 218 219 220 //prdebug("--------------%s----------------",__func__); 221 222 if(buzzer_dev->count != 1){ 223 224 if (gpio_get_value(BUZZER_IO) == 1) { 225 gpio_set_value(BUZZER_IO, 0); 226 227 buzzer_dev->kt = ktime_set(0, BUZZER_DELAY_TIME_LOW); 228 hrtimer_forward_now(&buzzer_dev->mytimer, buzzer_dev->kt); 229 230 } else { 231 gpio_set_value(BUZZER_IO, 1); 232 233 buzzer_dev->kt = ktime_set(0, BUZZER_DELAY_TIME_HIGHT); 234 hrtimer_forward_now(&buzzer_dev->mytimer, buzzer_dev->kt); 235 } 236 buzzer_dev->count --; 237 return HRTIMER_RESTART; 238 }else{ 239 buzzer_dev->count --; 240 buzzer_dev->status = BUZZER_DISABLE; 241 prdebug("buzzer_dev->count = %d",buzzer_dev->count); 242 prdebug("-----------finsh hrtimer timer-------------"); 243 wake_up(&buzzer_dev->wait_queue); 244 return HRTIMER_NORESTART; 245 } 246 247 248 } 249 250 251 static int __init buzzer_drv_init(void) 252 { 253 int ret; 254 255 256 prdebug("--------------%s----------------",__func__); 257 258 buzzer_dev = kzalloc(sizeof(struct buzzer_chip),GFP_KERNEL); 259 if(buzzer_dev == NULL){ 260 prdebug("kzalloc error"); 261 return -ENOMEM; 262 } 263 264 //动态的申请设备号 265 ret = alloc_chrdev_region(&buzzer_dev->devno,0,1,BUZZER_DEVICE_NAME); 266 if(ret != 0){ 267 prdebug("error alloc_chrdev_region"); 268 goto err_free; 269 } 270 271 //分配cdev对象 272 buzzer_dev->cdev = cdev_alloc(); 273 cdev_init(buzzer_dev->cdev,&buzzer_fops); 274 cdev_add(buzzer_dev->cdev,buzzer_dev->devno,1); 275 276 //自动创建设备节点 277 buzzer_dev->cls = class_create(THIS_MODULE,BUZZER_CLASS_NAME); 278 if(IS_ERR(buzzer_dev->cls)){ 279 prdebug("error class_create"); 280 ret = PTR_ERR(buzzer_dev->cls); 281 goto err_unregister; 282 } 283 284 buzzer_dev->dev = device_create(buzzer_dev->cls,NULL,buzzer_dev->devno,NULL,"buzzer-%d",BUZZER_DEVICE_NUM); 285 if(IS_ERR(buzzer_dev->dev)){ 286 prdebug("error device_create"); 287 ret = PTR_ERR(buzzer_dev); 288 goto err_class_error; 289 } 290 291 //信号量 292 sema_init(&buzzer_dev->sem,1); 293 294 init_waitqueue_head(&buzzer_dev->wait_queue); 295 296 297 298 299 300 #if BUZZER_GPIO_SWITCH 301 //初始化Buzzer的GPIO 302 ret = buzzer_gpio_init(); 303 if(ret !=0){ 304 prdebug("error buzzer_gpio_init"); 305 goto err_device_create; 306 } 307 308 #endif 309 310 311 312 return 0; 313 314 #if BUZZER_GPIO_SWITCH 315 err_device_create: 316 device_destroy(buzzer_dev->cls,buzzer_dev->devno); 317 #endif 318 319 err_class_error: 320 class_destroy(buzzer_dev->cls); 321 322 err_unregister: 323 cdev_del(buzzer_dev->cdev); 324 unregister_chrdev_region(buzzer_dev->devno,1); 325 326 err_free: 327 kfree(buzzer_dev); 328 return ret; 329 330 331 } 332 333 static void __exit buzzer_drv_exit(void) 334 { 335 prdebug("--------------%s----------------",__func__); 336 #if BUZZER_GPIO_SWITCH 337 buzzer_gpio_exit(); 338 #endif 339 device_destroy(buzzer_dev->cls,buzzer_dev->devno); 340 class_destroy(buzzer_dev->cls); 341 cdev_del(buzzer_dev->cdev); 342 unregister_chrdev_region(buzzer_dev->devno,1); 343 kfree(buzzer_dev); 344 345 } 346 347 module_init(buzzer_drv_init); 348 module_exit(buzzer_drv_exit); 349 MODULE_LICENSE("GPL"); 350 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.1-farsight/bin/arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc #APP_NAME = led_app MODULE_NAME = buzzer_driver #内核源码路径 #KERNEL_DIR = /home/zsf/rk3188_5.1/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 = -1; unsigned long on = -1; fd = open("/dev/buzzer-1",O_RDWR); if(fd < 0){ perror("open"); exit(1); } on = atol(argv[1]); write(fd,&on,4); close(fd); return 0; }
对应的Android.mk
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 5 LOCAL_MODULE_TAGS := optional 6 7 LOCAL_MODULE := buzzer_write 8 9 LOCAL_SRC_FILES := buzzer_write.cpp 10 11 include $(BUILD_EXECUTABLE)
编译驱动:错误和警告可以忽略,之后使用adb工具将buzzer_driver.ko推送进板子。
编译应用程序
实验操作如下:
查看相应的LOG
这里无法展示你们看,蜂鸣器的叫声,经过测试改程序勉强还是可以使用的啊。
以上是关于使用linux内核hrtimer高精度定时器实现GPIO口模拟PWM,原创的主要内容,如果未能解决你的问题,请参考以下文章
Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现
Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现
Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现