Linux简易键盘式电子琴

要求

  1. 在PC机的Linux系统中完成编写8253的内核模块驱动程序和应用程序;
  2. 利用驱动程序提供的方法,应用程序既可以实现简易的键盘式电子琴的功能,又可以完成一段固定乐曲的播放。
  3. 作业报告中包括能正常编译成设备驱动模块的Makefile、C语言源程序和头文件,及其他文字注释;
  4. 画出包含8253, 8255, 8259三个芯片的基本电路图,以说明如何实现扬声器发声。

背景知识与提示

这部分内容主要帮忙理清头绪和思路

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()的方法

音符与频率对应关系(单位: Hz):
音符-频率

作业内容

本次作业需要完成的内容和解决的问题

  • 设计电路原理图,包含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芯片的电路原理图:

  1. 8253芯片CLK接1.19MHz的时钟信号;
  2. 8255的PB0端口接到8253的门控GATE2;PB1与8253的OUT2接入与门 -> 驱动 -> 扬声器,从而控制扬声器;
  3. 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

make编译
~$ 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口,置位打开扬声器,函数应用使得这部分方便很多。应用程序编写相对简单点,主要是对接口函数的调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容