一 最重要的地址操作
1 基本模式
B7-B0
共8位,负责控制数码管的一些基本模式
,能够控制亮度,开启关闭显示,以及是否显示小数点,操作如下:
(1)调控亮度
把上述表格对应转化位具体地址:
二进制 十六进制
0001 0001 0x11 /*一级亮度,打开LED显示*/
0010 0001 0x21 /*二级亮度,打开LED显示*/
0011 0001 0x31 /*三级亮度,打开LED显示*/
0100 0001 0x41 /*四级亮度,打开LED显示*/
0101 0001 0x51 /*五级亮度,打开LED显示*/
0110 0001 0x61 /*六级亮度,打开LED显示*/
0111 0001 0x71 /*七级亮度,打开LED显示*/
0000 0001 0x01 /*八级亮度,打开LED显示*/
0000 0000 0x00 /*关闭LED显示*/
在IIC通信中,通信格式要求如下:
IIC通信格式:起始信号,模式命令(0x48 固定格式),显示命令(上表的十六进制地址),结束信号
(2)特殊的:关于B3为选择7段与8段
的作用,其实就是控制是否全显示小数点
,大家都知道,数码管一个数字位由7个灯段abcdefg
外加小数点位
组成。可能大家对这里有疑问:假如B3不设置为1,是不是后面就没法让屏幕显示小数点了呢?
其实不是的,这里设1的效果是这样的:
以8级亮度为例:
打开B3地址就变为: 0x08|0x01 //位运算,控制0x01的B3位变为1
TM1650_bright(0x08|0x01);
如果只写入地址,而不发送后续数据,效果是这样的:
而如果只是写入8级亮度:
TM1650_bright(0x01);
注意:屏幕是根本不会亮的,就是说没有后续数据时,不管几级亮度屏幕不会点亮。而若写入B3,只不过让所有小数点全都亮起来
而已,并且后续无法单独关闭其中某几个了,相当于无法设置可控的小数模式了,小数点会常亮,所以除非完全不用这四个小数点位置,否则B3位保持0就可以。
2 显示数据
(1)上图DIG1324分别代表数码管的4个数字位置,68 6A 6C 6E 可以理解为对应这4个数字位置的 起始地址。
而往一个指定(0x68 0x6A 0x6C 0x6E)的数字位置写入数字的通信格式为:
IIC发送格式:起始信号,地址码(0x68 0x6A 0x6C 0x6E其中之一),数码管段数据(1字节),结束信号
(2)地址码我们已经很而清楚了,那么数码管后面1字节的数据,是怎么转化为数字呢?怎么用一个字节让对应位置显示出像要设置的数字或小数点呢?
首先我们先了解数码管一个数字位可以点亮的位置:
可以看到共有8个位置,而一个字节是不是也是刚好8位,刚好每一位二进制
01可以控制一个位置的点亮,那问题就很简单了,搞清楚每个二进制位是对应哪一个位置就可以了。我们可以使用 1 2 4 6 8…64 这种位中只有1位是1二进制,测试一下后7位的内容:
for(int i=1;i<=64;i*=2){
TM1650_addPrint(DIG,i); //直接以10进制即可,DIGb表哪个数码管数字位
Delay_ms(400);
}
以随便一位为例,效果如下:
并且经单独测试可得出,从左往右第一位:控制的是小数点的点亮(1亮 0灭),对比可以得出下图:
那么根据这个图,来足组合出1到9,就没什么难度了,控制小数点只需要用得出的地址
和0x80
进行按位或
运算即可,也就是把地址的第一位修改为1。
以数字1 为例,只要2 和 4 点亮,那么就是在1字节的2位 和 4 位设置1 ,其余为位设0,准换为16进制表示即可,如下图:
得出0-9对应地址:
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}
同理,A-F 也一样可计算得出
二 封装功能与测试样例
1 封装函数一览
Tm1650的 SCL,SDA 分别接stm32f103c8t6的 B10,B11,有需求的宏定义
直接一键修改
//头文件:#include "Tm1650.h"
#ifndef TM1650_H_
#define TM1650_H_
//设置亮度参数
#define TM1650_BRIGHT1 0x11 /*一级亮度,打开LED显示*/
#define TM1650_BRIGHT2 0x21 /*二级亮度,打开LED显示*/
#define TM1650_BRIGHT3 0x31 /*三级亮度,打开LED显示*/
#define TM1650_BRIGHT4 0x41 /*四级亮度,打开LED显示*/
#define TM1650_BRIGHT5 0x51 /*五级亮度,打开LED显示*/
#define TM1650_BRIGHT6 0x61 /*六级亮度,打开LED显示*/
#define TM1650_BRIGHT7 0x71 /*七级亮度,打开LED显示*/
#define TM1650_BRIGHT8 0x01 /*八级亮度,打开LED显示*/
#define TM1650_DSP_OFF 0x00 /*关闭LED显示*/
//数码管位选
#define TM1650_DIG1 0
#define TM1650_DIG2 1
#define TM1650_DIG3 2
#define TM1650_DIG4 3
//是否开启小数点
#define ENABLE 1
#define DISABLE 0
//Tm1650的 SCL,SDA 分别接 B10,B11,有需求自己修改
#define IICGPIO GPIOB
#define RCC_APB2Periph_IICGPIO RCC_APB2Periph_GPIOB //需单独修改,勿忘
#define SCLPIN GPIO_Pin_10
#define SDAPIN GPIO_Pin_11
void TM1650_init(void);
void TM1650_bright(uint8_t param); //设置亮度 TM1650_BRIGHT1-8
void TM1650_showNum(int num); //显示一个4位以内的数字,右对齐,前补0
void TM1650_clear(void);
void TM1650_print(uint8_t dig,uint8_t seg_data); //第几位,显示数字几
void TM1650_pointPrint(uint8_t dig,uint8_t seg_data,uint8_t point);//第几位,显示数字几,带小数点
void TM1650_addPrint(uint8_t dig,uint8_t seg_data);//大佬请看这里:直接写入地址
void TM1650_lambLineTest(uint8_t DIG);//测试灯段损坏
#endif
2 功能实现
IIC与Tm1650封装在一起,便于移植函数功能都有介绍
#include "stm32f10x.h"
#include "Delay.h"
#include "Tm1650.h"
//显示内容阴码:0-9
const uint8_t number[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//A-F:0x77,0x7c,0x39,0x5e,0x79,0x71
//----------------------IIC设置部分------------------------
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(IICGPIO, SCLPIN, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平
Delay_us(10); //延时10us,防止时序频率超过要求
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(IICGPIO, SDAPIN, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
Delay_us(10); //延时10us,防止时序频率超过要求
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(IICGPIO, SDAPIN); //读取SDA电平
Delay_us(10); //延时10us,防止时序频率超过要求
return BitValue; //返回SDA电平
}
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_IICGPIO, ENABLE); //开启IICGPIO的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = SCLPIN | SDAPIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IICGPIO, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出
/*设置默认电平*/
GPIO_SetBits(IICGPIO, SCLPIN | SDAPIN); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
void MyI2C_Start(void)
{
MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平
MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平
MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平
MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平
MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位
{
MyI2C_W_SDA(Byte & (0x80 >> i)); //使用掩码的方式取出Byte的指定一位数据并写入到SDA线
MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDA
MyI2C_W_SCL(0); //拉低SCL,主机开始发送下一位数据
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位
{
MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量
//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA
}
return Byte; //返回接收到的一个字节数据
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线
MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位
MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit; //定义应答位变量
MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
AckBit = MyI2C_R_SDA(); //将应答位存储到变量里
MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
return AckBit; //返回定义应答位变量
}
//--------------------------------------------------
/*
功能:初始化数码管
参数:IIC: SCL-B10,SDA-B11,有需要跳转到MyI2C.c中修改
解释:IIC与设置亮度,清空显示
*/
void TM1650_init(void)
{
MyI2C_Init();
TM1650_bright(TM1650_BRIGHT5); //初始化为5级亮度,打开显示
TM1650_clear(); //将显存内容清0
}
/*
功能:设置亮度并打开显示
参数:param : TM1650_BRIGHTx,x为1-8表亮度,TM1650_DSP_OFF为关闭led显示
解释:这个操作不影响显存中的数据
*/
void TM1650_bright(uint8_t param)
{
MyI2C_Start();
MyI2C_SendByte(0x48); //通信格式:起始信号,模式命令(1字节),显示命令(1字节),结束信号
MyI2C_ReceiveAck();
MyI2C_SendByte(param);
MyI2C_ReceiveAck(); //显示命令
MyI2C_Stop();
}
/*
功能:清空数据,屏幕熄灭
解释:正常只能显示0-9,10作特殊判断,0000 0000 全部熄灭。
*/
void TM1650_clear(void)
{
uint8_t dig;
for(dig = TM1650_DIG1 ; dig<= TM1650_DIG4 ;dig++)
{
TM1650_print(dig,10);//特殊数字,将显存数据刷为0
}
}
/*
功能:往一个指定的数码管位写入指定的显示数字
参数1:DIG: TM1650_DIGX,(X:1234),对应1234位置
参数2:数字0~9
参数3:是否开启当前位小数点:ENABLE,DIAABLE
解释:通信格式:起始信号,地址码(1字节),数码管段数据(1字节),结束信号
*/
void TM1650_print(uint8_t dig,uint8_t seg_data)
{
MyI2C_Start();
MyI2C_SendByte(dig*2+0x68); //显存起始地址为0x68,dig直接用0x68,0x6A,0x6e,0x6c也行
MyI2C_ReceiveAck();
if(seg_data == 10) //clear 清空显示
MyI2C_SendByte(0);
else
MyI2C_SendByte(number[seg_data]); //数码管段数据
MyI2C_ReceiveAck();
MyI2C_Stop();
}
/*
功能:往一个指定的数码管位写入指定的显示数字,可设置小鼠点
参数1:DIG: TM1650_DIGX,(X:1234),对应1234位置
参数2:数字0~9
参数3:是否开启当前位小数点:ENABLE,DIAABLE
解释:通信格式:起始信号,地址码(1字节),数码管段数据(1字节),结束信号
*/
void TM1650_pointPrint(uint8_t dig,uint8_t seg_data,uint8_t point)
{
MyI2C_Start();
MyI2C_SendByte(dig*2+0x68); //显存起始地址为0x68,dig直接用0x68,0x6A,0x6e,0x6c也行
MyI2C_ReceiveAck();
if(seg_data == 10) //clear 清空显示
MyI2C_SendByte(0);
else if(seg_data==11){ //仅显示小数点
MyI2C_SendByte(0x80);
}
else { //显示数字+小数点
uint8_t t=number[seg_data]; //附加小数点
if(point) t=t|0x80;
MyI2C_SendByte(t); //数码管段数据
}
MyI2C_ReceiveAck();
MyI2C_Stop();
}
/*
功能:(保留地址写入功能)指定的数码管位写入指定的地址
参数1:DIG: TM1650_DIGX,(X:1234),对应1234位置
参数2:阴码地址
解释:通信格式:起始信号,地址码(1字节),数码管段数据(1字节),结束信号
*/
void TM1650_addPrint(uint8_t dig,uint8_t seg_data)
{
MyI2C_Start();
MyI2C_SendByte(dig*2+0x68); //显存起始地址为0x68,dig直接用0x68,0x6A,0x6e,0x6c也行
MyI2C_ReceiveAck();
MyI2C_SendByte(seg_data);//直接发地址
MyI2C_ReceiveAck();
MyI2C_Stop();
}
/*
功能:数字位灯段测试
参数:DIG:TM1650_DIGX,(X:1234)
解释:灯段测试,观察二进制1~64分别对应哪一位灯光变化
*/
void TM1650_lambLineTest(uint8_t DIG){
for(int i=1;i<=64;i*=2){
TM1650_addPrint(DIG,i);
Delay_ms(400);
}
TM1650_pointPrint(DIG,11,ENABLE);
Delay_ms(400);
TM1650_clear();
}
/*
功能:显示一个数字
参数:num<=9999
解释:空位补0,右对其
*/
void TM1650_showNum(int num){
int ge=num%10;
int sw=num>10?num/10%10:0;
int bw=num>100?num/100%10:0;
int qw=num>1000?num/1000:0;
TM1650_print(TM1650_DIG4,ge);
TM1650_print(TM1650_DIG3,sw);
TM1650_print(TM1650_DIG2,bw);
TM1650_print(TM1650_DIG1,qw);
}
3 demo全面测试
全亮度测试,所有灯段位置异常检测,显示4位以为数字,单独位数字设置(小数点亦可在设置数据时单独点亮),保留地址写入功能。有需求所有代码直接粘过去就行,接线想该就改一下Tm160.h
里引脚宏定义部分,很好改。不想改就SCL SDA 接 B10 B11
,应该可以直接使用。
数码管这东西,初学者看IIC,原理图和寄存器一脸懵逼,不知道如何下手,但其实也就那么一回事,硬件其实并不难,只是入门起来麻烦些,一堆原理对自学者不怎么友好,希望这篇博客能节省你的时间,(因为博主本人非常讨厌,去慢悠悠的看长视频)
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"//可不用
#include "Tm1650.h"
int main(void)
{
//oled模块,CD接的B8 B9,可不用管
OLED_Init();
OLED_ShowString(1, 1, "digital test");
TM1650_init();
while (1)
{
/*亮度测试
int speed=90;
TM1650_showNum(6666);
TM1650_bright(TM1650_BRIGHT1);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT2);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT3);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT4);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT5);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT6);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT7);Delay_ms(speed);
TM1650_bright(TM1650_BRIGHT8);Delay_ms(speed);
TM1650_bright(TM1650_DSP_OFF);Delay_ms(speed);
*/
/*每个位置灯段异常测试
TM1650_lambLineTest(TM1650_DIG1);//TM1650_DIGX: X-1324
Delay_ms(200);
TM1650_lambLineTest(TM1650_DIG2);//TM1650_DIGX: X-1324
Delay_ms(200);
TM1650_lambLineTest(TM1650_DIG3);//TM1650_DIGX: X-1324
Delay_ms(200);
TM1650_lambLineTest(TM1650_DIG4);//TM1650_DIGX: X-1324
Delay_ms(200);
*/
/*显示多位数,0-9999
int k=0;
while(k<10000){
TM1650_showNum(k++);
Delay_ms(20);
//TM1650_clear(); //后加delay可控制闪烁
}
*/
/*单个数字位 测试
for(int i=0;i<10;i++){
TM1650_print(TM1650_DIG1,i); //TM1650_DIGX: X-1324
Delay_ms(400);
}
*/
/*地址测试 0-9:0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f
TM1650_addPrint(TM1650_DIG1,0x06); // 1
Delay_ms(400);
TM1650_addPrint(TM1650_DIG1,0x80|0x06); //用1000 0000 与其余地址或运算,即可带小数点
Delay_ms(400);
*/
/*小数测试 13.14
TM1650_print(TM1650_DIG1,1);
TM1650_pointPrint(TM1650_DIG2,3,ENABLE);
TM1650_print(TM1650_DIG3,1);
TM1650_print(TM1650_DIG4,4);
*/
}
}