1. 程式人生 > >基於ARM-contexA9-蜂鳴器pwm驅動開發

基於ARM-contexA9-蜂鳴器pwm驅動開發

上次,我們寫過一個蜂鳴器叫的程式,但是那個程式僅僅只是驅動蜂鳴器,用電平1和0來驅動而已,跟驅動LED其實沒什麼兩樣。我們先來回顧一下蜂鳴器的硬體還有相關的暫存器吧:

還是和以前一樣的步驟:

1、看電路圖生氣

     (1)蜂鳴器介面位於電路板的底板,看電路圖可知道是高電平有效。


       (2)相對應的找到核心板的介面。由此可知,我們的蜂鳴器是GPD0_0


  接下來找資料手冊,找到對應的暫存器,然後配置它就可以了。

  2、查資料手冊,找到相關的暫存器,並配置生氣

(1)找到GPD0CON,地址是0x114000A0,我們需要配置GPD0CON(0)為輸出狀態。也就是寫0x1這個值到這個暫存器。

但是本節是PWM,我們就要配置成PWM模式,要選擇第二位也就是0x2。

 

(2)找到GPD0DAT這個暫存器,用於配置蜂鳴器的高低電平,實體地址是0x114000A4,剛好與上一個差4個位元組的偏移

我們只要對這個暫存器寫1和寫0,那麼蜂鳴器就可以叫起來了,哈哈。是不是很簡單?大笑

以上就是我們之前說的蜂鳴器的電路圖還有資料手冊查詢:

接下來我們來看看本節關於PWM的,首先先找到PWM的說明文件,先讀讀它是什麼意思先:


大致的意思可以翻譯成:

Exynos 4412微控制器有五位脈衝寬度調製(PWM)定時器。這些定時器產生內部中斷臂子系統。此外,定時器0,1,2,和3包括一個驅動一個外部輸入/輸出的脈寬調製函式訊號。在定時器0中的脈寬調製有一個可選死區發電機的能力,以支援一個大電流裝置。定時器4是一個沒有輸出引腳的內部定時器。定時器的使用apb-pclk作為時鐘源。定時器0和1共用一個8位預分頻器,可程式設計提供了分裂的第一級PCLK時鐘。定時器2,3,4股不同的8分頻器。每一個定時器它自己的時鐘分頻器,提供了一個二級時鐘分頻(分頻器分為2,4,8,或16)。每一個定時器都有它的32位計數器,定時器時鐘驅動器計數器。定時器計數緩衝暫存器(TCNTBn)的計數器載入初始值。如果計數器達到零,它會產生計時器中斷請求通知中央處理器,定時器操作完成。如果計時器計數器達到零,相應的TCNTBn暫存器值自動重新載入到向下計數器開始下一個迴圈。然而,如果
例如,定時器停止,通過清除啟用定時器定時器執行期間TCNTBn不載入到計數器。PWM功能用途的TCMPBn暫存器的值。定時器控制邏輯改變輸出電平下計數器值與定時器控制邏輯中比較暫存器的值相匹配。因此,比較暫存器決定了時間或關斷時間的脈寬調製輸出。每個定時器是雙緩衝結構,TCNTBn和TCMPBn暫存器允許定時器引數
在一個週期中更新。新的值不起作用,直到當前的計時器週期完成。



說了那麼多,其實我們可以提取和我們要寫的這個驅動程式的關鍵資訊出來:

1、預分頻  

2、固定分頻

3、pwm控制暫存器

4、還有一些跟定期器相關的

資料手冊還告訴了我們如何來使用PWM的步驟,我們來看看:


從英文描述可以知道:

第一步:初始化TCNTBn暫存器159(50 + 109)還有TCMPBn 某個暫存器設定成109。

第二步:啟動定時器:設定開始位和手動更新這個位的關閉。

第三步:將TCNTBn值159載入到向下計數器,然後輸出toutn設定為低電平。

第四步:如果計數器計數下降值從TCNTBn在TCMPBn暫存器109的值,輸出從低到高的變化。

第五步:如果向下計數器達到0,則會產生一箇中斷請求。

第六步:向下計數器自動過載TCNTBn。這是重新啟動週期。

知道了其中的關鍵資訊,我們就可以來看資料手冊,看看怎麼來配置這些相應的暫存器了。

以下是4個pwm輸出口


看看具體的暫存器:

這個就是配置預分頻.

0~7位是配置TIMER0/TIMER1

8~15位是配置TIMER2/TIMER3/TIMER4

