**这是本文档旧的修订版!**
STEP FPGA驱动无源蜂鸣器模块
本节将和大家一起使用FPGA驱动底板上的无源蜂鸣器模块实现不同音节的输出。
====硬件说明====
蜂鸣器的分类:
按其结构主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型:
- 电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
- 压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。多谐振荡器由晶体管或集成电路构成,当接通电源后(1.5~15V直流工作电压),多谐振荡器起振,输出1.5~2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声。
按是否带有信号源分为有源蜂鸣器和无源蜂鸣器两种类型:
- 有源蜂鸣器只需要在其供电端加上额定直流电压,其内部的震荡器就可以产生固定频率的信号,驱动蜂鸣器发出声音。
- 无源蜂鸣器可以理解成与喇叭一样,需要在其供电端上加上高低不断变化的电信号才可以驱动发出声音。
本章节和大家一起学习无源蜂鸣器的驱动,FPGA或单片机的GPIO口驱动能力弱,不能直接驱动无源蜂鸣器,常用的蜂鸣器驱动电路如下:
蜂鸣器使用NPN三极管(9013)驱动,三极管当开关用,当基极电压拉高时,蜂鸣器通电,当基极电压拉低时,蜂鸣器断电,FPGA控制GPIO口给三极管的基极输出不同频率的脉冲信号,蜂鸣器就可以发出不同的音节。
不同音节与蜂鸣器震荡频率的对应关系如下:
我们使用PWM的方法(关于PWM的说明,快速入门中的脉冲发生器章节有详细的介绍),使用计数器对系统时钟进行分频,改变计数器的计数终值从而实现调节PWM信号频率的目的,使用PWM信号控制蜂鸣器电路。
====Verilog代码====
// -------------------------------------------------------------------- // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< // -------------------------------------------------------------------- // Module:Segment_scan // // Author: Step // // Description: Display with Segment tube // // Web: www.stepfpga.com // // -------------------------------------------------------------------- // Code Revision History : // -------------------------------------------------------------------- // Version: |Mod. Date: |Changes Made: // V1.0 |2015/11/11 |Initial ver // -------------------------------------------------------------------- module Segment_scan ( input clk_in, //系统时钟 input rst_n_in, //系统复位,低有效 input [3:0] seg_data_1, //SEG1 数码管要显示的数据 input [3:0] seg_data_2, //SEG2 数码管要显示的数据 input [3:0] seg_data_3, //SEG3 数码管要显示的数据 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管脚 ); parameter CLK_DIV_PERIOD = 600; //分频系数 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 if(!rst_n_in) begin cnt <= 1'b0; end else begin if(cnt>=(CLK_DIV_PERIOD-1)) cnt <= 1'b0; else cnt <= cnt + 1'b1; end end //根据计数器计数的周期产生分频的脉冲信号 reg clk_div; always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) begin clk_div <= 1'b0; end else begin if(cnt==(CLK_DIV_PERIOD-1)) clk_div <= 1'b1; else clk_div <= 1'b0; 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
====小结====
本节主要为大家讲解了数码管显示的相关原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。
如果你对Diamond软件的使用不了解,请参考这里:Diamond的使用。
====相关资料====
使用STEP-MXO2第二代的数码管扫描程序: 后续会有下载连接 待更新
使用STEP-MAX10的数码管扫描程序: 后续会有下载连接 待更新