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。
- #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
* 0xb: 0000 1011
* disable dead zone, auto reload for Timer 0, output inverter off, Update TCNTB0&TCMPB0, start for Timer 0
*/
-
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;
-
}
-
}
- }