瑞萨RA MCU众测宝典 | OLED之【RA-Eco-RA2L1】I²C驱动OLED屏幕 BME280传感器

描述

 

前言


 

瑞萨RA MCU众测宝典 | 串口之【RA-Eco-RA2L1】RTC日历及串口设置时间

RA MCU众测宝典 | PWM之【RA2L1】呼吸灯

RA MCU众测宝典 | ADC/DAC之【RA2L1】DAC电压输出及ADC电压采集实验

本次实验的目标是把I2C相关的搞定,再尝试驱动SSD1306 0.96寸OLED屏幕以及BME280传感器,最后将传感器读到的数据和实时时间显示在屏幕上。


 

01


 

硬件部分


 

1

I2C协议简介

I2C通讯协议(Inter-Integrated Circuit)由于它引脚少,硬件实现简单,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。


 

在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;瑞萨的FPS库则是在寄存器与用户代码之间的软件层。


 

对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。

 

2

OLED屏幕

本次使用的屏幕是0.96寸4针I2C协议OLED屏幕,其驱动IC为SSD1306,屏幕分辨率为128x64。


编程时参考的数据手册,具体的修改参考软件部分。


 

3

BME280温湿度气压传感器

BME280是一款由Bosch Sensortec开发的多功能环境传感器,可同时精确测量温度、湿度和气压,具有低功耗和小尺寸的特点,广泛应用于气象监测、室内导航、健康监测及物联网等领域。


 

02


 

软件部分


 

将先前03_RTC工程复制一份,重命名为04_OLED_BME280-I2C。


 

1

配置I2C

首先在e2s内配置I2C

序号

操作

1

点击界面下方标签栏中的Pins标签,进入引脚配置界面。

2

在Pin Selection区域,展开Connectivity:I²C选项,选择I²C0。

3

在Pin Configuration区域,将Pin Group Selection设置为_A only,Operation Mode设置为Enabled。

4

勾选SDA0对应的P401引脚和SCL0对应的P400引脚。

OLED屏幕

点击可查看大图


 

序号

操作

1

在Pin Selection区域,分别选择P400和P401引脚。  

2

将Output type设置为n-ch open drain,把P400和P401配置成开漏输出。

OLED屏幕

点击可查看大图


 

序号

操作

1

点击界面下方标签栏中的Stacks标签,进入堆栈配置页面。

2

在HAL/Common Stacks区域,点击New Stack按钮。

3

在弹出菜单中,选择Connectivity选项。

4

在Connectivity子菜单中,选择I2C Master(r_iic_master)。

OLED屏幕

点击可查看大图


 

序号

操作

1

在HAL/Common Stacks区域,点击选中g_i2c_master0 I2C Master (r_iic_master)。

2

在下方Settings设置区域的Module g_i2c_master0 I2C Master (r_iic_master)部分,将Rate设置为Fast-mode。

3

Module g_i2c_master0 I2C Master (r_iic_master)部分,设置Slave Address为0x3c。

4

Module g_i2c_master0 I2C Master (r_iic_master)部分,设置Callback为iic_callback,Interrupt Priority Level为Priority 2。

OLED屏幕

点击可查看大图


 

这里说明一下,在移植OLED驱动库时看到屏幕地址为0x78,即01111000,是包含读写位的(最低位)。而瑞萨这里是7位地址,不含读写位,因此要将0x78右移1位,即0x3C(0111100)。


 

确认上面设置没问题后,生成项目代码。


 

03


 

编写代码


 

1

I2C通信相关

新建i2c.c和i2c.h文件。


 

i2c.h

左右滑动查看完整内容

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

#ifndef I2C_H_#define I2C_H_
externvolatilebool i2c_rx_complete;externvolatilebool i2c_tx_complete;
voidi2c_wait_rx();voidi2c_wait_tx();#endif


 

i2c.c

滑动查看完整内容

#include"hal_data.h"#include"i2c.h"
volatilebool i2c_rx_complete = false;volatilebool i2c_tx_complete = false;uint16_t timeout = 0;
voidiic_callback(i2c_master_callback_args_t *p_args){    if (p_args->event == I2C_MASTER_EVENT_RX_COMPLETE)    {        i2c_rx_complete = true;    }    elseif (p_args->event == I2C_MASTER_EVENT_TX_COMPLETE)    {        i2c_tx_complete = true;    }}
voidi2c_wait_tx(){    timeout = 1000;    while (!i2c_tx_complete && timeout > 0)    {        timeout--;    }    i2c_tx_complete = false;}
voidi2c_wait_rx(){    timeout = 1000;    while (!i2c_rx_complete && timeout > 0)    {        timeout--;    }    i2c_rx_complete = false;}


 

