TM1637读取键值调试笔记
因为项目原因需要用到TM1637,实现驱动数码管和按键扫描,参考了网络上搜索到的一些例程,基本实现了功能要求,能够实现数码管点亮和按键扫描。
调试过程中也出现一些问题,现在描述一下问题和解决方法。
问题1:函数必须带参数,无参数无法正确读取键值
问题2:获取到的键值与LUA版本(运行在AIR724UG上的TM1637驱动程序)获取的键值有出入
关于第一个问题,如下图所示的tm1637_process函数:
如果函数定义为tm1637_process(void),即无参数函数,则无法读取到正确按键值,一共四个按键,总是有两个按键的值是相同的。
如果函数定义为tm1637_process(uint16 num),即有参数函数,则可以读取到按键值。但是也存在一个问题,系统运行一段时间后,仍然变成上述的四个按键其中两个按键键值相同的故障。
笔者尝试从TM1637的驱动时序上入手、更改驱动速率波特率等方法解决,没有效果。最后发现问题出在按键读取函数上,按键读取函数定义变量没有赋初始值。
结合程序来说,函数内使用了变量rekey,如果不赋初值,则初值会出现随机数,则读取按键值会出现错误,无法读取到正确的按键值。修复这个错误之后,tm1637_process函数是否带参数,都不影响按键读取了。
第二个问题,STM32驱动TM1637得到的四个按键值分别是0xF7 0xF6 0xF5 0xF4,但是同样的电路板使用AIR724UG读取到的按键值却分别是 0xEF 0x6F 0xAF 0x2F。
经过分析,笔者找到了其中的原因,总结起来就是,两个单片机程序都是按照TM1637的读取时序逐个bit读取按键值,但是对按键读取到的二进制数值处理方向不同。STM32处理是 左低右高 AIR724UG处理是 左高右低,具体如下表所示:
下图是AIR724UG读取到的键值
最后 附上驱动代码
TM1637.H
#ifndef _TM1637_H_
#define _TM1637_H_
//包含头文件
#include "delay.h"
#include "stm32f0xx.h"
//定义端口操作
#define SET 1 //置高
#define CLR 0 //置低
#define TM_SCL_PORT GPIOB
#define TM_SCL_PIN GPIO_Pin_8
#define TM_SDA_PORT GPIOB
#define TM_SDA_PIN GPIO_Pin_9
#define SDA_IN() GPIO_ReadInputDataBit(TM_SCL_PORT, TM_SCL_PIN)
#define SDA_H() GPIO_SetBits(TM_SCL_PORT, TM_SCL_PIN) //端口置高
#define SDA_L() GPIO_ResetBits(TM_SCL_PORT, TM_SCL_PIN) //端口置低
#define SCL_H() GPIO_SetBits(TM_SDA_PORT, TM_SDA_PIN)
#define SCL_L() GPIO_ResetBits(TM_SDA_PORT, TM_SDA_PIN)
//命令定义
#define DisCtr 0x8D //显示控制,显示开,亮度4/16
#define DisMode 0x44 //固定地址方式写显存
#define ReadKey 0x42 //读取按键寄存器
#define tm_addr_0 0xc5 //显示地址0
#define tm_addr_1 0xc4 //显示地址1
#define tm_addr_2 0xc3 //显示地址2
#define tm_addr_3 0xc2 //显示地址3
#define tm_addr_4 0xc1 //显示地址4
#define tm_addr_5 0xc0 //显示地址5
#define key_up 0x01
#define key_down 0x02
#define key_left 0x03
#define key_right 0x04
#define key_ok 0x05
#define key_ng 0x06
//函数声明
void tm1637_init(void);
void tm1637_process(void);
void tm1637_key_process(void);
uint8_t tm1637_read_key(void);
void tm1637_menu_process(uint8_t key_value);
void tm1637_show_dec(uint16_t dec_num);
void tm1637_cmd_send(uint8_t addr, uint8_t cmd, uint8_t datH, uint8_t datL);
#endif
TM1637.C
//包含intrins.h头文件,可以使用_nop_()函数
#include "tm1637.h"
#include "looplist.h"
#include "usart.h"
#define TM1637 1
#define SPC_NONE 0x00
uint16_t key_press_cnt = 0;
uint8_t key_value_new = 0;
uint8_t key_value_old = 0;
uint8_t com_tx_buf[] = { 0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xE4 };
uint8_t num_code7[] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71 }; // 0-F
uint8_t num_code8[] = { 0xBF, 0x86, 0xDB, 0xCF, 0xE6, 0xED, 0xFD, 0x87, 0xFF, 0xEF, 0xF7, 0xFC, 0xB9, 0xDE, 0xF9, 0xF1 }; // 0-F
//显示缓冲区 默认为0
uint8_t DispBuf[6] = { 0 };
#ifdef TM1637
void TM1637_Delay(uint16_t us)
{
delay_us(us);
}
void TM1637_Start(void)
{
SDA_H(); //拉高数据线
SCL_H(); //拉高时钟线
TM1637_Delay(5); //延时
SDA_L(); //产生下降沿
TM1637_Delay(5); //延时
}
void TM1637_Stop(void)
{
SDA_L(); //拉低数据线
SCL_H(); //拉高时钟线
TM1637_Delay(5); //延时
SDA_H(); //拉高数据线
TM1637_Delay(5); //延时
}
char TM1637_RecvACK(void)
{
char ack;
SCL_L(); //拉低时钟线
TM1637_Delay(5);
SDA_H(); //端口读之前先置高
SCL_H(); //拉高时钟线
TM1637_Delay(5);
ack = SDA_IN(); //读应答信号
TM1637_Delay(5); //延时
SCL_L(); //拉低时钟线
TM1637_Delay(5);
return ack;
}
void TM1637_SendByte(uint8_t dat)
{
uint8_t i;
for (i = 0; i < 8; i++) //发送一个字节数据
{
SCL_L(); //拉低时钟线
TM1637_Delay(5); //延时
if ((dat & 0x01) == 0x01) //判断数据最低位
{
SDA_H(); //置高数据线
} else {
SDA_L(); //置低数据线
}
dat >>= 1; //移出数据的最低位
TM1637_Delay(5); //延时
SCL_H(); //拉高时钟线
TM1637_Delay(5); //延时
}
SCL_L(); //拉低时钟线
TM1637_Delay(5); //延时
TM1637_RecvACK(); //读取应答
}
void TM1637_WriteCMD(uint8_t cmd)
{
TM1637_Start(); //开始
TM1637_SendByte(cmd); //写入命令
TM1637_Stop(); //停止
}
void TM1637_WriteReg(uint8_t add, uint8_t dat)
{
TM1637_Start(); //开始
TM1637_SendByte(add); //写入地址
TM1637_SendByte(dat); //写入数据
TM1637_Stop(); //停止
}
void TM1637_Clear(void)
{
//写入数据0x00则全部熄灭不显示
TM1637_WriteCMD(DisMode);
TM1637_WriteReg(tm_addr_0, 0x00);
TM1637_WriteReg(tm_addr_1, 0x00);
TM1637_WriteReg(tm_addr_2, 0x00);
TM1637_WriteReg(tm_addr_3, 0x00);
TM1637_WriteReg(tm_addr_4, 0x00);
TM1637_WriteReg(tm_addr_5, 0x00);
}
uint8_t tm1637_read_key()
{
uint8_t rekey = 0;
uint8_t i = 0;
TM1637_Start();
TM1637_SendByte(ReadKey);
SDA_H();
for (i = 0; i < 8; i++) {
SCL_L();
TM1637_Delay(10);
rekey = rekey >> 1;
SCL_H();
if (SDA_IN() == 1)
rekey = rekey | 0x80;
else
rekey = rekey | 0x00;
TM1637_Delay(10);
}
SCL_L();
TM1637_Delay(2);
while (SDA_IN() == 1)
;
SCL_H();
return rekey;
}
#endif
void tm1637_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//延时等待上电稳定
TM1637_Delay(1000);
//使能GPIOC的外设时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); //使能GPIO的外设时钟
GPIO_InitStructure.GPIO_Pin = TM_SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(TM_SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = TM_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(TM_SDA_PORT, &GPIO_InitStructure);
TM1637_WriteCMD(DisMode); //固定地址方式写显存
TM1637_Clear(); //清屏
TM1637_WriteCMD(DisCtr); //显示开,亮度4/16
}
void tm1637_key_process(void)
{
if ((key_value_new != 0xFF) && (key_value_old == 0xFF)) { // 按键按下
test_rtt_printf(term_log, "key press down ,key value is %02X \r\n", key_value_new);
} else if ((key_value_new != 0xFF) && (key_value_old == key_value_new)) { // 按键长按
if (key_press_cnt < 9) {
key_press_cnt++;
if (0x09 == key_press_cnt) {
test_rtt_printf(term_log, "key long press ,key value is %02X \r\n", key_value_new);
}
} else {
if (key_ok == key_value_old) { // ok 键长按
} else if (key_ng == key_value_old) { // ok 键长按
} else if (key_up == key_value_old) { // ok 键长按
} else if (key_down == key_value_old) { // ok 键长按
}
}
} else if ((key_value_new == 0xFF) && (key_value_old != 0xFF)) { // 按键释放
key_press_cnt = 0;
test_rtt_printf(term_log, "key press release\r\n");
tm1637_menu_process(key_value_new);
}
key_value_old = key_value_new;
}
void tm1637_menu_process(uint8_t key_value)
{
}
void tm1637_process(void)
{
static uint8_t twinkle = 0;
twinkle = (twinkle > 19 ? 0 : (++twinkle));
//写入三位数码管显示数据
if (twinkle < 10) {
TM1637_WriteCMD(DisMode);
TM1637_WriteReg(tm_addr_0, num_code7[8]);
TM1637_WriteReg(tm_addr_1, num_code7[8]);
TM1637_WriteReg(tm_addr_2, num_code7[8]);
TM1637_WriteReg(tm_addr_3, num_code7[8]);
TM1637_WriteReg(tm_addr_4, num_code7[8]);
TM1637_WriteReg(tm_addr_5, num_code7[8]);
} else {
TM1637_Clear(); //清屏
}
key_value_new = tm1637_read_key();
tm1637_key_process();
}
void tm1637_show_dec(uint16_t dec)
{
uint8_t n1 = 0;
uint8_t n2 = 0;
uint8_t n3 = 0;
uint8_t n4 = 0;
if (dec < 10) {
n4 = num_code7[dec / 0x001 % 0x0A];
} else if (dec < 100) {
n3 = num_code7[dec / 0x00A % 0x0A];
n4 = num_code7[dec / 0x001 % 0x0A];
} else if (dec < 1000) {
n2 = num_code7[dec / 0x064 % 0x0A];
n3 = num_code7[dec / 0x00A % 0x0A];
n4 = num_code7[dec / 0x001 % 0x0A];
} else if (dec < 10000) {
n1 = num_code7[dec / 0x3E8 % 0x0A];
n2 = num_code7[dec / 0x064 % 0x0A];
n3 = num_code7[dec / 0x00A % 0x0A];
n4 = num_code7[dec / 0x001 % 0x0A];
} else {
n1 = num_code7[0x0F];
n2 = num_code7[0x0F];
n3 = num_code7[0x0F];
n4 = num_code7[0x0F];
}
//写入三位数码管显示数据
TM1637_WriteCMD(DisMode);
TM1637_WriteReg(tm_addr_5, n1);
TM1637_WriteReg(tm_addr_4, n2);
TM1637_WriteReg(tm_addr_3, n3);
TM1637_WriteReg(tm_addr_2, n4);
}
void tm1637_cmd_send(uint8_t addr, uint8_t cmd, uint8_t datH, uint8_t datL)
{
com_tx_buf[0] = 0xFF;
com_tx_buf[1] = addr;
com_tx_buf[2] = cmd;
com_tx_buf[3] = datH;
com_tx_buf[4] = datL;
com_tx_buf[5] = 0x00;
com_tx_buf[6] = 0x00;
com_tx_buf[7] = 0x00;
com_tx_buf[8] = get_check_sum(com_tx_buf, sizeof(com_tx_buf));
rbPutData(&rb_usart_tx, com_tx_buf, sizeof(com_tx_buf));
test_rtt_printf(term_log, "a cmd create by tm1637 key,trans to slave now!\r\n");
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构