欧美极品高清xxxxhd,国产日产欧美最新,无码AV国产东京热AV无码,国产精品人与动性XXX,国产传媒亚洲综合一区二区,四库影院永久国产精品,毛片免费免费高清视频,福利所导航夜趣136

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 1286|回復: 9
打印 上一主題 下一主題
收起左側

STC8單片機超簡單TM1650數碼管驅動庫函數

  [復制鏈接]
回帖獎勵 1 黑幣 回復本帖可獲得 1 黑幣獎勵! 每人限 1 次(中獎概率 70%)
跳轉到指定樓層
樓主
ID:1155837 發表于 2026-2-3 14:17 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
從51單片機轉到32單片機之后,我偶然間因為一個ESP32的開源項目,接觸了Arduino,然后我就被Arduino簡潔的庫函數驚訝到了,因此我就想著,我也可以寫一個,借此來深入學習庫函數的搭建。
正好,最近做的項目需要用數碼管顯示電壓,而我打算使用的STC8單片機也是有硬件I2C的,不用軟件跑時序。那么說寫就寫!
什么是Arduino風格呢?就是將復雜的函數封裝起來,最后呈現出來,只需要給一個簡單的函數名填入一些參數,就可以調用復雜的庫函數,實現功能。對于TM1650來說,可以是這樣的:
void TM1650_Init(unsigned int TM1650_Freq_kHz,unsigned char Brt)
通過傳入Freq和Brt這兩個變量,就可以輕松的配置I2C頻率,設置TM1650的亮度。比如TM1650_Init(100,4),就可以將I2C頻率設置為100khz,將亮度設置為4級。函數內部會自動根據MainFOSC計算寄存器值,并賦予。
又比如這樣
void TM1650_PinSet(unsigned char SCL,unsigned char SDA)

通過這個函數,可以簡單的配置I2C引腳,比如TM1650_Pinset(25,24),可以將I2C引腳設置到P25和P24。當然,不同型號可能不一樣,特別是8腳芯片完全不一樣。而且STC8沒有GPIO Matrix(GPIO交換矩陣),無法將功能腳任意映射,所以這個函數比較局限。
再比如
TM1650_Display_Num(unsigned int Num,unsigned char Dot_pos)

通過這個函數,就可以控制顯示的數值和小數點位置,比如(1234,2),就會顯示12.34
或者是這樣
TM1650_Display_Word(const char *Word)
這個函數需要傳入一個字符串,比如("STOP"),函數內部就會拆分字符串,并根據ASCII碼作為索引,在數碼管上顯示字母。不過由于七段數碼管的局限性,能顯示的東西還是不多。不過由于函數內部特性,你甚至可以傳入("S.T.O.P.")這種全是小數點的,很自由。
這就是Arduino風格
怎么樣,是不是很直觀?
我將本庫函數頭文件比較長,兩百多行,因此我將包含全部庫函數的頭文件TM1650.H作為附件上傳,需要的可以下載,至于怎么在KEIL中引用?可以通過keil導入“存在的文檔”即可。
而每個函數詳細的說明,會在之后的樓層一個個更新.

TM1650.zip

3.57 KB, 下載次數: 0, 下載積分: 黑幣 -5

評分

參與人數 2黑幣 +95 收起 理由
mashuiyou + 5 贊一個!
admin + 90 共享資料的獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏9 分享淘帖 頂 踩
回復

使用道具 舉報

來自 9#
ID:1155837 發表于 2026-2-13 01:59 | 只看該作者
下面是新版的庫函數驅動版本,總結了之前的一些問題
//本頭文件用于STC8G/8H系列單片機使用硬件I2C驅動TM1650四位數碼管芯片
//時間2026年1月15日20:45:48 版本V1.1
//本頭文件不使用I2C中斷,未開啟,如需要,請手動開啟。
//Written by SY_studio 2026/02/03
//////////////////////////////////////////
/*須知
本庫函數在設計時考慮到不同用戶系統環境不同,沒有使用中斷查詢硬件I2C,而是使用阻斷式循環查詢。
Display_Num典型任務耗時500us CPU @6MHZ I2C時鐘100KHZ
如果需要實現中斷式I2C查詢請按下面步驟:
1. 在TM1650_INIT函數中修改 I2CMSCR |= 0X00;//開啟I2C中斷
2. 在I2C_CMD函數中刪除while循環及之后的代碼
3. 在主程序中啟用:EA = 1;//開啟總中斷
4. 主程序C文件中添加中斷服務函數如下:
void I2C_ISR() interrupt 24 {
    if(I2CMSST & 0X40){
    I2CMSCR = (I2CMSCR & 0XF0) | I2C_IDLE;//清空I2C命令
    I2CMSST &= ~0X40;//清除完成標志(~0X40 = 10111111,做與運算,可清零bit6位)
    }
}
*/

