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

標(biāo)題: STC8單片機(jī)超簡單TM1650數(shù)碼管驅(qū)動庫函數(shù) [打印本頁]

作者: 千早愛音愛玩51    時間: 2026-2-3 14:17
標(biāo)題: STC8單片機(jī)超簡單TM1650數(shù)碼管驅(qū)動庫函數(shù)
從51單片機(jī)轉(zhuǎn)到32單片機(jī)之后,我偶然間因?yàn)橐粋ESP32的開源項(xiàng)目,接觸了Arduino,然后我就被Arduino簡潔的庫函數(shù)驚訝到了,因此我就想著,我也可以寫一個,借此來深入學(xué)習(xí)庫函數(shù)的搭建。
正好,最近做的項(xiàng)目需要用數(shù)碼管顯示電壓,而我打算使用的STC8單片機(jī)也是有硬件I2C的,不用軟件跑時序。那么說寫就寫!
什么是Arduino風(fēng)格呢?就是將復(fù)雜的函數(shù)封裝起來,最后呈現(xiàn)出來,只需要給一個簡單的函數(shù)名填入一些參數(shù),就可以調(diào)用復(fù)雜的庫函數(shù),實(shí)現(xiàn)功能。對于TM1650來說,可以是這樣的:
void TM1650_Init(unsigned int TM1650_Freq_kHz,unsigned char Brt)
通過傳入Freq和Brt這兩個變量,就可以輕松的配置I2C頻率,設(shè)置TM1650的亮度。比如TM1650_Init(100,4),就可以將I2C頻率設(shè)置為100khz,將亮度設(shè)置為4級。函數(shù)內(nèi)部會自動根據(jù)MainFOSC計算寄存器值,并賦予。
又比如這樣
void TM1650_PinSet(unsigned char SCL,unsigned char SDA)

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

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

TM1650.zip

3.57 KB, 下載次數(shù): 0, 下載積分: 黑幣 -5


