!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
说明:我使用了数码管与LCD显示屏共同显示,由于数码管数量有限,故数码管只能看到结果后两位,但是LCD屏可以将计算数与结果数全部显示!!!!!!在视频中可能需要较高画质才能看清!!!!!!!!!!但应该是看得到的!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
项目需求:
基于小脚丫FPGA套件实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。使用TFTLCD来做显示,自行设计显示界面,代替数码管显示输入的参数、运算符以及计算后的结果。
需求分析:
1. 硬件平台选择:
选择小脚丫FPGA套件作为硬件平台。
基于Lattice MXO2的小脚丫FPGA核心板 ——STEP MXO2 LPC。
2. 功能需求:
实现两位十进制数的加、减、乘、除运算。
运算数和运算符由按键来控制。
3. 按键控制:
设计按键输入逻辑,用于输入两个运算数和运算符。
考虑按键的布局和功能分配,确保用户能够方便地输入数据和运算符。
4. 数码管显示设计:
对于两位十进制数,左侧显示十位数,右侧显示个位数。
输入两位十进制数时,最高位先在右侧显示,然后跳变到左侧的数码管上。
在计算结果显示时,同样要按照规定的方式在TFTLCD上显示。
5. 显示界面设计:
使用TFTLCD显示器,设计合适的图形用户界面(GUI)。
界面需要包括输入两位数、选择运算符、显示计算结果等功能。
通过图形界面反馈当前输入的数字和运算符。
6. 运算逻辑设计:
实现加、减、乘、除四种基本运算逻辑。
考虑溢出、除零等异常情况的处理。
7. 调试和测试:
提供适当的调试和测试手段,以确保计算器的正常运行和正确性。
确保按键输入、数码管显示、运算逻辑等各个模块的稳定性。
8. 用户友好性:
界面设计应该简单、直观,用户容易理解和操作。
提供合适的反馈,以便用户知晓当前状态和输入。
10. 性能优化:
考虑在硬件资源受限的情况下进行性能优化,确保计算器的运行效率。
12. 可扩展性:
在设计时考虑未来可能的扩展需求,以便在需要时方便地进行升级或添加新功能。
实现的方式
系统状态机
按键设计:数字输入键(0-9)、运算符选择键(加、减、乘、除)、等0号键等。
按键输入: 使用FPGA套件上的按键作为输入设备,分配不同的按键用于输入数字、选择运算符等。
按键布局
借用官方例程,只需在最后将按键值连接到顶模块即可,输入就完成了。同时我们要对其进行添加一些东西来满足我们的需求:如在按键按下后我们要让状态机进入下一个状态(c_state_cal <= c_state_cal + 1'b1),且为了防止上一个数据因串行与并行的时间冲突而改变值,我们要添加一个key_pre来解决。
//4x4矩阵按键使用状态机实现,共分为4种状态,
//每隔20ms循环一次,每个状态对应5ms时间
//进行row扫描
always@(posedge clk_200hz or negedge rst_n) begin
if(!rst_n) begin
c_state <= STATE0;
row <= 4'b1110;
end else begin
case(c_state)
//状态c_state跳转及对应状态下矩阵按键的row输出
STATE0: begin c_state <= STATE1; row <= 4'b1101; end
STATE1: begin c_state <= STATE2; row <= 4'b1011; end
STATE2: begin c_state <= STATE3; row <= 4'b0111; end
STATE3: begin c_state <= STATE0; row <= 4'b1110; end
default:begin c_state <= STATE0; row <= 4'b1110; end
endcase
end
end
//进行col扫描
always@(negedge clk_200hz or negedge rst_n) begin
if (key_out[15:0] == 16'hffff && key_pre != 0)begin
key_pre <= (key_out[15:0] != 16'hffff) ? 1 : 0;end
if(!rst_n) begin c_state_cal=3'b000;
key_out <= 16'hffff; key_r <= 16'hffff; key <= 16'hffff;
end else begin
case(c_state)
//采集当前状态的列数据赋值给对应的寄存器位
//对键盘采样数据进行判定,连续两次采样低电平判定为按键按下
STATE0: begin
if(key_out[ 3: 0] !=4'b1111&&key_pre==0)begin
key_pre <= 1'b1;c_state_cal <= c_state_cal + 1'b1; end
key_out[ 3: 0] <= key_r[ 3: 0]|key[ 3: 0];
key_r[ 3: 0] <= key[ 3: 0]; key[ 3: 0] <= col;
end
STATE1: begin
if(key_out[ 7: 4] !=4'b1111&&key_pre==1'b0)begin
key_pre <= 1'b1;c_state_cal <= c_state_cal + 1'b1;end
key_out[ 7: 4] <= key_r[ 7: 4]|key[ 7: 4];
key_r[ 7: 4] <= key[ 7: 4]; key[ 7: 4] <= col;
end
STATE2: begin
if(key_out[ 11: 8] !=4'b1111&&key_pre==1'b0)begin
key_pre <= 1'b1;c_state_cal <= c_state_cal + 1'b1;end
key_out[11: 8] <= key_r[11: 8]|key[11: 8];
key_r[11: 8] <= key[11: 8]; key[11: 8] <= col;
end
STATE3: begin
if(key_out[15:12] !=4'b1111&&key_pre==1'b0)begin
key_pre <= 1'b1;c_state_cal <= c_state_cal + 1'b1;end
key_out[15:12] <= key_r[15:12]|key[15:12];
key_r[15:12] <= key[15:12]; key[15:12] <= col;
end
endcase
end
end
显示:
数码管显示模块: 将输入的两位十进制数和计算结果通过进行显示,按照规定的显示方式展示十位和个位数。
数码管显示
1.底板上使用的就是8位共阴极数码
2.74HC595驱动
3.通过3路SPI协议控制8路并行输出
由于底板的数码管是使用74HC595驱动,所以我们可以使用SPI协议通信。这个依旧有官方例程,可以直接拿来用。
case(state)
IDLE:begin //IDLE作为第一个状态,相当于软复位
state <= MAIN;
cnt_main <= 3'd0; cnt_write <= 6'd0;
seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
end
MAIN:begin
cnt_main <= cnt_main + 1'b1;
state <= WRITE; //在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序
case(cnt_main)
//对8位数码管逐位扫描
//data [15:8]为段选, [7:0]为位选
3'd0: data <= {{dot_en[7],seg[dat_1]},dat_en[7]?8'hfe:8'hff};
3'd1: data <= {{dot_en[6],seg[dat_2]},dat_en[6]?8'hfd:8'hff};
3'd2: data <= {{dot_en[5],seg[dat_3]},dat_en[5]?8'hfb:8'hff};
3'd3: data <= {{dot_en[4],seg[dat_4]},dat_en[4]?8'hf7:8'hff};
3'd4: data <= {{dot_en[3],seg[dat_5]},dat_en[3]?8'hef:8'hff};
3'd5: data <= {{dot_en[2],seg[dat_6]},dat_en[2]?8'hdf:8'hff};
3'd6: data <= {{dot_en[1],seg[dat_7]},dat_en[1]?8'hbf:8'hff};
3'd7: data <= {{dot_en[0],seg[dat_8]},dat_en[0]?8'h7f:8'hff};
default: data <= {8'h00,8'hff};
endcase
end
WRITE:begin
if(cnt_write >= 6'd33) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
//74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用
//74HC595的时序实现,参考74HC595的芯片手册
6'd0: begin seg_sck <= LOW; seg_din <= data[15]; end //SCK下降沿时SER更新数据
6'd1: begin seg_sck <= HIGH; end //SCK上升沿时SER数据稳定
6'd2: begin seg_sck <= LOW; seg_din <= data[14]; end
6'd3: begin seg_sck <= HIGH; end
6'd4: begin seg_sck <= LOW; seg_din <= data[13]; end
6'd5: begin seg_sck <= HIGH; end
6'd6: begin seg_sck <= LOW; seg_din <= data[12]; end
6'd7: begin seg_sck <= HIGH; end
6'd8: begin seg_sck <= LOW; seg_din <= data[11]; end
6'd9: begin seg_sck <= HIGH; end
6'd10: begin seg_sck <= LOW; seg_din <= data[10]; end
6'd11: begin seg_sck <= HIGH; end
6'd12: begin seg_sck <= LOW; seg_din <= data[9]; end
6'd13: begin seg_sck <= HIGH; end
6'd14: begin seg_sck <= LOW; seg_din <= data[8]; end
6'd15: begin seg_sck <= HIGH; end
6'd16: begin seg_sck <= LOW; seg_din <= data[7]; end
6'd17: begin seg_sck <= HIGH; end
6'd18: begin seg_sck <= LOW; seg_din <= data[6]; end
6'd19: begin seg_sck <= HIGH; end
6'd20: begin seg_sck <= LOW; seg_din <= data[5]; end
6'd21: begin seg_sck <= HIGH; end
6'd22: begin seg_sck <= LOW; seg_din <= data[4]; end
6'd23: begin seg_sck <= HIGH; end
6'd24: begin seg_sck <= LOW; seg_din <= data[3]; end
6'd25: begin seg_sck <= HIGH; end
6'd26: begin seg_sck <= LOW; seg_din <= data[2]; end
6'd27: begin seg_sck <= HIGH; end
6'd28: begin seg_sck <= LOW; seg_din <= data[1]; end
6'd29: begin seg_sck <= HIGH; end
6'd30: begin seg_sck <= LOW; seg_din <= data[0]; end
6'd31: begin seg_sck <= HIGH; end
6'd32: begin seg_rck <= HIGH; end //当16位数据传送完成后RCK拉高,输出生效
6'd33: begin seg_rck <= LOW; state <= MAIN; end
default: ;
endcase
end
default: state <= IDLE;
endcase
TFTLCD显示: 使用TFTLCD作为显示器,设计GUI界面显示输入的两位十进制数、运算符和计算结果。
TFTLCD显示
由于这个TFTLCD的官方例程比较抽象,没太看懂。我最开始是去借鉴了一下某原子的写法,发现引脚和驱动有些不一样,没改成。之后我又在网上找了两份代码,搞通了,成功后我在看代码,慢慢了解了写法和原理。回过头来,我再看官方例程,发现好像也能改也能用。但是有比代码进行更改,重点在于让它能够进行不断的刷新(原码它只能再TFTLCD初始化成功后显示一次)
//第一个就是control里,show_pic_flag 是继续显示的标志位,当初始化成功后变1,之后变0,我们将变0语句注释掉
//////这里改了
always@(posedge sys_clk_50MHz or negedge sys_rst_n)
if(!sys_rst_n)
show_pic_flag <= 1'b0;
else if(cnt1 == 'd2)
show_pic_flag <= 1'b1;
// else
// show_pic_flag <= 1'b0;
//再就是对lcd_show_pic里的更改这里由于有再当长度达到SIZE_LENGTH_MAX时就会结束显示,更改一下让它到max后回到0
//长度计数器====这里要改
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_length_num <= 'd0;
else if( length_num_flag)begin
cnt_length_num <= cnt_length_num + 1'b1;
if(cnt_length_num==SIZE_LENGTH_MAX)cnt_length_num <= 'd0;
end
//最后就是对pic_ram的更改,这模块我只能说写的很厉害···;直接将我们要的点改为数据进行了,不难,就是比较麻烦
9'd10 : q = 240'h0;
9'd11 : q = 240'h0;
9'd12 : q = {100'h0, y2[dat10], y2[dat9], y2[dat8], y2[dat7], y2[dat6], y2[dat5], y2[dat4], y2[dat3], y2[dat2], y2[dat1], 60'h0};
9'd13 : q = {100'h0, y3[dat10], y3[dat9], y3[dat8], y3[dat7], y3[dat6], y3[dat5], y3[dat4], y3[dat3], y3[dat2], y3[dat1], 60'h0};
9'd14 : q = {100'h0, y4[dat10], y4[dat9], y4[dat8], y4[dat7], y4[dat6], y4[dat5], y4[dat4], y4[dat3], y4[dat2], y4[dat1], 60'h0};
9'd15 : q = {100'h0, y5[dat10], y5[dat9], y5[dat8], y5[dat7], y5[dat6], y5[dat5], y5[dat4], y5[dat3], y5[dat2], y5[dat1], 60'h0};
9'd16 : q = {100'h0, y6[dat10], y6[dat9], y6[dat8], y6[dat7], y6[dat6], y6[dat5], y6[dat4], y6[dat3], y6[dat2], y6[dat1], 60'h0};
9'd17 : q = {100'h0, y7[dat10], y7[dat9], y7[dat8], y7[dat7], y7[dat6], y7[dat5], y7[dat4], y7[dat3], y7[dat2], y7[dat1], 60'h0};
9'd18 : q = {100'h0, y8[dat10], y8[dat9], y8[dat8], y8[dat7], y8[dat6], y8[dat5], y8[dat4], y8[dat3], y8[dat2], y8[dat1], 60'h0};
9'd19 : q = {100'h0, y9[dat10], y9[dat9], y9[dat8], y9[dat7], y9[dat6], y9[dat5], y9[dat4], y9[dat3], y9[dat2], y9[dat1], 60'h0};
9'd20 : q = {100'h0, y10[dat10], y10[dat9], y10[dat8], y10[dat7], y10[dat6], y10[dat5], y10[dat4], y10[dat3], y10[dat2], y10[dat1], 60'h0};
9'd21 : q = {100'h0, y11[dat10], y11[dat9], y11[dat8], y11[dat7], y11[dat6], y11[dat5], y11[dat4], y11[dat3], y11[dat2], y11[dat1], 60'h0};
9'd22 : q = {100'h0, y12[dat10], y12[dat9], y12[dat8], y12[dat7], y12[dat6], y12[dat5], y12[dat4], y12[dat3], y12[dat2], y12[dat1], 60'h0};
9'd23 : q = {100'h0, y13[dat10], y13[dat9], y13[dat8], y13[dat7], y13[dat6], y13[dat5], y13[dat4], y13[dat3], y13[dat2], y13[dat1], 60'h0};
9'd24 : q = {100'h0, y14[dat10], y14[dat9], y14[dat8], y14[dat7], y14[dat6], y14[dat5], y14[dat4], y14[dat3], y14[dat2], y14[dat1], 60'h0};
9'd25 : q = 240'h0;
计算:
运算逻辑: 在FPGA中实现加、减、乘、除四种基本运算逻辑,确保计算的准确性。
由于我暂时没见到过FPGA里的十进制数定义与运算,所以我是使用了先进行二进制数的运算后再将结果转化成十进制的。最开始我是想这直接将二进制看出十进制进行计算,但是进行运算时我发现,它们有点像逢十六进一(我用4位宽表示一个十进制数),虽然加法与减法可以手写进位与借位,但是乘除却不是那么好解决。所以我参考了一下这个,写了一个类似的但是答案不对,之后我想起了上课老师讲过BCD码这玩意,这下问题解决了。
reg [15:0] dat;
always @ (*)begin
dat[15:8] = led_data1[31:28]*10+led_data1[27:24];//运算数一
dat[7:0]=led_data1[19:16]*10+led_data1[15:12];//运算数二
if(c_state_cal==4'b0110)begin
case(led_data1[23:20])//存储的运算符
4'b1010:begin
result[15:0]=dat[15:8]+dat[7:0];
end
4'b1011:begin
if(dat[15:8]>dat[7:0])begin
result[15:0]=dat[15:8]-dat[7:0];
end
else begin
result[15:0]=dat[7:0]-dat[15:8];
end
end
4'b1100:begin
result[15:0]=dat[15:8]*dat[7:0];
end
4'b1101:begin
result[15:0]=dat[15:8]/dat[7:0];
end
endcase
end
reg [31:0] t;
always@(bin_code or rst_n)begin
t= {16'h0,bin_code};
if(!rst_n) begin
bcd_code=16'hffff;
end else if(c_state_cal== 4'b0110)begin
repeat(16) begin
if (t[19:16] >= 5) t[19:16] = t[19:16] + 2'b11;
if (t[23:20] >= 5) t[23:20] = t[23:20] + 2'b11;
if (t[27:24] >= 5) t[27:24] = t[27:24] + 2'b11;
if (t[31:28] >= 5) t[31:28] = t[31:28] + 2'b11;
t= t<< 1;
end
bcd_code=16'h0000;
bcd_code = t[31:16];
end else if(c_state_cal==4'b0000)
bcd_code=16'hffff;
end
仿真波形图
加法:8+12=16
减法:51-18=33
乘法:99*99=9801
除法:99/3=33
FPGA的资源利用报告
未来的计划
功能扩展:
为了进一步提高计算器的实用性,我们计划添加一系列新功能。其中包括平方根、百分比、三角函数等数学运算,使计算器能够更全面地满足用户在日常计算中的需求。这些功能的引入将大大拓展计算器的适用范围,使其成为更为强大的工具。
界面优化:
为了增强用户体验,我们将进行图形界面的全面优化。通过改进界面设计、增加动画效果和改善交互元素,我们旨在使用户与计算器的互动更加直观、流畅。美观直观的界面设计将有助于用户更轻松地理解和操作计算器,提高整体使用的愉悦感。
多模式支持:
我们计划为计算器引入多种计算模式,以满足不同用户的需求。除了基本的加减乘除模式,我们将考虑添加科学计算、统计计算等高级模式。这样一来,用户可以根据具体的计算需求选择合适的模式,使计算器更加灵活多样化。
历史记录:
为了方便用户追溯之前的计算,我们将添加历史记录功能。用户可以随时查看并导出之前的计算历史,也可以保存重要的计算结果。这不仅为用户提供了便利,还增加了计算器的实用性和用户友好性。
云同步:
引入云同步功能是为了让用户能够更便捷地在不同设备之间同步他们的计算器历史和设置。这将使用户能够在手机、平板等多个设备上访问他们的个性化设置和计算历史,实现无缝的使用体验。
硬件升级:
基于用户反馈和市场需求,我们将考虑对计算器硬件进行升级。这可能包括提高处理器性能、增加存储容量、优化显示屏等方面的改进,以确保计算器在性能和功能上保持领先地位。
项目总结:
1.实现的功能:到写稿为止我已经实现了如下:按键可以顺利输入数,且可在数码管与LCD屏进行正确快速的显示;可正确识别输入的“+-*/”运算符并进行正确的运算,显示出正确结果。
2.存在的问题:由于的的按键是以key_pluse进行转化的,所以在输入运算数时按下‘+’将输入‘0’、按下‘-’将输入‘1’、按下‘*’将输入‘2’、按下‘/’将输入‘3’、按下‘.’将输入‘4’、按下‘CE’将输入‘5’。这是由于我将它们进行switch成了‘8h10'、‘8h11'、‘8h12'、‘8h13'、‘8h14'、‘8h15',而由于位数原因且再加入输入标识符时时序会崩溃故这个问题还未解决。