由于瑞萨FSP库的高集成度,我只需要编写代码实现回调函数iic_callback、等待发送函数i2c_wait_tx、等待接收i2c_wait_rx函数改变标志位即可。


 

2

BME280操作相关

bme280.h

左右滑动查看完整内容

 

#ifndef BME280_H_#define BME280_H_#include"hal_data.h"#define BME280_ID 0x60typedefstruct{    double humi, temp, press;    bool initialized;} BME_Struct;
voidBME280_Get_Data(BME_Struct *bme);voidBME280_Init(BME_Struct *bme);voidBME280_Write_then_Read(uint8_t *src, uint8_t write_bytes, uint8_t *data_dest, uint8_t read_bytes);voidBME280_Trimming_Values();doubleBME280_compensate_T_double(int32_t adc_T);doubleBME280_compensate_P_double(int32_t adc_P);doublebme280_compensate_H_double(int32_t adc_H);
#endif/* BME280_H_ */


 

bme280.c

 

#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;
voidBME280_Write_then_Read(uint8_t *src, uint8_t write_bytes, uint8_t *data_dest, uint8_t read_bytes){    //临时设置I2C从机地址为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);}
voidBME280_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; // 设置湿度采集的寄存器 0xF2    write_settings[1] = 0x05; // 00000 101 湿度 oversampling x16    write_settings[2] = 0xF4; // 设置温度采集、气压采集、工作模式的寄存器 0xF4    write_settings[3] = 0x93; // 100 100 11 温度和气压 oversampling x8,模式为normal    write_settings[4] = 0xF5; // 配置config寄存器    write_settings[5] = 0x10; // 000 100 0 0 ,配置滤波器系数为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);    // 校准数据    BME280_Trimming_Values();}
voidBME280_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); // 适当加延迟否则数据错误    reg = 0xA1;    BME280_Write_then_Read(®, 1, &data[24], 1);    R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); // 适当加延迟否则数据错误    reg = 0xE1;    BME280_Write_then_Read(®, 1, &data[25], 7);    R_BSP_SoftwareDelay(5, BSP_DELAY_UNITS_MILLISECONDS); // 适当加延迟否则数据错误
    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 valuevolatilelongsignedint t_fine;doubleBME280_compensate_T_double(longsignedint 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 = (longsignedint)(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 hPadoubleBME280_compensate_P_double(longsignedint 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)    {        return0; // 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 % rHdoublebme280_compensate_H_double(longsignedint 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;    elseif (var_H < 0.0)        var_H = 0.0;    return var_H;}
voidBME280_Get_Data(BME_Struct *bme){    uint8_t dat[8] = {0};    uint32_tpress_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

这三个函数分别为温度、湿度、气压的补偿算法函数,借鉴了BME280官方数据手册内给出的参考代码。


 

bme280工作流程为

步骤

内容

1

上电初始化

2

写入0xF2、0xF4、0xF5寄存器以设定过采样率等参数

3

获取校准数据

4

调用BME280_Get_Data函数,读取0xF7~0xFE寄存器的数据

5

调用补偿算法函数得到人类可读的数值


 

注意

在写入+读取函数后记得跟1~5ms的延时,再进行下一步操作,否则会因为bme280侧的数据未准备好,有极大概率读取到错误数据或读不到数据。


 

3

OLED屏幕操作相关

oled.h

滑动查看完整内容

 

#ifndef OLED_H_#define OLED_H_
#include"hal_data.h"
#define OLED_CMD 0  // 写命令#define OLED_DATA 1 // 写数据voidOLED_ClearPoint(uint8_t x, uint8_t y);voidOLED_ColorTurn(uint8_t i);voidOLED_DisplayTurn(uint8_t i);voidOLED_WR_Byte(uint8_t dat, uint8_t mode);voidOLED_DisPlay_On(void);voidOLED_DisPlay_Off(void);voidOLED_Refresh(void);voidOLED_Clear(void);voidOLED_DrawPoint(uint8_t x, uint8_t y, uint8_t t);voidOLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode);voidOLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r);voidOLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1);voidOLED_ShowString(uint8_t x, uint8_t y, uint8_t *chr, uint8_t size1);voidOLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1);voidOLED_ShowChinese(uint8_t x, uint8_t y, uint8_t num, uint8_t size1);voidOLED_ScrollDisplay(uint8_t num, uint8_t space);voidOLED_ShowPicture(uint8_t x, uint8_t y, uint8_t sizex, uint8_t sizey, uint8_t BMP[], uint8_t mode);voidOLED_Init(void);
#endif


 

oled.c

滑动查看完整内容

  •  

#include"oled.h"#include"oled_font.h"#include"i2c.h"volatileuint8_t OLED_GRAM[144][8];// 反显函数voidOLED_ColorTurn(uint8_t i){    if (i == 0)    {        OLED_WR_Byte(0xA6, OLED_CMD); // 正常显示    }    if (i == 1)    {        OLED_WR_Byte(0xA7, OLED_CMD); // 反色显示    }}// 屏幕旋转180度voidOLED_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); // 反转显示        OLED_WR_Byte(0xA0, OLED_CMD);    }}// 发送一个字节// mode:数据/命令标志 0,表示命令;1,表示数据;voidOLED_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显示voidOLED_DisPlay_On(void){    OLED_WR_Byte(0x8D, OLED_CMD); // 电荷泵使能    OLED_WR_Byte(0x14, OLED_CMD); // 开启电荷泵    OLED_WR_Byte(0xAF, OLED_CMD); // 点亮屏幕}// 关闭OLED显示voidOLED_DisPlay_Off(void){    OLED_WR_Byte(0x8D, OLED_CMD); // 电荷泵使能    OLED_WR_Byte(0x10, OLED_CMD); // 关闭电荷泵    OLED_WR_Byte(0xAE, OLED_CMD); // 关闭屏幕}// 更新显存到OLEDvoidOLED_Refresh(void){    uint8_t i, n;    for (i = 0; i < 8; i++)    {        OLED_WR_Byte(0xb0 + i, OLED_CMD); // 设置行起始地址        OLED_WR_Byte(0x00, OLED_CMD);     // 设置低列起始地址        OLED_WR_Byte(0x10, OLED_CMD);     // 设置高列起始地址        for (n = 0; n < 128; n++)        {            OLED_WR_Byte(OLED_GRAM[n][i], OLED_DATA);        }    }}// 清屏函数voidOLED_Clear(void){    uint8_t i, n;    for (i = 0; i < 8; i++)    {        for (n = 0; n < 128; n++)        {            OLED_GRAM[n][i] = 0; // 清除所有数据        }    }    OLED_Refresh(); // 更新显示}// 画点// x:0~127// y:0~63// t:1 填充 0,清空voidOLED_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:起点坐标// x2,y2:结束坐标voidOLED_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; // 计算坐标增量    delta_y = y2 - y1;    uRow = x1; // 画线起点坐标    uCol = y1;    if (delta_x > 0)        incx = 1; // 设置单步方向    elseif (delta_x == 0)        incx = 0; // 垂直线    else    {        incx = -1;        delta_x = -delta_x;    }    if (delta_y > 0)        incy = 1;    elseif (delta_y == 0)        incy = 0; // 水平线    else    {        incy = -1;        delta_y = -delta_x;    }    if (delta_x > delta_y)        distance = delta_x; // 选取基本增量坐标轴    else        distance = delta_y;    for (t = 0; t < distance + 1; t++)    {        OLED_DrawPoint(uRow, uCol, mode); // 画点        xerr += delta_x;        yerr += delta_y;        if (xerr > distance)        {            xerr -= distance;            uRow += incx;        }        if (yerr > distance)        {            yerr -= distance;            uCol += incy;        }    }    //  OLED_Refresh();}// x,y:圆心坐标// r:圆的半径voidOLED_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; // 计算画的点离圆心的距离        if (num > 0)        {            b--;            a--;        }    }    //    OLED_Refresh();}// 显示字符 不建议直接使用,若要使用需要加上OLED_Refresh();更新到显存voidOLED_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;    // 计算字符的字节数    if (size1 == 8)        size2 = 6;    else        size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); // 字体占用的字节数    chr1 = chr - ' '; // 偏移字符值,转换为数组索引    for (i = 0; i < size2; i++)    {        // 根据字体大小选择相应的字模        if (size1 == 8)        {            temp = asc2_0806[chr1][i];        }        elseif (size1 == 12)        {            temp = asc2_1206[chr1][i];        }        elseif (size1 == 16)        {            temp = asc2_1608[chr1][i];        }        elseif (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)); // 设置显存中的点            else                OLED_GRAM[x][y / 8] &= ~(1 << (y % 8)); // 清除显存中的点            temp >>= 1;            y++;        }        x++;        if ((size1 != 8) && ((x - x0) == size1 / 2))        {            x = x0;            y0 = y0 + 8;        }        y = y0;    }}// 显示字符串// x,y:起点坐标// size1:字体大小//*chr:字符串起始地址voidOLED_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^nuint32_tOLED_Pow(uint8_t m, uint8_t n){    uint32_t result = 1;    while (n--)    {        result *= m;    }    return result;}// 显示数字// x,y :起点坐标// num :要显示的数字// len :数字的位数// size:字体大小voidOLED_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();}voidOLED_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; // 计算一个字符对应的字节数    uint8_t mask;                                                    // 用来构造显示的掩码    for (i = 0; i < size3; i++)    {        if (size1 == 16)        {            temp = Hzk1[num][i]; // 获取字形数据        }        else        {            return; // 只处理 16x16 字体        }        for (m = 0; m < 8; m++)        {            mask = (1 << (y % 8)); // 根据当前 y 坐标计算掩码            if (temp & 0x01) // 当前位为 1            {                OLED_GRAM[x][y / 8] |= mask; // 设置该位            }            else// 当前位为 0            {                OLED_GRAM[x][y / 8] &= ~mask; // 清除该位            }            temp >>= 1; // 右移,处理下一个像素            y++;        // 纵向位置移动        }        x++; // 横向位置移动        // 判断是否换行        if ((x - x0) == size1)        {            x = x0;            y0 = y0 + 8; // 换行时,y 坐标增加 8        }        y = y0; // 恢复 y 坐标    }    // 最后刷新整个显示屏    OLED_Refresh();}// num 显示汉字的个数// space 每一遍显示的间隔voidOLED_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); // 写入一个汉字保存在OLED_GRAM[][]数组中            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++) // 实现左移        {            for (n = 0; n < 8; n++)            {                OLED_GRAM[i - 1][n] = OLED_GRAM[i][n];            }        }        OLED_Refresh();    }}// x,y:起点坐标// sizex,sizey,图片长宽// BMP[]:要写入的图片数组// mode:0,反色显示;1,正常显示voidOLED_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的初始化voidOLED_Init(void){    OLED_WR_Byte(0xAE, OLED_CMD); //--turn off oled panel 关闭显示    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 address  Set 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 Mapping     0xa0左右反置 0xa1正常    OLED_WR_Byte(0xC8, OLED_CMD); // Set COM/Row Scan Direction   0xc0上下反置 0xc8正常    OLED_WR_Byte(0xA6, OLED_CMD); //--set normal display    OLED_WR_Byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64) 设置驱动路数    OLED_WR_Byte(0x3f, OLED_CMD); //--1/64 duty    OLED_WR_Byte(0xD3, OLED_CMD); //-set display offset  Shift 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屏幕上显示字的速度非常慢,几乎是一个字一个字地往外蹦。


 

解决方法是开一个显存数组OLED_GRAM,将内容先缓存到显存数组,再调用OLED_Refresh一次性地写给OLED屏幕控制器。


 

4

修改hal_entry.c

在hal_entry.c开头加入:

左右滑动查看完整内容

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

#include"hal_data.h"#include"debug_bsp_uart.h"#include"oled.h"#include"bme280.h"#include"rtc.h"#include
BME_Struct bme = {0, 0, 0, false};rtc_time_t get_time;


 

在hal_entry函数中加入:
 

滑动查看完整内容

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Debug_UART9_Init(); // SCI9 UART 调试串口初始化    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获取温湿度实验\n");    printf("若要通过串口设置时间,请输入类似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 计数时间            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); // 显示时分秒
        }        if (uart_rx_complete_flag)        {            char *time;            uart_rx_complete_flag = 0;            // 解析设置时间的命令 e.g: time:20250126080910            // warning: 未添加错误纠正算法,请输入正确的时间,否则工作异常!            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("若要通过串口设置时间,请输入类似time:20250126080910的字符串\n");            }        }    }


这段程序实现了每1秒刷新一次OLED屏幕上的时间和温湿度气压数据,同时能从串口接收格式化的数据以设定时间。


 

04


 

下载测试


 

把编译好的程序下载到开发板并复位。观察到OLED屏幕上正确显示了预设的时间和获取到的温湿度气压值。


可以打开串口助手,在发送框输入time:20250128235958。


 

05


 

工程附件


 


 

(可以点击“阅读原文”或扫描下方二维码/复制下方链接到浏览器下载附件查看完整代码)


 

工程附件—OLED

https://bbs.elecfans.com/jishu_2474903_1_1.html

OLED屏幕OLED屏幕

图片演示


 

OLED专题还会带来更多玩法:汉字显示、图片动画、菜单界面、多级页面交互等。


 

如果你在驱动移植、I2C地址、传感器数据补偿上遇到问题,欢迎在评论区交流~


 


 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分