Altera EPM570 CPLD 学习(二) 74HC595驱动8位数码管

玩FPGA/CPLD肯定少不了点数码管,一个数码管有有八个段(包括小数点)和一个公共端。如果点亮8位数码管那就需要8*9=72个IO?那太浪费IO资源了,并且接几十根杜邦线也是体力活,但也不是没有解决办法,某宝上就有卖使用两片74HC595 8位串行移位寄存器来控制的8位数码管

整个模块才5个引脚,除去电源和地,就是说只用三个IO口就能驱动8位数码管了,比起刚才动辄几十个IO,用这个模块显然要“省”得多 !

那这个模块是怎样实现的呢,要看74HC595芯片的内部结构 

从内部结构逻辑图就能清楚地看到,第一级是8个级联的D触发器,SCK每来一个脉冲,当前D触发器的输出就会变成前一个D触发器的输出(即当前D触发器的输入),那么来8个SCK脉冲,8位的串行数据就能全部输入到74HC595中“保存”起来了。并且第一级的8个D触发器的输出也都分别接到了D触发器 ,当第一级的8个串行数据都输入进来后,再给第二级的D触发器一个时钟信号,那么8位数据就会同时输出了。这也实现了串行转并行的功能,一个数码管是8段,刚刚好够我们用!但我们用的是8位的数码管,那咋办呢,就用两片74HC595级联起来,一片用来控制段(控制显示什么数字),一片用来控制位(哪一位数码管显示),这不就好了嘛  

那下面就开始写代码了,还是用的EPM570核心板

建立工程,具体步骤可以看上一篇博客 

先把单纯的8位数码管的代码写一下,新建Verilog HDL文件,命名为tube_8

保存,注意文件名要和module的名字一样

module tube_8(
    clk,
    reset_n,
    disp_data,
    sel,
    seg
    );
    input clk;
    input reset_n;
    input [31:0] disp_data; //8位数码管上字符值,每个数码管占四位
    output reg [7:0] sel; //8段选
    output reg [7:0] seg; //8位选
    reg [2:0] cnt_sel; //位选,3-8译码

    reg [15:0]div_cnt; //1ms计数
    parameter clock_freq = 50_000_000;//50MHz时钟
    parameter turn_freq = 1000;//数码管切换速率1ms,即1000Hz
    parameter MCNT = clock_freq/turn_freq - 1;

    always @(posedge clk or negedge reset_n)
    if(!reset_n)
        div_cnt <= 0;
    else if(div_cnt == MCNT)
        div_cnt <= 0;
    else
        div_cnt <= div_cnt + 1'd1;

    always@(posedge clk or negedge reset_n)
    if(!reset_n)
        cnt_sel <= 0;
    else if(div_cnt == MCNT)
        cnt_sel <= cnt_sel + 1'd1;

    always@(posedge clk)
        case(cnt_sel)
            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

    reg [3:0]data_temp;//查找表

    always@(posedge clk)
        case(data_temp) //共阳数码管
            0:seg <= 8'b1100_0000; //0
            1:seg <= 8'b1111_1001; //1
            2:seg <= 8'b1010_0100;
            3:seg <= 8'b1011_0000;
            4:seg <= 8'b1001_1001;
            5:seg <= 8'b1001_0010;
            6:seg <= 8'b1000_0010;
            7:seg <= 8'b1111_1000;
            8:seg <= 8'b1000_0000;
            9:seg <= 8'b1001_0000;
            10:seg <= 8'b1000_1000;//A
            11:seg <= 8'b1000_0011;//B
            12:seg <= 8'b1100_0110;//C
            13:seg <= 8'b1010_0001;//D
            14:seg <= 8'b1000_0110;//E
            15:seg <= 8'b1000_1110;//F
            default seg <= 8'b1111_1111;
        endcase

    always@(*)
        case(cnt_sel) //每一位的数码管对应要显示的值
            0: data_temp <= disp_data[3:0];
            1: data_temp <= disp_data[7:4];
            2: data_temp <= disp_data[11:8];
            3: data_temp <= disp_data[15:12];
            4: data_temp <= disp_data[19:16];
            5: data_temp <= disp_data[23:20];
            6: data_temp <= disp_data[27:24];
            7: data_temp <= disp_data[31:28];
        endcase
endmodule

上面的代码中,输出8位的sel和8位的seg,就是分别用来控制数码管的8个位和8个段的。核心板的时钟频率是50Hz,我们设置数码管切换的频率是1000Hz,由于切换频率很高和人眼视觉暂留效应,我们并不会看到数码管在一位一位地切换,而是会清楚地观察到8位数码管上清晰稳定地显示8个数字 。定义8位的cnt_sel;每个数码管可以显示0~F,因此定义4位的data_temp用来存放数码管将要显示的数值,总共是8个数码管,因此还要有32位的disp_data用来存放8位数码管上将要显示的值。然后按照cnt_sel从0到7的不同值,分别给段码data_temp和位选sel赋值。最后根据data_temp的值来给seg赋值,这决定了数码管上要显示的字符是什么样子。我这个模块用的是共阳数码管,即公共端接到高电平,当段选为低时,对应的LED段就会点亮。以数字8为例,7个段要亮,小数点不亮,因此赋给seg的值是8b1000_0000;

接下来要写74HC595部分了,我们的目标不是写一个74HC595芯片,根据74HC595的工作原理设计一个接口。

从时序图可以看出来,每来一个时钟脉冲上升沿,数据就会读进去。并且要读入的数据最好在前一个下降沿时就给出来,这样在时钟脉冲上升沿到来时数据就会很稳定。否则在时钟脉冲上升沿到来时同时给出数据会容易出错

module hc595(
    clk,
    reset_n,
    seg,
    sel,
    dio,
    srclk,
    rclk
    );
    input clk;
    input reset_n;
    input [7:0] seg;
    input [7:0] sel;//seg8位,sel也8位
    output reg dio;//sel和seg都通过dio输入进595芯片
    output reg srclk;//每一位的dio稳定时,srclk拉高,将dio送进595
    output reg rclk;//送完8位sel8位seg,rclk拉高一下,代表发完

    parameter MCNT = 3-1;//50MHz时钟,半个周期3个脉冲,一个周期就6个,srclk时钟8.3MHz
    reg [4:0]div_cnt; //产生srclk的计数器
    reg [5:0]srclk_cnt; //最多计32个值(srclk一个周期要计2个值)

    always @(posedge clk or negedge reset_n) 
    if(!reset_n)
        div_cnt<=0;
    else if(div_cnt == MCNT) 
        div_cnt<=0;
    else div_cnt <= div_cnt + 1'd1;

    always @(posedge clk or negedge reset_n) 
    if(!reset_n)
        srclk_cnt<=0;
    else if(div_cnt == MCNT) begin
        if(srclk_cnt == 31) 
            srclk_cnt <= 0;
        else srclk_cnt <= srclk_cnt + 1'd1;
    end

    always@(posedge clk or negedge reset_n)
    if(!reset_n) begin
        rclk <= 0;
        srclk <= 1'd0;
        dio <= seg[7];
    end
    else begin
        case (srclk_cnt)
            0: begin dio <= seg[7];srclk <= 1'd0;rclk <=1'd1;end
            1: begin srclk <= 1'd1;rclk <=1'd0;end
            2: begin dio <= seg[6];srclk <= 1'd0;end
            3: begin srclk <= 1'd1;end
            4: begin dio <= seg[5];srclk <= 1'd0;end
            5: begin srclk <= 1'd1;end
            6: begin dio <= seg[4];srclk <= 1'd0;end
            7: begin srclk <= 1'd1;end
            8: begin dio <= seg[3];srclk <= 1'd0;end
            9: begin srclk <= 1'd1;end
            10: begin dio <= seg[2];srclk <= 1'd0;end
            11: begin srclk <= 1'd1;end
            12: begin dio <= seg[1];srclk <= 1'd0;end
            13: begin srclk <= 1'd1;end
            14: begin dio <= seg[0];srclk <= 1'd0;end
            15: begin srclk <= 1'd1;end
            16: begin dio <= sel[7];srclk <= 1'd0;end
            17: begin srclk <= 1'd1;end
            18: begin dio <= sel[6];srclk <= 1'd0;end
            19: begin srclk <= 1'd1;end
            20: begin dio <= sel[5];srclk <= 1'd0;end
            21: begin srclk <= 1'd1;end
            22: begin dio <= sel[4];srclk <= 1'd0;end
            23: begin srclk <= 1'd1;end
            24: begin dio <= sel[3];srclk <= 1'd0;end
            25: begin srclk <= 1'd1;end
            26: begin dio <= sel[2];srclk <= 1'd0;end
            27: begin srclk <= 1'd1;end
            28: begin dio <= sel[1];srclk <= 1'd0;end
            29: begin srclk <= 1'd1;end
            30: begin dio <= sel[0];srclk <= 1'd0;end
            31: begin srclk <= 1'd1;end
            default: rclk <= 0;
        endcase
    end
endmodule

可以很方便地用线性序列机的方法写出来。dio是要输入的数据,srclk是给芯片的时钟,rclk是传输完成的标志,也是这一个标志让8位段选和8位位选输出。

完成了数码管和74HC595的驱动后写一个测试看看效果

module nixietube(
    clk,
    reset_n,
    dio,
    srclk,
    rclk,
	key
    );
    input clk;
    input reset_n;
    reg [31:0]disp_data;
	input [1:0]key;
    output dio;//sel和seg都通过dio输入进595芯片
    output srclk;//每一位的dio稳定时,srclk拉高,将dio送进595
    output rclk;//送完8位sel8位seg,rclk拉高一下,代表发完
    wire [7:0]sel,seg;

    always @(*)
    case (key)
        0: disp_data <= 32'h01010101;
        1: disp_data <= 32'h10101010;
        2: disp_data <= 32'hab0101cd;
        3: disp_data <= 32'hee1010ff;
    endcase
	 
    hc595 hc595_inst0(
        .clk(clk),
        .reset_n(reset_n),
        .seg(seg),
        .sel(sel), //hc595和hex8中的seg和sel只要名称一样就连接起来了
        .dio(dio),
        .srclk(srclk),
        .rclk(rclk)
    );

    hex8 hex8_inst0(
        .clk(clk),
        .reset_n(reset_n),
        .disp_data(disp_data),
        .sel(sel),
        .seg(seg)
    ); 
endmodule

板子上有两个按键,用这两个按键来控制显示不同的内容。

必须将最后的这个文件设为Top-Level Entity 

 综合编译,将未使用引脚设为三态输入,Pin Planner分配引脚,再编译

综合完成,这个设计用了86个逻辑单元 

打开Programmer,下载 

  

 看看现象

 成功! 

使用两片74HC595寄存器来控制八段数码管是一种常见的电路设计方法。74HC595是一种8串行输入/并行输出的移寄存器,常用于扩展微控制器的I/O端口。以下是使用两片74HC595控制八段数码管的基本步骤和原理: ### 硬件连接 1. **连接电源和地**:将两片74HC595的Vcc引脚连接到电源(如5V),GND引脚连接到地。 2. **连接串行输入**:将第一片74HC595的DS(串行数据输入)引脚连接到微控制器的数据输出引脚(如Arduino的D11)。 3. **连接时钟信号**:将两片74HC595的SH_CP(移寄存器时钟输入)引脚连接到同一个时钟信号引脚(如Arduino的D12),ST_CP(存储寄存器时钟输入)引脚连接到同一个存储时钟信号引脚(如Arduino的D8)。 4. **级联连接**:将第一片74HC595的Q7'(串行数据输出)引脚连接到第74HC595的DS引脚,实现级联。 5. **连接数码管**:将两片74HC595的并行输出引脚(Q0-Q7)连接到八段数码管的各个段(A-G,DP)和小数点。 ### 软件控制 1. **初始化**:在代码中初始化串行数据引脚、时钟引脚和存储时钟引脚。 2. **发送数据**:通过串行方式将数据发送到74HC595。每一数据对应数码管的一个段。 3. **更新显示**:在发送完数据后,触发存储时钟信号,将数据锁存到74HC595的输出引脚上。 ### 示例代码(Arduino) ```cpp const int dataPin = 11; // DS const int clockPin = 12; // SH_CP const int latchPin = 8; // ST_CP byte digit1 = 0b00111111; // 0 byte digit2 = 0b00000110; // 1 byte digit3 = 0b01011011; // 2 byte digit4 = 0b01001111; // 3 byte digit5 = 0b01100110; // 4 byte digit6 = 0b01101101; // 5 byte digit7 = 0b01111101; // 6 byte digit8 = 0b00000111; // 7 byte digit9 = 0b01111111; // 8 byte digit0 = 0b01101111; // 9 void setup() { pinMode(dataPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(latchPin, OUTPUT); } void loop() { for (int i = 0; i < 10; i++) { digitalWrite(latchPin, LOW); shiftOut(dataPin, clockPin, MSBFIRST, digit1); shiftOut(dataPin, clockPin, MSBFIRST, digit1); digitalWrite(latchPin, HIGH); delay(1000); } } ``` ### 总结 通过使用两片74HC595寄存器,可以有效地控制八段数码管的显示。这种方法不仅节省了微控制器的I/O端口,还提高了系统的可扩展性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值