Linux驱动学习之:PWM驱动

转载 2016年05月30日 15:44:19
PWM(Pulse Width Modulation)——脉宽调制,它是利用微控制器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量、通信、功率控制与变换等许多领域。

s3c2440芯片中一共有5个16位的定时器,其中有4个定时器(定时器0~定时器3)具有脉宽调制功能,因此用s3c2440可以很容易地实现PWM功能。载有s3c2440芯片的
Mini2440 板子带有一个蜂鸣器,它是由 PWM 控制的,下面是它的连接原理图:

操控PWM主要分以下四步:

1、PWM是通过引脚TOUT0~TOUT3输出的,而这4个引脚是与GPB0~GPB3复用的,因此要实现PWM功能首先要把相应的引脚配置成TOUT输出。

2、再设置定时器的输出时钟频率,它是以PCLK为基准,再除以用寄存器TCFG0配置的prescaler参数,和用寄存器TCFG1配置的divider参数。

3、然后设置脉冲的具体宽度,它的基本原理是通过寄存器TCNTBn来对寄存器TCNTn(内部寄存器)进行配置计数,TCNTn是递减的,如果减到零,则它又会重新装载TCNTBn里的数,重新开始计数,而寄存器TCMPBn作为比较寄存器与计数值进行比较,当TCNTn等于TCMPBn时,TOUTn输出的电平会翻转,而当TCNTn减为零时,电平会又翻转过来,就这样周而复始。因此这一步的关键是设置寄存器TCNTBn和TCMPBn,前者可以确定一个计数周期的时间长度,而后者可以确定方波的占空比。由于s3c2440的定时器具有双缓存,因此可以在定时器运行的状态下,改变这两个寄存器的值,它会在下个周期开始有效。


4、最后就是对PWM的控制,它是通过寄存器TCON来实现的,一般来说每个定时器主要有4个位要配置(定时器0多一个死区位):启动/终止位,用于启动和终止定时器;手动更新位,用于手动更新TCNTBn和TCMPBn,这里要注意的是在开始定时时,一定要把这位清零,否则是不能开启定时器的;输出反转位,用于改变输出的电平方向,使原先是高电平输出的变为低电平,而低电平的变为高电平;自动重载位,用于TCNTn减为零后重载TCNTBn里的值,当不想计数了,可以使自动重载无效,这样在TCNTn减为零后,不会有新的数加载给它,那么TOUTn输出会始终保持一个电平(输出反转位为0时,是高电平输出;输出反转位为1时,是低电平输出),这样就没有PWM功能了,因此这一位可以用于停止PWM。