#ifndef __TM1650_H__
#define __TM1650_H__
#include "STC8H.H"
//報錯FLAG
bit TM1650_Error = 0;
//顯存地址
#define TM1650_CMD1 0x48//TM1650數據命令,
#define TM1650_DIG1 0x68//四段分別地址
#define TM1650_DIG2 0x6A//一般DIG1為最高顯示位
#define TM1650_DIG3 0x6C
#define TM1650_DIG4 0x6E
//取模表
#define SEG_0 0X3F
#define SEG_1 0X06
#define SEG_2 0X5B
#define SEG_3 0X4F
#define SEG_4 0X66
#define SEG_5 0X6D
#define SEG_6 0X7D
#define SEG_7 0X07
#define SEG_8 0X7F
#define SEG_9 0X6F
#define SEG_Blank 0X00
#define SEG_Minus 0X40
#define SEG_E 0X79
#define SEG_r 0X50
#define SEG_O 0X3F
#define SEG_F 0X71
#define SEG_n 0X54
#define SEG_U 0X3E
#define SEG_V 0X3E
#define SEG_P 0X73
#define SEG_C 0X39
#define SEG_A 0X77
#define SEG_B 0X7F
#define SEG_b 0X7C
#define SEG_T 0X46
#define SEG_S 0X6D
#define SEG_L 0X38
#define SEG_H 0X76
#define SEG_I 0X06
#define SEG_o 0X5C
//I2C命令表
#define I2C_IDLE 0X00
#define I2C_START 0X01
#define I2C_STOP 0X06
#define I2C_SENDMS 0X02
#define I2C_RECV_ACK 0X03
//#include "TM1650_SEGMAP.H"
extern unsigned long Main_FOSC;//使用主程序中定義的時鐘頻率
const unsigned char TM1650_CMD2[] = {//此表決定了發送的亮度數值
    0x00,// 0 關閉顯示
    0x11,// 1級亮度
    0x21,// 2級亮度
    0x31,// 3級亮度
    0x41,// 4級亮度
    0x51,// 5級亮度
    0x61,// 6級亮度
    0x71,// 7級亮度
    0x01 // 8級亮度
};
//frequency單位為k,brightness取0時關閉數碼管輸出
//num為要顯示的數字,取值0到9999,DP為小數點位置,取值0到3
unsigned char Seg_Table(unsigned char word) {
    switch (word) {
        case '0': return SEG_0;      // 0x3F
        case '1': return SEG_1;      // 0x06
        case '2': return SEG_2;      // 0x5B
        case '3': return SEG_3;      // 0x4F
        case '4': return SEG_4;      // 0x66
        case '5': return SEG_5;      // 0x6D
        case '6': return SEG_6;      // 0x7D
        case '7': return SEG_7;      // 0x07
        case '8': return SEG_8;      // 0x7F
        case '9': return SEG_9;      // 0x6F
        case 'A': return SEG_A;      // 0x77
        case 'B': return SEG_B;      // 0x7F
        case 'C': return SEG_C;      // 0x39
        case 'E': return SEG_E;      // 0x79
        case 'F': return SEG_F;      // 0x71
        case 'G': return SEG_6;      // 0x7D
        case 'H': return SEG_H;      // 0x76
        case 'L': return SEG_L;      // 0x38
        case 'P': return SEG_P;      // 0x73
        case 'S': return SEG_S;      // 0x6D
        case 'T': return SEG_7;      // 0x07
        case 'U': return SEG_U;      // 0x3E
        case 'V': return SEG_V;      // 0x3E
        case 'O': return SEG_O;      // 0x3F
        //小寫字母
        case 'b': return SEG_b;      // 0x7C
        case 'n': return SEG_n;      // 0x54
        case 'r': return SEG_r;      // 0x44
        case 'o': return SEG_o;      // 0x3F
        case 'I': return SEG_I;      // 0x06
        case 'g': return SEG_9;      // 0x6F
        //標點
        case ' ': return SEG_Blank;  // 空格顯示空白
        case '-': return SEG_Minus;  // 0x40
        default: return SEG_Blank;  // 未定義字符顯示空白
    }
}
void I2C_CMD(unsigned char CMD){//I2C命令設置
    unsigned int Release_Timer = 0;
    I2CMSCR = (I2CMSCR & 0XF0) | CMD;//只填低四位
    //頭文件中沒有MSIF的定義,因此只能手動做與運算
    while(!(I2CMSST & 0X40)){
        if(++Release_Timer > 10000) {
            TM1650_Error = 1;break;}}//等待I2C控制器執行完命令,超時則退出并報錯
    I2CMSCR = (I2CMSCR & 0XF0) | I2C_IDLE;//清空I2C命令
    I2CMSST &= ~0X40;//清除完成標志(~0X40 = 10111111,做與運算,可清零bit6位)
}
bit I2C_Check_ACK(void){//TM1650不需要確認ACK
    unsigned char a = 0;
    a = (I2CMSST & 0X02);//讀取I2CMSST B1位是否為1
    return a;//返回1或0
}
void TM1650_Write(unsigned char Data,unsigned char Addr){//底層發送數據驅動
    //此處不需I2C開始與停止發送,而是手動添加,可連續發送。
    if(Addr != 0){//如果需要在同一地址發送超過8bit數據,可令Addr=0
        I2C_CMD(I2C_START);
        I2CTXD = Addr;//不需要擔心時序,每字節后必須有一個ack,時間足夠
        I2C_CMD(I2C_SENDMS);//發送命令
        I2C_CMD(I2C_RECV_ACK);//接受ACK
        I2CTXD = Data;//先發地址,后發數據
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
        I2CTXD = 0;//清空發送寄存器
        I2C_CMD(I2C_STOP);
    }
    else{//往一個八位地址一直發數據,比如寫OLED屏(特殊設備有16位I2C地址,在這里不做討論)
        I2CTXD = Data;//先發地址,后發數據
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
    }
}
void TM1650_PinSet(unsigned char SCL,unsigned char SDA){
    P_SW2 = 0X80;
    //僅對STC8H和G系列部分有效
    //本函數請謹慎使用,務必先查看技術手冊中的引腳定義,或者手動規定引腳
    //對于所有非法值,都不會改變I2C引腳
    if(SCL == 15 && SDA == 14){
        P_SW2 &= ~0X30;//B5,B4置0
        P1M0 |= 0x30; P1M1 |= 0x30; //P15 P14設置為開漏
    }
    else if(SCL == 25 && SDA == 24){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X10;//B4置1
        P2M0 = 0X30;P2M1 = 0X30;//P25 P24開漏
    }
    else if(SCL == 77 && SDA == 76){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X20;//B5置1
        P7M0 |= 0xC0;P7M1 |= 0xC0;//P77 P76開漏
    }
    else if(SCL == 32 && SDA == 33){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X30;//B5,B4置1
        P3M0 |= 0x0C;P3M1 |= 0X0C;//P32,P33開漏
    }
    else if(SCL == 54 && SDA == 55){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X10;//僅對STC8G1K08A 8PIN有效
        P5M0 |= 0X30;P5M1 |= 0X30;
    }
}
void TM1650_Init(unsigned int TM1650_Freq_kHz,unsigned char Brt){
    unsigned char MSSPEED = 0;
    P_SW2 |= 0X80;//允許訪問擴展寄存器
    if(TM1650_Freq_kHz != 0){//傳入0代表不重新初始化I2C
    I2CCFG = 0XE0;//啟用I2C,主機模式
    MSSPEED = (((Main_FOSC/4)/(TM1650_Freq_kHz * 1000UL)) - 2) & 0X3F;//與運算只保留低六位
    I2CCFG = (I2CCFG & 0XC0) | MSSPEED;//寫入低六位
    I2CMSCR = 0X00;//關閉I2C主機模式中斷,清空I2C命令
    I2CTXD = 0X00;//清空發送數據寄存器
    I2CRXD = 0X00;//清空接收數據寄存器
    I2CMSAUX = 0X00;//關閉主機自動發送功能
    I2CMSST = 0X00;//清零標志
    }
    //TM1650_Write(0x49,0x48);
    TM1650_Write(TM1650_CMD2[Brt],TM1650_CMD1);//發送命令1(0X48),再發送命令2(亮度)
    //MSBUSY指示I2C控制器是否忙碌,但本驅動程序不需要檢查MSBUSY
}
//在C語言中,字符串自動轉換為由二進制碼組成的數組
//const char *Num 接收的是這個字符串的首地址
//Num為指針(地址),*Num為這個地址讀到的東西
void TM1650_Display_Word(const char *Word){
    bit TM1650_Need_DP = 0;//是否需要小數點標志
    unsigned char TM1650_Current_Digit = 0;//記錄當前寫的數碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存
        while(*Word != '\0' && TM1650_Current_Digit < 4){//字符串未結尾,且并沒有寫完完整4位
        if(*(Word+1) == '.'){//檢測下一位是否是小數點(TM1650的小數點與上一個段綁定)
            TM1650_Need_DP = 1;//標記需要小數點
        }
        TM1650_Write_Buffer = Seg_Table(*Word);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數點
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標志
            Word = Word + 1;//下一位存在小數點,直接跳過
            }
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG1); break;
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
            TM1650_Current_Digit++;//數碼管段+1
            Word++;//指針+1
        }
}
void TM1650_Display_Num(unsigned int Num, unsigned char Dot_pos){
    //整數轉字符所用變量
    char Trans_Temp[4];//整數截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區
    char *Ptr = TM1650_String_Buffer;//數組的指針,用于在數組取值
    unsigned char i = 4; //數據保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數點標志
    unsigned char TM1650_Current_Digit = 0;//記錄當前寫的數碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區域
    //整數轉字符
    //先把整數拆解成純數字數組 (倒序存放)
    //比如 1234 -> temp[0]=4, temp[1]=3, temp[2]=2, temp[3]=1
    for(k=0; k<4; k++) {
        Trans_Temp[k] = (Num % 10) + '0';
        Num /= 10;
    }
    //根據 dot_pos 組裝帶點的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設定的點位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數點(TM1650的小數點與上一個段綁定)
            TM1650_Need_DP = 1;//標記需要小數點
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數點
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標志
            Ptr = Ptr + 1;//下一位存在小數點,直接跳過
            }
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG1); break;
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
            TM1650_Current_Digit++;//數碼管段+1
            Ptr++;//指針+1
        }
}
#endif
回復

