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 的輸出頻率了。
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <linux/poll.h>
- #include <linux/interrupt.h>
- #include <linux/gpio.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
- #include <mach/regs-gpio.h>
- #include <mach/hardware.h>
- #include <plat/regs-timer.h>
- #include <mach/regs-irq.h>
- #include <asm/mach/time.h>
- #include <linux/clk.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/miscdevice.h>
- #define DEVICE_NAME "pwm" //裝置名
- #define PWM_IOCTL_SET_FREQ 1 //定義宏變量,用于後面的 ioctl 中的控制指令
- #define PWM_IOCTL_STOP 0 //定義宏變量,用于後面的 ioctl 中的控制指令
- //定義信号量 lock用于互斥,是以,改驅動程式隻能同時有一個程序使用
- static struct semaphore lock;
- /* freq: pclk/50/16/65536 ~ pclk/50/16
- * if pclk = 50MHz, freq is 1Hz to 62500Hz
- * human ear : 20Hz~ 20000Hz
- */
- //設定 pwm 的頻率,配置各個寄存器
- static void PWM_Set_Freq( unsigned long freq )
- {
- unsigned long tcon;
- unsigned long tcnt;
- unsigned long tcfg1;
- unsigned long tcfg0;
- struct clk *clk_p;
- unsigned long pclk;
- //set GPB0 as tout0, pwm output 設定 GPB0 為 tout0,pwm 輸出
- s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0);
- tcon = __raw_readl(S3C2410_TCON); //讀取寄存器 TCON 到 tcon
- tcfg1 = __raw_readl(S3C2410_TCFG1); //讀取寄存器 TCFG1 到 tcfg1
- tcfg0 = __raw_readl(S3C2410_TCFG0); //讀取寄存器 TCFG0 到 tcfg0
- //設定TCFG0寄存器,prescaler = 50
- tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; // S3C2410_TCFG_PRESCALER0_MASK 定時器 0 和1 的預分頻值的掩碼,清除TCFG[0~8]
- tcfg0 |= (50 - 1); // 設定預分頻為 50
- //設定TCFG1寄存器,mux = 1/16
- tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; //S3C2410_TCFG1_MUX0_MASK 定時器 0 分割值的掩碼:清除TCFG1[0~3]
- tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; //定時器 0 進行 16 分割
- __raw_writel(tcfg1, S3C2410_TCFG1); //把 tcfg1 的值寫到分割寄存器 S3C2410_TCFG1 中
- __raw_writel(tcfg0, S3C2410_TCFG0); //把 tcfg0 的值寫到預分頻寄存器 S3C2410_TCFG0 中
- clk_p = clk_get(NULL, "pclk"); //得到 pclk
- pclk = clk_get_rate(clk_p);
- tcnt = (pclk/50/16)/freq; //得到定時器的輸入時鐘,進而設定 PWM 的調制頻率
- __raw_writel(tcnt, S3C2410_TCNTB(0)); //PWM 脈寬調制的頻率等于定時器的輸入時鐘,确定一個計數周期的時間長度
- __raw_writel(tcnt/2, S3C2410_TCMPB(0)); //占空比是 50%
- 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
- tcon |= 0xb;
- __raw_writel(tcon, S3C2410_TCON); //把 tcon 寫到計數器控制寄存器 S3C2410_TCON 中
- tcon &= ~2; //clear manual update bit
- __raw_writel(tcon, S3C2410_TCON);
- }
- static void PWM_Stop(void)
- {
- s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT); //設定 GPB0 為輸出
- s3c2410_gpio_setpin(S3C2410_GPB(0), 0); //設定 GPB0 為低電平,使蜂鳴器停止
- }
- static int s3c24xx_pwm_open(struct inode *inode, struct file *file)
- {
- if (!down_trylock(&lock)) //是否獲得信号量,是 down_trylock(&lock)=0,否則非 0
- return 0;
- else
- return -EBUSY; //傳回錯誤資訊:請求的資源不可用
- }
- static int s3c24xx_pwm_close(struct inode *inode, struct file *file)
- {
- PWM_Stop();
- up(&lock); //釋放信号量 lock
- return 0;
- }
- /*cmd 是 1,表示設定頻率;cmd 是 2 ,表示停止 pwm*/
- static int s3c24xx_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
- {
- switch (cmd) {
- case PWM_IOCTL_SET_FREQ: //if cmd=1 即進入 case PWM_IOCTL_SET_FREQ
- if (arg == 0) //如果設定的頻率參數是 0
- return -EINVAL; //傳回錯誤資訊,表示向參數傳遞了無效的參數
- PWM_Set_Freq(arg); //否則設定頻率
- break;
- case PWM_IOCTL_STOP: // if cmd=2 即進入 case PWM_IOCTL_STOP
- PWM_Stop(); //停止蜂鳴器
- break;
- }
- return 0; //成功傳回
- }
- /*初始化裝置的檔案操作的結構體*/
- static struct file_operations dev_fops = {
- .owner = THIS_MODULE,
- .open = s3c24xx_pwm_open,
- .release = s3c24xx_pwm_close,
- .ioctl = s3c24xx_pwm_ioctl,
- };
- static struct miscdevice misc = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = DEVICE_NAME,
- .fops = &dev_fops,
- };
- static int __init dev_init(void)
- {
- int ret;
- init_MUTEX(&lock); //初始化一個互斥鎖
- ret = misc_register(&misc); //注冊一個 misc 裝置
- printk (DEVICE_NAME"\tinitialized\n");
- return ret;
- }
- static void __exit dev_exit(void)
- {
- misc_deregister(&misc); //登出裝置
- }
- module_init(dev_init);
- module_exit(dev_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("FriendlyARM Inc.");
- MODULE_DESCRIPTION("S3C2410/S3C2440 PWM Driver");
測設程式如下:
- #include <stdio.h>
- #include <termios.h>
- #include <unistd.h>
- #include <stdlib.h>
- #define PWM_IOCTL_SET_FREQ 1
- #define PWM_IOCTL_STOP 2
- #define ESC_KEY 0x1b
- static int getch(void)
- {
- struct termios oldt,newt;
- int ch;
- if (!isatty(STDIN_FILENO)) {
- fprintf(stderr, "this problem should be run at a terminal\n");
- exit(1);
- }
- // save terminal setting
- if(tcgetattr(STDIN_FILENO, &oldt) < 0) {
- perror("save the terminal setting");
- exit(1);
- }
- // set terminal as need
- newt = oldt;
- newt.c_lflag &= ~( ICANON | ECHO );
- if(tcsetattr(STDIN_FILENO,TCSANOW, &newt) < 0) {
- perror("set terminal");
- exit(1);
- }
- ch = getchar();
- // restore termial setting
- if(tcsetattr(STDIN_FILENO,TCSANOW,&oldt) < 0) {
- perror("restore the termial setting");
- exit(1);
- }
- return ch;
- }
- static int fd = -1;
- static void close_buzzer(void);
- static void open_buzzer(void)
- {
- fd = open("/dev/pwm", 0);
- if (fd < 0) {
- perror("open pwm_buzzer device");
- exit(1);
- }
- // any function exit call will stop the buzzer
- atexit(close_buzzer);
- }
- static void close_buzzer(void)
- {
- if (fd >= 0) {
- ioctl(fd, PWM_IOCTL_STOP);
- close(fd);
- fd = -1;
- }
- }
- static void set_buzzer_freq(int freq)
- {
- // this IOCTL command is the key to set frequency
- int ret = ioctl(fd, PWM_IOCTL_SET_FREQ, freq);
- if(ret < 0) {
- perror("set the frequency of the buzzer");
- exit(1);
- }
- }
- static void stop_buzzer(void)
- {
- int ret = ioctl(fd, PWM_IOCTL_STOP);
- if(ret < 0) {
- perror("stop the buzzer");
- exit(1);
- }
- }
- int main(int argc, char **argv)
- {
- int freq = 1000 ;
- open_buzzer();
- printf( "\nBUZZER TEST ( PWM Control )\n" );
- printf( "Press +/- to increase/reduce the frequency of the BUZZER\n" ) ;
- printf( "Press 'ESC' key to Exit this program\n\n" );
- while( 1 )
- {
- int key;
- set_buzzer_freq(freq);
- printf( "\tFreq = %d\n", freq );
- key = getch();
- switch(key) {
- case '+':
- if( freq < 20000 )
- freq += 10;
- break;
- case '-':
- if( freq > 11 )
- freq -= 10 ;
- break;
- case ESC_KEY:
- case EOF:
- stop_buzzer();
- exit(0);
- default:
- break;
- }
- }
- }