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 /* * 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; } } }