根據要使用的定時器進行配置


以下這個是配置固定分頻:

0~3:TIMER0

4~7:   TIMER1

8~11: TIMER2 

12~15:TIMER3
16~19:TIMER4

根據要使用的定時器進行配置


以下這個是配置pwm控制暫存器:

第0位:開始/停止定時器0

第1位:更新TCNTB0和TCMTB0這兩個計數器的值

第2位:TOUT_0 輸出極性

第3位:定時器0自動裝載    開或者關

第4位:死區發生器開關

對以上幾位進行配置相應的引數就可以了:


以下這個是配置定時計數器:

TCNTB0:0-31:計數快取buffer:遞減數值

TCMPB0:0-31:定時器0比較buffer暫存器

TCNTO0:0-31:定時器計數觀察


還有就是配置相應的定時器中斷:

0~4:定時器0/1/2/3/4  中斷狀態/清中斷

5~9:使能定時器0/1/2/3/4 中斷


好了,接下來上程式碼,我們來看看驅動程式:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>

//定義裝置的名字為 pwm
#define DEVICE_NAME				"pwm"

#define PWM_IOCTL_SET_FREQ		1
#define PWM_IOCTL_STOP			0

#define NS_IN_1HZ				(1000000000UL)

//蜂鳴器PWM_ID  0 
#define BUZZER_PWM_ID			0
//蜂鳴器GPIO配置
#define BUZZER_PMW_GPIO			EXYNOS4_GPD0(0)

//定義一個結構體指標
static struct pwm_device *pwm4buzzer;
//定義一個結構體訊號量指標,因為訊號量與鎖的機制差不多
//Mutex是一把鑰匙,一個人拿了就可進入一個房間,出來的時候把鑰匙交給佇列的第一個。一般的用法是用於序列化對critical section程式碼的訪問,保證這段程式碼不會被並行的執行。
//Semaphore是一件可以容納N人的房間,如果人不滿就可以進去,如果人滿了,就要等待有人出來。對於N=1的情況,稱為binary semaphore。一般的用法是,用於限//制對於某一資源的同時訪問。
static struct semaphore lock;

//設定頻率
static void pwm_set_freq(unsigned long freq) {
//PWM的佔空比的配置
	int period_ns = NS_IN_1HZ / freq;

	pwm_config(pwm4buzzer, period_ns / 2, period_ns); 
	pwm_enable(pwm4buzzer);
	//配置相應的GPIO,將蜂鳴器IO配置成PWM輸出模式
	s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_SFN(2));
}
//stop方法函式,來源於operations結構體
static  void pwm_stop(void) {
	s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);

	pwm_config(pwm4buzzer, 0, NS_IN_1HZ / 100);
	pwm_disable(pwm4buzzer);
}

//open方法函式,來源於operations結構體,主要開啟pwm的操作
static int tiny4412_pwm_open(struct inode *inode, struct file *file) {
	if (!down_trylock(&lock)) //嘗試加鎖,如果失敗返回0
		return 0;
	else
		return -EBUSY;
}

//close方法函式,來源於operations結構體,主要是關閉pwm操作
static int tiny4412_pwm_close(struct inode *inode, struct file *file) {
	up(&lock);
	return 0;
}
//控制io口方法函式,來源於operations結構體,其實就是上層系統呼叫傳入一條命令,//驅動識別命令,然後執行相應過程。
static long tiny4412_pwm_ioctl(struct file *filep, unsigned int cmd,
		unsigned long arg)
{
	switch (cmd) {
		case PWM_IOCTL_SET_FREQ:
			if (arg == 0)
				return -EINVAL;
			pwm_set_freq(arg);
			break;

		case PWM_IOCTL_STOP:
		default:
			pwm_stop();
			break;
	}

	return 0;
}

//這就是我們要看的結構體了,其實這個結構體的定義在另一個.h當中,看看它的初始//化方式,跟我們上面那個程式的分析基本上是一樣的。對應的函式名(也就是函式的//首地址)賦值給對應的結構體成員,實現了整個結構體的初始化,這樣的方法類似於//C++和JAVA等高階語言的操作。
static  struct file_operations tiny4412_pwm_ops = {
	.owner			= THIS_MODULE,  			//表示本模組擁有
	.open			= tiny4412_pwm_open,		//表示呼叫open函式
	.release		= tiny4412_pwm_close,         //…
	.unlocked_ioctl	= tiny4412_pwm_ioctl,
};

