Linux驅動學習之:PWM驅動


PWM(Pulse Width Modulation)——脈寬調製,它是利用微控制器的數字輸出來對模擬電路進行控制的一種非常有效的技術,廣泛應用於測量、通信、功率控制與變換等許多領域。

s3c2440芯片中一共有5個16位的定時器,其中有4個定時器(定時器0~定時器3)具有脈寬調製功能,因此用s3c2440可以很容易地實現PWM功能。載有s3c2440芯片的
Mini2440 板子帶有一個蜂鳴器,它是由 PWM 控制的,下面是它的連接原理圖:
14753126_1319877085ftwQ.png
操控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多一個死區位):啓動/終止位,用於啓動和終止定時器;手動更新位,用於手動更新TCNTBnTCMPBn,這裏要注意的是在開始定時時,一定要把這位清零,否則是不能開啓定時器的;輸出反轉位,用於改變輸出的電平方向,使原先是高電平輸出的變爲低電平,而低電平的變爲高電平;自動重載位,用於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. }



<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
閱讀(1) | 評論(0) | 轉發(0) |
給主人留下些什麼吧!~~
評論熱議
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章