”2024寒假一起练“平台二 任务一:两位十进制加、减、乘、除计算器
注意:清零按键需要按下两次清空数码管,只按下一次清零按键不影响数值计算,但会显示随机内容,按下两次清零按键后一切正常。题目未对清零操作提出要求
一、项目需求
运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
二、需求分析
按键检测模块:负责扫描4x4矩阵键盘,以获取每个按键的检测波形信号。通过对键盘信号进行扫描和解析,准确捕获按键事件并生成相应的检测波形,为后续的处理提供准确的输入数据。
编码模块:对从键盘接收到的按键信号进行详细的处理和转换。通过对按键信号按照预设的规则进行编码转换,系统能够确保生成的编码符合系统要求,并为后续的处理和识别提供准确的数据基础。
显示模块:是一个重要的功能模块,其任务是记录每次按键事件的波形,并在检测到下一个按键信号到来时,自动触发数字的移位显示。通过LED灯的快速扫描和人眼持续感知的机制,能够实现按键事件波形的同时显示,方便用户对直接读取按键输入。
计算输出模块:按键检测系统中的关键模块之一,其功能是持续记录每次按键输入的内容,并准确区分数字和符号。一旦检测到等号按键的按下,系统将根据按键所代表的符号进行相应的加减乘除操作,并将计算结果传递给后续的转码模块,以便进行后续的处理和显示。
BCD译码模块:接收计算模块输出的结果并进行处理的模块。通过依据预设的转换规则,将计算结果转换成BCD码的格式,系统能够为后续的显示和处理提供准确的数据格式和基础。
结果显示模块:将计算结果显示出来的模块。在进行计算之前,系统会清除之前保存的数据,并接收计算输出的BCD码结果进行保存。通过快速扫描每个数字位,系统能够实现计算结果的同时显示,并确保显示的准确性和可读性。
三、硬件原理
74HC595模块
- 原理:74HC595是一种移位寄存器,通常用于扩展微控制器的输出端口。它能够通过串行输入接收数据,并将数据并行输出。该芯片内部包含8位移位寄存器和8位输出存储器,能够控制外部连接的8个数字输出。
- 工作原理:数据通过串行输入(SER)输入到移位寄存器中,在每个时钟周期的作用下,数据从串行输入移位到下一个移位寄存器,最终并行输出到8个输出引脚上。这使得使用少量的控制线即可控制多个输出引脚,非常适用于微控制器输出端口的扩展。
- 移位数据输入:当SER(Serial Data Input)引脚接收到一个新的数据位时,数据将会在每个时钟周期的上升沿移入移位寄存器中。这意味着在每个时钟周期的上升沿,SER引脚的状态将被读取,并将其值加载到移位寄存器中。
- 并行数据加载:在移位寄存器中的所有数据位被移位后,RCK引脚会接收到一个时钟信号。在每个时钟周期的上升沿,移位寄存器中的所有数据位将被同时加载到输出存储器中。这使得输出存储器中的数据与移位寄存器中的数据保持同步,从而确保数据在输出引脚上的正确输出。
- 级联连接:要级联多个74HC595芯片,将它们的串行输出(Q7')连接到下一个芯片的串行输入(SER)。这样,当第一个芯片的移位寄存器移位完成时,数据将传输到下一个芯片。同时,所有芯片的时钟输入(SRCLK)、存储器时钟输入(RCLK)和清除(MR)必须连接到相同的控制信号源。
- 移位和加载:在级联的74HC595芯片中,首先向第一个芯片的串行输入(SER)引脚输入数据。然后,通过时钟输入(SRCLK)触发移位操作,将数据从串行输入移位到移位寄存器中。当所有数据位被移位后,通过存储器时钟输入(RCLK)触发加载操作,将所有数据位并行加载到输出存储器中。这样,级联的所有芯片的输出将同时更新,以反映新加载的数据。
4x4矩阵键盘
- 原理:4x4矩阵键盘是一种常见的数字输入设备,由4行4列的按钮组成。每个按钮都与一行和一列相交,形成一个按键矩阵。按键通过按下连接的行和列,从而确定特定的按键被按下。
- 工作原理:矩阵键盘的工作原理是通过轮询的方式检测按键状态。首先,逐个设置行引脚为高电平,然后读取列引脚的状态。如果某一列引脚为低电平,则说明对应的按键被按下。通过依次检测每一行和列的状态,可以确定被按下的按钮位置。
综上所述,74HC595模块用于扩展输出端口,而4x4矩阵键盘则用于数字输入。通过它们的配合,可以实现从外部输入数字数据并通过微控制器进行处理的功能。
四、实现的方式
按键检测模块负责扫描4x4矩阵键盘,利用状态机在四个状态间循环,每个状态对键盘的行接口进行单行有效的扫描。通过改变行输出来检测矩阵键盘的列信号变化,并对前后两个时刻的检测结果进行比较,最终输出按键脉冲信号key_pulse。
编码模块接收key_pulse信号并进行编码处理,保持上一次输出,以便后续处理和识别。
显示跳变模块包含四个状态:IDLE、WRITE、TRANSMIT和EXPLORATION。在IDLE状态下,系统处于复位状态;在WRITE状态下,将写入显示内容;在TRANSMIT状态下,根据显示位置选择要发送的数据;在EXPLORATION状态下,利用seg_din引脚发送显示数据。
计算输出模块计算当前输入的位置和四个状态标记运算方式。在检测到等号后,根据symbol_state按照对应方式计算operand[0]和operand[1]。
BCD译码模块保存计算模块输出的结果,拼接shift_reg,利用“大于4加3”法得到BCD码,并对每个BCD码进行译码,使用数码管段选表示。
结果显示模块在接收到seg_clear_flag后清空保存的数据,state_input_model进入STAY状态不再接受矩阵键盘的按键输入。循环扫描结果,通过seg_din引脚输出。
五、FPAG学习步骤
1.查看STEP-MXO2数电基础实验:了解verilog基础语法,查看资料“STEP-MXO2数电基础实验”学习Verilog的基础语法和使用方法。Verilog是一种硬件描述语言,用于描述和设计数字电路。
2.查看原理图STEP-Baseboard-V4.0.pdf:了解引脚定义、矩阵键盘工作原理、74HC595芯片工作原理等在STEP-Baseboard-V4.0原理图中,可以找到有关引脚定义的信息,以及了解矩阵键盘和74HC595芯片的工作原理。矩阵键盘是一种常见的数字输入设备,通过行列交叉的方式来确定按键位置。而74HC595芯片则常用于扩展输出端口,能够实现并行数据输出。
3.查看例程基于STEP-MXO2中lab1_type_system:学习如何检测矩阵键盘:在lab1_type_system例程中,可以学习如何使用Verilog代码检测矩阵键盘的按键事件。这涉及到行列扫描、状态机设计等技术,以实现对矩阵键盘按键的准确检测和识别。
4.查看例程基于STEP-MXO2中lab9_digital_thm:学习如何转换BCD码,如何同时显示多个数字在lab9_digital_thm例程中,可以学习如何将数字转换为BCD码,并实现多个数字的同时显示。这涉及到BCD码转换器的设计、多路选择器的使用等技术,以实现数字的准确显示和切换。
5.学习硬禾学堂官网“基于小脚丫STEP MXO2开发板丨FPGA实战教学篇”:进行实操:深入学习FPGA开发板的使用方法和应用实例。提供实践经验,并帮助巩固前面学到的知识。
六、遇到的困难及解决方案
1.矩阵键盘的扫描:仔细查看原理图、查阅相关资料(如查看CSDN),了解矩阵键盘扫描原理,即每次改变行输出检测列电平变化
2.显示模块:了解74HC595芯片工作原理及级联工作模式,了解SCR、RCK、DIN引脚工作原理,依据工作原理用合理的计数器产生对应上升沿,从而正确传输数据
3.计算模块:单独保存每次按键输入,依据输入进行状态机跳转及输出
七、成果展示
八、功能框图
七、仿真波形图
九、FPGA资源使用情况
十、代码及说明
1.按键检测array_keyboard:每个状态都仅选中一行进行row输出来检测矩阵键盘的4列按键的状态
//按键检测array_keyboard
//状态机依据分频信号进行状态跳转
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
2.位置计数模块myBlock :依据seg_flag置1次数,为按键按下次数进行计数
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)begin
position <= 5'd0;
end else if (seg_flag == 1'b1) begin
position <= position + 5'd1;
end else if (position == COUNT_MAX_NUMBER)begin
position <= 5'd0;
end else begin
position <= position;
end
end
3.计算模块calculate(两种版本)
a.通过检测输入信号进行状态机的跳转进行数据的检测和计算,在按下符号键后之气按下的数字将被记录,在按下等号后将依据symbol_state 对operand[0]、operand[1]进行相应计算
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0) begin
state <= IDLE;
end else begin
case (state)
IDLE:begin
if(press_flag == 1'b1)begin
case (seg_num)
8'h3f:begin state <= NUMBER;end //0
8'h80:begin state <= NUMBER;end //.
8'h48:begin state <= CALCULATE;end //=
8'h46:begin state <= SYMBOL;end //+
8'h07:begin state <= NUMBER;end //7
8'h7f:begin state <= NUMBER;end //8
8'h6f:begin state <= NUMBER;end //9
8'h52:begin state <= SYMBOL;end ///
8'h66:begin state <= NUMBER;end //4
8'h6d:begin state <= NUMBER;end //5
8'h7d:begin state <= NUMBER;end //6
8'h76:begin state <= SYMBOL;end //x
8'h4f:begin state <= NUMBER;end //3
8'h5b:begin state <= NUMBER;end //2
8'h06:begin state <= NUMBER;end //1
8'h40:begin state <= SYMBOL;end //-
default: begin state <= IDLE;end
endcase
end else begin
state <= IDLE;
end
end
NUMBER:begin
state <= IDLE;
end
SYMBOL:begin
state <= IDLE;
end
CALCULATE: begin
state <= IDLE;
end
endcase
end
end
always @(state) begin
case (state)
IDLE:begin
seg_clear_flag <= 1'd0;
if(press_flag == 1'b1)begin
case (seg_num)
8'h3f:begin data <= 4'd0; end //0
8'h07:begin data <= 4'd7; end //7
8'h7f:begin data <= 4'd8; end //8
8'h6f:begin data <= 4'd9; end //9
8'h66:begin data <= 4'd4; end //4
8'h6d:begin data <= 4'd5; end //5
8'h7d:begin data <= 4'd6; end //6
8'h4f:begin data <= 4'd3; end //3
8'h5b:begin data <= 4'd2; end //2
8'h06:begin data <= 4'd1; end //1
default: begin
data <= 4'd0;
end
endcase
end
end
NUMBER:begin
operand[count_operand] <= ( operand[count_operand] *10 ) + data;
end
SYMBOL:begin
case (seg_num)
8'h46: symbol_state <= ADD;
8'h40: symbol_state <= SUBTRACT;
8'h76: symbol_state <= MULTIPLY;
8'h52: symbol_state <= DIVIDE;
default: ;
endcase
end
CALCULATE: begin
seg_clear_flag <= 1'd1;
case (symbol_state)
ADD: result <= (operand[0] + operand[1]);
SUBTRACT: result <= (operand[0] - operand[1]);
MULTIPLY: result <= (operand[0] * operand[1]);
DIVIDE: result <= (operand[0] / operand[1]);
endcase
end
endcase
end
b.使用data_0到data_3直接记录操作数(该方法只能且仅能进行十位数加减乘除运算),检测到等号时依据symbol_state进行计算(由于BCD译码模块是result信号电平触发的,所以对结果为0的result信号做了处理)
4.BCD译码模块:使用“大于4加3”的移位法切割计算结果,并且将其转换为数码管段选
case (symbol_state)
ADD:begin
result = ((data_0 * 4'd10) + data_1) + ((data_2 * 4'd10) + data_3);
end
SUBTRACT:begin
if (data_0 == data_2 && data_1 == data_3) begin
result_zero_flag = ~result_zero_flag;
result = 27'h7FF_FFFF;
end else begin
result = ((data_0 * 4'd10) + data_1) - ((data_2 * 4'd10) + data_3);
end
end
MULTIPLY:begin
result = ((data_0 * 4'd10) + data_1) * ((data_2 * 4'd10) + data_3);
end
DIVIDE:begin
result = ((data_0 * 4'd10) + data_1) / ((data_2 * 4'd10) + data_3);
end
default: ;
endcase
4.移位法计算出result的BCD码
result_reg = result;
shift_reg = {32'd0,result_reg};
repeat(27)begin
shift_reg[30:27] = (shift_reg[30:27] > 4) ? (shift_reg[30:27] + 2'd3) : shift_reg[30:27];
shift_reg[34:31] = (shift_reg[34:31] > 4) ? (shift_reg[34:31] + 2'd3) : shift_reg[34:31];
shift_reg[38:35] = (shift_reg[38:35] > 4) ? (shift_reg[38:35] + 2'd3) : shift_reg[38:35];
shift_reg[42:39] = (shift_reg[42:39] > 4) ? (shift_reg[42:39] + 2'd3) : shift_reg[42:39];
shift_reg[46:43] = (shift_reg[46:43] > 4) ? (shift_reg[46:43] + 2'd3) : shift_reg[46:43];
shift_reg[50:47] = (shift_reg[50:47] > 4) ? (shift_reg[50:47] + 2'd3) : shift_reg[50:47];
shift_reg[54:51] = (shift_reg[54:51] > 4) ? (shift_reg[54:51] + 2'd3) : shift_reg[54:51];
shift_reg[58:55] = (shift_reg[58:55] > 4) ? (shift_reg[58:55] + 2'd3) : shift_reg[58:55];
shift_reg[58:0] = shift_reg << 1; //左移一位
//对BCD码进行切割
result_bcd[31:0] = shift_reg[58:27];
data_1_bcd[3:0] = result_bcd[3:0];
data_2_bcd[3:0] = result_bcd[7:4];
data_3_bcd[3:0] = result_bcd[11:8];
data_4_bcd[3:0] = result_bcd[15:12];
data_5_bcd[3:0] = result_bcd[19:16];
data_6_bcd[3:0] = result_bcd[23:20];
data_7_bcd[3:0] = result_bcd[27:24];
data_8_bcd[3:0] = result_bcd[31:28];
5.显示模块seg_595:通过state_input_model 改变输出模式,输出模式包括按键键入和结果显示,PRESS实时显示按键按下的符号,STAY拒绝接受按键输入只显示计算结果
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)begin
state_input_model <= PRESS;
end else begin
case (state_input_model)
PRESS:begin
if(seg_clear_flag == 1'b1)begin
state_input_model <= CLEAR;
end else begin
state_input_model <= PRESS;
end
end
CLEAR:begin
state_input_model <= STAY;
end
STAY:begin
end
default: state_input_model <= PRESS;
endcase
end
b.seg_din引脚传输数据,在EXPORATION状态下依据count_SCK计数器实现循环显示
EXPORATION:begin
if(count_SCK == COUNT_MAX_SCK) begin //当data的16位数据都被保存后进入下一个状态
state <= IDLE;
end
c.595芯片控制:(1)在RCK输出寄存器时钟高电平后有效(即传输完一个数码管显示数据后该计数器自增)
(2)RCK输出寄存器时钟 将移位寄存器中数据储存起来 在seg_din传输完数据 SCK计数完后有效
(3)SCK移位寄存器时钟 seg_din传输16个数据 count_SCK计数32次产生16个上升沿
(4)保证SCK上升沿位于seg_din的稳定状态(波形图大概长这个样子)
SCK ----
| |
-------- -----------
seg_din传输0时 ---- ----------
| |
--------
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)begin
disply_position <= 5'd0;
end
else if (count_RCK == COUNT_MAX_RCK) begin
if(disply_position == COUNT_MAX_DISPLAY) begin
disply_position <= 5'd0;
end else begin
disply_position <= disply_position + 1'd1;
end
end
else begin
disply_position <= disply_position;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)begin
count_RCK <= 1'd0;
seg_rck <= 1'b0;
end
else if (count_SCK == COUNT_MAX_SCK) begin
count_RCK <= count_RCK + 1'd1;
seg_rck <= 1'b1;
end
else begin
count_RCK <= 1'd0;
seg_rck <= 1'd0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)begin
count_SCK <= 6'd0;
seg_sck <= 1'b1;
end
else if(state == EXPORATION)begin
if (count_SCK == COUNT_MAX_SCK) begin
count_SCK <= 6'd0;
seg_sck <= 1'b1;
end else begin
count_SCK <= count_SCK + 6'd1;
seg_sck <= ~seg_sck;
end
end
else begin
count_SCK <= 6'd0;
seg_sck <= 1'b1;
end
end
//SER串行传输数据 上升沿有效
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)begin
count_SER <= 1'd0;
end
else if (count_SER == COUNT_MAX_SER) begin
count_SER <= 1'd0;
end
else begin
count_SER <= count_SER + 1'd1;
end
end