用lattice machXO2做ADC电压计和PWM音乐播放器
能够实现ADC电压转换,通过电位计调节电压,并将电压值显示在oled屏幕上 能够实现PWM音乐播放,切换曲目,并且将曲名显示在OLED屏幕上
标签
FPGA
数字逻辑
显示
清风董小新
更新2021-09-07
1093

用lattice machXO2做ADC电压计和PWM音乐播放器

前言

感谢硬禾学堂提供的硬件支持,本次项目花费十天左右的时间,本来想完成更多的项目,但介于之后会有一个三周左右的实习时间,因此只完成了第一二条要求。

对于这次项目,可谓是感慨颇多。之前我并没有接触过FPGA以及Verilog,因此在做的时候遇到了很多困难,期间我也通过大量的查资料,自己解决了很多。但有些问题网上也查不到,不过有幸的是,在与周围人的交流下,这些困难也大都得到了解决。

模块介绍

本次项目分为三个模块:

ADC采样模块:利用串口ADC进行电压采样,将采集到的电压值转换为数字信号,并通过数学转换,将电压输出信号最大值由256转换为3.3,之后通过左移加三的算法,将原码转换为BCD码,最后通过发送到oled显示模块。

PWM音乐播放模块:该模块将乐谱存在ROM中,通过开关进行乐谱的切换来实现音乐的切换。该模块设置了一个时间计数器,输出信号由时间计数器由分频系数控制,每当时间计数器达到一定值时输出信号翻转,并由端口发送到蜂鸣器。分频系数取决于乐谱中对应的数字。同时,该模块还设置了一个计数器来控制节拍。

oled显示模块:该模块由状态机控制,分为以下几个状态:

1.起始状态

2.主体程序部分

3.刷屏状态

4.写入状态

5.延时状态

6.初始化状态

首先状态机由起始状态开始,为相关参数做初始化赋值;之后进入到主题部分,在该状态下,程序给需要送到oled显示模块的数据赋值,并进入到刷屏模块;在刷屏模块中,程序将送到oled的数据以八个字节为单位分组,形成8*8字块,并插入三个8维零向量,这样就形成了5*8字块,并进入到写入状态;在写入状态中,程序将扫描状态生成的8比特信号依次发送到oled显示屏当中,进入到延时状态;经过延时状态延时,状态机回到起始状态,这样,状态机就完成了一次循环。

FgsvmdHCxz5fZjQ2MdNj5DhQqqrh

1.ADC采样模块:

本次ADC采样使用的芯片是ADC7868,板子的晶振最大只能提供12MHz的频率,因此本程序还使用了PLL核进行调频。PLL核输出的频率为24MHz。

该ADC电压表模块精确到小数点后两位,程序输出35位数,该程序取前十二位。同时由于本程序只取前12位数,因此原本在电压上限改变一步中,原本应乘以系数0.0129,此处可直接乘以129。

代码如下:

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: ADC
// 
// Author: CHC
// 
// Description: ADC
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------


module ADC
(
input				clk,		//系统时钟
input				rst_n,  	//系统复位,低有效
output	reg			adc_cs,		//SPI总线CS
output	reg			adc_clk,	//SPI总线SCK
input				adc_dat,	//SPI总线SDA
output  reg [11:0]  bcd_code    //输出的BCD码
);

reg				adc_done;	//ADC采样完成标志
reg [7:0]		adc_data;	//ADC采样数据
reg [7:0]		cnt; //计数器
reg [7:0] 		data;
reg	[35:0]		shift_reg; 

localparam HIGH	= 1'b1, LOW = 1'b0;

