差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
后一修订版 两侧同时换到之后的修订记录
蜂鸣器模块 [2017/06/01 14:25]
anran [STEP FPGA驱动基于74HC595的数码管模块]
蜂鸣器模块 [2017/06/05 10:19]
anran [Verilog代码]
行 6: 行 6:
 ====硬件说明==== ====硬件说明====
 ------- -------
-在前面之前入门教程中[[4. 数码管显示| 数码管独立显示 ]]章节已为大家介绍了数码管独立显示的相关内容,关于独立显示这里就不在赘述。我们的底板上有6位数码管,根据驱动方法不同,有以下比较+蜂鸣器分类
 \\ \\
-独立显示:控制每个数码管至少需要8个I/​O口控制,6位数码管就需要6*8 = 48根信号线才能分别显示。独立显示实现简单,但是需要大量的信号线。 
 \\ \\
-扫描显示:将每位数码管的同一段选信号连接在一起,这样我们就只需8根段选信号6根位选信号,共计14根信号。扫描显示可以有效节约I/​O口资源,实现起来稍显复杂。 +按其结构主分为压电式蜂鸣器电磁式蜂鸣器两种类型:
-{{ :​6位数码管.jpg?​1600 |}}+
 \\ \\
-我们小脚丫底板上使6位阴极数码管,分析扫描显示原理如下+  * 电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁相互作用下,周期性地振动发声。 
 +  * 压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及鸣箱、外壳等组成。多谐振荡器由晶体或集成电路构成当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,​输出1.5~2.5kHZ音频信号,阻抗匹配器推动压电蜂鸣片发声。 
 +按是否带有信号源分为有源蜂鸣器和无源蜂鸣器两种类型
 \\ \\
-当某一时刻FPGA控制8根公共段选接口输出数字1对应数码管字库数据8'​h06(DP=0、G=0、F=0、E=0、D=0、C=1、B=1、A=0)时同时控制6位数码管只有第1位使能(DIG1=0、DIG2=1、DIG3=1、DIG4=1、DIG5=1、DIG6=1)这我们会看到第1位数码管显示数字1,其余5位数码管显示,如果不明白可以参考入门教程中实验四:[[4. 数码管显示| 数码管独立显示 ]]章节+  * 有源蜂鸣器只需要在其供电端加上额定直流电压其内部震荡器就可以产生固定频率信号驱动蜂鸣器发出声音。 
 +  * 无源蜂鸣器可以理解成与喇叭一样,需要在供电端上加上高低断变化的电信号才可以驱动发出声音。
 \\ \\
-按照扫描方式一共分为6个时刻,段选端口分别对应输出6位数码管需要显示字库数据,位选端保持每个时刻只有1位数码管处于使状态6个时刻依次循环当扫描频率足够高(例当扫描频率等于100Hz)时,则在人眼看到的数码管显示就是连续的,我们看到的就是6个不同的数字。+本章节和大家一起学习无源蜂鸣器驱动FPGA或单片机GPIO驱动力弱不能直接驱动无源蜂鸣器常用的蜂鸣器驱动电路下:
 \\ \\
-上面为大家介绍了数码管的独立显示和扫描显示两种方法,扫描显示的方式使用了14个I/​O口控制,相对于简单的处理来讲14个I/​O口也是非常多了,这里我们又使用了一款常见的驱动芯片74HC595,下面我们一起了解一下:+{{ :​无源蜂鸣器驱动电路.jpg?​600 |}}
 \\ \\
-74HC595是较为常的串行转并行的芯片内部集成了一个8位移位寄存、一个存储和8个态缓冲输出。在最简单情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/​O口控制两个级联的74HC595芯片,产生16路并行输,连接到扫描显示6位数码管上,可以轻松完成数码管驱动任务+蜂鸣器使NPN三极管(9013)驱动三极管当开关用,当基极电压拉高时,蜂鸣通电,当基极电压拉低时,蜂鸣断电,FPGA控制GPIO口给极管的基极输出不同频率脉冲信号,蜂鸣器就可以不同音节
 \\ \\
-{{ :​74hc595电路.jpg?​1000 |}}+不同音节与蜂鸣器震荡频率的对应关系如下:
 \\ \\
