基于STM32的TM1638的按键控制以及数码管和LED灯的动态扫描
目录
前言
关于按键控制的困惑及解决方案
关于按键控制判断只按下一次
数码管和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=0if(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=0TM1638_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=0TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器_STB=1; //STB=1_STB=0; //STB=0TM1638_WriteData(0x88); //显示开,亮度第1级_STB=1; //STB=1if(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=0TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器_STB=1; //STB=1_STB=0; //STB=0TM1638_WriteData(0x88); //显示开,亮度第1级_STB=1; //STB=1TM1638_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=0TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器_STB=1; //STB=1_STB=0; //STB=0TM1638_WriteData(0x88); //显示开,亮度第1级_STB=1; //STB=1if(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=0delay_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=0TM1638_WriteData(0x44); //普通模式,固定地址,写数据到显示寄存器_STB=1; //STB=1_STB=0; //STB=0TM1638_WriteData(0x88); //显示开,亮度第1级_STB=1; //STB=1for(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展示
按键按一次自增减展示
基于STM32的TM1638的按键控制以及数码管和LED灯的动态扫描相关推荐
- ARM开发(4)基于STM32的矩阵键盘按键控制TM1629A LED显示
一 矩阵键盘按键控制TM1629ALED显示原理: 1.1 本实验实现矩阵键盘按键控制TM1629ALED显示1.2 实验思路:共阴极数码管,掌握数码管段选位选原理 熟悉TM1629A芯片手册1.3 ...
- 【基于stm32 FreeRtos的智能台灯控制】
基于stm32 FreeRtos的智能台灯控制 之前做了一个裸机版本的智能台灯,最近刚好复习一下FreeRto的一些基础知识,朋友发给了我一个功能需求刚好用来实践一下,需要的朋友可以自行下载. 完整工 ...
- 基于STM32物联网WiFi智能家居控制系统设计(原理图+源代码+系统资料)
基于STM32物联网WiFi智能家居控制系统设计(原理图+源代码+系统资料) 原理图:Altium Designer 程序编译器:keil 5 编程语言:C语言 设计编号:C0053 主要功能: 1. ...
- 【毕业设计】基于stm32的智能水杯 - 恒温控制 饮水杯 单片机 物联网 嵌入式
文章目录 0 简介 1 项目介绍 2 系统设计 3 恒温控制实现 3.1 功能描述 3.2 PID算法原理 3.2.1 P:比例 3.2.2 I:积分 3.2.3 D:微分 3.3 温控代码实现 4 ...
- 基于STM32单片机的温湿度检测报警器(数码管)(Proteus仿真+程序)
编号:27 基于STM32单片机的温湿度检测报警器(数码管) 功能描述: 本设计由STM32F103单片机最小系统+DHT11温湿度传感器+数码管显示模块+声光报警模块+独立按键组成. 1.主控制器是 ...
- 一个按键控制数码管的开和关_一个按键控制一个数码管
一个按键控制一个数码管 C 程序 我用的 pic 单片机的与 51 也没有多大出入 #include #define key1 RA0 //RA0 脚接按键 key1 unsigned char RG ...
- 51单片机和3个74HC245和6个独立按键控制24个3组LED移动亮灭
51单片机和3个74HC245和6个独立按键控制24个3组LED移动亮灭 先上proteus仿真图: 74HC245:八路数据缓冲器
- c51数码管显示汇编语言,单片机按键控制单个数码管显示汇编程序
//***************************** //按键控制单个数码管显示程序 //***************************** ORG 0 LJMP MAIN ORG ...
- 第一节:C#工业控制编程基础--点亮LED灯实验
第一节:C#工业控制编程基础–点亮LED灯实验 文章目录 第一节:C#工业控制编程基础--点亮LED灯实验 一.实验目的: C#入门基础学习. 二.实验内容: 用C#控制LED的亮灭. 三.实验步骤: ...
最新文章
- TCMalloc(Thread-Caching malloc) 基本设计原理
- buildroot mysql
- PMCAFF原创文章人气周榜第三期(原创干货由你评鉴,在评论里写下你的看法吧~)
- 开启mysql慢查询日志,不重启数据库的方法
- php根据字符串生成变量名,PHP 自定义字符串中的变量名解析
- 微软西雅图总部DevOps交流总结
- 版是什么_晴天保保超越版好不好,有什么升级?
- CSS3 Flex布局(伸缩布局盒模型)学习
- 计算机软件基础第四版,《计算机软件技术基础》徐士良(第4版)课后习题答案详解|复习笔记...
- win7电脑误删鼠标键盘驱动_Win7系统鼠标键盘驱动检测不到的三种解决方法
- 【机器学习】实验5布置:基于K-近邻的车牌号识别
- wps表格错开半行_WPS文字制作左右错行表格(运用插入分节符、分栏等功能)
- c语言 实现参数值双向传递,基于C语言函数参数传递规律的探讨
- php随机发牌游戏,JavaScript_javascript实例--教你实现扑克牌洗牌功能,我们一般都会按照顺序把随机 - phpStudy...
- matlab 固定收益证券,matlab计算固定收益证券学习(二)
- XDOC 在线word文档表格预览
- python重装之前要卸载吗_关于fedroa下安全地卸载和重装python
- 七彩虹将星x15xs 2022款 怎么样
- THANK YOU FOR YOUR CONCERN, SIOBAN AND ANTHONY
- SQL 万能本地文件分析工具
热门文章
- ubuntu 下安装 skype,google talk
- Python标准库之turtle库——基础函数详解
- Java实现用户注册手机发码送验证
- Linux系统磁盘管理总结
- chkconfig(check config)命令详解
- 未来元宇宙并不仅仅是基于单独某一项技术的发展
- 51单片机读写WTV语音芯片
- 已完成的vue项目进行seo优化 (prerender-spa-plugin + vue-meta-info)
- 微服务API网关-kong初探
- java preference xml,java-将PreferenceScreen添加到linearlayout