always @(posedge clk or negedge rst_n)
	if(!rst_n) cnt <= 1'b0;
	else if(cnt >= 8'd34) cnt <= 1'b0;
	else cnt <= cnt + 1'b1;
 

always @(posedge clk or negedge rst_n)
	if(!rst_n) begin
		adc_cs <= HIGH; adc_clk <= HIGH;
	end else case(cnt)
		8'd0 :  begin adc_cs <= HIGH; adc_clk <= HIGH; end
		8'd1 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end
		8'd2,8'd4,8'd6,8'd8,8'd10,8'd12,8'd14,8'd16,
		8'd18,8'd20,8'd22,8'd24,8'd26,8'd28,8'd30,8'd32:	
				begin adc_cs <= LOW;  adc_clk <= LOW;  end
		8'd3 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end //0
		8'd5 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end //1
		8'd7 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end //2
		8'd9 :  begin adc_cs <= LOW;  adc_clk <= HIGH; data[7] <= adc_dat; end //3
		8'd11 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[6] <= adc_dat; end //4
		8'd13 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[5] <= adc_dat; end //5
		8'd15 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[4] <= adc_dat; end //6
		8'd17 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[3] <= adc_dat; end //7
		8'd19 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[2] <= adc_dat; end //8
		8'd21 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[1] <= adc_dat; end //9
		8'd23 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[0] <= adc_dat; end //10
		8'd25 : begin adc_cs <= LOW;  adc_clk <= HIGH; adc_data <= data; end //11
		8'd27 : begin adc_cs <= LOW;  adc_clk <= HIGH; adc_done <= HIGH; end //12
		8'd29 : begin adc_cs <= LOW;  adc_clk <= HIGH; adc_done <= LOW; end //13
		8'd31 : begin adc_cs <= LOW;  adc_clk <= HIGH; end //14
		8'd33 : begin adc_cs <= LOW;  adc_clk <= HIGH; end //15
		8'd34 : begin adc_cs <= HIGH;  adc_clk <= HIGH; end
		default : begin adc_cs <= HIGH;  adc_clk <= HIGH;  end
	endcase
	
wire [15:0] bin_code = adc_data * 16'd129;

always@(bin_code or rst_n)begin
	shift_reg = {20'h0,bin_code};
	if(!rst_n) bcd_code = 0; 
	else begin 
		repeat(16) begin //循环16次  
			//BCD码各位数据作满5加3操作,
			if (shift_reg[19:16] >= 5) shift_reg[19:16] = shift_reg[19:16] + 2'b11;
			if (shift_reg[23:20] >= 5) shift_reg[23:20] = shift_reg[23:20] + 2'b11;
			if (shift_reg[27:24] >= 5) shift_reg[27:24] = shift_reg[27:24] + 2'b11;
			if (shift_reg[31:28] >= 5) shift_reg[31:28] = shift_reg[31:28] + 2'b11;
			if (shift_reg[35:32] >= 5) shift_reg[35:32] = shift_reg[35:32] + 2'b11;
			shift_reg = shift_reg << 1; 
		end
		bcd_code = {shift_reg[35:24]};   
	end
end
endmodule

2.PWM模块

PWM模块个人认为还有优化的空间,在编写本模块时,由于三个曲子需要的音阶较多,而16进制能表示的音阶数量有限,因此在编写本模块的时候使用了大量的管脚来保存乐谱。个人认为这一部分还有优化的空间。

该模块通过使用两个时间计数器来输出变化的音调。当时钟沿来临时,程序先判断音乐选择参数来选择对应的乐谱,之后判断乐谱中的数字选择对应的计数终值,当计数器达到计数终值时,输出信号发生翻转,由此达到分频的效果。而另一个计数器用来控制节拍,使得音调发生转换。

代码如下:

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: Beeper
// 
// Author: CHC
// 
// Description: Beeper
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------
module Beeper
(
input					clk_in,		//系统时钟
input					rst_n_in,	//系统复位,低有效
input					tone_en,	//蜂鸣器使能信号
output	reg				piano_out,	//蜂鸣器控制输出
input	wire [2:0]		music_sel,  //音乐选择
output  reg  [1:0]      sel_num     //供OLED显示
);
/*
无源蜂鸣器可以发出不同的音节,与蜂鸣器震动的频率(等于蜂鸣器控制信号的频率)相关,
为了让蜂鸣器控制信号产生不同的频率,我们使用计数器计数(分频)实现,不同的音节控制对应不同的计数终值(分频系数)
计数器根据计数终值计数并分频,产生蜂鸣器控制信号
*/
reg [15:0] time_end;
reg [17:0] time_cnt;
reg [4:0]  tone;		 //蜂鸣器音节控制
reg [22:0] music_cnt;    //节拍控制
reg off;
reg [2:0] music=0;              //谱子
reg [480:0] music_rst;          //原谱保存
reg [2:0] music_lmh=0;          //中高低调
reg [480:0] music_lmh_rst;      //中高低调保存



//根据不同的音节控制,选择对应的计数终值(分频系数)
//低音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:	begin time_end =	16'd22935;	off=1; end//L1,
		5'd2:	begin time_end =	16'd20428;	off=1; end//L2,
		5'd3:	begin time_end =	16'd18203;	off=1; end//L3,
		5'd4:	begin time_end =	16'd17181;	off=1; end//L4,
		5'd5:	begin time_end =	16'd15305;	off=1; end//L5,
		5'd6:	begin time_end =	16'd13635;	off=1; end//L6,
		5'd7:	begin time_end =	16'd12147;	off=1; end//L7,
		5'd8:	begin time_end =	16'd11464;	off=1; end//M1,
		5'd9:	begin time_end =	16'd10215;	off=1; end//M2,
		5'd10:	begin time_end =	16'd9100;	off=1; end//M3,
		5'd11:	begin time_end =	16'd8589;	off=1; end//M4,
		5'd12:	begin time_end =	16'd7652;	off=1; end//M5,
		5'd13:	begin time_end =	16'd6817;	off=1; end//M6,
		5'd14:	begin time_end =	16'd6073;	off=1; end//M7,
		5'd15:	begin time_end =	16'd5740;	off=1; end//H1,
		5'd16:	begin time_end =	16'd5107;	off=1; end//H2,
		5'd17:	begin time_end =	16'd4549;	off=1; end//H3,
		5'd18:	begin time_end =	16'd4294;	off=1; end//H4,
		5'd19:	begin time_end =	16'd3825;	off=1; end//H5,
		5'd20:	begin time_end =	16'd3408;	off=1; end//H6,
		5'd21:	begin time_end =	16'd3036;	off=1; end//H7,
		5'd22:	begin time_end =	16'd21646;	off=1; end//c1#,
		5'd23:	begin time_end =	16'd19284;	off=1; end//d1#,
		5'd24:	begin time_end =	16'd16216;	off=1; end//f1#,
		5'd25:	begin time_end =	16'd14447;	off=1; end//g1#,
		5'd26:	begin time_end =	16'd12871;	off=1; end//a1#,
		5'd27:	begin time_end =	16'd10823;	off=1; end//c2#,
		5'd28:	begin time_end =	16'd9642;	off=1; end//d2#,
		5'd29:	begin time_end =	16'd8108;	off=1; end//f2#,
		5'd31:	begin time_end =	16'd7224;	off=1; end//g2#,
		5'd0:   begin time_end =    16'd25742;  off=1; end//A1#,

		default:off=0; 
	endcase
end
 


always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in)begin	
					  music_rst = 0;
					  music_lmh_rst = 0;
					  sel_num <= 0;
	end else if(!tone_en) begin
					  music_rst = 0;
					  music_lmh_rst = 0;
					  sel_num <= 0;
	end else begin
		case(music_sel) 
			3'd3:   begin 
					  music_rst =     480'o444447755455543001111772211230335404332236650777773311711155022222553323332102222027722223300055033656665507777733117111550222225533233321;     //保存原谱
					  music_lmh_rst = 480'o111111111111111001111001111110111101111110000000001111011100011111111111111101111010011111100000011000000000000011110111000111111111111111;    //保存中高低调
					  sel_num <= 2'd1;
				end
			3'd5:   begin  
					  music_rst =     480'o221222110221220302211211032532333022331103252122205562122202223212222123022133122125553233330221221221225553233300221331221225553233302212212212255532333;
					  music_lmh_rst = 480'o114441440114440404444144042444444044444404444411103331411104444441111444011444411444444444440444444114444444444400114444114444444444404444441144444444444;
					  sel_num <= 2'd2;
				end
			3'd6:	begin  
					  music_rst =     480'o777776646703330707466643034666430333336376337607770003776644306663337766443667700037766443000333336376337677442767476737630777476737677744276747673763;
					  music_lmh_rst = 480'o000000030041114000300030003000300000004000000000004441000033004440000000330000044410000330000000004000000033114000100010010333100010003311400010001001;
					  sel_num <= 2'd3;
				end
		endcase
	end
end






//节拍控制
always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		music_cnt <= 1'b0;
	end else if(!tone_en) begin
		music_cnt <= 1'b0;
	end else if(music_cnt>=23'h600000) begin
		music_cnt <= 1'b0;
	end else begin
		music_cnt <= music_cnt + 1'b1;
	end
end

reg [7:0] length;
//将乐谱的数字对应的曲调转换成相应的计数终值
always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		music<=music_rst[2:0];
		music_lmh <= music_lmh_rst[2:0];
		length <= 0;
	end else if(!tone_en) begin
		music<=music_rst[2:0];
		music_lmh <= music_lmh_rst[2:0];
		length <= 0;
	end else if(!sel_num) begin
		music<=music_rst[2:0];
		music_lmh <= music_lmh_rst[2:0];
		length <= 0;
	end else if(music_cnt==23'h600000) begin
			if(length>=160) begin
				music<=music_rst[2:0];
				music_lmh <= music_lmh_rst[2:0];
				length <= 0;
			end
			else begin
				music <= music_rst[3*length+:3];	//变调
				music_lmh <= music_lmh_rst[3*length+:3];
				length <= length+1;
			end
	end else begin
		tone <= tone;
	end
	tone <= music[2:0]+music_lmh[2:0]*7-2;
end


//当蜂鸣器使能时,计数器按照计数终值(分频系数)计数
always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		time_cnt <= 1'b0;
	end else if(!tone_en) begin
		time_cnt <= 1'b0;
	end else if(!off) begin
		time_cnt <= 1'b0;
	end else if(time_cnt>=time_end) begin
		time_cnt <= 1'b0;
	end else begin
		time_cnt <= time_cnt + 1'b1;
	end
end
 
//根据计数器的周期,翻转蜂鸣器控制信号
always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		piano_out <= 1'b0;
	end else if(!off) begin
		piano_out <= 1'b0;
	end else if(time_cnt==time_end) begin
		piano_out <= ~piano_out;	//蜂鸣器控制输出翻转,两次翻转为1Hz
	end else begin
		piano_out <= piano_out;
	end
end
endmodule

3.oled显示模块

本模块个人认为是最难的模块,代码是案例上代码加以修改,不过在看代码上花费了很长时间,遇到的问题也是最多的。最后在案例的基础上,修改了部分代码作为板子oled的驱动。

本次使用的是128*32的oled显示屏,而参考案例给的显示屏分辨率并不是128*32,这个问题当时难住了我好久,最后通过反复的对比,才发现是屏幕的分辨率不合适。本程序代码已对其进行修改。

该模块中还有一个问题困扰了我很久,就是Verilog语言的赋值,赋值之后的值位数问题。位数问题在oled显示中显得尤为重要。我在该模块使用了x<=0这种语言,但是这种语言赋的值默认是32位的,而我想要的位数为1,因此在oled屏幕上显示的效果是一串数字全是0,这个问题我也是查了很久才查出来。

该模块主要思想是使用状态机来进行驱动,通过初始化-赋值-屏幕扫描-发送数据-延时循环,来起到实时刷屏的效果。

代码如下:

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: oled
// 
// Author: CHC
// 
// Description: oled
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------
module oled
(

input					clk_in,		//系统时钟
input					rst_n_in,	//系统复位,低有效
input   [1:0]           sel_num,    //音乐选择
input   [11:0]			bcd_code,   //bcd码输入
input					tone_en,    //蜂鸣器使能
input					ADC_en,     //ADC使能

output	reg			    oled_csn,	//OLCD液晶屏使能
output	reg			    oled_rst,	//OLCD液晶屏复位
output	reg			    oled_dcn,	//OLCD数据指令控制
output	reg			    oled_clk,	//OLCD时钟信号
output	reg			    oled_dat	//OLCD数据信号

);


reg [7:0] cmd [24:0];
reg [39:0] mem [122:0];
reg	[7:0]	y_p, x_ph, x_pl;
reg	[(8*16-1):0] char;
reg	[7:0]	num, char_reg;				//
reg	[4:0]	cnt_main, cnt_init, cnt_scan, cnt_write;
reg	[15:0]	num_delay, cnt_delay, cnt;
reg	[5:0] 	state, state_back;

//*******************
//DEFINE PARAMETER
//*******************

	
localparam INIT_DEPTH = 16'd25; //LCD初始化的命令的数量
localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
localparam HIGH	= 1'b1, LOW = 1'b0;
localparam DATA	= 1'b1, CMD = 1'b0;




//显示屏显示曲目名
always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in) begin
			cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
			y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
			num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
			num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
			oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
			state <= IDLE; state_back <= IDLE;

		end else begin
			case(state)
				IDLE:begin
						cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
						y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
						num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
						num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
						oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
						state <= MAIN; state_back <= MAIN;
					end
				MAIN:begin
						if(cnt_main >= 5'd4) cnt_main <= 5'd1;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状态
							5'd0:	begin state <= INIT; end
							5'd1:	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;
									
									case(sel_num)
										2'd1:    char <= "TianKongZhiCheng";
										2'd2:    char <= "ChengDu         ";
										2'd3:    char <= "DaYuHaiTang     ";
										default: char <= "                ";
									endcase
									
									state <= SCAN; end
							5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; 
										if(ADC_en)char <= {{12{8'h20}},4'h0,bcd_code[11:8],8'd92,4'h0,bcd_code[7:4],4'h0,bcd_code[3:0]};
										else char <= "                ";
										state <= SCAN; end
							5'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end

							default: state <= IDLE;
						endcase
					end
				INIT:begin	//初始化状态
						case(cnt_init)
							5'd0:	begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end	//复位有效
							5'd1:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于3us
							5'd2:	begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end	//复位恢复
							5'd3:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于220us
							5'd4:	begin 
										if(cnt>=INIT_DEPTH) begin	//当25条指令及数据发出后,配置完成
											cnt <= 1'b0;
											cnt_init <= cnt_init + 1'b1;
										end else begin	
											cnt <= cnt + 1'b1; num_delay <= 16'd5;
											oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
										end
									end
							5'd5:	begin cnt_init <= 1'b0; state <= MAIN; end	//初始化完成,返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				SCAN:begin	//刷屏状态,从RAM中读取数据刷屏

							if(cnt_scan == 5'd11) begin
								if(num) cnt_scan <= 5'd3;
								else cnt_scan <= cnt_scan + 1'b1;
							end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
							else cnt_scan <= cnt_scan + 1'b1;
							case(cnt_scan)
								5'd 0:	begin oled_dcn <= CMD; char_reg <= y_p;  state <= WRITE; state_back <= SCAN; end		//定位列页地址
								5'd 1:	begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end	//定位行地址低位
								5'd 2:	begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end	//定位行地址高位
 
								5'd 3:	begin num <= num - 1'b1;end
								5'd 4:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
								5'd 5:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
								5'd 6:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
								5'd 7:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
								5'd 8:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
								5'd 9:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
								5'd10:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
								5'd11:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
								5'd12:	begin state <= MAIN; end
								default: state <= IDLE;
							endcase

					end
				WRITE:begin	//WRITE状态,将数据按照SPI时序发送给屏幕
						if(cnt_write >= 5'd17) cnt_write <= 1'b0;
						else cnt_write <= cnt_write + 1'b1;
						case(cnt_write)
							5'd 0:	begin oled_csn <= LOW; end	//9位数据最高位为命令数据控制位
							5'd 1:	begin oled_clk <= LOW; oled_dat <= char_reg[7]; end	//先发高位数据
							5'd 2:	begin oled_clk <= HIGH; end
							5'd 3:	begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
							5'd 4:	begin oled_clk <= HIGH; end
							5'd 5:	begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
							5'd 6:	begin oled_clk <= HIGH; end
							5'd 7:	begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
							5'd 8:	begin oled_clk <= HIGH; end
							5'd 9:	begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
							5'd10:	begin oled_clk <= HIGH; end
							5'd11:	begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
							5'd12:	begin oled_clk <= HIGH; end
							5'd13:	begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
							5'd14:	begin oled_clk <= HIGH; end
							5'd15:	begin oled_clk <= LOW; oled_dat <= char_reg[0]; end	//后发低位数据
							5'd16:	begin oled_clk <= HIGH; end
							5'd17:	begin oled_csn <= HIGH; state <= DELAY; end	//
							default: state <= IDLE;
						endcase
					end
				DELAY:begin	//延时状态
						if(cnt_delay >= num_delay) begin
							cnt_delay <= 16'd0; state <= state_back; 
						end else cnt_delay <= cnt_delay + 1'b1;
					end
				default:state <= IDLE;
			endcase
		end
	end
 
	//OLED配置指令数据
	always@(posedge rst_n_in)
		begin
			cmd[ 0] = {8'hae}; 
			cmd[ 1] = {8'h00}; 
			cmd[ 2] = {8'h10}; 
			cmd[ 3] = {8'h00}; 
			cmd[ 4] = {8'hb0}; 
			cmd[ 5] = {8'h81}; 
			cmd[ 6] = {8'hff}; 
			cmd[ 7] = {8'ha1}; 
			cmd[ 8] = {8'ha6}; 
			cmd[ 9] = {8'ha8}; 
			cmd[10] = {8'h1f}; 
			cmd[11] = {8'hc8};
			cmd[12] = {8'hd3};
			cmd[13] = {8'h00};
			cmd[14] = {8'hd5};
			cmd[15] = {8'h80};
			cmd[16] = {8'hd9};
			cmd[17] = {8'h1f};
			cmd[18] = {8'hda};
			cmd[19] = {8'h00};
			cmd[20] = {8'hdb};
			cmd[21] = {8'h40};
			cmd[22] = {8'h8d};
			cmd[23] = {8'h14};
			cmd[24] = {8'haf};
		end 
 
	//5*8点阵字库数据
	always@(posedge rst_n_in)
		begin
			mem[  0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E};   // 48  0
			mem[  1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00};   // 49  1
			mem[  2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46};   // 50  2
			mem[  3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31};   // 51  3
			mem[  4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10};   // 52  4
			mem[  5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39};   // 53  5
			mem[  6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30};   // 54  6
			mem[  7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03};   // 55  7
			mem[  8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36};   // 56  8
			mem[  9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E};   // 57  9
			mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C};   // 65  A
			mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36};   // 66  B
			mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22};   // 67  C
			mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C};   // 68  D
			mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41};   // 69  E
			mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01};   // 70  F

 
			mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00};   // 32  sp 
			mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00};   // 33  !  
			mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00};   // 34  
			mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14};   // 35  #
			mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12};   // 36  $
			mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23};   // 37  %
			mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50};   // 38  &
			mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00};   // 39  '
			mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00};   // 40  (
			mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00};   // 41  )
			mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14};   // 42  *
			mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08};   // 43  +
			mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00};   // 44  ,
			mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08};   // 45  -
			mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00};   // 46  .
			mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02};   // 47  /
			mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E};   // 48  0
			mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00};   // 49  1
			mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46};   // 50  2
			mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31};   // 51  3
			mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10};   // 52  4
			mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39};   // 53  5
			mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30};   // 54  6
			mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03};   // 55  7
			mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36};   // 56  8
			mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E};   // 57  9
			mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00};   // 58  :
			mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00};   // 59  ;
			mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00};   // 60  <
			mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14};   // 61  =
			mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08};   // 62  >
			mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06};   // 63  ?
			mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E};   // 64  @
			mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C};   // 65  A
			mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36};   // 66  B
			mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22};   // 67  C
			mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C};   // 68  D
			mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41};   // 69  E
			mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01};   // 70  F
			mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A};   // 71  G
			mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F};   // 72  H
			mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00};   // 73  I
			mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01};   // 74  J
			mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41};   // 75  K
			mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40};   // 76  L
			mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F};   // 77  M
			mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F};   // 78  N
			mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E};   // 79  O
			mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06};   // 80  P
			mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E};   // 81  Q
			mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46};   // 82  R
			mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31};   // 83  S
			mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01};   // 84  T
			mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F};   // 85  U
			mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F};   // 86  V
			mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F};   // 87  W
			mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63};   // 88  X
			mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07};   // 89  Y
			mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43};   // 90  Z
			mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00};   // 91  [
			mem[ 92] = {8'h00, 8'h00, 8'hC0, 8'hC0, 8'h00};   // 92  .
			mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00};   // 93  ]
			mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04};   // 94  ^
			mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40};   // 95  _
			mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00};   // 96  '
			mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78};   // 97  a
			mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38};   // 98  b
			mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20};   // 99  c
			mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F};   // 100 d
			mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18};   // 101 e
			mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02};   // 102 f
			mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C};   // 103 g
			mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78};   // 104 h
			mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00};   // 105 i
			mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00};   // 106 j
			mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00};   // 107 k
			mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00};   // 108 l
			mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78};   // 109 m
			mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78};   // 110 n
			mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38};   // 111 o
			mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18};   // 112 p
			mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC};   // 113 q
			mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08};   // 114 r
			mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20};   // 115 s
			mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20};   // 116 t
			mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C};   // 117 u
			mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C};   // 118 v
			mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C};   // 119 w
			mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44};   // 120 x
			mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C};   // 121 y
			mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44};   // 122 z
		end
 

endmodule

 

4.其他设置

顶层模块:

顶层模块是程序逻辑的核心,本次项目中顶层模块例化了PWM模块、oled模块、ADC模块以及防抖动模块。

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: top
// 
// Author: CHC
// 
// Description: top
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------
module top
(
input					clk_in,		//系统时钟
input					rst_n_in,	//系统复位,低有效
input					tone_en,	//蜂鸣器使能信号
input                   ADC_en,     //ADC使能信号
output	wire			piano_out,	//蜂鸣器控制输出
input	wire [2:0]		music_sel,  //音乐选择
output	wire			oled_csn,	//OLCD液晶屏使能
output	wire			oled_rst,	//OLCD液晶屏复位
output	wire			oled_dcn,	//OLCD数据指令控制
output	wire			oled_clk,	//OLCD时钟信号
output	wire			oled_dat,	//OLCD数据信号
output	wire			adc_cs,		//adcSPI总线CS
output	wire			adc_clk,	//adcSPI总线SCK
input	wire			adc_dat,	//adcSPI总线SDA
output  wire [9:0]		dac_data	//dac输出波形信号	
	
);
/*
无源蜂鸣器可以发出不同的音节,与蜂鸣器震动的频率(等于蜂鸣器控制信号的频率)相关,
为了让蜂鸣器控制信号产生不同的频率,我们使用计数器计数(分频)实现,不同的音节控制对应不同的计数终值(分频系数)
计数器根据计数终值计数并分频,产生蜂鸣器控制信号
*/
wire [1:0]              sel_num;
wire 					clk_24mhz;
wire 					adc_done;
wire [11:0]  			bcd_code;
wire [2:0]				music_sel_shake;
wire					rst_n_in_shake;