作者: 千早愛音愛玩51    時間: 2026-2-3 14:53
本代碼完全依靠硬件I2C實(shí)現(xiàn),因此大概只能在STC8單片機(jī)上使用。并且需要使用1.8kb flash,因?yàn)闆]有用浮點(diǎn),string還有sprintf等函數(shù),因此本庫函數(shù)的flash占用算是很小了。
具體占用Program Size: data=9.1 xdata=38 const=9 code=1848
TM1650作為一個類I2C總線器件,可以將其“數(shù)據(jù)命令”0x48作為I2C地址來使用,效果是一樣的。
要使用本庫函數(shù),一定要在主函數(shù)中聲明unsigned long Main_FOSC = 24000000L;
字母需要保持一致,否則可能會導(dǎo)致報錯或者庫函數(shù)無法正確計算I2C時鐘頻率,或者你可以手動修改頭文件。
其他I2C也就不需要了
對了。一般來說TM1650的亮度設(shè)置到4級就夠用了,否則發(fā)熱過大。
下面的函數(shù)包括硬件I2C寄存器值初始化,以及總線時鐘頻率計算并賦予。根據(jù)手冊中公式計算,已經(jīng)驗(yàn)證通過。
void TM1650_Init(unsigned int TM1650_Freq_kHz,unsigned char Brt){
    unsigned char MSSPEED = 0;
    P_SW2 |= 0X80;//允許訪問擴(kuò)展寄存器
    if(TM1650_Freq_kHz != 0){//傳入0代表不重新初始化I2C
    I2CCFG = 0XE0;//啟用I2C,主機(jī)模式
    MSSPEED = (((Main_FOSC/4)/(TM1650_Freq_kHz * 1000UL)) - 2) & 0X3F;//與運(yùn)算只保留低六位
    I2CCFG = (I2CCFG & 0XC0) | MSSPEED;//寫入低六位
    I2CMSCR = 0X00;//關(guān)閉I2C主機(jī)模式中斷,清空I2C命令
    I2CTXD = 0X00;//清空發(fā)送數(shù)據(jù)寄存器
    I2CRXD = 0X00;//清空接收數(shù)據(jù)寄存器
    I2CMSAUX = 0X00;//關(guān)閉主機(jī)自動發(fā)送功能
    I2CMSST = 0X00;//清零標(biāo)志
    }
    TM1650_Write(TM1650_CMD2[Brt],TM1650_CMD1);//發(fā)送命令1(0X48),再發(fā)送命令2(亮度)
    //MSBUSY指示I2C控制器是否忙碌,但本驅(qū)動程序不需要檢查MSBUSY
}
作者: 千早愛音愛玩51    時間: 2026-2-3 14:53
其實(shí)這次寫的有不少巧妙的地方,比如下面這個函數(shù)。
在C51下,一個float變量就需要給程序體積增加2kb,對于寸土寸金的8kb flash的小單片機(jī),是無法接受的,因此只能優(yōu)化。
因此,在這里,我使用的是傳入整數(shù)+小數(shù)點(diǎn)的方法。我也沒有用sprintf這個格式化輸出函數(shù),而是直接根據(jù)四位數(shù)值的特性,寫了一套根據(jù)ASCII碼特性的數(shù)值轉(zhuǎn)字符串方法。
另外,在整個庫函數(shù)里,查表,按段寫顯存都是基于ASCII碼和字符串的特性實(shí)現(xiàn)。因?yàn)槊總字符都對應(yīng)了一個0~127的7位ASCII碼。而一個字符串,則是由ASCII碼構(gòu)成的,結(jié)尾為\0的數(shù)組,因此我們可以用指針變量來指向這個數(shù)組,并讀取這個字符串?dāng)?shù)組中的數(shù)值,并通過\0來檢測結(jié)束。
void TM1650_Display_Num(unsigned int Num, unsigned char Dot_pos){
    //整數(shù)轉(zhuǎn)字符所用變量
    char Trans_Temp[4];//整數(shù)截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區(qū)
    char *Ptr = TM1650_String_Buffer;//數(shù)組的指針,用于在數(shù)組取值
    unsigned char i = 4; //數(shù)據(jù)保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數(shù)點(diǎn)標(biāo)志
    unsigned char TM1650_Current_Digit = 0;//記錄當(dāng)前寫的數(shù)碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區(qū)域
    //整數(shù)轉(zhuǎn)字符
    //先把整數(shù)拆解成純數(shù)字?jǐn)?shù)組 (倒序存放)
    //比如 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;
    }
    //根據(jù) dot_pos 組裝帶點(diǎn)的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數(shù)字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設(shè)定的點(diǎn)位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數(shù)組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結(jié)尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數(shù)點(diǎn)(TM1650的小數(shù)點(diǎn)與上一個段綁定)
            TM1650_Need_DP = 1;//標(biāo)記需要小數(shù)點(diǎn)
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數(shù)點(diǎn)
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標(biāo)志
            Ptr = Ptr + 1;//下一位存在小數(shù)點(diǎn),直接跳過
            }
        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++;//數(shù)碼管段+1
            Ptr++;//指針+1
        }
}
作者: 千早愛音愛玩51    時間: 2026-2-3 15:00
下面是全部代碼,可以自己手動創(chuàng)建頭文件,復(fù)制進(jìn)去即可。
//本頭文件用于TM1650四位數(shù)碼管芯片
//時間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數(shù)據(jù)命令,
#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[] = {//此表決定了發(fā)送的亮度數(shù)值
    0x00,// 0 關(guān)閉顯示
    0x11,// 1級亮度
    0x21,// 2級亮度
    0x31,// 3級亮度
    0x41,// 4級亮度
    0x51,// 5級亮度
    0x61,// 6級亮度
    0x71,// 7級亮度
    0x01 // 8級亮度
};
//frequency單位為k,brightness取0時關(guān)閉數(shù)碼管輸出
//num為要顯示的數(shù)字,取值0到9999,DP為小數(shù)點(diǎn)位置,取值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
        //標(biāo)點(diǎn)
        case ' ': return SEG_Blank;  // 空格顯示空白
        case '-': return SEG_Minus;  // 0x40
        default: return SEG_Blank;  // 未定義字符顯示空白
    }
}
void I2C_CMD(unsigned char CMD){//I2C命令設(shè)置
    I2CMSCR = (I2CMSCR & 0XF0) | CMD;//只填低四位
    //頭文件中沒有MSIF的定義,因此只能手動做與運(yùn)算
    while(!(I2CMSST & 0X40));//等待I2C控制器執(zhí)行完命令
    I2CMSCR = (I2CMSCR & 0XF0) | I2C_IDLE;//清空I2C命令
    I2CMSST &= ~0X40;//清除完成標(biāo)志(~0X40 = 10111111,做與運(yùn)算,可清零bit6位)
}
bit I2C_Check_ACK(void){//TM1650不需要確認(rèn)ACK
    unsigned char a = 0;
    a = (I2CMSST & 0X02);//讀取I2CMSST B1位是否為1
    return a;//返回1或0
}
void TM1650_Write(unsigned char Data,unsigned char Addr){//底層發(fā)送數(shù)據(jù)驅(qū)動
    //此處不需I2C開始與停止發(fā)送,而是手動添加,可連續(xù)發(fā)送。
    if(Addr != 0){//如果需要在同一地址發(fā)送超過8bit數(shù)據(jù),可令A(yù)ddr=0
        I2C_CMD(I2C_START);
        I2CTXD = Addr;//不需要擔(dān)心時序,每字節(jié)后必須有一個ack,時間足夠
        I2C_CMD(I2C_SENDMS);//發(fā)送命令
        I2C_CMD(I2C_RECV_ACK);//接受ACK
        I2CTXD = Data;//先發(fā)地址,后發(fā)數(shù)據(jù)
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
        I2CTXD = 0;//清空發(fā)送寄存器
        I2C_CMD(I2C_STOP);
    }
    else{//往一個八位地址一直發(fā)數(shù)據(jù),比如寫OLED屏(特殊設(shè)備有16位I2C地址,在這里不做討論)
        I2CTXD = Data;//先發(fā)地址,后發(fā)數(shù)據(jù)
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
    }
}
void TM1650_PinSet(unsigned char SCL,unsigned char SDA){
    P_SW2 = 0X80;
    //僅對STC8H和G系列部分有效
    //本函數(shù)請謹(jǐn)慎使用,務(wù)必先查看技術(shù)手冊中的引腳定義,或者手動規(guī)定引腳
    //對于所有非法值,都不會改變I2C引腳
    if(SCL == 15 && SDA == 14){
        P_SW2 &= ~0X30;//B5,B4置0
        P1M0 |= 0x30; P1M1 |= 0x30; //P15 P14設(shè)置為開漏
    }
    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;//允許訪問擴(kuò)展寄存器
    if(TM1650_Freq_kHz != 0){//傳入0代表不重新初始化I2C
    I2CCFG = 0XE0;//啟用I2C,主機(jī)模式
    MSSPEED = (((Main_FOSC/4)/(TM1650_Freq_kHz * 1000UL)) - 2) & 0X3F;//與運(yùn)算只保留低六位
    I2CCFG = (I2CCFG & 0XC0) | MSSPEED;//寫入低六位
    I2CMSCR = 0X00;//關(guān)閉I2C主機(jī)模式中斷,清空I2C命令
    I2CTXD = 0X00;//清空發(fā)送數(shù)據(jù)寄存器
    I2CRXD = 0X00;//清空接收數(shù)據(jù)寄存器
    I2CMSAUX = 0X00;//關(guān)閉主機(jī)自動發(fā)送功能
    I2CMSST = 0X00;//清零標(biāo)志
    }
    //TM1650_Write(0x49,0x48);
    TM1650_Write(TM1650_CMD2[Brt],TM1650_CMD1);//發(fā)送命令1(0X48),再發(fā)送命令2(亮度)
    //MSBUSY指示I2C控制器是否忙碌,但本驅(qū)動程序不需要檢查MSBUSY
}
//在C語言中,字符串自動轉(zhuǎn)換為由二進(jìn)制碼組成的數(shù)組
//const char *Num 接收的是這個字符串的首地址
//Num為指針(地址),*Num為這個地址讀到的東西
void TM1650_Display_Word(const char *Word){
    bit TM1650_Need_DP = 0;//是否需要小數(shù)點(diǎn)標(biāo)志
    unsigned char TM1650_Current_Digit = 0;//記錄當(dāng)前寫的數(shù)碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存
        while(*Word != '\0' && TM1650_Current_Digit < 4){//字符串未結(jié)尾,且并沒有寫完完整4位
        if(*(Word+1) == '.'){//檢測下一位是否是小數(shù)點(diǎn)(TM1650的小數(shù)點(diǎn)與上一個段綁定)
            TM1650_Need_DP = 1;//標(biāo)記需要小數(shù)點(diǎn)
        }
        TM1650_Write_Buffer = Seg_Table(*Word);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數(shù)點(diǎn)
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標(biāo)志
            Word = Word + 1;//下一位存在小數(shù)點(diǎn),直接跳過
            }
        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++;//數(shù)碼管段+1
            Word++;//指針+1
        }
}
void TM1650_Display_Num(unsigned int Num, unsigned char Dot_pos){
    //整數(shù)轉(zhuǎn)字符所用變量
    char Trans_Temp[4];//整數(shù)截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區(qū)
    char *Ptr = TM1650_String_Buffer;//數(shù)組的指針,用于在數(shù)組取值
    unsigned char i = 4; //數(shù)據(jù)保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數(shù)點(diǎn)標(biāo)志
    unsigned char TM1650_Current_Digit = 0;//記錄當(dāng)前寫的數(shù)碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區(qū)域
    //整數(shù)轉(zhuǎn)字符
    //先把整數(shù)拆解成純數(shù)字?jǐn)?shù)組 (倒序存放)
    //比如 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;
    }
    //根據(jù) dot_pos 組裝帶點(diǎn)的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數(shù)字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設(shè)定的點(diǎn)位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數(shù)組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結(jié)尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數(shù)點(diǎn)(TM1650的小數(shù)點(diǎn)與上一個段綁定)
            TM1650_Need_DP = 1;//標(biāo)記需要小數(shù)點(diǎn)
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數(shù)點(diǎn)
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標(biāo)志
            Ptr = Ptr + 1;//下一位存在小數(shù)點(diǎn),直接跳過
            }
        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++;//數(shù)碼管段+1
            Ptr++;//指針+1
        }
}
#endif
作者: jingling16    時間: 2026-2-3 20:22
非常感謝樓主的無私奉獻(xiàn)
作者: 千早愛音愛玩51    時間: 2026-2-11 20:49
對了,使用本驅(qū)動庫,必須要先使用PinSet函數(shù)初始化引腳,再使用init函數(shù)初始化硬件I2C!,否則單片機(jī)執(zhí)行INIT函數(shù)的時候,因?yàn)橐_沒有初始化,硬件I2C始終沒有返回值,會導(dǎo)致單片機(jī)在這里死循環(huán)。
另外如果是STC8G的話還需要修改頭文件中的include,8h不用改。
然后是庫函數(shù)中pinset函數(shù)有個等號忘記加或運(yùn)算符號了,會導(dǎo)致整個P3被修改,下面是改好的,可以手動替換頭文件中的函數(shù)。
遇到問題,可以先試試自己調(diào)試,頭文件里的代碼不復(fù)雜。
void TM1650_PinSet(unsigned char SCL,unsigned char SDA){
    P_SW2 = 0X80;
    //僅對STC8H和G系列部分有效
    //本函數(shù)請謹(jǐn)慎使用,務(wù)必先查看技術(shù)手冊中的引腳定義,或者手動規(guī)定引腳
    //對于所有非法值,都不會改變I2C引腳
    if(SCL == 15 && SDA == 14){
        P_SW2 &= ~0X30;//B5,B4置0
        P1M0 |= 0x30; P1M1 |= 0x30; //P15 P14設(shè)置為開漏
    }
    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;
    }
}
作者: 千早愛音愛玩51    時間: 2026-2-11 21:00
不能關(guān)閉對應(yīng)引腳的數(shù)字輸入功能,因?yàn)榻邮蹵CK需要檢測引腳電平
作者: 千早愛音愛玩51    時間: 2026-2-13 01:42
在我的這個庫函數(shù)里,i2c驅(qū)動使用的是阻斷式while循環(huán)讀取i2c命令寄存器,檢測是否執(zhí)行完命令的。這導(dǎo)致這個方案也會像軟件i2c一樣造成單片機(jī)死等,不能在發(fā)送i2c的時候執(zhí)行其他任務(wù)。阻斷時間取決于i2c時鐘頻率,因此可以盡量使i2c時鐘頻率高一些。這樣阻斷時間少一些。
另外,我推薦使用中斷方式查詢I2CMSST寄存器,硬件i2c在執(zhí)行完i2c命令后會觸發(fā)中斷,但是因?yàn)槲易龅氖菐欤詾榱税踩鹨娢也]有啟用i2c中斷,但是這個功能也很簡單。如果你用51做了很多東西,cpu性能局促,那么就可以改為中斷方式查詢了,下面是一些參考代碼,中斷服務(wù)函數(shù)是從手冊中截取的。
首先,初始化時設(shè)置I2CMSCR |= 0X80;
另外,必須在主函數(shù)中啟用總中斷 EA = 1;
然后復(fù)制這個代碼到程序中:(來自STC8手冊,做了修改)
void I2C_Isr() interrupt 24  
{
_push_(P_SW2);
P_SW2 |= 0x80;
if (I2CMSST & 0x40)
{
  I2CMSST &= ~0x40; //清中斷標(biāo)志
  //busy = 0;
}  
_pop_(P_SW2);
}

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