使用道具 舉報

沙發
ID:1155837 發表于 2026-2-3 14:53 | 只看該作者
本代碼完全依靠硬件I2C實現,因此大概只能在STC8單片機上使用。并且需要使用1.8kb flash,因為沒有用浮點,string還有sprintf等函數,因此本庫函數的flash占用算是很小了。
具體占用Program Size: data=9.1 xdata=38 const=9 code=1848
TM1650作為一個類I2C總線器件,可以將其“數據命令”0x48作為I2C地址來使用,效果是一樣的。
要使用本庫函數,一定要在主函數中聲明unsigned long Main_FOSC = 24000000L;
字母需要保持一致,否則可能會導致報錯或者庫函數無法正確計算I2C時鐘頻率,或者你可以手動修改頭文件。
其他I2C也就不需要了
對了。一般來說TM1650的亮度設置到4級就夠用了,否則發熱過大。
下面的函數包括硬件I2C寄存器值初始化,以及總線時鐘頻率計算并賦予。根據手冊中公式計算,已經驗證通過。
void TM1650_Init(unsigned int TM1650_Freq_kHz,unsigned char Brt){
    unsigned char MSSPEED = 0;
    P_SW2 |= 0X80;//允許訪問擴展寄存器
    if(TM1650_Freq_kHz != 0){//傳入0代表不重新初始化I2C
    I2CCFG = 0XE0;//啟用I2C,主機模式
    MSSPEED = (((Main_FOSC/4)/(TM1650_Freq_kHz * 1000UL)) - 2) & 0X3F;//與運算只保留低六位
    I2CCFG = (I2CCFG & 0XC0) | MSSPEED;//寫入低六位
    I2CMSCR = 0X00;//關閉I2C主機模式中斷,清空I2C命令
    I2CTXD = 0X00;//清空發送數據寄存器
    I2CRXD = 0X00;//清空接收數據寄存器
    I2CMSAUX = 0X00;//關閉主機自動發送功能
    I2CMSST = 0X00;//清零標志
    }
    TM1650_Write(TM1650_CMD2[Brt],TM1650_CMD1);//發送命令1(0X48),再發送命令2(亮度)
    //MSBUSY指示I2C控制器是否忙碌,但本驅動程序不需要檢查MSBUSY
}
回復