Beeper u1(
.clk_in					(clk_in),
.rst_n_in				(rst_n_in_shake),
.tone_en				(tone_en),
.piano_out				(piano_out),
.music_sel				(music_sel_shake),
.sel_num				(sel_num)
);


oled u2(
.clk_in					(clk_in),
.rst_n_in				(rst_n_in),
.tone_en				(tone_en),
.ADC_en					(ADC_en),
.oled_csn				(oled_csn),
.oled_rst				(oled_rst),
.oled_dcn				(oled_dcn),
.oled_clk				(oled_clk),
.oled_dat				(oled_dat),
.sel_num				(sel_num),
.bcd_code				(bcd_code)
);


pll u3
(
.CLKI					(clk_in			), //12MHz系统时钟输入
.CLKOP					(clk_24mhz		) //24MHz时钟输出
);


//使用I2C总线驱动PCF8591的ADC功能,例化
ADC u4
(
.clk					(clk_24mhz		),	//系统时钟
.rst_n					(rst_n_in		),	//系统复位,低有效
.adc_cs					(adc_cs			),	//SPI总线CS
.adc_clk				(adc_clk		),	//SPI总线SCK
.adc_dat				(adc_dat		),	//SPI总线SDA
.bcd_code				(bcd_code		)	//ADC采样数据 
);



