实验内容介绍见《GEC210嵌入式系统开发教材20131120(更新)》第102页--“5.4 嵌入式 Linux 的蜂鸣器控制实验”
5.4 嵌入式 Linux 的蜂鸣器控制实验
实验目的:
1、 掌握基本的字符设备的驱动程序设计。
2、 掌握基本的文件操作。
3、 掌握在操作系统下的 PWM 驱动程序的原理。
实验内容:
1、 阅读 S5PV210 数据手册,熟悉 PWM 的原理。
2、 编写 PWM 输出的应用程序。
3、 编写 makefile 文件
4、 下载并调试 PWM 输出的应用程序。
预备知识:
1、 C 语言基础知识。
2、 Linux 下常用命令的使用及 vim 编辑器的使用
3、 程序调试的基础知识和方法。
4、 ARM 应用程序的基本架构。
实验设备及工具:
1、 硬件:GEC210 物联网开发板
2、 软件:PC 操作系统 Ubuntu10.04 、minicom、arm-linux-交叉编译环
基础知识:
1、 硬件原理:
a) 原理图:
b) 系统框架
由该图可知,在选取了 PCLK 作为时钟源后,需经过 8BIT PRESCALER0 分频器,而后,经过 1/1 或 1/2 或 1/4 或 1/8 或 1/16 分频器后,送到逻辑控制电路(Control LogicX)后,被送到引脚输出。所以,对 PWM 的操作就是围绕设置时钟分频,得到合适的频率,然后,设置逻辑控制电路得到合适的脉宽。
c) 寄存器简介:
寄存器 TCFG0 用于配置 PWM 的预分频比,TCFG0[15:8]八位用于配置计时器 2、3、4 (timer2、timer3、timer4)的预分频值。TCFG0[7:0]八位用于配置计时器 0、1 的预分频值。
寄存器 TCFG1 用于配置 PWM 的分频比。
PWM 控制寄存器 TCON,设置启动 PWM,装载寄存器,输出反相及寄存器自动装载等功能。
TCNTB0 是 timer0 的定时器计数寄存器。
TCMPB0 是 timer0 的定时器比较寄存器。
TCNTO0 是 timer0 的定时器计数寄寄存器,可以从中读出当前计数寄存器中的值。
实验原理:
1、 PWM 输出的原理:PWM 输出脚,默认为低电平,PWM 计数器 TCNTn 的初始值等于 TCNTBn,当 TCNTn 的值递减到等于 TCMPBn 的值时,PWM 输出高电平,当 PWM 计数器的递减到 0 时,输出又变为低电平,如此周而复始。
2、 驱动原理:
该驱动实现为一个字符设备,通过 ioctl 函数来设置相关寄存器的值,以此来实现 PWM波形的输出与禁止
3、 驱动代码分析:
(详细代码请查看附件)
< driver / beep_driver.c >
/*
**
**This is a beep driver.
**The author is Alan.
**
**
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "pwm" //设备名称
#define PWM_IOCTL_SET_FREQ 1 //设置PWM的输出频率
#define PWM_IOCTL_STOP 0 //停止PWM
#define NS_IN_1HZ (1000000000UL)//用于设置计数器值来获取相应频率
#define BUZZER_PWM_ID 0
#define BUZZER_PWM_GPIO S5PV210_GPD0(0)//蜂鸣器的输出端口
static struct pwm_device *pwm4buzzer;//pwm设备结构
static struct semaphore lock;//信号锁
static void pwm_set_freq(unsigned long freq)
{
int period_ns = NS_IN_1HZ / freq;
pwm_config( pwm4buzzer,period_ns / 2,period_ns );//设置周期
pwm_enable(pwm4buzzer);//使能pwm
}
static void pwm_stop(void)
{
pwm_config( pwm4buzzer,0,NS_IN_1HZ / 100 );
pwm_disable(pwm4buzzer);
}
static int gec210_pwm_open(struct inode *inode ,struct file *file)
{
if(!down_trylock(&lock))//获取信号锁
return 0;
else
return -EBUSY;
}
static int gec210_pwm_close(struct inode *inode,struct file *file)
{
up(&lock);//p操作,即释放信号锁
return 0;
}
static long gec210_pwm_ioctl(struct file *filp , 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:
break;
}
return 0;
}
static struct file_operations gec210_pwm_ops = {//pwm操作接口
.owner = THIS_MODULE,
.open = gec210_pwm_open,
.release = gec210_pwm_close,
.unlocked_ioctl = gec210_pwm_ioctl,
};
static struct miscdevice gec210_misc_dev = {//misc设备
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &gec210_pwm_ops,
};
static int __init gec210_pwm_dev_init(void)
{
int ret;
ret = gpio_request(BUZZER_PWM_GPIO, DEVICE_NAME);
if(ret)
{
printk("request GPIO %d for pwm failed\n",BUZZER_PWM_GPIO);
return ret;
}
gpio_set_value(BUZZER_PWM_GPIO,0);
s3c_gpio_cfgpin(BUZZER_PWM_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();
s3c_gpio_cfgpin(BUZZER_PWM_GPIO,S3C_GPIO_SFN(2));
gpio_free(BUZZER_PWM_GPIO);
init_MUTEX(&lock);//初始化信号锁
ret = misc_register(&gec210_misc_dev);//注册misc设备
printk(DEVICE_NAME "\tinitiazed\n");
return ret;
}
static void __exit gec210_pwm_dev_exit(void)
{
pwm_stop(); //停止pwm
misc_deregister(&gec210_misc_dev); //接触misc设备
}
module_init(gec210_pwm_dev_init);
module_exit(gec210_pwm_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALAN");
MODULE_DESCRIPTION("This is a pwm_beep driver!");
< driver / Makefile >
ifneq ($(KERNELRELEASE),)
obj-m :=beep_driver.o
else
module-objs :=beep_driver.o
KERNELDIR :=/home/gec/linux_kernel/linux2.6.35.7/
PWD :=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
$(RM) *.ko *.mod.c *.mod.o *.o *.order *.symvers *.cmd
< app / beep_test.c >
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUZZER_IOCTL_SET_FREQ 1
#define BUZZER_IOCTL_STOP 0
void Usage(char *args)
{
printf("Usage: %s \n",args);
return ;
}
int main(int argc,char **argv)
{
int buzzer_fd;
unsigned long freq;
char *endstr,*str;
printf("Usage: %s \n","./beep_test");
if( argc==3 )
{ //例: ./beep_test on 100 或者 ./beep_test off 100
buzzer_fd = open( "/dev/pwm",O_RDWR );//打开设备
if(buzzer_fd<0)
{
perror("open device:");
exit(1);
}
str = argv[2];
errno = 0;
freq = strtol(str , &endstr,0);
if((errno == ERANGE &&(freq == LONG_MAX || freq == LONG_MIN))||(errno != 0 && freq == 0))
{
perror("freq :");
exit(EXIT_FAILURE);
}
if(endstr == str)
{
fprintf(stderr,"Please input a digits for freq\n");
exit(EXIT_FAILURE);
}
if(!strncmp(argv[1],"on",2))//判断是否为打开指令
{
ioctl(buzzer_fd,BUZZER_IOCTL_SET_FREQ,freq);
}
else if (!strncmp(argv[1],"off",3))//判断是否为关闭指令
{
ioctl(buzzer_fd,BUZZER_IOCTL_STOP,freq);
}
else
{
close(buzzer_fd);//出错处理
exit(EXIT_FAILURE);
}
}
else if (argc == 2) //例: ./beep_test off
{
buzzer_fd = open("/dev/pwm",O_RDWR);
if(buzzer_fd<0)
{
perror("open device:");
exit(1);
}
if(!strncmp(argv[1],"off",3)) //判断是否为关闭指令
{
ioctl(buzzer_fd,BUZZER_IOCTL_STOP,freq);
}
else
{
close(buzzer_fd);
exit(EXIT_FAILURE);
}
}
else //输入的参数不等于 3 或者 2
{
Usage(argv[0]);
exit(EXIT_FAILURE);
}
close(buzzer_fd);
return 0;
}
< app / Makefile >
SRC =${wildcard *.c}
OBJ =${patsubst %.c,%.o,$(SRC)}
TARGET = beep-test
CROSS = arm-linux-
CC =$(CROSS)gcc
.phony:clean echo
$(TARGET): $(OBJ)
$(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBM) $(LDLIBS) $(LIBGCC) -lm
clean:
$(RM) $(OBJ) $(TARGET) *.elf *.gdb
echo:
@echo $(OBJ) $(TARGET)