#ifndef __TM1650_H__
#define __TM1650_H__
#include "STC8H.H"
//報錯FLAG
bit TM1650_Error = 0;
//顯存地址
#define TM1650_CMD1 0x48//TM1650數(shù)據(jù)命令,
#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[] = {//此表決定了發(fā)送的亮度數(shù)值
    0x00,// 0 關(guān)閉顯示
    0x11,// 1級亮度
    0x21,// 2級亮度
    0x31,// 3級亮度
    0x41,// 4級亮度
    0x51,// 5級亮度
    0x61,// 6級亮度
    0x71,// 7級亮度
    0x01 // 8級亮度
};
//frequency單位為k,brightness取0時關(guān)閉數(shù)碼管輸出
//num為要顯示的數(shù)字,取值0到9999,DP為小數(shù)點(diǎn)位置,取值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
        //標(biāo)點(diǎn)
        case ' ': return SEG_Blank;  // 空格顯示空白
        case '-': return SEG_Minus;  // 0x40
        default: return SEG_Blank;  // 未定義字符顯示空白
    }
}
void I2C_CMD(unsigned char CMD){//I2C命令設(shè)置
    unsigned int Release_Timer = 0;
    I2CMSCR = (I2CMSCR & 0XF0) | CMD;//只填低四位
    //頭文件中沒有MSIF的定義,因此只能手動做與運(yùn)算
    while(!(I2CMSST & 0X40)){
        if(++Release_Timer > 10000) {
            TM1650_Error = 1;break;}}//等待I2C控制器執(zhí)行完命令,超時則退出并報錯
    I2CMSCR = (I2CMSCR & 0XF0) | I2C_IDLE;//清空I2C命令
    I2CMSST &= ~0X40;//清除完成標(biāo)志(~0X40 = 10111111,做與運(yùn)算,可清零bit6位)
}
bit I2C_Check_ACK(void){//TM1650不需要確認(rèn)ACK
    unsigned char a = 0;
    a = (I2CMSST & 0X02);//讀取I2CMSST B1位是否為1
    return a;//返回1或0
}
void TM1650_Write(unsigned char Data,unsigned char Addr){//底層發(fā)送數(shù)據(jù)驅(qū)動
    //此處不需I2C開始與停止發(fā)送,而是手動添加,可連續(xù)發(fā)送。
    if(Addr != 0){//如果需要在同一地址發(fā)送超過8bit數(shù)據(jù),可令A(yù)ddr=0
        I2C_CMD(I2C_START);
        I2CTXD = Addr;//不需要擔(dān)心時序,每字節(jié)后必須有一個ack,時間足夠
        I2C_CMD(I2C_SENDMS);//發(fā)送命令
        I2C_CMD(I2C_RECV_ACK);//接受ACK
        I2CTXD = Data;//先發(fā)地址,后發(fā)數(shù)據(jù)
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
        I2CTXD = 0;//清空發(fā)送寄存器
        I2C_CMD(I2C_STOP);
    }
    else{//往一個八位地址一直發(fā)數(shù)據(jù),比如寫OLED屏(特殊設(shè)備有16位I2C地址,在這里不做討論)
        I2CTXD = Data;//先發(fā)地址,后發(fā)數(shù)據(jù)
        I2C_CMD(I2C_SENDMS);
        I2C_CMD(I2C_RECV_ACK);
    }
}
void TM1650_PinSet(unsigned char SCL,unsigned char SDA){
    P_SW2 = 0X80;
    //僅對STC8H和G系列部分有效
    //本函數(shù)請謹(jǐn)慎使用,務(wù)必先查看技術(shù)手冊中的引腳定義,或者手動規(guī)定引腳
    //對于所有非法值,都不會改變I2C引腳
    if(SCL == 15 && SDA == 14){
        P_SW2 &= ~0X30;//B5,B4置0
        P1M0 |= 0x30; P1M1 |= 0x30; //P15 P14設(shè)置為開漏
    }
    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;//允許訪問擴(kuò)展寄存器
    if(TM1650_Freq_kHz != 0){//傳入0代表不重新初始化I2C
    I2CCFG = 0XE0;//啟用I2C,主機(jī)模式
    MSSPEED = (((Main_FOSC/4)/(TM1650_Freq_kHz * 1000UL)) - 2) & 0X3F;//與運(yùn)算只保留低六位
    I2CCFG = (I2CCFG & 0XC0) | MSSPEED;//寫入低六位
    I2CMSCR = 0X00;//關(guān)閉I2C主機(jī)模式中斷,清空I2C命令
    I2CTXD = 0X00;//清空發(fā)送數(shù)據(jù)寄存器
    I2CRXD = 0X00;//清空接收數(shù)據(jù)寄存器
    I2CMSAUX = 0X00;//關(guān)閉主機(jī)自動發(fā)送功能
    I2CMSST = 0X00;//清零標(biāo)志
    }
    //TM1650_Write(0x49,0x48);
    TM1650_Write(TM1650_CMD2[Brt],TM1650_CMD1);//發(fā)送命令1(0X48),再發(fā)送命令2(亮度)
    //MSBUSY指示I2C控制器是否忙碌,但本驅(qū)動程序不需要檢查MSBUSY
}
//在C語言中,字符串自動轉(zhuǎn)換為由二進(jìn)制碼組成的數(shù)組
//const char *Num 接收的是這個字符串的首地址
//Num為指針(地址),*Num為這個地址讀到的東西
void TM1650_Display_Word(const char *Word){
    bit TM1650_Need_DP = 0;//是否需要小數(shù)點(diǎn)標(biāo)志
    unsigned char TM1650_Current_Digit = 0;//記錄當(dāng)前寫的數(shù)碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存
        while(*Word != '\0' && TM1650_Current_Digit < 4){//字符串未結(jié)尾,且并沒有寫完完整4位
        if(*(Word+1) == '.'){//檢測下一位是否是小數(shù)點(diǎn)(TM1650的小數(shù)點(diǎn)與上一個段綁定)
            TM1650_Need_DP = 1;//標(biāo)記需要小數(shù)點(diǎn)
        }
        TM1650_Write_Buffer = Seg_Table(*Word);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數(shù)點(diǎn)
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標(biāo)志
            Word = Word + 1;//下一位存在小數(shù)點(diǎn),直接跳過
            }
        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++;//數(shù)碼管段+1
            Word++;//指針+1
        }
}
void TM1650_Display_Num(unsigned int Num, unsigned char Dot_pos){
    //整數(shù)轉(zhuǎn)字符所用變量
    char Trans_Temp[4];//整數(shù)截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區(qū)
    char *Ptr = TM1650_String_Buffer;//數(shù)組的指針,用于在數(shù)組取值
    unsigned char i = 4; //數(shù)據(jù)保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數(shù)點(diǎn)標(biāo)志
    unsigned char TM1650_Current_Digit = 0;//記錄當(dāng)前寫的數(shù)碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區(qū)域
    //整數(shù)轉(zhuǎn)字符
    //先把整數(shù)拆解成純數(shù)字?jǐn)?shù)組 (倒序存放)
    //比如 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;
    }
    //根據(jù) dot_pos 組裝帶點(diǎn)的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數(shù)字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設(shè)定的點(diǎn)位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數(shù)組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結(jié)尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數(shù)點(diǎn)(TM1650的小數(shù)點(diǎn)與上一個段綁定)
            TM1650_Need_DP = 1;//標(biāo)記需要小數(shù)點(diǎn)
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數(shù)點(diǎn)
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標(biāo)志
            Ptr = Ptr + 1;//下一位存在小數(shù)點(diǎn),直接跳過
            }
        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++;//數(shù)碼管段+1
            Ptr++;//指針+1
        }
}
#endif
作者: 千早愛音愛玩51    時間: 2026-2-15 08:36
再來個小更新,現(xiàn)在這個Display_Num函數(shù)支持顯示負(fù)數(shù)了,請手動替換
void TM1650_Display_Num(signed int Num, unsigned char Dot_pos){
    //整數(shù)轉(zhuǎn)字符所用變量
    char Trans_Temp[4];//整數(shù)截取低四位的緩存
    char TM1650_String_Buffer[8];//最終輸出的字符串緩存區(qū)
    char *Ptr = TM1650_String_Buffer;//數(shù)組的指針,用于在數(shù)組取值
    unsigned char i = 4; //數(shù)據(jù)保留低四位
    unsigned char j = 0;
    unsigned char k = 0;
    //寫入TM1650所用變量
    bit TM1650_Need_DP = 0;//是否需要小數(shù)點(diǎn)標(biāo)志
    bit TM1650_Is_Negtive = 0;//標(biāo)記為負(fù)數(shù)
    unsigned char TM1650_Current_Digit = 0;//記錄當(dāng)前寫的數(shù)碼管段
    unsigned char TM1650_Write_Buffer = 0;//寫過程的緩存區(qū)域
    //檢測是正數(shù)還是負(fù)數(shù)
    if(Num < 0){
        Num = -Num;//取反
        TM1650_Is_Negtive = 1;//標(biāo)記為負(fù)數(shù)
    }
    //即使輸入為負(fù)數(shù),下面的整數(shù)轉(zhuǎn)字符代碼也不需要修改,寫入時不寫DIG1即可
    //整數(shù)轉(zhuǎn)字符
    //先把整數(shù)拆解成純數(shù)字?jǐn)?shù)組 (倒序存放)
    //比如 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;
    }
    //根據(jù) dot_pos 組裝帶點(diǎn)的字符串
    //從高位往低位填入buffer
    for(k=0; k<4; k++) {
        TM1650_String_Buffer[j++] = Trans_Temp[3-k]; //填入數(shù)字
        if(Dot_pos == (k + 1) && k < 3) { //如果到了設(shè)定的點(diǎn)位
            TM1650_String_Buffer[j++] = '.';
        }
    }
    TM1650_String_Buffer[j] = '\0'; //封口
    //字符數(shù)組讀取與寫入顯存
    while(*Ptr != '\0' && TM1650_Current_Digit < 4){//字符串未結(jié)尾,且并沒有寫完完整4位
        if(*(Ptr+1) == '.'){//檢測下一位是否是小數(shù)點(diǎn)(TM1650的小數(shù)點(diǎn)與上一個段綁定)
            TM1650_Need_DP = 1;//標(biāo)記需要小數(shù)點(diǎn)
        }
        TM1650_Write_Buffer = Seg_Table(*Ptr);//按字符ASCII值查表
        if(TM1650_Need_DP){//如果需要小數(shù)點(diǎn)
            TM1650_Write_Buffer |= 0x80;//最高位置1
            TM1650_Need_DP = 0;//清零標(biāo)志
            Ptr = Ptr + 1;//下一位存在小數(shù)點(diǎn),直接跳過
            }
        if(!TM1650_Is_Negtive){//輸入為正數(shù)
        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{//如果輸入為負(fù)數(shù)
        switch(TM1650_Current_Digit){//寫入
            case 0: TM1650_Write(Seg_Table('-'),TM1650_DIG1); break;//最高位寫入負(fù)數(shù)
            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++;//數(shù)碼管段+1
            Ptr++;//指針+1
        }
}




歡迎光臨 (http://m.raoushi.com/bbs/) Powered by Discuz! X3.1