使用道具 舉報

板凳
ID:1155837 發表于 2026-2-3 14:53 | 只看該作者
其實這次寫的有不少巧妙的地方,比如下面這個函數。
在C51下,一個float變量就需要給程序體積增加2kb,對于寸土寸金的8kb flash的小單片機,是無法接受的,因此只能優化。
因此,在這里,我使用的是傳入整數+小數點的方法。我也沒有用sprintf這個格式化輸出函數,而是直接根據四位數值的特性,寫了一套根據ASCII碼特性的數值轉字符串方法。
另外,在整個庫函數里,查表,按段寫顯存都是基于ASCII碼和字符串的特性實現。因為每個字符都對應了一個0~127的7位ASCII碼。而一個字符串,則是由ASCII碼構成的,結尾為\0的數組,因此我們可以用指針變量來指向這個數組,并讀取這個字符串數組中的數值,并通過\0來檢測結束。
void TM1650_Display_Num(unsigned int Num, unsigned char Dot_pos){
    //整數轉字符所用變量
    char Trans_Temp[4];//整數截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區
    char *Ptr = TM1650_String_Buffer;//數組的指針,用于在數組取值
    unsigned char i = 4; //數據保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數點標志
    unsigned char TM1650_Current_Digit = 0;//記錄當前寫的數碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區域
    //整數轉字符
    //先把整數拆解成純數字數組 (倒序存放)
    //比如 1234 -> temp[0]=4, temp[1]=3, temp[2]=2, temp[3]=1
    for(k=0; k<4; k++) {
        Trans_Temp[k] = (Num % 10) + '0';
        Num /= 10;
    }
    //根據 dot_pos 組裝帶點的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設定的點位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數點(TM1650的小數點與上一個段綁定)
            TM1650_Need_DP = 1;//標記需要小數點
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數點
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標志
            Ptr = Ptr + 1;//下一位存在小數點,直接跳過
            }
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG1); break;
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
            TM1650_Current_Digit++;//數碼管段+1
            Ptr++;//指針+1
        }
}
回復

