首页 > 其他分享 >基于FPGA的数字电子秤设计(verilog)

基于FPGA的数字电子秤设计(verilog)

时间:2024-12-14 21:58:36浏览次数:10  
标签:wire FPGA clk 电子秤 sys verilog rst input i2c

目录

一、功能描述

二、顶层设计分析

2.1 I2c_ctrl模块

2.2 PCF8591_ad模块 

2.3 v_weigh 电压转质量模块

2.4 weighing_pre 去皮模块

2.5 mcx 计价模块

2.6 money 价格输出模块

2.7 chose 数码管选择显示模块

2.8 sign_give 信号提供模块

2.9 buffer 报警模块

2.10 顶层设计

三、烧录验证


一、功能描述

最近设计一款FPGA实现的具有称重、去皮、计价、报警功能的数字电子秤,分享如下。设计使用野火征途Pro系列开发板。称重范围设定0~200g,超过该范围蜂鸣器触发报警。称重传感器种类繁多,但输出信号相似,输出与称重线性的电压,因此本设计均适用,只需稍作修改即可。设计采用CSY2000系列传感器获取质量信号,其输出电压与称重质量成线性关系。

二、顶层设计分析

顶层文件看着繁琐复杂,实际每个模块的代码实现起来很简单,大家可以结合后面的子模块分析。

系统输入FPGA的信号有系统时钟sys_clk、系统复位sys_rst_n以及三个按键输入信号qp、cf、ok,依次代表去皮,计价,确定按键,具体在后面子模块解释。

系统输出有与AD模块PCF8591T进行I2c通信的i2c_scl、i2c_sda,输出给数码管显示数据的ds,oe,shcp,stcp,输出给蜂鸣器的报警信号buff。

在看代码之前,我在这备注下此设计中计价和价格区别,防止大家记混,本文计价过程是设定称重物品多少钱一克,而价格过程是输出当前质量物品在设置计价后的价格。

2.1 I2c_ctrl模块

PCF8591T模块采用I2c协议通信,所以我们需要一个I2c_ctrl模块来实现该通信协议,具体代码引用野火征途系列开发板官方代码如下

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/04/01
// Module Name   : i2c_ctrl
// Project Name  : ad
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : i2c_ctrl
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  i2c_ctrl
#(
    parameter   DEVICE_ADDR     =   7'b1010_000     ,   //i2c设备地址
    parameter   SYS_CLK_FREQ    =   26'd50_000_000  ,   //输入系统时钟频率
    parameter   SCL_FREQ        =   18'd250_000         //i2c设备scl时钟频率
)
(
    input   wire            sys_clk     ,   //输入系统时钟,50MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire            wr_en       ,   //输入写使能信号
    input   wire            rd_en       ,   //输入读使能信号
    input   wire            i2c_start   ,   //输入i2c触发信号
    input   wire            addr_num    ,   //输入i2c字节地址字节数
    input   wire    [15:0]  byte_addr   ,   //输入i2c字节地址
    input   wire    [7:0]   wr_data     ,   //输入i2c设备数据

    output  reg             i2c_clk     ,   //i2c驱动时钟
    output  reg             i2c_end     ,   //i2c一次读/写操作完成
    output  reg     [7:0]   rd_data     ,   //输出i2c设备读取数据
    output  reg             i2c_scl     ,   //输出至i2c设备的串行时钟信号scl
    inout   wire            i2c_sda         //输出至i2c设备的串行数据信号sda
);

