PotatoPie 2.1/3.0 教程(12) —— 实验11 FPGA实现TM1637数码管显示模块驱动-Anlogic-安路社区-FPGA CPLD-ChipDebug

PotatoPie 2.1/3.0 教程(12) —— 实验11 FPGA实现TM1637数码管显示模块驱动

1.实验说明

1.1管脚说明

本实验需要连接数码管模块,本实验以PotatoPie 3.0为例,PotatoPie 2.1只是绑定的管脚不一样请参考工程,其它都一样。

20231220103534583-image

工程中adc文件的管脚绑定如下:

20231220103727740-image

set_pin_assignment	{ tm_scl }	{ LOCATION = P94; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ tm_sda }	{ LOCATION = P92; IOSTANDARD = LVCMOS33; }

set_pin_assignment	{ rgb_led[0] }	{ LOCATION = P110; }
set_pin_assignment	{ rgb_led[1] }	{ LOCATION = P109; }
set_pin_assignment	{ rgb_led[2] }	{ LOCATION = P119; }


set_pin_assignment	{ led[0] }	{ LOCATION = P52; }
set_pin_assignment	{ led[1] }	{ LOCATION = P54; }
set_pin_assignment	{ led[2] }	{ LOCATION = P57; }
set_pin_assignment	{ led[3] }	{ LOCATION = P58; }

跟本模块相关的引脚只有这两

set_pin_assignment { tm_scl } { LOCATION = P94; IOSTANDARD = LVCMOS33; }
set_pin_assignment { tm_sda } { LOCATION = P92; IOSTANDARD = LVCMOS33; }

  • tm_scl连接模块的CLK, 即板子的P94。
  • DIO连接模块的sda,即板子的P92。

20231220103850745-5a6203f699bc6158295f85887a1100b

 

1.2实验现象

程序下载成功之后数码管模块会显示累加计数,效果如下:

 

2.实验原理

2.1 数码管的原理

数码管是一种半导体发光器件,其基本单元是发光二极管。数码管按段数一般分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管(多一个小数点显示)。当然也还有一些其他类型的数码管如“N”形管、“米”字管以及工业科研领域用的16段管、24段管等,在此就不详细介绍。

20231220111214850-image

 

 

20231220104328908-image

数码管是工程设计中实现简单,使用范围较大的显示输出器件。一个 7 段数码管(如果包括右下的
小点可以认为是
8 段)分别由 abcdefg 位段和表示小数点的 dp 位段组成。实际是由 8
LED 灯组成的,控制每个 LED 的点亮或熄灭实现数字显示。通常数码管分为共阳极数码管和共阴极数码
管,共阴
8 段数码管的信号端低电平有效,而共阳端接高电平有效。当共阳端接高电平时只要在各个
位段上加上相应的低电平信号就可以使相应的位段发光。比如:要使
a 段发光,则在 a 段信号端加上
低电平即可; 共阴极的数码管则相反。 因此数码管的控制和
LED 的控制有相似之处,在 MINI DEMO 上有
两位共阴极数码管, 数码管所有的信号都连接到
FPGA 的管脚,作为输出信号控制。 FPGA 输出不同信号
控制数码管相应位段
LED 灯的点亮或者熄灭。如下图所示是开发板上的数码管显示示意图。

20231220154111732-image

 

数码管分为共阳极数码管和共阴极数码管。共阳极数码管就是把发光二极管的正极连接在一起作为一个引脚,负极分开。相反的,共阴极数码管就是把发光二极管的阴极连接在一起作为一个引脚,正极分开。共阴8段数码管的信号端低电平有效,而共阳端接高电平有效。当共阳端接高电平时只要在各个位段上加上相应的低电平信号就可以使相应的位段发光。比如:要使a段发光,则在a段信号端加上低电平即可。共阴极的数码管则相反。

如果数码管所有的信号都连接到FPGA的管脚,作为输出信号控制。FPGA只要输出这些信号就能够控制数码管的哪一段LED亮或者灭。这样我们可以通过开关来控制FPGA的输出。下图是共阴数码管的码表。

20231220104448850-image

然而直接FPGA控制数码管比较费IO脚,特别是数码管位数一多,太没有性价比了,当然也有一些通过74HC595控制的,但功能太单一,所以我们通过了一个TM1637来控制这些数码管,如上所述只需要两个IO因为TM1637是串行接口。在FPGA中串行是大势所趋。

2.2 TM1637说明

我们先看一下TM1637模块提供的资料

20231220104804327-image

我们主要关心这三个文档,其它两个压缩包都是模块相关的参考C代码供MCU或者Arduino板使用,对FPGA参考意义不大。

  • 数码管驱动芯片规格书-TM1637_V2[1].1.pdf
  • 4-Digit Display v0.9b原理图.pdf
  • 4位0.36寸共阳数码管(带时钟点、不带小数点)引脚图-请忽略图中尺寸.jpg

