基于小脚丫FPGA套件STEP BaseBoard V4.0实现的两位十进制计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了两位十进制计算器的设计,它的主要功能为:使用verilog语言,使用小脚丫FPGA套件STEP BaseBoard V4.0,实现的主要功能是:两位十进制数的加减乘除。
标签
FPGA
显示
参加活动/培训
用户8423
更新2024-04-01
181

”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.计算模块:单独保存每次按键输入,依据输入进行状态机跳转及输出

七、成果展示

b8a60a74a7add48817b8e18359fc3cf.jpg581cb8d7c87f8bcb6aedce0152e8e5d.jpg

3a9012916d9daf483b5d8684087a4b3.jpg5ac812911e6814164a1e8bfacb81e89.jpgd4ff935fb03545d42aa9e8eeed70a65.jpg3d6c46215bd8d0212968a9464816833.jpga2b38b9c56f34c39e09379a9b13f4ca.jpgced3e9752f01958540b6c6d7c350545.jpg


八、功能框图

绘图1.png

七、仿真波形图

image.pngimage.pngimage.pngimage.pngimage.png

九、FPGA资源使用情况

image.png

十、代码及说明

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译码模块:使用“大于43”的移位法切割计算结果,并且将其转换为数码管段选   
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
附件下载
mix.zip
源代码
团队介绍
学生
团队成员
用户8423
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号