//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
// parameter define
localparam  CNT_CLK_MAX     =   (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3   ;   //cnt_clk计数器计数最大值

localparam  CNT_START_MAX   =   8'd100; //cnt_start计数器计数最大值

localparam  IDLE            =   4'd00,  //初始状态
            START_1         =   4'd01,  //开始状态1
            SEND_D_ADDR     =   4'd02,  //设备地址写入状态 + 控制写
            ACK_1           =   4'd03,  //应答状态1
            SEND_B_ADDR_H   =   4'd04,  //字节地址高八位写入状态
            ACK_2           =   4'd05,  //应答状态2
            SEND_B_ADDR_L   =   4'd06,  //字节地址低八位写入状态
            ACK_3           =   4'd07,  //应答状态3
            WR_DATA         =   4'd08,  //写数据状态
            ACK_4           =   4'd09,  //应答状态4
            START_2         =   4'd10,  //开始状态2
            SEND_RD_ADDR    =   4'd11,  //设备地址写入状态 + 控制读
            ACK_5           =   4'd12,  //应答状态5
            RD_DATA         =   4'd13,  //读数据状态
            N_ACK           =   4'd14,  //非应答状态
            STOP            =   4'd15;  //结束状态

// wire  define
wire            sda_in          ;   //sda输入数据寄存
wire            sda_en          ;   //sda数据写入使能信号

// reg   define
reg     [7:0]   cnt_clk         ;   //系统时钟计数器,控制生成clk_i2c时钟信号
reg     [3:0]   state           ;   //状态机状态
reg             cnt_i2c_clk_en  ;   //cnt_i2c_clk计数器使能信号
reg     [1:0]   cnt_i2c_clk     ;   //clk_i2c时钟计数器,控制生成cnt_bit信号
reg     [2:0]   cnt_bit         ;   //sda比特计数器
reg             ack             ;   //应答信号
reg             i2c_sda_reg     ;   //sda数据缓存
reg     [7:0]   rd_data_reg     ;   //自i2c设备读出数据

//************************************************************************//
//******************************* Main Code ******************************//
//************************************************************************//
// cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <=  8'd0;
    else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
        cnt_clk <=  8'd0;
    else
        cnt_clk <=  cnt_clk + 1'b1;

// i2c_clk:i2c驱动时钟
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_clk <=  1'b1;
    else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
        i2c_clk <=  ~i2c_clk;

// cnt_i2c_clk_en:cnt_i2c_clk计数器使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_i2c_clk_en  <=  1'b0;
    else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
        cnt_i2c_clk_en  <=  1'b0;
    else    if(i2c_start == 1'b1)
        cnt_i2c_clk_en  <=  1'b1;

// cnt_i2c_clk:i2c_clk时钟计数器,控制生成cnt_bit信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_i2c_clk <=  2'd0;
    else    if(cnt_i2c_clk_en == 1'b1)
        cnt_i2c_clk <=  cnt_i2c_clk + 1'b1;

// cnt_bit:sda比特计数器
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if((state == IDLE) || (state == START_1) || (state == START_2)
                || (state == ACK_1) || (state == ACK_2) || (state == ACK_3)
                || (state == ACK_4) || (state == ACK_5) || (state == N_ACK))
        cnt_bit <=  3'd0;
    else    if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
        cnt_bit <=  3'd0;
    else    if((cnt_i2c_clk == 2'd3) && (state != IDLE))
        cnt_bit <=  cnt_bit + 1'b1;

// state:状态机状态跳转
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else    case(state)
        IDLE:
            if(i2c_start == 1'b1)
                state   <=  START_1;
            else
                state   <=  state;
        START_1:
            if(cnt_i2c_clk == 3)
                state   <=  SEND_D_ADDR;
            else
                state   <=  state;
        SEND_D_ADDR:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_1;
            else
                state   <=  state;
        ACK_1:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                begin
                    if(addr_num == 1'b1)
                        state   <=  SEND_B_ADDR_H;
                    else
                        state   <=  SEND_B_ADDR_L;
                end
             else
                state   <=  state;
        SEND_B_ADDR_H:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_2;
            else
                state   <=  state;
        ACK_2:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  SEND_B_ADDR_L;
            else
                state   <=  state;
        SEND_B_ADDR_L:
            if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
                state   <=  ACK_3;
            else
                state   <=  state;
        ACK_3:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                begin
                    if(wr_en == 1'b1)
                        state   <=  WR_DATA;
                    else    if(rd_en == 1'b1)
                        state   <=  START_2;
                    else
                        state   <=  state;
                end
             else
                state   <=  state;
        WR_DATA:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_4;
            else
                state   <=  state;
        ACK_4:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  STOP;
            else
                state   <=  state;
        START_2:
            if(cnt_i2c_clk == 3)
                state   <=  SEND_RD_ADDR;
            else
                state   <=  state;
        SEND_RD_ADDR:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_5;
            else
                state   <=  state;
        ACK_5:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  RD_DATA;
            else
                state   <=  state;
        RD_DATA:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  N_ACK;
            else
                state   <=  state;
        N_ACK:
            if(cnt_i2c_clk == 3)
                state   <=  STOP;
            else
                state   <=  state;
        STOP:
            if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
                state   <=  IDLE;
            else
                state   <=  state;
        default:    state   <=  IDLE;
    endcase

// ack:应答信号
always@(*)
    case    (state)
        IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
        WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK:
            ack <=  1'b1;
        ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
            if(cnt_i2c_clk == 2'd0)
                ack <=  sda_in;
            else
                ack <=  ack;
        default:    ack <=  1'b1;
    endcase

// i2c_scl:输出至i2c设备的串行时钟信号scl
always@(*)
    case    (state)
        IDLE:
            i2c_scl <=  1'b1;
        START_1:
            if(cnt_i2c_clk == 2'd3)
                i2c_scl <=  1'b0;
            else
                i2c_scl <=  1'b1;
        SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
        ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
            if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
                i2c_scl <=  1'b1;
            else
                i2c_scl <=  1'b0;
        STOP:
            if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
                i2c_scl <=  1'b0;
            else
                i2c_scl <=  1'b1;
        default:    i2c_scl <=  1'b1;
    endcase

// i2c_sda_reg:sda数据缓存
always@(*)
    case    (state)
        IDLE:
            begin
                i2c_sda_reg <=  1'b1;
                rd_data_reg <=  8'd0;
            end
        START_1:
            if(cnt_i2c_clk <= 2'd0)
                i2c_sda_reg <=  1'b1;
            else
                i2c_sda_reg <=  1'b0;
        SEND_D_ADDR:
            if(cnt_bit <= 3'd6)
                i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
            else
                i2c_sda_reg <=  1'b0;
        ACK_1:
            i2c_sda_reg <=  1'b1;
        SEND_B_ADDR_H:
            i2c_sda_reg <=  byte_addr[15 - cnt_bit];
        ACK_2:
            i2c_sda_reg <=  1'b1;
        SEND_B_ADDR_L:
            i2c_sda_reg <=  byte_addr[7 - cnt_bit];
        ACK_3:
            i2c_sda_reg <=  1'b1;
        WR_DATA:
            i2c_sda_reg <=  wr_data[7 - cnt_bit];
        ACK_4:
            i2c_sda_reg <=  1'b1;
        START_2:
            if(cnt_i2c_clk <= 2'd1)
                i2c_sda_reg <=  1'b1;
            else
                i2c_sda_reg <=  1'b0;
        SEND_RD_ADDR:
            if(cnt_bit <= 3'd6)
                i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
            else
                i2c_sda_reg <=  1'b1;
        ACK_5:
            i2c_sda_reg <=  1'b1;
        RD_DATA:
            if(cnt_i2c_clk  == 2'd2)
                rd_data_reg[7 - cnt_bit]    <=  sda_in;
            else
                rd_data_reg <=  rd_data_reg;
        N_ACK:
            i2c_sda_reg <=  1'b1;
        STOP:
            if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
                i2c_sda_reg <=  1'b0;
            else
                i2c_sda_reg <=  1'b1;
        default:
            begin
                i2c_sda_reg <=  1'b1;
                rd_data_reg <=  rd_data_reg;
            end
    endcase

// rd_data:自i2c设备读出数据
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rd_data <=  8'd0;
    else    if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
        rd_data <=  rd_data_reg;

// i2c_end:一次读/写结束信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_end <=  1'b0;
    else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
        i2c_end <=  1'b1;
    else
        i2c_end <=  1'b0;

// sda_in:sda输入数据寄存
assign  sda_in = i2c_sda;
// sda_en:sda数据写入使能信号
assign  sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
                    || (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
                    ? 1'b0 : 1'b1;
// i2c_sda:输出至i2c设备的串行数据信号sda
assign  i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;

endmodule

2.2 PCF8591_ad模块 

PCF8591接线如下

此模块负责控制PCF8591模块输出采样后数据,这里引用野火FPGA征途Pro代码,需要注意的是我们这里要从外部获取电压信号进行ad转换,所以模拟输入通道要选择IN1-3,即AD/DA控制字要为010000101

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/04/01
// Module Name   : pcf8591_adda
// Project Name  : ad
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : AD电压测量模块
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  pcf8591_ad
(
    input   wire            sys_clk     ,   //输入系统时钟,50MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire            i2c_end     ,   //i2c设备一次读/写操作完成
    input   wire    [7:0]   rd_data     ,   //输出i2c设备读取数据

    output  reg             rd_en       ,   //输入i2c设备读使能信号
    output  reg             i2c_start   ,   //输入i2c设备触发信号
    output  reg     [15:0]  byte_addr   ,   //输入i2c设备字节地址
    output  wire    [19:0]  po_data         //数码管待显示数据
);

//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
//parameter     define
parameter   CTRL_DATA   =   8'b0100_0001;   //AD/DA控制字
parameter   CNT_WAIT_MAX=   18'd6_9999  ;   //采样间隔计数最大值
parameter   IDLE        =   3'b001,
            AD_START    =   3'b010,
            AD_CMD      =   3'b100;

//wire  define
wire    [31:0]  data_reg/* synthesis keep */;   //数码管待显示数据缓存

//reg   define
reg     [17:0]  cnt_wait;   //采样间隔计数器
reg     [4:0]   state   ;   //状态机状态变量
reg     [7:0]   ad_data ;   //AD数据

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_wait:采样间隔计数器
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_wait    <=  18'd0;
    else    if(state == IDLE)
        if(cnt_wait == CNT_WAIT_MAX)
            cnt_wait    <=  18'd0;
        else
            cnt_wait    <=  cnt_wait + 18'd1;
    else
        cnt_wait    <=  18'd0;

//state:状态机状态变量
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
        case(state)
            IDLE:
                if(cnt_wait == CNT_WAIT_MAX)
                    state   <=  AD_START;
                else
                    state   <=  IDLE;
            AD_START:
                state   <=  AD_CMD;
            AD_CMD:
                if(i2c_end == 1'b1)
                    state   <=  IDLE;
                else
                    state   <=  AD_CMD;
            default:state   <=  IDLE;
        endcase

//i2c_start:输入i2c设备触发信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_start   <=  1'b0;
    else    if(state == AD_START)
        i2c_start   <=  1'b1;
    else
        i2c_start   <=  1'b0;

//rd_en:输入i2c设备读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rd_en   <=  1'b0;
    else    if(state == AD_CMD)
        rd_en   <=  1'b1;
    else
        rd_en   <=  1'b0;

//byte_addr:输入i2c设备字节地址
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        byte_addr   <=  16'b0;
    else
        byte_addr   <=  CTRL_DATA;

//ad_data:AD数据
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        ad_data <=  8'b0;
    else    if(i2c_end == 1'b1) //(state == AD_CMD) && (i2c_end == 1'b1))
        ad_data <=  rd_data;

//data_reg:数码管待显示数据缓存
assign  data_reg = ((ad_data * 330) >> 4'd8);//shao0

//po_data:数码管待显示数据
assign  po_data = data_reg[19:0];

endmodule

2.3 v_weigh 电压转质量模块

PCF8591T采集的电压值与称重质量成线性,找到比例关系后设计v_weigh模块将电压转换为质量,本设计质量传感器电压输出经外接电压放大器放大后与质量比例系数为6:5,可根据传感器输出关系进行更改。代码实现如下

module v_weigh(
	input [19:0]v_data, //输入电压数据
	input sys_clk,
	input sys_rst_n,
	output reg [8:0]g_data //输出9位质量信号
);

always@(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		g_data<=9'd0; //初始输出信号赋值
	else
		g_data<=(v_data*5)/6;  //电压、质量比例转换
		
endmodule 

2.4 weighing_pre 去皮模块

去皮的具体流程即当qp按键按下时,此时将质量显示归零,相当于剔除秤盘质量,在此为零刻度进行称重。但实际传感器受到的压力仍为秤盘与称重物品质量之和。

module weighing_pre(
	input [8:0]weigh,  //从转换模块得到的质量数据
	input sys_clk,
	input sys_rst_n,
	input qp, //去皮信号输入
	output wire [8:0]now_weigh //去皮后现实的质量
	
);

reg [8:0]qz; //

//qp去皮信号有效后,将此刻质量赋值qz,表示秤盘质量
always@(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		qz<=9'd0;
	else if(qp)
		qz<=weigh;

//组合逻辑,立即赋值,剔除秤盘质量			
assign now_weigh=weigh-qz; 

endmodule
		
		

2.5 mcx 计价模块

征途Pro开发板的外设按键资源有限,这里采用一个按键控制计价的选择,每当按下一次按键,计价数据加1,比如你想要3块钱1克,触发三次按键即可。

module mcx(
	input cf, //计价按键输入信号
	input sys_clk,
	input sys_rst_n,
	output reg[6:0]mx //计按下按键次数

);

//每按下一次cf按键,计价数据加1
always@(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		mx<=7'd0;
	else if(cf)
		mx<=mx+1'd1;
	
endmodule
	

2.6 money 价格输出模块

上个模块给出了称重物品多少钱1克,此模块实现价格的输出

module money(
	input sys_clk,
	input sys_rst_n,
	input ok, //ok按键,当计价选定好后,触发此按键得到价格
	input [8:0]now_weigh, //质量输入
	input [6:0]mx, //计价输入
	output reg[9:0]moy //价格输出
);

//价格计算
always@(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		moy<=10'd0;
	else if(ok)
		moy<=now_weigh*mx;
	
endmodule

2.7 chose 数码管选择显示模块

chose模块主要功能是数码管显示的信号的选择,因为项目涉及数码管显示的数据不唯一,有质量显示、计价显示、价格显示,而数码管有限,所以需要设计一个模块负责控制什么时候数码管显示什么数据。

module chose (
	input sys_clk,
	input sys_rst_n,
	input sign_mo, //价格显示信号
	input sign_mx, //计价显示信号
	input [9:0]mo, //价格输入
	input [6:0]mx, //计价输入
	input [8:0]now_weigh, //质量输入
	output reg[19:0]data //输出给数码管的数据
	
);

//这里利用if,else if优先级特性,如果价格显示信号有效,优先显示价格,其次显示计价,最后优先级显示质量
//达到效果:初始计价信号和价格信号均无效,时刻显示质量,接着触发计价按键输入多少钱一克的计价数据,最后触发ok键,因其最高优先级,所以此时模块输出数据为价格数据。利用此模块达到数码管多数据切换利用目的
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
	data<=20'd0;
	else if(sign_mo) //如果价格信号有效
		data<={10'd0,mo};
	else if(sign_mx) //如果计价信号有效
		data<={13'd0,mx};
	else 
		data<={11'd0,now_weigh};
		
endmodule

2.8 sign_give 信号提供模块

上个模块控制数码管显示什么数据,用到了sign_mo、sign_mx信号,实现数据输出何种信号的控制。sign_give模块负责产生上面的两个控制信号。思路也很简单,当ok键按下代表数码管要显示价格,将sign_mo信号拉高,同理,当计价按键按下,sign_mx信号拉高。

module sign_give(
	input cf_in, //消抖后的计价按键信号输入
	input ok_in, //消抖后的价格按键信号输入
	input sys_clk,
	input sys_rst_n,
	output reg sign_mo, //价格显示控制信号
	output reg sign_mx //计价显示控制信号
);

always@(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)begin
		sign_mo<=0;
      sign_mx<=0;end
	else if(ok_in)
		sign_mo<=1;
	else if(cf_in)
		sign_mx<=1;
endmodule

数码管驱动显示模块同样采用野火FPGA征途Pro代码,无需更改,直接引用即可。若大家有需求,我后续上传完整代码

2.9 buffer 报警模块

报警实现也很简单,当输入质量超出规定200克将蜂鸣器对应的端口置为有效的高电平,其他时刻无效

module buffer (
	input sys_clk,
	input sys_rst_n,
	input [8:0]weigh,
	output reg buff
);
always@(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		buff<=1'b0;
	else if(weigh>200)
		buff<=1'b1;
	else
		buff<=1'b0;
		
endmodule

2.10 顶层设计

到这里所有的子模块就设计完毕了,接着只需要再创建个顶层文件进行连线即可

module weighing(
	input wire sys_clk,
	input wire sys_rst_n,
	input wire cf,
	input wire ok,
	input wire qp,
	//input wire [8:0]weigh,
	output wire stcp,   
   output wire shcp,   
   output wire ds,   
   output wire oe,  
	output wire buff,
	output  wire            i2c_scl     ,   //输出至i2c设备的串行时钟信号scl
   inout   wire            i2c_sda        //输出至i2c设备的串行数据信号sda
);

wire [8:0]now_weigh;
wire [6:0]mx;
wire [9:0]moy;
wire sign_we;
wire sign_mo;
wire sign_mx;
wire [19:0]data;
wire cf1;
wire ok1;
wire qp1;

wire [8:0]gdata;

wire            i2c_clk     ;   //i2c驱动时钟
wire            i2c_start   ;   //i2c触发信号
wire    [15:0]  byte_addr   ;   //i2c字节地址
wire            i2c_end     ;   //i2c一次读/写操作完成
wire    [ 7:0]  rd_data     ;   //i2c设备读取数据
wire    [19:0]  data1        ;   //数码管待显示数据
wire            rd_en       ;   //读使能信号

parameter    DEVICE_ADDR    =   7'h48           ;   //i2c设备地址
parameter    SYS_CLK_FREQ   =   26'd50_000_000  ;   //输入系统时钟频率
parameter    SCL_FREQ       =   18'd250_000     ;   //i2c设备scl时钟频率

v_weigh v_weigh_inst(
	.v_data(data1),
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.g_data(gdata)
);

key_filter key_filter_inst1
(
    .sys_clk(sys_clk)     ,   //系统时钟50Mhz
    .sys_rst_n(sys_rst_n)   ,   //全局复位
    .key_in(cf)      ,   //按键输入信号
    .key_flag(cf1)        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

key_filter key_filter_inst2
(
    .sys_clk(sys_clk)     ,   //系统时钟50Mhz
    .sys_rst_n(sys_rst_n)   ,   //全局复位
    .key_in(ok)      ,   //按键输入信号
    .key_flag(ok1)        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

key_filter key_filter_inst3
(
    .sys_clk(sys_clk)     ,   //系统时钟50Mhz
    .sys_rst_n(sys_rst_n)   ,   //全局复位
    .key_in(qp)      ,   //按键输入信号
    .key_flag(qp1)        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

buffer buffer_inst(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.weigh(gdata),
	.buff(buff)
);

weighing_pre weighing_pre_inst(
	.weigh(gdata),//data1
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.qp(qp1),
	.now_weigh(now_weigh)	
);

money money_inst(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.ok(ok1),
	.now_weigh(now_weigh),
	.mx(mx),
	.moy(moy)
);

mcx mcx_inst(
	 .cf(cf1),
	 .sys_clk(sys_clk),
	 .sys_rst_n(sys_rst_n),
	 .mx(mx)
);

chose chose_inst(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	//.sign_we(sign_we),
	.sign_mo(sign_mo),
	.sign_mx(sign_mx),
	.mo(moy),
	.mx(mx),
	.now_weigh(now_weigh),
	.data(data)
);

sign_give sign_give_inst(
	.cf_in(cf1),
	.ok_in(ok1),
	//.qp_in(qp1),
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.sign_mo(sign_mo),
	//.sign_we(sign_we),
	.sign_mx(sign_mx)
);

seg_595_dynamic seg_595_dynamic_inst
(
                .sys_clk(sys_clk)     , //系统时钟,频率50MHz
                .sys_rst_n(sys_rst_n)   , //复位信号,低有效
					 .data(data)        , //数码管要显示的值
                .point(6'b000000)       , //小数点显示,高电平有效
                .seg_en(1'b1)      , //数码管使能信号,高电平有效
                .sign(1'b0)        , //符号位,高电平显示负号
                .stcp(stcp)        , //数据存储器时钟
                .shcp(shcp)        , //移位寄存器时钟
                .ds(ds)          , //串行数据输入
                .oe(oe)            //使能信号

);

//------------- pcf8591_adda_inst -------------
pcf8591_ad  pcf8591_ad_inst
(
    .sys_clk     (i2c_clk   ),  //输入系统时钟,50MHz
    .sys_rst_n   (sys_rst_n ),  //输入复位信号,低电平有效
    .i2c_end     (i2c_end   ),  //i2c设备一次读/写操作完成
    .rd_data     (rd_data   ),  //输出i2c设备读取数据

    .rd_en       (rd_en     ),  //输入i2c设备读使能信号
    .i2c_start   (i2c_start ),  //输入i2c设备触发信号
    .byte_addr   (byte_addr ),  //输入i2c设备字节地址
    .po_data     (data1      )   //数码管待显示数据
);

//------------- i2c_ctrl_inst -------------
i2c_ctrl
#(
    .DEVICE_ADDR    (DEVICE_ADDR    ),  //i2c设备器件地址
    .SYS_CLK_FREQ   (SYS_CLK_FREQ   ),  //i2c_ctrl模块系统时钟频率
    .SCL_FREQ       (SCL_FREQ       )   //i2c的SCL时钟频率
)
i2c_ctrl_inst
(
    .sys_clk        (sys_clk        ),   //输入系统时钟,50MHz
    .sys_rst_n      (sys_rst_n      ),  //输入复位信号,低电平有效
    .wr_en          (               ),  //输入写使能信号
    .rd_en          (rd_en          ),  //输入读使能信号
    .i2c_start      (i2c_start      ),  //输入i2c触发信号
    .addr_num       (1'b0           ),  //输入i2c字节地址字节数
    .byte_addr      (byte_addr      ),  //输入i2c字节地址
    .wr_data        (               ),  //输入i2c设备数据

    .rd_data        (rd_data        ),  //输出i2c设备读取数据
    .i2c_end        (i2c_end        ),  //i2c一次读/写操作完成
    .i2c_clk        (i2c_clk        ),  //i2c驱动时钟
    .i2c_scl        (i2c_scl        ),  //输出至i2c设备的串行时钟信号scl
    .i2c_sda        (i2c_sda        )   //输出至i2c设备的串行数据信号sda
);




endmodule

三、烧录验证

设计侧重不在传感器模块,可以选择其他称重传感器替代文章传感器。因本设计的传感器信号较弱,所以在其输出后加了一个放大器。

放20g砝码相当于秤盘质量,此时数码管显示质量

按下去皮按键,去掉秤盘质量,显示置零

继续放两个20g砝码,显示40g

当按下计价键,此时数码管因为if语句优先级显示计价数据,假设1g3块钱,即按计价按键3次

按ok键,显示最终价格 3*40g=120

按复位键,显示秤盘+称重物质量之和

最后,个人能力有限,若有哪些描述不准确或有错误,希望大家多多包涵!

后续会不断发布一些相关设计。

标签:wire,FPGA,clk,电子秤,sys,verilog,rst,input,i2c
From: https://blog.csdn.net/m0_64758206/article/details/144341164

相关文章

  • 厨房电子秤芯片CSU18M88研发应用
    厨房电子秤是我们日常生活中非常熟悉的一种设备,而厨房电子秤方案设计主要由ADC芯片、压力传感器以及主控芯片等元器件设计开发而成。其中主控芯片CSU18M88也是我们今天要介绍的重要部分。主控芯片在厨房电子秤的方案设计中处于主导地位,它是一个类似于电脑CPU的核心部件。......
  • HDLBits-Verilog:Clock
    Youareprovidedamodulewiththefollowingdeclaration:moduledut(inputclk);Writeatestbenchthatcreatesoneinstanceofmoduledut(withanyinstancename),andcreateaclocksignaltodrivethemodule'sclkinput.Theclockhasaperi......
  • 家用厨房电子秤芯片解决方案研发设计
    厨房电子秤方案采用国产ADC芯片和主控MCU以及压力传感器等核心芯片设计开发而成,性能稳定的同时也保证其储存空间满足厨房秤软件的存放及应用。在芯片选型中,选择SIC8833,这款芯片是一个带24bitADC的8位RISCMCU,内置8k×16位OTP程序存储器。具体24位双向I/O口的特性,广泛应用于电......
  • 电子秤方案SIC8833芯片方案设计与研发
    厨房电子秤是通过主要元器件压力传感器和ADC芯片、MCU控制等多种芯片设计开发。采用高精度传感器、ADC芯片和先进的数据处理技术,可将物体的重量以千克和磅为单位进行准确测量和记录,同时它还具备低压报警、过载提示、自动归零等功能。在做厨房电子秤方案开发时,设置厨房电......
  • FPGA云服务器
    FPGA云服务器是一种将现场可编程门阵列(FPGA)硬件资源作为服务提供的云计算服务。FPGA是一种可编程的硬件设备,可以根据特定应用程序需求进行定制化配置,因此在需要高性能并行计算的场景中非常有优势。FPGA的基本概念FPGA(FieldProgrammableGateArray)是一种可编程的硬件......
  • 厨房电子秤应用方案设计
    厨房电子秤是通过主要元器件压力传感器和ADC芯片、MCU控制等多种芯片设计开发。采用高精度传感器、ADC芯片和先进的数据处理技术,可将物体的重量以千克和磅为单位进行准确测量和记录,同时它还具备低压报警、过载提示、自动归零等功能。咖啡机的芯片上采用的CSU1182集成SOC......
  • 【Verilog HDL】如何正确地进行移位操作?逻辑移位、算数移位
    【VerilogHDL】如何正确地进行移位操作?逻辑移位、算数移位为什么要移位操作移位操作逼近常数乘除法如何正确移位为什么要移位操作在FPGA中,数据的存储、逻辑运算、算数运算等都是以二进制的形式完成的,这就表明移位操作所需要的时间和占用的资源会非常少。举例:移......
  • 课程设计(FPGA交通灯)
    完全开源免费,供学弟学妹参考项目一:交通信号灯控制器的设计设计任务:模拟十字路口交通信号灯的工作过程(1)主干道和支干道交替通行,主干道先运行“绿灯-黄灯-左拐-红灯”,然后支干道再运行“绿灯-黄灯-左拐-红灯”。(2)主干道上的绿灯时间为40秒,支干道的绿灯时间为30秒,黄灯时间为5......
  • Hello-FPGA CoaXPress Over Fiber Host FPGA IP Core DataSheet
    CoaXPress-over-Fiber(CoF)是现有CoaXPress规范的一个重要扩展,旨在支持通过光纤进行传输。CoaXPress(CXP)是高带宽计算机视觉应用的事实标准。CoaXPress2.0规定了CXP-12速度,这是一种通过同轴铜缆实现的12.5Gbps(每秒千兆比特)链接。由于链路聚合在CoaXPress中很常见,因......
  • 基于CPLD/FPGA的呼吸灯效果实现(附全部verilog源码)
    一、功能介绍此设计可以让你的FPGA板子上那颗LED具有呼吸效果,像智能手机上的呼吸灯一样。以下源码已上板验证通过,大家可直接使用。二、呼吸灯Verilog源码ps1.带★号处可根据需要进行修改.ps2.有需要的话可自行添加rst复位信号. /**************************************......