TM1637 是一种带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU 数
字接口、数据锁存器、LED 高压驱动、键盘扫描等电路。本产品性能优良,质量可靠。主要应用于电磁炉、
微波炉及小家电产品的显示屏驱动。采用DIP/SOP20的封装形式。

功能特点
采用功率CMOS 工艺
显示模式(8 段×6 位),支持共阳数码管输出
键扫描(8×2bit),增强型抗干扰按键识别电路
辉度调节电路(占空比 8 级可调)
两线串行接口(CLK,DIO)
振荡方式:内置RC 振荡(450KHz+5%)
内置上电复位电路
内置自动消隐电路
封装形式:DIP20/SOP20
管脚信息

20231220105704694-image

 

看下原理图,比较简单,就是直接把数码管连到了TM1637上面。

20231220105112959-image

 

打开芯片规格书可以看到管脚定义

20231220105219771-image

由于模块本身已经固定了与芯片的连接,我们只关心CLK和DIO。所谓控制TM1637就是通过这对IO来配置TM1637的寄存器。所以我们要看下手册中的寄存器说明。

显示寄存器地址和显示模式
该寄存器存储通过串行接口从外部器件传送到TM1637 的数据,地址00H-05H共6个字节单元,分别与
芯片SGE和GRID管脚所接的LED灯对应,分配如下图:
写LED显示数据的时候,按照从显示地址从低位到高位,从数据字节的低位到高位操作。

20231220105800246-image

接口说明
微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号
必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变。数据输入的开始条件是 CLK
为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。
TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会
产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。

  • 1、指令数据传输过程如下图(读按键数据时序)

20231220105908345-image

Command:读按键指令;S0、S1、S2、K1、K2 组成按键信息编码,S0、S1、S2 为 SGn 的编码,K1、
K2 为 K1 和 K2 键的编码,读按键时,时钟频率应小于 250K,先读低位,后读高位。

  • 2、写 SRAM 数据地址自动加 1 模式

20231220110018781-image

Command1:设置数据
Command2:设置地址
Data1~N:传输显示数据
Command3:控制显示

  • 3、写 SRAM 数据固定地址模式

20231220110041623-image

Command1:设置数据

Command2:设置地址
Data1~N:传输显示数据
Command3:控制显示

数据指令
指令用来设置显示模式和LED 驱动器的状态。
在CLK下降沿后由DIO输入的第一个字节作为一条指令。经过译码,取最高B7、B6两位比特位以区别
不同的指令。

B7 B6 指令
0 1 数据命令设置
1 0 显示控制命令设置
1 1 地址命令设置

如果在指令或数据传输时发送STOP命令,串行通讯被初始化,并且正在传送的指令或数据无效(之前
传送的指令或数据保持有效)

  • 1、数据命令设置
    该指令用来设置数据写和读,B1和B0位不允许设置01或11。

    20231220110459196-image

     

  • 2、地址命令设设置
    该指令用来设置显示寄存器的地址;如果地址设为0C6H 或更高,数据被忽略,直到有效地址被设定;
    上电时,地址默认设为00H。

    20231220110512601-image

     

  • 3、显示控制

20231220110528677-image

程序流程图

  •  1、采用地址自动加一模式的程序流程图

20231220110629526-image

  • 2、采用固定地址的程序设计流程图

    20231220110737935-image

3.代码分析

数码管相关模块主要是这个seg_deci_top.v

20231220110840597-image

module seg_deci_top(
    input clk,
    input rst,
    inout tm_scl,
    inout tm_sda //,
    );
  • input clk, 系统输入时钟
  • input rst, 复位
  • inout tm_scl, TM1637通讯时钟
  • inout tm_sda ,TM1637通讯数据

模块下面有这些子模块

20231220111108109-image

 

  • decimal.v 十进制数输入模块
  • bcd.v 十进制转BCD码,用于将十进制数分解成个十百千位
  • dec_to_seg.v BCD码转数码管段码,相当于每位上数字转成数码管的显示码
  • tm1637.v TM1637的驱动模块

整体数据流就是 :

decimal.v –> bcd.v –> dec_to_seg.v –> tm1637.v

BCD码(Binary-Coded Decimal‎)

利用四个2进制位储存一个10进制的数,如下表所示。本文所讨论的问题均以8421BCD码为例,十进制的0~9分别用0000~1001来表示。

十进制数23,可表示为0010_0011,十进制数129,可表示为0001_0010_1001。

即分别对个位、十位、百位求对应的BCD码。

20231220112531365-image

二进制数到BCD码的转换
先根据输入不同位数的二进制数,求对应的BCD码

假设输入1位二进制数1,则对应的BCD码为0001,对应十进制1;

若输入2位二进制数11,则BCD码为0011,对应十进制3;

