电子森林STEP-MXO2_1 入门部分全部实验

15701600:

前言

本部分实验基于电子森林小脚丫开发板的数电入门教程实验。实验链接:step-mxo2入门教程 电子森林] (eetree.cn)

其中代码是博主学习后根据自己思路自己敲的,并非直接复制,且仅供学习交流使用,侵删。

lattice 环境配置在此不再赘述~

按键LED

module LED (key,sw,led);
	input [3:0] key;
	input [3:0] sw;
	output [7:0] led;
	
	assign led={key, sw};
	
endmodule

1688844371133

三色LED

开发板上的一个三色LED灯,通过赋3位值来决定亮哪个颜色。用三个按键控制 easy

module LED (key,led);
	input [2:0] key;
	output [2:0] led;
	
	assign led={key};
	
endmodule

image-20230709033048500

3-8译码器

三个按键输出8个状态。主要是练习 always case 的使用。

module LED (key,led);
	input [2:0] key;
	output [7:0] led;
	reg [7:0] led;
	always @(key)
	begin
		case(key)
			3'b000:led=8'b11111110;
			3'b001:led=8'b11111101;
			3'b010:led=8'b11111011;
			3'b011:led=8'b11110111;
			3'b100:led=8'b11101111;
			3'b101:led=8'b11011111;
			3'b110:led=8'b10111111;
			3'b111:led=8'b01111111;
			default:;		
		endcase
	end
			
	
endmodule

数码管显示

两个数码管,每个需要9位输入,其中4位是数据输入,可以输入10种状态对应0-9.

我们通过按键和拨码开关传入数据。led 设置我想就记住就行先。

module SEG_LED (key,sw,seg_led_1,seg_led_2);
	input [3:0] key;
	input  [3:0] sw;
	output [8:0] seg_led_1;
	output [8:0] seg_led_2;
	
	reg [8:0] seg [9:0];
	
	initial
	begin
		seg[0]=9'h3f;
		seg[1]=9'h06;
		seg[2]=9'h5b;
		seg[3]=9'h4f;
		seg[4]=9'h66;
		seg[5]=9'h6d;
		seg[6]=9'h7d;
		seg[7]=9'h07;
		seg[8]=9'h7f;
		seg[9]=9'h6f;
	end
	
	assign seg_led_1=seg[key];
	assign seg_led_2=seg[sw];
			
	
endmodule

image-20230709111832655

时钟分频

算法

偶数倍频:光看上升或者下降沿个数就行,0N/2-1时翻转,N/2N-1时再翻转。

奇数倍频:形如下图。

image-20230709151349660

如图为5分频,输入5个周期输出1个周期。所以每2.5个周期一翻转,比如从上升沿开始,再经过两个上升沿,后一个下降沿后翻转。

这样就没法只通过上升或者下降沿去判断了,因为结尾是半个上升/下降沿。不过通过纯上升,下降沿的波形(上升沿比下降沿多一个周期)我们可以发现,两者and操作得到的就是我们想要的结果。

实现

module div(clk_in, rst_n ,clk_out);
	input clk_in,rst_n;
	output clk_out;
	
	parameter WIDTH=3;
	parameter N=5;
	
	reg [WIDTH-1:0] cnt_p, cnt_n;
	reg clk_p, clk_n;
	
	always @ (posedge clk_in or negedge rst_n)
		begin
			if(!rst_n)cnt_p<=0;
			else if(cnt_p==(N-1))
				cnt_p<=0;
			else 
				cnt_p<=cnt_p+1;
		end

	always @ (negedge clk_in or negedge rst_n)
		begin
			if(!rst_n)cnt_n<=0;
			else if(cnt_n==(N-1))
				cnt_n<=0;
			else 
				cnt_n<=cnt_n+1;
		end
		
	always @ (posedge clk_in or negedge rst_n)
		begin
			if(!rst_n)clk_p<=0;
			else if(cnt_p<(N>>1))
				clk_p<=0;
			else 
				clk_p<=1;
		end
		
	always @ (negedge clk_in or negedge rst_n)
		begin
			if(!rst_n)clk_n<=0;
			else if(cnt_n<(N>>1))
				clk_n<=0;
			else 
				clk_n<=1;
		end
		
	assign clk_out=(N==1)?clk_in:(N[0])?clk_p&clk_n:clk_p;
endmodule

打不开仿真显示 license 错误可能是仿真和 license 版本不对。

1688887214194

1688887262813

流水灯

