目录
1. 单位数码管概述
数码管的内部基本单元是发光二极管,数码管是发光器件之一,内部由七个条形发光二极管(a、b、c、d、e、f、g)和一个圆点发光二极管(dp)构成。
按照数码管的公共接线不同,数码管又可分类为共阴极数码管和共阳极数码管两种,共阴极数码管的公共端接地,而共阳极数码管的公共端接电源。
2. 对应编码
2.1 共阳数码管
数码管中所有的正极连接在一起,这个端口被称之为位选端口,其余的数码管引脚a-h都为段选端口2、共阳极数码管的编码(CA):
u8 code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e}; //0-F
2.2 共阴数码管
是数码管所有的负极连接在一起,这个端口被称之为位选端口,其余的数码管引脚a-h都为段选端口4、共阴极数码管的编码(CC/CK):
u8 code[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; //0-F
3. TM1637驱动数码管
TM1637 是一种带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU 数字接口、数据锁存器、LED 高压驱动、键盘扫描等电路。本产品性能优良,质量可靠。主要应用于电磁炉、 微波炉及小家电产品的显示屏驱动。采用DIP/SOP20的封装形式。
3.1 工作原理
微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变。数据输入的开始条件是 CLK 为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。
TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。
符号 | 管脚名称 | 管脚号 | 说明 |
DIO | 数据输入/输出 | 17 | 串行数据输入/输出,输入数据在 SLCK 的低电平变化,在SCLK 的高电平被传输,每传输一个字节芯片内部都将在第八个时钟下降沿产生一个 ACK |
CLK | 时钟输入 | 18 | 在上升沿输入/输出数据 |
K1~K2 | 键扫数据输入 | 19~20 | 输入该脚的数据在显示周期结束后被锁存 |
SG1~SG8 | 输出(段) | 2~9 | 段输出(也用作键扫描),N 管开漏输出 |
GRID6~GRID1 | 输出(位) | 10~15 | 位输出,P 管开漏输出 |
VDD | 逻辑电源 | 16 | 5V±10% |
GND | 逻辑地 | 1 | 接地 |
3.1.1 读键扫数据
在有按键按下时,读键数据如下:
SG1 | SG2 | SG3 | SG4 | SG5 | SG6 | SG7 | SG8 | |
K1 | 1110_1111 | 0110_1111 | 1010_1111 | 0010_1111 | 1100_1111 | 0100_1111 | 1000_1111 | 0000_1111 |
K2 | 1111_0111 | 0111_0111 | 1011_0111 | 0011_0111 | 1101_0111 | 0101_0111 | 1001_0111 | 0001_0111 |
注意:在无按键按下时,读键数据为:1111_1111,低位在前,高位在后。
3.1.2 显示器寄存器地址和显示模式
该寄存器存储通过串行接口从外部器件传送到TM1637 的数据,地址00H-05H共6个字节单元,分别与芯片SGE和GRID管脚所接的LED灯对应,分配如下图:
写LED显示数据的时候,按照从显示地址从低位到高位,从数据字节的低位到高位操作。
SEG1 | SEG2 | SEG3 | SEG4 | SEG5 | SEG6 | SEG7 | SEG8 | |
xxHL(第四位) | xxHU(高四位) | |||||||
B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | |
00HL | 00HU | GRID1 | ||||||
01HL | 01HU | GRID2 | ||||||
02HL | 02HU | GRID3 | ||||||
03HL | 03HU | GRID4 | ||||||
04HL | 04HU | GRID5 | ||||||
05HL | 05HU | GRID6 |
3.2 时序
3.2.1 指令数据传输过程(读案件数据时序)
注意:先读低位在读高位
3.2.2 写SRAM数据地址自动加1模式
Command1:设置数据
Command2:设置地址
Data1~N:传输数据
Command3:控制显示
3.2.3 写SRAM数据固定地址模式
Command1:设置数据
Command2:设置地址
Data1~N:传输数据
Command3:控制显示
3.3 数据指令
指令用来设置显示模式和LED 驱动器的状态。
在CLK下降沿后由DIO输入的第一个字节作为一条指令。经过译码,取最高B7、B6两位比特位以区别不同的指令。
B7 | B6 | 指令 |
0 | 1 | 数据命令设置 |
1 | 0 | 显示控制命令设置 |
1 | 1 | 地址命令设置 |
如果在指令或数据传输时发送STOP命令,串行通讯被初始化,并且正在传送的指令或数据无效(之前传送的指令或数据保持有效)。
3.3.1 数据命令设置
3.3.2 显示控制命令设置
3.3.3 地址命令设置
3.4 程序流程图
3.4.1 采用地址自动加一模式的程序流程图
3.4.2 采用固定地址的程序设计流程图
4. 代码编写
开始前可以先了解一下IIC的用法,以及相关时序:
TM1637采用的是IIC通信,但是又不是标准的IIC通信,标准的IIC协议是从高位到低位传输的,即MSB方式,但是TM1637介绍也介绍到工作原理时曾介绍到,其是从低位到高位进行数据传输。
4.1 准备配置
4.1.1 C语言调用文件
STM32的编写,有些需要调用C语言的头文件才能正常使用:
#include <string.h>//提供了一些用于处理字符串和内存块的函数。
#include <stdint.h>//定义了具有确定大小的整数类型。
#include <stdbool.h>//提供布尔类型支持。
4.1.2 宏定义
这个宏定义了一个名为 TUBE_DISPLAY_NULL 的常量,值为 26。通常情况下,这个值可能用于表示一个不显示的状态或占位符,在显示器的控制代码中用来指示数字管不应显示任何有效数字:
#define TUBE_DISPLAY_NULL 26
这个宏定义了一个名为 TUBE_DISPLAY_DECIMAL_POINT_OFFSET 的常量,值为 16。这个值可能用于表示小数点在显示器中的位置偏移量。例如,在一个七段显示器上,使用这个偏移量可以帮助确定小数点的位置,以便正确显示浮点数或需要小数点的数值:
#define TUBE_DISPLAY_DECIMAL_PIONT_OFFSET 16
4.1.3 段码表
用于存储七段显示器中每个数字和字母的段码:
const uint8_t u8NumTab[] =
{
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, b, C, d, E, F,
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//0., 1., 2., 3., 4., 5., 6., 7., 8., 9. Null
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x00
};
4.1.4 数码管地址表
//最左至最右数码管 ,依次为0-3号,对应的显示寄存器地址
const uint8_t u8TubeAddrTab[] =
{
0xC0,0xC1,0xC2,0xC3
};
4.1.5 引脚初始化
由于这里我们使用的软件I2C不受引脚限制,随便找两个普通的GPIO口就可以使用,首先我们随机找两个引脚对其进行初始化:
void TM1637_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出
/*设置默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
4.1.6 电平配置
/**
* 函 数:I2C写SCL引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
*/
void TM1637_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C写SDA引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
*/
void TM1637_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C读SDA引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
*/
uint8_t TM1637_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平
Delay_us(10); //延时10us,防止时序频率超过要求
return BitValue; //返回SDA电平
}
4.2 IIC相关配置
4.2.1 起始信号
起始条件:SCL高电平期间,SDA从高电平切换到低电平。
根据上图虚线框柱的地方进行配置:
void TM1637_Start(void)
{
TM1637_W_SDA(1); //释放SDA,确保SDA为高电平
TM1637_W_SCL(1); //释放SCL,确保SCL为高电平
TM1637_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
TM1637_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
4.2.2 终止信号
终止条件:SCL高电平期间,SDA从低电平切换到高电平。
同理,根据上图虚线框柱的地方进行配置:
void TM1637_Stop(void)
{
TM1637_W_SCL(0); //拉低SCL,确保SCL为低电平
TM1637_W_SDA(0); //拉低SDA,确保SDA为低电平
TM1637_W_SCL(1); //释放SCL,使SCL呈现高电平
TM1637_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
4.2.3 接收应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
//应答信号
uint8_t TM1637_ReceiveAck(void)
{
uint8_t AckBit; //定义应答位变量
TM1637_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
TM1637_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
AckBit = TM1637_R_SDA(); //将应答位存储到变量里
TM1637_W_SCL(0); //拉低SCL,开始下一个时序模块
return AckBit; //返回定义应答位变量
}
4.2.4 发送一个字节
发送一个字节: SCL低电平期间,主机将数据位依次放到SDA线上(高位先行) ,然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
主机首先在SCL低电平,主机如果想发送0,就拉低SDA到低电平,主机如果想发送1,就放手,SDA回弹到高电平,在SCL低电平期间允许改变SDA的电平,如图黄色部分:
但是这里需要注意标准的IIC协议是从高位到低位传输的,但是TM1637是从低位到高位进行数据传输,因此:
// 向 TM1637 发送一个字节数据
void TM1637_Write_Byte(uint8_t data)
{
uint8_t i;
TM1637_W_SCL(0); // 设置时钟线 SCL 为低电平,准备开始发送数据
// 循环发送 8 位数据
for (i = 0; i < 8; i++)
{
// 根据数据的最低位决定数据线 SDA 的状态
if(data & 0x01)
{
TM1637_W_SDA(1); // 发送 1
}
else
{
TM1637_W_SDA(0); // 发送 0
}
data = data >> 1; // 右移数据,处理下一个位
TM1637_W_SCL(1); // 拉高时钟线,表示位数据有效
TM1637_W_SCL(0); // 拉低时钟线,准备发送下一个位
}
}
4.3 TM1637相关配置
为了方便地对任意一个数码管写入数据,我们采用固定地址的方式,其流程如下:
相关时序:
4.3.1 发送写显存的数据命令
// 向 TM1637 数码管写入命令
void TM1637_WriteCmd(uint8_t u8Cmd)
{
// 开始信号,表示通信开始
TM1637_Start();
// 发送命令字节
TM1637_Write_Byte(u8Cmd);
// 接收确认应答
TM1637_ReceiveAck();
// 停止信号,表示通信结束
TM1637_Stop();
}
4.3.2 向指定地址写入数据
// 向指定地址写入数据
// u8Addr: 要写入的地址
// u8Data: 要写入的数据
void TM1637_WriteData(uint8_t u8Addr, uint8_t u8Data)
{
// 开始信号,表示通信开始
TM1637_Start();
// 发送地址字节
TM1637_Write_Byte(u8Addr);
// 接收确认应答
TM1637_ReceiveAck();
// 发送数据字节
TM1637_Write_Byte(u8Data);
// 接收确认应答
TM1637_ReceiveAck();
// 停止信号,表示通信结束
TM1637_Stop();
}
4.3.3 在四个数码管上显示数据
// 在四个数码管上显示数据
// sData: 显示数据结构体,包含四个数码管的显示值
void TM1637_TubeDisplay(TM1637Tube_ts sData)
{
uint8_t temp[4], i;
// 根据 sData 中的值,从 u8NumTab 数组中获取对应的段码
temp[0] = u8NumTab[sData.tube0]; // 获取第一个数码管的段码
temp[1] = u8NumTab[sData.tube1]; // 获取第二个数码管的段码
temp[2] = u8NumTab[sData.tube2]; // 获取第三个数码管的段码
temp[3] = u8NumTab[sData.tube3]; // 获取第四个数码管的段码
// 遍历四个数码管,逐个写入数据
for (i = 0; i < 4; i++)
{
// 向每个数码管的地址写入对应的段码
TM1637_WriteData(u8TubeAddrTab[i], temp[i]);
}
}
结构体TM1637Tube_ts,该结构体存放到.h中,若是存放到.c文件需要将其放到头文件后面,防止TM1637_TubeDisplay()函数搜索不到:
typedef struct
{
uint8_t tube0;
uint8_t tube1;
uint8_t tube2;
uint8_t tube3;
}TM1637Tube_ts;
4.3.4 设置亮度
//设置亮度
//0x88为开显示,u8Brt亮度
void TM1637_SetBrightness(uint8_t u8Brt)
{
TM1637_WriteCmd(0x88 | u8Brt);
}
4.3.5 显示开关
//显示开关
//0x88为开显示,0x80关显示
void TM1637_Switch(bool bState)
{
bState ? TM1637_WriteCmd(0x88) : TM1637_WriteCmd(0x80);
}
4.3.6 初始化配置
void Display_Init(void)
{
TM1637_Switch(0);//关显示
TM1637_SetBrightness(0x87);//设置亮度,开显示
TM1637_WriteCmd(0x44);//写数据到寄存器,固定地址模式
memset(&sDisplayData, 0xFF, sizeof(sDisplayData));
}
4.3.7 显示配置
void Display_TubeDataProcess(uint16_t u16Data)
{
memset(&sDisplayData, 0xFF, sizeof(sDisplayData));
if (u16Data > 9999)
{
u16Data = 9999;//最多四位数
}
if (u16Data > 999)//四位数
{
sDisplayData.tube0 = (uint8_t)(u16Data / 1000);//千位
sDisplayData.tube1 = (uint8_t)(u16Data / 100 % 10);//百位
sDisplayData.tube2 = (uint8_t)(u16Data % 100 / 10);//十位
sDisplayData.tube3 = (uint8_t)(u16Data % 10);//个位
}
else if (u16Data > 99)//三位数
{
sDisplayData.tube0 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube1 = (uint8_t)(u16Data / 100);//百位
sDisplayData.tube2 = (uint8_t)(u16Data / 10 % 10);//十位
sDisplayData.tube3 = (uint8_t)(u16Data % 10);//个位
}
else if (u16Data > 9)//两位数
{
sDisplayData.tube0 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube1 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube2 = (uint8_t)(u16Data / 10);//十位
sDisplayData.tube3 = (uint8_t)(u16Data % 10);//个位
}
else//一位数
{
sDisplayData.tube0 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube1 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube2 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube3 = (uint8_t)u16Data;//个位
}
TM1637_TubeDisplay(sDisplayData);
}
4.3.8 TM1637.h文件配置
以上代码为.c文件配置,下面是.h文件配置:
#ifndef _TM1637_H_
#define _TM1637_H_
#include <stdbool.h>
typedef struct
{
uint8_t tube0;
uint8_t tube1;
uint8_t tube2;
uint8_t tube3;
}TM1637Tube_ts;
void TM1637_Init(void);
void TM1637_Start(void);
void TM1637_Stop(void);
uint8_t TM1637_ReceiveAck(void);
void TM1637_Write_Byte(uint8_t data);
void TM1637_WriteCmd(uint8_t u8Cmd);
void TM1637_WriteData(uint8_t u8Addr, uint8_t u8Data);
void TM1637_TubeDisplay(TM1637Tube_ts sData);
void TM1637_SetBrightness(uint8_t u8Brt);
void TM1637_Switch(bool bState);
void Display_Init(void);
void Display_TubeDataProcess(uint16_t u16Data);
#endif
4.4 主函数
#include "stm32f10x.h"
#include "Delay.h"
#include "TM1637.h"
int main(void)
{
TM1637_Init();
Display_Init();
while (1)
{
Display_TubeDataProcess(1234);
}
}
标签:SCL,void,uint8,STM32,数码管,SDA,TM1637 From: https://blog.csdn.net/MANONGDKY/article/details/143433994