因此,我们需要在驱动程序中,按照上述的操控序列就可以控制 PWM 的输出频率了。

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/init.h>
  5. #include <linux/delay.h>
  6. #include <linux/poll.h>
  7. #include <linux/interrupt.h>
  8. #include <linux/gpio.h>
  9. #include <asm/irq.h>
  10. #include <asm/io.h>
  11. #include <asm/uaccess.h>
  12. #include <mach/regs-gpio.h>
  13. #include <mach/hardware.h>
  14. #include <plat/regs-timer.h>
  15. #include <mach/regs-irq.h>
  16. #include <asm/mach/time.h>
  17. #include <linux/clk.h>
  18. #include <linux/cdev.h>
  19. #include <linux/device.h>
  20. #include <linux/miscdevice.h>
  21. #define DEVICE_NAME "pwm" //设备名

  22. #define PWM_IOCTL_SET_FREQ 1 //定义宏变量,用于后面的 ioctl 中的控制命令
  23. #define PWM_IOCTL_STOP 0     //定义宏变量,用于后面的 ioctl 中的控制命令

  24. //定义信号量 lock用于互斥,因此,改驱动程序只能同时有一个进程使用
  25. static struct semaphore lock;

  26. /* freq: pclk/50/16/65536 ~ pclk/50/16
  27.  * if pclk = 50MHz, freq is 1Hz to 62500Hz
  28.  * human ear : 20Hz~ 20000Hz
  29.  */
  30. //设置 pwm 的频率,配置各个寄存器
  31. static void PWM_Set_Freq( unsigned long freq )
  32. {
  33.     unsigned long tcon;
  34.     unsigned long tcnt;
  35.     unsigned long tcfg1;
  36.     unsigned long tcfg0;

  37.     struct clk *clk_p;
  38.     unsigned long pclk;

  39.     //set GPB0 as tout0, pwm output 设置 GPB0 为 tout0,pwm 输出
  40.     s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0);

  41.     tcon = __raw_readl(S3C2410_TCON); //读取寄存器 TCON 到 tcon
  42.     tcfg1 = __raw_readl(S3C2410_TCFG1); //读取寄存器 TCFG1 到 tcfg1
  43.     tcfg0 = __raw_readl(S3C2410_TCFG0); //读取寄存器 TCFG0 到 tcfg0

  44.     //设置TCFG0寄存器,prescaler = 50
  45.     tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; // S3C2410_TCFG_PRESCALER0_MASK 定时器 0 和1 的预分频值的掩码,清除TCFG[0~8]
  46.     tcfg0 |= (50 - 1); // 设置预分频为 50

  47.     //设置TCFG1寄存器,mux = 1/16
  48.     tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; //S3C2410_TCFG1_MUX0_MASK 定时器 0 分割值的掩码:清除TCFG1[0~3]
  49.     tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; //定时器 0 进行 16 分割

  50.     __raw_writel(tcfg1, S3C2410_TCFG1); //把 tcfg1 的值写到分割寄存器 S3C2410_TCFG1 中
  51.     __raw_writel(tcfg0, S3C2410_TCFG0); //把 tcfg0 的值写到预分频寄存器 S3C2410_TCFG0 中

  52.     clk_p = clk_get(NULL, "pclk"); //得到 pclk
  53.     pclk = clk_get_rate(clk_p);
  54.     tcnt = (pclk/50/16)/freq; //得到定时器的输入时钟,进而设置 PWM 的调制频率

  55.     __raw_writel(tcnt, S3C2410_TCNTB(0)); //PWM 脉宽调制的频率等于定时器的输入时钟,确定一个计数周期的时间长度
  56.     __raw_writel(tcnt/2, S3C2410_TCMPB(0)); //占空比是 50%

  57.     tcon &= ~0x1f;    //清空低5位,其中:TCON[4] --Dead zone enable, TCON[3] -- Timer 0 auto reload on/off, TCON[2] -- Timer 0 output inverter on/off, TCON[1] -- Timer 0 manual update, TCON[0] -- Timer 0 start/stop
      /*
       * 0xb: 0000 1011 
       * disable dead zone, auto reload for Timer 0, output inverter off, Update TCNTB0&TCMPB0, start for Timer 0
       */
  1.     tcon |= 0xb; 
  2.     __raw_writel(tcon, S3C2410_TCON); //把 tcon 写到计数器控制寄存器 S3C2410_TCON 中
  3.     tcon &= ~2;   //clear manual update bit
  4.     __raw_writel(tcon, S3C2410_TCON);
  5. }

  6. static void PWM_Stop(void)
  7. {
  8.     s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT); //设置 GPB0 为输出
  9.     s3c2410_gpio_setpin(S3C2410_GPB(0), 0); //设置 GPB0 为低电平,使蜂鸣器停止
  10. }

  11. static int s3c24xx_pwm_open(struct inode *inode, struct file *file)
  12. {
  13.     if (!down_trylock(&lock)) //是否获得信号量,是 down_trylock(&lock)=0,否则非 0
  14.         return 0;
  15.     else
  16.         return -EBUSY; //返回错误信息:请求的资源不可用
  17. }

  18. static int s3c24xx_pwm_close(struct inode *inode, struct file *file)
  19. {
  20.     PWM_Stop();
  21.     up(&lock); //释放信号量 lock
  22.     return 0;
  23. }

  24. /*cmd 是 1,表示设置频率;cmd 是 2 ,表示停止 pwm*/
  25. static int s3c24xx_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
  26. {
  27.     switch (cmd) {
  28.         case PWM_IOCTL_SET_FREQ: //if cmd=1 即进入 case PWM_IOCTL_SET_FREQ
  29.             if (arg == 0) //如果设置的频率参数是 0
  30.                 return -EINVAL; //返回错误信息,表示向参数传递了无效的参数
  31.             PWM_Set_Freq(arg); //否则设置频率
  32.             break;
  33.         case PWM_IOCTL_STOP: // if cmd=2 即进入 case PWM_IOCTL_STOP
  34.             PWM_Stop(); //停止蜂鸣器
  35.             break;
  36.     }
  37.     return 0; //成功返回
  38. }

  39. /*初始化设备的文件操作的结构体*/
  40. static struct file_operations dev_fops = {
  41.     .owner = THIS_MODULE,
  42.    .open = s3c24xx_pwm_open,
  43.     .release = s3c24xx_pwm_close,
  44.     .ioctl = s3c24xx_pwm_ioctl,
  45. };

  46. static struct miscdevice misc = {
  47.     .minor = MISC_DYNAMIC_MINOR,
  48.     .name = DEVICE_NAME,
  49.     .fops = &dev_fops,
  50. };

  51. static int __init dev_init(void)
  52. {
  53.     int ret;
  54.     init_MUTEX(&lock); //初始化一个互斥锁
  55.     ret = misc_register(&misc); //注册一个 misc 设备
  56.     printk (DEVICE_NAME"\tinitialized\n");
  57.     return ret;
  58. }

  59. static void __exit dev_exit(void)
  60. {
  61.     misc_deregister(&misc); //注销设备
  62. }

  63. module_init(dev_init);
  64. module_exit(dev_exit);
  65. MODULE_LICENSE("GPL");
  66. MODULE_AUTHOR("FriendlyARM Inc.");
  67. MODULE_DESCRIPTION("S3C2410/S3C2440 PWM Driver");