使用道具 舉報

地板
ID:1155837 發表于 2026-2-3 15:00 | 只看該作者
下面是全部代碼,可以自己手動創建頭文件,復制進去即可。
//本頭文件用于TM1650四位數碼管芯片
//時間2026年1月15日20:45:48
//本頭文件不使用I2C中斷,未開啟,如需要,請手動開啟。
//Written by SY_studio 2026/02/03
#ifndef __TM1650_H__
#define __TM1650_H__
#include "STC8H.H"
//顯存地址
#define TM1650_CMD1 0x48//TM1650數據命令,
#define TM1650_DIG1 0x68//四段分別地址
#define TM1650_DIG2 0x6A//一般DIG1為最高顯示位
#define TM1650_DIG3 0x6C
#define TM1650_DIG4 0x6E
//取模表
#define SEG_0 0X3F
#define SEG_1 0X06
#define SEG_2 0X5B
#define SEG_3 0X4F
#define SEG_4 0X66
#define SEG_5 0X6D
#define SEG_6 0X7D
#define SEG_7 0X07
#define SEG_8 0X7F
#define SEG_9 0X6F
#define SEG_Blank 0X00
#define SEG_Minus 0X40
#define SEG_E 0X79
#define SEG_r 0X50
#define SEG_O 0X3F
#define SEG_F 0X71
#define SEG_n 0X54
#define SEG_U 0X3E
#define SEG_V 0X3E
#define SEG_P 0X73
#define SEG_C 0X39
#define SEG_A 0X77
#define SEG_B 0X7F
#define SEG_b 0X7C
#define SEG_T 0X46
#define SEG_S 0X6D
#define SEG_L 0X38
#define SEG_H 0X76
#define SEG_I 0X06
#define SEG_o 0X5C
//I2C命令表
#define I2C_IDLE 0X00
#define I2C_START 0X01
#define I2C_STOP 0X06
#define I2C_SENDMS 0X02
#define I2C_RECV_ACK 0X03
//#include "TM1650_SEGMAP.H"
extern unsigned long Main_FOSC;//使用主程序中定義的時鐘頻率
const unsigned char TM1650_CMD2[] = {//此表決定了發送的亮度數值
    0x00,// 0 關閉顯示
    0x11,// 1級亮度
    0x21,// 2級亮度
    0x31,// 3級亮度
    0x41,// 4級亮度
    0x51,// 5級亮度
    0x61,// 6級亮度
    0x71,// 7級亮度
    0x01 // 8級亮度
};
//frequency單位為k,brightness取0時關閉數碼管輸出
//num為要顯示的數字,取值0到9999,DP為小數點位置,取值0到3
unsigned char Seg_Table(unsigned char word) {
    switch (word) {
        case '0': return SEG_0;      // 0x3F
        case '1': return SEG_1;      // 0x06
        case '2': return SEG_2;      // 0x5B
        case '3': return SEG_3;      // 0x4F
        case '4': return SEG_4;      // 0x66
        case '5': return SEG_5;      // 0x6D
        case '6': return SEG_6;      // 0x7D
        case '7': return SEG_7;      // 0x07
        case '8': return SEG_8;      // 0x7F
        case '9': return SEG_9;      // 0x6F
        case 'A': return SEG_A;      // 0x77
        case 'B': return SEG_B;      // 0x7F
        case 'C': return SEG_C;      // 0x39
        case 'E': return SEG_E;      // 0x79
        case 'F': return SEG_F;      // 0x71
        case 'H': return SEG_H;      // 0x76
        case 'L': return SEG_L;      // 0x38
        case 'P': return SEG_P;      // 0x73
        case 'S': return SEG_S;      // 0x6D
        case 'T': return SEG_7;      // 0x07
        case 'U': return SEG_U;      // 0x3E
        case 'V': return SEG_V;      // 0x3E
        case 'O': return SEG_O;
        //小寫字母
        case 'b': return SEG_b;      // 0x7C
        case 'n': return SEG_n;      // 0x54
        case 'r': return SEG_r;      // 0x44
        case 'o': return SEG_o;      // 0x3F
        case 'I': return SEG_I;      // 0x06
        //標點
        case ' ': return SEG_Blank;  // 空格顯示空白
        case '-': return SEG_Minus;  // 0x40
        default: return SEG_Blank;  // 未定義字符顯示空白
    }
}
void I2C_CMD(unsigned char CMD){//I2C命令設置
    I2CMSCR = (I2CMSCR & 0XF0) | CMD;//只填低四位
    //頭文件中沒有MSIF的定義,因此只能手動做與運算
    while(!(I2CMSST & 0X40));//等待I2C控制器執行完命令
    I2CMSCR = (I2CMSCR & 0XF0) | I2C_IDLE;//清空I2C命令
    I2CMSST &= ~0X40;//清除完成標志(~0X40 = 10111111,做與運算,可清零bit6位)
}
bit I2C_Check_ACK(void){//TM1650不需要確認ACK
    unsigned char a = 0;
    a = (I2CMSST & 0X02);//讀取I2CMSST B1位是否為1
    return a;//返回1或0
}
void TM1650_Write(unsigned char Data,unsigned char Addr){//底層發送數據驅動
    //此處不需I2C開始與停止發送,而是手動添加,可連續發送。
    if(Addr != 0){//如果需要在同一地址發送超過8bit數據,可令Addr=0
        I2C_CMD(I2C_START);
        I2CTXD = Addr;//不需要擔心時序,每字節后必須有一個ack,時間足夠
        I2C_CMD(I2C_SENDMS);//發送命令
        I2C_CMD(I2C_RECV_ACK);//接受ACK
        I2CTXD = Data;//先發地址,后發數據
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
        I2CTXD = 0;//清空發送寄存器
        I2C_CMD(I2C_STOP);
    }
    else{//往一個八位地址一直發數據,比如寫OLED屏(特殊設備有16位I2C地址,在這里不做討論)
        I2CTXD = Data;//先發地址,后發數據
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
    }
}
void TM1650_PinSet(unsigned char SCL,unsigned char SDA){
    P_SW2 = 0X80;
    //僅對STC8H和G系列部分有效
    //本函數請謹慎使用,務必先查看技術手冊中的引腳定義,或者手動規定引腳
    //對于所有非法值,都不會改變I2C引腳
    if(SCL == 15 && SDA == 14){
        P_SW2 &= ~0X30;//B5,B4置0
        P1M0 |= 0x30; P1M1 |= 0x30; //P15 P14設置為開漏
    }
    else if(SCL == 25 && SDA == 24){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X10;//B4置1
        P2M0 = 0X30;P2M1 = 0X30;//P25 P24開漏
    }
    else if(SCL == 77 && SDA == 76){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X20;//B5置1
        P7M0 |= 0xC0;P7M1 |= 0xC0;//P77 P76開漏
    }
    else if(SCL == 32 && SDA == 33){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X30;//B5,B4置1
        P3M0 = 0x0C;P3M1 = 0X0C;//P32,P33開漏
    }
    else if(SCL == 54 && SDA == 55){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X10;//僅對STC8G1K08A 8PIN有效
        P5M0 = 0X30;P5M1 = 0X30;
    }
}
void TM1650_Init(unsigned int TM1650_Freq_kHz,unsigned char Brt){
    unsigned char MSSPEED = 0;
    P_SW2 |= 0X80;//允許訪問擴展寄存器
    if(TM1650_Freq_kHz != 0){//傳入0代表不重新初始化I2C
    I2CCFG = 0XE0;//啟用I2C,主機模式
    MSSPEED = (((Main_FOSC/4)/(TM1650_Freq_kHz * 1000UL)) - 2) & 0X3F;//與運算只保留低六位
    I2CCFG = (I2CCFG & 0XC0) | MSSPEED;//寫入低六位
    I2CMSCR = 0X00;//關閉I2C主機模式中斷,清空I2C命令
    I2CTXD = 0X00;//清空發送數據寄存器
    I2CRXD = 0X00;//清空接收數據寄存器
    I2CMSAUX = 0X00;//關閉主機自動發送功能
    I2CMSST = 0X00;//清零標志
    }
    //TM1650_Write(0x49,0x48);
    TM1650_Write(TM1650_CMD2[Brt],TM1650_CMD1);//發送命令1(0X48),再發送命令2(亮度)
    //MSBUSY指示I2C控制器是否忙碌,但本驅動程序不需要檢查MSBUSY
}
//在C語言中,字符串自動轉換為由二進制碼組成的數組
//const char *Num 接收的是這個字符串的首地址
//Num為指針(地址),*Num為這個地址讀到的東西
void TM1650_Display_Word(const char *Word){
    bit TM1650_Need_DP = 0;//是否需要小數點標志
    unsigned char TM1650_Current_Digit = 0;//記錄當前寫的數碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存
        while(*Word != '\0' && TM1650_Current_Digit < 4){//字符串未結尾,且并沒有寫完完整4位
        if(*(Word+1) == '.'){//檢測下一位是否是小數點(TM1650的小數點與上一個段綁定)
            TM1650_Need_DP = 1;//標記需要小數點
        }
        TM1650_Write_Buffer = Seg_Table(*Word);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數點
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標志
            Word = Word + 1;//下一位存在小數點,直接跳過
            }
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG1); break;
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
            TM1650_Current_Digit++;//數碼管段+1
            Word++;//指針+1
        }
}
void TM1650_Display_Num(unsigned int Num, unsigned char Dot_pos){
    //整數轉字符所用變量
    char Trans_Temp[4];//整數截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區
    char *Ptr = TM1650_String_Buffer;//數組的指針,用于在數組取值
    unsigned char i = 4; //數據保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數點標志
    unsigned char TM1650_Current_Digit = 0;//記錄當前寫的數碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區域
    //整數轉字符
    //先把整數拆解成純數字數組 (倒序存放)
    //比如 1234 -> temp[0]=4, temp[1]=3, temp[2]=2, temp[3]=1
    for(k=0; k<4; k++) {
        Trans_Temp[k] = (Num % 10) + '0';
        Num /= 10;
    }
    //根據 dot_pos 組裝帶點的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設定的點位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數點(TM1650的小數點與上一個段綁定)
            TM1650_Need_DP = 1;//標記需要小數點
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數點
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標志
            Ptr = Ptr + 1;//下一位存在小數點,直接跳過
            }
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG1); break;
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
            TM1650_Current_Digit++;//數碼管段+1
            Ptr++;//指針+1
        }
}
#endif
回復

使用道具 舉報

5#
ID:154665 發表于 2026-2-3 20:22 | 只看該作者
非常感謝樓主的無私奉獻
回復

使用道具 舉報

6#
ID:1155837 發表于 2026-2-11 20:49 | 只看該作者
對了,使用本驅動庫,必須要先使用PinSet函數初始化引腳,再使用init函數初始化硬件I2C!,否則單片機執行INIT函數的時候,因為引腳沒有初始化,硬件I2C始終沒有返回值,會導致單片機在這里死循環。
另外如果是STC8G的話還需要修改頭文件中的include,8h不用改。
然后是庫函數中pinset函數有個等號忘記加或運算符號了,會導致整個P3被修改,下面是改好的,可以手動替換頭文件中的函數。
遇到問題,可以先試試自己調試,頭文件里的代碼不復雜。
void TM1650_PinSet(unsigned char SCL,unsigned char SDA){
    P_SW2 = 0X80;
    //僅對STC8H和G系列部分有效
    //本函數請謹慎使用,務必先查看技術手冊中的引腳定義,或者手動規定引腳
    //對于所有非法值,都不會改變I2C引腳
    if(SCL == 15 && SDA == 14){
        P_SW2 &= ~0X30;//B5,B4置0
        P1M0 |= 0x30; P1M1 |= 0x30; //P15 P14設置為開漏
    }
    else if(SCL == 25 && SDA == 24){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X10;//B4置1
        P2M0 = 0X30;P2M1 = 0X30;//P25 P24開漏
    }
    else if(SCL == 77 && SDA == 76){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X20;//B5置1
        P7M0 |= 0xC0;P7M1 |= 0xC0;//P77 P76開漏
    }
    else if(SCL == 32 && SDA == 33){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X30;//B5,B4置1
        P3M0 |= 0x0C;P3M1 |= 0X0C;//P32,P33開漏
    }
    else if(SCL == 54 && SDA == 55){
        P_SW2 &= ~0X30;//B5,B4置0
        P_SW2 |= 0X10;//僅對STC8G1K08A 8PIN有效
        P5M0 |= 0X30;P5M1 |= 0X30;
    }
}
回復

