STC8單片機驅動I2C屏幕:實現時間、日期與溫濕度顯示

STC8 單片機驅動 I2C 屏幕:實現時間、日期與溫濕度顯示

在單片機項目中,“數據可視化” 是核心需求之一 —— 將時間、溫濕度等關鍵信息實時顯示在屏幕上,能讓項目更具實用性。本文以STC8 系列單片機為核心,搭配 I2C 接口的 OLED 屏幕、RTC 實時時鐘模塊和溫濕度傳感器,手把手教你實現 “時間 + 日期 + 溫濕度” 的一體化顯示功能,從硬件選型到代碼調試全程覆蓋,新手也能輕松上手。

一、項目核心硬件清單(附選型理由)

STC8 系列單片機無硬件 I2C 外設,需通過軟件模擬 I2C驅動外設,因此選擇 I2C 接口的模塊可減少引腳占用,簡化接線。以下是經過實測的穩定硬件組合:

模塊推薦型號關鍵參數通信方式選型理由
主控單片機STC8A8K64U8051 內核,64KB Flash-性價比高,IO 口充足,支持 5V/3.3V 供電
I2C 顯示屏幕128x64 OLED(SSD1306)0.96 英寸,對比度可調I2C功耗低、顯示清晰,I2C 僅需 2 根線
實時時鐘(RTC)PCF8563精度 ±2ppm,支持掉電走時I2C無需單片機計時,時間穩定性遠超軟件延時
溫濕度傳感器DHT11(入門)/SHT30(進階)DHT11:±2℃精度;SHT30:±0.3℃單總線 / I2CDHT11 接線簡單,SHT30 精度更高
輔助元件4.7kΩ 上拉電阻(2 個)--I2C 總線強制要求,保證通信穩定

二、硬件接線圖(關鍵!避免接錯)

STC8 的 I2C 引腳需自定義(軟件模擬),建議優先選擇 P2 口(電平穩定),接線時需注意 “共地”—— 所有模塊的 GND 必須連接到單片機的 GND,否則會因電平紊亂導致通信失敗。
![在這里插入圖片描述] Alt
在這里插入圖片描述

三、軟件核心邏輯:從驅動到顯示

項目軟件分為 “底層驅動” 和 “上層顯示邏輯” 兩部分,底層驅動是基礎,需先確保各外設能正常通信,再整合數據顯示。以下代碼基于 Keil C51 開發環境編寫,關鍵函數已標注注釋。

1. 第一步:實現通用 I2C 驅動(復用核心)

STC8 無硬件 I2C,需用軟件模擬 I2C 時序(起始、停止、發送、接收),該驅動可同時供 OLED 和 SSD1306使用,避免重復代碼。

