当前位置: 首页 > news >正文

基于STM32通过TM1637驱动4位数码管详细解析(可直接移植使用)

目录

1.  单位数码管概述

2.  对应编码

2.1  共阳数码管

2.2  共阴数码管

3.  TM1637驱动数码管

3.1  工作原理

3.1.1  读键扫数据

3.1.2  显示器寄存器地址和显示模式

3.2  时序

3.2.1  指令数据传输过程(读案件数据时序)

3.2.2  写SRAM数据地址自动加1模式

3.2.3  写SRAM数据固定地址模式

3.3  数据指令

3.3.1  数据命令设置

3.3.2  显示控制命令设置

3.3.3  地址命令设置

3.4  程序流程图

3.4.1  采用地址自动加一模式的程序流程图

3.4.2  采用固定地址的程序设计流程图

4.  代码编写

4.1  准备配置  

4.1.1  C语言调用文件

4.1.2  宏定义

4.1.3  段码表

4.1.4  数码管地址表

 4.1.5  引脚初始化

4.1.6  电平配置 

4.2  IIC相关配置

4.2.1  起始信号

4.2.2  终止信号

4.2.3  接收应答

4.2.4  发送一个字节

4.3  TM1637相关配置

4.3.1  发送写显存的数据命令

4.3.2  向指定地址写入数据

4.3.3  在四个数码管上显示数据

4.3.4  设置亮度

4.3.5  显示开关

4.3.6  初始化配置

4.3.7  显示配置

4.3.8  TM1637.h文件配置

4.4  主函数


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逻辑电源165V±10%
GND逻辑地1接地

3.1.1  读键扫数据

        在有按键按下时,读键数据如下:

SG1SG2SG3SG4SG5SG6SG7SG8
K11110_11110110_11111010_11110010_11111100_11110100_11111000_11110000_1111
K21111_01110111_01111011_01110011_01111101_01110101_01111001_01110001_0111

注意:在无按键按下时,读键数据为:1111_1111,低位在前,高位在后。

3.1.2  显示器寄存器地址和显示模式

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

SEG1SEG2SEG3SEG4SEG5SEG6SEG7SEG8
xxHL(第四位)xxHU(高四位)
B0B1B2B3B4B5B6B7
00HL00HUGRID1
01HL01HUGRID2
02HL02HUGRID3
03HL03HUGRID4
04HL04HUGRID5
05HL05HUGRID6

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两位比特位以区别不同的指令。

B7B6指令
01数据命令设置
10显示控制命令设置
11地址命令设置

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

3.3.1  数据命令设置

3.3.2  显示控制命令设置

3.3.3  地址命令设置

3.4  程序流程图

3.4.1  采用地址自动加一模式的程序流程图

3.4.2  采用固定地址的程序设计流程图

4.  代码编写

        开始前可以先了解一下IIC的用法,以及相关时序:

STM32F1之I2C通信_stm32f1 i2c-CSDN博客

STM32F1之I2C通信·软件I2C代码编写-CSDN博客

        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. Null0xBF,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高电平期间读取SDAAckBit = 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);}
}

基于STM32通过SN74HC595驱动4位数码管详细解析-CSDN博客


http://www.mrgr.cn/news/70126.html

相关文章:

  • blenderFds代码解读
  • 代码随想录第二十三天| 39. 组合总和 40.组合总和II 131.分割回文串
  • 287. 寻找重复数(二分查找)
  • hive的tblproperties支持修改的属性
  • 2024 年Postman 如何安装汉化中文版?
  • Flink_DataStreamAPI_输出算子Sink
  • js中const讲解
  • SQLite 全文检索:快速高效的文本查询方案
  • PGMP-串串040506 效益管理相关方争取治理
  • ESP32-S3模组上跑通esp32-camera(11)
  • 腾讯首个3D生成大模型Hunyuan3D-1.0分享
  • 算法求解 -- (炼码 3853 题)检查是否有路径经过相同数量的0和1
  • WIndows搭建NGINX环境
  • Python学习从0到1 day26 第三阶段 Spark ⑤ 搜索引擎日志分析
  • [C++] 函数详解
  • 嵌入式面试八股文(六)·ROM和RAM的区别、GPIO的八种工作模式、串行通讯和并行通讯的区别、同步串行和异步串行的区别
  • 声学中频率概念
  • 云计算在智能交通系统中的应用
  • 【LLM Agents体验 2】利用Dify本地部署Qwen2.5:7B大模型的安装指南
  • Python 第三方库 PyQt5 的安装
  • 科研绘图系列:R语言多个图形组合(scatterplot heatmap)
  • 【题解】—— LeetCode一周小结45
  • Maven 项目模板
  • Python学习从0到1 day27 第三阶段 Spark ⑤ 搜索引擎日志分析
  • iOS问题记录 - 503 Service Temporarily Unavailable
  • TypeScript 中的三斜杠指令语法