TM1637与四位七段数码管显示的应用与解析
1. 硬件介绍
在项目中使用的四位七段数码管显示设备,采用了TM1637驱动IC,成本较低。它仅需PIC的两个输出引脚进行控制,再加上VCC和地,总共只需四个连接,相比之前使用12个输出引脚的四位七段数码管显示设备,大大简化了连接。
不过,TM1637驱动IC使用的通信协议是一个问题。该协议类似I2C协议,但并非I2C协议,是Titan工程师自行设计的。其数据手册对该协议的解释不够清晰,给使用带来了一定困难。
TM1637是一个20引脚的设备,最多可控制六个七段数码管,还能接收数字键盘的输入。在本项目中,我们用它来控制四个七段数码管。控制TM1637时,需先发送一系列命令字节,再发送一系列数据字节,以控制七段数码管中LED的亮灭。
2. 命令与寄存器
TM1637主要有四个命令,如下表所示:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Usage |
| — | — | — | — | — | — | — | — | — |
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 写入数据到显示寄存器,正常模式,自动增加地址指针 |
| 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 读取按键扫描数据 |
| 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 固定地址 |
| 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 测试模式 |
我们主要使用正常模式,即命令0b01000000,可向显示寄存器写入数据。地址指针从我们设定的地址开始,写入当前显示寄存器数据后会自动增加地址。
七段数码管的数据寄存器有六个,其地址如下表:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Usage |
| — | — | — | — | — | — | — | — | — |
| 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | CH0 显示0 |
| 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | CH1 显示1 |
| 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | CH2 显示2 |
| 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | CH3 显示3 |
| 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | CH4 显示4 |
| 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | CH5 显示5 |
由于我们只使用四个数码管,所以使用前四个地址:
1. first: 0b11000000
2. second: 0b11000001
3. third: 0b11000010
4. fourth: 0b11000011
3. 亮度控制
可通过合适的命令字节设置显示亮度,亮度变化通过脉冲宽度调制信号改变LED两端电压实现。我们将LED设置为最大亮度。若命令的第3位设置为逻辑‘0’,显示将关闭。亮度命令如下表:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Usage |
| — | — | — | — | — | — | — | — | — |
| 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1/16,PWM设置为1/16 |
| 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 2/16,PWM设置为2/16 |
| 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 4/16,PWM设置为4/16 |
| 1 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 10/16,PWM设置为10/16 |
| 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 11/16,PWM设置为11/16 |
| 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 12/16,PWM设置为12/16 |
| 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 13/16,PWM设置为13/16 |
| 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 14/16,PWM设置为14/16 |
4. 通信协议与操作步骤
要与TM1637正确通信,需遵循其自制协议,具体步骤如下:
-
通信线路
:有两条通信线,clk是我们需创建的伪时钟信号,频率要低于250kHz;dio是向TM1637传输信息的线路,数据只能在时钟信号为高电平时改变。这两条线都是PIC的输出引脚,可使用任意可用输出。
-
起始信号
:
1. 时钟线初始为低电平,将其置为逻辑‘1’。
2. 同时,将dio线从低电平驱动到高电平。
3. 产生2ms延迟。
4. 之后,将dio线拉回低电平。dio线在时钟为高电平时从高到低的转变,会被TM1637解释为起始信号。
-
发送命令
:发送命令0b01000000或0X40,将TM1637置于正常模式。
-
确认信号
:TM1637会返回确认信号,在第8个时钟信号的负沿或下降沿开始,将dio线拉低,表示已接收命令。
-
停止信号
:发送停止信号,在时钟线为高电平时将dio线从低电平驱动到高电平,让TM1637有时间进入正常工作模式。
-
再次发送起始信号
:重复起始信号的创建步骤。
-
发送寄存器地址
:发送要写入的第一个寄存器地址,即CH0的地址0b11000000或0XC0,TM1637会打开该寄存器,准备接收数据。发送数据后,会自动打开下一个地址寄存器。
-
发送数据
:依次发送控制四个七段数码管LED亮灭的数据,每次发送后TM1637都会返回确认信号。
-
最终操作
:发送最后一个确认信号后,发送停止信号。再发送一个起始信号,接着发送设置显示亮度的命令,最后发送停止信号结束通信。
该过程与I2C协议类似,但起始和停止信号不同,且信息是先发送最低有效位(LSB),而I2C先发送最高有效位(MSB)。
5. 程序实现
程序分为三个部分:
1.
设置时钟时间
:使用三个开关,分别为PORTA的第0位(增量)、第1位(减量)和第2位(设置)。
2.
显示实际时间
:持续1秒在显示设备上显示实际时间。
3.
显示温度
:接下来1秒显示温度(°C),程序使用TC74确定温度。
时钟和dio线的两个输出分别为:
- clk: PORTB的第0位
- dio: PORTB的第1位
以下是完整代码:
/*
* File: the 4 7seg display
Author: Mr H. H. Ward
displays the time and then the temperature
using the PIC18f4525
Created 11/06/2021
*/
#include <xc.h>
#include <conFigInternalOscNoWDTNoLVP.h>
#include <i2cProtocol.h>
#define clk PORTBbits.RB0
#define dio PORTBbits.RB1
#define zero 0b00111111
#define one 0b00000110
#define two 0b01011011
#define three 0b01001111
#define four 0b01100110
#define five 0b01101101
#define six 0b01111100
#define seven 0b00000111
#define eight 0b01111111
#define nine 0b01100111
#define SO 0b01100011
#define C 0b00111001
#define incButton PORTAbits.RA0
#define decButton PORTAbits.RA1
#define setButton PORTAbits.RA2
// declare any variables
unsigned char n, m, thigh;
unsigned char secUnits, minUnits = 0, minTens = 3,
hourUnits = 0, hourTens = 0, first = zero, second =
one,third = two, fourth = one;
float sysTemperature;
//some arrays
unsigned char displaynumber [10] =
{
zero,
one,
two,
three,
four,
five,
six,
seven,
eight,
nine,
};
//some subroutines
void delay (unsigned char t)
{
for (n = 0; n < t; n++)
{
TMR0 = 0;
while (TMR0 < 255);
}
}
void delayus (unsigned char t)
{
TMR3 = 0;
while (TMR3 < t);
}
void debounce ()
{
TMR0 = 0;
while (TMR0 < 105);
}
void tmStart ()
{
clk = 1;
dio = 1;
delayus (2);
dio = 0;
}
void tmAck ()
{
clk = 0;
delayus (5);
while (dio);
clk = 1;
delayus (2);
clk = 0;
}
void tmStop ()
{
clk = 0;
delayus (2);
dio = 0;
delayus (2);
clk = 1;
delayus (2);
dio = 1;
}
void tmByteWrite (unsigned char tmByte)
{
for (m = 0; m <8; m++)
{
clk = 0;
if (tmByte & 0x01) dio = 1;
else dio = 0;
delayus (3);
tmByte = tmByte >> 1;
clk = 1;
delayus (3);
}
}
void displaySet()
{
tmStart();
tmByteWrite(0x40);
tmAck();
tmStop();
tmStart();
tmByteWrite(0xc0);
tmAck();
tmByteWrite(first);
tmAck();
tmByteWrite(second);
tmAck();
tmByteWrite(third);
tmAck();
tmByteWrite(fourth);
tmAck();
tmStop();
tmStart();
tmByteWrite(0x8f);
tmAck();
tmStop();
}
void displayMsg()
{
second = second ^(0b10000000);
tmStart();
tmByteWrite(0x40);
tmAck();
tmStop();
tmStart();
tmByteWrite(0xc0);
tmAck();
tmByteWrite(first);
tmAck();
tmByteWrite(second);
tmAck();
tmByteWrite(third);
tmAck();
tmByteWrite(fourth);
tmAck();
tmStop();
tmStart();
tmByteWrite(0x8f);
tmAck();
tmStop();
}
void setTime ()
{
//set minutes
while (setButton)
{
displaySet();
fourth = displaynumber [minUnits];
if (!incButton)debounce ();
if (!incButton)
{
if (minUnits < 9)minUnits ++;
else minUnits = 9;
fourth = displaynumber [minUnits];
while (!incButton);
}
if (!decButton )debounce ();
if (!decButton)
{
if (minUnits > 0) minUnits --;
else minUnits = 0;
fourth = displaynumber [minUnits];
while (!decButton);
}
}
debounce ();
while (!setButton);
//**************************************************
while (setButton)
{
displaySet();
third = displaynumber [minTens];
if (!incButton)debounce ();
if (!incButton)
{
if (minTens < 5)minTens ++;
else minTens = 5;
third = displaynumber [minTens];
while (!incButton);
}
if (!decButton)debounce ();
if (!decButton)
{
if (minTens > 0)minTens --;
else minTens = 0;
third = displaynumber [minTens];
while (!decButton);
}
}
debounce ();
while (!setButton);
//sethours
while (setButton)
{
displaySet();
second = displaynumber [hourUnits];
if (!incButton)debounce ();
if (!incButton)
{
if (hourUnits < 9)hourUnits ++;
else hourUnits = 9;
second = displaynumber [hourUnits];
while (!incButton);
}
if (!decButton)debounce ();
if (!decButton)
{
if (hourUnits > 0)hourUnits --;
else hourUnits = 0;
second = displaynumber [hourUnits];
while (!decButton);
}
}
//**************************************************
debounce ();
while (!setButton);
while (setButton)
{
displaySet();
first = displaynumber [hourTens];
if (!incButton)debounce ();
if (!incButton)
{
if (hourTens < 2)hourTens ++;
else hourTens = 2;
first = displaynumber [hourTens];
while (!incButton);
}
if (!decButton)debounce ();
if (!decButton)
{
if (hourTens > 0)hourTens --;
else hourTens = 0;
first = displaynumber [hourTens];
while (!decButton);
}
}
debounce ();
while (!setButton);
return;
}
void interrupt isr1 ()
{
secUnits ++;
PIR1bits.TMR1IF = 0;
displayMsg();
TMR1H = 0b10000000;
}
void main()
{
PORTA = 0;
PORTB = 0;
PORTC = 0;
PORTD = 0;
TRISA = 0XFF;
TRISB = 0;
TRISC = 0;
TRISD = 0b00000000;
ADCON0 = 0;
ADCON1 = 0X0F;
OSCCON = 0X74;
T0CON = 0XC7;
T1CON = 0b00001111;
T3CON = 0b10010001;
INTCON = 0b11000000;
PIE1bits.TMR1IE = 0;
setTime ();
MSSP2CInit();
PIE1bits.TMR1IE = 1;
while (1)
{
MSSP2Cidle ();
MSSP2CStart ();
MSSP2CWrite (0x9A);
MSSP2CWrite (0x00);
MSSP2CRestart();
MSSP2CWrite(0x9B);
thigh = MSSP2CreadNAck ();
MSSP2CStop();
if (secUnits == 60)
{
minUnits ++;
secUnits = 0;
if (minUnits == 10)
{
minUnits = 0;
minTens ++;
if (minTens == 6)
{
minTens = 0;
hourUnits ++;
if (hourTens < 2)
{
if (hourUnits == 10)
{
hourUnits = 0;
hourTens ++;
}
}
else if (hourTens == 2)
{
if (hourUnits == 4)
{
hourUnits = 0;
hourTens =0;
}
}
}
}
}
if (minUnits & (0b00000001))
{
fourth = displaynumber [minUnits];
third = displaynumber [minTens];
second = displaynumber [hourUnits];
first = displaynumber [hourTens];
}
else
{
fourth = C;
third = SO;
second = displaynumber [thigh % 10];
first = displaynumber [thigh/10];
}
}
}
6. 代码分析
- 注释与包含文件 :代码开头的1 - 7行是常规注释,8 - 9行是正常的包含文件,10行包含了自定义的i2cProtocol.h头文件,其中包含了使用I2C协议的所有子例程。
- 引脚分配 :11 - 12行将clk和dio输出分配给PORTB的第0位和第1位,用于PIC与TM1637通信。
- 数字定义 :13 - 22行定义了0 - 9的二进制值,用于控制七段数码管显示相应数字。23行定义了显示温度单位中“°”的二进制数据,24行定义了显示大写“C”的二进制值。
- 开关分配 :25 - 27行将输入开关分配给PORTA的第0、1、2位。
- 变量与数组 :29 - 31行创建了程序中使用的变量,33 - 45行创建了一个包含10个存储位置的数组,用于存储0 - 9的二进制值。
- 延时函数 :47 - 54行创建了基于32ms的可变延时函数,55 - 59行创建了基于1μs的可变延时函数,60 - 64行创建了用于开关消抖的13ms延时函数。
- 信号子例程 :65 - 71行创建了TM1637的起始信号子例程,72 - 80行创建了等待确认信号的子例程,81 - 90行创建了停止信号子例程,91 - 103行创建了向TM1637写入8位信息的子例程。
- 显示设置子例程 :104 - 126行的displaySet子例程,用于向TM1637写入数据并设置时钟显示,包括发送起始信号、命令、地址、数据和亮度设置等操作。
-
显示消息子例程
:127 - 150行的displayMsg子例程,与displaySet子例程类似,但多了一行
second = second ^(0b10000000);,用于控制第二和第三个显示之间的冒号闪烁。 - 设置时间子例程 :151 - 250行的setTime子例程,允许用户使用三个按钮(增量、减量和设置)来控制时钟的起始时间。通过多个while循环,分别设置分钟的个位、十位,小时的个位和十位。
- 中断服务程序 :251 - 257行的isr1中断服务程序,在timer1溢出时触发,每秒执行一次。每次执行时,增加secUnits的值,调用displayMsg子例程更新时间显示,并重新设置TMR1H的值。
- 主程序 :258 - 271行是正常的PIC初始化设置,272行将timer1设置为两个8位寄存器,并使用外部32.768kHz的晶体振荡器作为时钟源,273行将timer3设置为16位寄存器,计数频率为1Mhz。274行启用全局和外设中断,275行先禁用timer1的中断,276行调用setTime子例程设置时间,277行初始化MSSP以使用I2C协议,278行启用timer1的中断。279行设置一个无限循环,在循环中获取TC74的温度读数,并根据minUnits的值决定显示时间还是温度。
通过以上步骤和代码,我们可以实现使用TM1637驱动四位七段数码管显示时钟时间和温度的功能。整个过程涉及硬件连接、通信协议的遵循以及软件程序的编写和调试,需要对相关知识有深入的理解和掌握。
TM1637与四位七段数码管显示的应用与解析(续)
7. 关键代码详细解析
7.1 起始信号子例程
tmStart
void tmStart ()
{
clk = 1;
dio = 1;
delayus (2);
dio = 0;
}
-
clk = 1;:将时钟线置为高电平,确保TM1637能响应dio线的变化。 -
dio = 1;:将dio线置为高电平,为后续的下降沿做准备。 -
delayus (2);:产生2μs的延时,使时钟信号保持高电平一段时间。 -
dio = 0;:将dio线拉低,其在时钟为高电平时从高到低的转变被TM1637识别为起始信号。
7.2 确认信号子例程
tmAck
void tmAck ()
{
clk = 0;
delayus (5);
while (dio);
clk = 1;
delayus (2);
clk = 0;
}
-
clk = 0;:将时钟线拉低,产生第8个时钟信号的下降沿。 -
delayus (5);:产生5μs的延时,给TM1637足够时间将dio线拉低。 -
while (dio);:等待TM1637将dio线拉低,作为确认信号。 -
clk = 1;:将时钟线置为高电平。 -
delayus (2);:产生2μs的延时。 -
clk = 0;:将时钟线拉低。
7.3 停止信号子例程
tmStop
void tmStop ()
{
clk = 0;
delayus (2);
dio = 0;
delayus (2);
clk = 1;
delayus (2);
dio = 1;
}
-
clk = 0;:将时钟线拉低,为后续的高电平做准备。 -
delayus (2);:产生2μs的延时。 -
dio = 0;:将dio线拉低。 -
delayus (2);:产生2μs的延时。 -
clk = 1;:将时钟线置为高电平。 -
delayus (2);:产生2μs的延时。 -
dio = 1;:将dio线置为高电平,其在时钟为高电平时从低到高的转变被TM1637识别为停止信号。
7.4 字节写入子例程
tmByteWrite
void tmByteWrite (unsigned char tmByte)
{
for (m = 0; m <8; m++)
{
clk = 0;
if (tmByte & 0x01) dio = 1;
else dio = 0;
delayus (3);
tmByte = tmByte >> 1;
clk = 1;
delayus (3);
}
}
-
for (m = 0; m <8; m++):循环8次,每次发送一位数据。 -
clk = 0;:将时钟线拉低。 -
if (tmByte & 0x01) dio = 1; else dio = 0;:判断tmByte的最低位是否为1,若是则将dio线置为高电平,否则置为低电平。 -
delayus (3);:产生3μs的延时,保持当前状态。 -
tmByte = tmByte >> 1;:将tmByte右移一位,准备发送下一位数据。 -
clk = 1;:将时钟线置为高电平。 -
delayus (3);:产生3μs的延时。
8. 时间与温度显示逻辑
graph TD;
A[开始] --> B[设置时钟时间];
B --> C[进入无限循环];
C --> D[获取温度读数];
D --> E{minUnits最低位是否为1};
E -- 是 --> F[显示时间];
E -- 否 --> G[显示温度];
F --> H{secUnits是否为60};
G --> H;
H -- 是 --> I[更新时间];
H -- 否 --> C;
I --> C;
在主程序的无限循环中,首先获取TC74的温度读数。然后通过判断
minUnits
的最低位是否为1,决定显示时间还是温度。当
secUnits
达到60时,更新时间。
9. 实际应用中的注意事项
- 通信协议 :TM1637的通信协议与I2C不同,要严格按照其自制协议进行通信,特别是起始信号、停止信号和数据传输的时序。
- 延时设置 :延时时间的设置非常关键,如起始信号、确认信号和停止信号中的延时,要确保符合协议要求,否则可能导致通信失败。
- 开关消抖 :在使用开关设置时间时,要进行消抖处理,避免开关抖动引起的误操作。
- 温度读取 :使用I2C协议读取TC74的温度时,要确保I2C总线的初始化和通信正常。
10. 总结
通过对TM1637驱动IC和四位七段数码管显示的研究和实践,我们实现了使用PIC控制数码管显示时钟时间和温度的功能。整个过程涵盖了硬件选择、通信协议的理解与应用、软件程序的编写与调试等多个方面。
在硬件方面,TM1637的低引脚数设计简化了电路连接,但通信协议的特殊性增加了开发难度。在软件方面,通过合理的程序设计和子例程的编写,实现了时间设置、显示更新和温度读取等功能。
在实际应用中,要注意通信协议的严格遵循、延时时间的准确设置、开关消抖和温度读取的稳定性等问题。通过不断的调试和优化,可以提高系统的稳定性和可靠性,为相关项目的开发提供参考和借鉴。
通过深入理解和掌握TM1637的工作原理和应用方法,我们可以在更多的项目中灵活运用这一技术,实现各种显示需求。同时,对于类似的驱动IC和显示设备,也可以采用类似的分析和开发方法,提高开发效率和质量。
超级会员免费看
997

被折叠的 条评论
为什么被折叠?