代碼文件:I2C.h(頭文件,定義引腳和函數聲明)
#ifndef	__I2C_H
#define	__I2C_H#include	"config.h"//========================================================================
//                               I2C設置
//========================================================================#define		I2C_Function(n)	(n==0?(I2CCFG &= ~0x80):(I2CCFG |= 0x80))	//0:禁止 I2C 功能;1:使能 I2C 功能
#define		I2C_ENABLE()	I2CCFG |= 0x80		/* 使能 I2C 功能 */
#define		I2C_DISABLE()	I2CCFG &= ~0x80		/* 禁止 I2C 功能 */
#define		I2C_Master()	I2CCFG |=  0x40		/* 1: 設為主機	*/
#define		I2C_Slave()		I2CCFG &= ~0x40		/* 0: 設為從機	*/
#define		I2C_SetSpeed(n)	I2CCFG = (I2CCFG & ~0x3f) | (n & 0x3f)	/* 總線速度=Fosc/2/(Speed*2+4) */#define		I2C_WDTA_EN()		I2CMSAUX |= 0x01		/* 使能自動發送 */
#define		I2C_WDTA_DIS()	I2CMSAUX &= ~0x01		/* 禁止自動發送 */#define		I2C_ESTAI_EN(n)		I2CSLCR = (I2CSLCR & ~0x40) | (n << 6)		/* 使能從機接收START信號中斷 */
#define		I2C_ERXI_EN(n)		I2CSLCR = (I2CSLCR & ~0x20) | (n << 5)		/* 使能從機接收1字節數據中斷 */
#define		I2C_ETXI_EN(n)		I2CSLCR = (I2CSLCR & ~0x10) | (n << 4)		/* 使能從機發送1字節數據中斷 */
#define		I2C_ESTOI_EN(n)		I2CSLCR = (I2CSLCR & ~0x08) | (n << 3)		/* 使能從機接收STOP信號中斷 */
#define		I2C_SLRET()				I2CSLCR |= 0x01			/* 復位從機模式 */#define		I2C_Address(n)	I2CSLADR = (I2CSLADR & 0x01) | (n << 1)	/* 從機地址 */
#define		I2C_MATCH_EN()	I2CSLADR &= ~0x01	/* 使能從機地址比較功能,只接受相匹配地址 */
#define		I2C_MATCH_DIS()	I2CSLADR |= 0x01	/* 禁止從機地址比較功能,接受所有設備地址 *///========================================================================
//                              定義聲明
//========================================================================#define DEV_ADDR    0xA0    //從機設備寫地址#define	I2C_BUF_LENTH	8#define		I2C_ESTAI					0x40		/* 從機接收START信號中斷 */
#define		I2C_ERXI					0x20		/* 從機接收1字節數據中斷 */
#define		I2C_ETXI					0x10		/* 從機發送1字節數據中斷 */
#define		I2C_ESTOI					0x08		/* 從機接收STOP信號中斷 */typedef struct
{u8	I2C_Speed;				//總線速度=Fosc/2/(Speed*2+4),      0~63u8	I2C_Enable;				//I2C功能使能,   ENABLE, DISABLEu8	I2C_Mode;					//主從模式選擇,  I2C_Mode_Master,I2C_Mode_Slaveu8	I2C_MS_WDTA;				//主機使能自動發送,  ENABLE, DISABLEu8	I2C_SL_ADR;				//從機設備地址,  0~127u8	I2C_SL_MA;				//從機設備地址比較使能,  ENABLE, DISABLE
} I2C_InitTypeDef;typedef struct
{u8	isma;				//MEMORY ADDRESS 接收判斷標志u8	isda;				//DEVICE ADDRESS 接收判斷標志u8	addr;				//ADDRESS 緩存
} I2C_IsrTypeDef;extern u8 xdata I2C_Buffer[I2C_BUF_LENTH];
extern bit DisplayFlag;void I2C_Init(I2C_InitTypeDef *I2Cx);
void I2C_WriteNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number);
void I2C_ReadNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number);
u8 Get_MSBusy_Status(void);
void SendCmdData(u8 cmd, u8 dat);#endif
代碼文件:I2C.c(驅動實現,關鍵時序)
#include	"I2C.h"u8 xdata I2C_Buffer[I2C_BUF_LENTH];//========================================================================
// 函數: void I2C_Init(I2C_InitTypeDef *I2Cx)
// 描述: I2C初始化程序.
// 參數: I2Cx: 結構參數,請參考I2C.h里的定義.
// 返回: none.
// 版本: V1.0, 2012-11-22
//========================================================================
void I2C_Init(I2C_InitTypeDef *I2Cx)
{if(I2Cx->I2C_Mode == I2C_Mode_Master){I2C_Master();			//設為主機	I2CMSST = 0x00;		//清除I2C主機狀態寄存器I2C_SetSpeed(I2Cx->I2C_Speed);if(I2Cx->I2C_MS_WDTA == ENABLE)		I2C_WDTA_EN();	//使能自動發送else									I2C_WDTA_DIS();	//禁止自動發送}else{I2C_Slave();	//設為從機I2CSLST = 0x00;		//清除I2C從機狀態寄存器I2C_Address(I2Cx->I2C_SL_ADR);if(I2Cx->I2C_SL_MA == ENABLE)		I2C_MATCH_EN();	//從機地址比較功能,只接受相匹配地址else									I2C_MATCH_DIS();	//禁止從機地址比較功能,接受所有設備地址}I2C_Function(I2Cx->I2C_Enable);
}//========================================================================
// 函數: u8	Get_MSBusy_Status (void)
// 描述: 獲取主機忙碌狀態.
// 參數: none.
// 返回: 主機忙碌狀態.
// 版本: V1.0, 2012-11-22
//========================================================================
u8 Get_MSBusy_Status(void)
{return (I2CMSST & 0x80);
}//========================================================================
// 函數: void	Wait (void)
// 描述: 等待主機模式I2C控制器執行完成I2CMSCR.
// 參數: none.
// 返回: none.
// 版本: V1.0, 2012-11-22
//========================================================================
void Wait()
{while (!(I2CMSST & 0x40));I2CMSST &= ~0x40;
}//========================================================================
// 函數: void Start (void)
// 描述: I2C總線起始函數.
// 參數: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void Start()
{I2CMSCR = 0x01;                         //發送START命令Wait();
}//========================================================================
// 函數: void SendData (char dat)
// 描述: I2C發送一個字節數據函數.
// 參數: 發送的數據.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendData(char dat)
{I2CTXD = dat;                           //寫數據到數據緩沖區I2CMSCR = 0x02;                         //發送SEND命令Wait();
}//========================================================================
// 函數: void RecvACK (void)
// 描述: I2C獲取ACK函數.
// 參數: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void RecvACK()
{I2CMSCR = 0x03;                         //發送讀ACK命令Wait();
}//========================================================================
// 函數: char RecvData (void)
// 描述: I2C讀取一個字節數據函數.
// 參數: none.
// 返回: 讀取數據.
// 版本: V1.0, 2020-09-15
//========================================================================
char RecvData()
{I2CMSCR = 0x04;                         //發送RECV命令Wait();return I2CRXD;
}//========================================================================
// 函數: void SendACK (void)
// 描述: I2C發送ACK函數.
// 參數: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendACK()
{I2CMSST = 0x00;                         //設置ACK信號I2CMSCR = 0x05;                         //發送ACK命令Wait();
}//========================================================================
// 函數: void SendNAK (void)
// 描述: I2C發送NAK函數.
// 參數: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendNAK()
{I2CMSST = 0x01;                         //設置NAK信號I2CMSCR = 0x05;                         //發送ACK命令Wait();
}//========================================================================
// 函數: void Stop (void)
// 描述: I2C總線停止函數.
// 參數: none.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void Stop()
{I2CMSCR = 0x06;                         //發送STOP命令Wait();
}//========================================================================
// 函數: void SendCmdData (u8 cmd, u8 dat)
// 描述: I2C發送一個字節數據函數.
// 參數: 命令/數據.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void SendCmdData(u8 cmd, u8 dat)
{I2CTXD = dat;                           //寫數據到數據緩沖區I2CMSCR = cmd;                          //設置命令Wait();
}//========================================================================
// 函數: void I2C_WriteNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number)
// 描述: I2C寫入數據函數.
// 參數: dev_addr: 設備地址, mem_addr: 存儲地址, *p寫入數據存儲位置, number寫入數據個數.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void I2C_WriteNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number)  /*  DeviceAddress,WordAddress,First Data Address,Byte lenth   */
{Start();                                //發送起始命令SendData(dev_addr);                     //發送設備地址+寫命令RecvACK();SendData(mem_addr);                     //發送存儲地址RecvACK();do{SendData(*p++);RecvACK();}while(--number);Stop();                                 //發送停止命令
}//========================================================================
// 函數: void I2C_ReadNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number)
// 描述: I2C讀取數據函數.
// 參數: dev_addr: 設備地址, mem_addr: 存儲地址, *p讀取數據存儲位置, number讀取數據個數.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void I2C_ReadNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number)   /*  DeviceAddress,WordAddress,First Data Address,Byte lenth   */
{Start();                                //發送起始命令SendData(dev_addr);                     //發送設備地址+寫命令RecvACK();SendData(mem_addr);                     //發送存儲地址RecvACK();Start();                                //發送起始命令SendData(dev_addr|1);                   //發送設備地址+讀命令RecvACK();do{*p = RecvData();p++;if(number != 1) SendACK();          //send ACK}while(--number);SendNAK();                              //send no ACK	Stop();                                 //發送停止命令
}

2. 第二步:驅動關鍵外設(OLED+PCF8563+DHT11)

(1)OLED 屏幕驅動(PCF8563.h)

實現屏幕初始化、顯示字符串 / 數字等基礎功能,重點是 “固定顯示區域”(避免全屏刷新導致閃爍)。

代碼文件:PCF8563.h(關鍵函數聲明)
#ifndef __PCF8563_H__
#define __PCF8563_H__#include	"GPIO.h"
#include    "NVIC.h"
#include    "Switch.h"
#include	"I2C.h"// 設備地址
#define		PCF8563_DEV_ADDR		0xa2
// 存儲地址(寄存器地址): 時間(秒)存儲地址
#define		PCF8563_REG_SECOND		0x02// 10進制數轉BCD數:十位取出左移4位 + 個位 (得到BCD數)
#define WRITE_BCD(val) 	((val / 10) << 4) + (val % 10)
// BCD數轉10進制數:將高4位乘以10 + 低四位 (得到10進制數)
#define READ_BCD(val) 	(val >> 4) * 10 + (val & 0x0F) // ======================時間日期
typedef struct {u16 year;u8 month;u8 day;u8 weekday;u8 hour;u8 minute;u8 second;
} Clock_t;// PCF8563初始化
void PCF8563_init();// 設置時間
void PCF8563_set_clock(Clock_t temp);// 獲取時間
void PCF8563_get_clock(Clock_t *temp);//=============================鬧鐘
typedef struct {// 設置分\時\天\周,如果為-1,禁用此項char minute ;char hour ;char day ;char weekday;
} Alarm_t;// 設置鬧鐘
void PCF8563_set_alarm(Alarm_t alarm);
// 啟用鬧鐘
void PCF8563_enable_alarm();
// 禁用鬧鐘Alarm
void PCF8563_disable_alarm();
// 清理鬧鐘標記
void PCF8563_alarm_clear_flag();//=============================定時器
// 國產芯片的HZ1有問題,不要使用,建議使用HZ64
typedef enum { HZ4096 = 0, HZ64 = 1, HZ1 = 2, HZ1_60 = 3} TimerFreq;// 啟動定時器
void PCF8563_enable_timer();
// 禁用定時器
void PCF8563_disable_timer();
// 清除定時器標志位
void PCF8563_clear_timer();     
// 設置定時器,參數1:時鐘頻率 參數2:倒計時計算值,時間為:參數2/參數1
void PCF8563_set_timer(TimerFreq freq, u8 countdown);// 鬧鐘中斷處理函數,需要用戶定義此函數
void PCF8563_on_alarm();
// 定時器中斷處理函數,需要用戶定義此函數
void PCF8563_on_timer();#endif
(2)PCF8563RTC 驅動(獲取時間 / 日期)

PCF8563的時間 / 日期存儲在 0x00-0x06 寄存器中,需通過 I2C 讀寫這些寄存器,注意 “BCD 碼轉換”(寄存器值為 BCD 碼,需轉為十進制才能顯示)。

代碼片段:PCF8563.c(讀取時間核心函數)
#include "PCF8563.h"// GPIO
static void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//結構定義// P32 P33 開漏輸出GPIO_InitStructure.Pin  = GPIO_Pin_2 | GPIO_Pin_3;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_OUT_OD;	//指定IO的輸入或輸出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化// INT3: P37   準雙向口GPIO_InitStructure.Pin  = GPIO_Pin_7;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的輸入或輸出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化}/****************  I2C初始化函數 *****************/
static void	I2C_config(void)
{I2C_InitTypeDef		I2C_InitStructure;I2C_InitStructure.I2C_Mode      = I2C_Mode_Master;	//主從選擇   I2C_Mode_Master, I2C_Mode_SlaveI2C_InitStructure.I2C_Enable    = ENABLE;			//I2C功能使能,   ENABLE, DISABLEI2C_InitStructure.I2C_MS_WDTA   = DISABLE;			//主機使能自動發送,  ENABLE, DISABLEI2C_InitStructure.I2C_Speed     = 13;				//總線速度=Fosc/2/(Speed*2+4),      0~63// 400k, 24M => 13/*總線速度=Fosc/2/(Speed*2+4),      0~63400 k  = =24 M/2/(Speed*2+4)400 000 = 24 000 000 / 2 / (Speed*2+4)*/I2C_Init(&I2C_InitStructure);NVIC_I2C_Init(I2C_Mode_Master,DISABLE,Priority_0);	//主從模式, I2C_Mode_Master, I2C_Mode_Slave; 中斷使能, ENABLE/DISABLE; 優先級(低到高) Priority_0,Priority_1,Priority_2,Priority_3I2C_SW(I2C_P33_P32);					//I2C_P14_P15,I2C_P24_P25,I2C_P33_P32
}
// PCF8563初始化
void PCF8563_init() {// 一定要EAXSFR();		/* 擴展寄存器訪問使能 */GPIO_config();I2C_config();
}
// 設置時間
void PCF8563_set_clock(Clock_t temp) {u8 p[7], C;//              8  4  2  1  8421碼// 7  6  5  4   3  2  1  0  位置,從右往左算// y  y  y  y   x  x  x  x  內容占位// 秒的寄存器地址為: 0x02// 秒p[0]:  第0~3位記錄個位,第4~6位記錄十位p[0] =  WRITE_BCD(temp.second);// 分p[1]: 第0~3位,保存個數,第4到6位,保存十位p[1] =  WRITE_BCD(temp.minute);// 時p[2]:第0~3位,保存個數,第4到5位,保存十位p[2] =  WRITE_BCD(temp.hour);// 日p[3]:第0~3位,保存個數,第4到5位,保存十位p[3] =  WRITE_BCD(temp.day);// 周p[4]:第0~2位,保存個數p[4] =  temp.weekday;// 月_世紀p[5]:  第0~3位記錄個位,第4位記錄十位,第7位為0,世紀數為20xx,為1,世紀數為21xxC = temp.year >= 2100 ? 1 : 0;  // 第7位為0,世紀數為20xx,為1,世紀數為21xxp[5] =   WRITE_BCD(temp.month) + (C << 7);// 年p[6]:第0~3位,保存個數,第4到7位,保存十位// 2024   取出 24 中  2 和 4// p[6] =  (year % 10) + ((year % 100 / 10) << 4);p[6] = WRITE_BCD(temp.year % 100);// 寫// I2C_WriteNbyte(0xa2, 0x02, &p[0], 7);I2C_WriteNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);
}// 獲取時間
void PCF8563_get_clock(Clock_t *temp) {u8 p[7], C;// 讀I2C_ReadNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);//              8  4  2  1  8421碼// 7  6  5  4   3  2  1  0  位置,從右往左算// y  y  y  y   x  x  x  x  內容占位// 秒的寄存器地址為: 0x02// 秒p[0]:  第0~3位記錄個位,第4~6位記錄十位temp->second = READ_BCD(p[0]);// 分p[1]: 第0~3位,保存個數,第4到6位,保存十位temp->minute = READ_BCD(p[1]);// 時p[2]:第0~3位,保存個數,第4到5位,保存十位temp->hour = READ_BCD(p[2]);// 日p[3]:第0~3位,保存個數,第4到5位,保存十位temp->day = READ_BCD(p[3]);// 周p[4]:第0~2位,保存個數temp->weekday = p[4];// 月_世紀p[5] C:  第0~3位記錄個位,第4位記錄十位,第7位為0,世紀數為20xx,為1,世紀數為21xx// 取出最高位C =  p[5] >> 7;// p[5] 第7位置0p[5] &= ~(1 << 7);temp->month = READ_BCD(p[5]);// 年p[6]:第0~3位,保存個數,第4到7位,保存十位temp->year = READ_BCD(p[6]) + (C == 0 ? 2000 : 2100);}
//=============================鬧鐘
// 設置鬧鐘
void PCF8563_set_alarm(Alarm_t alarm) {u8 p[4];// 默認第7位為0,默認啟動的// 分p[0]: 第0~3位,記錄個數, 第4~6位記錄十位, 第7位:置0啟動, 置1禁用if (alarm.minute == -1) {p[0] = (1 << 7); // 禁用    1 << 7 ===> 0x80} else {p[0] =  WRITE_BCD(alarm.minute);}// 時p[1]: 第0~3位,記錄個數, 第4~5位記錄十位, 第7位:置0啟動, 置1禁用p[1] =  alarm.hour == -1 ? 0x80 : WRITE_BCD(alarm.hour);// 日p[2]: 第0~3位,記錄個數, 第4~5位記錄十位, 第7位:置0啟動, 置1禁用p[2] =   alarm.day == -1 ? 0x80 : WRITE_BCD(alarm.day);// 周p[3]: 第0~2位,記錄個數, 第7位:置0啟動, 置1禁用p[3] =  alarm.weekday == -1 ? 0x80 : alarm.weekday ;// 寫數據I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x09, p, 4);}// 啟用鬧鐘
void PCF8563_enable_alarm() {u8 cfg;//===================2.2 鬧鐘開啟 寄存器地址 0x01//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(0xa2, 0x01, &cfg,  1);//b) 在原來配置的基礎上,清除標志位  第3位:置0清除標志位,置1維持不變cfg &= ~(1 << 3);//c) 在原來配置基礎上,啟動鬧鐘,第1位:置0禁用,置1啟動cfg |= (1 << 1);  // 置1啟動//d) 重新寫入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);}// 禁用鬧鐘Alarm
void PCF8563_disable_alarm() {u8 cfg;//===================2.2 鬧鐘開啟 寄存器地址 0x01//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(0xa2, 0x01, &cfg,  1);//b) 在原來配置的基礎上,清除標志位  第3位:置0清除標志位,置1維持不變cfg &= ~(1 << 3);//c) 在原來配置基礎上,啟動鬧鐘,第1位:置0禁用,置1啟動cfg &= ~(1 << 1); // 置0禁用//d) 重新寫入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}
// 清理鬧鐘標記
void PCF8563_alarm_clear_flag() {u8 cfg;//===================寄存器地址 0x01//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg,  1);//b) 在原來配置的基礎上,清除標志位  第3位:置0清除標志位,置1維持不變cfg &= ~(1 << 3);//c) 重新寫入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1); 
}//=============================定時器
// 啟動定時器
void PCF8563_enable_timer() {u8 cfg;//============2 定時器開啟  寄存器地址 0x01//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原來配置的基礎上,清除標志位,第2位:置0清除標志位,置1維持不變cfg &= ~(1 << 2);//c) 在原來配置基礎上,啟動定時器,第0位:置0禁用,置1啟用cfg |= (1 << 0);   // 第0位:置1啟用 //d) 重新寫入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);}// 禁用定時器
void PCF8563_disable_timer() {u8 cfg;//============2 定時器  寄存器地址 0x01//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原來配置的基礎上,清除標志位,第2位:置0清除標志位,置1維持不變cfg &= ~(1 << 2);//c) 在原來配置基礎上,啟動定時器,第0位:置0禁用,置1啟用cfg &= ~(1 << 0);   // 第0位:置0禁用//d) 重新寫入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 清除定時器標志位
void PCF8563_clear_timer() {u8 cfg;//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原來配置的基礎上,清除標志位,第2位:置0清除標志位,置1維持不變cfg &= ~(1 << 2);    //c) 重新寫入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 設置定時器,參數1:時鐘頻率 參數2:倒計時計算值,時間為:參數2/參數1
void PCF8563_set_timer(TimerFreq freq, u8 countdown) {u8 p[2];//============1 定時器設置 寄存器地址 0x0e//a) 時鐘頻率// 0x00: 4.096 khz   0x01: 64 hz  0x02: 1hz(咱們芯片用不了)  0x03: 1/60 hz// 第7位為0,定時器禁用,第七位為1,定時器啟用p[0] = freq + (1 << 7);//b) 計數值(0~255) ===》時間為: 計數值/時鐘頻率p[1] = countdown;// 寫I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x0e, p, 2);
}// INT3 中斷回調函數
void exti_int3_call() {u8 cfg;//寄存器地址 0x01//a) 讀原來的配置(不要亂改配置,只改自己的位,其它維持不變)I2C_ReadNbyte(0xa2, 0x01, &cfg,  1);// 鬧鐘第1位和第3位為1,說明是鬧鐘觸發了中斷if ((cfg & 0x02) && (cfg & 0x08)) {PCF8563_on_alarm(); // 調用// 清除標志位后,鬧鐘,才能重復使用PCF8563_alarm_clear_flag();} if ((cfg & 0x01) && (cfg & 0x04)) {  // 定時器第0位和第2位為1,說明是定時器觸發了中斷PCF8563_on_timer(); // 調用//============定時器清除標志位,才能重復  寄存器地址 0x01PCF8563_clear_timer();}}
(3)DHT11 溫濕度驅動(單總線)

DHT11 對時序要求嚴格,需精確控制 “拉低總線→等待響應→讀取 40 位數據” 的過程,建議用_nop_()微調延時。

代碼片段:DHT11.h(讀取溫濕度)

注意:由于DHT11.c里面有使用printf,所以,main函數一定要配置串口打印

#ifndef __DHT11_H__
#define __DHT11_H__#include "GPIO.h"
#include "delay.h"// 注意:由于DHT11.c里面有使用printf,所以,main函數一定要配置串口打印#define DHT		P46	// DHT11引腳void DHT11_init();
// 返回-1:獲取數據失敗, 返回0:獲取數據成功
char DHT11_get_humidity_temperature(int *humidity, float *temperature);#endif
代碼片段:DHT11.c(讀取溫濕度)
#include "DHT11.h"#define wait_level_change(level, min, max, desc)  { cnt = 0;  \do { cnt++; Delay1us(); } while (DHT == level); \if (cnt < min || cnt > max) { printf("%s err cnt = %d\n", desc, (int)cnt); return -1;}   }// XDATA內存模型 NOP5()  DATA內存模型 NOP10()    
void Delay1us(void) {NOP5();
}// GPIO
static void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//結構定義// P46GPIO_InitStructure.Pin  = GPIO_Pin_6;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的輸入或輸出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P4, &GPIO_InitStructure);//初始化
}void DHT11_init() {GPIO_config();
}// 返回-1:獲取數據失敗, 返回0:獲取數據成功
char DHT11_get_humidity_temperature(int *humidity, float *temperature) {u16 cnt = 0;u8 num = 0;char i, j;u8 dat[5] = {0};// printf("start\n");// 1. 主機(stc8h) 拉低  18ms ~ 30msDHT = 0;delay_ms(20);DHT = 1;// 2. 主機釋放總時間 10us ~ 35uscnt = 0;do {cnt++;Delay1us();} while(DHT == 1 && cnt < 50);if (cnt < 10 || cnt > 35) {printf("%s err cnt = %d\n", "主機釋放總時間", (int)cnt);return -1;}// 3. 響應低電平時間 78us ~ 88uswait_level_change(0, 78, 88, "響應低電平時間");// 4. 響應高電平時間 80us ~ 92uswait_level_change(1, 80, 92, "響應高電平時間");// 5. 收到主機起始信號后,傳感器一次性從數據總線(SDA)串出40位數據,高位先出// 40位數據,1字節8位,需要5個字節for (i = 0; i < 5; i++)  {for (j = 0; j < 8; j++)  {// 5.1 信號低電平時間 50us ~ 58uswait_level_change(0, 45, 58, "信號低電平時間");// 5.2 真正的0、1數據 23us ~74 us// 信號0 高電平時間  23us ~ 27us// 信號1 高電平時間  68us ~ 74us// wait_level_change(1, 23, 74, "真正的數據");cnt = 0;do {Delay1us();cnt++;} while (DHT == 1);dat[i] <<= 1;if (cnt >= 68) {dat[i] |= 0x01;}}}// 6. 數據校驗// 濕度高 8 位    濕度低 8 位        溫度高 8 位    溫度低 8 位    校驗位// dat[0]           dat[1]            dat[2]         dat[3]        dat[4]// 6.1 校驗位=濕度高位+濕度低位+溫度高位+溫度低位if (dat[4] != (dat[0]    +      dat[1]    +       dat[2]    +    dat[3] )) {printf("校驗失敗\n");return - 1;}// 6.2 濕度高位為濕度整數部分數據,濕度低位為濕度小數部分數據,其中濕度小數部分為 0*humidity = (int)dat[0];//printf("濕度: %d %%\n", (int)dat[0]);// 6.3 溫度高位為溫度整數部分數據,溫度低位為溫度小數部分數據(1位小數點)// 且溫度低位 Bit8 (最高位) 為 1 則表示負溫度,否則為正溫度*temperature = dat[2] + (dat[3] & 0x7f) * 0.1;if (dat[3] & 0x80) {*temperature = -(*temperature);}//printf("溫度: %.1f °\n", t);return 0;
}

3. 第三步:主函數整合(數據采集 + 顯示)

主函數邏輯很簡單:初始化外設→循環采集數據→格式化顯示,重點是 “定時刷新”(1 秒刷新一次,避免屏幕閃爍)。

代碼文件:main.c(核心邏輯)
#include    "config.h"
#include    "oled.h"
#include    "bmp.h"
#include    "GPIO.h"
#include    "UART.h"
#include    "NVIC.h"
#include    "Switch.h"
#include    "Delay.h"
#include    "PCF8563.h"
#include	"Exti.h"
#include    "DHT11.h" // 注意:由于DHT11.c里面有使用printf,所以,main函數一定要配置串口打印
// GPIO
void GPIO_config(void) {GPIO_InitTypeDef	GPIO_InitStructure;		//結構定義// UART1: P30   P31   準雙向口GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1;		//指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp;	//指定IO的輸入或輸出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);//初始化// P32 P33 開漏輸出P3_MODE_OUT_OD(GPIO_Pin_2 | GPIO_Pin_3);
}// 串口配置的函數定義
void UART_config(void) {// >>> 記得添加 NVIC.c, UART.c, UART_Isr.c <<<//   結構體類型      變量COMx_InitDefine		a;					//結構定義a.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTxa.UART_BRT_Use   = BRT_Timer1;			//選擇波特率發生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)a.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200a.UART_RxEnable  = ENABLE;				//接收允許,   ENABLE或DISABLEa.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLEUART_Configuration(UART1, &a);		//初始化串口1 UART1,UART2,UART3,UART4NVIC_UART1_Init(ENABLE,Priority_1);		//中斷使能, ENABLE/DISABLE; 優先級(低到高) Priority_0,Priority_1,Priority_2,Priority_3UART1_SW(UART1_SW_P30_P31);		// 引腳選擇, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}/******************** INT配置 ********************/
void	Exti_config(void)
{EXTI_InitTypeDef	Exti_InitStructure;							//結構定義// INT3Exti_InitStructure.EXTI_Mode      = EXT_MODE_RiseFall;//中斷模式,   EXT_MODE_RiseFall,EXT_MODE_FallExt_Inilize(EXT_INT3,&Exti_InitStructure);				//初始化NVIC_INT3_Init(ENABLE,Priority_0);		//中斷使能, ENABLE/DISABLE; 優先級(低到高) Priority_0,Priority_1,Priority_2,Priority_3
}// PCF8563_on_alarm和PCF8563_on_timer一定要定義,除非把函數調用注釋// 鬧鐘中斷處理函數
void PCF8563_on_alarm() {}// 定時器中斷處理函數
void PCF8563_on_timer() {}void main() {Clock_t clk;u8 buf[30];char res;float t;int h;// PCF8563初始化PCF8563_init();// 調用函數GPIO_config();UART_config();Exti_config(); // 中斷DHT11_init();OLED_Init();//初始化OLEDOLED_ColorTurn(0);//0正常顯示,1 反色顯示OLED_DisplayTurn(0);//0正常顯示 1 屏幕翻轉顯示// 串口,要使用到中斷,需要打開開關、EA = 1;//==================================寫時間日期clk.year = 2025;clk.month = 4;clk.day = 21;// 0  星期天  1 星期一   6 星期六clk.weekday = 0; // 星期幾  (0~6范圍)clk.hour = 23;clk.minute = 58;clk.second = 55;PCF8563_set_clock(clk);   // 設置時間while(1) {PCF8563_get_clock(&clk);  // 獲取時間sprintf(buf, "date: %02d-%02d-%02d", (int)clk.year, (int)clk.month, (int)clk.day);OLED_ShowString(0, 0, buf, 16);sprintf(buf, "time: %02d:%02d:%02d", (int)clk.hour, (int)clk.minute, (int)clk.second);OLED_ShowString(0, 2, buf, 16);res = DHT11_get_humidity_temperature(&h, &t);if (res == 0) {sprintf(buf, "temp: %.1f", t);OLED_ShowString(0, 4, buf, 16);sprintf(buf, "humidity: %d", h);OLED_ShowString(0, 6, buf, 16);}delay_ms(250);delay_ms(250);delay_ms(250);delay_ms(250);}
}

在這里插入圖片描述

oled.h 代碼文件時我買i2c屏幕中的贈送的庫函數

#ifndef __OLED_H
#define __OLED_H#include "config.h"		  	 //#define  u8 unsigned char  // 記得注釋,不然,后面串口打印有問題
//#define  u16 unsigned int
//#define  u32 unsigned int#define OLED_CMD  0	//寫命令
#define OLED_DATA 1	//寫數據sbit OLED_SCL=P3^2;//SCL
sbit OLED_SDA=P3^3;//SDA
sbit OLED_RES =P1^2;//RES//-----------------OLED端口定義----------------#define OLED_SCL_Clr() OLED_SCL=0
#define OLED_SCL_Set() OLED_SCL=1#define OLED_SDA_Clr() OLED_SDA=0
#define OLED_SDA_Set() OLED_SDA=1#define OLED_RES_Clr() OLED_RES=0
#define OLED_RES_Set() OLED_RES=1////OLED控制用函數
//void delay_ms(unsigned int ms);
void OLED_ColorTurn(u8 i);
void OLED_DisplayTurn(u8 i);
void OLED_WR_Byte(u8 dat,u8 cmd);
void OLED_Set_Pos(u8 x, u8 y);
void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Clear(void);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 sizey);
u32 oled_pow(u8 m,u8 n);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 sizey);
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 sizey);
void OLED_ShowChinese(u8 x,u8 y,u8 no,u8 sizey);
void OLED_DrawBMP(u8 x,u8 y,u8 sizex, u8 sizey,u8 BMP[]);
void OLED_Init(void);#endif  

在這里插入圖片描述

oled.c 代碼文件時我買i2c屏幕中的贈送的庫函數


#include "oled.h"
#include "oledfont.h" 
#include	"Delay.h"
#include	"I2C.h"//OLED的顯存
//存放格式如下.
//[0]0 1 2 3 ... 127	
//[1]0 1 2 3 ... 127	
//[2]0 1 2 3 ... 127	
//[3]0 1 2 3 ... 127	
//[4]0 1 2 3 ... 127	
//[5]0 1 2 3 ... 127	
//[6]0 1 2 3 ... 127	
//[7]0 1 2 3 ... 127 			   
//void delay_ms(unsigned int ms)
//{                         
//	unsigned int a;
//	while(ms)
//	{
//		a=1800;
//		while(a--);
//		ms--;
//	}
//	return;
//}//反顯函數
void OLED_ColorTurn(u8 i)
{if(i==0){OLED_WR_Byte(0xA6,OLED_CMD);//正常顯示}if(i==1){OLED_WR_Byte(0xA7,OLED_CMD);//反色顯示}
}//屏幕旋轉180度
void OLED_DisplayTurn(u8 i)
{if(i==0){OLED_WR_Byte(0xC8,OLED_CMD);//正常顯示OLED_WR_Byte(0xA1,OLED_CMD);}if(i==1){OLED_WR_Byte(0xC0,OLED_CMD);//反轉顯示OLED_WR_Byte(0xA0,OLED_CMD);}
}//延時
void IIC_delay(void)
{u8 t=1;while(t--);
}//起始信號
void I2C_Start(void)
{OLED_SDA_Set();OLED_SCL_Set();IIC_delay();OLED_SDA_Clr();IIC_delay();OLED_SCL_Clr();}//結束信號
void I2C_Stop(void)
{OLED_SDA_Clr();OLED_SCL_Set();IIC_delay();OLED_SDA_Set();
}//等待信號響應
void I2C_WaitAck(void) //測數據信號的電平
{OLED_SDA_Set();IIC_delay();OLED_SCL_Set();IIC_delay();OLED_SCL_Clr();IIC_delay();
}//寫入一個字節
void Send_Byte(u8 dat)
{u8 i;for(i=0;i<8;i++){OLED_SCL_Clr();//將時鐘信號設置為低電平if(dat&0x80)//將dat的8位從最高位依次寫入{OLED_SDA_Set();}else{OLED_SDA_Clr();}IIC_delay();OLED_SCL_Set();IIC_delay();OLED_SCL_Clr();dat<<=1;}
}//發送一個字節
//向SSD1306寫入一個字節。
//mode:數據/命令標志 0,表示命令;1,表示數據;
void OLED_WR_Byte(u8 dat,u8 mode)
{/*I2C_Start();Send_Byte(0x78);I2C_WaitAck();if(mode){Send_Byte(0x40);}else{Send_Byte(0x00);}I2C_WaitAck();Send_Byte(dat);I2C_WaitAck();I2C_Stop(); */if(mode) { // 0x78  0x40I2C_WriteNbyte(0x78,0x40, &dat, 1);} else { // 0x78  0x00I2C_WriteNbyte(0x78 , 0x00, &dat, 1);}}//坐標設置void OLED_Set_Pos(u8 x, u8 y) 
{ OLED_WR_Byte(0xb0+y,OLED_CMD);OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);OLED_WR_Byte((x&0x0f),OLED_CMD);
}   	  
//開啟OLED顯示    
void OLED_Display_On(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ONOLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//關閉OLED顯示     
void OLED_Display_Off(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFFOLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}		   			 
//清屏函數,清完屏,整個屏幕是黑色的!和沒點亮一樣!!!	  
void OLED_Clear(void)  
{  u8 i,n;		    for(i=0;i<8;i++)  {  OLED_WR_Byte (0xb0+i,OLED_CMD);    //設置頁地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //設置顯示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //設置顯示位置—列高地址   for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); } //更新顯示
}//在指定位置顯示一個字符,包括部分字符
//x:0~127
//y:0~63				 
//sizey:選擇字體 6x8  8x16
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 sizey)
{      	u8 c=0,sizex=sizey/2;u16 i=0,size1;if(sizey==8)size1=6;else size1=(sizey/8+((sizey%8)?1:0))*(sizey/2);c=chr-' ';//得到偏移后的值OLED_Set_Pos(x,y);for(i=0;i<size1;i++){if(i%sizex==0&&sizey!=8) OLED_Set_Pos(x,y++);if(sizey==8) OLED_WR_Byte(asc2_0806[c][i],OLED_DATA);//6X8字號else if(sizey==16) OLED_WR_Byte(asc2_1608[c][i],OLED_DATA);//8x16字號
//		else if(sizey==xx) OLED_WR_Byte(asc2_xxxx[c][i],OLED_DATA);//用戶添加字號else return;}
}
//m^n函數
u32 oled_pow(u8 m,u8 n)
{u32 result=1;	 while(n--)result*=m;    return result;
}				  
//顯示數字
//x,y :起點坐標
//num:要顯示的數字
//len :數字的位數
//sizey:字體大小		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 sizey)
{         	u8 t,temp,m=0;u8 enshow=0;if(sizey==8)m=2;for(t=0;t<len;t++){temp=(num/oled_pow(10,len-t-1))%10;if(enshow==0&&t<(len-1)){if(temp==0){OLED_ShowChar(x+(sizey/2+m)*t,y,' ',sizey);continue;}else enshow=1;}OLED_ShowChar(x+(sizey/2+m)*t,y,temp+'0',sizey);}
}
//顯示一個字符號串
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 sizey)
{u8 j=0;while (chr[j]!='\0'){		OLED_ShowChar(x,y,chr[j++],sizey);if(sizey==8)x+=6;else x+=sizey/2;}
}
//顯示漢字
void OLED_ShowChinese(u8 x,u8 y,u8 no,u8 sizey)
{u16 i,size1=(sizey/8+((sizey%8)?1:0))*sizey;for(i=0;i<size1;i++){if(i%sizey==0) OLED_Set_Pos(x,y++);if(sizey==16) OLED_WR_Byte(Hzk[no][i],OLED_DATA);//16x16字號
//		else if(sizey==xx) OLED_WR_Byte(xxx[c][i],OLED_DATA);//用戶添加字號else return;}				
}//顯示圖片
//x,y顯示坐標
//sizex,sizey,圖片長寬
//BMP:要顯示的圖片
void OLED_DrawBMP(u8 x,u8 y,u8 sizex, u8 sizey,u8 BMP[])
{ 	u16 j=0;u8 i,m;sizey=sizey/8+((sizey%8)?1:0);for(i=0;i<sizey;i++){OLED_Set_Pos(x,i+y);for(m=0;m<sizex;m++){      OLED_WR_Byte(BMP[j++],OLED_DATA);	    	}}
} //初始化				    
void OLED_Init(void)
{OLED_RES_Clr();delay_ms(200);OLED_RES_Set();OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panelOLED_WR_Byte(0x00,OLED_CMD);//---set low column addressOLED_WR_Byte(0x10,OLED_CMD);//---set high column addressOLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control registerOLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current BrightnessOLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常OLED_WR_Byte(0xA6,OLED_CMD);//--set normal displayOLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 dutyOLED_WR_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)OLED_WR_Byte(0x00,OLED_CMD);//-not offsetOLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequencyOLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/SecOLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge periodOLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 ClockOLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configurationOLED_WR_Byte(0x12,OLED_CMD);OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomhOLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect LevelOLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)OLED_WR_Byte(0x02,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disableOLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disableOLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) OLED_Clear();OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/ 
}

bmp.h 是專門存放照片文件的,在買屏幕的時候,官方會有寫的可以參考學習一下,還有畫一個圖案實現在屏幕上。

#ifndef __BMP_H
#define __BMP_H
unsigned char code BMP1[] =
{0x00,0x03,0x05,0x09,0x11,0xFF,0x11,0x89,0x05,0xC3,0x00,0xE0,0x00,0xF0,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x28,0xFF,0x11,0xAA,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x83,0x01,0x38,0x44,0x82,0x92,0x92,0x74,0x01,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x44,0xFF,0x01,0x7D,0x7D,0x7D,0x01,0x7D,0x7D,0x7D,0x7D,0x01,0x7D,0x7D,0x7D,0x7D,0x7D,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x3F,0x03,0x03,0xF3,0x13,0x11,0x11,0x11,0x11,0x11,0x11,0x01,0xF1,0x11,0x61,0x81,0x01,0x01,0x01,0x81,0x61,0x11,0xF1,0x01,0x01,0x01,0x01,0x41,0x41,0xF1,0x01,0x01,0x01,0x01,0x01,0xC1,0x21,0x11,0x11,0x11,0x11,0x21,0xC1,0x01,0x01,0x01,0x01,0x41,0x41,0xF1,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x11,0x11,0x11,0x11,0x11,0xD3,0x33,0x03,0x03,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xE0,0x00,0x00,0x7F,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x7F,0x00,0x00,0x01,0x06,0x18,0x06,0x01,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x40,0x40,0x7F,0x40,0x40,0x00,0x00,0x00,0x1F,0x20,0x40,0x40,0x40,0x40,0x20,0x1F,0x00,0x00,0x00,0x00,0x40,0x40,0x7F,0x40,0x40,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x40,0x30,0x0C,0x03,0x00,0x00,0x00,0x00,0xE0,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x06,0x06,0x06,0x06,0x04,0x04,0x04,0x84,0x44,0x44,0x44,0x84,0x04,0x04,0x84,0x44,0x44,0x44,0x84,0x04,0x04,0x04,0x84,0xC4,0x04,0x04,0x04,0x04,0x84,0x44,0x44,0x44,0x84,0x04,0x04,0x04,0x04,0x04,0x84,0x44,0x44,0x44,0x84,0x04,0x04,0x04,0x04,0x04,0x84,0x44,0x44,0x44,0x84,0x04,0x04,0x84,0x44,0x44,0x44,0x84,0x04,0x04,0x04,0x04,0x06,0x06,0x06,0x06,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x18,0x14,0x12,0x11,0x00,0x00,0x0F,0x10,0x10,0x10,0x0F,0x00,0x00,0x00,0x10,0x1F,0x10,0x00,0x00,0x00,0x08,0x10,0x12,0x12,0x0D,0x00,0x00,0x18,0x00,0x00,0x0D,0x12,0x12,0x12,0x0D,0x00,0x00,0x18,0x00,0x00,0x10,0x18,0x14,0x12,0x11,0x00,0x00,0x10,0x18,0x14,0x12,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x03,0x0C,0x30,0x0C,0x03,0x7F,0x00,0x00,0x38,0x54,0x54,0x58,0x00,0x00,0x7C,0x04,0x04,0x78,0x00,0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xAA,0xAA,0xAA,0x28,0x08,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x03,0x0C,0x30,0x0C,0x03,0x7F,0x00,0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00,0x7F,0x02,0x04,0x08,0x10,0x7F,0x00,/********************************/
};
#endif

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93452.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93452.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93452.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于SpringBoot+Vue的智能消費記賬系統(AI問答、WebSocket即時通訊、Echarts圖形化分析)

&#x1f388;系統亮點&#xff1a;AI問答、WebSocket即時通訊、Echarts圖形化分析&#xff1b;一.系統開發工具與環境搭建1.系統設計開發工具后端使用Java編程語言的Spring boot框架 項目架構&#xff1a;B/S架構 運行環境&#xff1a;win10/win11、jdk17前端&#xff1a; 技術…

[論文筆記] WiscKey: Separating Keys from Values in SSD-Conscious Storage

閱讀 WiscKey 論文時隨手記錄一些筆記。 這篇論文的核心思想理解起來還是很簡單的&#xff0c;但是具體涉及到實現還有一些想不明白的地方&#xff0c;后來看到 TiKV 的 Titan 實現也很有趣&#xff0c;索性把這些問題都記錄下來并拋出來。 本文中和論文相關的內容&#xff0…

week1-[循環嵌套]畫正方形

week1-[循環嵌套]畫正方形 題目描述 輸入一個正整數 nnn&#xff0c;請使用數字 000 到 999 拼成一個這樣的正方形圖案&#xff08;參考樣例輸入輸出&#xff09;&#xff1a;由上至下、由左至右依次由數字 000 到 999 填充。每次使用數字 999 填充后&#xff0c;將從頭使用數字…

在 Vue2 中使用 pdf.js + pdf-lib 實現 PDF 預覽、手寫簽名、文字批注與高保真導出

本文演示如何在前端&#xff08;Vue.js&#xff09;中結合 pdf.js、pdf-lib 與 Canvas 技術實現 PDF 預覽、圖片簽名、手寫批注、文字標注&#xff0c;并導出高保真 PDF。 先上demo截圖&#xff0c;后續會附上代碼倉庫地址&#xff08;目前還有部分問題暫未進行優化&#xff0…

tomcat 定時重啟

tomcat 定時重啟 定時重啟的目的是:修復內存泄漏等問題,tomcat 長時間未重啟,導致頁面卡頓,卡死,無法訪問,影響用戶訪問 1.編寫腳本 su - tomcat [tomcat@u1abomap02 ~]$ ls restart_tomcat_gosi.sh tomcat_gosi.log vi restart_tomcat_gosi.sh #!/bin/bash# 定義日志目…

WinForm 簡單用戶登錄記錄器實現教程

目錄 功能概述 實現思路 一、程序入口&#xff08;Program.cs&#xff09; 二、登錄用戶控件&#xff08;Login.cs&#xff09; 2.1 控件初始化與密碼顯示邏輯 2.2 登錄控件設計器&#xff08;Login.Designer.cs&#xff09; 三、主窗體&#xff08;Form1.cs&#xff09…

docker 安裝 使用

Docker安裝 一鍵安裝命令 sudo curl -fsSL https://get.docker.com| bash -s docker --mirror Aliyun啟動docker sudo service docker startpull鏡像加速配置 sudo vi /etc/docker/daemon.json輸入下列內容&#xff0c;最后按ESC&#xff0c;輸入 :wq! 保存退出。 {"regis…

無人機探測器技術解析

一、工作模式 無人機探測器通過多模式協同實現全流程防御閉環&#xff1a; 1. 主動掃描模式 雷達主動探測&#xff1a;發射電磁波&#xff08;如Ka/Ku波段&#xff09;&#xff0c;通過回波時差與多普勒頻移計算目標距離、速度及航向&#xff0c;適用于廣域掃描&#xff08;…

Linux學習-軟件編程(進程與線程)

進程回收wait原型&#xff1a;pid_t wait(int *wstatus); 功能&#xff1a;回收子進程空間 參數&#xff1a;wstatus&#xff1a;存放子進程結束狀態空間的首地址 返回值&#xff1a;成功返回回收到的子進程的PID失敗返回-1WIFEXITED(wstatus)&#xff1a;測試進程是否正常結束…

大模型微調分布式訓練-大模型壓縮訓練(知識蒸餾)-大模型推理部署(分布式推理與量化部署)-大模型評估測試(OpenCompass)

大模型微調分布式訓練 LLama Factory與Xtuner分布式微調大模型 大模型分布式微調訓練的基本概念 為什么需要分布式訓練&#xff1f; 模型規模爆炸&#xff1a;現代大模型&#xff08;如GPT-3、LLaMA等&#xff09;參數量達千億級別&#xff0c;單卡GPU無法存儲完整模型。 …

物聯網、大數據與云計算持續發展,樓宇自控系統應用日益廣泛

在深圳某智慧園區的控制中心&#xff0c;管理人員通過云端平臺實時監控著5公里外園區內每臺空調的運行參數、每盞路燈的開關狀態和每個區域的能耗數據。當系統檢測到某棟樓宇的電梯運行振動異常時&#xff0c;大數據算法自動預判可能的故障點并推送維修建議&#xff1b;物聯網傳…

在實驗室連接地下車庫工控機及其數據采集設備

在實驗室連接地下車庫工控機及其數據采集設備 我們小組為項目的數據采集組&#xff0c;目前在車頂集成了一個工控機、兩個激光雷達、兩個攝像頭、一個戶外電源 由于地下車庫蚊子太多了&#xff0c;我們可受不了這個苦&#xff0c;所以想坐在實驗室吹著空調就能連接工控機來修改…

icmpsh、PingTunnel--安裝、使用

用途限制聲明&#xff0c;本文僅用于網絡安全技術研究、教育與知識分享。文中涉及的滲透測試方法與工具&#xff0c;嚴禁用于未經授權的網絡攻擊、數據竊取或任何違法活動。任何因不當使用本文內容導致的法律后果&#xff0c;作者及發布平臺不承擔任何責任。滲透測試涉及復雜技…

系統思考:情緒內耗與思維模式

我們正在努力解決的問題&#xff0c;很多時候&#xff0c;根源就在我們自己。 在日常的工作和生活中&#xff0c;我們常常感到焦慮、內耗和失控。這些情緒和狀態&#xff0c;似乎總是在不斷循環。但如果停下來仔細思考&#xff0c;會發現&#xff0c;問題的背后&#xff0c;并不…

詳解grafana k6 中stage的核心概念與作用

在Grafana k6中&#xff0c;??Stage&#xff08;階段&#xff09;?? 是負載測試腳本的核心配置概念&#xff0c;用于動態控制虛擬用戶&#xff08;VUs&#xff09;的數量隨時間的變化。通過定義多個階段&#xff0c;用戶可以模擬真實場景中的流量波動&#xff08;如用戶逐步…

JS 和 JSX 的區別

JS 和 JSX 是兩種不同的概念&#xff0c;盡管它們都與 JavaScript 密切相關&#xff0c;尤其是在 React 開發中。以下是它們的主要區別&#xff1a;1. 定義JS (JavaScript): 一種通用的編程語言&#xff0c;用于開發動態網頁、服務器端應用程序等。它是標準的 ECMAScript 語言。…

Linux軟件編程-進程(2)及線程(1)

1.進程回收資源空間&#xff08;1&#xff09;wait函數頭文件&#xff1a;#include <sys/types.h>#include <sys/wait.h>函數接口&#xff1a;pid_t wait(int *wstatus);功能&#xff1a;阻塞等待回收子進程的資源空間參數&#xff1a;wstatus &#xff1a;保存子進…

java 集合 之 集合工具類Collections

前言早期開發者經常需要對集合進行各種操作比如排序、查找最大最小值等等但是當時沒有統一的工具類來處理所以導致代碼重復且容易出錯java.util.Collections 工具類的引入為開發者提供了大量 靜態方法 來操作集合它就像一個經驗豐富的助手和數組工具類 Arrays 一樣避免了我們重…

2025 年電賽 C 題 發揮部分 1:多正方形 / 重疊正方形高精度識別與最小邊長測量

2025 年全國大學生電子設計競賽 C 題 發揮部分 1&#xff1a;多正方形 / 重疊正方形高精度識別與最小邊長測量 香橙派 OpenCV C 全流程解析 目錄 賽題背景與需求技術難點全景圖系統總體架構硬件平臺與接線軟件架構與線程模型算法流水線逐幀拆解 6.1 圖像預處理6.2 輪廓提取與…

【自動駕駛】自動駕駛概述 ② ( 自動駕駛技術路徑 | L0 ~ L5 級別自動駕駛 )

文章目錄一、自動駕駛技術路徑1、L0 級別 自動駕駛2、L1 級別 自動駕駛3、L2 級別 自動駕駛4、L3 級別 自動駕駛5、L4 級別 自動駕駛6、L5 級別 自動駕駛一、自動駕駛技術路徑 美國汽車工程師學會 ( SAE ) 將 自動駕駛 分為 L0 ~ L5 六個級別 : 其中 L0 級別 是 完全手動 , L5…