前言
瑞萨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 | |
2 | 在Pin Selection区域,展开Connectivity:I²C选项,选择I²C0。 |
3 | 在Pin Configuration区域,将Pin Group Selection设置为_A only,Operation Mode设置为Enabled。 |
4 | 勾选SDA0对应的P401引脚和SCL0对应的P400引脚。 |

点击可查看大图
序号 | 操作 |
1 | 在Pin Selection区域,分别选择P400和P401引脚。 |
2 | 将Output type设置为n-ch open drain,把P400和P401配置成开漏输出。 |

点击可查看大图
序号 | 操作 |
1 | 点击界面下方标签栏中的Stacks标签,进入堆栈配置页面。 |
2 | 在HAL/Common Stacks区域,点击New Stack按钮。 |
3 | 在弹出菜单中,选择Connectivity选项。 |
4 | 在Connectivity子菜单中,选择I2C Master(r_iic_master)。 |

点击可查看大图
序号 | 操作 |
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驱动库时看到屏幕地址为0x78,即01111000,是包含读写位的(最低位)。而瑞萨这里是7位地址,不含读写位,因此要将0x78右移1位,即0x3C(0111100)。
确认上面设置没问题后,生成项目代码。
03
编写代码
1
I2C通信相关
新建i2c.c和i2c.h文件。
i2c.h
左右滑动查看完整内容
#ifndefI2C_H_#defineI2C_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_ttimeout= 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
左右滑动查看完整内容
#ifndefBME280_H_#defineBME280_H_#include"hal_data.h"#defineBME280_ID 0x60typedefstruct{ doublehumi, temp, press; boolinitialized;} BME_Struct;
voidBME280_Get_Data(BME_Struct *bme);voidBME280_Init(BME_Struct *bme);voidBME280_Write_then_Read(uint8_t*src,uint8_twrite_bytes,uint8_t*data_dest,uint8_tread_bytes);voidBME280_Trimming_Values();doubleBME280_compensate_T_double(int32_tadc_T);doubleBME280_compensate_P_double(int32_tadc_P);doublebme280_compensate_H_double(int32_tadc_H);
#endif/* BME280_H_ */
bme280.c
#include"bme280.h"#include"hal_data.h"#include"i2c.h"
uint16_tdig_T1;int16_tdig_T2;int16_tdig_T3;uint16_tdig_P1;int16_tdig_P2;int16_tdig_P3;int16_tdig_P4;int16_tdig_P5;int16_tdig_P6;int16_tdig_P7;int16_tdig_P8;int16_tdig_P9;
int8_tdig_H1;int16_tdig_H2;int8_tdig_H3;int16_tdig_H4;int16_tdig_H5;int8_tdig_H6;
voidBME280_Write_then_Read(uint8_t*src,uint8_twrite_bytes,uint8_t*data_dest,uint8_tread_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_treg =0xD0; uint8_twrite_settings[7] = {0x00}; uint8_tread_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_tdata[33] = { 0, }; uint8_treg =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){ doublevar1, 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; returnT;}// Returns pressure in Pa as double. Output value of “96386.2” equals 96386.2 Pa = 963.862 hPadoubleBME280_compensate_P_double(longsignedint adc_P){ doublevar1, 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; returnp;}// Returns humidity in %rH as as double. Output value of “46.332” represents 46.332 % rHdoublebme280_compensate_H_double(longsignedint adc_H){ doublevar_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_tdat[8] = {0}; uint32_tpress_t,temp_t,hum_t=0; uint8_treg =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
滑动查看完整内容
#ifndefOLED_H_#defineOLED_H_
#include"hal_data.h"
#defineOLED_CMD 0 // 写命令#defineOLED_DATA 1// 写数据voidOLED_ClearPoint(uint8_tx,uint8_ty);voidOLED_ColorTurn(uint8_ti);voidOLED_DisplayTurn(uint8_ti);voidOLED_WR_Byte(uint8_tdat,uint8_tmode);voidOLED_DisPlay_On(void);voidOLED_DisPlay_Off(void);voidOLED_Refresh(void);voidOLED_Clear(void);voidOLED_DrawPoint(uint8_tx,uint8_ty,uint8_tt);voidOLED_DrawLine(uint8_tx1,uint8_ty1,uint8_tx2,uint8_ty2,uint8_tmode);voidOLED_DrawCircle(uint8_tx,uint8_ty,uint8_tr);voidOLED_ShowChar(uint8_tx,uint8_ty,uint8_tchr,uint8_tsize1);voidOLED_ShowString(uint8_tx,uint8_ty,uint8_t*chr,uint8_tsize1);voidOLED_ShowNum(uint8_tx,uint8_ty,uint32_tnum,uint8_tlen,uint8_tsize1);voidOLED_ShowChinese(uint8_tx,uint8_ty,uint8_tnum,uint8_tsize1);voidOLED_ScrollDisplay(uint8_tnum,uint8_tspace);voidOLED_ShowPicture(uint8_tx,uint8_ty,uint8_tsizex,uint8_tsizey,uint8_tBMP[],uint8_tmode);voidOLED_Init(void);
#endif
oled.c
滑动查看完整内容
#include"oled.h"#include"oled_font.h"#include"i2c.h"volatileuint8_tOLED_GRAM[144][8];// 反显函数voidOLED_ColorTurn(uint8_ti){ if(i ==0) { OLED_WR_Byte(0xA6, OLED_CMD);// 正常显示 } if(i ==1) { OLED_WR_Byte(0xA7, OLED_CMD);// 反色显示 }}// 屏幕旋转180度voidOLED_DisplayTurn(uint8_ti){ 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_tdat,uint8_tmode){ uint8_tdata[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_ti, 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_tx,uint8_ty,uint8_tr){ inta, 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_tx,uint8_ty,uint8_tchr,uint8_tsize1){ uint8_ti, m, temp, size2, chr1; uint8_tx0 = 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_tx,uint8_ty,uint8_t*chr,uint8_tsize1){ 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_tnum,uint8_tspace){ uint8_ti, 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_tget_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专题还会带来更多玩法:汉字显示、图片动画、菜单界面、多级页面交互等。
如果你在驱动移植、I2C地址、传感器数据补偿上遇到问题,欢迎在评论区交流~
-
传感器
+关注
关注
2578文章
55610浏览量
794422 -
mcu
+关注
关注
147文章
19174浏览量
404932 -
瑞萨
+关注
关注
38文章
22526浏览量
91422 -
OLED屏幕
+关注
关注
3文章
208浏览量
29582
发布评论请先 登录
瑞萨RA2L1 MCU e² studio和FSP的使用指南
RA MCU众测宝典 | I²C读取EEPROM
瑞萨RA MCU众测宝典 | 环境搭建之【RA-Eco-RA6E2-64PIN-V1.0 开发板】介绍、环境搭建、工程测试
【瑞萨RA2L1入门学习】开箱+Keil环境搭建+点灯+点亮OLED
【瑞萨RA2L1入门学习】04、I2C驱动OLED屏幕 BME280传感器
【RA-Eco-RA2E1-V1.0开发板试用】+ OLED显示ADC采样数据
CPK-RA2L1评估板I2C点亮OLED
RA MCU众测宝典 | 环境搭建之【FPB-RA0E2】开发环境搭建
瑞萨RA MCU众测宝典 | OLED之【RA-Eco-RA2L1】I²C驱动OLED屏幕 BME280传感器
评论