电工电子实验报告

设计题目: 可编程音乐自动演奏电路
课程名称: 电子电路课程设计

学 院: 通信与信息工程学院
班 级: B200124
学 号: B20012418
姓 名: 张宏宇
指导教师:
开课时间: 2022 年第 09 月13日
至2022年09月23日
目录
摘要 6
1.课程目的 6
2.实现方法 6
3.实现情况 6
关键词 6
一、课题技术指标 8
1.乐曲要求: 8
2.演奏要求 8
3.电气指标 8
4.选作指标 9
二、系统设计 9
1.模块化设计 9
4.顶层模块设计 10
5.程序框图 11
三、源程序设计 11
1.按键消抖模块设计 11
(1)为什么要按键消抖 11
(2)按键消抖的方法 12
(3)按键消抖代码 12
2.状态机模块 13
(1)状态机是什么 13
(2)为什么要用状态机 14
(3)状态机的设计方法 14
(4)状态机的代码设计 14
3.点灯模块 16
(1)发光二极管的工作原理 16
(2)点灯模块代码设计 16
4.单个音符时间存储ROM模块 17
(1)时间ROM IP核中需要存储什么数据 17
5.频率计数值存储模块 18
(1)频率输出是怎么实现的 18
(2)存储频率计数值的模块代码设计 18
6.单个音符频率存储ROM模块 20
(1)频率ROM IP核中需要存储什么数据 20
7.时间计数和频率计数模块 20
(1)时间计数模块代码实现 20
(2)频率计数模块代码实现 21
8.PWM方波产生模块 22
(1)PWM原理 22
(2)PWM产生模块代码实现 22
9.音乐选择模块 23
(1)读ROM地址选择 23
(2)读ROM地址代码实现 23
四、单元电路、整体电路功能测试 25
1.按键消抖仿真测试 25
(1)按键消抖仿真测试模块代码 25
(2)仿真波形图 27
2.状态机仿真测试 27
(1)状态机仿真测试模块代码 27
(2)仿真波形图 29
3.点灯模块 29
(1)LED灯仿真测试模块代码 29
(2)仿真波形图 30
4.音乐播放模块 31
(1)音乐播放仿真测试代码 31
(2)仿真波形图 32
五、喇叭功放电路及振荡电路 33
1.喇叭功放电路的搭建 33
(1)LM386芯片引脚配置和功能图 33
(2)LM386应用电路 33
(3)注意事项 34
2.振荡电路 35
(1)SN74HC132芯片引脚配置及功能图 35
(2)振荡电路电路图 36
(3)振荡电路波形 36
六、输出正弦波形(附加指标) 37
1.为什么要输出正弦波 37
2.正弦波的妙用 37
3.产生正弦波的代码实现 37
(1)使用MATLAB产生波表 37
(2)FPGA对波表操作 39
(3)DDS模块 40
(4)PWM产生模块 41
(5)输出正弦波仿真 42
七、问题与解决 42
八、元件清单 43
九、参考文献 43
十、实验小结及心得体会 43
十一、附录 45
1.最终电路图 45
2.课程设计日志 46

摘要
1.课程目的
① 巩固和深化前期电子电路所学知识。
② 掌握综合性和系统性电子电路设计的原则和方法。
③ 进一步掌握电子电路的装配、调测技术。
④ 培养科研、工程应用能力,自学、查找资料能力。
⑤ 进一步提高科技论文的撰写和文档整理能力。
⑥ 培养学生的创新意识和创新能力。
2.实现方法
使用ISE软件通过Verilog HDL语言编辑程序,使用Xilinx公司的Spartan - 3AN系列的XC30S50AN – 4TQG144I硬件下载程序,同时在面包板上搭建外围电路实现。
3.实现情况
使用按键控制选择预先设置在电路中的乐曲,选中某一乐曲后对应的发光二极管亮,音乐演奏电路反复自动演奏所选的乐曲,经功率放大后由喇叭播出,直至选中下一首位置,系统时钟通过使用有源晶振震荡而出。
关键词
Verilog、现场可编辑逻辑门阵列FPGA、音乐播放、音阶频率、芯片使用。

Summary
• 1. Purpose of the course
① Consolidate and deepen the knowledge of early electronic circuit.
② Master the principles and methods of comprehensive and systematic electronic circuit design.
(3) further grasp the assembly and commissioning technology of electronic circuits.
④ Cultivate the ability of scientific research and engineering application, self-study and data searching.
⑤ Further improve the ability of writing scientific and technological papers and document sorting.
⑥ Cultivate students’ innovative consciousness and ability.

• 2. Implementation method
Using ISE software through Verilog HDL language editing program, using Xilinx Spartan-3AN XC30S50AN-4TQG144I hardware download program, and at the same time build peripheral circuit on bread board.
• 3. Implementation
Use key control to select the music preset in the circuit, select a certain music after the corresponding light-emitting diode bright, music playing circuit repeatedly automatic play the selected music, after power amplification by the horn broadcast, until the next position selected, the system clock through the use of active crystal vibration and out.
• 4. Keyword
Verilog, field editable logic gate array FPGA, music playback, scale frequency, chip use.

一、 课题技术指标

  1. 乐曲要求:
    ① 乐曲数目3首;
    ② 每首乐曲长度在20s~30s之间;
    ③ 所选择的乐曲应在4个8度内,以第六个8度作为最高的8度;
    ④乐曲演奏速度为100拍/min~120拍/min。
  2. 演奏要求
    ①用一个自复键Key选择所需的乐曲,用3个LED表示选中对应乐曲,当3个LED均不亮时,表示没有选中,电路没有乐曲输出。
    ②一旦选中某一首乐曲,电路将自动循环放送所选的乐曲。
  3. 电气指标
    ① 音频功放输入为方波;
    ② 音节频率误差E<=5生;
    ③ 负载(喇叭)阻抗为8Ω,功率为1/8W(也可以用蜂鸣器);
    ④ 输出音量可调。
    4.选作指标
    对于产生声音的波形来说,方波听起来是刺耳的,如果向喇叭中输入正弦波(具有基波和谐波),声音会悦耳很多,并且可以通过改变谐波模仿绝大多数乐器的声音。

二、系统设计
1.模块化设计
(1)按键消抖模块
为了消除按键的机械抖动,防止按键误判,设置当只有低电平超过20ms时检测到按键按下。
(2)状态机模块
一个按键控制四种状态,需要使用状态机,按键按下即进行一次状态机跳转,分别是不放音乐NONE(不亮灯),放第一首音乐LED0(亮第一个灯),放第二首音乐LED1(亮第二个灯),放第三首音乐LED2(亮第三个灯)。
(3)点灯模块
通过读取状态机的现态,控制三个灯的亮灭。
(4)单个音符时间存储ROM模块
设置一拍时间为 1 秒,那么四分音符也就是 1 秒,三十二分音符也就是 1/8 秒,可以看到最短的时值为三十二分音符,按 1/8 秒作为最小时间单位,时间 ROM 里读出的时长数据,就是有多少个时间单位,比如第一个简谱名中音 3,为四分音符,也就是 8 个时间单位,ROM中存储的值为 8,也就是 1 秒。
(5)单个音符频率存储ROM模块
每个简谱名对应不同的频率,可以用系统时钟生成对应的频率,对每个简谱名进行编码,hz_sel 为频率选择信号,cycle 为每个简谱名对应的计数值。
(6)频率计数值存储模块
将二十八个音符的频率计数值写在频率存储模块中,供hz_sel选择信号选择调用。
(7)时间计数和频率计数模块
通过两个ROM中读取的数据,经过计算后送入对应计数器中计数,就可以实现产生对应的频率和音符时值。
(8)PWM方波产生模块
设置占空比,在一个音符的时钟周期中占空比对应数值之前的时间置1,之后的时间置0,就可以产生对应的PWM波。
(9)音乐选择
分别在两个ROM存储时间和频率对应值,三首歌存在同一个ROM中,在状态机跳转后读取不同的ROM的地址位,即可实现不同音乐的播放。
4. 顶层模块设计
顶层模块包含了按键消抖、状态机跳转、led灯控制、音乐播放的底层模块。
通过按键消抖模块读取按键输入,按键输入信号进行状态机跳转,在不同状态下控制led灯的亮灭情况和音乐选曲播放的情况。

  1. 程序框图

图2-1 系统程序框图设计

三、源程序设计
1.按键消抖模块设计
(1)为什么要按键消抖
按键开关位机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合式不会马上稳定的接通,在断开时也不会立即断开,因此在按键闭合和断开的瞬间伴随有一连串的抖动,为了不产生这种现象就需要进行按键消抖。抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。按键稳定闭合时间的长短则是由按键动作决定的,一般为零点几秒至数秒。
按键抖动会导致按键被误读多次,并且即便没有抖动,在按键按下后会读取很多次时钟上升沿,这就会导致按键被检测到多次按下,不符合我们想达成的目的,因此需要进行按键消抖。
(2)按键消抖的方法
按键消抖有硬件和软件的两种方式。
硬件消抖方法:可以用两个与非门构成一个RS触发器进行硬件消抖。
软件消抖方法:软件消抖的原则就是检测出按键闭合后执行一个延时程序,当检测到低电平的时候就开始计时,计数为10ms以内并检测到了高电平信号,就代表着是一次按键抖动,当低电平持续时间达到20ms及以上的时候,将按键按下标志信号拉高一个时钟周期,代表着按键按下了一次。
(3)按键消抖代码