-不同的IC厂家都可以生产74HC595芯片,功能都是一样的,然而不同厂家的芯片手册对于管脚的命名会存在差异,管脚顺序相同,大家可以对应识别 +{{ :蜂鸣音节频率.jpg?​800 |}}
-上图是本设计中74HC595芯片的硬件电路连接,参考74HC595数据手册了解其具体用法,下图中我们了解到OE#​(G#​)和MR#​(SCLR#​)信号分别为输出使能(低电平输出)和复位管脚(低电平复位),OE#​(G#​)我们接GND让芯片输出使能,MR#​(SCLR#​)我们接VCC让芯片的移位寄存永远不复位,如此FPGA只需要控制SH_CP(SCK)、ST_CP(RCK)和DS(SER)即可。+
 \\ \\
-{{ :​74hc595引脚功能.jpg |74hc595引脚功能}}+我们使用PWM的方法(关于PWM的说明,快速入门中的[[脉冲发生器]]章节有详细的介绍),使用计数器对系统时钟进行分频,改变计数器的计数终值从而实现调节PWM信号频率的目的,使用PWM信号控制蜂鸣器电路。
 \\ \\
-{{ :​74hc595逻辑图.jpg |74hc595逻辑图}} 
-\\ 
-{{ :​74hc595时序图.jpg |74hc595时序图}} 
-\\ 
-{{ :​数码管程序框图.jpg?​1000 |数码管驱动程序框图}} 
-\\ 
- 
 ====Verilog代码==== ====Verilog代码====
 ------ ------
行 44: 行 37:
 // >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​ // >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
-// Module:Segment_scan ​+// Module: ​Beeper
 //  // 
 // Author: Step // Author: Step
 //  // 
-// Description: ​Display with Segment tube+// Description: ​Beeper
 //  // 
-// Web: www.stepfpga.com+// Web: www.stepfapga.com
 //  // 
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
行 56: 行 49:
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
 // Version: |Mod. Date:   ​|Changes Made: // Version: |Mod. Date:   ​|Changes Made:
-// V1.0     |2015/11/11   |Initial ver+// V1.0     |2016/04/20   |Initial ver
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
-module ​Segment_scan+module ​Beeper
 ( (
-input clk_in, //​系统时钟 +input clk_in, //​系统时钟 
-input rst_n_in, //​系统复位,低有效 +input rst_n_in,​ //​系统复位,低有效 
-input [3:​0] seg_data_1, //SEG1 数码管要显示的数据 +input tone_en, //蜂鸣器使能信号 
-input [3:​0] seg_data_2,​ //​SEG2 数码管要显示的数据 +input [4:0] tone, //蜂鸣器音节控制 
-input [3:​0] seg_data_3,​ //​SEG3 数码管要显示的数据 +output reg piano_out //蜂鸣器控制输出
-input [3:​0] seg_data_4,​ //​SEG4 数码管要显示的数据 +
-input [3:​0] seg_data_5,​ //​SEG5 数码管要显示的数据 +
-input [3:​0] seg_data_6,​ //​SEG6 数码管要显示的数据 +
-input [5:​0] seg_data_en,​ //​各位数码管数据显示使能,[MSB~LSB]=[SEG6~SEG1] +
-input [5:0] seg_dot_en, //各位数码管小数点显示使能,[MSB~LSB]=[SEG6~SEG1] +
-output reg rclk_out,​ //​74HC595的RCK管脚 +
-output reg sclk_out,​ //​74HC595的SCK管脚 +
-output reg sdio_out //74HC595的SER管脚+
 ); );
 +/*
 +无源蜂鸣器可以发出不同的音节,与蜂鸣器震动的频率(等于蜂鸣器控制信号的频率)相关,
 +为了让蜂鸣器控制信号产生不同的频率,我们使用计数器计数(分频)实现,不同的音节控制对应不同的计数终值(分频系数)
 +计数器根据计数终值计数并分频,产生蜂鸣器控制信号
 +*/
 +reg [15:0] time_end;
 +//​根据不同的音节控制,选择对应的计数终值(分频系数)
 +//​低音1的频率为261.6Hz,蜂鸣器控制信号周期应为12MHz/​261.6Hz = 45871.5,
 +//​因为本设计中蜂鸣器控制信号是按计数器周期翻转的,所以几种终值 = 45871.5/2 = 22936
 +//​需要计数22936个,计数范围为0 ~ (22936-1),所以time_end = 22935
 +always@(tone) begin
 + case(tone)
 + 5'​d1:​ time_end = 16'​d22935;​ //​L1,​
 + 5'​d2:​ time_end = 16'​d20428;​ //​L2,​
 + 5'​d3:​ time_end = 16'​d18203;​ //​L3,​
 + 5'​d4:​ time_end = 16'​d17181;​ //​L4,​
 + 5'​d5:​ time_end = 16'​d15305;​ //​L5,​
 + 5'​d6:​ time_end = 16'​d13635;​ //​L6,​
 + 5'​d7:​ time_end = 16'​d12147;​ //​L7,​
 + 5'​d8:​ time_end = 16'​d11464;​ //​M1,​
 + 5'​d9:​ time_end = 16'​d10215;​ //​M2,​
 + 5'​d10:​ time_end = 16'​d9100;​ //​M3,​
 + 5'​d11:​ time_end = 16'​d8589;​ //​M4,​
 + 5'​d12:​ time_end = 16'​d7652;​ //​M5,​
 + 5'​d13:​ time_end = 16'​d6817;​ //​M6,​
 + 5'​d14:​ time_end = 16'​d6073;​ //​M7,​
 + 5'​d15:​ time_end = 16'​d5740;​ //​H1,​
 + 5'​d16:​ time_end = 16'​d5107;​ //​H2,​
 + 5'​d17:​ time_end = 16'​d4549;​ //​H3,​
 + 5'​d18:​ time_end = 16'​d4294;​ //​H4,​
 + 5'​d19:​ time_end = 16'​d3825;​ //​H5,​
 + 5'​d20:​ time_end = 16'​d3408;​ //​H6,​
 + 5'​d21:​ time_end = 16'​d3036;​ //​H7,​
 + default:​time_end = 16'​d65535;​
 + endcase
 +end
  
