當我們需要通過單片機去驅(qū)動一款芯片正常工作的時候,我們第一時間會去閱讀它的數(shù)據(jù)手冊,當你沒有接觸過類似的芯片時候,這個時候越看會感覺腦殼越疼,哪怕等你真的驅(qū)動它正常工作以后會發(fā)現(xiàn)原來它是如此的簡單。大部分人卡在最難的部分無疑是時序上,數(shù)據(jù)手冊里面給出了時序的要求如下:
拋開復(fù)雜的時序,軟件其實沒那么復(fù)雜,首先我們要找到連接的引腳,并進行初始化:
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);
// 先保持高冷姿態(tài)
TM1638_STB_HIGH();
TM1638_CLK_HIGH();
}
接下來,我們需要實現(xiàn)最終的就是根據(jù)時序完成一個字節(jié)的讀取和寫入操作:
// 發(fā)送一個字節(jié),像說一句情話
void TM1638_WriteByte(uint8_t data) {
for(uint8_t i = 0; i < 8; i++) {
TM1638_CLK_LOW(); // 先低頭示好
// 根據(jù)數(shù)據(jù)位決定是送花(1)還是送巧克力(0)
if(data & 0x01) {
TM1638_DIO_HIGH();
} else {
TM1638_DIO_LOW();
}
HAL_Delay(1); // 停頓一下,別太著急
TM1638_CLK_HIGH(); // 抬起頭等待回應(yīng)
HAL_Delay(1);
data >>= 1; // 準備下一句情話
}
}
// 接收一個字節(jié),像等待對方回復(fù)
uint8_t TM1638_ReadByte(void) {
uint8_t data = 0;
// 先把DIO設(shè)置為輸入模式,像豎起耳朵聽回復(fù)
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狀態(tài),像揣摩對方心思
if(HAL_GPIO_ReadPin(TM1638_PORT, TM1638_DIO_PIN)) {
data |= (1 << i);
}
TM1638_CLK_HIGH();
HAL_Delay(1);
}
// 讀完切回輸出模式,繼續(xù)主動出擊
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(TM1638_PORT, &GPIO_InitStruct);
return data;
}
有了讀取和寫入的操作,我們就可以嘗試讓數(shù)碼管顯示一個數(shù)字,需要自定義顯示的數(shù)組,這個跟數(shù)碼管的連接相關(guān),這里應(yīng)用共陰極數(shù)碼管,最最通用的連接方式下的數(shù)組定義及顯示函數(shù):
// 顯示數(shù)字,像送出一份禮物
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)); // 設(shè)置顯示位置
// 送出數(shù)字"禮物"
TM1638_WriteByte(digitToSegment[num]);
TM1638_STB_HIGH();
}
TM1638還支持軟件設(shè)置數(shù)碼管的顯示亮度,這比我們用三極管調(diào)電阻的方式方便太多了:
// 設(shè)置亮度,像調(diào)節(jié)戀愛熱度
void TM1638_SetBrightness(uint8_t brightness) {
// 亮度范圍0-7,7最亮
brightness = brightness > 7 ? 7 : brightness;
TM1638_STB_LOW();
TM1638_WriteByte(0x88 | brightness);
TM1638_STB_HIGH();
}
除了點數(shù)碼管以外,它還支持掃描按鍵輸入功能(真心對得起這個價格):
// 讀取按鍵狀態(tài),像揣摩對方心思
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代表一個按鍵狀態(tài)
}
以下是終極的示例代碼:
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) {
// 持續(xù)關(guān)注"女神"的反饋(按鍵)
uint8_t keys = TM1638_ReadKeys();
if(keys != 0) {
// 如果有按鍵按下,改變顯示內(nèi)容
TM1638_DisplayNum(3, keys % 10);
}
HAL_Delay(100);
}
}
在實際調(diào)試中可能會遇到的問題:
1. 數(shù)碼管無顯示情況
- 檢查硬件連接,是不是"紅線"(VCC)接錯了。
- 確認STM32的GPIO時鐘已開啟,有時候可以連上示波器調(diào)到觸發(fā)狀態(tài)看下波形。
2.數(shù)碼管顯示亂碼(顯示不正常)
- 檢查時序延遲,是不是時序太快了,示波器該上了。
- 確認數(shù)碼管是共陰還是共陽。
3.按鍵讀取數(shù)據(jù)不準
- 檢查上拉電阻是否接好。
- 增加去抖動處理。
到這里本篇的內(nèi)容就結(jié)束了,希望對大家有所幫助,感謝閱讀!