SPI接口的74HC595驱动数码管实现

2024-04-19 07:28

本文主要是介绍SPI接口的74HC595驱动数码管实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

摸鱼记录 Day_17      (((^-^)))

review

        前边已经学习了:

        数码管显示原理:数码管动态扫描显示-CSDN博客

        且挖了个SPI的坑坑

1.  今日份摸鱼任务

学习循环移位寄存器18 串行移位寄存器原理详解_哔哩哔哩_bilibili

学习SPI接口的74HC595驱动数码管19 SPI接口的74HC595驱动数码管实验_哔哩哔哩_bilibili

了解SPI协议:SPI协议详解(图文并茂+超详细) - 知乎 (zhihu.com)

                        SPI总线协议及SPI时序图详解 - Ady Lee - 博客园 (cnblogs.com)

2.  循环移位寄存器

        四位D触发器,输入信号1001,经过四次时钟上升沿,D0-D3 1001

        DATA是串行数据,在此结构下,每个上升沿到来,都会改变D0-D3的输出

        为了正确输出四位的串转并数据

        可在红色箭头处,添加一个使能信号,对输出进行控制

此时,使用CLK、DATA、LATCH三根信号线,即可完成将串行信号转为并行信号

3.  74HC959 循环移位寄存器

        一文搞懂74HC595芯片(附使用方法)_74hc595芯片引脚图及功能-CSDN博客

        74HC595的最重要的功能就是:串行输入,并行输出。

        其次,74HC595里面有2个8位寄存器:移位寄存器、存储寄存器。

    第一个从SER送入的bit将会从 Q7 出去

   本篇在草稿呆了很多天,因为上图SHCP  STCP的画法有一定理解上的问题

    SHCP       移位寄存器的时钟输出

    STCP       存储寄存器的时钟输出

    但是在例程中

        STCP是在数据都保存后,完成一次输出,这保证了输出数据是一个完整的

        ACZ702 配套 EDA 扩展板设计用到了芯片 74HC595,该芯片的作用是移位寄存器,通过移位的方式,节省 FPGA 的管脚。FPGA 只需要输出 3 个管脚,即可达到发送数码管数据的目的,与数码管动态扫描显示-CSDN博客的传统段选位选方式相比节省了 IO 设计资源。

        3.3V供电情况下,50MHz -----》25MHz-----》12.5MHz

4. VIO  Virtual Input/Output

        关于这个IP核可以看:Vivado中VIO IP核的使用_vivado vio-CSDN博客

        本次实验,用于设定数码管的显示内容,具体设置如下:

4.  SPI接口的74HC595驱动数码管实现 (((^-^)))

                SPI(Serial Peripheral Interface),串行外围设备接口。

                SPI是一个同步的数据总线,用单独的数据线一个单独的时钟信号来保证发送端和接收端的同步

                可以参考:SPI协议详解(图文并茂+超详细) - 知乎 (zhihu.com)

        对于74HC595,本次SPI协议,是学习SCK MOSI,无需MISO,片选默认选中

4.1   design sources

hex_8  

module hex_8(input clk,
                     input reset_n,
                     input [31:0]disp_data, 

                     //8个数码管进行显示,每个显示0~F,输入格式为disp_data = 32'h12345678
                     output reg [7:0]sel,
                     output reg [7:0]seg
                     );

        //[31:0]disp_data  16hex 4*8
        //[7:0]sel 位选信号
        //[7:0]seg 段选信号

// 1kHz分频时钟 
    reg [14:0]div_clk;
    always@(posedge clk or negedge reset_n)
    if(!reset_n) 
        div_clk <= 1'b0;
    else if(div_clk == 24999) 
        div_clk <= 1'b0;
    else 
        div_clk <= div_clk + 1'b1;
    reg disp_en;
   always@(posedge clk or negedge reset_n)
    if(!reset_n) 
        disp_en <= 1'b0;
    else if(div_clk == 24999) 
        disp_en <= 1'b1;
    else 
        disp_en <= 1'b0;    

//  位选sel
    reg[2:0]sel_num;
    always@(posedge clk or negedge reset_n)
    if(!reset_n) 
        sel_num <= 3'b000;
    else if(disp_en) 
        sel_num <= sel_num + 1'b1;
        
    always@(posedge clk or negedge reset_n)
    if(!reset_n) 
        sel <= 8'b0000_0000;
    else case(sel_num) 
         0:sel <= 8'b0000_0001;
         1:sel <= 8'b0000_0010;
         2:sel <= 8'b0000_0100;
         3:sel <= 8'b0000_1000;
         4:sel <= 8'b0001_0000;
         5:sel <= 8'b0010_0000;
         6:sel <= 8'b0100_0000;
         7:sel <= 8'b1000_0000;
    endcase   
   
// 段选seg   [31:0]disp_data  16hex 4*8
    reg [3:0] dis_tmp;
    always@(posedge clk )
    case(sel_num) //高位放前面
         0:dis_tmp <= disp_data[31:28];
         1:dis_tmp <= disp_data[27:24];
         2:dis_tmp <= disp_data[23:20];
         3:dis_tmp <= disp_data[19:16];
         4:dis_tmp <= disp_data[15:12];
         5:dis_tmp <= disp_data[11:8];
         6:dis_tmp <= disp_data[7:4];
         7:dis_tmp <= disp_data[3:0];
    endcase 
    
    always@(posedge clk )
    case(dis_tmp) 
         0:seg <= 8'hc0;
         1:seg <= 8'hf9;
         2:seg <= 8'ha4;
         3:seg <= 8'hb0;
         4:seg <= 8'h99;
         5:seg <= 8'h92;
         6:seg <= 8'h82;
         7:seg <= 8'hf8;
         8:seg <= 8'h80;
         9:seg <= 8'h90;
         4'ha:seg <= 8'h88;
         4'hb:seg <= 8'h83;
         4'hc:seg <= 8'hc6;
         4'hd:seg <= 8'ha1;
         4'he:seg <= 8'h86;
         4'hf:seg <= 8'h8e;
    endcase 

