要求
- 在PC机的Linux系统中完成编写8253的内核模块驱动程序和应用程序;
- 利用驱动程序提供的方法,应用程序既可以实现简易的键盘式电子琴的功能,又可以完成一段固定乐曲的播放。
- 作业报告中包括能正常编译成设备驱动模块的Makefile、C语言源程序和头文件,及其他文字注释;
- 画出包含8253, 8255, 8259三个芯片的基本电路图,以说明如何实现扬声器发声。
背景知识与提示
这部分内容主要帮忙理清头绪和思路
音符与频率对应关系(单位: Hz):1、PC机中有一片可编程的定时器/计数器 8253 芯片, 8253在系统中占用 40H∼43H端口。具备控制系统的扬声器,产生声音信号(T/C2)的功能。控制端 GATE2和扬声器前均接有控制信号,来自可编程 I/O接口芯片 8255的 PB0和 PB1。8255初始化已将B口设为方式0输出。主中断控制器 8259A 占用 20H 和 21H 端口,其IR2供级联从片输入。PC机为可编程并行接口 8255A 分配端口 60H∼63H,初始化后A口作为键盘输入端口,B口用于一些控制信号输出。
2、 利用T/C2对扬声器的控制功能,8253在方式三工作下输出近似方波,可产生适当频率的方波从而将相关的开关打开,使其作用在扬声器上,便可以听到该频率的声音。可以直接使用汇编指令对扬声器等I/O端口操作。
(但是这里我们不使用汇编语言,而可以使用内核提供的C语言函数inb p()、outb p()的方法)
作业内容
本次作业需要完成的内容和解决的问题
- 设计电路原理图,包含8253、8255、8259三个芯片;
- 在电路原理图部分里,对8253使用工作方式三而不是方式二做一个简单说明;
- 编写字符设备驱动程序keyboard.c和应用程序app.c,以及相应的Makefile文件,源代码展示;
- 运行过程展示。
1. 电路原理图
在背景知识和提示部分里,我们已经对8253、8255、8259芯片的功能进行的解释,8259是主中断控制器,而8253利用工作方式三产生一定频率信号,8255控制频率信号的通断。
1、8255的B口设置为输出,8255的B口的低两位用来控制扬声器驱动,当输出端口的PB0位为“1”或为“0”时,将使控制驱动器的与门电路接通或关闭,使8253所发出的音频信号能到达驱动器或被阻断。
2、通过控制PB0的变化,可使扬声器接通和断开,控制扬声器是否能发出声音。
3、通过控制PB0的通断时间,就能发出不同的音长。
4、8255的PB1位为“1或为0”时,控制8253定时器产生驱动扬声器发声的音频信号或不发信号。8253 T/C2 工作在方式三负责向扬声器发送指定频率的脉冲信号。当8255的PB0和PB1都为1时,8253发出指定频率的声音信号的前提下,声音信号通过与门到达驱动器驱动扬声器发声。
通过上面这段说明我们可以绘制包含8253、8255、8259芯片的电路原理图:
- 8253芯片CLK接1.19MHz的时钟信号;
- 8255的PB0端口接到8253的门控GATE2;PB1与8253的OUT2接入与门 -> 驱动 -> 扬声器,从而控制扬声器;
-
8259作为主中断控制器,产生计时中断信号。
关于8253工作方式的说明
8253 有六种工作方式:
- 方式0:计数结束中断,GATE 保持高电平,输出N*T 的低电平,后变为高电平并保持不变。计数中如果GATE 突然变成低电平,然后变回高电平,则延长低电平输出时间,延长时间为GATE 变为低电平的时间。
- 方式1:可编程单个触发信号。与方式0 一样,只是GATE 是上升沿触发。计数期间如果GATE变为低电平再变高电平,则重新开始计数。
- 方式2:速率发生器。GATE 保持高电平。输出(N-1)*T 的高电平,之后输出一个T 的低电平。
- 方式3:方波发生器。如果N 为偶数,输出占空比50%,周期NT 的方波,如果N 为奇数,输出的方波中高电平为(N+1)/2T,低电平为(N-1)/2*T。
- 方式4:软件触发选通。如同方式2,只是只计数一个周期,即输出(N-1)T 的高电平和1T的低电平之后保持高电平不变。
- 方式5: 硬件触发选通。如同方式4,GATE 改为上升沿触发。
在 8253提供的六种工作方式中,只有方式二和方式三是不需要硬件触发能产生连续波形的方式。方式二产生信号的占空比随频率改变而改变,而与方式二相比,方式三产生的信号更接近于方波,因此选择方式三。
2、字符设备驱动与应用程序编写
1)字符设备驱动程序keyboard.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ioport.h>
#define MAJOR_NUM 200
#define DEVICE_NAME "8253_driver"
#define BUF_LEN 2
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static int Device_Open = 0;
static char msg[BUF_LEN];
static char *msg_Ptr;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
//初始化函数
int init_module(void){
int major;
major = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
if (major < 0){
printk (KERN_ALERT "Failed ro register the device!");
return major;
}
printk (KERN_ALERT "Success to register the device! The MAJOR_NUM is %d!",MAJOR_NUM);
return 0;
}
//清除函数
void cleanup_module(void){
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
printk(KERN_ALERT "The device has been unregistered!\n");
}
static int device_open(struct inode *inode, struct file *file){
if (Device_Open)
return -EBUSY;
Device_Open++;
printk(KERN_ALERT "Device opened!\n");
outb_p(0xb6,0x43);
return 0;
}
static int device_release(struct inode *inode, struct file *file){
Device_Open=0;
outb_p(0,0x61);
return 0;
}
//用户空间写操作
static ssize_t device_write(struct file *file, const char *buffer, size_t length,loff_t *offset){
outb_p(0,0x61);
copy_from_user(msg, buffer, length);
outb_p(msg[0],0x42);
outb_p(msg[1],0x42);
outb_p(3,0x61);
printk(KERN_ALERT "Written\n");
return 0;
}
//用户空间读操作
static ssize_t device_read( struct file *filp, char *buffer,size_t length,loff_t *offset){
if (*msg_Ptr == 0)
return 0;
copy_to_user(buffer, msg_Ptr, length);
return length;
}
MODULE_AUTHOR("qyz");
MODULE_LICENSE("ZWMDR");
2)应用程序app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
int main(){
int fd;
int flag=1;
int run=1;
char mode;
char key_input;
int i,j;
int fre;
int key_div;
const int keys[3][7]={
{131,147,165,175,196,220,247}, //低⼋度
{262,294,330,349,392,440,494},
{523,587,659,698,784,880,987} //⾼⼋度
};
//小星星
int Little_Star[48]={7,7,11,11,12,12,11,11, 10,10,9,9,8,8,7,7,11,11,10,10,9,9,8,8,11,11,10,10,9,9,8,8, 7,7,11,11,12,12,11,11,10,10,9,9,8,8,7,7};
//两只老虎
int Two_Tigers[36]={7,8,9,7, 7,8,9,7, 9,10,11,11, 9,10,11,11, 11,12,11,10,9,7,11,12,11,10,9,7,7,4,7,7, 7,5,7,7};
fd = open("/dev/keyboard", O_RDWR);
if (fd == -1){
printf ("Can't open device file\n");
return -1;
}
while (run){
printf("[请选择程序运⾏模式:\n");
printf("1.电⼦琴模式\n");
printf("2.播放器模式\n");
printf("q.退出程序\n");
printf("\n");
scanf("%c",&mode);
getchar();
switch(mode){
case '1':{
printf("[Mode 1: 电⼦琴模式\n");
flag=1;
while(flag){
printf("音调从高到低依次为:\n");
printf("[< w e r t y u i >\n");
printf("[< a s d f g h j >\n");
printf("[< z x c v b n m >\n");
printf("输入q退出\n");
scanf("%c", &key_input);
getchar();
switch(key_input){
case 'w':fre = keys[2][6];break;
case 'e':fre = keys[2][5];break;
case 'r':fre = keys[2][4];break;
case 't':fre = keys[2][3];break;
case 'y':fre = keys[2][2];break;
case 'u':fre = keys[2][1];break;
case 'i':fre = keys[2][0];break;
case 'a':fre = keys[1][6];break;
case 's':fre = keys[1][5];break;
case 'd':fre = keys[1][4];break;
case 'f':fre = keys[1][3];break;
case 'g':fre = keys[1][2];break;
case 'h':fre = keys[1][1];break;
case 'j':fre = keys[1][0];break;
case 'z':fre = keys[0][6];break;
case 'x':fre = keys[0][5];break;
case 'c':fre = keys[0][4];break;
case 'v':fre = keys[0][3];break;
case 'b':fre = keys[0][2];break;
case 'n':fre = keys[0][1];break;
case 'm':fre = keys[0][0];break;
case 'q':fre=0;flag=0;break;
}
key_div=(1193182.0/(1.0*fre));
write(fd, &key_div, 2);
if(flag){
usleep(500000);
key_div=0;
write(fd, &key_div,2);
}
}
break;
}
case '2':{
printf("[Mode 2: 播放器模式\n");
printf("[播放列表:\n");
printf("[1:两只老虎\n");
printf("[2:小星星\n");
printf("[请选择播放歌曲:\n");
scanf("%c", &key_input);
getchar();
switch(key_input){
case '1': //两只老虎
for(j=0;j<36;j++){
key_div=1193182.0/keys[Two_Tigers[j]/7]
[Two_Tigers[j]%7];
write(fd,&key_div,2);
usleep(500000);
printf("正在播放中...\n");
}
break;
case '2':
for(j=0;j<48;j++){
key_div=1193182.0/keys[Little_Star[j]/7]
[Little_Star[j]%7];
write(fd, &key_div,2);
usleep(500000);
printf("[正在播放中...\n");
}
break;
default:
printf("[退出播放器模式\n");
}
key_div=0;
write(fd, &key_div,2);
break;
printf("In mode 2: keyboard\n");
}
case 'q':{
printf("[退出");
printf("\n");
run=0;
break;
}
default:{
printf("[ERROR\n");
break;
}
}
}
close(fd);//关闭设备
return 0;
}
3)Makefile文件
KSRC = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
obj-m = keyboard.o
default:
$(MAKE) -C $(KSRC) SUBDIRS=$(PWD) modules
clean:
$(MAKE) -C $(KSRC) SUBDIRS=$(PWD) clean
3、程序运行展示
1)make编译与加载驱动
~$ make
得到设备驱动文件keyboard.ko
~$ sudo insmod keyboard.ko
加载驱动~$ lsmod
查看驱动加载成功
2)编译运行应用程序
~$ sudo gcc -o app app.c
编译应用程序
~$ sudo ./app
运行应用程序
4、心得体会
本次实验在加载内核模块部分耽误了很多时间。原因是编译用内核版本与主机内核版本不一致,然后更改grub文件切换内核版本时,出现了失误,开机进入Memtest86界面,我一时没有找到方法处理这个问题,就先搁置了。然后重装了一个版本的Linux,继续完成本次作业。
本次实验难点先是在于对电子琴电路设计的理解,背景知识与提示内容已经给出很多但是有点凌乱,需要自己从中整理出思绪。最关键是设备驱动程序编写,因为不需要通过汇编程序来调用扬声器等I/O端口,使用例如inb_p(0x61);outb_p(3|bit,0x61)
读8255PB口,置位打开扬声器,函数应用使得这部分方便很多。应用程序编写相对简单点,主要是对接口函数的调用。