第一篇:北邮小学期实验报告-简易红外遥控系统
简易红外遥控系统
实验报告
班级: 姓名: 学号: 指导老师: 学院:
北京邮电大学
一、实验要求
1、遥控对象8个,被控制设备用LED分别代替,LED发光表示工作。接收机与发射机的距离不小于2米。2、8路设备中的一路为LED灯,用指令遥控LED灯亮度,亮度分为8级并用数码管显示级数。
3、在一定的发射功率下,尽量增大接收距离。
4、增加信道干扰措施。
二、实验步骤
1、实验前的准备工作
分析题目,对各个模块进行详细的设计,查找要使用的芯片的管脚图、功能表等,设计整体电路,详细到各个管脚的连线、所接电阻电容的大小、电路工作频率等。
2、搭建电路
按照设计的电路图搭建电路,电路分为发射和接收两部分,搭建时一定要耐心细致,避免错误,连线应当清晰明了,方便后期电路的检查和调试,各模块间最好相互独立,搭线时注意VCC和地线的连接。总之,搭建电路就是要细心、耐心。
3、电路调试与改进
这是本次实验的重点和难点,也是最耗费时间和精力的部分。往往在电路调试中会发现实验电路的一些细节性错误。如果在电路确认无误的情况下却仍然得不到期望的结果,就要考虑电路的改进了,主要是要改变一些所连接的电阻和电容的值。当然,问题也可能出在面包板上,我们所用的面包板由于长期的使用,有一些插口之间发生了短路,或者接触不良,都将会影响我们的实验结果,所以这也是一个需要考虑和检查的地方。
三、芯片选择以及具体电路图
见《简易红外遥控系统设计方案》
四、实验截图
发射管的输出波形:
接收波形:
五、实验中遇到的问题及解决方法
1、无线接收问题。
很多同学都遇到了这个问题,使用有线连接时能够得到想要的输出,使用无线时却根本收不到信号。通过示波器对各个管脚输出进行检测,发现问题出在CX20106这个芯片。后来通过改变CX20106芯片管脚所接的电阻,得到了输出,接受到了信号。
2、数码管显示问题
在发射和接收都调试好后,我发现我的数码管在开关拨1和拨4的时候都只显示0,用示波器检查输出没有错误,怀疑是面包板插口下面短路了,于是重新换了个位置插,结果就输出显示正确了。验证了是面包板的问题。
六、心得体会
这次的小学期为期一周,我选择的题目是简易红外遥控系统设计,因为在网上找到了上一届学长的设计方案,因此方案设计这个较为困难的步骤就比较投机取巧地解决了。
然后是按照设计方案搭建电路,搭建电路是一件不困难但要细致的工作。事实也证明,我们大多数同学都很快的完成了电路的搭建,但调试却不那么顺利,最后检查会发现很多细节上的遗漏或错误。对于我们的电路而言,任何一个细节的错误都可能导致结果的失败。
在电路搭建完成并且检查完成后,我开始用示波器检查每个芯片的输出管脚,在发射模块的调制部分,一开始NE555D的输出不正确,检查电路连接也没有错误,芯片也是好的,最后只能重插了该电路,结果就对了,所以之前应该是面包板某个地方短路了,事实证明我们用的面包板某些插口间短路或者接触不良是个还比较普遍存在的问题。总之发射部分比较顺利。
至于接收部分,当我从发射部分直接连线过去,结果很快就出来了,本来还挺高兴,结果用无线时接收部分却一点反应都没有。由于对芯片的了解并不深,一开始只是盲目按照学长的设计方案连线,连线完成后结果出不来,瞬间有种手足无措的感觉,无奈只好自己又重新学习认识了一下用到的几块芯片,尝试着改变一些管脚所接的电阻和电容,后来又重新搭建了两次电路,调试了三天终于有了结果。
通过这次课程设计,我不仅提高了动手能力,还学会了一些道理,那就是不能投机取巧,打好基础最重要。试想如果一开始自己先好好研究芯片和电路而不是为了省事儿直接用学长的电路,后期的调试就不会那么困难,结果也应该会更快出来。
最后感谢老师在整个课程设计中对我们的细心教诲和耐心指导,谢谢。
第二篇:北邮小学期c++实验报告
面向对象程序设计与实践
c++实验总结报告
——网络工程14班饶思哲 ——学号:2013211574 实验一简单C++程序设计 1.猜价格游戏
编写C++程序完成以下功能:
(1)假定有一件商品,程序用随机数指定该商品的价格(1-1000的整数);
(2)提示用户猜价格,并输入:若用户猜的价格比商品价格高或低,对用户作出相应的提示;
(3)直到猜对为止,并给出提示。
题目1-1总结:
1)本题需要随机生成整数,我开始只使用rand(),即price=rand();来生成随机整数
但这样只是一个伪随机函数,每一次重新打开程序生成的数都是一致的。因此加入时间随机种子:srand((unsigned)time(NULL))2)个人改进1:定义最大值最小值,在每次猜测数字时显示应猜的数字范围,作为提示。
想到这个是因为有一次猜了很多遍都没猜到,一时突然忘记猜到什么范围,然后往前翻猜过的数字和大小感觉相当麻烦,所以就添上了应猜范围,方便再一次猜数。3)个人改进2:本来有一个判断条件判断生成的随机数是不是1~1000范围内,而后更进为随机生成整数对1000取余得到0~999整数,再+1得到1~1000的整数。
实验二类与对象 1.矩形
编写C++程序完成以下功能:
(1)定义一个Point类,其属性包括点的坐标,提供计算两点之间距离的方法;(2)定义一个矩形类,其属性包括左上角和右下角两个点,提供计算面积的方法;(3)创建一个矩形对象,提示用户输入矩形左上角和右下角的坐标;(4)观察矩形对象以及Point类成员的构造函数与析构函数的调用;(5)计算其面积,并输出。
题目2-1总结:
1)这一题是第一次用到class类的题目,开始并不明白为何要定义class,然后还需要区分public和private。而后来去图书馆借了书看到c++最大特色就是可以封装,定义私有属性和公有函数,以确保有些函数和参数不会被轻易访问到,降低错误率。2)class类在最起初定义时总是在class Rectangle那一行报错,经查书发现class定义最后一个大括号后有分号,开始并没有加上。
3)起初在调用class中函数时用c调用的方式,没有跟面向的对象结合,导致编译出错,而后从distance()改为p.distance()就正确了。
4)计算两点距离和面积运用开方和绝对值函数,前面若没有加头文件math.h则会报错。5)输入左上角右下角坐标时,若输入不当,可能会出现面积为负值的情况,所以加上绝对值函数保证面积非负。
6)起初不知道构造函数和析构函数的定义和用法,经翻阅书籍和运行程序得知构造函数在创建对象时调用,可以有多个。而析构函数则在释放对象时调用,一般每一个class中都只有一个默认析构函数。且构造函数与类名称一致,析构函数则在类名称前加~。
2.友元
编写C++程序完成以下功能:
(1)定义一个Boat和Car两个类,他们都具有私用属性——重量;
(2)编写一个函数,计算两者的重量和。double TotalWeight(Boat& b, Car& c);
题目2-3总结:
1)友元函数:在两个对象中都使用到时,可以使用友元函数,并在类外单独定义。
友元函数是允许在类外访问类中的任何成员的。开始在类外单独定义时跟类的成员函数单独定义混淆,写成了double Boat::TotalWeight(),导致编译错误,而后发现友元函数直接用函数名和函数返回值类型定义即可,不需要加上class类的名称。
2)起初定义完class Boat和class Car后发现编译错误,在友元函数的声明那一行出错,而后发现在这行之前没有定义Car类,于是将其在最开头声明出来,通过。
3)起初并没有加上boat和car类的构造函数和析构函数,可是经上网查阅,默认构造函数和析构函数可以系统自动生成,但析构函数只能删除成员指针,并不能释放指针指向的空间,所以若没有申请动态内存,析构函数可不写出,若申请,则需自行在析构函数中delete。
实验三数组与指针 1.矩阵
(一)编写C++程序完成以下功能:
(1)假定矩阵大小为4×5(整型数组表示);
(2)定义矩阵初始化函数,可以从cin中输入矩阵元素;(3)定义矩阵输出函数,将矩阵格式化输出到cout;
(4)定义矩阵相加的函数,实现两个矩阵相加的功能,结果保存在另一个矩阵中;(5)定义矩阵相减的函数,实现两个矩阵相减的功能,结果保存在另一个矩阵中;(6)定义三个矩阵:A1、A2、A3;(7)初始化A1、A2;
(8)计算并输出:A3 = A1加A2,A3 = A1减A2。
题目3-1总结:
1)起初在矩阵相加相减的赋值中所用语句为:m.matrix[i][j]=a.matrix[i][j]+b.matrix[i][j] 但是运行程序发现m矩阵所有元素都是0。经单步调试,发现并没有赋值成功。经查阅书籍,了解到this指针是指向类的对象的地址,便改用this->matrix[i][j]作为赋值对象,最后程序正确。
2)个人改进:将行数列数在文件开头用define定义,可以随时更改。
3)经多次调试后,程序运行结果正确,但矩阵看起来非常混乱,因为并没有行列对齐,于是在打印矩阵中每列直接用table空格隔开,保证美观。2.矩阵
(二)编写C++程序完成以下功能:
(1)假定矩阵大小为4×5(整型);
(2)矩阵空间采用new动态申请,保存在指针中;
(3)定义矩阵初始化函数,可以从cin中输入矩阵元素;(4)定义矩阵输出函数,将矩阵格式化输出到cout;
(5)定义矩阵相加的函数,实现两个矩阵相加的功能,结果保存在另一个矩阵中;(6)定义矩阵相减的函数,实现两个矩阵相减的功能,结果保存在另一个矩阵中;(7)动态申请三个矩阵:A1、A2、A3;(8)初始化A1、A2;
(9)计算并输出A3 = A1加A2,A3 = A1减A2;(10)释放矩阵空间。
题目3-2总结:
1)与3-1题目的区别在int main中用new函数动态申请内存,然后析构函数需要释放申请的空间而不只是自动删除指向空间的指针。析构函数如图。
一开始并不知道该怎么动态申请内存,在c中使用malloc可是c++中并不一样。经查阅书籍,发现c++中申请释放内存用new和delete非常简便。2)指针调用类成员函数一开始不知道该怎么写,写成A1.input()结果编译错误。经查阅书籍,得知指针调用成员函数需写成A1->input()的形式。
3)在释放动态内存时,用到delete函数。但开始的时候写delete matrix时候会出问题。上网查询后得到以下delete用法。
Delete用法:当释放内部类型,如int double型时,直接delete a 即可。若是释放自己定义的class类型,需用delete[]matrix来释放内存。3.矩阵
(三)编写C++程序完成以下功能:
(1)用类来实现矩阵,定义一个矩阵的类,属性包括:
矩阵大小,用 lines, rows(行、列来表示);
存贮矩阵的数组指针,根据矩阵大小动态申请(new)。(2)矩阵类的方法包括:
构造函数,参数是矩阵大小,需要动态申请存贮矩阵的数组; 析构函数,需要释放矩阵的数组指针; 拷贝构造函数,需要申请和复制数组; 输入,可以从cin中输入矩阵元素; 输出,将矩阵格式化输出到cout; 矩阵相加的函数,实现两个矩阵相加的功能,结果保存在另一个矩阵类,但必须矩阵大小相同; 矩阵相减的函数,实现两个矩阵相减的功能,结果保存在另一个矩阵类,但必须矩阵大小相同。
(3)定义三个矩阵:A1、A2、A3;(4)初始化A1、A2;
(5)计算并输出A3 = A1加A2,A3=A1减A2;
(6)用new动态创建三个矩阵类的对象:pA1、pA1、pA3;(7)初始化pA1、pA2;
(8)计算并输出pA3=pA1加pA2,pA3=pA1减pA2;(9)释放pA1、pA1、pA3。
题目3-3总结:
1)拿到这道题我认为要定义一个矩阵类对象,其中包括3-1和3-2一样的input函数,print函数,plus函数,subtract函数,还有新增的构造函数析构函数。一上来我认为class中属性不再是之前的某行某列的数值,而是行数和列数还有矩阵的头指针。根据输入的行数列数来申请内存,再将数值存入不同的位置。然后在主函数中分为两个部分,一个是直接创建对象,另一个是创建矩阵数组指针。
2)第一部分跟前两题类似,所以很快就调试完成并且得到了正确的结果,但是指针部分一直出现问题。最开始是三个矩阵分别申请内存,这样的后果就是要输入三次行数和列数的数值,有可能出现行列数不相等的情况,给之后的加减操作带来麻烦。因此想到让A1矩阵申请内存,而让A2、A3矩阵都与A1相等,就可以避免多次输入行列数,且保证了矩阵大小相等。但这样输入第一个矩阵数值后,矩阵元素获取正常。可当输入第二个矩阵的数值以后,A1和A2矩阵的元素都变为第二个矩阵的元素,即第一个矩阵的元素被覆盖掉。导致和矩阵为第二个矩阵的2倍,差矩阵都是零。经过反复调试,曾经试过将含参构造函数中输入行列数改到主函数中,再给A1申请动态内存。但是因为A2和A3还是初始化跟A1相等,结果并没有改变,还是错误的。而后想到用含参构造函数来申请动态内存,输入固定的行列数后,用确定的行列数来new Matrix(x,y),这样一来不仅矩阵大小相等,而且也不会存在前一个矩阵的值被后一个覆盖掉的问题。经过调试,终于得到了正确的结果。
3)调试过程中我了解到指针初始化和赋值过程中是容易发生树脂覆盖的,所以尽量不要将指针初始化成跟某个指针相等。
实验四继承与派生
1、形状
(一)编写C++程序完成以下功能:
(1)声明一个基类Shape(形状),其中包含一个方法来计算面积;(2)从Shape派生两个类矩形和圆形;(3)从矩形派生正方形;
(4)分别实现派生类构造函数、析构函数和其他方法;
(5)创建派生类的对象,观察构造函数、析构函数调用次序;(6)不同对象计算面积。
题目4-1总结:
1)这一题主要是函数的派生方法使用,经过查阅书籍,得知在class定义后加上:: public(基类名称)便是作为一个派生类的定义,可以沿用基类中的成员函数。
2)并且经过对构造函数和析构函数调用,得知是先调用基类的构造函数,再调用派生类的构造函数创建派生类对象,在程序结束时,先调用派生类的析构函数释放派生类对象,再调用基类的析构函数释放基类对象。
2、形状
(二)——虚函数(1)将【形状
(一)】中的基类计算面积的方法定义为虚函数,比较与【形状
(一)】程序的差异;
(2)将【形状
(一)】中的基类定义抽象类,比较与【形状
(一)】程序的差异。
题目4-2总结:
1)起初并不知道虚函数的作用,后来经过查阅书籍得知在基类中定义虚函数,是为了在派生类中定义这一函数的不同操作方式。并且起初只改变了基类中area函数,在其前面加了virtual,但并没有体现出虚函数的运用。而后看到书上虚函数的应用,发现虚函数是可以让一个基类指针调用派生类相应函数的。于是经过改进,在主函数中定义了3个基类指针,分别指向派生矩形类对象、派生圆形类对象、派生正方形对象,然后并非通过对象名称调用成员函数,而是用基类指针调用派生类中的area函数,充分体现出对虚函数的应用。
2)这一题虽然跟上一题相似,但却因为运用了虚函数而变得不同。这一题让我对虚函数有了更加深入的了解。也了解到调用类中的成员函数不一定非要用对象名或者对象指针调用,也可以用其基类的指针通过虚函数调用。
实验五多态性
1、对Point类重载++和――运算符
编写C++程序完成以下功能:
(1)Point类的属性包括点的坐标(x,y);(2)实现 Point类重载++和――运算符:
++p,--p,p++,p--。
++和――分别表示x,y增加或减少1。
题目5-1总结:
1)对于符号的重载开始并不理解什么意思,后来得知是重新定义一下++、--操作的过程,包括自加自减和赋值。
2)通过这一道题,对于自加符号的前后置的区别又有了更深的印象。起初在—p和++p的时候都只是将x和y加一,并没有赋值,导致在主函数中写q=++p和q=--p时q保持原值,造成结果错误。单步调试后发现这一错误,及时改正过来。3)程序结果正确后编写菜单界面,使其操作步骤更加清晰明了。实验六流式IO
1、流式IO
(一)编写C++程序完成以下功能:
(1)使用ofstream向一个文本文件中输出各种类型的数据,并打开文件观察结果:
整数、无符号整型、长整型、浮点型、字符串、……
(2)用十进制、八进制、十六进制方式向文本文件中输出整数;(3)使用控制符和成员函数来控制输出的格式:
set()precision()...题目6-1总结:
1)文件流主要是向文件中输入信息和从文件读取信息。而跟c语言不同的是c需要fopen来打开一个文件,而c++中只需要用fstream函数。且文件输入方式也比c语言简便很多。2)转换进制开始想到的是用数学的方法算出不同进制下的数值,再输入文件中,但非常的麻烦。后经查阅书籍,看到有指定进制的函数,可以用来修改默认的十进制:oct和hex。3)最后程序正确后,编辑menu显示菜单,使操作更加清晰明了。
2、流式
(三)编写C++程序完成以下功能:(1)输入一个文本文件名;
(2)打开文件名,在该文件的每一行前面加上一个行号,保存在另外一个文本文件中。
题目6-3总结:
1)因为第一题流式已经尝试过向文件中输入信息,而经过翻阅书籍得知从文件中可以用getline整行读取字符串,便可以轻易的将文件内容加上行数,并输出到另一个文本文件中。
2)本题我觉得最大的困难在于输入文本文件名,并打开文件名。书上也并没有关于打开文件名的相关资料,于是上网查询,发现可以用string.h中的c_str()函数来获取键盘输入的文件名字符串,并打开文件名。
3)因为文件处理并没有在屏幕上有所显示,为了让每一步操作更加明了,在复制完一行后会在屏幕上打出某某行复制成功,使程序运行过程更清晰。实验七 C++程序设计应用
1、电话本
编写C++程序完成以下功能:
(1)实现简单电话本功能,用姓名来搜索电话号码;(2)用户输入姓名,程序查找并输出结果;(3)用户可以通过输入,添加姓名和电话号码;(4)用户可以删除姓名和电话号码;(5)电话本可以保存在指定文件中;(6)电话可被从指定文件中读入到内存。
题目7-1总结:
1)电话本分为几大部分:读到内存、输出到文件、新增联系人、删除联系人、查找联系人。因为是读到内存,就想到了可以用数组或者链表来构成电话本的框架。而这两种方式在c语言用过,所以逻辑方便很熟悉,于是选择了比较简便的数组,这样不易出现指针错误的情况。
2)个人改进1: 新增联系人函数中,就是将联系人信息从键盘逐项读入,然后选择将联系人插入到什么位置。若一共6个联系人,插入位置输入3,则插入第三个位置,后面的联系人依次向后移一位。但若是一共6个联系人,插入位置输入8,则会提醒输入位置不当,直接将联系人插入到最后一个位置,即第七。
3)删除、查找、修改函数中都用到了用姓名查找,于是将其单独摘出来定义findposition,找到联系人位置。起初写的是将输入的字符串逐个跟name比较,即a == name,但却发现这样程序查找出来联系人永远不存在。经上网查阅,发现字符串相等不能直接像字符一样用==来判断,而是用><来判断,于是将其改为>=&&<=。但一开始由于疏忽,将&&且关系写成||或关系,导致查找出来永远是第一个联系人。后来单步调试发现错误,改正过来。
4)个人改进2: 删除、查找、修改中,若是联系人不存在,需要输出提示。而我另外又加了一句,并不是说不存在就直接返回,而是让用户自己选择是继续输入姓名还是返回主菜单。也让手误的时候不再把前面的操作进行一遍,留有手误的余地。5)修改联系人模块,一开始class类中只有setPhone函数,并没有setname,setmobile等等,而是直接将所有属性值组合起来构建联系人。这样的后果就是在修改联系人的时候,只修改了一项,但是并不能赋值进去。表面上虽然修改了,可查找后发现联系人的信息并没有改变。后来将每一项信息都分别set,经过调试最终结果正确。
6)从指定文件将电话本读入内存,就跟实验6第三题的流式很相似,都是逐行读取信息,只是读取后是存入数组的不同属性值中。开始不知道该怎么确定文本文件中不再有联系人信息,而后上网查资料发现当文件中不再有信息可以读的时候,infile>>name>>mobile…的值就是0,便可停止操作。而姓名等信息也可以逐个赋值到phone类数组里。
7)个人改进3: 电话本不需要手动保存在指定文件中,主菜单也不再显示这一操作,而是当选择退出电话本时自动保存到输出的output.txt中。
8)最后程序执行结果正确,编写menu菜单界面,美化界面并且让操作编号更加清晰。
第三篇:北邮小学期AVR单片机电子琴实验报告
北京邮电大学信息与通信工程学院
小学期AVR单片机实验报告
实验题目: 基于ATmega16L单片机的电子琴设计
学生姓名:学渣
班 级:2012XXXXXX 班内序号:XX 学 号:2012XXXXXX 日 期:2014年9月30日
同组同学:学渣
第1页 北京邮电大学信息与通信工程学院
目录:
一、实验介绍......................................3 1.1实验课题名称.................................3 1.2实验平台.....................................3 1.3实验课题关键字 ..............................3 1.4实验摘要.....................................3
二、小组分工......................................3
三、基本题目训练——流水灯与数码管秒表计时器.......3 3.1实现功能......................................3 3.2程序代码分析..................................4 3.3实验结果图片...................................7
四、有关发声的基础知识............................7
五、电子琴的设计与测试..............................9 5.1设计过程......................................9 5.2实验所需元器件.................................9 5.3实验程序主要流程图.............................10 5.4实验原理及原理图...............................10 5.4.1实验原理....................................10 5.4.2原理图.....................................11 5.5各个模块的设计与讲解............................11 5.6程序源代码及程序分析..........................14 5.7实验结果.......................................24
六、排错过程.........................................26
七、心得体会..........................................29
八、参考文献..........................................32
九、意见与建议........................................33
第2页 北京邮电大学信息与通信工程学院
一、实验介绍:
1.1实验课题名称:基于ATmega16L单片机的电子琴设计 1.2实验平台:本实验所用平台为AVR Studio 4 1.3实验课题关键字:
ATmega16L型单片机 电子琴 键盘按键 LCD液晶显示屏
1.4实验摘要:
本实验设计的电子琴拥有可视化操作界面,能实现即时弹奏音乐、音乐播放、音乐变速、音乐变调,并可以进行任意长度录音(通过按键记录音阶)等功能
二、小组分工:
XXX负责电路硬件的连接和报告的撰写 XXX负责程序代码的编写 XXX负责资料的收集整理和查阅
三、基本题目训练——流水灯与数码管秒表计时器
3.1功能:八盏LED二极管按顺序依次循环点亮,实现流水灯的效果,同时两只数码管分别代表秒和十分之一秒,进行秒表计时,配有
第3页 北京邮电大学信息与通信工程学院
两个按键,实现计时过程中的暂停和继续,同时在按下暂停键的时候蜂鸣器会响一声。利用ATmega16的寄存器中断功能,实现流水灯和数码管秒表计时器在实验板上同时工作,并且互相独立不影响。
3.2程序代码分析:
#include
char b[10] = {
0b11111010,//0
0b00110000,//1
0b11011001,//2
0b01111001,//3
0b00110011,//4
0b01101011,//5
0b11101011,//6
0b00111000,//7
0b11111011,//8
0b01111011,//9
};char a[10] = {
0b11111010,//0
0b00110000,//1
0b11011001,//2
0b01111001,//3
0b00110011,//4
0b01101011,//5
0b11101011,//6
0b00111000,//7
0b11111011,//8
0b01111011,//9
};
volatile char temp;
int main(void){ DDRA = 0xff;PORTA = 0b10000000;DDRC = 0xff;
第4页
北京邮电大学信息与通信工程学院
DDRD = 0b11111011;DDRB = 0b11111011;
TCNT0 = 55;PORTB = b[0];PORTD = a[0];PORTC |=(1 << 0);TCCR0 |=(1 << CS01);int count1 = 0,count2 = 0,i;
MCUCR |=(1 << ISC00)|(1 << ISC01);//INT0上升沿触发
GICR |=(1 << INT0);//使能INT0,INT1 sei();//使能全局中断
while(1){
for(i = 0;i<9000;i++)
{
while(!(TIFR &(1< TCNT0=55; } count1++; if(count1!=10) PORTD = a[count1]; else { count2++; count1 = 0; if(count2 == 10) count2 = 0; temp = PORTA; PORTA = PORTA >> 1; if(temp & 0b00000001) { PORTA = PORTA | 0b10000000; } PORTB = b[count2]; PORTD = a[0]; } } } SIGNAL(SIG_INTERRUPT0)//INT0中断服务程序 { int count3 = 0,count4=0; 第5页 北京邮电大学信息与通信工程学院 while(!(PINB &(1<<2))) { count3++; if(count3 == 10) { count4++; count3 = 0; temp = PORTA; PORTA = PORTA >> 1; if(temp & 0b00000001) { PORTA = PORTA | 0b10000000; } } for(int i=0;i<9000;i++) { while(!(TIFR &(1< TCNT0=55; } } } 第6页 北京邮电大学信息与通信工程学院 3.3实验结果: 四、有关发声的基础知识: 声波是振动产生的。频率即表示每秒钟振动的次数,采用CTC方式时AVR单片机通过特定的端口(PD4及PD5)输出一定频率的方波,TCCR1A设为比较匹配时OC1A/OC1B电平取反,TCCR1B的计数上限为 第7页 北京邮电大学信息与通信工程学院 OC1A,根据公式OCnA=f/2N(1+OCRnA)计算出7个频率音阶所需的OCR1A,则只需将喇叭接在PD4或PD5,通过程序控制端口输出特定频率的方波波形(发声使用正弦波最好,方波效果稍次但影响不大),喇叭就会发出七种不同的声音,依照人听觉分辨7个音阶分为三组,分别为高中,低音阶频率,经计算可得,当OCR1A=(500000/musicmem[i]-1)时,{131,147,165,175,196,220,247}存放低音阶频率,{262,294,330,349,392,440,494}存放中音阶频率,{524,588,660,698,784,880,988}则存放高音阶频率,所以需要定义三个数组存放各音阶的频率值。除了音符频率以外还需要音长,所以定义1个2位数组表示一段音乐,第一个表示频率,第二个表示音长,播放时先访问频率数组,使喇叭发声,之后访问音长数组,确定喇叭发声时间。而有了音符频率数组,只要再得到任意一首歌的简谱,就可以将其转化为两个数组的形式,由音符对应的频率得出频率数组,然后再根据每个音符的音长,将其通过乐曲的节拍和音符的拍数计算出音符持续时间即可得出音长数字。 第8页 北京邮电大学信息与通信工程学院 五、电子琴的设计过程: 5.1设计过程: 5.2 实验所需元器件: ATmega16L型单片机,JTAG下载器,扬声器,4*4矩阵键盘,液晶屏,LM386,实验盒(内装剪刀、镊子、导线等用品)等 第9页 1602LCD北京邮电大学信息与通信工程学院 5.3实验程序主要流程图: 5.4实验原理及原理图 : 5.4.1实验原理 : 以ATmega16单片机作为整个系统的控制中心,外加琴键控制模块、播放模块、显示模块,使制作的电子琴完成设想的功能。琴键控制模块为4*4矩阵键盘,可以通过按下不同的琴键弹奏出不同的音阶,每个音阶对应着不同的频率,一段音乐是由许多不同的音阶组成,这样我们就可以根据不同的频率组合得到我们想要的音乐,同时在录制模式下,还可以通过按不同的按键记录下不同的音阶,由此记录一段音乐。播放模块接收对应频率的方波,由此播放琴键弹奏的音阶以 第10页 北京邮电大学信息与通信工程学院 及播放预先存放在单片机里的音乐。显示模块显示出当前所处的模式。 5.4.2 原理图: 5.5各模块的设计与详解: 中央处理器——ATmega16: 第11页 北京邮电大学信息与通信工程学院 实验中,PB0~PB7全部设置为输出,分配给LCD液晶显示屏D0~D7管脚;PA0~PA7连接4*4矩阵键盘的八个引脚;PD4、PD6和PD7设置为输出状态,分别连接到LCD显示屏的RS、R/W和E端口上;PD5置为输出状态,接到扬声器的一个管脚,扬声器的另一管脚接地;VCC为电源,向LCD显示屏供电;GND为公共接地。 琴键控制模块——4*4矩阵键盘: 工作原理:按键设置在行、列线交点上,行、列线分别连接到按键开关的两端。行线通过上拉电阻接到+5V 电源上。无按键按下时,行线处于高电平的状态,而当有按键按下时,行线电平由与此行线相连的列线的电平决定。 显示模块——LCD液晶显示屏: 第12页 北京邮电大学信息与通信工程学院 引脚详解: 第1脚:VSS为地电源。第2脚:VDD接5V正电源。 第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。 第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。 第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平R/W为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据。 第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。 第7~14脚:D0~D7为8位双向数据线。 第13页 北京邮电大学信息与通信工程学院 第15脚:背光源正极。第16脚:背光源负极。 播放模块——扬声器: 原先以为单片机本身的电源电压不足以驱动扬声器工作,所以设计了有LM386的功放模块,后来在实际操作的过程中测试发现,单片机本身的电源足以驱动扬声器播放音乐音调,因此舍弃了LM386功放模块的设置。 5.6程序源代码及程序分析: #include #define uchar unsigned char #define uint unsigned int const uchar table[]=“HELLO WELCOME!”; volatile uint num=0,count=0;volatile uint Mode=0,list=2,pause=0,aim=0,sure=0,del=0;volatile int state=0, S=1; //定义全局变量,S为变速变量 const uint Mode_Data[16]={0,440,494,523,587,659,698,784,880,998,1046,1156,1318,1396,1568};//存放声音的频率 //音高对应定时器初始化数值(低la~高la+休止符),cpu频率1MHz,用8分频 const uchar tone[]={0x00,0x8E,0x7E,0x77,0x6A,0x5E,0x59,0x4F,0x47,0x3F,0x3B,0x35,0x2F,0x2C,0x27,0x23,0X19,0X15}; uint Ssong[10][2]={{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}}; //******************************************** void delay_us(int n) //微妙级延时函数; { 第14页 北京邮电大学信息与通信工程学院 while(n--){ asm(“nop”);//自动延时一个时钟周期什么也不做 } } void delay_ms(int n) //毫秒级延时函数; { int m=14500*n;while(m--){ asm(“nop”);} } //********************************************** /************************ 屏幕显示函数 ************************/ void write_com(uchar com){ PORTD&=~(1<<4); //低电平指令模式。高电平数据; PORTD&=~(1<<6); //低电平写; PORTB=com;PORTD|=(1<<7); //高电平使能 delay_ms(1);PORTD&=~(1<<7); } void write_dat(uchar dat){ PORTD|=(1<<4);PORTD&=~(1<<6);PORTB=dat;PORTD|=(1<<7);delay_ms(1);PORTD&=~(1<<7);} /*********************************** * 音乐产生函数 * * 功能:输出频率为x的方波* * 范围:x:100-20000Hz,0:不发声 * * ***********************************/ 第15页 北京邮电大学信息与通信工程学院 void sound(int i) { TIMSK|=(1<<2);sei(); if(i&&i<17) //按了音符键了,且在0到F键这16个发音键上,才发声 { OCR1A=tone[i]; TCCR1A=0X50; //控制寄存器,选择可翻转功能,产生方波; TCCR1B=0X0a; PORTD=(1<<5);} else { TCCR1A = 0x00; //频率为0,休止符,不发声 TCCR1B=0X00; OCR1A=0; } } /************************************ 弹奏函数 ************************************/ void play(int n) //参数为键盘上的数字; { write_com(0X80+0X40+n);write_dat(16);sound(n); //弹奏音符; delay_ms(1);write_dat(20); TCCR1A=0x00; TCCR1B=0X00; //中断,为下一个节拍做准备; PORTD|=(1<<5); //PD5作为输出管脚,接扬声器; } //___________________________________________ void INI_POTR(void){ DDRD|=0xff;DDRB=0xff; PORTD=(1<<5);PORTD&=~(1<<7);DDRA=0xff; //A口作为键盘输入口; DDRB=0xff; } //_____________________________________ 第16页 北京邮电大学信息与通信工程学院 void INN_DEVICE(void){ MCUCR=0X00; // 中断寄存器置零; } //____________________________________ //检测键盘是否被按下的函数 uint isKeyPress(){ DDRA=0xff; //初始设置端口均为输出 PORTA=0xf0; //设置端口的初值为11110000 DDRA=0xf0; //将低四位设为输入 if(PINA == 0xf0) //当没有按下时,PINA仍然为11110000 { DDRA=0xff; return 0; //返回0,代表未按下 } else //当按下后,PINA不再是11110000 { DDRA=0xff; return 1; //返回1,代表按下了 } } //键盘输入检测函数; //_______________________________ int key_in(void) { DDRA = 0xff; //先全部设置为输出,再将后四位置为输入 PORTA = 0xf0; //设置高低电平 DDRA = 0xf0;uint i = 0,j = 16,Key = 17; unsigned char temp1,temp2; if(isKeyPress()){ temp1 = PINA; temp1 &= 0x0f; //只关心低四位的情况 switch(temp1) { case 0b00000001: j = 0; break; case 0b00000010: j = 1; 第17页 北京邮电大学信息与通信工程学院 break; case 0b00000100: j = 2; break; case 0b00001000: j = 3; break; } temp2 = PINA; temp2 &= 0x0f; if(temp1!= temp2) return 17; DDRA = 0xff; PORTA= 0x0f; DDRA = 0x0f; temp1 = PINA; temp1 &= 0xf0; switch(temp1) { case 0b00010000: i = 0; break; case 0b00100000: i = 1; break; case 0b01000000: i = 2; break; case 0b10000000: i = 3; break; } temp2 = PINA; temp2 &= 0xf0; if(temp1!= temp2) return 17; Key = i*4 + j+1;// return Key; } } /***************************** //去抖动 //只关心高四位的情况第18页 北京邮电大学信息与通信工程学院 音乐播放函数; *****************************/ void m_sound(uint a){ int m=(62500/a)-1; //发声原理 OCR1A=m; TCCR1A=0X50; //控制寄存器,选择可翻转功能,产生方波; TCCR1B=0X0a;} //**********************************播放函数; void music_play(uint a[][2]){ const char Mtable[]=“Music Mode”; write_com(0X01); //清屏; for(int i=0;i<5;i++) { write_dat(Mtable[i]); } int i=0; char tem=aim,T=1; while((a[i][1]!=0)&&(tem==aim)&&state) { //A 返回 B 暂停 F 模式 C 上一曲你 D 播放 E 下一曲; num=key_in(); switch(num) { case 16: state=0; // F STATE = 0弹奏 break; case 15: aim=1; //下一首E break; case 14: pause=1; //播放D break; case 13: aim=0; ///上一首C break; case 12: pause=0; ///暂停B break; case 11: 第19页 北京邮电大学信息与通信工程学院 T=2; break;case 10: T=1; break;case 9: T=0;break;case 5: //变速,1代表正常速度A //慢速0 //快速9 //5 降调 S=0.5; break; case 6: S=1; break; default: break; } while(a[i][0]&&pause) { num=key_in(); if(num==12) { pause=0; } else { if(S == 0.5) m_sound(a[i][0]*S); else m_sound(a[i][0]); if(T==2) delay_ms(a[i][1]-100); else if(T==0) delay_ms(a[i][1]+100); else delay_ms(a[i][1]); i++; TCCR1A=0x00; TCCR1B=0X00; } } pause=0; //6 正常调 //加减速 //下一个音符; 第20页 北京邮电大学信息与通信工程学院 TCCR1A=0x00; TCCR1B=0X00; //中断,为下一个节拍做准备; } if(a[i][1]==0){ aim =(++aim)% list; } } /***************** 录制音乐函数; *****************/ void record(void){ write_com(0X01); const uchar R_table[]=“Recording Mode”; for(int i=0;i<9;i++){ write_dat(R_table[i]);} pause=1;while(pause){ uint Skey = key_in(); if((Skey>0)&&(Skey<11)) { int i=0;//count=0; while(i<10) { Skey=key_in(); OCR1A=0; TCCR1A=0x00; TCCR1B=0X00; if((Skey>0)&&(Skey<11)) { play(Skey); Ssong[i][0]=tone[Skey]; Ssong[i][1]=600; i++; delay_ms(20); } if(Skey==14) 第21页 //清屏;//开始录制 //先清零计数器 //录制完成,北京邮电大学信息与通信工程学院 按D键结束录制 { i=10; } } pause=0; //借助 PAUSE 来标记一下什么时候退出while } } } //***************************歌曲数据 uint music_data[][2]= { {440,400},{440,400},{659,400},{659,400},{740,400},{740,400},{659,800}, {587,400},{587,400},{554,400},{554,400},{494,400},{494,400},{440,800}, {659,400},{659,400},{587,400},{587,400},{554,400},{554,400},{497,800}, {659,400},{659,400},{587,400},{587,400},{554,400},{554,400},{497,800}, {440,400},{440,400},{659,400},{659,400},{740,400},{740,400},{659,800}, {587,400},{587,400},{554,400},{554,400},{494,400},{494,400},{440,800}, {0,0} }; // abcdefg uint music_1[][2]= {{262,400},{294,400},{330,400},{262,400},{262,400}, //乐谱 {294,400},{330,400},{262,400},{330,400},{349,400}, {392,800},{330,400},{349,400},{392,800},{392,300}, {440,100},{392,300},{349,100},{262,400},{392,400}, {440,300},{392,100},{349,300},{330,100},{262,400}, {294,400},{196,400},{262,400},{294,800},{196,400}, {262,400},{294,800},{0,0}};//music_1 int main(void){ INI_POTR(); //初始化 write_com(0X38); //显示光标等; write_com(0X01); //清屏; write_com(0X0f); //打开光标; write_com(0X06); write_com(0X80+0X02); for(int i=0;i<8;i++) 第22页 北京邮电大学信息与通信工程学院 { write_dat(table[i]); } write_com(0X80+0X11); delay_ms(5); while(1) { num = key_in(); if(num==16) //模式选择 { if(state==1) state=0; else state=1; } 放模式 if(state==1) { (state==1) { switchaim() { case 0: music_play(music_data); break; case 1: music_play(Ssong); break; default: break; }while delay_ms(10); } } else if(num==11) //录制模式; { record(); delay_ms(10); } else { write_com(0X01); 第23页 //播 北京邮电大学信息与通信工程学院 } const uchar Ptable[]=“playing Mode”; for(int i=0;i<7;i++) { write_dat(Ptable[i]); } while((num!=16)&&(num!=11)) { play(num); num=key_in(); } } } //弹奏模式 5.7实验结果: 实现了最初设想的功能,所设计的电子琴可以进行即兴弹奏、录制音乐、播放音乐以及音乐变速。测试图片如下: 第24页 北京邮电大学信息与通信工程学院 第25页 北京邮电大学信息与通信工程学院 接通电源后,显示屏以每次显示一个字符的形式显示出“HELLO WELCOM!”的欢迎文字,接着自动进入弹奏模式,并在LCD的第一行显示出“Playing Mode”的文字,用户每次按下按键都会在LCD的第二行的相应位置出现对应按键顺序的光标闪烁。在弹奏模式下,按下F键会进入播放模式,LCD显示出“Music Mode”字样,此时按下D键是播放/继续,按下B键是暂停,C键和E键分别代表上一首和下一首,9键则是将音乐速度调整为慢速,再次按下F键会回到弹奏模式。在弹奏模式下,按下A键会进入录制模式,此时LCD显示“Recording Mode”字样,可以使用1~0十个按键进行录音,想要听听录制好的音乐,可以切换到播放模式下进行欣赏。 六、排错过程: 基础题目训练阶段: 1.硬件连接好之后,在进行编程的过程中发现端口不够用,因为PB3和PD3这两个能产生中断的端口必须空出来作为暂停和开 第26页 北京邮电大学信息与通信工程学院 始按键的接口,于是我重新布线,将数码管显示小数点的右下角的引脚改接到PC0,同时在代码中将此端口设置为高电平输出,这样就解决了该问题。 2.当我们简单地把流水灯和计时器代码合并在一起然后编译运行时,却发现流水灯在工作,计时器却停止了工作。经过仔细排查,发现简单地将代码合并会导致单片机陷入流水灯的工作循环,而没有进入计时器的工作进程。经过一番思考,我们将流水灯的代码加以修改写入计时器的工作循环中,解决了计时器不工作的问题。 3.但是新问题又出现了,我们发现按下计时器的暂停按钮后,会导致流水灯也停止工作。经过小组的讨论,我们决定在中断程序函数里面加上流水灯工作的代码并加以修改使其能够在中断函数里面运行。经过多次调试,终于实现了计时器的计时、暂停、继续功能,并且流水灯能够一直工作不受计时器的暂停影响。 4.在调整计时器计时精读的过程中,我们发现本实验中使用的ATmega16频率不是16MHz,也不是1MHz,为了尽可能的实现秒表计时,试验了很多的数字组合,最后达到了30秒误差1秒的精确度,当然还可以达到更高的精确度。 电子琴设计与实现阶段: 1.在硬件连接的时候,没想到LCD液晶显示屏的每根管脚都需要 第27页 北京邮电大学信息与通信工程学院 连接,导致在进行程序下载验证的时候,显示屏总是不能显示出应有的文字,后来参考了相关文档资料发现是V0这个管脚也必须接地才可以,解决了问题。 2.在安插矩阵键盘的时候,费了很多功夫,首先,不知道哪根管脚是行线哪根管脚是列线,其次不知道所连接的端口的输出输入模式应该怎么设置,导致设计过程受阻,后来观看了视频并查阅相关资料解决了此问题。 3.在初步完成电子琴的弹奏功能程序时,我们开始对单片机进行测试,发现在按下数次按钮后单片机就会卡死,我们仔细检查了弹奏功能的代码,经过多次排查,我们小组最终将键盘检测函数key_in()和弹奏函数play()两条语句的执行顺序交换,以达到比较好的逻辑顺序效果,提高了程序的稳定性,把多次按下按钮会导致单片机卡死的问题给基本解决了。 4.播放音乐功能也是我们设想的一个重要功能。刚开始,按照我们的思路去实现该功能时,在测试时播放音乐是实现了,但是暂停和继续功能都没能够实现。我们小组对代码进行重新检查,检查出几个比较严重的逻辑错误。我们重新调整播放音乐函数的判断语句和循环结构,并加入一个pause变量来标志暂停状态,在按下暂停按钮时将pause标志为0,使程序退出播放循环。经过一番努力,我们顺利完成了暂停播放和继续播放的功能。 5.录制音乐功能的实现是整个实验过程里面最为困难的一环。我第28页 北京邮电大学信息与通信工程学院 们预先设想的实现方法是每按下一个按键,将对应音阶的数字和节拍存入数组中,然后录制完成后自动播放录制的音乐。但是,我们实际测试时发现录制的音乐并没有能自动播放。经过小组讨论,我们决定将录制好的音乐在播放音乐模式里面播放,根据这个思路,我们将录制的音乐对应的数组用播放音乐函数进行播放,成功地实现了录制音乐并播放的功能。 七、心得体会: 为期9天的单片机小学期圆满结束,不得不说,我从中学到了很多知识,从一个对单片机一窍不通的小白,蜕变成了一个熟悉单片机各个端口的作用和使用方法、懂得如何编程实现相应功能的技术人员。 在这九天中,我们通过观看老师给的视频资料和文档,加上查阅的资料,独立完成了所布置的任务。由于有C++的基础,加上有硬件的端口以及相应设备的使用说明文档的帮助,因此在我看来,在AVR Studio上进行C语言的编程从逻辑上比C++更容易理解,所需要的只是在编程时将端口的配置和逻辑函数结合到一起,实现编写的函数对单片机端口以及内部中断的控制。 第一天刚接到流水灯和数码管秒表计时器的任务的时候,内心真的十分激动,心想:实现了这个不就实现了街边广告牌上滚动的文字了么!但是一看到ATmega16芯片时还是傻了眼,心想:这么多的管脚都该怎么用啊?“视频里有”,同学告诉我,于是我就开始认真地 第29页 北京邮电大学信息与通信工程学院 研究起老师给的单片机教学视频和它的技术文档。由此发现,只需要将单片机的端口设置好,再套上一个循环结构,就可以实现流水灯的功能了。说做就做,于是最初的流水灯就实现了,可以一个一个依次循环点亮。那时真是好高兴!在做数码管的时候,由于不知道数码管的显示与管脚之间的关系,于是我先创建了一个数组,数组中的元素都是8位二进制数,其中只有一位是1,其余位都是0,如下: char a[8] = { 0b00000001,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000 }; 然后我采用一个循环,将端口PORTX每次等于一个a[i],单步运行,找出了数码管的显示与管脚之间的对应关系。这样,之后的工作就轻松许多,只需要在该亮的位置将对应的PORTX端口置为1,就可以显示出自己想要的数字了。流水灯和数码管秒表计时器都分别完成了,最后只需将它们合在一起共同工作即可。不过这个过程也不是一帆风顺的,因为中断的概念我刚开始觉得很难理解,也不知道该将中断的有关函数放在代码的哪个位置,因此反复纠结反复研究视频和技术文档,持续了一段时间。后来我和同学决定将两份流水灯的程序,一份放在主程序main中,和秒表计时器的程序放在一起,即实现两者同时工作,且流水灯的闪烁间隔是一秒,另一份放在中断服务程序中SIGNAL()中,即实现按下按钮进入中断服务程序后,秒表计时器暂停 第30页 北京邮电大学信息与通信工程学院 工作,而流水依旧在依次循环闪烁。由此实现了流水灯和秒表计时器的独立工作,互不影响。 完成了基础题目的训练任务,我信心满满,迎来了我的下一个任务——电子琴。刚开始选题的时候觉得电子琴蛮有趣的,可以弹奏可以录音可以播放,而且原理看起来也比较简单。但是其实不是这样的,拿到元器件的时候,我发现,LCD液晶显示屏有16个管脚!矩阵键盘有8个管脚!但是有了前面对付ATmega16芯片的经验,我沉住气,查阅了有关LCD液晶显示屏和矩阵键盘的技术文档,以及一些经验之作,初步定下了键盘接到A端口,LCD的8个数据口接到B端口,D端口用于LCD的读写控制使能端以及扬声器的输入端。至此,硬件的连接工作就完成了,根据硬件的连接,我们开始了程序的编写。键盘检测函数、播放函数、液晶显示函数,弹奏功能,录制功能„„一步一步,一个完整的系统渐渐完善。由于有硬件,因此我们编写的程序可以马上下载到板子里验证效果,并及时修改,大大方便了我们排错和系统优化。期间由于JTAG下载器和AVR Studio总是出现连接失败的情况,浪费了我们许多宝贵的时间,个人总结原因是驱动可能用了一段时间会失效,解决办法是重新启动计算机,并将WINAVR这个环境卸载后再次装上,并且是装到与上一次不同的盘中,同时编译AVR程序的时候先接上JTAG下载器,试验了许多次,这个方法很管用,之后就没有出现该问题了。最终,电子琴的功能基本完成,并且达标,能实现弹奏、播放、录制、变速等最初设想的功能,但是可能还有一些小BUG没有调出来,导致电子琴在运行的过程中有不稳定的现象,第31页 北京邮电大学信息与通信工程学院 也不能排除是板子的问题。 最终,单片机小学期圆满结束了!9天的时间,完全的自己动手实验,不仅增加了自己对单片机的理解,提高了自己的编程能力,而且在这个过程中,我觉得团队的合作至关重要,自己连接的电路或者自己编写的程序,在自己看来都是对的,但是旁观者清,队友能很容易地帮你找出其中的错误;在和队友的讨论中,我能了解对方的想法和思路,多种想法和思路的碰撞能让我收获更多新的东西;组内的分工能让每个人都尽到自己的责任,发挥自己所长。 总之,这次单片机小学期是一次难忘的过程,一次丰富知识、提升技能的历程,也激起了我对单片机的兴趣,我今后会多多研究单片机,争取更上一层楼! 八、参考文献: 1.AVR C库函数介绍.pdf 2.AVR C语言开发入门指导.pdf 3.AVR单片机原理及应用.pdf 4.AVR高速嵌入式单片机原理及应用(修订版).pdf 5.深入浅出AVR单片机.pdf 6.1602液晶说明.pdf 7.AVR系统板说明.doc 8.cn_mega16-16L.pdf 9.EN_TC1602.pdf 第32页 北京邮电大学信息与通信工程学院 10.字符手册.pdf 11.百度百科《乐理》 12.AVR单片机软硬件设计视频教程-入门篇-第二讲-AVR硬件电路设计教程 13.AVR单片机软硬件设计视频教程-入门篇-第三讲-AVR开发基础知识 14.AVR单片机软硬件设计视频教程-入门篇-第四讲-C语言的流水灯验证 15.AVR单片机软硬件设计视频教程-入门篇-第五讲-按键与数码管的程序设计 16.AVR单片机软硬件设计视频教程-入门篇-第六讲-中断与定时器 九、意见与建议: 1.建议老师能稍微讲解一下实验室AVR软件的用法(视频中讲的是没有JTAG下载器的,与实验室不同,刚开始还以为是板子有问题); 2.实验室的元器件老旧,个别元器件特别是单片机芯片有问题之后,调BUG好久都不知道错在哪里,建议更新。 第33页 北京邮电大学 嵌入式系统开发实验报告 学院: 班级: 姓名: 学号: 序号: 目录 一、实验目的..............................................................................................1 二、实验设备..............................................................................................1 三、基础实验(实验一~实验七)............................................................1 1.实验五..................................................................................................1 2.实验六..................................................................................................1 3.实验七..................................................................................................1 四、驱动程序..............................................................................................5 1.设备驱动程序的概念..........................................................................5 2.驱动程序结构......................................................................................6 3.设备注册和初始化..............................................................................7 4.设备驱动程序的开发过程..................................................................8 五、基本接口实验......................................................................................8 1.实验十二简单设备驱动程序..............................................................9 2.实验十三 CPU GPIO驱动程序设计...................................................9 3.实验十四中断实验...........................................................................10 4.实验十五数码管显示实验................................................................12 5.实验十六 LED点阵驱动程序设计...................................................19 6.实验十七 AD驱动实验....................................................................23 7.实验十八 DA驱动实验....................................................................26 六、实验中遇到的问题及解决方法........................................................30 七、实验总结及心得................................................................................31 一、实验目的 通过实验熟悉Linux环境,并掌握一些基本接口驱动的写法和用C语言编写简单的实验程序。学习LINUX开发环境的搭建,通讯配置等。并熟练掌握LINUX驱动程序的编写及开发流程。对嵌入式系统有进一步的了解。 二、实验设备 1.一套PXA270EP嵌入式实验箱 2.安装Redhat9的宿主PC机,并且配置好ARM Linux的开发环境 三、基础实验(实验一~实验七) 实验一~七为基础实验,目的是为后续实验搭建好软、硬件环境,配置好相关的协议、服务,并通过编写最简单的HelloWorld程序进行测试。由于后面的实验都要依靠前面实验的配置,故本段只着重叙述实验七的具体实现。 1.实验五 实验五为宿主PC机配置了TFTP服务。TFTP(Trivial File Transfer Protocol)是简单文件传输协议,由于特定开发环境的制约,这一服务是必须的。在配置完成后,每次重启宿主PC机时,都须先输入命令:service xinetd restart,以启动TFTP服务。 2.实验六 实验六为宿主PC机配置了NFS服务。NFS(Network File System)指网络文件系统,它实现了文件在不同的系统间使用。当我们想用远端档案时,只需调用“mount”就可以远端系统挂接在自己的档案系统之下。每次重启宿主PC机时,都须先输入命令:service nfs restart,以启动nfs服务。 3.实验七 实验七通过用c语言编写的简单程序HelloWorld,测试前面几个实验是否成功配置好环境,从超级终端可以看到HelloWorld程序的运行结果。 实验步骤如下: 1)硬件连接: 连接宿主 PC 机和一台 PXA270-RP目标板。2)打开宿主PC 机电源,进入 Linux操作系统。 3)启动RedHat 9.0 的图形界面,如下图,若您是以 root 身份登陆在文本模式下,则输入命令startx启动图形界面。进入RedHat 9.0 图形界面后,打开一个终端窗(Terminal)。 4)输入minicom然后回车,minicim设置为115200 8NI无流控。 5)打开PXA270_RP目标板电源,按目标板上的BOOT键,在minicom中应该会看到如下图: 6)在minicom终端窗口中,如图,输入下列四条命令 root ifconfig eth 192.168.0.50 up mount-o nolock 192.168.0.100:/ /mnt cd /mnt 此时,先将该窗口最小化,在后面的第 10 操作步骤中还将会回到该窗口中进行操作。 7)宿主机上打开一个终端窗口(Terminal),点击【红帽/System Tools/Terminal】启动终端窗口,输入下列 4 条命令: ① cd /home ②mkdir HW ③ cd HW ④ vi HelloWorld.c /*请您输入程序 7.1 程序清单*/ 此时会显示一个空白的屏幕,这条命令的含义是,使用 Vi 编辑器,对一个名叫HelloWorld.c的文件进行编辑,我们看到的空白窗口是对文件进行编辑的窗口,如下图。就像在 Windows系统下面使用写字板等一样道理。 在 vi 里面先单击键盘 A 键,然后左下角会变成—INSER。输入程序的时候和其他编辑器是一样的,如下图。 当输入程序完毕后,单击键盘 Esc 键,然后按“:”(冒号)此时左下角会出现冒号然后输入“wq”最后按“Enter”确认存盘退出 vi 编辑器,如下图。 8)在上面同一个终端窗口中,输入下列 2 条命令交叉编译HelloWorld.c源程序,并查看生成的.o 目标文件,如图 7-10,图7-11: ① arm-linux-gcc–oHelloWorldHelloWorld.c ②ls 等到再次出现提示符,代表程序已经正确编译。如果此步出现错误信息,请查看错误信息,并且重新编辑原来的 C文件,修改错误。直到正确编译。 9)重新打开第 7 步最小化的开有minicom的终端窗口,即到 PXA270-RP 目标板的mnt目录下,请您输入下列 3 条命令,运行HelloWorld编译成功的HelloWorld目标程序: ① cd home/HW /*回到minicom中目标板的/mnt/home/HW目录下*/ ②ls ③./ HelloWorld /*此时会看到如下图*/ 四、驱动程序 1.设备驱动程序的概念 设备驱动程序实际是处理和操作硬件控制器的软件,从本质上讲,是内核中具有最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操作系统内核与硬件设备的直接接口,驱动程序屏蔽了硬件的细节,完成以下功能: 对设备初始化和释放; 对设备进行管理,包括实时参数设置,以及提供对设备的操作接口; 读取应用程序传送给设备文件的数据或者回送应用程序请求的数据; 检测和处理设备出现的错误。 Linux操作系统将所有的设备全部看成文件,并通过文件的操作界面进行操作。对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说,是把设备映射为一个特殊的设备文件,用户程序可以像对其他文件一样对此设备文件进行操作。这意味着: 由于每一个设备至少由文件系统的一个文件代表,因而都有一个“文件名”。应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。 打开了代表着目标设备的文件,即建立起与设备的连接后,可以通过read()、write()、ioctl()等常规的文件操作对目标设备进行操作。 设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一起惟一地确定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。 由于Linux 中将设备当做文件处理,所以对设备进行操作的调用格式与对文件的操作类似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用命令后,会从用户态转到核心态,通过内核将open()这样的系统调用转换成对物理设备的操作。 2.驱动程序结构 一个设备驱动程序模块的基本框架 在系统内部,I/O设备的存取通过一组固定的入口点来进行,入口点也可以理解为设备的句柄,就是对设备进行操作的基本函数。字符型设备驱动程序提供如下几个入口点: open入口点。打开设备准备I/O操作。对字符设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。 close入口点。关闭一个设备。当最后一次使用设备完成后,调用close子程序。独占设备必须标记设备方可再次使用。 read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。 write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。 ioctl入口点。执行读、写之外的操作。 select入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备文件相关的文件描述符时使用select入口点。 3.设备注册和初始化 设备的驱动程序在加载的时候首先需要调用入口函数init_module(),该函数最重要的一个工作就是向内核注册该设备,对于字符设备调用register_chrdev()完成注册。register_chrdev的定义为:intregister_chrdev(unsigned int major, const char *name, struct file_ operations *fops);其中,major是为设备驱动程序向系统申请的主设备号,如果为0,则系统为此驱动程序动态分配一个主设备号。name是设备名,fops是对各个调用的入口点说明。此函数返回0时表示成功;返回-EINVAL,表示申请的主设备号非法,主要原因是主设备号大于系统所允许的最大设备号;返回-EBUSY,表示所申请的主设备号正在被其他设备程序使用。如果动态分配主设备号成功,此函数将返回所分配的主设备号。如果register_chrdev()操作成功,设备名就会出现在/proc/dvices文件中。 Linux在/dev目录中为每个设备建立一个文件,用ls–l命令列出函数返回值,若小于0,则表示注册失败;返回0或者大于0的值表示注册成功。注册以后,Linux将设备名与主、次设备号联系起来。当有对此设备名的访问时,Linux通过请求访问的设备名得到主、次设备号,然后把此访问分发到对应的设备驱动,设备驱动再根据次设备号调用不同的函数。 当设备驱动模块从Linux内核中卸载,对应的主设备号必须被释放。字符设备在cleanup_ module()函数中调用unregister_chrdev()来完成设备的注销。unregister_chrdev()的定义为:intunregister_chrdev(unsigned int major, const char *name);包括设备注册在内,设备驱动的初始化函数主要完成的功能是有以下5项。(1)对驱动程序管理的硬件进行必要的初始化。 对硬件寄存器进行设置。比如,设置中断掩码,设置串口的工作方式、并口的数据方向等。 (2)初始化设备驱动相关的参数。 一般说来,每个设备都要定义一个设备变量,用以保存设备相关的参数。在这一步骤里对设备变量中的项进行初始化。 (3)在内核注册设备。 调用register_chrdev()函数来注册设备。(4)注册中断。 如果设备需要IRQ支持,则要使用request_irq()函数注册中断。(5)其他初始化工作。 初始化部分一般还负责给设备驱动程序申请包括内存、时钟、I/O端口等在内的系统资源,这些资源也可以在open子程序或者其他地方申请。这些资源不用时,应该释放,以利于资源的共享。 若驱动程序是内核的一部分,初始化函数则要按如下方式声明: int __initchr_driver_init(void);其中__init是必不可少的,在系统启动时会由内核调用chr_driver_init,完成驱动程序的初始化。 当驱动程序是以模块的形式编写时,则要按照如下方式声明: intinit_module(void)当运行后面介绍的insmod命令插入模块时,会调用init_module函数完成初始化工作。 4.设备驱动程序的开发过程 由于嵌入式设备由于硬件种类非常丰富,在默认的内核发布版中不一定包括所有驱动程序。所以进行嵌入式Linux系统的开发,很大的工作量是为各种设备编写驱动程序。除非系统不使用操作系统,程序直接操纵硬件。嵌入式Linux系统驱动程序开发与普通Linux开发没有区别。可以在硬件生产厂家或者Internet上寻找驱动程序,也可以根据相近的硬件驱动程序来改写,这样可以加快开发速度。实现一个嵌入式Linux设备驱动的大致流程如下。 (1)查看原理图,理解设备的工作原理。一般嵌入式处理器的生产商提供参考电路,也可以根据需要自行设计。 (2)定义设备号。设备由一个主设备号和一个次设备号来标识。主设备号惟一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,区分被一个设备驱动控制下的某个独立的设备。 (3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。(4)设计所要实现的文件操作,定义file_operations结构。(5)实现所需的文件操作调用,如read、write等。 (6)实现中断服务,并用request_irq向内核注册,中断并不是每个设备驱动所必需的。 (7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。 五、基本接口实验 在完成了基本实验后,我们开始着手基本接口实验。在这些实验中,我们学习如何编写设备驱动程序,及如何用测试程序检验驱动程序是否正确,并通过改写测试程序正常地对驱动程序进行相关操作。 1.实验十二 简单设备驱动程序 本次实验的任务是编写一个字符型设备驱动程序,并学习在应用程序中调用驱动。考虑到我们初次接触驱动程序的编写,对此还十分陌生,因此指导书中提供了本次实验所要用到的程序源代码。虽然这样一个字符型设备驱动程序并没有任何实际作用,但是它让我们轻松掌握了嵌入式驱动的编写过程,因为复杂繁琐的驱动,其骨架都是相同的。因此,看懂本实验的源代码,学习并模仿其编写方法,对于后续实验有着非常重要的意义。 2.实验十三 CPU GPIO驱动程序设计 在本实验中,我们要编写第一个针对实际硬件的驱动程序。我们知道,凡是操作系统控制外部设备,即使是最简单的硬件电路,也是需要驱动的。本实验涉及的外部硬件只有电阻和发光二极管。我们使用自己编写的驱动程序与应用程序控制 GPIO96的电平,通过 LED 的亮灭来判断,是否 CPU 做出了正确的响应。 补充代码(1) //-------------------WRITE-----------------------ssize_tSIMPLE_GPIO_LED_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_GPIO_LED_DEBUG printk(“SIMPLE_GPIO_LED_write [--kernel--]n”); #endif return count;} 补充代码(2) //-------------------OPEN------------------------ssize_tSIMPLE_GPIO_LED_open(structinode * inode ,struct file * file){ #ifdef OURS_GPIO_LED_DEBUG printk(“SIMPLE_GPIO_LED_open [--kernel--]n”); #endif MOD_INC_USE_COUNT; return 0;} 补充代码(3) //------------------structfile_operationsGPIO_LED_ctl_ops ={ open:SIMPLE_GPIO_LED_open, read:SIMPLE_GPIO_LED_read, write:SIMPLE_GPIO_LED_write, ioctl:SIMPLE_GPIO_LED_ioctl, release:SIMPLE_GPIO_LED_release, };实验作业 要求在目标板上LED闪烁产生亮7秒,灭2秒的效果 在测试程序中有这样一段代码: while(1){ ioctl(fd,LED_OFF);sleep(1); sleep(1);while(1){ ioctl(fd,LED_OFF);sleep(2); sleep(7);} 3.实验十四 中断实验 // 灭2秒 // 亮7秒 ioctl(fd,LED_ON);} // 休眠1秒 ioctl(fd,LED_ON);只需将上面的代码改为如下代码即可: 在理论课中,我们学习了许多中断方面的知识,包括中断向量、中断优先级、中断过程等。在PXA270系统里,中断控制器分外部设备和 PXA270X 处理器设备产生的两个层次的中断,前者是初级的中断源,后者是次级中断源,大量的次级中断源通常被映射为一个初级中断源。 补充代码1 voidshowversion(void){ printk(“*********************************************n”); printk(“t %s tn”, VERSION); printk(“*********************************************nn”); } static intSimpleINT_temp_count = 0;补充代码2 //-------------------READ------------------------ssize_tSIMPLE_INT_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_INT_DEBUG #endif return count;printk(“SIMPLE_INT_read [--kernel--]n”);} 补充代码3 //-------------------WRITE-----------------------ssize_tSIMPLE_INT_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_INT_DEBUG } 补充代码4 //------------------structfile_operationsINT_ctl_ops ={ open: SIMPLE_INT_open, read: SIMPLE_INT_read, #endif return count;printk(“SIMPL_INT_write [--kernel--]n”);write:SIMPLE_INT_write, ioctl:SIMPLE_INT_ioctl, release:SIMPLE_INT_release, }; 通过此实验,我了解了硬件中断管脚与中断号的对应关系,以及中断号与中断处理程序的对应关系,对于今后编写更为复杂的中断程序打下基础。 4.实验十五 数码管显示实验 在此实验中,我们要编写针对 74LV164 的驱动程序,并用其串并转换功能来控制八段LED数码管的显示。 补充代码1 voidshowversion(void){ printk(“*********************************************n”); printk(“t %s tn”, VERSION); printk(“*********************************************nn”); } 补充代码2 //-------------------READ------------------------ssize_tSERIAL_LED_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_HELLO_DEBUG } 补充代码3 //-------------------WRITE-----------------------ssize_tSERIAL_LED_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops)return count;printk(“SERIAL_LED_read [--kernel--]n”);#endif { #ifdef OURS_HELLO_DEBUG } 补充代码4 //-------------------IOCTL-----------------------ssize_tSERIAL_LED_ioctl(structinode * inode ,struct file * file, unsigned intcmd, long data){ #ifdef OURS_HELLO_DEBUG #endif } 补充代码5 //-------------------OPEN------------------------ssize_tSERIAL_LED_open(structinode * inode ,struct file * file){ #ifdef OURS_HELLO_DEBUG #endif return 0;} MOD_INC_USE_COUNT;printk(“SERIAL_LED_open [--kernel--]n”);return 0;printk(“SERIAL_LED_ioctl [--kernel--]n”);return count;#endif write_byte(* buf);printk(“SERIAL_LED_write [--kernel--]n”);补充代码6 //-------------------RELEASE/CLOSE---------------ssize_tSERIAL_LED_release(structinode *inode ,struct file * file){ #ifdef OURS_HELLO_DEBUG printk(“SERIAL_LED_release [--kernel--]n”); #endif MOD_DEC_USE_COUNT;return 0;} 补充代码7 //------------------structfile_operationsSERIAL_LED_ops ={ open: SERIAL_LED_open,read: SERIAL_LED_read,write:SERIAL_LED_write,ioctl:SERIAL_LED_ioctl,release:SERIAL_LED_release, };补充代码8 staticint __initHW_SERIAL_LED_init(void){ int ret =-ENODEV; ret = devfs_register_chrdev(SERIAL_LED_MAJOR, &SERIAL_LED_ops); showversion();if(ret < 0)“serial_led_ctl”,} { } else { } return ret;printk(“ pxa270 serial_led_driver register success!![--kernel--]n”);printk(“ pxa270 init_module failed with %dn [--kernel--]”, ret);return ret;补充代码9 staticint __init pxa270_SERIAL_LED_init(void){ int ret =-ENODEV; printk(“pxa270_SERIAL_LED_init [--kernel--]n”); #endif ret = HW_SERIAL_LED_init();if(ret)return ret;return 0;} 补充代码10 static void __exit cleanup_SERIAL_LED(void){ #ifdef OURS_HELLO_DEBUG #ifdef OURS_HELLO_DEBUG #endif } 补充代码11 MODULE_DESCRIPTION(“serial_led driver module”); MODULE_AUTHOR(“liduo”); MODULE_LICENSE(“GPL”); module_init(pxa270_SERIAL_LED_init);module_exit(cleanup_SERIAL_LED);使用测试程序看到的测试结果是数码管按0-9显示输出。实验作业要求在上述基础上,分别实现一下两个功能: ①要求您再编写一个测试程序,实现 PXA270-EP 目标板上的 LED 数码管循环显示的数字9-0。 ②要求您再编写一个测试程序,实现 PXA270-EP 目标板上的 LED 数码管循环显示的数字02468。 由于在测试程序中定义了数组buf[10]分别存储了0-9是个数,因此上述功能的实现方法是,分别对测试程序做如下修改: 原测试程序: while(1){ for(count=0;count<10;count++){ data[0] = buf[count];ret=write(fd,data,1);sleep(1);} } 实现功能①: while(1){ for(count=9;count>=0;count--)} } 结果显示 // 倒序显示数字 { data[0] = buf[count];ret=write(fd,data,1);sleep(1);devfs_unregister_chrdev(SERIAL_LED_MAJOR, “serial_led”);printk(“cleanup_SERIAL_LED [--kernel--]n”);实现功能②: while(1){ for(count=0;count<9;count=count+2)} } 结果显示 // 更改显数顺序 { data[0] = buf[count];ret=write(fd,data,1);sleep(1); 通过更改显数的顺序,很容易实现实验作业里要求的功能。 5.实验十六 LED点阵驱动程序设计 通过本实验的操作,我们将 8X8 的点阵 LED 驱动起来并通过编写测试程序,使其能够按照您的意图进行显示。要求您还编写更多的测试程序 补充代码1 voidshowversion(void){ printk(“*********************************************n”);printk(“t %s tn”, VERSION);printk(“*********************************************nn”); } 补充代码2 //-------------------READ------------------------ssize_tSIMPLE_LED_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_LED_DEBUG #endif return count;printk(“SIMPLE_LED_read [--kernel--]n”);} 补充代码3 //-------------------IOCTL-----------------------ssize_tSIMPLE_LED_ioctl(structinode * inode ,struct file * file, unsigned intcmd, long data){ #endif } 补充代码4 //------------------structfile_operationsLED_ctl_ops ={ open: SIMPLE_LED_open, read: SIMPLE_LED_read, write: SIMPLE_LED_write, ioctl: SIMPLE_LED_ioctl, release:SIMPLE_LED_release, };补充代码5 staticint __init pxa270_LED_CTL_init(void){ int ret =-ENODEV; printk(“pxa270_LED_CTL_init [--kernel--]n”); #endif ret = HW_LED_CTL_init();if(ret) return ret;#ifdef OURS_LED_DEBUG return 0;printk(“SIMPLE_LED_ioctl [--kernel--]n”);#ifdef OURS_LED_DEBUG return 0;} 补充代码6 static void __exit cleanup_LED_ctl(void){ #ifdef OURS_LED_DEBUG #endif } ①要求您再编写一个测试程序,实现按横的方向隔行顺序扫描 LED 点阵数码管。 ②要求您再编写一个测试程序,实现按竖的方向顺序扫描 LED 点阵数码管。作业一,隔行扫描: printk(“cleanup_LED_ctl [--kernel--]n”);outw(0x0000,ioremap_addr); devfs_unregister_chrdev(SIMPLE_LED_MAJOR, “led_ary_ctl”);for(i=1;i<=8;i2++){ buf[0]=c;buf[1]=~r;// row for(j=1;j<=8;j++){ } r = 1;c = c<<1; write(fd,buf,2); printf(“buf[0],buf[1]: [%x,%x]n”,buf[0],buf[1]);usleep(200000);// sleep 0.2 second r=r<<1; buf[1]=~r;// column 结果显示 作业二,竖向扫描: for(i=1;i<=8;i++){ buf[0]=c;buf[1]=~r;// row for(j=1;j<=8;j++){ } r = 1;c = c<<1; write(fd,buf,2); printf(“buf[0],buf[1]: [%x,%x]n”,buf[0],buf[1]);usleep(200000);// sleep 0.2 second r=r<<1; buf[1]=~r;// column 结果显示 6.实验十七 AD驱动实验 通过本实验的操作,我们将 AD 转换器驱动起来并通过编写测试程序,使其能够将模拟信号量按照我们的要求转换成数字信号量。为了更加清楚地理解 AD 转换器的工作过程,请您再编写一个测试程序,将 UCB_ADC_INP_AD0 换成其他通道,来观察其他 AD 通道情况。 补充代码1 voidshowversion(void){ printk(“%sn”,VERSION);} struct ucb1x00 *ad_ucb; 补充代码2 //-------------------READ------------------------staticssize_tadctl_read(struct file * file ,char *buf, size_t count, loff_t *offset){ } 补充代码3 //-------------------WRITE-----------------------ssize_tadctl_write(struct file * file ,const char *buf, size_t count, loff_t *offset){ #ifdef OURS_HELLO_DEBUG printk(“writen”); #endif } 补充代码4 //-------------------OPEN------------------------ssize_tadctl_open(structinode * inode ,struct file * file){ #ifdef OURS_HELLO_DEBUG printk(“openn”); #endif } 补充代码5 //-------------------RELEASE/CLOSE---------------ssize_tadctl_release(structinode *inode ,struct file * file){ #ifdef OURS_HELLO_DEBUG printk(“releasen”); #endif return 0;return 0;return count;#ifdef OURS_HELLO_DEBUG printk(“readn”);#endif return count;} 补充代码6 staticstructfile_operationsadctl_ops = { };补充代码7 //-------------------INIT------------------------staticint __initHW_AD_CTL_init(void){ return ret;} 补充代码8 staticint __init pxa270_AD_CTL_init(void){ int ret =-ENODEV;#ifdef OURS_HELLO_DEBUG int ret =-ENODEV;ret = devfs_register_chrdev(ADCTL_MAJOR, “adctl”, &adctl_ops);showversion();ad_ucb=ucb1x00_get();if(ret < 0){ } else { } adctl_dev_handle = devfs_register(NULL, “ad_ctl”, DEVFS_FL_DEFAULT, printk(“adctl driver register success!n”);printk(“fail %dn”,ret);return 0;read: ioctl: adctl_read, adctl_ioctl, write: adctl_write, open: adctl_open, release:adctl_release,ADCTL_MAJOR, 0, S_IFCHR, &adctl_ops, NULL);printk(“initn”);#endif ret=HW_AD_CTL_init();if(ret)} 补充代码9 static void __exit cleanup_AD_ctl(void){ } 7.实验十八 DA驱动实验 通过本实验的操作,我们使用示波器看到了通过DA转换而输出的波形。在此基础上,要求试写一个实现输出三角波的测试程序。 补充代码1 #include } printk(“t %st n”,VERSION);printk(“*****************************n”);static long ioremap_addr;补充代码3 //-------------------READ------------------------ssize_tSIMPLE_DA_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_DA_DEBUG } 补充代码4 //-------------------WRITE-----------------------ssize_tSIMPLE_DA_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops){ printk(“SIMPLE_DA_write[--kernel--]n”); #endif return count;} 补充代码5 //-------------------IOCTL-----------------------ssize_tSIMPLE_DA_ioctl(structinode * inode ,struct file * file, unsigned intcmd, outb(buf[0],ioremap_addr);#ifdef OURS_DA_DEBUG return count;#endif printk(“SIMPLE_DA_read[--kernel--]n”);long data){ #ifdef OURS_DA_DEBUG printk(“SIMPLE_DA_ioctl[--kernel--]n”); #endif return 0;} 补充代码6 //-------------------OPEN------------------------ssize_tSIMPLE_DA_open(structinode * inode ,struct file * file){ #ifdef OURS_DA_DEBUG printk(“SIMPLE_DA_open [--kernel--]n”); MOD_INC_USE_COUNT;return 0; #endif } 补充代码7 /------------------structfile_operationsDA_ctl_ops ={ read: SIMPLE_DA_read,}; 补充代码8 release: SIMPLE_DA_release, ioctl: SIMPLE_DA_ioctl, write: SIMPLE_DA_write, //-------------------INIT------------------------staticint __initHW_DA_CTL_init(void){ int ret =-ENODEV; } 补充代码9 staticint __init pxa270_DA_CTL_init(void){ int ret =-ENODEV; printk(“pxa270_DA_CTL_init [--kernel--]n”); #endif #ifdef OURS_DA_DEBUG } printk(“ pxa270 led_driver register success!![--kernel--]n”);{ else } return ret;printk(“ pxa270: init_module failed with %dn [--kernel--]”, ret);{ if(ret < 0)showversion();ret = devfs_register_chrdev(SIMPLE_DA_MAJOR, “da_ctl”, &DA_ctl_ops); ret = HW_DA_CTL_init();if(ret) return ret;return 0;} 补充代码10 static void __exit cleanup_DA_ctl(void){ #endif } 补充代码11 MODULE_DESCRIPTION(“DA_ctl driver module”);MODULE_AUTHOR(“liduo”);MODULE_LICENSE(“GPL”);module_init(pxa270_DA_CTL_init);module_exit(cleanup_DA_ctl);printk(“cleanup_DA_ctl [--kernel--]n”);#ifdef OURS_DA_DEBUG 六、实验中遇到的问题及解决方法 每一次上课重新启动后,当需要将宿主PC机的根目录挂在到PXA270-EP目标板的mnt目录下(即在超级终端中输入命令“mount –o soft,timeo=100,rsize=1024 192.168.0.100:/ /mnt”)时,常显示无法挂载。 解决方法:在超级终端下的挂载命令应该用”mount –o nolock 192.168.0.100:/ /mnt”,如果依然不能挂载需要重启NFS服务,即在PC机终端中输入命令”service nfs restart”两遍后就可以挂载,当然有时候也可能是因为网线没插好。 在每次重启机器之后都需要将PC机终端的IP地址和开发板中的系统的IP地址设定正确,不然也无法挂载。 七、实验总结及心得 本学期的所有实验均在宿主PC机与PXA270-EP目标板上进行。在实验中,我们先建立硬件实验平台,又建立主机软件开发环境,接着为实验进行各项配置,最后完成了各个实验中的多种功能。值得注意的是,前期的硬件、软件准备必须完整无误地实现,后续的实验才能顺利进行。所以,打基础的工作一定要仔细谨慎。后续实验中虽然给出了驱动程序的框架,仍需要我们自己补充完整,并开动脑筋举一反三,在原代码的基础上进行一定修改以实现新的功能。 通过这学期的实验,我逐步完成了建立实验软件开发平台,搭建实验编译软件环境,在PC上编辑、编译一个应用程序,并且在嵌入式系统上运行和调试它的过程。在实验中,不难发现,编译驱动程序大体框架都是一样的,比如里面的读函数、写函数、ioctl函数、打开、关闭以及函数模块的初始化并且在超级终端上显示出等。但所不同的是,要根据不同的实验要求修改名称,并且对其中必要的部分进行修改。 除此之外,我认为很多基础知识对实验的进行也起着非常大的作用,例如数码管的显示原理。在掌握了基础知识之后,上机的过程会显得相对简单,尤其是代码框架已经给出,我们所以需要做的就是根据需要稍作改动来得到我们想要的结果。 在实验过程中常常会遇到各种各样的问题,刚开始时我不知如何是好,只能求助于老师和同学,后来随着实验的进行,我对实验的内容和虚拟机都有了一定的了解,遇到问题时也可以静下心来思考其原因,自己尝试各种方法去解决问题。整个实验让我了解了一套完整的嵌入式系统驱动程序开发的全过程,学到的内容非常丰富,相信在学习了这些内容后,在今后的学习工作中接触到类似内容,我不会感到无从下手,而是能够有条不紊。 感谢老师的辛勤指导! 数据库实验报告 (三)姓名:学号:班级: 1.用Transact-SQL语句、数据导入、SQL Server Management Studio(企业管理器)输入的方法对所给定的8张表的数据输入到数据库中。自行决定每张表的数据导入办法,但每种方法各针对二或三张表。 Transact-SQL语句: 导入department,student, student_course表。 insertinto department select*from openrowset ('microsoft.jep.oledb.4.0','excel 5.0;hdr=yes;database=D:课件数据库database2.xls',department$); insertinto student select*from openrowset ('microsoft.jep.oledb.4.0','excel 5.0;hdr=yes;database=D:课件数据库database2.xls',student$); insertinto student_course select*from openrowset ('microsoft.jep.oledb.4.0','excel 5.0;hdr=yes;database=D:课件数据库database2.xls',student_course$); 数据导入: 操作:选中数据库studentsdb,右键-任务-导入数据。导入book, class, course表。 SQL Server Management Studio: 操作:右键需要编辑的表,选择编辑前200行。 Teacher: Teacher_course_class: 导入结果: Book: Class: Course: Department: Student: Student_course: Teacher: Teacher_course_class: 2.用Transact-SQL向Course表中插入一条记录,course_name为空,看运行的结果。 SQL语句: INSERTINTO course VALUES('dep02_s002', null,'dep02_s002', '72', '5', '4');运行结果: 分析:course_name有not null的约束,因此这条语句不能执行。 3.用Transact-SQL修改Course表中credit为5的记录,将其credit改为7, credit小于4的改为2,看运行的结果。 SQL语句: update course set credit=7 where credit=5;执行结果: 分析:约束C1指定了credit的范围为1至6.SQL语句: update course set credit=2 where credit<4;执行结果: 4.删除一条学生记录,看运行结果,对运行结果进行分析。SQL语句: deletefrom student where student_id='g9940201';执行结果: 分析:因为有参照完整性约束,不能删除。 5.用Transact-SQL完成将编号为dep04_b001的课程的选修信息插入到一个新的选课信息表中。 SQL语句: Creattablestudent_course2(course_idchar(20), student_idchar(20)gradeint, creditint, semesterint,school_yearchar(20),primarykey(course_id,student_id)); insertintostudent_course2 select*fromstudent_course wherecourse_id='dep04_b001';执行结果: 6.用Transact-SQL完成删除单片机原理课程的选课信息,分析运行结果。 SQL语句: deletefrom student_course where course_id in(select course_id from course where course_name='单片机原理')执行结果: 分析:所有课程号为dep04_s003的课程被删除。 本实验中遇到的问题和解决方法: 本实验的顺利完成需要预先作很多准备工作。以下就是我在遇到缺少组件accessdatabaseengine时的解决过程的记录。 AccessDatabaseEngine的安装 accessdatabaseengine用于和office连接,导入导出数据,本实验中需要导入excel文件。安装配合office的版本,我安装的是accessdatabaseengine2017(English)版本。安装32位版本,因office2016是32位。之前误操作安装了不能使用的老旧版本accessdatabaseengine2007,通过控制面板-应用程序卸载将其卸载了。安装accessdatabaseengine依然报错,是因为microsoftofficeclicktorun阻碍sqlserver的一些功能,需要将其卸载。这是微软推出的用于减少office打开速度的应用程序,安装office2016时会自动安装上,原理是开机时将一部分内容放到内存中,因此打开文件时会更快一些。检测自己的office是通过clickto run 还是MSI安装的,可以在word中点击文件-账户,查看产品信息,如果有下图中“office更新”这个选项,则说明安装过click to run。这个程序在控制面板-应用程序中找不到,因此用删除注册表的方式卸载。快捷键“win+R”输入“regedit”打开注册表编辑器,左边HKEY_CLASSES_ROOT-Installer-Product-00006开头的选项,有四个。单击这几个选项,在右侧查看详细信息,可以看到ProductNam是Microsoft Access database engine 2007(我原来误安装的老版本)。删除之前先备份注册表。方法一:选中要删除的文件,右键-导出,保存。只保存了要删除的文件。方法二:注册表编辑器,文件-导出,保存。保存了注册表所有信息。这是因为如果误删了重要文件会导致严重后果,可能需要重装系统,留此备份是为了可以恢复系统。 备份完之后,选中要删除的文件(00006开头的四个),右键-删除即可。回到Access database engine 32位的程序安装包,安装。我无法安装64位,可能是因为office是32位。安装成功之后就可以在sqlserver中导入excel文件了。第四篇:北邮嵌入式实验报告
第五篇:北邮数据库实验报告