2021寒假在家一起练项目4——刘子深
利用基于Lattice XO2-4000HC的小脚丫FPGA综合技能训练平台制作的可以实现定时时钟,显示,测温,报警和控制的项目
标签
嵌入式系统
FPGA
数字逻辑
风之歌
更新2021-02-28
1075

项目需求

  • 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
  • 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
  • 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
  • PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
  • 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

实现思路

Fto8G6CWgoqyipQN8YHfZt6YzvQd

Fv1lvQVKy1kDSJxwEz3Ww1DCfIEK

FqlM9rCIPI2Tw8IiYa2gzNvR8CyZ

  • OLED模块

      OLED模块使用的代码是根据硬禾学堂提供的开源代码经过我自己的修改后得到的。重点是学习开源OLED代码中可变部分代码的表达格式和建立起适合自己需求的链接字库,代码比较繁杂,具体可在文后的附件中查看。这里仅显示MAIN部分

MAIN:begin
    if(cnt_main >= 5'd8) cnt_main <= 5'd5;
    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 <= "Time            ";state <= SCAN; end
	5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
        5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "Tem             ";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
	5'd5:	begin y_p <= 8'hb1; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {tim[31:24],tim[23:16],8'd58,tim[15:8],tim[7:0]}; state <= SCAN; end
	5'd6:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= " "; state <= SCAN; end
	5'd7:	begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {tem[31:24],tem[23:16],8'd46,tem[15:8],tem[7:0]}; state <= SCAN; end
	5'd8:	begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= " "; state <= SCAN; end
        default: state <= IDLE;
    endcase
end
  • DS18B20温度采集模块

      这部分也是使用了硬禾学堂提供的开源代码,并参考了杨涛同学案例中的使用方法。

module DS18B20Z
(
	input				clk,			// system clock
	input				rst_n,			// system reset, active low
	inout				one_wire,		// ds18b20z one-wire-bus
	output	reg	[15:0]	data_out		// ds18b20z data_out
);
	
	localparam	IDLE	=	3'd0;
	localparam	MAIN	=	3'd1;
	localparam	INIT	=	3'd2;
	localparam	WRITE	=	3'd3;
	localparam	READ	=	3'd4;
	localparam	DELAY	=	3'd5;
	
	//generate clk_1mhz clock
	reg					clk_1mhz;
	reg		[2:0]		cnt_1mhz;
	always@(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			cnt_1mhz <= 3'd0;
			clk_1mhz <= 1'b0;
		end else if(cnt_1mhz >= 3'd5) begin
			cnt_1mhz <= 3'd0;
			clk_1mhz <= ~clk_1mhz;
		end else begin
			cnt_1mhz <= cnt_1mhz + 1'b1;
		end
	end
	
	reg				one_wire_buffer;
	reg		[3:0]		cnt_main;
	reg		[7:0]		data_wr;
	reg		[7:0]		data_wr_buffer;
	reg		[2:0]		cnt_init;
	reg		[19:0]		cnt_delay;
	reg		[19:0]		num_delay;
	reg		[5:0]		cnt_write;
	reg		[5:0]		cnt_read;
	reg		[15:0]		temperature;
	reg		[7:0]		temperature_buffer;
	reg		[2:0] 		state = IDLE;
	reg		[2:0] 		state_back = IDLE;
	always@(posedge clk_1mhz or negedge rst_n) begin
		if(!rst_n) begin
			state <= IDLE;
			state_back <= IDLE;
			cnt_main <= 4'd0;
			cnt_init <= 3'd0;
			cnt_write <= 6'd0;
			cnt_read <= 6'd0;
			cnt_delay <= 20'd0;
			one_wire_buffer <= 1'bz;
			temperature <= 16'h0;
		end else begin
			case(state)
				IDLE:begin
						state <= MAIN;
						state_back <= MAIN;
						cnt_main <= 4'd0;
						cnt_init <= 3'd0;
						cnt_write <= 6'd0;
						cnt_read <= 6'd0;
						cnt_delay <= 20'd0;
						one_wire_buffer <= 1'bz;
					end
				MAIN:begin
						if(cnt_main >= 4'd11) cnt_main <= 1'b0;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)
							4'd0: begin state <= INIT; end
							4'd1: begin data_wr <= 8'hcc;state <= WRITE; end
							4'd2: begin data_wr <= 8'h44;state <= WRITE; end
							4'd3: begin num_delay <= 20'd750000;state <= DELAY;state_back <= MAIN; end
							
							4'd4: begin state <= INIT; end
							4'd5: begin data_wr <= 8'hcc;state <= WRITE; end
							4'd6: begin data_wr <= 8'hbe;state <= WRITE; end
							
							4'd7: begin state <= READ; end
							4'd8: begin temperature[7:0] <= temperature_buffer; end
							
							4'd9: begin state <= READ; end
							4'd10: begin temperature[15:8] <= temperature_buffer; end
							
							4'd11: begin state <= IDLE;data_out <= temperature; end
							default: state <= IDLE;
						endcase
					end
				INIT:begin
						if(cnt_init >= 3'd6) cnt_init <= 1'b0;
						else cnt_init <= cnt_init + 1'b1;
						case(cnt_init)
							3'd0: begin one_wire_buffer <= 1'b0; end
							3'd1: begin num_delay <= 20'd500;state <= DELAY;state_back <= INIT; end
							3'd2: begin one_wire_buffer <= 1'bz; end
							3'd3: begin num_delay <= 20'd100;state <= DELAY;state_back <= INIT; end
							3'd4: begin if(one_wire) state <= IDLE; else state <= INIT; end
							3'd5: begin num_delay <= 20'd400;state <= DELAY;state_back <= INIT; end
							3'd6: begin state <= MAIN; end
							default: state <= IDLE;
						endcase
					end
				WRITE:begin
						if(cnt_write >= 6'd50) cnt_write <= 1'b0;
						else cnt_write <= cnt_write + 1'b1;
						case(cnt_write)
							//lock data_wr
							6'd0: begin data_wr_buffer <= data_wr; end
							//write bit 0
							6'd1: begin one_wire_buffer <= 1'b0; end
							6'd2: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd3: begin one_wire_buffer <= data_wr_buffer[0]; end
							6'd4: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd5: begin one_wire_buffer <= 1'bz; end
							6'd6: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 1
							6'd7: begin one_wire_buffer <= 1'b0; end
							6'd8: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd9: begin one_wire_buffer <= data_wr_buffer[1]; end
							6'd10: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd11: begin one_wire_buffer <= 1'bz; end
							6'd12: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 2
							6'd13: begin one_wire_buffer <= 1'b0; end
							6'd14: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd15: begin one_wire_buffer <= data_wr_buffer[2]; end
							6'd16: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd17: begin one_wire_buffer <= 1'bz; end
							6'd18: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 3
							6'd19: begin one_wire_buffer <= 1'b0; end
							6'd20: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd21: begin one_wire_buffer <= data_wr_buffer[3]; end
							6'd22: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd23: begin one_wire_buffer <= 1'bz; end
							6'd24: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 4
							6'd25: begin one_wire_buffer <= 1'b0; end
							6'd26: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd27: begin one_wire_buffer <= data_wr_buffer[4]; end
							6'd28: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd29: begin one_wire_buffer <= 1'bz; end
							6'd30: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 5
							6'd31: begin one_wire_buffer <= 1'b0; end
							6'd32: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd33: begin one_wire_buffer <= data_wr_buffer[5]; end
							6'd34: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd35: begin one_wire_buffer <= 1'bz; end
							6'd36: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 6
							6'd37: begin one_wire_buffer <= 1'b0; end
							6'd38: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd39: begin one_wire_buffer <= data_wr_buffer[6]; end
							6'd40: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd41: begin one_wire_buffer <= 1'bz; end
							6'd42: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//write bit 7
							6'd43: begin one_wire_buffer <= 1'b0; end
							6'd44: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							6'd45: begin one_wire_buffer <= data_wr_buffer[7]; end
							6'd46: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd47: begin one_wire_buffer <= 1'bz; end
							6'd48: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
							//back to main
							6'd49: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
							6'd50: begin state <= MAIN; end
							default: state <= IDLE;
						endcase
					end
				READ:begin
						if(cnt_read >= 6'd48) cnt_read <= 1'b0;
						else cnt_read <= cnt_read + 1'b1;
						case(cnt_read)
							//read bit 0
							6'd0: begin one_wire_buffer <= 1'b0; end
							6'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd2: begin one_wire_buffer <= 1'bz; end
							6'd3: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd4: begin temperature_buffer[0] <= one_wire; end
							6'd5: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 1
							6'd6: begin one_wire_buffer <= 1'b0; end
							6'd7: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd8: begin one_wire_buffer <= 1'bz; end
							6'd9: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd10: begin temperature_buffer[1] <= one_wire; end
							6'd11: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 2
							6'd12: begin one_wire_buffer <= 1'b0; end
							6'd13: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd14: begin one_wire_buffer <= 1'bz; end
							6'd15: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd16: begin temperature_buffer[2] <= one_wire; end
							6'd17: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 3
							6'd18: begin one_wire_buffer <= 1'b0; end
							6'd19: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd20: begin one_wire_buffer <= 1'bz; end
							6'd21: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd22: begin temperature_buffer[3] <= one_wire; end
							6'd23: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 4
							6'd24: begin one_wire_buffer <= 1'b0; end
							6'd25: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd26: begin one_wire_buffer <= 1'bz; end
							6'd27: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd28: begin temperature_buffer[4] <= one_wire; end
							6'd29: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 5
							6'd30: begin one_wire_buffer <= 1'b0; end
							6'd31: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd32: begin one_wire_buffer <= 1'bz; end
							6'd33: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd34: begin temperature_buffer[5] <= one_wire; end
							6'd35: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 6
							6'd36: begin one_wire_buffer <= 1'b0; end
							6'd37: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd38: begin one_wire_buffer <= 1'bz; end
							6'd39: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd40: begin temperature_buffer[6] <= one_wire; end
							6'd41: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//read bit 7
							6'd42: begin one_wire_buffer <= 1'b0; end
							6'd43: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
							6'd44: begin one_wire_buffer <= 1'bz; end
							6'd45: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
							6'd46: begin temperature_buffer[7] <= one_wire; end
							6'd47: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
							//back to main
							6'd48: begin state <= MAIN; end
							default: state <= IDLE;
						endcase
					end
				DELAY:begin
						if(cnt_delay >= num_delay) begin
							cnt_delay <= 1'b0;
							state <= state_back; 
						end else cnt_delay <= cnt_delay + 1'b1;
					end
			endcase
		end
	end
	
	assign	one_wire = one_wire_buffer;
	
endmodule
  •    PWM蜂鸣器控制模块

     PWM控制信号主要通过不同音调对应的频率值对应脉冲宽度cycle产生,cycle由中心处理判断结构查找音符switch表进行赋值。控制程序参考了杨涛同学的案例方法。

reg	[WIDTH-1:0]	cnt;
//counter for cycle
always @(posedge clk or negedge rst_n)
	if(!rst_n) cnt <= 1'b1;
	else if(cnt >= cycle) cnt <= 1'b1;
	else cnt <= cnt + 1'b1;

//pulse with duty
always @(posedge clk or negedge rst_n)
	if(!rst_n) pwm_out <= 1'b1;
	else if(cnt < duty) pwm_out <= 1'b1;
	else pwm_out <= 1'b0;
always @(posedge clk or negedge rst_n)begin       //************************蜂鸣器**************************//
		if(!rst_n) begin
			note <= 8'd0;
			pwm_finish_flag <= 1'b0;
		end else begin
			if(pwm_flag)begin
				case(cnt_pwm[28:24])
					5'd0: note <= 8'hA1;	//L1,
					5'd1: note <= 8'hA2;	//L2,
					5'd2: note <= 8'hA3;	//L3,
					5'd3: note <= 8'hB1;	//L4,
					5'd4: note <= 8'hB2;	//L5,
					5'd5: note <= 8'hB3;	//L6,
					5'd6: note <= 8'hC1;	//L7,
					5'd7: note <= 8'hC2;	//M1,
					5'd8: note <= 8'hC3;	//M2,
					5'd9: note <= 8'hA1;	//L1,
					5'd10: note <= 8'hA2;	//L2,
					5'd11: note <= 8'hC1;	//L3,
					5'd12: note <= 8'hA3;	//L4,
					5'd13: note <= 8'hB1;	//L5,
					5'd14: note <= 8'hB2;	//L6,
					5'd15: note <= 8'hB3;	//L7,
					5'd16: note <= 8'hC7;	//M1,
					5'd17: note <= 8'hC1;	//M2,
					default: begin note <= 8'h0;pwm_finish_flag <= 1'b1;	end	//cycle为0,PWM占空比为0,低电平
				endcase
			end else if(tone_flag)begin
				case(cnt_tone[26:22])
					5'd0:note <= tone[255:248] ;
					5'd1:note <= tone[247:240] ;
					5'd2:note <= tone[239:232] ;
					5'd3:note <= tone[231:224] ;
					5'd4:note <= tone[223:216] ;
					5'd5:note <= tone[215:208] ;
					5'd6:note <= tone[207:200] ;
					5'd7:note <= tone[199:192] ;
					5'd8:note <= tone[191:184] ;
					5'd9:note <= tone[183:176] ;
					5'd10:note <= tone[175:168] ;
					5'd11:note <= tone[167:160] ;
					5'd12:note <= tone[159:152] ;
					5'd13:note <= tone[151:144] ;
					5'd14:note <= tone[143:136] ;
					5'd15:note <= tone[135:128] ;					
					5'd16:note <= tone[127:120] ;
					5'd17:note <= tone[119:112] ;
					5'd18:note <= tone[111:104] ;
					5'd19:note <= tone[103:96] ;
					5'd20:note <= tone[95:88] ;
					5'd21:note <= tone[87:80] ;
					5'd22:note <= tone[79:72] ;
					5'd23:note <= tone[71:64] ;
					5'd24:note <= tone[63:56] ;
					5'd25:note <= tone[55:48] ;
					5'd26:note <= tone[47:40] ;
					5'd27:note <= tone[39:32] ;
					5'd28:note <= tone[31:24] ;
					5'd29:note <= tone[23:16] ;
					5'd30:note <= tone[15:8] ;
					5'd31:note <= 8'hFF ;
					default:  note <= 8'h0;		//cycle为0,PWM占空比为0,低电平
				endcase				
			end else begin
				note <= 8'h0;
			end
		end		
	end
	
	

	always @(posedge clk or negedge rst_n)begin       //***********************音符频率对应表***************************//
		if(!rst_n) begin
			cycle <= 8'b0;
			tone_finish_flag <= 1'b0;
		end else begin
			case(note)
				8'hA1: cycle <= 16'd45872;	//L1,
				8'hA2: cycle <= 16'd40858;	//L2,
				8'hA3: cycle <= 16'd36408;	//L3,
				8'hA4: cycle <= 16'd34364;	//L4,
				8'hA5: cycle <= 16'd30612;	//L5,
				8'hA6: cycle <= 16'd27273;	//L6,
				8'hA7: cycle <= 16'd24296;	//L7,
				8'hB1: cycle <= 16'd22931;	//M1,
				8'hB2: cycle <= 16'd20432;	//M2,
				8'hB3: cycle <= 16'd18201;	//M3,
				8'hB4: cycle <= 16'd17180;	//M4,
				8'hB5: cycle <= 16'd15306;	//M5,
				8'hB6: cycle <= 16'd13636;	//M6,
				8'hB7: cycle <= 16'd12148;	//M7,
				8'hC1: cycle <= 16'd11478;	//H1,
				8'hC2: cycle <= 16'd10215;	//H2,
				8'hC3: cycle <= 16'd9140;	//H3,
				8'hC4: cycle <= 16'd8598;	//H4,
				8'hC5: cycle <= 16'd7653;	//H5,
				8'hC6: cycle <= 16'd6818;	//H6,
				8'hC7: cycle <= 16'd6072;	//H7,
				8'hD1: cycle <= 16'd5730;	//V1,
				8'hD2: cycle <= 16'd5106;	//V2,
				8'hD3: cycle <= 16'd4548;	//V3,
				8'hD4: cycle <= 16'd4294;	//V4,
				8'hD5: cycle <= 16'd3826;	//V5,
				8'hD6: cycle <= 16'd3409;	//V6,
				8'hD7: cycle <= 16'd3036;	//V7,
				8'hFF: begin cycle <=16'd0; tone_finish_flag <= 1'b1; end
				default:cycle = 16'd0;
			endcase
		end
	end
	
	
endmodule

      蜂鸣器控制程序中主题if-else if语句中第一部分是自己制定的针对于整点报警情况的30秒左右的报警音频;第二部分是针对于串行传输发送给板子的音符数据对应的接收方法。控制程序的重点是通过状态变量确保每一部分有序运行。

  •  串行传输发送模块

      主要通过串行传输发送使电脑端的显示界面一秒钟更新一次时间和温度信息,因此在串行发送模块最重要的是分频和串行传输帧格式的体现,这里采用了9600Baud的串行传输方式。发送格式参考了叶开同学的方法,使得能够以在显示界面字符串的形式清晰显示温度时间信息。

//驱动发送数据操作
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		bps_en <= 1'b0;
		tx_data_r <= 10'd0;
	end else if(tx_data_valid && (!bps_en))begin	
		bps_en <= 1'b1;		//当检测到接收时钟使能信号的下降沿,表明接收完成,需要发送数据,使能发送时钟使能信号
		tx_data_r <= {1'b1,tx_data_in,1'b0};	
	end else if(num==4'd10) begin	
		bps_en <= 1'b0;		//一次UART发送需要10个时钟信号,然后结束
	end
end

//当处于工作状态中时,按照发送时钟的节拍发送数据
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		num <= 1'b0;
		uart_tx <= 1'b1;
	end else if(bps_en) begin
		if(bps_clk) begin
			num <= num + 1'b1;
			uart_tx <= tx_data_r[num];
		end else if(num>=4'd10) begin
			num <= 4'd0;	
		end
	end
end
module Baud #
(
parameter				BPS_PARA = 1250 //12MHz时钟时参数1250对应9600的波特率
)
(
input					clk,		//系统时钟
input					rst_n,		//系统复位,低有效
input					bps_en,		//接收或发送时钟使能
output	reg				bps_clk		//接收或发送时钟输出
);	

reg				[12:0]	cnt;
//计数器计数满足波特率时钟要求
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) 
		cnt <= 1'b0;
	else if((cnt >= BPS_PARA-1)||(!bps_en)) //当时钟信号不使能(bps_en为低电平)时,计数器清零并停止计数
		cnt <= 1'b0;	                    //当时钟信号使能时,计数器对系统时钟计数,周期为BPS_PARA个系统时钟周期
	else 
		cnt <= cnt + 1'b1;
end
	
//产生相应波特率的时钟节拍,接收模块将以此节拍进行UART数据接收
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) 
		bps_clk <= 1'b0;
	else if(cnt == (BPS_PARA>>1)) //右移一位等于除以2,终值BPS_PARA为数据更替点,中值数据稳定,做采样点
		bps_clk <= 1'b1;	
	else 
		bps_clk <= 1'b0;
end

endmodule
reg [23:0] cnt_uart;
	always @(posedge clk or negedge rst_n)begin //计时一秒
		if(!rst_n) cnt_uart <= 1'b0;
		else if(cnt_uart >= 24'd11_999_999) cnt_uart <= 1'b0;
		else if (temp_tx_flag) cnt_uart <= cnt_uart + 1'b1;
		else cnt_uart <= cnt_uart;
	end
	
	
	
	always @(posedge clk or negedge rst_n)begin      //*********************每秒发一个温度时间数据,字符串显示***********************//
		if(!rst_n) begin 
			tx_data_valid <= 1'b0; 
			tx_data_out <= 1'b0; 
		end else begin
			if(temp_tx_flag)begin       //发送温度数据
				if(cnt_uart == 24'd10_599_999 ) begin //温度.高位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,temp_in[3:0]};			 
				end 
				else if(cnt_uart == 24'd11_999_999 ) begin //换行
					tx_data_valid <= 1'b1; 
					tx_data_out <= 8'd10;
				end
				else if(cnt_uart == 24'd11_099_999 ) begin //温度.低位 
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,4'b0};
				end 
				else if(cnt_uart == 24'd10_099_999 ) begin //“.”
					tx_data_valid <= 1'b1; 
					tx_data_out <= 8'd46;
				end 
				else if(cnt_uart == 24'd9_599_999 ) begin //温度低位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,temp_in[7:4]};
				end 
				else if(cnt_uart == 24'd9_099_999 ) begin //温度高位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,temp_in[11:8]};
				end 
				else if(cnt_uart == 24'd11_599_999 ) begin //"C"
					tx_data_valid <= 1'b1; 
					tx_data_out <= 8'd67;
				end 
				else if(cnt_uart == 24'd8_599_999 ) begin //“ ”
					tx_data_valid <= 1'b1; 
					tx_data_out <= 8'd32;
				end 
				else if(cnt_uart == 24'd8_099_999 ) begin //分低位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,time_buffer[3:0]};
				end
				else if(cnt_uart == 24'd7_599_999 ) begin //分高位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,time_buffer[7:4]};
				end
				else if(cnt_uart == 24'd7_099_999 ) begin //“:”
					tx_data_valid <= 1'b1; 
					tx_data_out <= 8'd58;
				end
				else if(cnt_uart == 24'd6_599_999 ) begin //时低位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,time_buffer[11:8]};
				end
				else if(cnt_uart == 24'd6_099_999 ) begin //时高位
					tx_data_valid <= 1'b1; 
					tx_data_out <= {4'd3,time_buffer[15:12]};
				end
				else begin 
					tx_data_valid <= 1'b0; tx_data_out <= tx_data_out;
				end
			end else begin
				tx_data_valid <= 1'b0; tx_data_out <= tx_data_out;
			end
		end
	end
  • 时钟模块

      时钟模块包含按键设置功能与时钟相对应和自动计时两方面,我主要参考了硬禾学堂的开源代码时钟分频和杨涛同学案例中的使用方法。

reg [29:0] cnt_1m;
	always @(posedge clk or negedge rst_n)begin //计时一分钟
		if(!rst_n) cnt_1m <= 1'b0;
		else if(cnt_1m >= 30'd719_999_999) cnt_1m <= 1'b0;
		else cnt_1m <= cnt_1m + 1'b1;
	end
	
		
	reg time_1plus_flag;
	reg time_60plus_flag;
	reg time_plus_full;	
	always@(posedge clk or negedge rst_n) begin       //*********************按键控制**************************//
		if(!rst_n)begin
			time_1plus_flag <= 1'b0;
			time_60plus_flag <= 1'b0;
			time_buffer_flag <= 1'b0;
			tone_flag <= 1'b0;
		end else begin	
			if(time_buffer_flag ==1'b0)begin           //停止计时并按键设置时间
				case(key)
					3'b100:time_60plus_flag <= 1'b1;							//按键4增加时
					3'b010:time_1plus_flag <= 1'b1;								//按键3增加分
					3'b001:time_buffer_flag <= 1'b1;							//按键2开始计时
					default:begin time_1plus_flag <= 1'b0;time_60plus_flag <= 1'b0;  end
				endcase
				if(tone_finish_flag == 1'b1) begin
					tone_flag <= 1'b0;       //停止演奏
					time_buffer_flag <= 1'b1;//开始计时
				end
			end else if(tone_flag == 1'b0 )begin                              //计时
				if(cnt_1m >= 30'd719_999_999)begin				
					time_1plus_flag <= 1'b1;
				end else begin      
					time_1plus_flag <= 1'b0;
					time_60plus_flag <= 1'b0;
				end	
				
				if(key == 3'b001 && tone_set_flag == 1'b1 ) begin
					tone_flag <= 1'b1;	       //开始演奏
					time_buffer_flag <= 1'b0; //停止计时
				end else begin
					tone_flag <= tone_flag ;
					time_buffer_flag <= time_buffer_flag ;
				end	
				
			end else begin
				time_1plus_flag <= 1'b0;
				time_60plus_flag <= 1'b0;	
			end
				
		end
	end
	
	
	
	reg [15:0] time_buffer;
	always@(posedge clk or negedge rst_n) begin       //**********************time+1*******************//
		if(!rst_n)begin			
			time_buffer[7:0] <= 8'b0;
		end else begin
			if(time_1plus_flag)begin
				if(time_buffer[3:0] >= 4'd9 && time_buffer[7:4] < 4'd5) time_buffer[7:0] <= time_buffer[7:0] + 4'd7;
				else if(time_buffer[3:0] >= 4'd9 && time_buffer[7:4] == 4'd5) begin
					time_buffer[7:0] <= 8'b0;
					time_plus_full <= 1'b1;				
				end else begin
					time_buffer[7:0] <= time_buffer[7:0] + 1'b1;
				end
			end else begin
				time_buffer[7:0] <= time_buffer[7:0];
				time_plus_full <= 1'b0;
			end
		end
	end
	
	
	always@(posedge clk or negedge rst_n) begin       //***********************time+60***************************//
		if(!rst_n)begin			
			time_buffer[15:8] <= 8'b0;
		end else begin
			if(time_60plus_flag||time_plus_full)begin
				if(time_buffer[11:8] >= 4'd9 && time_buffer[15:12] < 4'd2) time_buffer[15:8] <= time_buffer[15:8] + 4'd7;
				else if(time_buffer[11:8] >= 4'd3 && time_buffer[15:12] == 4'd2) time_buffer[15:8] <= 8'b0;
				else time_buffer[15:8] <= time_buffer[15:8] + 1'b1;
			end else begin
				time_buffer[15:8] <= time_buffer[15:8];
			end
		end
	end
  • 串口接收模块

      依然是利用了硬禾学堂的开源代码,主要特点与发送类似,不再赘述。

reg	uart_rx0,uart_rx1,uart_rx2;	
//多级延时锁存去除亚稳态
always @ (posedge clk) begin
	uart_rx0 <= uart_rx;
	uart_rx1 <= uart_rx0;
	uart_rx2 <= uart_rx1;
end

//检测UART接收输入信号的下降沿
wire	neg_uart_rx = uart_rx2 & ~uart_rx1;	
		
reg				[3:0]	num;			
//接收时钟使能信号的控制
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n)
		bps_en <= 1'b0;
	else if(neg_uart_rx && (!bps_en))	//当空闲状态(bps_en为低电平)时检测到UART接收信号下降沿,进入工作状态(bps_en为高电平),控制时钟模块产生接收时钟
		bps_en <= 1'b1;		
	else if(num==4'd9)		            //当完成一次UART接收操作后,退出工作状态,恢复空闲状态
		bps_en <= 1'b0;			
end

reg				[7:0]	rx_data;
//当处于工作状态中时,按照接收时钟的节拍获取数据
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		num <= 4'd0;
		rx_data <= 8'd0;
	end else if(bps_en) begin	
		if(bps_clk) begin			
			num <= num + 1'b1;
			if(num<=4'd8) rx_data[num-1] <= uart_rx1; //先接受低位再接收高位,8位有效数据
		end else if(num == 4'd9) begin		          //完成一次UART接收操作后,将获取的数据输出
			num <= 4'd0;				
		end
	end else begin
		num <= 4'd0;
	end
end

//将接收的数据输出,同时控制输出有效信号产生脉冲
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		rx_data_out <= 8'd0;
		rx_data_valid <= 1'b0;
	end else if(num == 4'd9) begin	
		rx_data_out <= rx_data;
		rx_data_valid <= 1'b1;
	end else begin
		rx_data_out <= rx_data_out;
		rx_data_valid <= 1'b0;
	end
end
  • 顶层top模块

      这次实验给我最大的帮助之一就是让我认识到顶层top模块的设计和应用,这种编写方式就像C++语言中的头文件和函数一样,大大增强了代码的可移植性,使得很多开源的代码能够直接应用到一个很大的项目中去。

      top模块的编写格式参考了杨涛同学的案例中的例化方法。

module top
(
	input				clk,			// system clock
	input				rst_n,			// system reset, active low
	input	    [3:0]	sw,	
	input		[2:0]	key,
	inout         		one_wire,
	
	input				fpga_rx,	//UART接收输入
	output				fpga_tx,	//UART发送输出
	
	output				oled_csn,	//OLCD液晶屏使能
	output				oled_rst,	//OLCD液晶屏复位
	output				oled_dcn,	//OLCD数据指令控制
	output				oled_clk,	//OLCD时钟信号
	output				oled_dat,	//OLCD数据信号
	
	output				beeper
);



wire	[15:0]	data_out;
//Drive DS18B20Z to get temperature code
DS18B20Z DS18B20Z_uut
(
.clk					(clk			),	// system clock
.rst_n					(rst_n			),	// system reset, active low
.one_wire				(one_wire		),	// ds18b20z one-wire-bus
.data_out				(data_out		)	// ds18b20z data_out
);


// judge sign of temperature
wire temperature_flag = data_out[15:11]? 1'b0:1'b1;
// complement if negative
wire [10:0] temperature_code = temperature_flag? data_out[10:0]:(~data_out[10:0])+1'b1; 
// translate temperature_code to real temperature
wire [20:0] bin_code = temperature_code * 16'd625;
wire [24:0] bcd_code; //十位[23:20],个位[19:16],小数位[14:12]
//Translate binary code to bcd code
bin_to_bcd bin_to_bcd_uut
(
.rst_n					(rst_n			),	// system reset, active low
.bin_code				(bin_code		),	// binary code
.bcd_code				(bcd_code		)	// bcd code
);


wire [31:0] time_oled;
wire [31:0] temp_oled;
OLED12832 OLED12832_uut
(
.clk					(clk			),	// system clock
.rst_n					(rst_n			),	// system reset, active low
.tim			    	(time_oled		),	// ds18b20z one-wire-bus
.tem    				(temp_oled		),	// ds18b20z data_out
.oled_csn				(oled_csn		),
.oled_rst				(oled_rst		),
.oled_dcn				(oled_dcn		),
.oled_clk				(oled_clk		),
.oled_dat				(oled_dat		)

);



wire rx_data_valid;
wire [7:0]	rx_data;
wire tx_data_valid;
wire [7:0]	tx_data;
//Uart_Bus module
Uart_Bus u1
(	
.clk					(clk			),	//系统时钟 12MHz
.rst_n					(rst_n			),	//系统复位,低有效
//负责FPGA接收UART芯片的数据
.uart_rx				(fpga_rx		),	//UART接收输入
.rx_data_valid			(rx_data_valid	),	//接收数据有效脉冲
.rx_data_out			(rx_data		),	//接收到的数据     8位
//负责FPGA发送数据给UART芯片
.tx_data_valid			(tx_data_valid	),
.tx_data_in				(tx_data		),
.uart_tx				(fpga_tx		)
);


wire [15:0] cycle;
//根据不同音节的周期cycle值产生对应的PWM信号
PWM #
(
.WIDTH					(16			)	//ensure that 2**WIDTH > cycle
)
PWM_uut
(
.clk					(clk		),
.rst_n					(rst_n		),
.cycle					(cycle		),	//cycle > duty
.duty					(cycle>>1	),	//duty < cycle
.pwm_out				(beeper	)
);


wire [2:0] key_pulse;
key_debounce #
(
.N						(3			)	
)
key_debounce_uut
(
.clk					(clk		),
.rst					(rst_n		),
.key					(key		),	
.key_pulse				(key_pulse	)	
);



control_module control_module_uut
(
.clk					(clk			),	// system clock
.rst_n					(rst_n			),	// system reset, active low
.sw      				(sw				),	
.key					(key_pulse		),
.temp_in				(bcd_code[23:12]		),	// ds18b20z data_out

.rx_data_valid        	(rx_data_valid	),
.rx_data_in  			(rx_data	 	),

.tx_data_valid        	(tx_data_valid	),
.tx_data_out  			(tx_data	 	),

.time_out				(time_oled		),
.temp_out				(temp_oled		),

.cycle					(cycle			)

);




endmodule

完成的功能

参考项目需求部分,满足了全部需求。

遇到的难题

  • 整个项目比较庞大,一开始设计出来后发现板子执行逻辑顺序混乱,各部分执行先后顺序、因果关系错误。后来在程序各个模块中设置了很多状态变量,从数字电路的角度也可以看成是门控信号,并且配套了很多if-else语句用来调配顺序,解决了问题。
  • 串行传输如果直接按十六进制显示,时间和温度信息没有区分度,而且在界面上是一个接一个格式,参考了叶开同学的代码后,将直接十六进制数的传输改成了ACSll码字符串的传输,实现了符号的表达和换行等功能的使用,使信息更有区分度。
  • 自己设计乐谱时,只能对着相关歌曲的乐谱进行格式改编,10秒左右的音频选自歌曲《大鱼》。

未来计划与建议

  • 汉字字库的实现和在OLED屏上的显示
  • 蜂鸣器发声音质的改变,通过调节使连在一起的音符发声产生间断感。
附件下载
10秒音频16进制数序列.txt
step.zip
团队介绍
北京理工大学信息与电子学院
团队成员
刘子深
刚踏进FPGA大门1/4只脚的菜菜
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号