若输入3位二进制数111,则BCD码为0111,对应十进制7;

若输入4位二进制数1110,那么问题来了,BCD码范围在0000~1001之间, 是满10进位的, 它只能表示十进制数0~9,而1110对应的十进制数为14!理应转换为0001_0100才对!

那怎么才能转换成0001_0100呢?

需要对进位的时机做一些处理,

先看看以下的分析:

1110(十进制14,BCD码需要表示十位和个位)是111(十进制7)左移一位的结果,其大小等于二倍的111,同理:

1100(十进制12,BCD码需要表示十位和个位)是110(十进制6)左移一位的结果,其大小等于二倍的110,

1010(十进制10,BCD码需要表示十位和个位)是101(十进制5)左移一位的结果,其大小等于二倍的101,

1000(十进制8,BCD码只需要表示个位)是100(十进制4)左移一位的结果,其大小等于二倍的100,

左移相当于乘2, 那么当二进制数 ≥ 0101b, 即≥5(或> 0100b, 即 >4)的时候,左移以后就 ≥ 1010b , 即 ≥ 10,对应的BCD码就需要表示个位和十位了,那么对于一个 4 位的二进制数,先输入的高3位在 ≥5 (或 > 4 )的时候,要对它们处理一下,使得最低位输入进来后,表示十位的BCD码为0001。

这个处理过程, 称为:加3移位法
举例说明一下, 先设输入一个4位二进制数, 记作abcd,输出为其对应的8421BCD码.

在最低位输入前,如果高3位的 abc ≥ 0101(或 abc > 0100)时, 对其加上3(即0011), 最低位d输入, 使得加过3的高3位整体左移一位.

这相当于(abc + 0011)*2+d,即abc*2 + 6 + d,红字的部分就直接 ≥ 16 了,超过了4位2进制数表示的范围, 向更高位进一位!那么此时表示十位的BCD码为0001。

举两个实例, 更容易理解:

例1:
还是以输入为4位二进制数1110(十进制14)为例:

送入最高位得到0001;

送入第二位得到0011;

送入第三位得到0111 > 0100,在0111基础上进行修正,即0111 + 0011 = 1010;

在修正的结果上送入第四位得到1_0100,即0001_0100即为1110的BCD码(十进制14)。

例2:
再以输入为8位二进制数10100101(十进制165)为例,8位二进制数表示范围为0~255,BCD码需要表示百位、十位、个位, 将10100101b从高位到低位依次送入:

送入最高位1, 得到0001;

送入第二位0, 得到0010;

送入第三位1, 得到0101 ,因为0101 > 0100,修正:0101 + 0011 = 1000;

送入第四位0, 得到0001_0000;

送入第五位0, 得到0010_0000;

送入第六位1, 得到0100_0001;

送入第七位0, 得到1000_0010,1000 > 0100,修正:1000 + 0011 = 1011;

送入第八位1, 得到0001_0110_0101,得到输出结果为0001_0110_0101(十进制165)。

归纳为: 每次当新的一位输入前, 都需要对表示 百位/十位/个位 的连续4bit数据判断大小. 大于4, 则进行加3移位处理.

根据以上论述,bcd.v的代码就很容易理解了,本文不再赘述。

dec_to_seg.v

module dec_to_seg(
    input [3:0] dec_digit,
    output [6:0] seg
    );

    reg [6:0] seg;

    always @(dec_digit)
        case (dec_digit)
            4'h0: seg = 7'b0111111;
            4'h1: seg = 7'b0000110;
            4'h2: seg = 7'b1011011;
            4'h3: seg = 7'b1001111;
            4'h4: seg = 7'b1100110;
            4'h5: seg = 7'b1101101;
            4'h6: seg = 7'b1111101;
            4'h7: seg = 7'b0000111;
            4'h8: seg = 7'b1111111;
            4'h9: seg = 7'b1100111;
            // output 'E' on non-digits
            default: seg = 7'b1111001;
        endcase
endmodule

代码比较简单就是一个译码器,将每个数字转成数码管的段码。

tm1637.v

module tm1637(
    clk,
    rst,
    data_latch,
    data_in,
    data_stop_bit,
    busy,
    scl_en,
    scl_out,
    sda_en,
    sda_out,
    sda_in
    );
  • clk, 模块的输入时钟
  • rst, 复位
  • data_latch, 数据锁存
  • data_in, 输入模块的数据
  • data_stop_bit, 数据结束位
  • busy, 模块忙
  • scl_en, 通讯时钟使能
  • scl_out, 通讯时钟输出
  • sda_en, 数据使能
  • sda_out, 数据数输出
  • sda_in, 数据数入

代码定义了一个状态机,通过这个状态机来控制SCL和SDA电平。 对照TM1637规格书中的时序图就很容易看明白了。

 

 

请登录后发表评论

    没有回复内容