1.   //定义常量为计数器最大值
2.  parameter CNT_MAX=1_999;
3.
4.  //按键按下标志信号,作为按键消抖后的有效判断信号
5.  reg key_flag;
6.  //按键按下后的延时电路的计数器,用来计数是否到达20ms,计数最大值为1999,位宽为11位。
7.  reg [10:0]cnt_20;
8.
9.  /*按键消抖计数器*/
10. always @(posedge clk or negedge rst_n)
11.     if(!rst_n)
12.         cnt_20<=1'b0;
13.     else if(key_in==1'b1)
14.         cnt_20<=1'b0;
15.     else if(cnt_20==CNT_MAX &&  key_in==1'b0)
16.         cnt_20<=cnt_20;
17.     else
18.         cnt_20<=cnt_20+1'b1;
19.
20. /*按键有效标志信号key_flag*/
21. always @(posedge clk or negedge rst_n)
22.     if(!rst_n)
23.         key_flag<=1'b0;
24.     else if(cnt_20==CNT_MAX-1'b1)
25.         key_flag<=1'b1;
26.     else
27.         key_flag<=1'b0;

CNT_MAX:系统时钟输入100KHz,一个时钟周期是0.00001s计数器计数计数20ms的计数值CNT_MAX为1_999次。
cnt_20:计数器初始值为0,按键端口没有按下时的输入是拉高状态,因此在kye_in等于高电平的时候,计数器始终为0,当key_in等于低电平的时候,计数器开始工作,每一个时钟上升沿计数器加一,在记到最大值CNT_MAX并且此时key_in为低电平时,计数器始终保持最高值,直到按键结束。
key_flag:初始值为0,在计数器达到最大值的前一个状态时,key_flag拉高,并且在下一个时钟上升沿到来的时候拉低,这样就保证了一次按键按下只会被检测到一次。

2.状态机模块
(1)状态机是什么
状态机是一个具有有限个状态以及状态转移的数学模型,通过状态机,可以实现不同判定条件下的准确的状态判定,从而达到控制及选择的目的。状态机的编码方式有多种,这里采用最简单的二进制码来控制四个状态的判定,对于更多状态和更高精度要求的状态机,要通过格雷码、独热码等编码方式实现。状态机有一段式、二段式、三段式之分,这里使用二段式状态机。
(2)为什么要用状态机
题目要求使用一个按键来控制四个状态的切换,用一个信号的高低状态来控制四个状态显然是不可能的,这个时候就需要使用到状态机来进行状态跳转。
(3)状态机的设计方法
可以通过绘制状态状态转移图的方式来设计状态机的状态跳转情况,下面给出本实验用到的状态转移图

图3-1 状态机原理图
(4)状态机的代码设计

1.   /*将状态机的四个状态定义为常量*/
2.  parameter       NONE = 2'b00,
3.                  LED0 = 2'b01,
4.                  LED1 = 2'b10,
5.                  LED2 = 2'b11;
6.
7.
8.  /*定义一个二位的次态和一个二位的现态*/
9.  reg [1:0]       nstate;
10. reg [1:0]       cstate;
11.
12. /*使用时序逻辑在第一个always块中将次态赋给现态*/
13. always @(posedge clk or negedge rst_n)
14.         if(!rst_n)
15.         cstate <= NONE;
16.         else
17.         cstate <= nstate;
18.
19. /*使用组合逻辑在第二个always块中进行次态的跳转*/
20. always @(*)
21.         case(cstate)
22.             NONE :
23.                 if(key_flag)
24.                     nstate <= LED0;
25.                 else
26.                     nstate <= NONE;
27.             LED0 :
28.                 if(key_flag)
29.                     nstate <= LED1;
30.                 else
31.                     nstate <= LED0;
32.             LED1 :
33.                 if(key_flag)
34.                     nstate <= LED2;
35.                 else
36.                     nstate <= LED1;
37.             LED2 :
38.                 if(key_flag)
39.                     nstate <= NONE;
40.                 else
41.                     nstate <= LED2;
42.             default :
43.                 nstate <= NONE;
44.         endcase

状态跳转实现:次态,通过组合逻辑实现,当现态在NONE状态时,如果检测到一个按键信号,次态跳转到LED0,如果没有检测到,次态依然保持NONE,当下一个时钟上升沿到来时,在时序逻辑里现态cstate等于nstate,实现了状态跳转,同理,接下来的几个状态也是如此跳转。当现态为LED2时,检测到按键信号后,次态会立即跳转到NONE,如此进行了一次状态循环。

3.点灯模块
(1)发光二极管的工作原理
如图所示,向发光二极管的阳极输入低电平时不亮,输入高电平时点亮了LED灯

图3-2 发光二极管原理图
(2)点灯模块代码设计

1.   module led_ctrl(
2.      /*输入系统时钟,频率为100KHz*/
3.      input wire clk,
4.      /*复位信号,低电平有效*/
5.      input wire rst_n,
6.      /*在顶层模块向led_ctrl输入的状态机状态*/
7.      input wire [1:0]nstate,
8.      /*三个LED灯输出*/
9.      output reg [2:0]led
10. );
11.     /*将输入的状态机状态定义为常量,命名方式和顶层保持一致,便    于操作*/
12.     parameter       NONE = 2'b00,
13.                     LED0 = 2'b01,
14.                     LED1 = 2'b10,
15.                     LED2 = 2'b11;
16.
17.     /*LED灯控制*/
18.     always @(posedge clk or negedge rst_n)
19.     if(!rst_n)
20.         led <= 1'b0;
21.     else if(nstate == NONE)
22.         led <= 3'b000;
23.     else if(nstate == LED0)
24.         led <= 3'b001;
25.     else if(nstate == LED1)
26.         led <= 3'b010;
27.     else if(nstate == LED2)
28.         led <= 3'b100;
29.     else
30.         led <= led;
31.
32. endmodule

LED灯控制:当输入状态为NONE时,led输出为3’b000,全为低电平,led不亮,当输入状态为LED0时,led输出为3’b001,第一位led拉高,其余为低电平,对应的第一位的led点亮,其余熄灭;当输入状态为LED1时,led输出为3’b010,第二位led点亮,其余熄灭;当输入状态为LED2时,led输出为3’b100,对应的第三位led点亮,其余熄灭。

4.单个音符时间存储ROM模块
(1)时间ROM IP核中需要存储什么数据
配置位宽为8位,深度为512的ROM,进行乐谱单个音符时间的存储。
设置一拍时间为 1 秒,那么四分音符也就是 1 秒,三十二分音符也就是 1/8 秒,可以看到最短的时值为三十二分音符,按 1/8 秒作为最小时间单位,时间 ROM 里读出的时长数据,就是有多少个时间单位,

图3-3 乐谱
以这段简谱为例,第一行的第十五个音符高音re,是一整拍,在COE文件中存储的第一个数便是十进制的08;第一行的第三个音符高音do,在下面有两条横杠,是四分之一拍,在COE文件中存储的数便是十进制的02;第一行的第六个音符高音fa,是二分之一拍,在COE文件中存储的数据便是十进制的04;而第一行的第一个音符高音do,是两拍,持续两拍的时间,在COE文件中存储的数据便是十进制的16。
5.频率计数值存储模块
(1)频率输出是怎么实现的
系统输入时钟为100KHz,记为fclk,以中音do为例,频率为523Hz,记为fout,时钟对应的周期为Tclk=1/fclk,中音do对应的周期为Tout=1/fout,频率的输出通过计数器来实现,在系统时钟中,计数器计满Tout/Tclk,即fclk/fout,便得到了一个音符频率的计数器最大值。
(2)存储频率计数值的模块代码设计

1.   module hz(
2.
3.      /*频率选择输入信号*/
4.      input wire [7:0]hz_sel,
5.
6.      /*输出频率计数值*/
7.      output reg [19:0]cycle
8.  );
9.      /*定义系统时钟为常数*/
10.     parameter CLK_FRE = 1 ;
11.
12.     /*使用case语句进行频率计数值选择*/
13. always @(*)begin
14. case(hz_sel)
15.     8'h01 : cycle <= CLK_FRE*100000/261 ; //low 1 261Hz
16.     8'h02 : cycle <= CLK_FRE*100000/293 ; //low 2 293Hz
17.     8'h03 : cycle <= CLK_FRE*100000/329 ; //low 3 329Hz
18.     8'h04 : cycle <= CLK_FRE*100000/349 ; //low 4 349Hz
19.     8'h05 : cycle <= CLK_FRE*100000/392 ; //low 5 392Hz
20.     8'h06 : cycle <= CLK_FRE*100000/440 ; //low 6 440Hz
21.     8'h07 : cycle <= CLK_FRE*100000/499 ; //low 7 499Hz
22.     8'h11 : cycle <= CLK_FRE*100000/523 ; //middle 1 523Hz
23.     8'h12 : cycle <= CLK_FRE*100000/587 ; //middle 2 587Hz
24.     8'h13 : cycle <= CLK_FRE*100000/659 ; //middle 3 659Hz
25.     8'h14 : cycle <= CLK_FRE*100000/698 ; //middle 4 698Hz
26.     8'h15 : cycle <= CLK_FRE*100000/784 ; //middle 5 784Hz
27.     8'h16 : cycle <= CLK_FRE*100000/880 ; //middle 6 880Hz
28.     8'h17 : cycle <= CLK_FRE*100000/998 ; //middle 7 998Hz
29.     8'h21 : cycle <= CLK_FRE*100000/1046 ; //high 1 1046Hz
30.     8'h22 : cycle <= CLK_FRE*100000/1174 ; //high 2 1174Hz
31.     8'h23 : cycle <= CLK_FRE*100000/1318 ; //high 3 1318Hz
32.     8'h24 : cycle <= CLK_FRE*100000/1396 ; //high 4 1396Hz
33.     8'h25 : cycle <= CLK_FRE*100000/1568 ; //high 5 1568Hz
34.     8'h26 : cycle <= CLK_FRE*100000/1760 ; //high 6 1760Hz
35.     8'h27 : cycle <= CLK_FRE*100000/1976 ; //high 7 1976Hz
36.     8'h31 : cycle <= CLK_FRE*100000/2093 ; //super high 1 2093Hz
37.     8'h32 : cycle <= CLK_FRE*100000/2349 ; //super high 2 2349Hz
38.     8'h33 : cycle <= CLK_FRE*100000/2637 ; //super high 3 2637Hz
39.     8'h34 : cycle <= CLK_FRE*100000/2794 ; //super high 4 2794Hz
40.     8'h35 : cycle <= CLK_FRE*100000/3136 ; //super high 5 3136Hz
41.     8'h36 : cycle <= CLK_FRE*100000/3520 ; //super high 6 3520Hz
42.     8'h37 : cycle <= CLK_FRE*100000/3951 ; //super high 7 3951Hz
43.     default:cycle<=20'd0;
44. endcase
45. end
46.
47. endmodule

可以看到,当输入hz_sel信号后,就可以对各种频率的计数值进行选择,hz_sel信号的数值,便是在单个音符频率存储的ROM中存储的数据。

6.单个音符频率存储ROM模块
(1)频率ROM IP核中需要存储什么数据
在上一个模块中,实现了对每个音符频率对应计数值的编写,在频率ROM模块中,就可以写入每个音符频率对应的选择值

图3-4 乐谱
依然以这段简谱为例,乐谱中高音do的音符对应存储在COE文件中的数据为十六进制的21,ROM会输出频率为1046的计数值;高音fa的音符对应存储在COE文件中的数据为十六进制的24,ROM会输出频率为1396的计数值,以此类推。
7.时间计数和频率计数模块
(1)时间计数模块代码实现

1.   /*时间计数器*/
2.  reg [31:0]cnt;
3.
4.  /*单个音符时间值*/
5.  reg [31:0]time_cycle;
6.
7.  /*单个音符时间计数值*/
8.  always @(posedge clk or negedge rst_n)
9.  if(!rst_n)
10.     time_cycle<=32'd0;
11. else
12.     time_cycle<=time_music*(CLK_FRE*100000/8) ;
13.
14. /*单个音符时间计数器计数,最大值为time_cycle*/
15. always @(posedge clk or negedge rst_n)
16. if(!rst_n)
17.     cnt<=1'b0;
18. else if(cnt==time_cycle)
19.     cnt<=1'b0;
20. else
21.     cnt<=cnt+1'b1;

time_cycle:定义32位的寄存器来存放单个音符时间的计数值,计算公式为time_cycle<=time_music*(CLK_FRE*100000/8),其中CLK_FRE为定义的常数1,其值根据输入系统的不同的时钟而改变。
cnt:单个音符时间计数器,在计满一个time_cycle的时间时,就代表着一个音符的时间读取完毕,再进入下一个音符时间。

(2)频率计数模块代码实现

1.       /*在hz模块输出的cycle值*/
2.  wire [19:0]cycle;
3.
4.  /*频率计数器*/
5.  reg [19:0]cnt_cycle;
6.
7.  /*定义占空比*/
8.  wire [19:0]duty_data;
9.
10. //频率计数器
11. always @(posedge clk or negedge rst_n)
12. if(!rst_n)
13.     cnt_cycle<=1'b0;
14. else if(cnt_cycle==cycle || cnt==time_cycle)
15.     cnt_cycle<=1'b0;
16. else
17.     cnt_cycle<=cnt_cycle+1'b1;
18.
19. //占空比
20. assign duty_data=cycle/8;

cycle:单个音符的频率计数值,由hz模块中输入得来。
cnt_cycle:频率计数器,用来计算输出PWM波的频率,在一个音符的时间内,循环加计数器,计满为cnt_cycle与cycle相等时归零,重新开始计数,直到这个音符的时间结束,也就是cnt等于time_cycle时,接下来进入下一个音符的频率计数。
duty_data:PWM占空比,占空比控制着声音的强度,为了声音柔和不刺耳,将占空比调整为了20%,一般情况下的占空比可调整为50%。占空比的计算公式为duty_data=cycle/8。

8.PWM方波产生模块
(1)PWM原理
PWM方波的频率可以控制音色,占空比可以控制声音强度,在一个周期里,占空比数值前的时间输出高电平,占空比数值后的时间输出低电平。
(2)PWM产生模块代码实现

1.   //蜂鸣器输出PWM
2.  always @(posedge clk or negedge rst_n)
3.  if(!rst_n)
4.      beep=1'b0;
5.  else if(cnt_cycle>=duty_data)
6.      beep=1'b1;
7.  else
8.      beep=1'b0;

beep:喇叭输出,初始值为低电平,在低电平时刻喇叭不响,在高电平时刻喇叭响,因此在占空比之数值之前输出高电平,在占空比数值之后输出低电平,就实现了使用PWM波输出对应的频率的波形到喇叭中,可以播放对应的音符。

9.音乐选择模块
(1)读ROM地址选择
音乐的播放需要用到两个ROM,将这两个ROM的地址同时控制,可以实现时间和频率的一致输出。
三首歌是同时存储在一个ROM中,每首歌曲的地址不同,因此只需要在不同的状态时选择不同的初始地址循环播放就可以实现三首歌的选择。
(2)读ROM地址代码实现

1.   /*输出的真正地址,在不同的状态选择不同的值*/
2.  reg [9:0]address;
3.
4.  /*歌曲一的地址选择*/
5.  reg [9:0]address_1;
6.
7.  /*歌曲二的地址选择*/
8.  reg [9:0]address_2;
9.
10. /*歌曲三的地址选择*/
11. reg [9:0]address_3;
12.
13. /*歌曲一的地址,从0-111*/
14. always @(posedge clk or negedge rst_n)
15. if(!rst_n)
16.     address_1 <= 1'b0;
17. else if(address_1 == 10'd111 && cnt == time_cycle && nstate == LED0)
18.     address_1 <= 1'b0;
19. else if(cnt == time_cycle && nstate == LED0)
20.     address_1 <= address_1 + 1'b1;
21.
22. /*歌曲二的地址,从112-177*/
23. always @(posedge clk or negedge rst_n)
24. if(!rst_n)
25.     address_2 <= 10'd112;
26. else if(address_2 == 10'd177 && cnt == time_cycle && nstate == LED1)
27.     address_2 <= 10'd112;
28. else if(cnt == time_cycle && nstate == LED1)
29.     address_2 <= address_2 + 1'b1;
30.
31. /*歌曲三的地址,从178-323*/
32. always @(posedge clk or negedge rst_n)
33. if(!rst_n)
34.     address_3 <= 10'd178;
35. else if(address_1 == 10'd323 && cnt == time_cycle && nstate == LED2)
36.     address_3 <= 10'd178;
37. else if(cnt == time_cycle && nstate == LED2)
38.     address_3 <= address_3 + 1'b1;
39.
40. /*真正输出的地址,通过状态选择不同的歌曲地址*/
41. always @(posedge clk or negedge rst_n)
42. if(!rst_n)
43.     address <= 1'b0;
44. else if(nstate == NONE)
45.     address <= 1'b0;
46. else if(nstate == LED0)
47.     address <= address_1;
48. else if(nstate == LED1)
49.     address <= address_2;
50. else if(nstate == LED2)
51.     address <= address_3;

三首歌曲的首地址不同,结束的地址也不同,因此将address_1的首地址初始值设为0,尾地址设为111,address_2的首地址初始值设为112,尾地址设为177,address_3的首地址初始值设为178,尾地址设为323。
当状态为NONE时,不播放音乐,address为0,当状态为LED0时,播放第一段音乐,address等于address_1,播放第一段音乐;当状态为LED1时,播放第二段音乐,address等于address_2,播放第二段音乐;当状态为LED2时,播放第三段音乐,address等于address_3,播放第三段音乐。

四、单元电路、整体电路功能测试
1.按键消抖仿真测试
(1)按键消抖仿真测试模块代码

1.   /*设置时间步进为1ns,精度为1ns*/
2.  `timescale 1ns/1ns
3.  /*定义时钟周期为10000ns,时钟频率就为1KHz*/
4.  `define clk_period 10000
5.
6.  module  key_filter_tb;
7.
8.      /*定义仿真的不同时间*/
9.      parameter   CNT_1MS  = 20'd19   ,
10.                 CNT_11MS = 21'd69   ,
11.                 CNT_41MS = 22'd149  ,
12.                 CNT_51MS = 22'd199  ,
13.                 CNT_60MS = 22'd249  ;
14.
15.     //wire  define
16.     wire            key_flag        ;   //消抖后按键信号
17.
18.     //reg   define
19.     reg             clk         ;   //仿真时钟信号
20.     reg             rst_n       ;   //仿真复位信号
21.     reg             key_in          ;   //模拟按键输入
22.     reg     [21:0]  tb_cnt          ;  //模拟按键抖动计数器
23.
24.     /*初始化输入信号*/
25.     initial begin
26.         clk    = 1'b1;
27.         rst_n <= 1'b0;
28.         key_in    <= 1'b0;
29.         #20
30.         rst_n <= 1'b1;
31.     end
32.
33.     //时钟信号
34.     always #10 clk = ~clk;
35.
36.     /*tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程*/
37.     always@(posedge clk or negedge rst_n)
38.     if(rst_n == 1'b0)
39.         tb_cnt <= 22'b0;
40.     else    if(tb_cnt == CNT_60MS)
41.         tb_cnt <= 22'b0;
42.     else
43.         tb_cnt <= tb_cnt + 1'b1;
44.
45.     /*key_in:产生输入随机数,模拟按键的输入情况*/
46.     always@(posedge clk or negedge rst_n)
47.     if(rst_n == 1'b0)
48.         key_in <= 1'b1;
49.     else    if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
50.              || (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
51.     /*在该计数区间内产生非负随机数0、1来模拟10ms的前抖动和10ms的后抖动*/
52.         key_in <= {$random} % 2;
53.     else    if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
54.         key_in <= 1'b0;
55.     /*按键经过10ms的前抖动后稳定在低电平,持续时间需大于CNT_MAX*/
56.     else
57.         key_in <= 1'b1;
58.
59.     key_filter
60.     #(
61.         .CNT_MAX    (20'd24     )
62.     )
63.     key_filter_inst
64.     (
65.         .clk    (clk    ),
66.         .rst_n  (rst_n  ),
67.         .key_in     (key_in     ),
68.
69.         .key_flag   (key_flag   )
70.     );
71.
72. endmodule

为了节约仿真时间,对计数器的计数值做了相应的调整,可以使我们更加清晰地观测到按键消抖的效果,后面提到的20ms均为模拟的值,不是真实时间。
使用随机数产生代码{$random} % 2来模拟按键的按下和松开,通过使用tb_cnt来模拟每一个key_in的持续时间,当持续时间大于设置的20ms后,按键被检测到有效,这时key_flag拉高一个时钟周期的高电平,代表着按键按下有效。

(2)仿真波形图

图4-1 按键消抖仿真
观察波形图,可以看到,当key_in的低电平时间不足设置的20ms时,key_flag无效,当key_in的低电平时间到了20ms后,key_flag拉高一个时钟周期,并且立即变为低电平,这样就达到了只检测到一个按键按下的效果。
2.状态机仿真测试
(1)状态机仿真测试模块代码

1.   /*设置时间步进为1ns,精度为1ns*/
2.  `timescale 1ns/1ns
3.  /*定义时钟周期为10000ns,时钟频率就为1KHz*/
4.  `define clk_period 10000
5.
6.  module state_test_tb;
7.
8.      /*定义系统时钟*/
9.      reg clk;
10.     /*复位输入,低电平有效*/
11.     reg rst_n;
12.     /*输入按键*/
13.     reg key_flag;
14.
15.     /*输出现态*/
16.     wire [1:0]cstate;
17.
18.     /*模拟时钟输入*/
19.     initial clk = 1'b0;
20.     always #(`clk_period/2) clk = ~clk;
21.
22.     initial begin
23.         /*初始化输入*/
24.         rst_n = 1'b0;
25.         key_flag = 1'b0;
26.         #200;
27.         /*复位置1,进行四次按键输入,可以模拟整个状态转移*/
28.         rst_n = 1'b1;
29.         key_flag = 1'b1;
30.         #20;
31.         key_flag = 1'b0;
32.         #200;
33.         key_flag = 1'b1;
34.         #20;
35.         key_flag = 1'b0;
36.         #200;
37.         key_flag = 1'b1;
38.         #20;
39.         key_flag = 1'b0;
40.         #200;
41.         key_flag = 1'b1;
42.         #20;
43.         key_flag = 1'b0;
44.         #500;
45.         $stop;
46.     end
47.
48.     /*实例化状态机模块代码*/
49.     state_test state_test_inst(
50.         .clk        (clk),
51.         .rst_n      (rst_n),
52.         .key_flag     (key_flag),
53.
54.         .cstate     (cstate)
55.     );
56.
57. endmodule

(2)仿真波形图

图4-2 状态机仿真
观察波形图,可以看到,当按键四次按下时,现态进行了四次跳转,且每次跳转均正确,仿真成功。
3.点灯模块
(1)LED灯仿真测试模块代码

1.   /*设置时间步进为1ns,精度为1ns*/
2.  `timescale 1ns/1ns
3.  /*定义时钟周期为10000ns,时钟频率就为1KHz*/
4.  `define clk_period 10000
5.
6.  module led_ctrl_tb;
7.
8.      reg clk;
9.      reg rst_n;
10.     reg [1:0]nstate;
11.
12.     wire [2:0]led;
13.
14.     /*设置模拟仿真时钟*/
15.     initial clk = 1'b0;
16.     always #(`clk_period/2) clk = ~clk;
17.
18.     initial begin
19.         rst_n = 1'b0;
20.         nstate = 2'b00;
21.         #23;
22.         rst_n = 1'b1;
23.         #200;
24.         nstate = 2'b01;
25.         #200;
26.         nstate = 2'b10;
27.         #200;
28.         nstate = 2'b11;
29.         #200;
30.         $stop;
31.     end
32.
33.     /*实例化LED模块*/
34.     led_ctrl led_ctrl_inst(
35.         /*输入系统时钟,频率为100KHz*/
36.         .clk        (clk),
37.         /*复位信号,低电平有效*/
38.         .rst_n      (rst_n),
39.         /*在顶层模块向led_ctrl输入的状态机状态*/
40.         .nstate     (nstate),
41.         /*三个LED灯输出*/
42.         .led        (led)
43.     );
44.
45.
46. endmodule

将LED模块例化在仿真激励测试文件中,通过状态机输入的状态值来进行灯的控制。

(2)仿真波形图

图4-3 LED模块仿真
通过波形图,可以看到,在状态为2’b00时,LED全灭,在状态2’b01时,LED0亮,在状态2’b10时,LED1亮,在状态2’b11时,LED2亮。仿真通过。
4.音乐播放模块
(1)音乐播放仿真测试代码

1.   /*设置时间步进为1ns,精度为1ns*/
2.  `timescale 1ns/1ns
3.  /*定义时钟周期为10000ns,时钟频率就为1KHz*/
4.  `define clk_period 10000
5.
6.  module beep_music_tb;
7.
8.      /*定义时钟*/
9.      reg clk;
10.     /*异步复位*/
11.     reg rst_n;
12.
13.     /*输出PWM波*/
14.     wire beep;
15.
16.     /*模拟输入系统时钟,时钟频率为100KHz*/
17.     initial clk=1'b1;
18.     always #(`clk_period/2) clk=~clk;
19.
20.     /*初始化端口变量*/
21.     initial begin
22.         rst_n=1'b0;
23.         #(`clk_period*20+1);
24.         rst_n=1'b1;
25.     end
26.
27.     /*实例化音乐播放模块*/
28.     beep_music
29.     #(
30.         .CLK_FRE(100_000)
31.     )
32.     beep_music(
33.
34.         .clk(clk),
35.         .rst_n(rst_n),
36.
37.         .beep(beep)
38.
39.     );
40.
41. endmodule

音乐自动播放,直接在初始化之后将复位信号拉高,地址自动增加。

(2)仿真波形图

图4-4 音乐播放模块仿真
地址依次加一。

图4-5 频率仿真图
单个音符的频率显示,此音符显示为839Hz。
五、喇叭功放电路及振荡电路
1.喇叭功放电路的搭建
(1)LM386芯片引脚配置和功能图

图5-1 LM386引脚图

图5-2 LM386功能图
(2)LM386应用电路
设置增益为20的应用电路:

图5-3 LM386电路图
(3)注意事项
电源输入电压信号会伴有噪声,在喇叭发出声音时会有较大的干扰,所以要在LM386芯片的电源脚上接一个1nF的滤波电容,这样就可以滤掉电源带来的噪声。
2.振荡电路
(1)SN74HC132芯片引脚配置及功能图

图5-4 SN74HC132引脚图

图5-5 SN74HC132功能图
(2)振荡电路电路图

图5-5 振荡电路图
其中,R为10K欧姆电阻,C为471、数值为470PF的电容。
(3)振荡电路波形

图5-6 时钟波形图
六、输出正弦波形(附加指标)
1.为什么要输出正弦波
对于声音来说,方波和正弦波输出声音,但是方波发出的声音的波形捕获下来是一段不规则的正弦波叠加而成,声音不会很悦耳。但是如果输出正弦波,包括基波和一至多个谐波,是很柔和且可控的声音。
2.正弦波的妙用
各种声音,尤其是乐器的声音听起来不同的原因是声音的基波和谐波不同,电子乐器就是通过模拟各种不同的乐器的每个声音的基波和谐波来达到发出对应的乐器的声音的,因此,只要知道了某一种声音的波形,就可以用代码来模拟并且输出出来。
3.产生正弦波的代码实现
(1)使用MATLAB产生波表
使用MATLAB数学工具可以产生任意形状的波形,FPGA通过在ROM中存储波表再输出可以实现波形发生器(DDS)。
MATLAB产生一个基波加一个一次谐波的代码

1.   F1=1;       %信号频率
2.  F2=2;
3.  P1=0;       %信号初始相位
4.  P2=0;
5.  Fs=248;     %采样频率
6.  N=248;      %采样点数
7.  t=[0:1/Fs:(N-1)/Fs];    %采样时刻
8.  ADC=2^7+35;
9.  A=2^7;      %信号幅度
10.
11. %生成信号
12. s1=A*sin(2*pi*F1*t+pi*P1/180)+ADC;
13. s2=A/2*sin(2*pi*F2*t+pi*P2/180)+ADC;
14. s=s1+s2-ADC;
15.
16. plot(t,s1,'-',t,s2,'*',t,s,'-');
17. legend('s1','s2','s3');
18. grid;
19.
20. fild = fopen('sin_wave_2.txt','wt');
21. for i = 1:N
22.     s0(i) = round(s(i));    %对小数四舍五入以取整
23.     if s0(i)<0
24.         s0(i)=0
25.     end
26.     fprintf(fild, '\t%g\t',i-1);    %地址编码
27.     fprintf(fild, '%s\t',':');      %冒号
28.     fprintf(fild, '%d',s0(i));      %数据写入
29.     fprintf(fild, '%s\n',';');      %分号,换行
30. end
31. fprintf(fild, '%s\n','END;');       %结束
32. fclose(fild);

波形图

图6-1 带有一次谐波的波形图
(2)FPGA对波表操作
取产生波表的一半,作为数据输入,在输入完半个波形周期后,进行反向输入,在节省存储空间的同时还可以正确的输出波形,这样就获得了一个完整的带有一次谐波的正弦波, 代码如下:

1.   module lookup_tables(
2.      input wire [7:0]phase,
3.      output wire [9:0]sin_out
4.  );
5.      reg [6:0]address;
6.      wire sel;
7.      wire [8:0]sine_table_out;
8.
9.      reg [9:0]sin_onecycle_amp;
10.
11.     sin_table sin_table_inst(
12.         .address(address),
13.         .sin(sine_table_out)
14.     );
15.
16.      /*输出十位的正弦波信号*/
17.     assign sin_out = sin_onecycle_amp[9:0];
18.     /*在波表地址读满128个之后对波形进行翻转输出,这样就实现了只
19.      用半个波表就输出了整个波形*/
20.      assign sel = phase[7];
21.
22.      /*读取波表*/
23.     always @(*)
24.         case(sel)
25.             1'b0:  begin
26.                     sin_onecycle_amp =  9'h1ff + sine_table_out[8:0];
27.                     address = phase[6:0];
28.                     end
29.             1'b1:  begin
30.                     sin_onecycle_amp = 9'h1ff - sine_table_out[8:0];
31.                     address = ~phase[6:0];
32.                     end
33.         endcase
34.
35. endmodule

(3)DDS模块
其中 fre_add 表示相位累加器输出值,位宽为 32 位,系统上电后,fre_add 信号一直执行自加操作,每个时钟周期自加参数 FREQ_CTRL,参数FREQ_CTRL 的计算方法为:FREQ_CTRL= 2N * fOUT / fCLK 。

1.   module dds
2.  #(
3.      parameter FREQ_CTRL = 24'd731
4.  )
5.  (
6.      input wire clk,
7.      input wire rst_n,
8.      /*波形输出*/
9.          output wire [9:0]data_out
10. );
11.
12.     reg [23:0]fre_add;
13.
14.      /*通过fre_add的自加操作实现了不同频率的输出*/
15.     always @(posedge clk or negedge rst_n)
16.     if(!rst_n)
17.         fre_add <= 1'b0;
18.     else
19.         fre_add <= fre_add + FREQ_CTRL;
20.
21.     lookup_tables lookup_tables_inst(
22.         .phase(fre_add[23:16]),
23.         .sin_out(data_out)
24.     );
25.
26. endmodule

通过对定义的常量FREQ_CTRL的改变,可以调整不同声音的频率。

(4)PWM产生模块

1.   module speaker_pwm(
2.      input wire clk,
3.      input wire rst_n,
4.      input wire [9:0]duty,
5.
6.      output wire pwm_out
7.  );
8.      reg [10:0]pwm_acc;
9.
10.     always @(posedge clk or negedge rst_n)
11.     if(!rst_n)
12.         pwm_acc <= 1'b0;
13.     else
14.         pwm_acc <= pwm_acc[9:0] + duty;
15.
16.      /*PWM输出*/
17.     assign pwm_out = pwm_acc[10];
18.
19. endmodule

PWM产生的原理是:通过pwm_acc和波形数据duty进行累加,duty是低十位的数据,pwm_out是最高位的数据,这样就实现了一个以duty为分频点的分频器,在波形输出的不同时间里可以有不同的PWM输出,就可以发出正弦波波形的声音了。

(5)输出正弦波仿真

图6-2 带有一次谐波的波形仿真图
根据波形图,可以看到信号发生器产生的波形和我们输入波表中的波形是一致的,并且正弦波的频率是531Hz,为中音Do,仿真通过。
七、问题与解决
1.声音经过放大电路后有很大的噪声
解决方法:在LM386芯片的电源脚上加上一个1nF的滤波电容,就可以有效地去除电源噪声。
2.使用103电容和104电容振荡不出需要的高频的频率。
解决方法:根据公式T=2RC,使用更小的511的电容,获得更小的周期,便可以振荡出更大的频率。
3.在对一个ROM操作时改变选取
解决方法:根据FPGA上电自复位,将三个歌曲的首地址设为复位时的状态,便可以自由选曲。
八、元件清单
1、Spartan - 3AN XC30S50AN – 4TQG144I
2、LM386功放芯片 1个
3、SN74HC132有源晶振 1个
4、1/8W喇叭 1个
5、LED发光二极管 3只
6、1K电阻 4个
7、103电容、104电容、511电容 各1只
8、10K电位器 1个
9、导线若干

九、参考文献
1、LM386芯片手册
2、SN74HC132芯片手册

十、实验小结及心得体会
本次实验我使用的是Verilog语言编写整个系统程序,通过工程实践,我发现了很多之前没有注意到的不好的代码习惯,也注意到了程序框图的重要性,在大型的系统设计中,系统框图可以对工程代码的编写起到引导性的作用。同时仿真也是必不可少的一环,正确编写仿真测试激励文件和如何观察仿真波形是一项必备的技巧性技能。
在此之前,我总是对全英文的芯片手册束手无策,但是这次我自己对照着芯片手册,一步一步搭建起了两个芯片对应的外围电路,对于这一方面的技能有了长足的进步。
在后面的大学生活中,我也会继续进行更深入的FPGA设计实现,将这一项技能变成我的优势能力,完善自身,力争上游。
十一、附录
1.最终电路图

图11-1 最终电路图
2.课程设计日志
9月12日,我对整个系统电路进行了系统框图的设计,对每一个模块的作用做了初步的计划。
9月13日,编写按键消抖模块的代码,并通过仿真。
9月14日,编写LED模块代码并且搭配按键消抖模块实现了3个LED灯的控制,完成了这一项目指标。
9月15日,编写了音乐播放模块代码并通过了仿真。
9月16日,搭建了功放的外围电路并且实现了声音的输出。
9月19日,搭建了SN74HC132的外围电路并且达成了需要的输出频率。
9月20日,实现了整个工程,并通过了验收。
9月21日-23日,编写了额外指标的输出正弦波代码设计并通过了仿真。

南京邮电大学电子电路课程设计可编程音乐自动演奏电路相关推荐

  1. 电子技术课程设计-正弦波发生及频率显示电路-电子线路CAD原理图

    微信公众号:创享日记 发送关键词:电子技术课设 免费获取完整无水印实验报告+付费电子线路CAD原理图源文件及其导入教程 前些天发现了十分不错的人工智能学习电子书,通俗易懂,风趣幽默,没有广告,分享给大 ...

  2. 大连海事大学电子线路课程设计/频率电压转化器/LM324multisim仿真图与LM331原理图

    好的学渣没有屏障,好的互联网不要搞信息差 LM324反向放大器&反向加法运算器与方波转换部分原理图 multisim中没有LM331,不要浪费时间去找它,雨课堂任务也不需要做它的仿真.也不要幻 ...

  3. 计数器的设计--电子技术课程设计说明书--模99

    < 计数器的设计> 电子技术课程设计说明书 目录 < 计数器的设计> 电子技术课程设计说明书 引言 方案比较 电路设计 电路的仿真 结论 参考文献: 引言 计数是一种最简单基本 ...

  4. 电子电路课程设计——8位数字抢答器设计论文

    江苏广播电视大学 (电子电路课程设计--8位数字抢答器设计论文) 班级: 姓名: 指导老师: 日期:2010年10月 目录 一.摘要 二.前言 三.设计目的与要求 1 .设计目的 2. 设计要求 四. ...

  5. android秒表课程设计,电子秒表电路课程设计.doc

    数字电子技术课程设计 题目 电子秒表电路课程设计 姓名: 学号: 专业班级: 指导老师: 日期: 一.设计目的:3 二.设计任务及说明:3 三. 功能要求3 四.总体方案及原理:3 五.电子秒表原理仿 ...

  6. 电子技术课程设计基于FPGA的音乐硬件演奏电路的设计与实现

    wx供重浩:创享日记 对话框发送:乐曲电路 免费获取完整无水印论文报告(包含电路图) 文章目录 一.设计任务要求 二.总体框图 三.选择器件 四.功能模块 五.总体设计电路图 六.结束语 一.设计任务 ...

  7. 数字电子技术课程设计之基于触发器的三位二进制同步减法计数器无效态000/110

    基于触发器的三位二进制同步减法计数器无效态000/110 1 课程设计的目的与作用 掌握用multisim 的电路仿真程序 熟悉同步计数器工作原理和逻辑功能 熟悉计数器电路的分析和设计方法 掌握161 ...

  8. 五邑大学安卓开发程序设计报告_五邑大学模拟电路课程设计报告模板.docx

    五邑大学模拟电路课程设计报告模板 模拟电路课程设计 PAGE10 / NUMPAGES10 模拟电路课程设计报告 课程题目:二阶低通滤波器和50HZ陷波滤波器 院系名称: 专业名称: 班级: 学号: ...

  9. 《数字电子技术课程设计》课程笔记(二)————multisim仿真模拟

    仿真源文件:链接:百度网盘 请输入提取码 提取码:1234 一.前言 对于一个完整的设计过程而言,仿真是一个必不可少的过程,因为仿真给了你一个重要的数据参考,信心来源,在之后的硬件设计过程中,心中的引 ...

  10. 数字电子技术课程设计——盲人报时钟

    数字电子技术课程设计 盲人报时钟 任务书 0.1  设计课题 盲人报时钟 0.2  设计目的 (1)     掌握盲人报时钟的设计.组装和调试方法. (2)     掌握声响模块的设计. 0.3  设 ...

最新文章

  1. 樱木花道为原型,丰田做了个投篮机器人!完虐人类选手,还创下了吉尼斯世界纪录...
  2. Java使用jmeter源码进行接口测试_jmeter用java代码怎样编写接口测试源码
  3. php不支持定时器么,PHP没有定时器?
  4. 台州银行登录显示服务器异常,台州银行网上银行异常问题处理
  5. java的empty_Java Stack empty()方法与示例
  6. 网页图表Highcharts实践教程之标签组与载入动画
  7. Exam化的软件项目管理
  8. BAT中如何使用for循环
  9. delphi xe3 oracle,delphixe3
  10. 基于51单片机的超声波倒车雷达防撞系统 proteus仿真 LCD1602显示
  11. 合抱之木,生于毫末。九层之台,起于累土。千里之行,始于足下
  12. 数据库建模——概念模型、逻辑模型、物理模型
  13. pscc2018安装服务器无响应,一招解决PSCC2018无法安装扩展插件
  14. 计算机考研408哪个视频好,计算机408考研视频哪个好
  15. 关于“微笑涛声”博客
  16. GameofMir引擎架设传奇服务器【3:在服务器上架设引擎】
  17. 谈技术文章翻译的信雅达-上
  18. C语言数组指针(指向数组的指针)详解
  19. 从懵懂走向成熟,证明属于自己的征程
  20. WinAPI 对话框DialogBox、EndDialog、MAKEINTRESOURCE(资源转化宏)、窗口消息处理函数(返回值为FALSE)

热门文章

  1. Windows 安装maven
  2. 初学视觉学习笔记----打开摄像头遇到的问题
  3. SimpleDateFormat 中的 yyyyMMdd 与 yyyyMMDD
  4. JavaScript闭包理解
  5. bootice添加黑苹果引导_2019款小米air12.5完美黑苹果10.15.6macOS CatalinaEFI
  6. 设置win10自动登录/免密码自动登录方法
  7. Python Apex YOLO V5 6.2 目标检测 全过程记录
  8. 金蝶K3供应链单据套打设置(以采购订单为例)
  9. prince2 成功的项目管理_PRINCE2项目管理方法论
  10. 电商项目测试核心内容