采用模块的思想。我们把前面的3-8译码器(led)部分和时钟分频部分代码结合起来,然后用一个模块去用他俩,传递输入输出。

module flashled (clk,rst,led);
	input clk,rst;
	output [7:0]led;
	
	reg [2:0] cnt;
	
	wire clk1h;
	
	LED u1 (
		.key(cnt),
		.led(led)
	);
	
	div #(.WIDTH(32),.N(12000000)) u2 (         
        .clk_in(clk),
        .rst_n(rst),
        .clk_out(clk1h)
    );               
	
	always @(posedge clk1h or negedge rst)
		if(!rst)
			cnt<=0;
		else
			cnt<=cnt+1;
endmodule

还有一种简单的 led 赋值法,我们知道 led 形如 1111 1101 也就是8位不断循环来形成流水灯的效果。所以我们只需要让 led 不断循环左移或者右移就行。

led={led[0],led[7:1]};

1688891724885

按键消抖

如果只是一个简单的按下按键翻转LED灯状态,那确实很好写:

module debounce (key,rst,key_pulse);
	input key,rst;
	output key_pulse;
	
	reg key_pulse;
	
	always @(negedge rst or negedge key)
		begin
			if(!rst)
				key_pulse=1;
			else
				key_pulse=~key_pulse;
		end
	
endmodule

选用一个按键作为 rst,一个按键为 led 的输入。

但是经典问题,按下按键和松开的一瞬间按键的电平是不断抖动的并不稳定:

image-20230709165306536

这里以(下降沿触发)为例。解决方案为:检测到第一次下降沿后,先延时>10ms,再检测按键是否仍然处于低电平,如果是说明确实是被按下了,如果不是可能只是一个小抖动。

module debounce (clk,key,rst,key_pulse);
	input key;
	input rst;
	input clk;
	output key_pulse;
	
	reg key_rst_pre;
	reg key_rst;
	
	
	reg [17:0] cnt;
	
	wire key_edge;
	
	always @(posedge clk or negedge rst)
		begin
			if(!rst) begin
				key_rst_pre<=1'b1;
				key_rst<=1'b1;
			end
			else begin
				key_rst<=key;
				key_rst_pre<=key_rst;
			end
		end
		
	assign key_edge=key_rst_pre&~(key_rst);
	reg key_sec_pre,key_sec;
	
	always @(posedge clk or negedge rst)
		begin
			if(!rst)
				cnt<=18'h0;
			else if(key_edge)
				cnt<=18'b0;
			else
				cnt<=cnt+1'h1;
		end
		
	always @(posedge clk or negedge rst)
		begin
			if(!rst)
				key_sec<=1'b1;
			else if(cnt==18'h3ffff)
				key_sec<=key;
		end
		
	always @(posedge clk or negedge rst)
		begin
			if(!rst)
				key_sec_pre<=1'b1;
			else
				key_sec_pre<=key_sec;
		end
		
	assign key_pulse=key_sec_pre&~(key_sec);
	
endmodule

下降沿的时候 key_edge 置1,只有当起始状态上升沿, key_edge 下降后一定时间内没有新的下降沿(无抖动)且此时仍然为下降沿时脉冲信息才为1.

处理脉冲部分很简单,读到 pulse 如果是1就翻转 led,否则保持。

module top (clk,key,led,rst);

	input clk;
	input key;
	input rst;
	output reg led;
	
	wire key_pulse;

	debounce u1 (
		.clk(clk),
		.key(key),
		.rst(rst),
		.key_pulse(key_pulse)
	);
	
	always @ (posedge clk or negedge rst)
		begin
			if(!rst)
				led<=1'b1;
			else if(key_pulse)
				led<=~led;
			else
				led<=led;
		end

endmodule

这里写的时候有一个很有趣的bug,我把cnt计数++部分漏掉了,然后生成引脚图定义的时候 clk key rst 都是 unconnected 的引脚,查了一下说是“编译器认为你这几个输入对输出无影响 就自动忽略了”。

计时控制

需求:写一个篮球24s计时软件。要求:

  1. 上电后从24开始倒计时到0为止停止。
  2. 按下 rst 归位到24.
  3. 按下按钮后暂停当前计时,再次按下后继续计时。

主要就是写一个模块用到了时钟分频模块(传入参数设定间隔1s),按键模块(用刚刚用的消抖的)。数码管显示 LED显示都可以在本模块内补全。

写的时候发现一个 always 赋值语法规范:一个变量赋值不要在多个 always 里分别有过赋值,会报错,尽量集中到一个 always 里。

module BasketballBoard (key, clk, rst, seg_led_1,seg_led_2,led);
	input key, clk, rst;
	output [8:0] seg_led_1;
	output [8:0] seg_led_2;
	output reg [7:0] led;

	reg [8:0] seg [9:0];
	
	reg [4:0] SED_LED_H;
	reg [4:0] SED_LED_L;
	wire key_pulse;
	wire clk1h;
	
	reg stop_flag=0;
	reg rst_flag=0;
	
	initial
	begin
		seg[0]=9'h3f;
		seg[1]=9'h06;
		seg[2]=9'h5b;
		seg[3]=9'h4f;
		seg[4]=9'h66;
		seg[5]=9'h6d;
		seg[6]=9'h7d;
		seg[7]=9'h07;
		seg[8]=9'h7f;
		seg[9]=9'h6f;
	end
	
	always @ (posedge key_pulse)
		if(rst) 
			rst_flag<=~rst_flag;
			
		
	debounce u1 (
			.clk(clk),
			.rst(rst),
			.key(key),
			.key_pulse(key_pulse)
		);
		
		
	div #(.WIDTH(32),.N(12000000)) u2 ( 
		.clk_in(clk),
		.rst_n(rst),      
		.clk_out(clk1h)
		);
		
	always @ (posedge clk1h or negedge rst)
		begin
			if(!rst)
				begin
					SED_LED_H<=4'd2;
					SED_LED_L<=4'd4;
					stop_flag<=0;
				end
			else if((!rst_flag)&(!stop_flag))
				begin
					led<=8'b11111111;
					if(SED_LED_L)
						SED_LED_L<=SED_LED_L-4'd1;
					else if(SED_LED_H) begin
							SED_LED_L<=4'd9;
							SED_LED_H<=SED_LED_H-4'd1;
						end
					if((!SED_LED_H)&(!SED_LED_L))
						stop_flag<=1;
				end
			else
				led<=8'b00000000;
		end
		
	assign seg_led_1=seg[SED_LED_H];
	assign seg_led_2=seg[SED_LED_L];