endmodule

hc595_driver   //在Verilog中,不能使用数字开头命名

module hc595_driver(
                    input clk,
                    input reset_n,
                    input [15:0]data,
                    input s_en,
                    
                    output reg sh_cp,
                    output reg st_cp,
                    output reg ds
                 );

                /启动信号s_en时,保存当前data

             reg [15:0]r_data;
            always@(posedge clk)
            if(s_en)
                r_data <= data;


    parameter CNT_MAX = 2;
   // 3.3V 状态下工作于 12.5MHz   

    reg [7:0]divider_cnt;//分频计数器
    always@(posedge clk or negedge reset_n)
    if(!reset_n)
        divider_cnt <= 0;
    else if(divider_cnt == CNT_MAX - 1'b1)
        divider_cnt <= 0;
    else
        divider_cnt <= divider_cnt + 1'b1;
        
    wire sck_plus;
    assign sck_plus = (divider_cnt == CNT_MAX - 1'b1);
        
    reg [5:0]SHCP_EDGE_CNT;
    
    always@(posedge clk or negedge reset_n)
    if(!reset_n)
        SHCP_EDGE_CNT <= 0;
    else if(sck_plus)
        begin
            if(SHCP_EDGE_CNT == 6'd32) //32 16个数据,按照SH_CP上升沿、下降沿
                SHCP_EDGE_CNT <= 0;
            else
                SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;
        end
    else
        SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
        
    always@(posedge clk or negedge reset_n)
    if(!reset_n)
        begin
            st_cp <= 1'b0;
            ds <= 1'b0;
            sh_cp <= 1'd0;
        end 
    else begin
        case(SHCP_EDGE_CNT)//重点就是线性序列机这部分分析啦

                        //SH_CP 移位寄存器的时钟

                        //在SH_CP上升沿  0->1 输出数据

                        //在SH_CP下降沿  1->0 改变数据
            0: begin sh_cp <= 0; st_cp <= 1'd0;ds <= r_data[15];end
            1: begin sh_cp <= 1; st_cp <= 1'd0;end
            2: begin sh_cp <= 0; ds <= r_data[14];end
            3: begin sh_cp <= 1; end
            4: begin sh_cp <= 0; ds <= r_data[13];end    
            5: begin sh_cp <= 1; end
            6: begin sh_cp <= 0; ds <= r_data[12];end    
            7: begin sh_cp <= 1; end
            8: begin sh_cp <= 0; ds <= r_data[11];end    
            9: begin sh_cp <= 1; end
            10: begin sh_cp <= 0; ds <= r_data[10];end    
            11: begin sh_cp <= 1; end
            12: begin sh_cp <= 0; ds <= r_data[9];end    
            13: begin sh_cp <= 1; end
            14: begin sh_cp <= 0; ds <= r_data[8];end    
            15: begin sh_cp <= 1; end
            16: begin sh_cp <= 0; ds <= r_data[7];end    
            17: begin sh_cp <= 1; end
            18: begin sh_cp <= 0; ds <= r_data[6];end    
            19: begin sh_cp <= 1; end
            20: begin sh_cp <= 0; ds <= r_data[5];end    
            21: begin sh_cp <= 1; end
            22: begin sh_cp <= 0; ds <= r_data[4];end    
            23: begin sh_cp <= 1; end
            24: begin sh_cp <= 0; ds <= r_data[3];end    
            25: begin sh_cp <= 1; end
            26: begin sh_cp <= 0; ds <= r_data[2];end    
            27: begin sh_cp <= 1; end
            28: begin sh_cp <= 0; ds <= r_data[1];end            
            29: begin sh_cp <= 1; end
            30: begin sh_cp <= 0; ds <= r_data[0];end
            31: begin sh_cp <= 1; end
            32: st_cp <= 1'd1;//最后拉高一下st_cp锁存器输出
            default:        
                begin
                    st_cp <= 1'b0;
                    ds <= 1'b0;
                    sh_cp <= 1'd0;
                end
        endcase
    end

endmodule

hex_top

module hex_top(
                clk,
                reset_n,
                sh_cp,
                st_cp,
                ds
                 );

    input clk;    //50M
    input reset_n;
    
    output sh_cp;
    output st_cp;
    output ds;
    
    wire [31:0]disp_data;
    wire [7:0] sel;//数码管位选(选择当前要显示的数码管)
    wire [7:0] seg;//数码管段选(当前要显示的内容)
    
    vio_0 vio_0 (
        .clk(clk), 
        .probe_out0(disp_data)  
    );
    
    hc595_driver hc595_driver(
        .clk(clk),
        .reset_n(reset_n),
        .data({seg,sel}),  //将段选与位选信号拼接在一起
        .s_en(1'b1),
        .sh_cp(sh_cp),
        .st_cp(st_cp),
        .ds(ds)
    );
    
    hex8 hex8(
        .clk(clk),
        .reset_n(reset_n),
        .en(1'b1),
        .disp_data(disp_data),
        .sel(sel),
        .seg(seg)
    );
    
endmodule

4.2  板级验证

//好啦, (((^-^)))

这篇关于SPI接口的74HC595驱动数码管实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/916907

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg