2021寒假在家一起练项目4小脚丫(杨晓楠)
利用Lattice版本的小脚丫完成基本的定时和音乐播放功能,实现简单的FPGA模块认识与使用。
标签
FPGA
测试
显示
nigam
更新2021-02-27
1105

项目描述及要求:

  基于包含Lattice版本的小脚丫FPGA综合训练板(STEP-MXO2)完成以下基本功能:

1.实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,持续30秒;

2.实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;

3.定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息,要与OLED显示的温度值一致;

4.PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;

5.音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

实现思路:

基本功能模块的关系:

FjOWJ9lFVH3uHMUHgjqljUNVD4V2

Netlist分析电路:

FmYRd7jOQUUCHANa3-8m0WJKJ6DQ

串口调试工具设定页面:

FuG7g61nKtW3ALLjte0i_xNsUjud

完成功能及部分功能模块展示:

  经过对相关代码的编写及调试,完成了项目要求里的内容,实现了定时、报警、显示、音乐播放等功能,下面对功能模块做一定的阐释。

TOP模块:

  top模块是顶层模块,是实现各个功能的控制中心,通过在top模块里调用各个模块功能并进行组合调用,从而实现完整的功能。编程语言为verilog,此语言对于模块的组合调用可以说是十分方便,甚至是精妙,它能够使各种复杂的数字逻辑变得十分清晰甚至有序,不失为数字电路中一种好的编程语言,下面为部分代码(仅展示部分,显示其调用关系,完整的代码见附件):

module top(
	input clk,
	
	input key_hour,//小时增加键
	input key_min_a,//分钟增加键
	input key_min_m,//分钟减少键
	input rst_n,//复位键
	
	input	uart_rxd,				//串口输入
	input   uart_on,
	
	inout tone_en,
	inout one_wire,                 
	
	output				oled_csn,	//OLCD液晶屏使能	
	output				oled_rst,	//OLCD液晶屏复位	
	output				oled_dcn,	//OLCD数据指令控制
	output				oled_clk,	//OLCD时钟信号
	output				oled_dat,	//OLCD数据信号

	
	output 				uart_out,	//串口输出
	output				beeper      //蜂鸣器
);
wire[15:0] 	d_out;
wire[3:0]	temp1,temp2,temp3,temp4;	
wire[3:0]	hour1,hour2,min1,min2,sec1,sec2;
wire		flag;
//温度监测
DS18B20Z DS18B20Z_v1(
	.clk_in(clk),
	.rst_n_in(rst_n),
	.one_wire(one_wire),
	.data_out(d_out)
);
//二进制转十进制

bin_to_code bin_to_code_v1(
	.clk(clk),
	.rst(rst_n),
	.flag(flag),
	.data_in(d_out),
	.d_out1(temp1),
	.d_out2(temp2),
	.d_out3(temp3),
	.d_out4(temp4)
);

Ds18b20模块:

  Ds18b20模块温度传感模块,DS18B20是我们日常设计中常用的一款温度传感器芯片,只需要一根总线就可以实现通信,非常的方便,利用FPGA驱动,需将总线做上拉处理,此总线为双向总线,编程时需了解如何设置双向通信及一系列指令操作。此部分参考了电子森林的代码:

module DS18B20Z
(
	input				clk_in,			//系统时钟
	input				rst_n_in,		//系统复位,低有效
	inout				one_wire,		//DS18B20Z传感器单总线,双向管脚
	output	reg	[15:0]	data_out		//DS18B20Z有效温度数据输出
);
 
	/*
	本设计通过驱动DS18B20Z芯片获取温度数据,
	需要了解inout类型的接口如何实现双向通信,
	中间涉及各种不同的延时和寄存器指令操作,注释部分以作简要说明,更多详情需参考数据手册
	*/
 
	localparam	IDLE	=	3'd0;
	localparam	MAIN	=	3'd1;
	localparam	INIT	=	3'd2;
	localparam	WRITE	=	3'd3;
	localparam	READ	=	3'd4;
	localparam	DELAY	=	3'd5;
 
	//计数器分频产生1MHz的时钟信号
	reg					clk_1mhz;
	reg		[2:0]		cnt_1mhz;
	always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in) 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;	//产生1MHz分频
		end else begin
			cnt_1mhz <= cnt_1mhz + 1'b1;
		end
	end
 
	reg		[2:0]		cnt;
	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		[3:0]		cnt_write;
	reg		[2:0]		cnt_read;
	reg		[15:0]		temperature;
	reg		[7:0]		temperature_buffer;
	reg		[2:0] 		state = IDLE;
	reg		[2:0] 		state_back = IDLE;
	//使用1MHz时钟信号做触发完成下面状态机的功能
	always@(posedge clk_1mhz or negedge rst_n_in) begin
		if(!rst_n_in) begin
			state <= IDLE;
			state_back <= IDLE;
			cnt <= 1'b0;
			cnt_main <= 1'b0;
			cnt_init <= 1'b0;
			cnt_write <= 1'b0;
			cnt_read <= 1'b0;
			cnt_delay <= 1'b0;
			one_wire_buffer <= 1'bz;
			temperature <= 16'h0;
		end else begin
			case(state)
				IDLE:begin		//IDLE状态,程序设计的软复位功能,各状态异常都会跳转到此状态
						state <= MAIN;	//软复位完成,跳转之MAIN状态重新工作
						state_back <= MAIN;
						cnt <= 1'b0;
						cnt_main <= 1'b0;
						cnt_init <= 1'b0;
						cnt_write <= 1'b0;
						cnt_read <= 1'b0;
						cnt_delay <= 1'b0;
						one_wire_buffer <= 1'bz;
					end
				MAIN:begin		//MAIN状态控制状态机在不同状态间跳转,实现完整的温度数据采集
						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	//跳转至INIT状态进行芯片的复位及验证
							4'd1: begin data_wr <= 8'hcc;state <= WRITE; end	//主设备发出跳转ROM指令
							4'd2: begin data_wr <= 8'h44;state <= WRITE; end	//主设备发出温度转换指令
							4'd3: begin num_delay <= 20'd750000;state <= DELAY;state_back <= MAIN; end	//延时750ms等待转换完成
 
							4'd4: begin state <= INIT; end	//跳转至INIT状态进行芯片的复位及验证
							4'd5: begin data_wr <= 8'hcc;state <= WRITE; end	//主设备发出跳转ROM指令
							4'd6: begin data_wr <= 8'hbe;state <= WRITE; end	//主设备发出读取温度指令
 
							4'd7: begin state <= READ; end	//跳转至READ状态进行单总线数据读取
							4'd8: begin temperature[7:0] <= temperature_buffer; end	//先读取的为低8位数据
 
							4'd9: begin state <= READ; end	//跳转至READ状态进行单总线数据读取
							4'd10: begin temperature[15:8] <= temperature_buffer; end	//后读取的为高8为数据
 
							4'd11: begin state <= IDLE;data_out <= temperature; end	//将完整的温度数据输出并重复以上所有操作
							default: state <= IDLE;
						endcase
					end
				INIT:begin		//INIT状态完成DS18B20Z芯片的复位及验证功能
						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	//复位脉冲保持拉低500us时间
							3'd2: begin one_wire_buffer <= 1'bz; end	//单总线复位脉冲释放,自动上拉
							3'd3: begin num_delay <= 20'd100;state <= DELAY;state_back <= INIT; end	//复位脉冲保持释放100us时间
							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	//如果检测正常继续保持释放400us时间
							3'd6: begin state <= MAIN; end	//INIT状态操作完成,返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				WRITE:begin		//按照DS18B20Z芯片单总线时序进行写操作
						if(cnt <= 3'd6) begin	//共需要发送8bit的数据,这里控制循环的次数
							if(cnt_write >= 4'd6) begin cnt_write <= 1'b1; cnt <= cnt + 1'b1; end
							else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_write >= 4'd8) begin cnt_write <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
						end
						//对于WRITE状态中cnt_write来讲,执行过程为:0;[1~6]*8;7;8;
						case(cnt_write)
							//lock data_wr
							4'd0: begin data_wr_buffer <= data_wr; end	//将需要写出的数据缓存
							//发送 1bit 数据的用时在60~120us之间,参考数据手册
							4'd1: begin one_wire_buffer <= 1'b0; end	//总线拉低
							4'd2: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end	//延时2us时间,保证15us以内
							4'd3: begin one_wire_buffer <= data_wr_buffer[cnt]; end	//先发送数据最低位
							4'd4: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end	//延时80us时间
							4'd5: begin one_wire_buffer <= 1'bz; end	//总线释放
							4'd6: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end	//延时2us时间
							//back to main
							4'd7: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end	//延时80us时间
							4'd8: begin state <= MAIN; end	//返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				READ:begin		//按照DS18B20Z芯片单总线时序进行读操作
						if(cnt <= 3'd6) begin	//共需要接收8bit的数据,这里控制循环的次数
							if(cnt_read >= 3'd5) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_read >= 3'd6) begin cnt_read <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end
						case(cnt_read)
							//读取 1bit 数据的用时在60~120us之间,总线拉低后15us时间内读取数据,参考数据手册
							3'd0: begin one_wire_buffer <= 1'b0; end	//总线拉低
							3'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end	//延时2us时间
							3'd2: begin one_wire_buffer <= 1'bz; end	//总线释放
							3'd3: begin num_delay <= 20'd5;state <= DELAY;state_back <= READ; end	//延时5us时间
							3'd4: begin temperature_buffer[cnt] <= one_wire; end	//读取DS18B20Z返回的总线数据,先收最低位
							3'd5: begin num_delay <= 20'd60;state <= DELAY;state_back <= READ; end	//延时60us时间
							//back to main
							3'd6: begin state <= MAIN; end	//返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				DELAY:begin		//延时控制
						if(cnt_delay >= num_delay) begin	//延时控制,延时时间由num_delay指定
							cnt_delay <= 1'b0;
							state <= state_back; 	//很多状态都需要延时,延时后返回哪个状态由state_back指定
						end else cnt_delay <= cnt_delay + 1'b1;
					end
			endcase
		end
	end
 
	assign	one_wire = one_wire_buffer;
 
endmodule

转码模块:

  bin_to_code为转码模块,将温度信息转化为BCD码输出,此部分利用现成的代码,此处不再累赘。

OLED模块:

  oled模块是显示模块,通过驱动芯片SSD1306实现点阵屏幕的显示,编程时应用了状态机的知识,数据通过4线串行总线输入刷新RAM,代码参考了电子森林的部分代码,同时由于显示温度和时间两种信息,输入输出寄存器端口等较复杂,与温度模块和时钟模块的数据输出端联系。

module OLED12832
(
	input				clk,		//12MHz系统时钟
	input				rst_n,		//系统复位,低有效
	input		[3:0]	temp1,temp2,temp3,temp4,
	input		[3:0]	hour1,hour2,min1,min2,sec1,sec2,
	
	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数据信号
);

 
	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;
 
	reg [7:0] cmd [24:0];
	reg [39:0] mem [123:0];
	reg	[7:0]	y_p, x_ph, x_pl;
	reg	[(8*21-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;
 
	always@(posedge clk or negedge rst_n) begin
		if(!rst_n) 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'd6) 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'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TEMP:           ";						 state <= SCAN; end	
								5'd3:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";						 state <= SCAN; end
								5'd4:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";						 state <= SCAN; end	
							
							5'd5:	begin 
									y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 5; char <= {4'd0, hour1, 4'd0,hour2, ":", 4'd0,min1, 4'd0, min2}; state <= SCAN; 									
									end
                            5'd6:if(temp1 == 4'd2)						
									begin 
									y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 6; char <= {"-", 4'd0, temp3, 4'd0, temp4, ".", 4'd0, temp1,8'h7b,"C"}; state <= SCAN; 									
									end
								else if(temp2 == 4'd1)
									begin 
									y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 6; char <= {4'd0, temp2, 4'd0, temp3, 4'd0, temp4, ".", 4'd0, temp1,8'h7b,"C"}; state <= SCAN; 									
									end
								else 
									begin 
									y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 6; char <= {4'd0, temp3, 4'd0, temp4, ".", 4'd0, temp1,8'h7b,"C"}; 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	//5条指令及数据发出后,配置完成
											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	//?*8点阵编程8*8
							5'd 5:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//?*8点阵编程8*8
							5'd 6:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//?*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)
		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)
		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'h55, 8'h2A, 8'h55, 8'h2A, 8'h55};   // 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			
			mem[123] = {8'h00, 8'h00, 8'h00, 8'h03, 8'h03};   // 123 摄氏度小圈
		end
 
endmodule

 

UART串口输入输出模块:

  此模块需要实现将整点及温度信息传至PC端,以及进行温度报警时将一段音频发送给小脚丫通过蜂鸣器进行播放,此部分参考了CSDN部分代码及李卓然同学串口代码,对于接受和发送上位机,一开始我准备自己用python编写一个出来,但写了一些就遇到了很多问题,最终决定参考使用串口工具XCOM,十分方便,代码也不放了,自行下载查看吧。

beeper模块:

  beeper模块为蜂鸣器,负责音乐的播放,实现此功能要求将乐谱转化为代码形式。恰好最近一直在研究乐谱的相关知识,因此对于乐谱知识十分熟悉,但对代码的对应关系却一窍不通,参考其他小伙伴的转化代码,明白了乐谱参数与时钟音高的关系,实现了蜂鸣器音乐的播放。整点播放音乐选用了《谁不说咱家乡好》这首歌,并对播放时长和循环做了定义,下面为代码展示:

module	beeper(
	input	   	clk,					//系统时钟12MHz
	input		clk_1h,
	input		rst_n,	
	input		tone_en,			//蜂鸣器使能
	input		uart_en,			//串口
	input[7:0]	uart_data,
	
	input[3:0]		min1,min2,sec1,				
	
	output	    trflag,
	output		beeper					//蜂鸣器输出
);	

reg		uart_cnt;		//计数
reg		beep;	
reg 	tr;
//乐谱音高、分频、时长参数设定
parameter	TIME = 3_000_000;
parameter   L1 =	16'd22935,	//L1,5997
		L2 =	16'd20428,	//L2,4fcc
		L3 =	16'd18203,	//L3,471b
		L4 =	16'd17181,	//L4,431d
		L5 =	16'd15305,	//L5,3bc9
		L6 =	16'd13635,	//L6,3541
		L7 =	16'd12147,	//L7,2f73
		M1 =	16'd11464,	//M1,2cc8
		M2 =	16'd10215,	//M2,27e7
		M3 =	16'd9100,	//M3,238c
		M4 =	16'd8589,	//M4,218d
		M5 =	16'd7652,	//M5,1de4
		M6 =	16'd6817,	//M6,1aa1
		M7 =	16'd6073,	//M7,17b9
		H1 =	16'd5740,	//H1,166c
		H2 =	16'd5107,	//H2,13f3
		H3 =	16'd4549,	//H3,11c5
		H4 =	16'd4294,	//H4,10c6
		H5 =	16'd3825,	//H5,0ef1
		H6 =	16'd3408,	//H6,0d50
		H7 =	16'd3036;	//H7,0bdc				
		

assign beeper = beep;			//输出
assign trflag = tr;

reg[16:0]count,count_end,count_end1;
reg[23:0]count1;
reg[7:0]state;
always@(posedge clk) 
begin//整点播放
	if(tone_en && min1 == 0 && min2 == 0 && sec1 < 3)
	begin
		count <= count + 1'b1;		
		if(count == count_end)
		begin	
			count <= 17'h0;			
			beep <= !beep;		
		end
	end
	else if(tone_en && !tr)
	begin
		count <= count + 1'b1;		
		if(count == count_end1) 
		begin	
			count <= 17'h0;			
			beep <= !beep;		
		end
	end
end

//设定音乐乐谱
always @(posedge clk) 
begin
	if(count1 < TIME)             
      count1 = count1 + 1'b1;
	else 
	begin
		count1 = 24'd0;
		if(state == 8'd63)
			state = 8'd0;
		else
			state = state + 1'b1;
			case(state)
				8'd0:count_end =M1;  
				8'd1:count_end=M1;
				8'd2:count_end=M2;
				8'D3:count_end=M1;
				8'D4:count_end=M5;
				8'D5:count_end=M3;
				8'D6:count_end=M5;
				8'D7:count_end=M1;
				8'D8:count_end=M1;
				8'D9:count_end=M2;
				8'D10:count_end=M3;
				8'D11:count_end=M5;
				8'D12,8'D13:count_end=M2;
				8'D14:count_end=L5;
				8'D15:count_end=L6;
				
				8'D16:count_end=L6;
				8'D17:count_end=L3;
				8'D18:count_end=M2;
				8'D19:count_end=M1;
				8'D20:count_end=M1;
				8'D21:count_end=L7;
				8'D22:count_end=M3;
				8'D23:count_end=M2;
				
				8'D24:count_end=M2;
				8'D25:count_end=L5;
				8'D26:count_end=L6;
				8'D27:count_end=L6;
				8'D28:count_end=M1;
				8'D29:count_end=M2;
				8'D30:count_end=M3;
				8'D31:count_end=M5;
				
				8'd32:count_end = M3;  
				8'd33:count_end=M2;
				8'd34:count_end=L6;
				8'D35:count_end=L6;
				8'D36:count_end=L7;
				8'D37:count_end=M2;
				8'D38:count_end=L6;
				8'D39:count_end=L5;
			   
				8'D40,8'D41:count_end=M5;
				8'D42:count_end=M5;
				8'D43:count_end=M5;
				8'D44:count_end=M6;
				8'D45:count_end=M3;
				8'D46:count_end=M5;
				8'D47:count_end=M3;
				
				8'D48:count_end=M2;
				8'D49:count_end=L7;
				8'D50:count_end=M5;
				8'D51:count_end=M5;
				8'D52:count_end=M3;
				8'D53:count_end=M2;
				8'D54:count_end=L5;
				8'D55:count_end=M1;
				
				8'D56:count_end=M2;
				8'D57:count_end=M1;
				8'D58:count_end=L6;
				8'D59:count_end=M1;
				8'D60:count_end=M2;
				8'D61:count_end=M3;
				8'D62:count_end=M6;
				8'D63:count_end=M5;

				default: count_end = 16'h0;
			endcase
	end
end

//串口输出乐谱设定
always @(posedge clk)
begin
	if(uart_en)
	begin

		case(uart_data)
			8'h1:count_end1 <=16'd22935;	//L1,
			8'h2:count_end1 <=16'd20428;	//L2,
			8'h3:count_end1 <=16'd18203;	//L3,
			8'h4:count_end1 <=16'd17181;	//L4,
			8'h5:count_end1 <=16'd15305;	//L5,
			8'h6:count_end1 <=16'd13635;	//L6,
			8'h7:count_end1 <=16'd12147;	//L7,
			8'h8:count_end1 <=16'd11464;	//M1,
			8'h9:count_end1 <=16'd10215;	//M2,
			8'ha:count_end1 <=16'd9100;	//M3,
			8'hb:count_end1 <=16'd8589;	//M4,
			8'hc:count_end1  <=16'd7652;	//M5,
			8'hd:count_end1 <=16'd6817;	//M6,
			8'he:count_end1 <=16'd6073;	//M7,
			8'hf:count_end1 <=16'd5740;	//H1,
			8'h10:count_end1 <=16'd5107;	//H2,
			8'h11:count_end1 <=16'd4549;	//H3,
			8'h12:count_end1 <=16'd4294;	//H4,
			8'h13:count_end1 <=16'd3825;	//H5,
			8'h14:count_end1 <=16'd3408;	//H6,
			8'h15:count_end1 <=16'd3036;	//H7,
			default:count_end1 <=16'd65535;//stopp
		endcase
	end		
end
always@(posedge uart_en or posedge clk_1h)
begin
	if(uart_en)
	begin
		uart_cnt <= 1'b1;
	end
	else
		if(uart_cnt)
		begin
			tr  <= 1'b0;
			uart_cnt <= 1'b0;
		end	
		else
			tr <= 1'b1;
			
end

endmodule

 

clock模块及clk_1h模块:

  这两个模块实现了时钟的分频和控制,参考了刘建伟同学及森林的代码,通过对四个按键进行编程,实现对小时和分钟的加减以及复位,并在OLED显示。或许是编C等其他语言编多了,对于时钟定时和计时的编程使用了大量的嵌套循环判断(类似于if、while语句的特点),尝试了用标志位判断或其他方法,但不太顺利,最终只能折中使用类似的方法进行定时和恢复功能,此部分代码有点繁琐,不在此贴出。

 

遇到的主要难题:

  对于一个对FPGA“一清二白”的我来说,完成此项目还是有一定难度的,之前接触学习过Arduino单片机等,接触FPGA并进行编程总是感觉有阻碍,最终抛弃了之前的编程思路,重新进行了参考学习,而学习基本知识就用去了很长时间,多亏有很多开源的代码和案例用于参考。

  在对温度报警停止刷新时遇到了一定的阻碍,无法实现音乐播放停止刷新,后采用了标志位flag判断时钟和数据的接收,解决了此问题。在OLED的显示方面也遇到了问题,由于对点阵屏幕的驱动显示知识的错误理解,导致输出总是重叠或超出显示范围,最终参考了诸多显示代码后发现了问题所在,实现了显示功能。

未来的计划:

1.自行编写上位机串口调试

2.音乐播放实现不同音长,转音和半音的流畅播放

3.实现OLED屏幕信息的字体字号的调整显示

 

附件下载
FPGATEST.zip
测试文件
XCOM V2.6.rar
串口测试工具
团队介绍
北京理工大学信息与电子学院
团队成员
杨晓楠
一个在电子道路上跌跌撞撞的萌新
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号