mini6410的蜂鳴器Buzzer是通過PWM控制的,原理圖如下所示,其中,串連蜂鳴器的PWM0對應GPF14,該引腳可通過軟體佈建為PWM輸出,也可以作為普通的GPIO使用。
由以上可知,我們需要在驅動程式中,首先把GPF14連接埠設定為PWM功能輸出,再設定相應的Timer就可以控制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 <asm/irq.h>#include <asm/io.h>#include <linux/interrupt.h>#include <asm/uaccess.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>#include <mach/map.h>#include <mach/regs-clock.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#include <mach/gpio-bank-e.h>#include <mach/gpio-bank-f.h>#include <mach/gpio-bank-k.h>#define DEVICE_NAME "pwm" //定義裝置名稱#define PWM_IOCTL_SET_FREQ1 //定義宏變數,用於後面的ioctl中的switch case#define PWM_IOCTL_STOP0 //定義訊號量lockstatic struct semaphore lock;/* freq: pclk/50/16/65536 ~ pclk/50/16 * if pclk = 50MHz, freq is 1Hz to 62500Hz * human ear : 20Hz~ 20000Hz */static void PWM_Set_Freq( unsigned long freq ) //設定pwm的頻率,配置各個寄存器{unsigned long tcon;unsigned long tcnt;unsigned long tcfg1;unsigned long tcfg0;struct clk *clk_p;unsigned long pclk;unsigned tmp;tmp = readl(S3C64XX_GPFCON); //參考第一個表,設定GPF14為TOUT0,pwm輸出tmp &= ~(0x3U << 28);tmp |= (0x2U << 28);writel(tmp, S3C64XX_GPFCON);tcon = __raw_readl(S3C_TCON); //讀定時器配置寄存器TCON到tcontcfg1 = __raw_readl(S3C_TCFG1); //讀寄存器TCFG1到tcfg1tcfg0 = __raw_readl(S3C_TCFG0); //讀寄存器TCFG0到tcfg0//prescaler = 50tcfg0 &= ~S3C_TCFG_PRESCALER0_MASK; S3C_TCFG_PRESCALER0_MASK=255(11111111),為定時器0和1的預分頻值得掩碼,TCFG[0~8]tcfg0 |= (50 - 1); tcfg0=00110001,預分頻為50//mux = 1/16tcfg1 &= ~S3C_TCFG1_MUX0_MASK; //定時器0分割值得掩碼 (15<<0)tcfg1 |= S3C_TCFG1_MUX0_DIV16; //定時器0進行1/16分割 (4<<0)->0100__raw_writel(tcfg1, S3C_TCFG1); //將tcfg1的值寫到分割寄存器中__raw_writel(tcfg0, S3C_TCFG0); //將tcfg0的值寫到分頻寄存器中clk_p = clk_get(NULL, "pclk"); //得到pclkpclk = clk_get_rate(clk_p);tcnt = (pclk/50/16)/freq; //得到定時器的輸入時鐘,進而設定PWM的調製頻率__raw_writel(tcnt, S3C_TCNTB(0)); //PWM脈寬調製的頻率等於定時器的輸入時鐘__raw_writel(tcnt/2, S3C_TCMPB(0)); //占空比是50%tcon &= ~0x1f;tcon |= 0xb;//禁用死區, 間隔模式開啟, 逆變器關閉, 自動更新TCNTB0&TCMPB0, 開始定時器0__raw_writel(tcon, S3C_TCON); 把tcon的設定寫到計數控制寄存器S3C_TCON中tcon &= ~2;//clear manual update bit__raw_writel(tcon, S3C_TCON);}void PWM_Stop( void ){unsigned tmp;tmp = readl(S3C64XX_GPFCON); //設定GPF14為輸出tmp &= ~(0x3U << 28);writel(tmp, S3C64XX_GPFCON); //設定GPF14為低電平,使蜂鳴器停止}static int s3c64xx_pwm_open(struct inode *inode, struct file *file){if (!down_trylock(&lock)) //是否獲得訊號量。如果是,down_trylock(&lock)=0,否則非0。return 0;elsereturn -EBUSY; //返回錯誤資訊:請求資源不可用。}static int s3c64xx_pwm_close(struct inode *inode, struct file *file){up(&lock);return 0;}/*cmd是1,表示設定頻率;cmd是0,表示停止pwm*/static long s3c64xx_pwm_ioctl(struct file *filep, unsigned int cmd, unsigned long arg){switch (cmd) { //如果cmd=1,進入case PWM_IOCTL_SET_FREQcase PWM_IOCTL_SET_FREQ:if (arg == 0) //如果設定的頻率參數是0return -EINVAL; //返回錯誤資訊,表示向參數傳遞了無效的參數PWM_Set_Freq(arg); //否則設定給定的頻率break;case PWM_IOCTL_STOP: //如果cmd=0,進入default:PWM_Stop(); //停止蜂鳴器break;}return 0;}static struct file_operations dev_fops = { //初始化裝置的檔案操作的結構體 .owner= THIS_MODULE, .open= s3c64xx_pwm_open, .release= s3c64xx_pwm_close, .unlocked_ioctl= s3c64xx_pwm_ioctl,};static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR,.name = DEVICE_NAME,.fops = &dev_fops,};static int __init dev_init(void){int ret;sema_init(&lock, 1); //初始化一個互斥鎖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("S3C6410 PWM Driver");
1 CPU 計數器控制寄存器
1)配置定時器輸入時鐘
TCFG0-時鐘配置寄存器0,用於獲得預分頻值(1~255)
TCFG1-時鐘配置寄存器1,用於獲得分割值(2,4,8,16,32)
定時器輸入時鐘頻率=PLCK/{預分頻+1}/{分割值}
2)配置PWM 的占空比
TCNTB0-定時器0 計數緩衝寄存器 ,是由定時器的輸入時鐘分頻得到,是脈寬調製的頻率
TCMTB0-定時器0 比較緩衝寄存器 ,用於設定PWM 的占空比 ,寄存器值為高電平的
假設TCNTB0 的頻率是160,如果TCMTB0 是110,則PWM 在110 個周期是高電平,50 周期是低電平,從而占空比為11:5
3)定時器控制寄存器TCON
TCON[0~4]用於控制定時器0
2 核心中基於訊號量的Llinux 的並發控制
在驅動程式中,當多個線程同時訪問相同的資源時,可能會引發“競態”,因此必須對共用資源進行並發控制。訊號量(絕大多數作為互斥鎖使用)是一種進行並發控制的手段(還有自旋鎖,它適合於保持時間非常短的時間)。訊號量只能在進程的上下文中使用。
sema_init(&lock,1)初始化一個互斥鎖,即他把訊號量lock 設定為1
void up (&lock) 釋放訊號量,喚醒等待者
int down_trylock(&lock) 嘗試獲得訊號量lock ,如果能夠立刻獲得,就獲得訊號量並返回為0.否則返回非0.並且它不會導致休眠,可以在中斷上下文中使用。在PWM 中,當計
數值溢出時,就會引發計數中斷。所以在這裡用這個函數來獲得訊號。
測試程式pwm.c:
#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 0x1bstatic 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 settingif(tcgetattr(STDIN_FILENO, &oldt) < 0) {perror("save the terminal setting");exit(1);}// set terminal as neednewt = oldt;newt.c_lflag &= ~( ICANON | ECHO );if(tcsetattr(STDIN_FILENO,TCSANOW, &newt) < 0) {perror("set terminal");exit(1);}ch = getchar();// restore termial settingif(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 buzzeratexit(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 frequencyint 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;}}}
在pwm.c所在的目錄命令列下輸入:
arm-linux-gcc -o pwm pwm.c
產生了pwm可執行二進位檔案,將其拷貝到開發板中,修改其屬性後運行測試。可通過按下鍵盤上的“+”、“-”以改變pwm輸出的頻率。