shake u5
(
.clk_in  				(clk_in			),    
.rst_n_in				(rst_n_in		),//系统复位,低有效
.music_sel  			(music_sel		),//音乐选择
.rst_n_in_shake 		(rst_n_in_shake ),//防抖动音乐选择输出
.music_sel_shake		(music_sel_shake) //防抖动复位输出
);

endmodule

防抖动模块:

项目要求防抖动功能需要延时20ms,晶振频率为12Mhz,因此设置一个计数器,当计数器值等于240000时将信号传递给PWM模块。

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: shake
// 
// Author: CHC
// 
// Description: top
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------

module shake
(
input					clk_in,		//系统时钟
input					rst_n_in,	//系统复位,低有效
input	wire [2:0]		music_sel,  //音乐选择
output  reg				rst_n_in_shake,//防抖动音乐选择输出
output  reg [2:0]		music_sel_shake//防抖动复位输出
);

reg [17:0] 				cnt;//延时计数器

//延时部分
always@(posedge clk_in) begin
	if(rst_n_in==1&music_sel==7) begin
		cnt<=0;
		rst_n_in_shake<=1;
		music_sel_shake<=1;
	end
	else if(!rst_n_in) begin
		cnt<=cnt+1;
		if(cnt>18'd240000)rst_n_in_shake<=0;//延时20ms
	end
	else if(music_sel==3'd6)begin
		cnt<=cnt+1;
		if(cnt>18'd240000)music_sel_shake<=3'd6;
	end
	else if(music_sel==3'd5)begin
		cnt<=cnt+1;
		if(cnt>18'd240000)music_sel_shake<=3'd5;
	end
	else if(music_sel==3'd3)begin
		cnt<=cnt+1;
		if(cnt>18'd240000)music_sel_shake<=3'd3;
	end
	else begin
		cnt<=0;
		rst_n_in_shake<=1;
		music_sel_shake<=1;
	end
end

endmodule

PLL参数设置如下:

FkWdw17nbBO7moCM_hCSoKDTST9n

资源占用如下:

FusWw5j_bc-MnB1Iv44byGR5kmBO

结果展示:

ADC电压表部分:

Fm4WgcYjibAS0kIH4GGpnBpUdbc1

音乐播放部分:

FvfLMKqMJ8mMP05ngoHr3vQo71VS

总结:

本次项目中我学到了很多,通过大量的查资料学习,使我掌握了SPI、PWM、OLED驱动原理等相关知识。最主要的,本次项目提高了我自主学习的能力,因为这次很多问题都是在没有相关人员指导下完成的,Verilog语言格式,diamond的操作方法都是在网上找的教程自学,遇到的大部分问题也是通过查阅芯片手册,查阅电路原理,自己摸索解决的,这让我发自内心地觉得,只要想做,办法总比困难多。

附件下载
wing_impl1.jed
wing.rar
团队介绍
陈宏畅,北京理工大学信息与电子学院本科生
团队成员
陈宏畅
北京理工大学
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号