-parameter CLK_DIV_PERIOD = 600; //​分频系数 +reg [17:0] time_cnt
- +//当蜂鸣器使能时,计数器按照计数终值(分频数)计数
-localparam IDLE = 3'​d0;​ +
-localparam MAIN = 3'​d1;​ +
-localparam WRITE = 3'​d2;​ +
- +
-localparam LOW = 1'​b0;​ +
-localparam HIGH = 1'​b1;​ +
- +
-//​创建数码管的字库,字库数据依段码顺序有关 +
-//​这里字库数据[MSB~LSB]={DP,​G,​F,​E,​D,​C,​B,​A} +
-reg[7:0] seg [15:0];  +
-initial begin +
-    seg[0] = 8'​h3f; ​  // ​ 0 +
-    seg[1] = 8'​h06; ​  // ​ 1 +
-    seg[2] = 8'​h5b; ​  // ​ 2 +
-    seg[3] = 8'​h4f; ​  // ​ 3 +
-    seg[4] = 8'​h66; ​  // ​ 4 +
-    seg[5] = 8'​h6d; ​  // ​ 5 +
-    seg[6] = 8'​h7d; ​  // ​ 6 +
-    seg[7] = 8'​h07; ​  // ​ 7 +
-    seg[8] = 8'​h7f; ​  // ​ 8 +
-    seg[9] = 8'​h6f; ​  // ​ 9 +
- seg[10] = 8'​h77; ​  // ​ A +
-    seg[11] = 8'​h7c; ​  // ​ b +
-    seg[12] = 8'​h39; ​  // ​ C +
-    seg[13] = 8'​h5e; ​  // ​ d +
-    seg[14] = 8'​h79; ​  // ​ E +
-    seg[15] = 8'​h71; ​  // ​ F +
-end  +
-  +
-//计数器统时钟信号进行计数 +
-reg[9:0] cnt=0;+
 always@(posedge clk_in or negedge rst_n_in) begin always@(posedge clk_in or negedge rst_n_in) begin
  if(!rst_n_in) begin  if(!rst_n_in) begin
- cnt <= 1'b0;+ time_cnt <= 1'​b0;​ 
 + end else if(!tone_en) begin 
 + time_cnt <= 1'​b0;​ 
 + end else if(time_cnt>​=time_end) begin 
 + time_cnt ​<= 1'b0;
  end else begin  end else begin