测设程序如下:
  1. #include <stdio.h>
  2. #include <termios.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>

  5. #define PWM_IOCTL_SET_FREQ 1
  6. #define PWM_IOCTL_STOP 2

  7. #define ESC_KEY 0x1b

  8. static int getch(void)
  9. {
  10.     struct termios oldt,newt;
  11.     int ch; 

  12.     if (!isatty(STDIN_FILENO)) {
  13.         fprintf(stderr, "this problem should be run at a terminal\n");
  14.         exit(1);
  15.     } 
  16.     // save terminal setting
  17.     if(tcgetattr(STDIN_FILENO, &oldt) < 0) {
  18.         perror("save the terminal setting");
  19.         exit(1);
  20.     } 

  21.     // set terminal as need
  22.     newt = oldt;
  23.     newt.c_lflag &= ~( ICANON | ECHO );
  24.     if(tcsetattr(STDIN_FILENO,TCSANOW, &newt) < 0) {
  25.         perror("set terminal");
  26.         exit(1);
  27.     }

  28.     ch = getchar();

  29.     // restore termial setting
  30.     if(tcsetattr(STDIN_FILENO,TCSANOW,&oldt) < 0) {
  31.         perror("restore the termial setting");
  32.         exit(1);
  33.     }
  34.     return ch;
  35. }

  36. static int fd = -1;
  37. static void close_buzzer(void);
  38. static void open_buzzer(void)
  39. {
  40.     fd = open("/dev/pwm", 0);
  41.     if (fd < 0) {
  42.         perror("open pwm_buzzer device");
  43.         exit(1);
  44.     }

  45.     // any function exit call will stop the buzzer
  46.     atexit(close_buzzer);
  47. }

  48. static void close_buzzer(void)
  49. {
  50.     if (fd >= 0) {
  51.         ioctl(fd, PWM_IOCTL_STOP);
  52.         close(fd);
  53.         fd = -1;
  54.     }
  55. }

  56. static void set_buzzer_freq(int freq)
  57. {
  58.     // this IOCTL command is the key to set frequency
  59.     int ret = ioctl(fd, PWM_IOCTL_SET_FREQ, freq);
  60.     if(ret < 0) {
  61.         perror("set the frequency of the buzzer");
  62.         exit(1);
  63.     }
  64. }
  65. static void stop_buzzer(void)
  66. {
  67.     int ret = ioctl(fd, PWM_IOCTL_STOP);
  68.     if(ret < 0) {
  69.         perror("stop the buzzer");
  70.         exit(1);
  71.     }
  72. }

  73. int main(int argc, char **argv)
  74. {
  75.     int freq = 1000 ;

  76.     open_buzzer();
  77.    printf( "\nBUZZER TEST ( PWM Control )\n" );
  78.     printf( "Press +/- to increase/reduce the frequency of the BUZZER\n" ) ;
  79.     printf( "Press 'ESC' key to Exit this program\n\n" );


  80.     while( 1 )
  81.     {
  82.         int key;

  83.         set_buzzer_freq(freq);
  84.         printf( "\tFreq = %d\n", freq );

  85.         key = getch();

  86.         switch(key) {
  87.         case '+':
  88.             if( freq < 20000 )
  89.                 freq += 10;
  90.             break;

  91.         case '-':
  92.             if( freq > 11 )
  93.                 freq -= 10 ;
  94.             break;

  95.         case ESC_KEY:
  96.         case EOF:
  97.             stop_buzzer();
  98.             exit(0);
  99.         default:
  100.             break;
  101.         }
  102.     }
  103. }