使用道具 舉報

7#
ID:1155837 發表于 2026-2-11 21:00 | 只看該作者
不能關閉對應引腳的數字輸入功能,因為接受ACK需要檢測引腳電平
回復

使用道具 舉報

8#
ID:1155837 發表于 2026-2-13 01:42 | 只看該作者
在我的這個庫函數里,i2c驅動使用的是阻斷式while循環讀取i2c命令寄存器,檢測是否執行完命令的。這導致這個方案也會像軟件i2c一樣造成單片機死等,不能在發送i2c的時候執行其他任務。阻斷時間取決于i2c時鐘頻率,因此可以盡量使i2c時鐘頻率高一些。這樣阻斷時間少一些。
另外,我推薦使用中斷方式查詢I2CMSST寄存器,硬件i2c在執行完i2c命令后會觸發中斷,但是因為我做的是庫,所以為了安全起見我并沒有啟用i2c中斷,但是這個功能也很簡單。如果你用51做了很多東西,cpu性能局促,那么就可以改為中斷方式查詢了,下面是一些參考代碼,中斷服務函數是從手冊中截取的。
首先,初始化時設置I2CMSCR |= 0X80;
另外,必須在主函數中啟用總中斷 EA = 1;
然后復制這個代碼到程序中:(來自STC8手冊,做了修改)
void I2C_Isr() interrupt 24  
{
_push_(P_SW2);
P_SW2 |= 0x80;
if (I2CMSST & 0x40)
{
  I2CMSST &= ~0x40; //清中斷標志
  //busy = 0;
}  
_pop_(P_SW2);
}
回復