endmodule

led seg key rst clk 这些都是和前面采取类似的引脚设置。再加上前面的 div 模块和 key 模块即可。

呈现效果:计时结束和按下暂停键时 LED 灯会全亮来代表暂停时钟。

LED 流水灯

PWM 原理。随着时间推移改变占空比来呈现出不同亮度的 LED。

1688927302381

比如每个周期长度 100,第一个周期设定高电平0 低电平100,第二个周期设定高电平10 低电平90,第三个周期设定高电平20 低电平80……

也就是说我们需要两个计时器,第一个就是老老实实的记录一个又一个周期,0~Period-1.

第二个计时器一方面,在第一个计时器结束一个周期的时候改变一下自己的占空比,比如上例每个周期结束高电平部分+10.

另一方面,第二个计时器和第一个计时器作比较。比如当前第二个计时器是20也就是20高电平 80低电平,那么计时器1<计时器2的时候设置为高电平,计时器1>计时器2的时候设置为低电平。

module WaterLED(clk,rst,led);
	input clk, rst;
	output led;
	reg [13:0] cnt1;
	reg [13:0] cnt2;
	
	parameter CNT_NUM=2400;
	reg flag=0;
	
	always @ (posedge clk or negedge rst)
		begin
			if(!rst)
				cnt1<=13'b0;
			else if(cnt1==CNT_NUM-1) begin
					cnt1<=13'b0;
					if(flag==0)
						if(cnt2>=CNT_NUM) begin
								flag<=1'b1;
							end
						else cnt2<=cnt2+13'b1;
					else if(flag==1)
						if(cnt2<=0) begin
								flag<=1'b0;
							end
						else cnt2<=cnt2-13'b1;
				end
			else cnt1<=cnt1+13'b1;
		end
		
	
	assign led=cnt1<cnt2?1:0;
endmodule

代码没啥难的。

交通信号灯

两个三色模块各亮各的,表示主路和支路的红绿灯信号。

主路:15绿,3黄,10红(单位:秒)。

支路:7绿,3黄,18红。

概念:状态机,一种抽象模型。对象有状态(state,比如此例中两个红绿灯的三种状态),事件(event,外界施加的,比如我让计数–,或者切换状态颜色),行为(action,交通灯按 event 自己执行的动作,比如切颜色),切换状态(transition,比如红灯绿灯黄灯之间三种状态)。

一开始觉得很容易写。就是两个数组存储主路支路状态表嘛。

module TrafficLED (clk,rst,LED_1,LED_2);
	input clk,rst;
	output reg [2:0] LED_1;
	output reg [2:0] LED_2;
	
	reg [4:0] state_1 [2:0];
	reg [4:0] state_2 [2:0];
	reg [2:0] color_state [2:0];
	
	reg [2:0] cur_state_1;
	reg [2:0] cur_state_2;
	reg [4:0] cur_time_1;
	reg	[4:0] cur_time_2;
	
	wire clk1h;
	
	initial begin
		state_1[0]=5'd14;
		state_1[1]=5'd2;
		state_1[2]=5'd9;
		state_2[0]=5'd6;
		state_2[1]=5'd2;
		state_2[2]=5'd17;
		color_state[0]=3'b101;
		color_state[1]=3'b100;
		color_state[2]=3'b110;
		cur_time_1=5'd14;
		cur_time_2=5'd6;
	end
	
	div #(.WIDTH(32),.N(12000000)) u2 (         
        .clk_in(clk),
        .rst_n(rst),
        .clk_out(clk1h)
    );
	
	always @ (posedge clk1h or negedge rst) begin
			if(!rst) begin
					cur_state_1<=0;
					cur_state_2<=0;
					cur_time_1<=5'd14;
					cur_time_2<=5'd6;
					LED_1<=color_state[cur_state_1];
					LED_2<=color_state[cur_state_2];
				end
			else begin
					if(cur_time_1)cur_time_1<=cur_time_1-5'b1;
					else begin
							if(cur_state_1<2)cur_state_1<=(cur_state_1+1);
							else cur_state_1<=0;
							cur_time_1<=state_1[cur_state_1+1];
						end
					if(cur_time_2)cur_time_2<=cur_time_2-5'b1;
					else begin
							if(cur_state_2<2)cur_state_2<=(cur_state_2+1);
							else cur_state_2<=0;
							cur_time_2<=state_2[cur_state_2+1];
							
						end
					LED_1<=color_state[cur_state_1];
					LED_2<=color_state[cur_state_2];
				end
		end
	
endmodule

有两个注意的地方,都是和 fpga 非阻塞赋值相关的。

之前知道 always 里如果用 <= 赋值,所有变量是结束 always 的时候同时赋值的。第一个错误写法是这么写的:

if(cur_time_1)cur_time_1<=cur_time_1-5'b1;
else begin
        if(cur_state_1<2)cur_state_1<=(cur_state_1+1);
        else cur_state_1<=0;
    	cur_time_1<=state_1[cur_state_1];//这里有区别
    end
if(cur_time_2)cur_time_2<=cur_time_2-5'b1;
else begin
        if(cur_state_2<2)cur_state_2<=(cur_state_2+1);
        else cur_state_2<=0;
    	cur_time_2<=state_2[cur_state_2];//这里有区别
    end
LED_1<=color_state[cur_state_1];
LED_2<=color_state[cur_state_2];

因为我想的是,cur_state+1后切换状态为当前cur_state的状态,也就是已经+1过后的状态。

实际不是这么回事,实际上他俩指令是同时执行的,也就是说state[cur_state]还是刚才没+1的状态。

所以我改成 state[cur_state+1] 了。

第二个错误如下:

if(cur_time_1)cur_time_1<=cur_time_1-5'b1;
else begin
        cur_state_1<=(cur_state_1+1);//这里有区别
    	if(cur_state_1==3)cur_state_1<=0;//这里有区别
    	cur_time_1<=state_1[cur_state_1+1];
    end
if(cur_time_2)cur_time_2<=cur_time_2-5'b1;
else begin
    	cur_state_2<=(cur_state_2+1);//这里有区别
    	if(cur_state_2==3)cur_state_2<=0;//这里有区别
    	cur_time_2<=state_2[cur_state_2+1];
    end
LED_1<=color_state[cur_state_1];
LED_2<=color_state[cur_state_2];

想的也是顺序执行,先+1判断是否超出了(0,1,2)的数组范围,如果超出了就归0.

但是 LED 赋值和 cur_state+1 是同时执行的,也就是有1s我的cur_state是3,LED也尝试获取数组3下标的状态。

所以干脆赋值的时候就:如果为2,直接归0,否则++。这样就不用再一步判断了。

猜你喜欢

转载自blog.csdn.net/jtwqwq/article/details/131636125