1.前言
本次實(shí)驗(yàn)的目標(biāo)是把I2C相關(guān)的搞定,再嘗試驅(qū)動(dòng)SSD1306 0.96寸OLED屏幕以及BME280傳感器,最后將傳感器讀到的數(shù)據(jù)和實(shí)時(shí)時(shí)間顯示在屏幕上。
2,硬件部分
2.1 I2C協(xié)議簡(jiǎn)介
I2C 通訊協(xié)議(Inter-Integrated Circuit)是由Philips公司開發(fā)的,由于它引腳少,硬件實(shí)現(xiàn)簡(jiǎn)單,可擴(kuò)展性強(qiáng), 不需要USART、CAN等通訊協(xié)議的外部收發(fā)設(shè)備,現(xiàn)在被廣泛地使用在系統(tǒng)內(nèi)多個(gè)集成電路(IC)間的通訊。
在計(jì)算機(jī)科學(xué)里,大部分復(fù)雜的問題都可以通過分層來簡(jiǎn)化。如芯片被分為內(nèi)核層和片上外設(shè); 瑞薩的FPS庫(kù)則是在寄存器與用戶代碼之間的軟件層。對(duì)于通訊協(xié)議,我們也以分層的方式來理解, 最基本的是把它分為物理層和協(xié)議層。物理層規(guī)定通訊系統(tǒng)中具有機(jī)械、電子功能部分的特性,確保原始數(shù)據(jù)在物理媒體的傳輸。 協(xié)議層主要規(guī)定通訊邏輯,統(tǒng)一收發(fā)雙方的數(shù)據(jù)打包、解包標(biāo)準(zhǔn)。簡(jiǎn)單來說物理層規(guī)定我們用嘴巴還是用肢體來交流, 協(xié)議層則規(guī)定我們用中文還是英文來交流。
具體的I2C協(xié)議入門可以看TI的A Basic Guide to I2C - Texas Instruments
2.2 OLED屏幕
本次使用的屏幕是0.96寸 4針 I2C協(xié)議 OLED屏幕,其驅(qū)動(dòng)IC為SSD1306,屏幕分辨率為128x64。
編程時(shí)參考的數(shù)據(jù)手冊(cè),以及基于商家給的STM32驅(qū)動(dòng)庫(kù)修改,具體的修改參考軟件部分。
2.3 BME280溫濕度氣壓傳感器
BME280是一款由Bosch Sensortec開發(fā)的多功能環(huán)境傳感器,可同時(shí)精確測(cè)量溫度、濕度和氣壓,具有低功耗和小尺寸的特點(diǎn),廣泛應(yīng)用于氣象監(jiān)測(cè)、室內(nèi)導(dǎo)航、健康監(jiān)測(cè)及物聯(lián)網(wǎng)等領(lǐng)域。
3.軟件部分
將先前03_RTC工程復(fù)制一份,重命名為04_OLED_BME280-I2C。
3.1 配置I2C
首先在e2s內(nèi)配置I2C
序號(hào)
操作
1
點(diǎn)擊界面下方標(biāo)簽欄中的Pins標(biāo)簽,進(jìn)入引腳配置界面。
2
在Pin Selection區(qū)域,展開Connectivity:I2C選項(xiàng),選擇I2C0。
3
在Pin Configuration區(qū)域,將Pin Group Selection設(shè)置為_A only,Operation Mode設(shè)置為Enabled。
4
勾選SDA0對(duì)應(yīng)的P401引腳和SCL0對(duì)應(yīng)的P400引腳。
序號(hào)
操作
1
在Pin Selection區(qū)域,分別選擇P400和P401引腳。
2
將Output type設(shè)置為n-ch open drain,把P400和P401配置成開漏輸出。
序號(hào)
操作
1
點(diǎn)擊界面下方標(biāo)簽欄中的Stacks標(biāo)簽,進(jìn)入堆棧配置頁(yè)面。
2
在HAL/Common Stacks區(qū)域,點(diǎn)擊New Stack按鈕。
3
在彈出菜單中,選擇Connectivity選項(xiàng)。
4
在Connectivity子菜單中,選擇I2C Master (r_iic_master)。
序號(hào)
操作
1
在HAL/Common Stacks區(qū)域,點(diǎn)擊選中g(shù)_i2c_master0 I2C Master (r_iic_master)。
2
在下方Settings設(shè)置區(qū)域的Module g_i2c_master0 I2C Master (r_iic_master)部分,將Rate設(shè)置為Fast-mode。
3
Module g_i2c_master0 I2C Master (r_iic_master)部分,設(shè)置Slave Address為0x3c。
4
Module g_i2c_master0 I2C Master (r_iic_master)部分,設(shè)置Callback為iic_callback,Interrupt Priority Level為Priority 2。
這里說明一下,在移植商家給的STM32 OLED驅(qū)動(dòng)庫(kù)時(shí)看到屏幕地址為0x78,即 01111000,是包含讀寫位的(最低位)。而瑞薩這里是7位地址,不含讀寫位,因此要將0x78右移1位,即0x3C (0111100)。
確認(rèn)上面設(shè)置沒問題后,生成項(xiàng)目代碼。
2 編寫代碼
2.1 I2C通信相關(guān)
新建i2c.c和i2c.h文件。
i2c.h:
::: details 查看代碼
#ifndef I2C_H_
#define I2C_H_
extern volatile bool i2c_rx_complete;
extern volatile bool i2c_tx_complete;
void i2c_wait_rx();
void i2c_wait_tx();
#endif
:::
i2c.c:
::: details 查看代碼
#include \"hal_data.h\"
#include \"i2c.h\"
volatile bool i2c_rx_complete = false;
volatile bool i2c_tx_complete = false;
uint16_t timeout = 0;
void iic_callback(i2c_master_callback_args_t *p_args)
{
if (p_args->event == I2C_MASTER_EVENT_RX_COMPLETE)
{
i2c_rx_complete = true;
}
else if (p_args->event == I2C_MASTER_EVENT_TX_COMPLETE)
{
i2c_tx_complete = true;
}
}
void i2c_wait_tx()
{
timeout = 1000;
while (!i2c_tx_complete && timeout > 0)
{
timeout--;
}
i2c_tx_complete = false;
}
void i2c_wait_rx()
{
timeout = 1000;
while (!i2c_rx_complete && timeout > 0)
{
timeout--;
}
i2c_rx_complete = false;
}
:::
由于瑞薩FSP庫(kù)的高集成度,我只需要編寫代碼實(shí)現(xiàn)回調(diào)函數(shù)iic_callback、等待發(fā)送函數(shù)i2c_wait_tx、等待接收i2c_wait_rx函數(shù)改變標(biāo)志位即可。
3.2.2 BME280操作相關(guān)
bme280.h:
::: details 查看代碼
#ifndef BME280_H_
#define BME280_H_
#include \"hal_data.h\"
#define BME280_ID 0x60
typedef struct
{
double humi, temp, press;
bool initialized;
} BME_Struct;
void BME280_Get_Data(BME_Struct *bme);
void BME280_Init(BME_Struct *bme);
void BME280_Write_then_Read(uint8_t *src, uint8_t write_bytes, uint8_t *data_dest, uint8_t read_bytes);
void BME280_Trimming_Values();
double BME280_compensate_T_double(int32_t adc_T);
double BME280_compensate_P_double(int32_t adc_P);
double bme280_compensate_H_double(int32_t adc_H);
#endif /* BME280_H_ */
:::
bme280.c:
::: details 查看代碼
#include \"bme280.h\"
#include \"hal_data.h\"
#include \"i2c.h\"
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
int8_t dig_H1;
int16_t dig_H2;
int8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
void BME280_Write_then_Read(uint8_t *src, uint8_t write_bytes, uint8_t *data_dest, uint8_t read_bytes)
{
//臨時(shí)設(shè)置I2C從機(jī)地址為0x76
g_i2c_master0.p_api->slaveAddressSet(&g_i2c_master0_ctrl, 0x76, I2C_MASTER_ADDR_MODE_7BIT);
g_i2c_master0.p_api->write(&g_i2c_master0_ctrl, src, write_bytes, true);
i2c_wait_tx();
g_i2c_master0.p_api->read(&g_i2c_master0_ctrl, data_dest, read_bytes, false);
i2c_wait_rx();
g_i2c_master0.p_api->slaveAddressSet(&g_i2c_master0_ctrl, 0x3C, I2C_MASTER_ADDR_MODE_7BIT);
}
void BME280_Init(BME_Struct *bme)
{
uint8_t reg = 0xD0;
uint8_t write_settings[7] = {0x00};
uint8_t read_data;
BME280_Write_then_Read(®, 1, &read_data, 1);
if (read_data != BME280_ID)
{
printf(\"Init BME280 Failed!\\\\n\");
bme->initialized = false;
return;
}
else
{
bme->initialized = true;
}
write_settings[0] = 0xF2; // 設(shè)置濕度采集的寄存器 0xF2
write_settings[1] = 0x05; // 00000 101 濕度 oversampling x16
write_settings[2] = 0xF4; // 設(shè)置溫度采集、氣壓采集、工作模式的寄存器 0xF4
write_settings[3] = 0x93; // 100 100 11 溫度和氣壓 oversampling x8,模式為normal
write_settings[4] = 0xF5; // 配置config寄存器
write_settings[5] = 0x10; // 000 100 0 0 ,配置濾波器系數(shù)為16
g_i2c_master0.p_api->slaveAddressSet(&g_i2c_master0_ctrl, 0x76, I2C_MASTER_ADDR_MODE_7BIT);
g_i2c_master0.p_api->write(&g_i2c_master0_ctrl, &write_settings[0], 6, false);
i2c_wait_tx();
g_i2c_master0.p_api->slaveAddressSet(&g_i2c_master0_ctrl, 0x3C, I2C_MASTER_ADDR_MODE_7BIT);
R_BSP_SoftwareDelay(2, BSP_DELAY_UNITS_MILLISECONDS);
// 校準(zhǔn)數(shù)據(jù)
BME280_Trimming_Values();
}
void BME280_Trimming_Values()
{
uint8_t data[33] = {
0,
};
uint8_t reg = 0x88;
BME280_Write_then_Read(®, 1, &data[0], 24);
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); // 適當(dāng)加延遲否則數(shù)據(jù)錯(cuò)誤
reg = 0xA1;
BME280_Write_then_Read(®, 1, &data[24], 1);
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); // 適當(dāng)加延遲否則數(shù)據(jù)錯(cuò)誤
reg = 0xE1;
BME280_Write_then_Read(®, 1, &data[25], 7);
R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); // 適當(dāng)加延遲否則數(shù)據(jù)錯(cuò)誤
dig_T1 = (data[1] << 8) | data[0];
dig_T2 = (data[3] << 8) | data[2];
dig_T3 = (data[5] << 8) | data[4];
dig_P1 = (data[7] << 8) | data[6];
dig_P2 = (data[9] << 8) | data[8];
dig_P3 = (data[11] << 8) | data[10];
dig_P4 = (data[13] << 8) | data[12];
dig_P5 = (data[15] << 8) | data[14];
dig_P6 = (data[17] << 8) | data[16];
dig_P7 = (data[19] << 8) | data[18];
dig_P8 = (data[21] << 8) | data[20];
dig_P9 = (data[23] << 8) | data[22];
dig_H1 = data[24];
dig_H2 = (data[26] << 8) | data[25];
dig_H3 = data[27];
dig_H4 = (data[28] << 4) | (data[29] & 0x0F);
dig_H5 = (data[30] << 4) | ((data[29] >> 4));
dig_H6 = data[31];
}
// Returns temperature in DegC, double precision. Output value of “51.23” equals 51.23 DegC.
// t_fine carries fine temperature as global value
volatile long signed int t_fine;
double BME280_compensate_T_double(long signed int adc_T)
{
double var1, var2, T;
var1 = (((double)adc_T) / 16384.0 - ((double)dig_T1) / 1024.0) * ((double)dig_T2);
var2 = ((((double)adc_T) / 131072.0 - ((double)dig_T1) / 8192.0) *
(((double)adc_T) / 131072.0 - ((double)dig_T1) / 8192.0)) *
((double)dig_T3);
t_fine = (long signed int)(var1 + var2);
T = (var1 + var2) / 5120.0;
return T;
}
// Returns pressure in Pa as double. Output value of “96386.2” equals 96386.2 Pa = 963.862 hPa
double BME280_compensate_P_double(long signed int adc_P)
{
double var1, var2, p;
var1 = ((double)t_fine / 2.0) - 64000.0;
var2 = var1 * var1 * ((double)dig_P6) / 32768.0;
var2 = var2 + var1 * ((double)dig_P5) * 2.0;
var2 = (var2 / 4.0) + (((double)dig_P4) * 65536.0);
var1 = (((double)dig_P3) * var1 * var1 / 524288.0 + ((double)dig_P2) * var1) / 524288.0;
var1 = (1.0 + var1 / 32768.0) * ((double)dig_P1);
if (var1 == 0.0)
{
return 0; // avoid exception caused by division by zero
}
p = 1048576.0 - (double)adc_P;
p = (p - (var2 / 4096.0)) * 6250.0 / var1;
var1 = ((double)dig_P9) * p * p / 2147483648.0;
var2 = p * ((double)dig_P8) / 32768.0;
p = p + (var1 + var2 + ((double)dig_P7)) / 16.0;
return p;
}
// Returns humidity in %rH as as double. Output value of “46.332” represents 46.332 % rH
double bme280_compensate_H_double(long signed int adc_H)
{
double var_H;
var_H = (((double)t_fine) - 76800.0);
var_H = (adc_H - (((double)dig_H4) * 64.0 + ((double)dig_H5) / 16384.0 *
var_H)) *
(((double)dig_H2) / 65536.0 * (1.0 + ((double)dig_H6) / 67108864.0 * var_H * (1.0 + ((double)dig_H3) / 67108864.0 * var_H)));
var_H = var_H * (1.0 - ((double)dig_H1) * var_H / 524288.0);
if (var_H > 100.0)
var_H = 100.0;
else if (var_H < 0.0)
var_H = 0.0;
return var_H;
}
void BME280_Get_Data(BME_Struct *bme)
{
uint8_t dat[8] = {0};
uint32_t press_t, temp_t, hum_t = 0;
uint8_t reg = 0xF7;
BME280_Write_then_Read(®, 1, &dat[0], 8);
R_BSP_SoftwareDelay(2, BSP_DELAY_UNITS_MILLISECONDS);
press_t = ((((uint32_t)dat[0] << 12) | ((uint32_t)dat[1] << 4)) | ((uint32_t)dat[2] >> 4));
temp_t = ((((uint32_t)dat[3] << 12) | ((uint32_t)dat[4] << 4)) | ((uint32_t)dat[5] >> 4));
hum_t = (((uint32_t)dat[6] << 8) | (uint32_t)dat[7]);
bme->temp = BME280_compensate_T_double(temp_t);
bme->press = BME280_compensate_P_double(press_t) / 100.0;
bme->humi = bme280_compensate_H_double(hum_t);
// printf(\"temp: %.2lf, humid: %.2lf, pressure: %.2lf\\\\n\", bme->temp, bme->humi, bme->press);
}
:::BME280_compensate_T_double、bme280_compensate_H_double、bme280_compensate_P_double這三個(gè)函數(shù)分別為溫度、濕度、氣壓的補(bǔ)償算法函數(shù),借鑒了BME280官方數(shù)據(jù)手冊(cè)內(nèi)給出的參考代碼。
bme280工作流程為
步驟
內(nèi)容
1
上電初始化
2
寫入0xF2、0xF4、0xF5寄存器以設(shè)定過采樣率等參數(shù)
3
獲取校準(zhǔn)數(shù)據(jù)
4
調(diào)用BME280_Get_Data函數(shù),讀取0xF7~0xFE寄存器的數(shù)據(jù)
5
調(diào)用補(bǔ)償算法函數(shù)得到人類可讀的數(shù)值
:::warning 注意
在寫入+讀取函數(shù)后記得跟1~5ms的延時(shí),再進(jìn)行下一步操作,否則會(huì)因?yàn)閎me280側(cè)的數(shù)據(jù)未準(zhǔn)備好,有極大概率讀取到錯(cuò)誤數(shù)據(jù)或讀不到數(shù)據(jù)。
:::
3.2.3 OLED屏幕操作相關(guān)
oled.h:
::: details 查看代碼
#ifndef OLED_H_
#define OLED_H_
#include \"hal_data.h\"
#define OLED_CMD 0// 寫命令
#define OLED_DATA 1 // 寫數(shù)據(jù)
void OLED_ClearPoint(uint8_t x, uint8_t y);
void OLED_ColorTurn(uint8_t i);
void OLED_DisplayTurn(uint8_t i);
void OLED_WR_Byte(uint8_t dat, uint8_t mode);
void OLED_DisPlay_On(void);
void OLED_DisPlay_Off(void);
void OLED_Refresh(void);
void OLED_Clear(void);
void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t t);
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode);
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *chr, uint8_t size1);
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1);
void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t num, uint8_t size1);
void OLED_ScrollDisplay(uint8_t num, uint8_t space);
void OLED_ShowPicture(uint8_t x, uint8_t y, uint8_t sizex, uint8_t sizey, uint8_t BMP[], uint8_t mode);
void OLED_Init(void);
#endif
oled.c:
::: details 查看代碼
#include \"oled.h\"
#include \"oled_font.h\"
#include \"i2c.h\"
volatile uint8_t OLED_GRAM[144][8];
// 反顯函數(shù)
void OLED_ColorTurn(uint8_t i)
{
if (i == 0)
{
OLED_WR_Byte(0xA6, OLED_CMD); // 正常顯示
}
if (i == 1)
{
OLED_WR_Byte(0xA7, OLED_CMD); // 反色顯示
}
}
// 屏幕旋轉(zhuǎn)180度
void OLED_DisplayTurn(uint8_t 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); // 反轉(zhuǎn)顯示
OLED_WR_Byte(0xA0, OLED_CMD);
}
}
// 發(fā)送一個(gè)字節(jié)
// mode:數(shù)據(jù)/命令標(biāo)志 0,表示命令;1,表示數(shù)據(jù);
void OLED_WR_Byte(uint8_t dat, uint8_t mode)
{
uint8_t data[2];
if (mode)
{
data[0] = 0x40;
}
else
{
data[0] = 0x00;
}
data[1] = dat;
R_IIC_MASTER_Write(&g_i2c_master0_ctrl, data, 2, false);
i2c_wait_tx();
}
// 開啟OLED顯示
void OLED_DisPlay_On(void)
{
OLED_WR_Byte(0x8D, OLED_CMD); // 電荷泵使能
OLED_WR_Byte(0x14, OLED_CMD); // 開啟電荷泵
OLED_WR_Byte(0xAF, OLED_CMD); // 點(diǎn)亮屏幕
}
// 關(guān)閉OLED顯示
void OLED_DisPlay_Off(void)
{
OLED_WR_Byte(0x8D, OLED_CMD); // 電荷泵使能
OLED_WR_Byte(0x10, OLED_CMD); // 關(guān)閉電荷泵
OLED_WR_Byte(0xAE, OLED_CMD); // 關(guān)閉屏幕
}
// 更新顯存到OLED
void OLED_Refresh(void)
{
uint8_t i, n;
for (i = 0; i < 8; i++)
{
OLED_WR_Byte(0xb0 + i, OLED_CMD); // 設(shè)置行起始地址
OLED_WR_Byte(0x00, OLED_CMD);// 設(shè)置低列起始地址
OLED_WR_Byte(0x10, OLED_CMD);// 設(shè)置高列起始地址
for (n = 0; n < 128; n++)
{
OLED_WR_Byte(OLED_GRAM[n][i], OLED_DATA);
}
}
}
// 清屏函數(shù)
void OLED_Clear(void)
{
uint8_t i, n;
for (i = 0; i < 8; i++)
{
for (n = 0; n < 128; n++)
{
OLED_GRAM[n][i] = 0; // 清除所有數(shù)據(jù)
}
}
OLED_Refresh(); // 更新顯示
}
// 畫點(diǎn)
// x:0~127
// y:0~63
// t:1 填充 0,清空
void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t t)
{
uint8_t i, m, n;
i = y / 8;
m = y % 8;
n = 1 << m;
if (t)
{
OLED_GRAM[x][i] |= n;
}
else
{
OLED_GRAM[x][i] = ~OLED_GRAM[x][i];
OLED_GRAM[x][i] |= n;
OLED_GRAM[x][i] = ~OLED_GRAM[x][i];
}
}
// 畫線
// x1,y1:起點(diǎn)坐標(biāo)
// x2,y2:結(jié)束坐標(biāo)
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode)
{
uint16_t t;
int xerr = 0, yerr = 0, delta_x, delta_y, distance;
int incx, incy, uRow, uCol;
delta_x = x2 - x1; // 計(jì)算坐標(biāo)增量
delta_y = y2 - y1;
uRow = x1; // 畫線起點(diǎn)坐標(biāo)
uCol = y1;
if (delta_x > 0)
incx = 1; // 設(shè)置單步方向
else if (delta_x == 0)
incx = 0; // 垂直線
else
{
incx = -1;
delta_x = -delta_x;
}
if (delta_y > 0)
incy = 1;
else if (delta_y == 0)
incy = 0; // 水平線
else
{
incy = -1;
delta_y = -delta_x;
}
if (delta_x > delta_y)
distance = delta_x; // 選取基本增量坐標(biāo)軸
else
distance = delta_y;
for (t = 0; t < distance + 1; t++)
{
OLED_DrawPoint(uRow, uCol, mode); // 畫點(diǎn)
xerr += delta_x;
yerr += delta_y;
if (xerr > distance)
{
xerr -= distance;
uRow += incx;
}
if (yerr > distance)
{
yerr -= distance;
uCol += incy;
}
}
//OLED_Refresh();
}
// x,y:圓心坐標(biāo)
// r:圓的半徑
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r)
{
int a, b, num;
a = 0;
b = r;
while (2 * b * b >= r * r)
{
OLED_DrawPoint(x + a, y - b, 1);
OLED_DrawPoint(x - a, y - b, 1);
OLED_DrawPoint(x - a, y + b, 1);
OLED_DrawPoint(x + a, y + b, 1);
OLED_DrawPoint(x + b, y + a, 1);
OLED_DrawPoint(x + b, y - a, 1);
OLED_DrawPoint(x - b, y - a, 1);
OLED_DrawPoint(x - b, y + a, 1);
a++;
num = (a * a + b * b) - r * r; // 計(jì)算畫的點(diǎn)離圓心的距離
if (num > 0)
{
b--;
a--;
}
}
//OLED_Refresh();
}
// 顯示字符 不建議直接使用,若要使用需要加上OLED_Refresh();更新到顯存
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1)
{
uint8_t i, m, temp, size2, chr1;
uint8_t x0 = x, y0 = y;
// 計(jì)算字符的字節(jié)數(shù)
if (size1 == 8)
size2 = 6;
else
size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); // 字體占用的字節(jié)數(shù)
chr1 = chr - \' \'; // 偏移字符值,轉(zhuǎn)換為數(shù)組索引
for (i = 0; i < size2; i++)
{
// 根據(jù)字體大小選擇相應(yīng)的字模
if (size1 == 8)
{
temp = asc2_0806[chr1][i];
}
else if (size1 == 12)
{
temp = asc2_1206[chr1][i];
}
else if (size1 == 16)
{
temp = asc2_1608[chr1][i];
}
else if (size1 == 24)
{
temp = asc2_2412[chr1][i];
}
else
{
return; // 字體不支持
}
for (m = 0; m < 8; m++)
{
if (temp & 0x01)
OLED_GRAM[x][y / 8] |= (1 << (y % 8)); // 設(shè)置顯存中的點(diǎn)
else
OLED_GRAM[x][y / 8] &= ~(1 << (y % 8)); // 清除顯存中的點(diǎn)
temp >>= 1;
y++;
}
x++;
if ((size1 != 8) && ((x - x0) == size1 / 2))
{
x = x0;
y0 = y0 + 8;
}
y = y0;
}
}
// 顯示字符串
// x,y:起點(diǎn)坐標(biāo)
// size1:字體大小
//*chr:字符串起始地址
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *chr, uint8_t size1)
{
while ((*chr >= \' \') && (*chr <= \'~\')) // 判斷是不是非法字符!
{
OLED_ShowChar(x, y, *chr, size1);
if (size1 == 8)
x += 6;
else
x += size1 / 2;
chr++;
}
OLED_Refresh();
}
// m^n
uint32_t OLED_Pow(uint8_t m, uint8_t n)
{
uint32_t result = 1;
while (n--)
{
result *= m;
}
return result;
}
// 顯示數(shù)字
// x,y :起點(diǎn)坐標(biāo)
// num :要顯示的數(shù)字
// len :數(shù)字的位數(shù)
// size:字體大小
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1)
{
uint8_t t, temp, m = 0;
if (size1 == 8)
m = 2;
for (t = 0; t < len; t++)
{
temp = (num / OLED_Pow(10, len - t - 1)) % 10;
if (temp == 0)
{
OLED_ShowChar(x + (size1 / 2 + m) * t, y, \'0\', size1);
}
else
{
OLED_ShowChar(x + (size1 / 2 + m) * t, y, temp + \'0\', size1);
}
}
OLED_Refresh();
}
void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t num, uint8_t size1)
{
uint8_t m, temp;
uint8_t x0 = x, y0 = y;
uint16_t i, size3 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * size1; // 計(jì)算一個(gè)字符對(duì)應(yīng)的字節(jié)數(shù)
uint8_t mask; // 用來構(gòu)造顯示的掩碼
for (i = 0; i < size3; i++)
{
if (size1 == 16)
{
temp = Hzk1[num][i]; // 獲取字形數(shù)據(jù)
}
else
{
return; // 只處理 16x16 字體
}
for (m = 0; m < 8; m++)
{
mask = (1 << (y % 8)); // 根據(jù)當(dāng)前 y 坐標(biāo)計(jì)算掩碼
if (temp & 0x01) // 當(dāng)前位為 1
{
OLED_GRAM[x][y / 8] |= mask; // 設(shè)置該位
}
else // 當(dāng)前位為 0
{
OLED_GRAM[x][y / 8] &= ~mask; // 清除該位
}
temp >>= 1; // 右移,處理下一個(gè)像素
y++;// 縱向位置移動(dòng)
}
x++; // 橫向位置移動(dòng)
// 判斷是否換行
if ((x - x0) == size1)
{
x = x0;
y0 = y0 + 8; // 換行時(shí),y 坐標(biāo)增加 8
}
y = y0; // 恢復(fù) y 坐標(biāo)
}
// 最后刷新整個(gè)顯示屏
OLED_Refresh();
}
// num 顯示漢字的個(gè)數(shù)
// space 每一遍顯示的間隔
void OLED_ScrollDisplay(uint8_t num, uint8_t space)
{
uint8_t i, n, t = 0, m = 0, r;
while (1)
{
if (m == 0)
{
OLED_ShowChinese(128, 24, t, 16); // 寫入一個(gè)漢字保存在OLED_GRAM[][]數(shù)組中
t++;
}
if (t == num)
{
for (r = 0; r < 16 * space; r++) // 顯示間隔
{
for (i = 1; i < 144; i++)
{
for (n = 0; n < 8; n++)
{
OLED_GRAM[i - 1][n] = OLED_GRAM[i][n];
}
}
OLED_Refresh();
}
t = 0;
}
m++;
if (m == 16)
{
m = 0;
}
for (i = 1; i < 144; i++) // 實(shí)現(xiàn)左移
{
for (n = 0; n < 8; n++)
{
OLED_GRAM[i - 1][n] = OLED_GRAM[i][n];
}
}
OLED_Refresh();
}
}
// x,y:起點(diǎn)坐標(biāo)
// sizex,sizey,圖片長(zhǎng)寬
// BMP[]:要寫入的圖片數(shù)組
// mode:0,反色顯示;1,正常顯示
void OLED_ShowPicture(uint8_t x, uint8_t y, uint8_t sizex, uint8_t sizey, uint8_t BMP[], uint8_t mode)
{
uint16_t j = 0;
uint8_t i, n, temp, m;
uint8_t x0 = x, y0 = y;
sizey = sizey / 8 + ((sizey % 8) ? 1 : 0);
for (n = 0; n < sizey; n++)
{
for (i = 0; i < sizex; i++)
{
temp = BMP[j];
j++;
for (m = 0; m < 8; m++)
{
if (temp & 0x01)
OLED_DrawPoint(x, y, mode);
else
OLED_DrawPoint(x, y, !mode);
temp >>= 1;
y++;
}
x++;
if ((x - x0) == sizex)
{
x = x0;
y0 = y0 + 8;
}
y = y0;
}
}
OLED_Refresh();
}
// OLED的初始化
void OLED_Init(void)
{
OLED_WR_Byte(0xAE, OLED_CMD); //--turn off oled panel 關(guān)閉顯示
OLED_WR_Byte(0x00, OLED_CMD); //---set low column address
OLED_WR_Byte(0x10, OLED_CMD); //---set high column address
OLED_WR_Byte(0x40, OLED_CMD); //--set start line addressSet Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81, OLED_CMD); //--set contrast control register
OLED_WR_Byte(0xCF, OLED_CMD); // Set SEG Output Current Brightness
OLED_WR_Byte(0xA1, OLED_CMD); //--Set SEG/Column Mapping0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8, OLED_CMD); // Set COM/Row Scan Direction0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6, OLED_CMD); //--set normal display
OLED_WR_Byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64) 設(shè)置驅(qū)動(dòng)路數(shù)
OLED_WR_Byte(0x3f, OLED_CMD); //--1/64 duty
OLED_WR_Byte(0xD3, OLED_CMD); //-set display offsetShift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00, OLED_CMD); //-not offset
OLED_WR_Byte(0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80, OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9, OLED_CMD); //--set pre-charge period
OLED_WR_Byte(0xF1, OLED_CMD); // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA, OLED_CMD); //--set com pins hardware configuration
OLED_WR_Byte(0x12, OLED_CMD);
OLED_WR_Byte(0xDB, OLED_CMD); //--set vcomh
OLED_WR_Byte(0x30, OLED_CMD); // Set VCOM Deselect Level
OLED_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/disable
OLED_WR_Byte(0x14, OLED_CMD); //--set(0x10) disable
OLED_Clear();
OLED_WR_Byte(0xAF, OLED_CMD);
}
:::tip
最開始OLED屏幕上顯示字的速度非常慢,幾乎是一個(gè)字一個(gè)字地往外蹦。
解決方法是開一個(gè)顯存數(shù)組OLED_GRAM ,將內(nèi)容先緩存到顯存數(shù)組,再調(diào)用OLED_Refresh一次性地寫給OLED屏幕控制器
:::
3.2.4 修改hal_entry.c
在hal_entry.c開頭加入
::: details 查看代碼
#include \"hal_data.h\"
#include \"debug_bsp_uart.h\"
#include \"oled.h\"
#include \"bme280.h\"
#include \"rtc.h\"
#include <stdio.h>
BME_Struct bme = {0, 0, 0, false};
rtc_time_t get_time;
:::
在hal_entry函數(shù)中加入
::: details 查看代碼
Debug_UART9_Init(); // SCI9 UART 調(diào)試串口初始化
g_i2c_master0.p_api->open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
BME280_Init(&bme);
OLED_Init();
RTC_Init();
printf(\"I2C OLED屏幕+BME280獲取溫濕度實(shí)驗(yàn)\\\\n\");
printf(\"若要通過串口設(shè)置時(shí)間,請(qǐng)輸入類似time:20250126080910的字符串\\\\n\");
while (1)
{
uint8_t t1[50] = {0}, t2[50] = {0}, t3[50] = {0}, t4[50] = {0};
if (rtc_flag)
{
g_rtc0.p_api->calendarTimeGet(&g_rtc0_ctrl, &get_time); // 獲取 RTC 計(jì)數(shù)時(shí)間
rtc_flag = 0;
//printf(\"%d年%d月%d日 %d:%d:%d\\\\n\",
// get_time.tm_year + 1900, get_time.tm_mon + 1, get_time.tm_mday,
// get_time.tm_hour, get_time.tm_min, get_time.tm_sec);
sprintf((char *)t1, \"%4d.%02d.%02d\",
get_time.tm_year + 1900, get_time.tm_mon + 1, get_time.tm_mday);
sprintf((char *)t2, \"%02d:%02d:%02d\",
get_time.tm_hour, get_time.tm_min, get_time.tm_sec);
if (bme.initialized)
{
BME280_Get_Data(&bme);
sprintf((char *)t3, \"%.1fC %.1f%%RH\", bme.temp, bme.humi);
sprintf((char *)t4, \"%.1fhPa\", bme.press);
OLED_ShowString(12, 32, t3, 16); // 顯示溫度濕度
OLED_ShowString(24, 48, t4, 16); // 顯示氣壓
}
OLED_ShowString(24, 0, t1, 16);// 顯示年月日
OLED_ShowString(32, 16, t2, 16); // 顯示時(shí)分秒
}
if (uart_rx_complete_flag)
{
char *time;
uart_rx_complete_flag = 0;
// 解析設(shè)置時(shí)間的命令 e.g: time:20250126080910
// warning: 未添加錯(cuò)誤糾正算法,請(qǐng)輸入正確的時(shí)間,否則工作異常!
if (strncmp(rx_data, \"time:\", 5) == 0)
{
time = rx_data + 5;
set_time.tm_year = ((time[0] - \'0\') * 1000) + ((time[1] - \'0\') * 100) +
((time[2] - \'0\') * 10) + (time[3] - \'0\') - 1900;
set_time.tm_mon = ((time[4] - \'0\') * 10) + (time[5] - \'0\') - 1;
set_time.tm_mday = ((time[6] - \'0\') * 10) + (time[7] - \'0\');
set_time.tm_hour = ((time[8] - \'0\') * 10) + (time[9] - \'0\');
set_time.tm_min = ((time[10] - \'0\') * 10) + (time[11] - \'0\');
set_time.tm_sec = ((time[12] - \'0\') * 10) + (time[13] - \'0\');
g_rtc0.p_api->calendarTimeSet(&g_rtc0_ctrl, &set_time);
}
else{
printf(\"若要通過串口設(shè)置時(shí)間,請(qǐng)輸入類似time:20250126080910的字符串\\\\n\");
}
}
}
:::
這段程序?qū)崿F(xiàn)了每1秒刷新一次OLED屏幕上的時(shí)間和溫濕度氣壓數(shù)據(jù),同時(shí)能從串口接收格式化的數(shù)據(jù)以設(shè)定時(shí)間。
4.下載測(cè)試
把編譯好的程序下載到開發(fā)板并復(fù)位。觀察到OLED屏幕上正確顯示了預(yù)設(shè)的時(shí)間和獲取到的溫濕度氣壓值。
可以打開串口助手,在發(fā)送框輸入
time:20250128235958
工程附件
把代碼全貼上來的話超字?jǐn)?shù)限制了,請(qǐng)下載附件查看完整代碼
*附件:04_OLED_BME280-I2C.zip
發(fā)表于 01-29 17:09
評(píng)論