使用道具 舉報

10#
ID:1155837 發表于 2026-2-15 08:36 | 只看該作者
再來個小更新,現在這個Display_Num函數支持顯示負數了,請手動替換
void TM1650_Display_Num(signed int Num, unsigned char Dot_pos){
    //整數轉字符所用變量
    char Trans_Temp[4];//整數截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區
    char *Ptr = TM1650_String_Buffer;//數組的指針,用于在數組取值
    unsigned char i = 4; //數據保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數點標志
    bit TM1650_Is_Negtive = 0;//標記為負數
    unsigned char TM1650_Current_Digit = 0;//記錄當前寫的數碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區域
    //檢測是正數還是負數
    if(Num < 0){
        Num = -Num;//取反
        TM1650_Is_Negtive = 1;//標記為負數
    }
    //即使輸入為負數,下面的整數轉字符代碼也不需要修改,寫入時不寫DIG1即可
    //整數轉字符
    //先把整數拆解成純數字數組 (倒序存放)
    //比如 1234 -> temp[0]=4, temp[1]=3, temp[2]=2, temp[3]=1
    //比如 -123 → temp[0]=-
    for(k=0; k<4; k++) {
        Trans_Temp[k] = (Num % 10) + '0';
        Num /= 10;
    }
    //根據 dot_pos 組裝帶點的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設定的點位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數點(TM1650的小數點與上一個段綁定)
            TM1650_Need_DP = 1;//標記需要小數點
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數點
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標志
            Ptr = Ptr + 1;//下一位存在小數點,直接跳過
            }
        if(!TM1650_Is_Negtive){//輸入為正數
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG1); break;
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
        }
        else{//如果輸入為負數
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(Seg_Table('-'),TM1650_DIG1); break;//最高位寫入負數
            case 1: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG2); break;
            case 2: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG3); break;
            case 3: TM1650_Write(TM1650_Write_Buffer,TM1650_DIG4); break;
            }
        }
            TM1650_Current_Digit++;//數碼管段+1
            Ptr++;//指針+1
        }
}
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表