下面是新版的庫函數驅動版本,總結了之前的一些問題
//本頭文件用于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 |