刘佑红 发表于 2024-9-10 08:20:35

请指点一下8H硬件I2C驱动TM1637

本帖最后由 DebugLab 于 2024-9-10 09:24 编辑

    在做蓄电池容量测试器,之前是采用STC8H单片机直接驱动数码管方式,但为了方便安装,决定采用TM1637驱动5位数码管方式。TM1637采用IO口模拟I2C驱动的教程较多,我也能学着顺利驱动,但考虑减轻单片机资源占用及深入学习硬件I2C,决定尝试一下硬件I2C。多次阅读STC手册及论坛中的有关帖子,改写出TM1637的驱动代码如下(单片机采用STC1K8H08T),采用中断方式,因PCB板滞后得的关系,还没有实际测试,先请论坛内老师给看一下,重点看一下读取TM1637按键值的部分是否可行,是否存在问题及是否还可以优化。#ifndef __TM1637_H__
#define __TM1637_H__
#include <intrins.h> //调用_nop_函数

sbit SCL=P1^5;
sbit SDA=P1^4;
bit busy;

unsigned char code LED[] ={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,//0-9
                        0x77,      //A 10
                        0x3E,      //U 11
                        0x76,      //H-12
                        0x73,      //P 13
                        0x38,      //L 14
                        0x40,         //- 15
                        0x79,         //E 16
                        0x00         //消隐 17
                        };

void TM1637_Start( )      //1637 开始
{
      busy = 1;
      I2CMSCR = 0x81;   //发送START命令0x81=1000 00010001开始命令
      while (busy);
}

void TM1637_ack()            //1637 应答
{
      busy = 1;
      I2CMSCR = 0x83;      //发送读ACK命令0x83=1000 00110011接收ACK命令
      while (busy);
}

void TM1637_Stop()          //1637 停止
{
      busy = 1;
      I2CMSCR = 0x86;       //发送STOP命令0x86=1000 01100110停止命令
      while (busy);
}

void TM1637_Write(unsigned char dat)      //写一个字节
{
      I2CTXD = dat;            //写数据到数据缓冲区   I2CTXD
      busy = 1;
      I2CMSCR = 0x82;       //发送数据命令0x82=1000 00100010发送数据命令
      while (busy);
}

unsigned char TM1637_ScanKey()       //读按键值(这个我没有找到参考,仅凭个人理解写的)
{               
      TM1637_Start();
      TM1637_Write(0x42);         //向1637发送读按键扫描命令
      TM1637_ack();               
      TM1637_Stop();      
      busy = 1;
       I2CMSCR = 0x84;               //向单片机发送接收数据命令      
      while (busy);
      return I2CRXD;             //返回读取的按键数值(原始值,高低位不再调整)
}

void TM1637_Display(unsigned char a,unsigned char b,unsigned char c,unsigned char d,unsigned char e)//按顺序显示
{
      TM1637_Start();
      TM1637_Write(0x40);          //写数据+自动地址加1+普通模式
      TM1637_ack();
      TM1637_Stop();

      TM1637_Start();
      TM1637_Write(0xC0);          //设置显示首地址即第一个LED,5位数码管
      TM1637_ack();
      TM1637_Write(LED);
      TM1637_ack();
      TM1637_Write(LED);
      TM1637_ack();
      TM1637_Write(LED);
      TM1637_ack();
      TM1637_Write(LED);
      TM1637_ack();
      TM1637_Write(LED);
      TM1637_ack();
      TM1637_Stop();
}

void TM1637_ligh(unsigned char L)//显示亮度
{
      TM1637_Start();
      TM1637_Write(0x88|(L-1));   //开显示,写入亮度级别
      TM1637_ack();
      TM1637_Stop();
}

#endif

DebugLab 发表于 2024-9-10 09:35:32

之前写的1637驱动数码管测试可以用的
#define SDA Pxx
#define SCL Pxx
unsigned char Buff;

void I2C_Start(void)
{
SCL=1;
SDA=1;
_nop_();
SDA=0;
_nop_();
}

void I2C_Stop(void)
{
SCL=1;
SDA=0;
_nop_();
SDA=1;
_nop_();
}

void I2C_Send(unsigned char temp)
{
unsigned char i;
SCL=0;
_nop_();
for(i=0;i<8;i++)
{
if((temp>>i)&0x01)
SDA=1;
else
SDA=0;
_nop_();
SCL=1;
_nop_();
SCL=0;
_nop_();
}
SCL=1;
_nop_();
SCL=0;
_nop_();
}

void Display(unsigned char temp)
{
unsigned char i;
I2C_Start();
I2C_Send(0x40);
I2C_Stop();
I2C_Start();
I2C_Send(0xc0);
for(i=0;i<4;i++)
{
I2C_Send(~Buff);
}
I2C_Stop();
I2C_Start();
if(temp<8)
I2C_Send(0x88|temp);
else
I2C_Send(0x8f);
I2C_Stop();
}

void Display(void)
{
Buff=Seg;
Buff=Seg;
Buff=Seg;
Buff=Seg;
Display(5);
}

刘佑红 发表于 2024-9-10 10:28:28

DebugLab 发表于 2024-9-10 09:35
之前写的1637驱动数码管测试可以用的

谢谢你的回复,我想弄明白的是单片机硬件I2C方式的。

DebugLab 发表于 2024-9-10 10:32:28

本帖最后由 DebugLab 于 2024-9-10 10:34 编辑

刘佑红 发表于 2024-9-10 10:28
谢谢你的回复,我想弄明白的是单片机硬件I2C方式的。#define         SCL                P32
#define         SDA                P33
bit                I2C_Busy;
      P_SW2|=EAXFR;
      P3M0=0x00;
      P3M1=0x00;
      P5M0=0x00;
      P5M1=0x00;
      P3PU=0x0c;
      I2CCFG=0xC1;      //921.6K@11.0592M
      I2CMSCR=EMSI;
      I2CMSST=0x00;

void I2C_Start(void)
{
      I2C_Busy=1;
      I2CMSCR=0x81;
      while(I2C_Busy);
}

void I2C_SendData(unsigned char dat)
{
      I2CTXD=dat;
      I2C_Busy=1;
      I2CMSCR=0x82;
      while(I2C_Busy);
}

void I2C_RecvACK(void)
{
      I2C_Busy=1;
      I2CMSCR=0x83;
      while(I2C_Busy);
}

unsigned char I2C_RecvData(void)
{
I2C_Busy=1;
I2CMSCR=0x84;
while(I2C_Busy);
return I2CRXD;
}

void I2C_SendACK(void)
{
I2CMSST=0x00;
I2C_Busy=1;
I2CMSCR=0x85;
while(I2C_Busy);
}

void I2C_SendNAK(void)
{
I2CMSST=0x01;
I2C_Busy=1;
I2CMSCR=0x85;
while(I2C_Busy);
}

void I2C_Stop(void)
{
      I2C_Busy=1;
      I2CMSCR=0x86;
      while(I2C_Busy);
}

void I2C_Isr(void) interrupt 24
{
      if(I2CMSST&MSIF)
      {
                I2CMSST&=~MSIF;
                I2C_Busy=0;
      }
}

刘佑红 发表于 2024-10-5 08:43:38

反馈一下进展

本帖最后由 刘佑红 于 2024-10-5 08:48 编辑

      使用模拟I2C顺利驱动了数码管,读取按键也正常,1637的驱动是参照天微电子提供的例程稍加修改的,但里面有2us、3us、5us的延时,在读取按键时甚至使用了两个30us长延时,我是用于电池容量测试器的,系统的主要精力在于ADC,还需不停的读取按键值,程序中的大量长延时严重拖累了系统,所以我才考虑硬件I2C。但经过多次摸索,硬件I2C始终无法点亮数码管,反复阅读天微电子的器件手册,感觉其不适用STC8H的硬件I2C驱动(?),又发现数据读写及传输没有要求那么长的延时,于是就尝试将延时改为不同数目的_nop_();然后进行实际测试,最后发现把2-30us的延时改为一个_nop_();也能正常驱动数码管,按键读取也正常。把长延时改为一个空周期,拖累系统的问题大为改观,已经满足我的需要了(起码心理没有别扭了),在发帖回复时又仔细看了一下楼上朋友的回复,才发现他已经将不同的延时改为一个_nop_();了!不认真看帖的教训呀?
      发一下我改的驱动,请朋友指点与借鉴。
#ifndef __TM1637_H__
#define __TM1637_H__
#include <intrins.h>         //调用_nop_函数

sbit SCL=P1^5;
sbit SDA=P1^4;

unsigned char code LEDA[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,//0-9
                      0x77,                        //A 10
                      0x3E,                        //U 11
                      0x76,                        //H-12
                      0x73,                        //P 13
                      0x38,                        //L 14
                      0x40,                        //- 15
                      0x79,                        //E 16
                      0x00                        //消隐 17
                      };


void TM1637_Start(void)         //1637开始
{
      SCL = 1;
      SDA = 1;
      _nop_();
      SDA = 0;
}

void TM1637_ack(void)      //1637应答
{
      SCL = 0;
      _nop_();      //在第八个时钟下降沿之后,开始判断 ACK 信号
      while(SDA);
      SCL = 1;
      _nop_();
      SCL = 0;
}

void TM1637_Stop(void)//1637停止
{
      SCL = 0;
      _nop_();
      SDA = 0;
      _nop_();
      SCL = 1;
      _nop_();
      SDA = 1;
}

void TM1637_Write(unsigned char oneByte)    //写一个字节
{
      unsigned char i;
      for(i = 0;i<8;i++)
      {
                SCL = 0;
                if(oneByte&0x01) {SDA = 1;}      //低位在前
                else      {SDA = 0;}
                _nop_();
                oneByte = oneByte>>1;
                SCL = 1;
                _nop_();
      }
}

unsigned char TM1637_ScanKey(void)       //读按键
{
      unsigned char rekey,i;
      TM1637_Start();
      TM1637_Write(0x42);                     //读按键命令
      TM1637_ack();
      SDA = 1;                           //在读按键前拉高数据线
      for(i=0;i<8;i++)                           //从低位开始读
                {
                        SCL = 0;
                        rekey = rekey>>1;
                        _nop_();
                        SCL = 1;
                        if(SDA) {rekey = rekey|0x80;}
                        else {rekey = rekey|0x00;}
                        _nop_();
                }
      TM1637_ack();
      TM1637_Stop();
      return (rekey);
}

void TM1637_Display(unsigned char a,unsigned char b,unsigned char c,unsigned char d,unsigned char e,unsigned char dp)//按顺序显示xxxx
{
      TM1637_Start();
      TM1637_Write(0x40);             //写数据+自动地址加1+普通模式
      TM1637_ack();
      TM1637_Stop();

      TM1637_Start();
      TM1637_Write(0xC0);               //设置显示首地址即第一个LED
      TM1637_ack();
      if(dp == 3) {TM1637_Write(LEDA | 0x80);} else {TM1637_Write(LEDA);}      //显示x.xxx,3位小数
      TM1637_ack();
      if(dp == 2) {TM1637_Write(LEDA | 0x80);} else {TM1637_Write(LEDA);}      //显示xx.xx,2位小数
      TM1637_ack();
      if(dp == 1) {TM1637_Write(LEDA | 0x80);} else {TM1637_Write(LEDA);}      //显示xxx.x,1位小数
      TM1637_ack();
      TM1637_Write(LEDA);
      TM1637_ack();
      TM1637_Write(LEDA);
      TM1637_ack();
      TM1637_Stop();
}

void TM1637_ligh(unsigned char L)      //按顺序显示
{      
      TM1637_Start();
      TM1637_Write(0x88|(L-1));      //开显示,写入亮度级别
      TM1637_ack();
      TM1637_Stop();
}

#endif

soma 发表于 2024-10-5 09:09:46

刘佑红 发表于 2024-10-5 08:43
使用模拟I2C顺利驱动了数码管,读取按键也正常,1637的驱动是参照天微电子提供的例程稍加修改的,但 ...

感觉这个函数不是很好,TM1637_Display(unsigned char a,unsigned char b,unsigned char c,unsigned char d,unsigned char e,unsigned char dp)

刘佑红 发表于 2024-10-5 09:48:06

soma 发表于 2024-10-5 09:09
感觉这个函数不是很好,TM1637_Display(unsigned char a,unsigned char b,unsigned char c,unsigned char ...

这个是解决小数点自动移位的,函数中传递的参数dp是为了控制小数点的。原来不是在1637驱动中控制小数点的,是传递LEDA[]与小数点 | 0x80后的数据,但我嫌代码行太长,看着不舒服,就在1637驱动中解决了。如有更好的请指点一下。

vb2002 发表于 2025-4-4 00:43:13

刘佑红 发表于 2024-10-5 09:48
这个是解决小数点自动移位的,函数中传递的参数dp是为了控制小数点的。原来不是在1637驱动中控制小数点的 ...

我也在弄这个. 1637 软件的驱动确实很卡,
导致识别不了按键. 或者是按键不灵敏了
你的硬件i2c驱动的怎么样了? 代码可以分享一下吗?

草木灰06 发表于 2025-4-4 21:04:39

没有明确要求,空周期做延时是常用的手法,这简单
页: [1]
查看完整版本: 请指点一下8H硬件I2C驱动TM1637