基于STM32的TM1638的按键控制以及数码管和LED灯的动态扫描

32 篇文章 11 订阅

目录

前言

关于按键控制的困惑及解决方案

关于按键控制判断只按下一次

数码管和LED动态扫描

关于驱动代码(HAL库加寄存器位端控制GPIO)

效果展示

数码管和led展示

按键按一次自增减展示


前言

        趁着国庆这几天想着做个与硬件的控制,于是就需要交互,LCD屏幕可以用来显示数据,而输入我想到了以前用过的矩阵键盘,但矩阵键盘需要的io太多了。于是买了TM1638模块,在网上看了很多博文,唉,一言难尽啊,驱动都是一样的,TM1638是一种类似于iic的时序但又不是iic时序,它的好处就是用三个引脚同时控制 8位数码管 8位LED灯以及8个按键 。

        LED灯和数码管的动态扫描容易实现,按键按理也简单,第一次用这个本着以跑起来为主的目的在网上看了看别人怎么做的,结果,按键方面的控制硬是没有一个可以跑的,,大多数文章基本类似(因为驱动已经写好了)。无语的是它功能确实厉害(3个引脚控制3种8位的外设),但按键方面确实我没有找到能直接抄过来就能跑的,最终我怀疑是不是我硬件的问题(在某宝买了2块,结果2块或多或少有几个led灯点不亮,led灯的测试电压是2v可以电亮,我用万用表测我的只有1.13v。。。。),于是我找了个esp32的例程成功把按键跑起来了。

附上ESP32的TM1638例程

TM1638 LED数码显示模块ARDUINO驱动代码_悟渔的博客-CSDN博客_tm1638按键怎么编码

关于按键控制的困惑及解决方案

              第一困惑:  我看博客困在了那个 DIO 这个引脚位,注释写着是输入,但为什么并没有更改GPIO的输出模式呢?在stm32里推挽输出,设置输出状态0/1后再对它进行读无效。于是,我不得不去看使用文档,后续知道DIO写数据是输出模式,读数据是输入模式,于是乎在写读时需要更改GPIO的引脚状态。

/**重新设置引脚输入输出状态**/

void Set_Pin_Mode(GPIO_TypeDef * GPIOx,u16 PIN,u32 Mode){
    
    GPIO_InitTypeDef s ={0};
    s.Pin=PIN;
    s.Mode=Mode;
    s.Speed=GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOx,&s);
}

        第二困惑:我第一困惑解决了,还是跑不起来,后面看上面的那个esp32例程里用了延时来适应高主频的MCU,比如我用STM32F103是72MHZ全速运行,所以不加延时也是跑不起来的。调用驱动里的读按键一直都是读出来KEY 为 1。

于是加延时

 /***************************************************************************************/
/**
  * TM1638读键扫数据函数
  * 参数:无
  * 返回值:读取的按键号,1~8
  */
unsigned char TM1638_ReadKey(void)  //TM1638读键扫数据函数
{     
      unsigned char c[4],i,key_value=0;

        _STB=0;  //STB=0,开始写命令  
        TM1638_WriteData(0x42);  //普通模式,地址自动增加,读键扫数据  
      
        for(i=0;i<4;i++) c[i]=TM1638_Read();  //读取键值
             
            _STB=1;  //STB=1,读键值结束

            for(i=0;i<4;i++)
                key_value|=c[i]<<i;
            for(i=0;i<8;i++)
                if((0x01<<i)==key_value)
                    break;
            if(i == 8)return 0;
            return i+1;
 
  
    
}

经过我的实践,得出:这个按键读出来则一定是被按下了,而不是受抖动,不足的是通过这个模块我只是知道这个按键被按下,而不知道它到底触发了多少次,在测试中,我按下未松开,我通过串口去打印发现按键被按下,前若干次打印出来是键值,后面则是未按下的值,断断续续地又打印出键值,我通过跑例程也是这样。

 不知道是不是时序有问题,欢迎同行赐教。

关于按键控制判断只按下一次

        尽管这样我还是想通过写算法知道它到底是被按下了一次还是被按下多次(若是考虑到那种按一次数值单位增减的需求),虽然读出来的按键不稳定,但经过我的实际测试,刚按下读出来的数必然是连续的键值,然后必定是连续的0,若还是按着不动,则有可能又是连续的键值(这种情况下一般是按了有几s之久)。所以我也给出我的算法,经过我测试按下按着不动不超过5s,一般被视为只按下了一次。如果它被按下时通过读的键值是固定那么就百分百判断只按下了一次。


void TM1638_KEY_SCAN(void){
    

    u8 static key_state=0;
    u8 temp=TM1638_ReadKey(); //实时读取
      if(key_state ==0 && temp){
      
          key_state=1;
          rkey=temp;
      }
      
      else if(key_state == 1)  { //不稳定状态 为 0 或 原缓存数值
         
          rkey=0;
      }
      if(!temp)key_state=0;
     
    
    }
    
    
    

测试按键被按下一次的测试代码(在定时器中断keytime每1s自加1,20ms判断一次,不加时间判断也可以)

if(keytime>20){
    keytime=0;
       TM1638_KEY_SCAN();
       if(rkey)SMGDT[6]=rkey; 
  
       if(rkey== 1) ++a;
       else if(rkey ==2 )--a;

}

若是不需要比较细节的这种需求,直接通过读取键值就行。不同的键值读出来不一样就很容易判断。

数码管和LED动态扫描

LED灯每20ms刷新一次,数码管刷新放定时器中断内每1s刷新一根数码管如此反复轮询。

定义缓存

u8 SMGDT[8]={10,10,10,10,10,10,10,10};
u8 LEDDT[8]={0,0,0,0,0,0,0,0};

 动态扫描,使用直接改SMGDT 和LEDDT对应位的值就行,0-7 对应LED和数码管1-8


//数码管动态扫描
void  smg_play(u8 ser,u8 val){
    
     if(val == 10) {
         TM1638_TubeOff(ser);           //关闭该位数码管
         return ;
     }
     TM1638_Tube(ser, val, 0);
    
}

//8位数码管显示
void scan_smg(void){
    u8 static s=1;
     smg_play(s,SMGDT[s-1]); 
    if(++s>8)s=1;

    
}

void scan_led(void){  
    
    for( int i= 0;i< 8;i++){      
        TM1638_Light(i+1, LEDDT[i]);
       }

       
}

在定时器里刷新数码管

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
   
    
  
    
                if(htim == &htim2){
                    
                    scan_smg();
}
}

刷新led


if(ledtime>20){

    ledtime=0;

scan_led();



}

关于驱动代码(HAL库加寄存器位端控制GPIO)

TM1638.C

 
 
#include "stm32f1xx_hal.h"
#include "tm1638.h"  //tm1638模块实现头文件
#include "delay.h"


 #define _STB    PGout(2)
 #define _CLK    PGout(3)
 #define _DIO    PGout(4)
 #define DIOIN  PGin(4)

u8 rkey=0;
u8 SMGDT[8]={10,10,10,10,10,10,10,10};
u8 LEDDT[8]={0,0,0,0,0,0,0,0};

 
unsigned char TM1638_LED[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,  //共阴极数码管段码,不带小数点
                            0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71}; //0~F,1亮0灭
 
unsigned char TM1638_LED_P[]={0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,  //共阴极数码管段码,带小数点
                            0xFF,0xEF,0xF7,0xFC,0xB9,0xDE,0xF9,0xF1}; //0~F,1亮0灭
 
/***************************************************************************************/
/**
  * TM1638写数据函数
  * 参数:data:要写入的8位数据
  * 返回值:无
  */
void TM1638_WriteData(unsigned char data) //TM1638写数据函数
{
    
  Set_Pin_Mode(GPIOG,GPIO_PIN_4,GPIO_MODE_OUTPUT_PP);
  unsigned char i;
  for(i=0;i<8;i++)
  {
 
     
      _CLK=0;     //CLK=0
    if(data&0x01)
    {
     _DIO=1; //DIO=1
    }
    else
    {
     _DIO=0; //DIO=0
    }
     
    data>>=1;
     _CLK=1; //CLK=1
  }
}
/***************************************************************************************/





 
/***************************************************************************************/
/**
  * TM1638指定地址写数据函数
  * 参数1:addr:要写入数据的地址
  * 参数2:data:要写入的8位数据
  * 返回值:无
  */
void TM1638_WriteAddressData(unsigned char addr,unsigned char data) //TM1638指定地址写数据函数
{
  
  _STB=0; //STB=0
  TM1638_WriteData(addr); //地址
  TM1638_WriteData(data); //数据
  _STB=1; //STB=1
}
/***************************************************************************************/





 
/***************************************************************************************/
/**
  * TM1638指定数码管序号与显示数字函数
  * 参数1:serial:数码管序号,1-8
  * 参数2:num:要显示的数字,0-F
  * 参数3:point:是否带小数点,Point:带,NoPoint:不带
  * 返回值:无
  */
void TM1638_Tube(unsigned char serial, unsigned char num, unsigned char point) //TM1638指定数码管序号与显示数字函数
{
  _STB=0; //STB=0
  TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器
  _STB=1; //STB=1
  
    _STB=0; //STB=0
  TM1638_WriteData(0x88); //显示开,亮度第1级
    _STB=1; //STB=1
 
  if(point == 1)  //带小数点
  {
    TM1638_WriteAddressData(0XC0+2*(serial-1),TM1638_LED_P[num]); //第serial个数码管显示num,带小数点
  }
    
  else if(point == 0)  //不带小数点
  {
    TM1638_WriteAddressData(0XC0+2*(serial-1),TM1638_LED[num]); //第serial个数码管显示num,不带小数点
  }
}
/***************************************************************************************/




 
/***************************************************************************************/
/**
  * TM1638关闭指定数码管函数
  * 参数:serial:数码管序号
  * 返回值:无
  */
void TM1638_TubeOff(unsigned char serial)  //TM1638关闭指定数码管函数
{
  _STB=0; //STB=0
  TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器
  _STB=1; //STB=1
  
  _STB=0; //STB=0
  TM1638_WriteData(0x88); //显示开,亮度第1级
  _STB=1; //STB=1
  
  TM1638_WriteAddressData(0XC0+2*(serial-1),0x00); //第serial个数码管灭
}
/***************************************************************************************/





 
/***************************************************************************************/
/**
  * TM1638指定LED灯序号num与亮灭state函数
  * 参数1:num:LED灯序号
  * 参数2:state:LED灯状态,LightOn:开,LightOff:关
  * 返回值:无
  */
void TM1638_Light(unsigned char num, unsigned char state) //TM1638指定LED灯序号num与亮灭state函数
{
    _STB=0; //STB=0
    TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器
    _STB=1; //STB=1
  
    _STB=0; //STB=0
  TM1638_WriteData(0x88); //显示开,亮度第1级
    _STB=1; //STB=1
  
  if(state == 1)
  {
    TM1638_WriteAddressData(0XC0+2*(num-1)+1,0X01); //第num个灯亮
  }
  else if(state == 0)
  {
    TM1638_WriteAddressData(0XC0+2*(num-1)+1,0X00); //第num个灯灭
  }
}
/***************************************************************************************/
 


 
/***************************************************************************************/
/**
  * TM1638读数据函数
  * 参数:无
  * 返回值:读取的8位数据
  */
unsigned char TM1638_Read(void) //读数据函数
{
  unsigned char i,temp=0;
  
   //   _DIO=1;
    Set_Pin_Mode(GPIOG,GPIO_PIN_4,GPIO_MODE_INPUT);
     delay_us(1);
   
  for(i=0;i<8;i++)
  {
    temp>>=1;
      delay_us(1);
    _CLK=0; //CLK=0
   delay_us(1);
      
    if( DIOIN == 1) //读取DIO值
      temp|=0x80; //按位或:与0或不变、与1或置1
      _CLK=1; //CLK=1
  }
  return temp;  
}
/***************************************************************************************/
 







 /***************************************************************************************/
/**
  * TM1638读键扫数据函数
  * 参数:无
  * 返回值:读取的按键号,1~8
  */
unsigned char TM1638_ReadKey(void)  //TM1638读键扫数据函数
{     
      unsigned char c[4],i,key_value=0;

        _STB=0;  //STB=0,开始写命令  
        TM1638_WriteData(0x42);  //普通模式,地址自动增加,读键扫数据  
      
        for(i=0;i<4;i++) c[i]=TM1638_Read();  //读取键值
             
            _STB=1;  //STB=1,读键值结束

            for(i=0;i<4;i++)
                key_value|=c[i]<<i;
            for(i=0;i<8;i++)
                if((0x01<<i)==key_value)
                    break;
            if(i == 8)return 0;
            return i+1;
 
  
    
}
/***************************************************************************************/






 
/***************************************************************************************/
/**
  * TM1638初始化函数
  * 参数:无
  * 返回值:无
  */
void TM1638_Init(void) //TM1638初始化函数
{
  unsigned char i;  //临时变量
                
    //已在hal库自动初始化过
    _STB=0; //STB=0
  TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器
    _STB=1; //STB=1
  
    _STB=0; //STB=0
  TM1638_WriteData(0x88); //显示开,亮度第1级
    _STB=1; //STB=1
  
  for(i=0;i<16;i++)
  {
    TM1638_WriteAddressData(0XC0+i,0X00); //全地址写入0X00
  }
}





/**重新设置引脚输入输出状态**/

void Set_Pin_Mode(GPIO_TypeDef * GPIOx,u16 PIN,u32 Mode){
    
    GPIO_InitTypeDef s ={0};
    s.Pin=PIN;
    s.Mode=Mode;
    s.Speed=GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOx,&s);
}






/***************************************************************************************/



//数码管动态扫描
void  smg_play(u8 ser,u8 val){
    
     if(val == 10) {
         TM1638_TubeOff(ser);           //关闭该位数码管
         return ;
     }
     TM1638_Tube(ser, val, 0);
    
}

//8位数码管显示
void scan_smg(void){
    u8 static s=1;
     smg_play(s,SMGDT[s-1]); 
    if(++s>8)s=1;

    
}

void scan_led(void){  
    
    for( int i= 0;i< 8;i++){      
        TM1638_Light(i+1, LEDDT[i]);
       }

       
}


void TM1638_KEY_SCAN(void){
    

    u8 static key_state=0;
    u8 temp=TM1638_ReadKey(); //实时读取
      if(key_state ==0 && temp){
      
          key_state=1;
          rkey=temp;
      }
      
      else if(key_state == 1)  { //不稳定状态 为 0 或 原缓存数值
         
          rkey=0;
      }
      if(!temp)key_state=0;
     
    
    }
    
    
    




TM1638.h

 
 #ifndef __TM1638_H_
 #define __TM1638_H_
 #include "reg.h"
 
 
 
 
void TM1638_Init(void); //TM1638初始化函数
 
void TM1638_WriteData(unsigned char data); //TM1638写数据函数
 
void TM1638_WriteAddressData(unsigned char addr,unsigned char data); //TM1638指定地址写数据函数
 
void TM1638_Tube(unsigned char serial, unsigned char num, unsigned char point); //TM1638指定数码管序号与显示数字函数
 
void TM1638_TubeOff(unsigned char serial);  //TM1638关闭指定数码管函数
 
void TM1638_Light(unsigned char num, unsigned char state); //TM1638指定LED灯序号num与亮灭state函数
 
unsigned char TM1638_Read(void); //TM1638读数据函数
 
unsigned char TM1638_ReadKey(void); //TM1638读键扫数据函数



//动态扫描 抽象出应用层
void scan_smg(void);
extern u8 SMGDT[8];
void   scan_led(void);
extern u8 LEDDT[8];

void Set_Pin_Mode(GPIO_TypeDef * GPIOx,u16 PIN,u32 Mode);
void TM1638_KEY_SCAN(void);
extern u8 rkey;
extern u8 _rkey;

#endif






寄存器位段

#ifndef REG_H
#define REG_H
#include "stm32f1xx_hal.h"

#ifndef __TYPEDEF_
#define __TYPEDEF_
typedef unsigned char u8;
typedef unsigned short u16;
typedef uint32_t  u32;


typedef const uint32_t uc32;  
typedef const uint16_t uc16;  
typedef const uint8_t uc8; 

typedef __IO uint32_t  vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t  vu8;

typedef __I uint32_t vuc32;  
typedef __I uint16_t vuc16; 
typedef __I uint8_t vuc8; 

#endif
	 
//λ������,ʵ��51���Ƶ�GPIO���ƹ���
//����ʵ��˼��,�ο�<<CM3Ȩ��ָ��>>������(87ҳ~92ҳ).
//IO�ڲ����궨��
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO�ڵ�ַӳ��
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO�ڲ���,ֻ�Ե�һ��IO��!
//ȷ��n��ֵС��16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //��� 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //���� 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //��� 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //���� 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //��� 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //���� 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //��� 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //���� 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //��� 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //����

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //��� 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //����

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //��� 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //����

#define Right       PEout(5)
#define BUFF      PBout(8)
#define Left  PBout(5)
#define LEDSTATE   PEout(1)
#define  ReadKEY0 PEin(4)


#define mKEY0 PEin(4)
#define mKEY1 PEin(3)
#define mKEY2 PFin(4)
#endif

效果展示

数码管和led展示

按键按一次自增减展示

 

 

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于STM32F7xx单片机按键控制LED亮灭的代码: 首先,需要在CubeMX中配置GPIO引脚。我们将LED连接到PA5引脚,按键连接到PA0引脚。在“Pinout & Configuration”选项卡中,将PA5设置为GPIO输出模式,将PA0设置为GPIO输入模式。然后,生成代码并打开工程。 在main.c文件中,我们需要在GPIO初始化函数中初始化PA0和PA5引脚: ``` void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin : PA0 */ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pin : PA5 */ GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 然后,我们需要在main函数中编写代码来检测按键的状态并控制LED的亮灭。以下是完整的main函数代码: ``` int main(void) { /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); /* Infinite loop */ while (1) { /* Check if button is pressed */ if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { /* Turn on LED */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); } else { /* Turn off LED */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); } } } ``` 在无限循环中,我们检测PA0引脚的状态。如果按键被按下,PA0引脚将被拉低,我们将PA5引脚设置为高电平以点亮LED。否则,我们将PA5引脚设置为低电平以关闭LED。 最后,我们需要编译和下载代码到STM32F7xx单片机中。启动单片机后,按下按键可以看到LED亮起,松开按键可以看到LED熄灭。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值