linux设备树之pwm

驱动层 #include #include #include #include #include #include #include #include #include #incl...

Linux系统PWM驱动

硬件平台:IMX6 内核版本:kernel3.0.35 在linux内核中有一个规律,Linux内核开发者把通用的东西都总结出来,个性化的东西就留出接口,和GPIO驱动类似,PWM驱动在内核中也提...
  • BorntoX
  • BorntoX
  • 2016年07月11日 12:07
  • 2705

ubuntu下如何控制风扇速度?

问题:ubuntu下如何控制风扇速度? 问题描述:Windows下有一个很好的程序叫 SpeedFan,那么如何在在ubuntu下控制风扇速度呢? 解决方法:  1.安装lm-senso...

Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动

编写驱动的第一步仍是看原理图:        可以看到,该蜂鸣器由 GPD0_0 来控制 ,查手册可知该I/O口由Time0 来控制,找到相应的寄存器: a -- I/O口寄存器及地址      ...

嵌入式Linux下PWM功能调试

By Toradex秦海 1). 简介 PWM(Pulse-width modulation)接口是嵌入式设备最为常用的接口之一,常用于电机驱动,蜂鸣器,脉冲计数等嵌入式领域,因此本文就基于嵌入式...

linux驱动开发之pwm蜂鸣器

驱动开发,控制pwm蜂鸣器! 蜂鸣器有多种类型,一种是给电就叫,另一种给电了还不行,还需要freq才会叫。大概称作有源和无源吧!我们此时将buzzer的驱动加入到内核中去。/* * linux/d...

编写Linux标准的PWM驱动

编写Linux标准的PWM驱动,需要定义一个结构体 struct pwm_device ,实现五个个PWM函数(include/linux/pwm.h),如下所示: struct pwm_d...

linux驱动之PWM(定时器)

说明:以下由两部分组成,定时器驱动、定时器应用程序构成; 1.驱动程序; #include #include #include #include #include #includ...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux驱动学习之:PWM驱动
举报原因:
原因补充:

(最多只允许输入30个字)