- if(cnt>​=(CLK_DIV_PERIOD-1)) cnt <= 1'​b0;​ + time_cnt ​<= time_cnt ​+ 1'b1;
- else cnt <= cnt + 1'b1;+
  end  end
 end end
- +  
-//​根据计数器计数的周期产生分频的脉冲信号 +//​根据计数器的周期,翻转蜂鸣器控制信号
-reg clk_div; ​+
 always@(posedge clk_in or negedge rst_n_in) begin always@(posedge clk_in or negedge rst_n_in) begin
  if(!rst_n_in) begin  if(!rst_n_in) begin
- clk_div ​<= 1'b0;+ piano_out ​<= 1'b0; 
 + end else if(time_cnt==time_end) begin 
 + piano_out <= ~piano_out;​ //​蜂鸣器控制输出翻转,两次翻转为1Hz
  end else begin  end else begin
- if(cnt==(CLK_DIV_PERIOD-1)) clk_div <= 1'​b1;​ + piano_out ​<= piano_out;
- else clk_div ​<= 1'b0;+
  end  end
 end end
- +
-//​使用状态机完成数码管的扫描和74HC595时序的实现 +
-reg [15:​0] data_reg;​ +
-reg [2:​0] cnt_main;​ +
-reg [5:​0] cnt_write;​ +
-reg [2:0] state = IDLE; +
-always@(posedge clk_in or negedge rst_n_in) begin +
- if(!rst_n_in) begin //​复位状态下,各寄存器置初值 +
- state <= IDLE; +
- cnt_main <= 3'​d0;​ +
- cnt_write <= 6'​d0;​ +
- sdio_out <= 1'​b0;​ +
- sclk_out <= LOW; +
- rclk_out <= LOW; +
- end else begin +
- case(state) +
- IDLE:​begin //​IDLE作为第一个状态,相当于软复位 +
- state <= MAIN; +
- cnt_main <= 3'​d0;​ +
- cnt_write <= 6'​d0;​ +
- sdio_out <= 1'​b0;​ +
- sclk_out <= LOW; +
- rclk_out <= LOW; +
- end +
- MAIN:​begin +
- if(cnt_main >= 3'd5) cnt_main <= 1'​b0;​ +
- else cnt_main <= cnt_main + 1'​b1;​ +
- case(cnt_main) +
- //​对6位数码管逐位扫描 +
- 3'​d0:​ begin  +
- state <= WRITE;​ //​在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序 +
- data_reg <= {seg[seg_data_1]|(seg_dot_en[0]?​8'​h80:​8'​h00),​seg_data_en[0]?​8'​hfe:​8'​hff};​ +
- //​data_reg[15:​8]为段选,data_reg[7:​0]为位选 +
- //​seg[seg_data_1] ​ 是根据端口的输入获取相应字库数据 +
- //​seg_dot_en[0]?​8'​h80:​8'​h00 ​ 是根据小数点显示使能信号 控制SEG1数码管的小数点DP段的电平 +
- //​seg_data_en[0]?​8'​hfe:​8'​hff ​ 是根据数据显示使能信号 控制SEG1数码管的位选引脚的电平 +
- end +
- 3'​d1:​ begin  +
- state <= WRITE; +
- data_reg <= {seg[seg_data_2]|(seg_dot_en[1]?​8'​h80:​8'​h00),​seg_data_en[1]?​8'​hfd:​8'​hff};​  +
- end +
- 3'​d2:​ begin  +
- state <= WRITE; +
- data_reg <= {seg[seg_data_3]|(seg_dot_en[2]?​8'​h80:​8'​h00),​seg_data_en[2]?​8'​hfb:​8'​hff};​  +
- end +
- 3'​d3:​ begin  +
- state <= WRITE; +
- data_reg <= {seg[seg_data_4]|(seg_dot_en[3]?​8'​h80:​8'​h00),​seg_data_en[3]?​8'​hf7:​8'​hff};​  +
- end +
- 3'​d4:​ begin  +
- state <= WRITE; +
- data_reg <= {seg[seg_data_5]|(seg_dot_en[4]?​8'​h80:​8'​h00),​seg_data_en[4]?​8'​hef:​8'​hff};​ +
- end +
- 3'​d5:​ begin  +
- state <= WRITE; +
- data_reg <= {seg[seg_data_6]|(seg_dot_en[5]?​8'​h80:​8'​h00),​seg_data_en[5]?​8'​hdf:​8'​hff};​  +
- end +
- default:​ state <= IDLE; +
- endcase +
- end +
- WRITE:​begin +
- if(clk_div) begin //​74HC595的串行时钟有速度要求,需要按照分频后的节拍 +
- if(cnt_write >= 6'd33) cnt_write <= 1'​b0;​ +
- else cnt_write <= cnt_write + 1'​b1;​ +
- case(cnt_write) +
- //​74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用 +
- //​74HC595的时序实现,参考74HC595的芯片手册 +
- 6'​d0: ​ begin sclk_out <= LOW; sdio_out <= data_reg[15];​ end //​SCK下降沿时SER更新数据 +
- 6'​d1: ​ begin sclk_out <= HIGH; end //​SCK上升沿时SER数据稳定 +
- 6'​d2: ​ begin sclk_out <= LOW; sdio_out <= data_reg[14];​ end +
- 6'​d3: ​ begin sclk_out <= HIGH; end +
- 6'​d4: ​ begin sclk_out <= LOW; sdio_out <= data_reg[13];​ end +
- 6'​d5: ​ begin sclk_out <= HIGH; end +
- 6'​d6: ​ begin sclk_out <= LOW; sdio_out <= data_reg[12];​ end +
- 6'​d7: ​ begin sclk_out <= HIGH; end +
- 6'​d8: ​ begin sclk_out <= LOW; sdio_out <= data_reg[11];​ end +
- 6'​d9: ​ begin sclk_out <= HIGH; end +
- 6'​d10:​ begin sclk_out <= LOW; sdio_out <= data_reg[10];​ end +
- 6'​d11:​ begin sclk_out <= HIGH; end +
- 6'​d12:​ begin sclk_out <= LOW; sdio_out <= data_reg[9];​ end +
- 6'​d13:​ begin sclk_out <= HIGH; end +
- 6'​d14:​ begin sclk_out <= LOW; sdio_out <= data_reg[8];​ end +
- 6'​d15:​ begin sclk_out <= HIGH; end +
- 6'​d16:​ begin sclk_out <= LOW; sdio_out <= data_reg[7];​ end +
- 6'​d17:​ begin sclk_out <= HIGH; end +
- 6'​d18:​ begin sclk_out <= LOW; sdio_out <= data_reg[6];​ end +
- 6'​d19:​ begin sclk_out <= HIGH; end +
- 6'​d20:​ begin sclk_out <= LOW; sdio_out <= data_reg[5];​ end +
- 6'​d21:​ begin sclk_out <= HIGH; end +
- 6'​d22:​ begin sclk_out <= LOW; sdio_out <= data_reg[4];​ end +
- 6'​d23:​ begin sclk_out <= HIGH; end +
- 6'​d24:​ begin sclk_out <= LOW; sdio_out <= data_reg[3];​ end +
- 6'​d25:​ begin sclk_out <= HIGH; end +
- 6'​d26:​ begin sclk_out <= LOW; sdio_out <= data_reg[2];​ end +
- 6'​d27:​ begin sclk_out <= HIGH; end +
- 6'​d28:​ begin sclk_out <= LOW; sdio_out <= data_reg[1];​ end +
- 6'​d29:​ begin sclk_out <= HIGH; end +
- 6'​d30:​ begin sclk_out <= LOW; sdio_out <= data_reg[0];​ end +
- 6'​d31:​ begin sclk_out <= HIGH; end +
- 6'​d32:​ begin rclk_out <= HIGH; end //​当16位数据传送完成后RCK拉高,输出生效 +
- 6'​d33:​ begin rclk_out <= LOW; state <= MAIN; end +
- default:​ state <= IDLE; +
- endcase +
- end else begin +
- sclk_out <= sclk_out; +
- sdio_out <= sdio_out; +
- rclk_out <= rclk_out; +
- cnt_write <= cnt_write;​ +
- state <= state; +
- end +
- end +
- default: state <= IDLE; +
- endcase +
- end +
-end +
 endmodule endmodule