//雜類裝置的註冊
static struct miscdevice tiny4412_misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &tiny4412_pwm_ops,
};
//pwm裝置初始化,裝置在被insmod插入模組到核心的過程中會呼叫這個函式
static int __init tiny4412_pwm_dev_init(void) {
	int ret;
	ret = gpio_request(BUZZER_PMW_GPIO, DEVICE_NAME);
	if (ret) {
		printk("request GPIO %d for pwm failed\n", BUZZER_PMW_GPIO);
		return ret;
	}

	gpio_set_value(BUZZER_PMW_GPIO, 0);
	s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);

	pwm4buzzer = pwm_request(BUZZER_PWM_ID, DEVICE_NAME);
	if (IS_ERR(pwm4buzzer)) {
		printk("request pwm %d for %s failed\n", BUZZER_PWM_ID, DEVICE_NAME);
		return -ENODEV;
	}

	pwm_stop();

	sema_init(&lock, 1);
	ret = misc_register(&tiny4412_misc_dev);

	printk(DEVICE_NAME "\tinitialized\n");

	return ret;
}
//裝置在被解除安裝rmmod的過程中會呼叫這個函式
static void __exit tiny4412_pwm_dev_exit(void) {
	pwm_stop();

	misc_deregister(&tiny4412_misc_dev);
	gpio_free(BUZZER_PMW_GPIO);
}

//模組初始化
module_init(tiny4412_pwm_dev_init);
//銷燬模組
module_exit(tiny4412_pwm_dev_exit);
//宣告GPL協議
MODULE_LICENSE("GPL");
//作者:yangyuanxin
MODULE_AUTHOR("Yangyuanxin");
//描述:三星PWM裝置
MODULE_DESCRIPTION("Exynos4 PWM Driver");
好了,驅動程式咱們已經寫完了,接著寫Makefile進行編譯(此過程略),然後插入模組等步驟略過。

接下里寫一個測試程式:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//定義蜂鳴器開啟的操作
#define PWM_OPEN		1
//定義蜂鳴器關閉的操作
#define PWM_STOP			0
static void close_bell(void);
static void open_bell(void);
static void set_bell_freq(int freq);
static void stop_bell(void);
int main(int argc, char **argv)
{
	//設定蜂鳴器的頻率的初始值為1000
	int freq = 1000 ;
	//開啟蜂鳴器
	open_bell();
	stop_bell();
	while( 1 )
	{
		while(1){
		//設定蜂鳴器
		set_bell_freq(freq);
		//如果頻率小於20000
		if(freq < 20000){
			//自加
			freq+=100 ;
			printf( "\tFreq = %d\n", freq );
			//跳出到另外一個迴圈
			if(freq == 20000){
				break ;
			}
		}
		}
		
		while(1){
			//設定蜂鳴器	
			set_bell_freq(freq);
			//如果頻率大於1000
			if(freq > 1000)
				//自減
				freq-=100 ;
			printf( "\tFreq = %d\n", freq );
			if(freq == 1000){
				break ;
			}			
		}
		//周而復始的執行,於是蜂鳴器就會像唱歌一樣
	}
}

static int fd = -1;
//開啟蜂鳴器
static void open_bell(void)
{
	//開啟裝置
	fd = open("/dev/pwm", 0);
	//如果開啟小於0表示失敗
	if (fd < 0) {
		perror("open pwm_buzzer device");
		exit(1);
	}

	//初始化蜂鳴器的時候先關閉,不讓它叫
	atexit(close_bell);
}

//關閉峰鳴器
static void close_bell(void)
{
	if (fd >= 0) {
		//關閉蜂鳴器
		ioctl(fd, PWM_STOP);
		if (ioctl(fd, 2) < 0) {
			perror("ioctl 2:");
		}
		close(fd);
		fd = -1;
	}
}
//設定蜂鳴器的頻率
static void set_bell_freq(int freq)
{
	//設定頻率
	int ret = ioctl(fd, PWM_OPEN, freq);
	if(ret < 0) {
		perror("set the frequency of the buzzer");
		exit(1);
	}
}
//停止蜂鳴器
static void stop_bell(void)
{
	//讓蜂鳴器停止叫
	int ret = ioctl(fd, PWM_STOP);
	if(ret < 0) {
		perror("stop the buzzer");
		exit(1);
	}
	if (ioctl(fd, 2) < 0) {
		perror("ioctl 2:");
	}
}
編寫完成之後進行編譯後,執行程式會看到以下現象,然後蜂鳴器像唱歌一樣開始叫: