当我们需要通过单片机去驱动一款芯片正常工作的时候,我们第一时间会去阅读它的数据手册,当你没有接触过类似的芯片时候,这个时候越看会感觉脑壳越疼,哪怕等你真的驱动它正常工作以后会发现原来它是如此的简单。大部分人卡在最难的部分无疑是时序上,数据手册里面给出了时序的要求如下:
抛开复杂的时序,软件其实没那么复杂,首先我们要找到连接的引脚,并进行初始化:
STM32 TM1638
PA5 ----- CLK
PA6 ----- DIO
PA7 ----- STB
3.3V ----- VCC
GND ----- GND
// 先定义几个"暗号"
#define TM1638_CLK_PIN GPIO_PIN_5
#define TM1638_DIO_PIN GPIO_PIN_6
#define TM1638_STB_PIN GPIO_PIN_7
#define TM1638_PORT GPIOA
// 初始化GPIO,相当于教STM32怎么"搭讪"
void TM1638_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
// CLK和STB是输出模式,像主动出击的直男
GPIO_InitStruct.Pin = TM1638_CLK_PIN | TM1638_STB_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TM1638_PORT, &GPIO_InitStruct);
// DIO是双向的,时而输出时而输入,像恋爱中的忽冷忽热
GPIO_InitStruct.Pin = TM1638_DIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(TM1638_PORT, &GPIO_InitStruct);
// 先保持高冷姿态
TM1638_STB_HIGH();
TM1638_CLK_HIGH();
}
接下来,我们需要实现最终的就是根据时序完成一个字节的读取和写入操作:
// 发送一个字节,像说一句情话
void TM1638_WriteByte(uint8_t data) {
for(uint8_t i = 0; i < 8; i++) {
TM1638_CLK_LOW(); // 先低头示好
// 根据数据位决定是送花(1)还是送巧克力(0)
if(data & 0x01) {
TM1638_DIO_HIGH();
} else {
TM1638_DIO_LOW();
}
HAL_Delay(1); // 停顿一下,别太着急
TM1638_CLK_HIGH(); // 抬起头等待回应
HAL_Delay(1);
data >>= 1; // 准备下一句情话
}
}
// 接收一个字节,像等待对方回复
uint8_t TM1638_ReadByte(void) {
uint8_t data = 0;
// 先把DIO设置为输入模式,像竖起耳朵听回复
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = TM1638_DIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(TM1638_PORT, &GPIO_InitStruct);
for(uint8_t i = 0; i < 8; i++) {
TM1638_CLK_LOW();
HAL_Delay(1);
// 读取DIO状态,像揣摩对方心思
if(HAL_GPIO_ReadPin(TM1638_PORT, TM1638_DIO_PIN)) {
data |= (1 << i);
}
TM1638_CLK_HIGH();
HAL_Delay(1);
}
// 读完切回输出模式,继续主动出击
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(TM1638_PORT, &GPIO_InitStruct);
return data;
}
有了读取和写入的操作,我们就可以尝试让数码管显示一个数字,需要自定义显示的数组,这个跟数码管的连接相关,这里应用共阴极数码管,最最通用的连接方式下的数组定义及显示函数:
// 显示数字,像送出一份礼物
void TM1638_DisplayNum(uint8_t pos, uint8_t num) {
const uint8_t digitToSegment[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};
TM1638_STB_LOW(); // 开始深情告白
TM1638_WriteByte(0x44); // 固定地址模式
TM1638_STB_HIGH();
TM1638_STB_LOW();
TM1638_WriteByte(0xC0 | (pos << 1)); // 设置显示位置
// 送出数字"礼物"
TM1638_WriteByte(digitToSegment[num]);
TM1638_STB_HIGH();
}
TM1638还支持软件设置数码管的显示亮度,这比我们用三极管调电阻的方式方便太多了:
// 设置亮度,像调节恋爱热度
void TM1638_SetBrightness(uint8_t brightness) {
// 亮度范围0-7,7最亮
brightness = brightness > 7 ? 7 : brightness;
TM1638_STB_LOW();
TM1638_WriteByte(0x88 | brightness);
TM1638_STB_HIGH();
}
除了点数码管以外,它还支持扫描按键输入功能(真心对得起这个价格):
// 读取按键状态,像揣摩对方心思
uint8_t TM1638_ReadKeys(void) {
uint8_t keys = 0;
TM1638_STB_LOW();
TM1638_WriteByte(0x42); // 读取按键指令
for(uint8_t i = 0; i < 4; i++) {
keys |= TM1638_ReadByte() << i;
}
TM1638_STB_HIGH();
return keys; // 每个bit代表一个按键状态
}
以下是终极的示例代码:
int main(void) {
HAL_Init();
SystemClock_Config();
TM1638_Init();
// 先来个"自我介绍"
TM1638_SetBrightness(7); // 最大亮度示爱
// 显示"520"表白
TM1638_DisplayNum(0, 5);
TM1638_DisplayNum(1, 2);
TM1638_DisplayNum(2, 0);
while(1) {
// 持续关注"女神"的反馈(按键)
uint8_t keys = TM1638_ReadKeys();
if(keys != 0) {
// 如果有按键按下,改变显示内容
TM1638_DisplayNum(3, keys % 10);
}
HAL_Delay(100);
}
}
在实际调试中可能会遇到的问题:
1. 数码管无显示情况
- 检查硬件连接,是不是"红线"(VCC)接错了。
- 确认STM32的GPIO时钟已开启,有时候可以连上示波器调到触发状态看下波形。
2.数码管显示乱码(显示不正常)
- 检查时序延迟,是不是时序太快了,示波器该上了。
- 确认数码管是共阴还是共阳。
3.按键读取数据不准
- 检查上拉电阻是否接好。
- 增加去抖动处理。
到这里本篇的内容就结束了,希望对大家有所帮助,感谢阅读!